Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd23f7ab7d | ||
|
|
518dceac72 | ||
|
|
19f43e30c8 | ||
|
|
b3be9f33c6 | ||
|
|
430098856a | ||
|
|
5932f5f94f | ||
|
|
fcf2e32071 | ||
|
|
19827dac70 | ||
|
|
3ed1c6644e | ||
|
|
cf6e714946 | ||
|
|
383f43548b | ||
|
|
26b1c67964 | ||
|
|
7e065dfe12 | ||
|
|
8429da02b8 | ||
|
|
ab51f5d39f | ||
|
|
3ee519c7b0 | ||
|
|
c9b86b7815 | ||
|
|
9b6d8f629a | ||
|
|
6a2f43858a | ||
|
|
601ea32a1c |
@@ -220,6 +220,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -346,6 +391,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -472,6 +562,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -598,6 +733,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -724,6 +904,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -850,6 +1075,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -976,6 +1246,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1102,6 +1417,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1228,6 +1588,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1354,6 +1759,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1480,6 +1930,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1606,6 +2101,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1732,6 +2272,51 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enabled",
|
||||
"disabled"
|
||||
]
|
||||
},
|
||||
"budgetTokens": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
]
|
||||
},
|
||||
"reasoningEffort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"xhigh"
|
||||
]
|
||||
},
|
||||
"textVerbosity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
"medium",
|
||||
"high"
|
||||
]
|
||||
},
|
||||
"providerOptions": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
bun.lock
28
bun.lock
@@ -27,13 +27,13 @@
|
||||
"typescript": "^5.7.3",
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"oh-my-opencode-darwin-arm64": "3.0.1",
|
||||
"oh-my-opencode-darwin-x64": "3.0.1",
|
||||
"oh-my-opencode-linux-arm64": "3.0.1",
|
||||
"oh-my-opencode-linux-arm64-musl": "3.0.1",
|
||||
"oh-my-opencode-linux-x64": "3.0.1",
|
||||
"oh-my-opencode-linux-x64-musl": "3.0.1",
|
||||
"oh-my-opencode-windows-x64": "3.0.1",
|
||||
"oh-my-opencode-darwin-arm64": "3.1.0",
|
||||
"oh-my-opencode-darwin-x64": "3.1.0",
|
||||
"oh-my-opencode-linux-arm64": "3.1.0",
|
||||
"oh-my-opencode-linux-arm64-musl": "3.1.0",
|
||||
"oh-my-opencode-linux-x64": "3.1.0",
|
||||
"oh-my-opencode-linux-x64-musl": "3.1.0",
|
||||
"oh-my-opencode-windows-x64": "3.1.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -225,19 +225,19 @@
|
||||
|
||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||
|
||||
"oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.0.1", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-LRcLVi6DsmGh3ICFeN4yVJ0KinvCM5jotd2z7tZQ74n0sziHO7grjK1CmJaPV9eCv0clatoK5xfFCeEJ3FvXYg=="],
|
||||
"oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.1.0", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-8j7XI+n1bz7xIg35Zpjqp1AqoIoFWuVZdYyI9vTAZ0b6ta/mIlNOWPLAbFyEHfKelA9g3Xa+4sYnKPSxU5dQoA=="],
|
||||
|
||||
"oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.0.1", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-ZaC0ZBe5M2f2aMncNsAMu9IZ3MjSPfNVcfUTCgJkp03db8lLPsajgjeG3556Er72hxignDPsEbrLkJBNlsDbAA=="],
|
||||
"oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.1.0", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-Kd/3KpnF07cw+qBAyLwA0y8tp3S0X8b8HWH55WGlVp6m4gvQ432kKgDum/jat1vqP/3J8hm4P/sly5ibY5gMqw=="],
|
||||
|
||||
"oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.0.1", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-pcOvV6Y2GSwKr0exDndeB2BtFt297XhJFQgrq1cbeEJawoRONDRp7LNSpjwILSQpQ7YkkYnO2bIczBmxI5llNA=="],
|
||||
"oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.1.0", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-qy/QohHGM6eSQjHVEgibsDauUvlAgYPw5xrQqa9cVLo1hL4KMIhb+i4wGAxCK2p84rG2bfC2m8+IfZUxhhwcTg=="],
|
||||
|
||||
"oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.0.1", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-7kXKaVbgFnOMSaw+j4JbZNs7O7mkvCekcfWPwh/9I/0WD21/n4PbAGl01ePhRoQh+u9MC6t8FH046hEjL2sk1g=="],
|
||||
"oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.1.0", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-HIO7zj3M5QAYOfgvFM7Djeuen9kdZD4RA51wzXcXiPj1FPAuBNAW9N7lTEGYBSgObgwX+vXnC3HwLSF7nqkw8w=="],
|
||||
|
||||
"oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.0.1", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-1BOV1EnKa5BErhZmWiddnbriHwm1KFrPr+0BUCDdFX/d/hrMAJTo1733zaEnvKuXzvrdHSp/VznXheeUI1VjkA=="],
|
||||
"oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.1.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-zcKaibnEhvbReiTsqbg+dog/Z3pnBx4v6R3AR5nVhGBO27hRSAXgA/fviYyE5bWD591WB7Pqwduf0t854ilKjw=="],
|
||||
|
||||
"oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.0.1", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-ASyTVatvU1nNJ0mk9o+A/GjybT5vOdgU172ystzCsnQ+12Mnv68GgaeMu/UFJgJNaZmKdhyUAP9XhnOKvEDBGQ=="],
|
||||
"oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.1.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-xmtHEyAhY93Djg5qEauvMqSF0x3tf8pzOGdKB6CuZmhCG69fZXk/dEwPrO0vKbOeGMV/T4K6HAg1+8Ue1N1ZaQ=="],
|
||||
|
||||
"oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.0.1", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-QIuA564mVpwzCprhhAoyd8TSw0Rt2VM6M9y7H0fOoC/UjXuU+d7wIuUNuqUUMVaUnMedkctTZop0X0i2Q+Bvhg=="],
|
||||
"oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.1.0", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-pDgHd0mGWWVsiO0fT8C7bi6CziOXU38g+k2dWlGm1YXCMzyrrWZZCF7oIp+EzJB02saSCF/oJ2f1/uj/VPeLMA=="],
|
||||
|
||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||
|
||||
|
||||
@@ -219,6 +219,183 @@ agent-browser screenshot result.png
|
||||
agent-browser close
|
||||
```
|
||||
|
||||
## Tmux Integration
|
||||
|
||||
Run background subagents in separate tmux panes for **visual multi-agent execution**. See your agents working in parallel, each in their own terminal pane.
|
||||
|
||||
**Enable tmux integration** via `tmux` in `oh-my-opencode.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"tmux": {
|
||||
"enabled": true,
|
||||
"layout": "main-vertical",
|
||||
"main_pane_size": 60,
|
||||
"main_pane_min_width": 120,
|
||||
"agent_pane_min_width": 40
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `enabled` | `false` | Enable tmux subagent pane spawning. Only works when running inside an existing tmux session. |
|
||||
| `layout` | `main-vertical` | Tmux layout for agent panes. See [Layout Options](#layout-options) below. |
|
||||
| `main_pane_size` | `60` | Main pane size as percentage (20-80). |
|
||||
| `main_pane_min_width` | `120` | Minimum width for main pane in columns. |
|
||||
| `agent_pane_min_width` | `40` | Minimum width for each agent pane in columns. |
|
||||
|
||||
### Layout Options
|
||||
|
||||
| Layout | Description |
|
||||
|--------|-------------|
|
||||
| `main-vertical` | Main pane left, agent panes stacked on right (default) |
|
||||
| `main-horizontal` | Main pane top, agent panes stacked bottom |
|
||||
| `tiled` | All panes in equal-sized grid |
|
||||
| `even-horizontal` | All panes in horizontal row |
|
||||
| `even-vertical` | All panes in vertical stack |
|
||||
|
||||
### Requirements
|
||||
|
||||
1. **Must run inside tmux**: The feature only activates when OpenCode is already running inside a tmux session
|
||||
2. **Tmux installed**: Requires tmux to be available in PATH
|
||||
3. **Server mode**: OpenCode must run with `--port` flag to enable subagent pane spawning
|
||||
|
||||
### How It Works
|
||||
|
||||
When `tmux.enabled` is `true` and you're inside a tmux session:
|
||||
- Background agents (via `delegate_task(run_in_background=true)`) spawn in new tmux panes
|
||||
- Each pane shows the subagent's real-time output
|
||||
- Panes are automatically closed when the subagent completes
|
||||
- Layout is automatically adjusted based on your configuration
|
||||
|
||||
### Running OpenCode with Tmux Subagent Support
|
||||
|
||||
To enable tmux subagent panes, OpenCode must run in **server mode** with the `--port` flag. This starts an HTTP server that subagent panes connect to via `opencode attach`.
|
||||
|
||||
**Basic setup**:
|
||||
```bash
|
||||
# Start tmux session
|
||||
tmux new -s dev
|
||||
|
||||
# Run OpenCode with server mode (port 4096)
|
||||
opencode --port 4096
|
||||
|
||||
# Now background agents will appear in separate panes
|
||||
```
|
||||
|
||||
**Recommended: Shell Function**
|
||||
|
||||
For convenience, create a shell function that automatically handles tmux sessions and port allocation. Here's an example for Fish shell:
|
||||
|
||||
```fish
|
||||
# ~/.config/fish/config.fish
|
||||
function oc
|
||||
set base_name (basename (pwd))
|
||||
set path_hash (echo (pwd) | md5 | cut -c1-4)
|
||||
set session_name "$base_name-$path_hash"
|
||||
|
||||
# Find available port starting from 4096
|
||||
function __oc_find_port
|
||||
set port 4096
|
||||
while test $port -lt 5096
|
||||
if not lsof -i :$port >/dev/null 2>&1
|
||||
echo $port
|
||||
return 0
|
||||
end
|
||||
set port (math $port + 1)
|
||||
end
|
||||
echo 4096
|
||||
end
|
||||
|
||||
set oc_port (__oc_find_port)
|
||||
set -x OPENCODE_PORT $oc_port
|
||||
|
||||
if set -q TMUX
|
||||
# Already inside tmux - just run with port
|
||||
opencode --port $oc_port $argv
|
||||
else
|
||||
# Create tmux session and run opencode
|
||||
set oc_cmd "OPENCODE_PORT=$oc_port opencode --port $oc_port $argv; exec fish"
|
||||
if tmux has-session -t "$session_name" 2>/dev/null
|
||||
tmux new-window -t "$session_name" -c (pwd) "$oc_cmd"
|
||||
tmux attach-session -t "$session_name"
|
||||
else
|
||||
tmux new-session -s "$session_name" -c (pwd) "$oc_cmd"
|
||||
end
|
||||
end
|
||||
|
||||
functions -e __oc_find_port
|
||||
end
|
||||
```
|
||||
|
||||
**Bash/Zsh equivalent**:
|
||||
|
||||
```bash
|
||||
# ~/.bashrc or ~/.zshrc
|
||||
oc() {
|
||||
local base_name=$(basename "$PWD")
|
||||
local path_hash=$(echo "$PWD" | md5sum | cut -c1-4)
|
||||
local session_name="${base_name}-${path_hash}"
|
||||
|
||||
# Find available port
|
||||
local port=4096
|
||||
while [ $port -lt 5096 ]; do
|
||||
if ! lsof -i :$port >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
port=$((port + 1))
|
||||
done
|
||||
|
||||
export OPENCODE_PORT=$port
|
||||
|
||||
if [ -n "$TMUX" ]; then
|
||||
opencode --port $port "$@"
|
||||
else
|
||||
local oc_cmd="OPENCODE_PORT=$port opencode --port $port $*; exec $SHELL"
|
||||
if tmux has-session -t "$session_name" 2>/dev/null; then
|
||||
tmux new-window -t "$session_name" -c "$PWD" "$oc_cmd"
|
||||
tmux attach-session -t "$session_name"
|
||||
else
|
||||
tmux new-session -s "$session_name" -c "$PWD" "$oc_cmd"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
```
|
||||
|
||||
**How subagent panes work**:
|
||||
|
||||
1. Main OpenCode starts HTTP server on specified port (e.g., `http://localhost:4096`)
|
||||
2. When a background agent spawns, Oh My OpenCode creates a new tmux pane
|
||||
3. The pane runs: `opencode attach http://localhost:4096 --session <session-id>`
|
||||
4. Each subagent pane shows real-time streaming output
|
||||
5. Panes are automatically closed when the subagent completes
|
||||
|
||||
**Environment variables**:
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `OPENCODE_PORT` | Default port for the HTTP server (used if `--port` not specified) |
|
||||
|
||||
### Server Mode Reference
|
||||
|
||||
OpenCode's server mode exposes an HTTP API for programmatic interaction:
|
||||
|
||||
```bash
|
||||
# Standalone server (no TUI)
|
||||
opencode serve --port 4096
|
||||
|
||||
# TUI with server (recommended for tmux integration)
|
||||
opencode --port 4096
|
||||
```
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `--port` | `4096` | Port for HTTP server |
|
||||
| `--hostname` | `127.0.0.1` | Hostname to listen on |
|
||||
|
||||
For more details, see the [OpenCode Server documentation](https://opencode.ai/docs/server/).
|
||||
|
||||
## Git Master
|
||||
|
||||
Configure git-master skill behavior:
|
||||
|
||||
@@ -62,6 +62,27 @@ delegate_task(agent="explore", background=true, prompt="Find auth implementation
|
||||
background_output(task_id="bg_abc123")
|
||||
```
|
||||
|
||||
#### Visual Multi-Agent with Tmux
|
||||
|
||||
Enable `tmux.enabled` to see background agents in separate tmux panes:
|
||||
|
||||
```json
|
||||
{
|
||||
"tmux": {
|
||||
"enabled": true,
|
||||
"layout": "main-vertical"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When running inside tmux:
|
||||
- Background agents spawn in new panes
|
||||
- Watch multiple agents work in real-time
|
||||
- Each pane shows agent output live
|
||||
- Auto-cleanup when agents complete
|
||||
|
||||
See [Tmux Integration](configurations.md#tmux-integration) for full configuration options.
|
||||
|
||||
Customize agent models, prompts, and permissions in `oh-my-opencode.json`. See [Configuration](configurations.md#agents).
|
||||
|
||||
---
|
||||
@@ -445,6 +466,29 @@ Disable specific hooks in config:
|
||||
| **session_search** | Full-text search across session messages |
|
||||
| **session_info** | Get session metadata and statistics |
|
||||
|
||||
### Interactive Terminal Tools
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| **interactive_bash** | Tmux-based terminal for TUI apps (vim, htop, pudb). Pass tmux subcommands directly without prefix. |
|
||||
|
||||
**Usage Examples**:
|
||||
```bash
|
||||
# Create a new session
|
||||
interactive_bash(tmux_command="new-session -d -s dev-app")
|
||||
|
||||
# Send keystrokes to a session
|
||||
interactive_bash(tmux_command="send-keys -t dev-app 'vim main.py' Enter")
|
||||
|
||||
# Capture pane output
|
||||
interactive_bash(tmux_command="capture-pane -p -t dev-app")
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
- Commands are tmux subcommands (no `tmux` prefix)
|
||||
- Use for interactive apps that need persistent sessions
|
||||
- One-shot commands should use regular `Bash` tool with `&`
|
||||
|
||||
---
|
||||
|
||||
## MCPs: Built-in Servers
|
||||
|
||||
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -73,13 +73,13 @@
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"oh-my-opencode-darwin-arm64": "3.1.0",
|
||||
"oh-my-opencode-darwin-x64": "3.1.0",
|
||||
"oh-my-opencode-linux-arm64": "3.1.0",
|
||||
"oh-my-opencode-linux-arm64-musl": "3.1.0",
|
||||
"oh-my-opencode-linux-x64": "3.1.0",
|
||||
"oh-my-opencode-linux-x64-musl": "3.1.0",
|
||||
"oh-my-opencode-windows-x64": "3.1.0"
|
||||
"oh-my-opencode-darwin-arm64": "3.1.1",
|
||||
"oh-my-opencode-darwin-x64": "3.1.1",
|
||||
"oh-my-opencode-linux-arm64": "3.1.1",
|
||||
"oh-my-opencode-linux-arm64-musl": "3.1.1",
|
||||
"oh-my-opencode-linux-x64": "3.1.1",
|
||||
"oh-my-opencode-linux-x64-musl": "3.1.1",
|
||||
"oh-my-opencode-windows-x64": "3.1.1"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"@ast-grep/cli",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-darwin-arm64",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "Platform-specific binary for oh-my-opencode (darwin-arm64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-darwin-x64",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "Platform-specific binary for oh-my-opencode (darwin-x64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-arm64-musl",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-arm64-musl)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-arm64",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-arm64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-x64-musl",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-x64-musl)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-linux-x64",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "Platform-specific binary for oh-my-opencode (linux-x64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oh-my-opencode-windows-x64",
|
||||
"version": "3.1.0",
|
||||
"version": "3.1.1",
|
||||
"description": "Platform-specific binary for oh-my-opencode (windows-x64)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -815,6 +815,70 @@
|
||||
"created_at": "2026-01-25T03:13:52Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1084
|
||||
},
|
||||
{
|
||||
"name": "misyuari",
|
||||
"id": 12197761,
|
||||
"comment_id": 3798225767,
|
||||
"created_at": "2026-01-26T07:31:02Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1132
|
||||
},
|
||||
{
|
||||
"name": "boguan",
|
||||
"id": 3226538,
|
||||
"comment_id": 3798448537,
|
||||
"created_at": "2026-01-26T08:40:37Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1137
|
||||
},
|
||||
{
|
||||
"name": "boguan",
|
||||
"id": 3226538,
|
||||
"comment_id": 3798471978,
|
||||
"created_at": "2026-01-26T08:46:03Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1137
|
||||
},
|
||||
{
|
||||
"name": "Jeremy-Kr",
|
||||
"id": 110771206,
|
||||
"comment_id": 3799211732,
|
||||
"created_at": "2026-01-26T11:59:13Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1141
|
||||
},
|
||||
{
|
||||
"name": "orientpine",
|
||||
"id": 32758428,
|
||||
"comment_id": 3799897021,
|
||||
"created_at": "2026-01-26T14:30:33Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1145
|
||||
},
|
||||
{
|
||||
"name": "craftaholic",
|
||||
"id": 63741110,
|
||||
"comment_id": 3797014417,
|
||||
"created_at": "2026-01-25T17:52:34Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1110
|
||||
},
|
||||
{
|
||||
"name": "acamq",
|
||||
"id": 179265037,
|
||||
"comment_id": 3801038978,
|
||||
"created_at": "2026-01-26T18:20:17Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1151
|
||||
},
|
||||
{
|
||||
"name": "itsmylife44",
|
||||
"id": 34112129,
|
||||
"comment_id": 3802225779,
|
||||
"created_at": "2026-01-26T23:20:30Z",
|
||||
"repoId": 1108837393,
|
||||
"pullRequestNo": 1157
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -523,9 +523,6 @@ function buildDynamicOrchestratorPrompt(ctx?: OrchestratorContext): string {
|
||||
}
|
||||
|
||||
export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig {
|
||||
if (!ctx.model) {
|
||||
throw new Error("createAtlasAgent requires a model in context")
|
||||
}
|
||||
const restrictions = createAgentToolRestrictions([
|
||||
"task",
|
||||
"call_omo_agent",
|
||||
@@ -534,7 +531,7 @@ export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig {
|
||||
description:
|
||||
"Orchestrates work via delegate_task() to complete ALL tasks in a todo list until fully done",
|
||||
mode: "primary" as const,
|
||||
model: ctx.model,
|
||||
...(ctx.model ? { model: ctx.model } : {}),
|
||||
temperature: 0.1,
|
||||
prompt: buildDynamicOrchestratorPrompt(ctx),
|
||||
thinking: { type: "enabled", budgetTokens: 32000 },
|
||||
|
||||
@@ -106,6 +106,30 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("createBuiltinAgents without systemDefaultModel", () => {
|
||||
test("creates agents successfully without systemDefaultModel", async () => {
|
||||
// #given - no systemDefaultModel provided
|
||||
|
||||
// #when
|
||||
const agents = await createBuiltinAgents([], {}, undefined, undefined)
|
||||
|
||||
// #then - agents should still be created using fallback chain
|
||||
expect(agents.oracle).toBeDefined()
|
||||
expect(agents.oracle.model).toBe("openai/gpt-5.2")
|
||||
})
|
||||
|
||||
test("sisyphus uses fallback chain when systemDefaultModel undefined", async () => {
|
||||
// #given - no systemDefaultModel
|
||||
|
||||
// #when
|
||||
const agents = await createBuiltinAgents([], {}, undefined, undefined)
|
||||
|
||||
// #then - sisyphus should use its fallback chain
|
||||
expect(agents.sisyphus).toBeDefined()
|
||||
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-5")
|
||||
})
|
||||
})
|
||||
|
||||
describe("buildAgent with category and skills", () => {
|
||||
const { buildAgent } = require("./utils")
|
||||
const TEST_MODEL = "anthropic/claude-opus-4-5"
|
||||
|
||||
@@ -151,10 +151,6 @@ export async function createBuiltinAgents(
|
||||
client?: any,
|
||||
browserProvider?: BrowserAutomationProvider
|
||||
): Promise<Record<string, AgentConfig>> {
|
||||
if (!systemDefaultModel) {
|
||||
throw new Error("createBuiltinAgents requires systemDefaultModel")
|
||||
}
|
||||
|
||||
const connectedProviders = readConnectedProvidersCache()
|
||||
const availableModels = client
|
||||
? await fetchAvailableModels(client, { connectedProviders: connectedProviders ?? undefined })
|
||||
@@ -201,13 +197,14 @@ export async function createBuiltinAgents(
|
||||
const override = findCaseInsensitive(agentOverrides, agentName)
|
||||
const requirement = AGENT_MODEL_REQUIREMENTS[agentName]
|
||||
|
||||
// Use resolver to determine model
|
||||
const { model, variant: resolvedVariant } = resolveModelWithFallback({
|
||||
const resolution = resolveModelWithFallback({
|
||||
userModel: override?.model,
|
||||
fallbackChain: requirement?.fallbackChain,
|
||||
availableModels,
|
||||
systemDefaultModel,
|
||||
})
|
||||
if (!resolution) continue
|
||||
const { model, variant: resolvedVariant } = resolution
|
||||
|
||||
let config = buildAgent(source, model, mergedCategories, gitMasterConfig, browserProvider)
|
||||
|
||||
@@ -243,72 +240,76 @@ export async function createBuiltinAgents(
|
||||
const sisyphusOverride = agentOverrides["sisyphus"]
|
||||
const sisyphusRequirement = AGENT_MODEL_REQUIREMENTS["sisyphus"]
|
||||
|
||||
// Use resolver to determine model
|
||||
const { model: sisyphusModel, variant: sisyphusResolvedVariant } = resolveModelWithFallback({
|
||||
const sisyphusResolution = resolveModelWithFallback({
|
||||
userModel: sisyphusOverride?.model,
|
||||
fallbackChain: sisyphusRequirement?.fallbackChain,
|
||||
availableModels,
|
||||
systemDefaultModel,
|
||||
})
|
||||
|
||||
let sisyphusConfig = createSisyphusAgent(
|
||||
sisyphusModel,
|
||||
availableAgents,
|
||||
undefined,
|
||||
availableSkills,
|
||||
availableCategories
|
||||
)
|
||||
|
||||
// Apply variant from override or resolved fallback chain
|
||||
if (sisyphusOverride?.variant) {
|
||||
sisyphusConfig = { ...sisyphusConfig, variant: sisyphusOverride.variant }
|
||||
} else if (sisyphusResolvedVariant) {
|
||||
sisyphusConfig = { ...sisyphusConfig, variant: sisyphusResolvedVariant }
|
||||
}
|
||||
if (sisyphusResolution) {
|
||||
const { model: sisyphusModel, variant: sisyphusResolvedVariant } = sisyphusResolution
|
||||
|
||||
if (directory && sisyphusConfig.prompt) {
|
||||
const envContext = createEnvContext()
|
||||
sisyphusConfig = { ...sisyphusConfig, prompt: sisyphusConfig.prompt + envContext }
|
||||
}
|
||||
let sisyphusConfig = createSisyphusAgent(
|
||||
sisyphusModel,
|
||||
availableAgents,
|
||||
undefined,
|
||||
availableSkills,
|
||||
availableCategories
|
||||
)
|
||||
|
||||
if (sisyphusOverride?.variant) {
|
||||
sisyphusConfig = { ...sisyphusConfig, variant: sisyphusOverride.variant }
|
||||
} else if (sisyphusResolvedVariant) {
|
||||
sisyphusConfig = { ...sisyphusConfig, variant: sisyphusResolvedVariant }
|
||||
}
|
||||
|
||||
if (sisyphusOverride) {
|
||||
sisyphusConfig = mergeAgentConfig(sisyphusConfig, sisyphusOverride)
|
||||
}
|
||||
if (directory && sisyphusConfig.prompt) {
|
||||
const envContext = createEnvContext()
|
||||
sisyphusConfig = { ...sisyphusConfig, prompt: sisyphusConfig.prompt + envContext }
|
||||
}
|
||||
|
||||
result["sisyphus"] = sisyphusConfig
|
||||
if (sisyphusOverride) {
|
||||
sisyphusConfig = mergeAgentConfig(sisyphusConfig, sisyphusOverride)
|
||||
}
|
||||
|
||||
result["sisyphus"] = sisyphusConfig
|
||||
}
|
||||
}
|
||||
|
||||
if (!disabledAgents.includes("atlas")) {
|
||||
const orchestratorOverride = agentOverrides["atlas"]
|
||||
const atlasRequirement = AGENT_MODEL_REQUIREMENTS["atlas"]
|
||||
|
||||
// Use resolver to determine model
|
||||
const { model: atlasModel, variant: atlasResolvedVariant } = resolveModelWithFallback({
|
||||
const atlasResolution = resolveModelWithFallback({
|
||||
userModel: orchestratorOverride?.model,
|
||||
fallbackChain: atlasRequirement?.fallbackChain,
|
||||
availableModels,
|
||||
systemDefaultModel,
|
||||
})
|
||||
|
||||
let orchestratorConfig = createAtlasAgent({
|
||||
model: atlasModel,
|
||||
availableAgents,
|
||||
availableSkills,
|
||||
userCategories: categories,
|
||||
})
|
||||
|
||||
// Apply variant from override or resolved fallback chain
|
||||
if (orchestratorOverride?.variant) {
|
||||
orchestratorConfig = { ...orchestratorConfig, variant: orchestratorOverride.variant }
|
||||
} else if (atlasResolvedVariant) {
|
||||
orchestratorConfig = { ...orchestratorConfig, variant: atlasResolvedVariant }
|
||||
}
|
||||
if (atlasResolution) {
|
||||
const { model: atlasModel, variant: atlasResolvedVariant } = atlasResolution
|
||||
|
||||
if (orchestratorOverride) {
|
||||
orchestratorConfig = mergeAgentConfig(orchestratorConfig, orchestratorOverride)
|
||||
}
|
||||
let orchestratorConfig = createAtlasAgent({
|
||||
model: atlasModel,
|
||||
availableAgents,
|
||||
availableSkills,
|
||||
userCategories: categories,
|
||||
})
|
||||
|
||||
if (orchestratorOverride?.variant) {
|
||||
orchestratorConfig = { ...orchestratorConfig, variant: orchestratorOverride.variant }
|
||||
} else if (atlasResolvedVariant) {
|
||||
orchestratorConfig = { ...orchestratorConfig, variant: atlasResolvedVariant }
|
||||
}
|
||||
|
||||
result["atlas"] = orchestratorConfig
|
||||
if (orchestratorOverride) {
|
||||
orchestratorConfig = mergeAgentConfig(orchestratorConfig, orchestratorOverride)
|
||||
}
|
||||
|
||||
result["atlas"] = orchestratorConfig
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
17
src/cli/index.test.ts
Normal file
17
src/cli/index.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { describe, it, expect } from "bun:test"
|
||||
import packageJson from "../../package.json" with { type: "json" }
|
||||
|
||||
describe("CLI version", () => {
|
||||
it("reads version from package.json as valid semver", () => {
|
||||
//#given
|
||||
const semverRegex = /^\d+\.\d+\.\d+(-[\w.]+)?$/
|
||||
|
||||
//#when
|
||||
const version = packageJson.version
|
||||
|
||||
//#then
|
||||
expect(version).toMatch(semverRegex)
|
||||
expect(typeof version).toBe("string")
|
||||
expect(version.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
@@ -116,6 +116,19 @@ export const AgentOverrideConfigSchema = z.object({
|
||||
.regex(/^#[0-9A-Fa-f]{6}$/)
|
||||
.optional(),
|
||||
permission: AgentPermissionSchema.optional(),
|
||||
/** Maximum tokens for response. Passed directly to OpenCode SDK. */
|
||||
maxTokens: z.number().optional(),
|
||||
/** Extended thinking configuration (Anthropic). Overrides category and default settings. */
|
||||
thinking: z.object({
|
||||
type: z.enum(["enabled", "disabled"]),
|
||||
budgetTokens: z.number().optional(),
|
||||
}).optional(),
|
||||
/** Reasoning effort level (OpenAI). Overrides category and default settings. */
|
||||
reasoningEffort: z.enum(["low", "medium", "high", "xhigh"]).optional(),
|
||||
/** Text verbosity level. */
|
||||
textVerbosity: z.enum(["low", "medium", "high"]).optional(),
|
||||
/** Provider-specific options. Passed directly to OpenCode SDK. */
|
||||
providerOptions: z.record(z.string(), z.unknown()).optional(),
|
||||
})
|
||||
|
||||
export const AgentOverridesSchema = z.object({
|
||||
|
||||
@@ -305,6 +305,7 @@ export class BackgroundManager {
|
||||
task: false,
|
||||
delegate_task: false,
|
||||
call_omo_agent: true,
|
||||
question: false,
|
||||
},
|
||||
parts: [{ type: "text", text: input.prompt }],
|
||||
},
|
||||
@@ -551,6 +552,7 @@ export class BackgroundManager {
|
||||
task: false,
|
||||
delegate_task: false,
|
||||
call_omo_agent: true,
|
||||
question: false,
|
||||
},
|
||||
parts: [{ type: "text", text: input.prompt }],
|
||||
},
|
||||
|
||||
@@ -166,34 +166,110 @@ delegate_task(agent="oracle", prompt="Review my approach: [describe plan]")
|
||||
YOU MUST LEVERAGE ALL AVAILABLE AGENTS / **CATEGORY + SKILLS** TO THEIR FULLEST POTENTIAL.
|
||||
TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
|
||||
|
||||
## AGENTS / **CATEGORY + SKILLS** UTILIZATION PRINCIPLES (by capability, not by name)
|
||||
- **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS for file patterns, internal implementations, project structure
|
||||
- **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS for API references, examples, external library docs
|
||||
- **Planning & Strategy**: NEVER plan yourself - ALWAYS spawn the Plan agent for work breakdown
|
||||
- MUST invoke: \`delegate_task(subagent_type="plan", prompt="<gathered context + user request>")\`
|
||||
- In your prompt to the Plan agent, ASK it to recommend which CATEGORY + SKILLS / AGENTS to leverage for implementation.
|
||||
- IF IMPLEMENT TASK, MUST ADD TODO NOW: "Consult Plan agent via delegate_task(subagent_type='plan') for work breakdown with category + skills recommendations"
|
||||
- **High-IQ Reasoning**: Leverage specialized agents for architecture decisions, code review, strategic planning
|
||||
- **SPECIAL TASKS COVERED WITH CATEGORY + LOAD_SKILLS**: Delegate to specialized agents with category+skills for design and implementation, as following guide:
|
||||
- CATEGORY + SKILL GUIDE
|
||||
- MUST PASS \`load_skills\` FOR REQUIRED_SKILLS. MUST USE \`load_skills\` FOR REQUIRED_SKILLS.
|
||||
- Simple project setup -> delegate_task(category="unspecified-low", load_skills=[{project-setup-skill}])
|
||||
- Super Complex Server Workflow Implementation -> delegate_task(category="ultrabrain", load_skills=["terraform-master"], ...)
|
||||
- Web Frontend Component Writing -> delegate_task(category="visual-engineering", load_skills=["frontend-ui-ux", "playwright"], ...)
|
||||
## MANDATORY: PLAN AGENT INVOCATION (NON-NEGOTIABLE)
|
||||
|
||||
## EXECUTION RULES
|
||||
- **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each.
|
||||
- **PARALLEL**: Fire independent agent calls simultaneously via delegate_task(background=true) - NEVER wait sequentially.
|
||||
- **BACKGROUND FIRST**: Use delegate_task for exploration/research agents (10+ concurrent if needed).
|
||||
- **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done.
|
||||
- **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths.
|
||||
- **CATEGORY + LOAD_SKILLS**
|
||||
**YOU MUST ALWAYS INVOKE THE PLAN AGENT FOR ANY NON-TRIVIAL TASK.**
|
||||
|
||||
## WORKFLOW
|
||||
1. Analyze the request and identify required capabilities
|
||||
2. Spawn exploration/librarian agents via delegate_task(background=true) in PARALLEL (10+ if needed)
|
||||
3. Spawn Plan agent: \`delegate_task(subagent_type="plan", prompt="<context + request>")\` to create detailed work breakdown
|
||||
4. Execute with continuous verification against original requirements
|
||||
| Condition | Action |
|
||||
|-----------|--------|
|
||||
| Task has 2+ steps | MUST call Plan agent |
|
||||
| Task scope unclear | MUST call Plan agent |
|
||||
| Implementation required | MUST call Plan agent |
|
||||
| Architecture decision needed | MUST call Plan agent |
|
||||
|
||||
\`\`\`
|
||||
delegate_task(subagent_type="plan", prompt="<gathered context + user request>")
|
||||
\`\`\`
|
||||
|
||||
**WHY THIS IS MANDATORY:**
|
||||
- Plan agent analyzes dependencies and parallel execution opportunities
|
||||
- Plan agent recommends CATEGORY + SKILLS for each task
|
||||
- Plan agent ensures nothing is missed
|
||||
- YOU are an orchestrator, NOT an implementer
|
||||
|
||||
**FAILURE TO CALL PLAN AGENT = INCOMPLETE WORK.**
|
||||
|
||||
---
|
||||
|
||||
## AGENTS / **CATEGORY + SKILLS** UTILIZATION PRINCIPLES
|
||||
|
||||
**DEFAULT BEHAVIOR: DELEGATE. DO NOT WORK YOURSELF.**
|
||||
|
||||
| Task Type | Action | Why |
|
||||
|-----------|--------|-----|
|
||||
| Codebase exploration | delegate_task(subagent_type="explore", run_in_background=true) | Parallel, context-efficient |
|
||||
| Documentation lookup | delegate_task(subagent_type="librarian", run_in_background=true) | Specialized knowledge |
|
||||
| Planning | delegate_task(subagent_type="plan") | Structured work breakdown |
|
||||
| Architecture/Debugging | delegate_task(subagent_type="oracle") | High-IQ reasoning |
|
||||
| Implementation | delegate_task(category="...", load_skills=[...]) | Domain-optimized models |
|
||||
|
||||
**CATEGORY + SKILL DELEGATION:**
|
||||
\`\`\`
|
||||
// Frontend work
|
||||
delegate_task(category="visual-engineering", load_skills=["frontend-ui-ux"])
|
||||
|
||||
// Complex logic
|
||||
delegate_task(category="ultrabrain", load_skills=["typescript-programmer"])
|
||||
|
||||
// Quick fixes
|
||||
delegate_task(category="quick", load_skills=["git-master"])
|
||||
\`\`\`
|
||||
|
||||
**YOU SHOULD ONLY DO IT YOURSELF WHEN:**
|
||||
- Task is trivially simple (1-2 lines, obvious change)
|
||||
- You have ALL context already loaded
|
||||
- Delegation overhead exceeds task complexity
|
||||
|
||||
**OTHERWISE: DELEGATE. ALWAYS.**
|
||||
|
||||
---
|
||||
|
||||
## EXECUTION RULES (PARALLELIZATION MANDATORY)
|
||||
|
||||
| Rule | Implementation |
|
||||
|------|----------------|
|
||||
| **PARALLEL FIRST** | Fire ALL independent agents simultaneously via delegate_task(run_in_background=true) |
|
||||
| **NEVER SEQUENTIAL** | If tasks A and B are independent, launch BOTH at once |
|
||||
| **10+ CONCURRENT** | Use 10+ background agents if needed for comprehensive exploration |
|
||||
| **COLLECT LATER** | Launch agents -> continue work -> background_output when needed |
|
||||
|
||||
**ANTI-PATTERN (BLOCKING):**
|
||||
\`\`\`
|
||||
// WRONG: Sequential, slow
|
||||
result1 = delegate_task(..., run_in_background=false) // waits
|
||||
result2 = delegate_task(..., run_in_background=false) // waits again
|
||||
\`\`\`
|
||||
|
||||
**CORRECT PATTERN:**
|
||||
\`\`\`
|
||||
// RIGHT: Parallel, fast
|
||||
delegate_task(..., run_in_background=true) // task_id_1
|
||||
delegate_task(..., run_in_background=true) // task_id_2
|
||||
delegate_task(..., run_in_background=true) // task_id_3
|
||||
// Continue working, collect with background_output when needed
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
|
||||
## WORKFLOW (MANDATORY SEQUENCE)
|
||||
|
||||
1. **GATHER CONTEXT** (parallel background agents):
|
||||
\`\`\`
|
||||
delegate_task(subagent_type="explore", run_in_background=true, prompt="...")
|
||||
delegate_task(subagent_type="librarian", run_in_background=true, prompt="...")
|
||||
\`\`\`
|
||||
|
||||
2. **INVOKE PLAN AGENT** (MANDATORY for non-trivial tasks):
|
||||
\`\`\`
|
||||
delegate_task(subagent_type="plan", prompt="<context + request>")
|
||||
\`\`\`
|
||||
|
||||
3. **EXECUTE VIA DELEGATION** (category + skills):
|
||||
\`\`\`
|
||||
delegate_task(category="...", load_skills=[...], prompt="<task from plan>")
|
||||
\`\`\`
|
||||
|
||||
4. **VERIFY** against original requirements
|
||||
|
||||
## VERIFICATION GUARANTEE (NON-NEGOTIABLE)
|
||||
|
||||
|
||||
@@ -350,4 +350,63 @@ describe("createThinkModeHook integration", () => {
|
||||
expect(input.message.model?.modelID).toBe("claude-opus-4-5")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Agent-level thinking configuration respect", () => {
|
||||
it("should NOT inject thinking config when agent has thinking disabled", async () => {
|
||||
// #given agent with thinking explicitly disabled
|
||||
const hook = createThinkModeHook()
|
||||
const input: ThinkModeInput = {
|
||||
parts: [{ type: "text", text: "ultrathink deeply" }],
|
||||
message: {
|
||||
model: { providerID: "google", modelID: "gemini-3-pro" },
|
||||
thinking: { type: "disabled" },
|
||||
} as ThinkModeInput["message"],
|
||||
}
|
||||
|
||||
// #when the chat.params hook is called
|
||||
await hook["chat.params"](input, sessionID)
|
||||
|
||||
// #then should NOT override agent's thinking disabled setting
|
||||
const message = input.message as MessageWithInjectedProps
|
||||
expect((message.thinking as { type: string }).type).toBe("disabled")
|
||||
expect(message.providerOptions).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should NOT inject thinking config when agent has custom providerOptions", async () => {
|
||||
// #given agent with custom providerOptions
|
||||
const hook = createThinkModeHook()
|
||||
const input: ThinkModeInput = {
|
||||
parts: [{ type: "text", text: "ultrathink" }],
|
||||
message: {
|
||||
model: { providerID: "google", modelID: "gemini-3-flash" },
|
||||
providerOptions: {
|
||||
google: { thinkingConfig: { thinkingBudget: 0 } },
|
||||
},
|
||||
} as ThinkModeInput["message"],
|
||||
}
|
||||
|
||||
// #when the chat.params hook is called
|
||||
await hook["chat.params"](input, sessionID)
|
||||
|
||||
// #then should NOT override agent's providerOptions
|
||||
const message = input.message as MessageWithInjectedProps
|
||||
const providerOpts = message.providerOptions as Record<string, unknown>
|
||||
expect((providerOpts.google as Record<string, unknown>).thinkingConfig).toEqual({
|
||||
thinkingBudget: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it("should still inject thinking config when agent has no thinking override", async () => {
|
||||
// #given agent without thinking override
|
||||
const hook = createThinkModeHook()
|
||||
const input = createMockInput("google", "gemini-3-pro", "ultrathink")
|
||||
|
||||
// #when the chat.params hook is called
|
||||
await hook["chat.params"](input, sessionID)
|
||||
|
||||
// #then should inject thinking config as normal
|
||||
const message = input.message as MessageWithInjectedProps
|
||||
expect(message.providerOptions).toBeDefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -65,13 +65,32 @@ export function createThinkModeHook() {
|
||||
}
|
||||
|
||||
if (thinkingConfig) {
|
||||
Object.assign(output.message, thinkingConfig)
|
||||
state.thinkingConfigInjected = true
|
||||
log("Think mode: thinking config injected", {
|
||||
sessionID,
|
||||
provider: currentModel.providerID,
|
||||
config: thinkingConfig,
|
||||
})
|
||||
const messageData = output.message as Record<string, unknown>
|
||||
const agentThinking = messageData.thinking as { type?: string } | undefined
|
||||
const agentProviderOptions = messageData.providerOptions
|
||||
|
||||
const agentDisabledThinking = agentThinking?.type === "disabled"
|
||||
const agentHasCustomProviderOptions = Boolean(agentProviderOptions)
|
||||
|
||||
if (agentDisabledThinking) {
|
||||
log("Think mode: skipping - agent has thinking disabled", {
|
||||
sessionID,
|
||||
provider: currentModel.providerID,
|
||||
})
|
||||
} else if (agentHasCustomProviderOptions) {
|
||||
log("Think mode: skipping - agent has custom providerOptions", {
|
||||
sessionID,
|
||||
provider: currentModel.providerID,
|
||||
})
|
||||
} else {
|
||||
Object.assign(output.message, thinkingConfig)
|
||||
state.thinkingConfigInjected = true
|
||||
log("Think mode: thinking config injected", {
|
||||
sessionID,
|
||||
provider: currentModel.providerID,
|
||||
config: thinkingConfig,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
thinkModeState.set(sessionID, state)
|
||||
|
||||
@@ -25,10 +25,12 @@ import { loadMcpConfigs } from "../features/claude-code-mcp-loader";
|
||||
import { loadAllPluginComponents } from "../features/claude-code-plugin-loader";
|
||||
import { createBuiltinMcps } from "../mcp";
|
||||
import type { OhMyOpenCodeConfig } from "../config";
|
||||
import { log } from "../shared";
|
||||
import { log, fetchAvailableModels, readConnectedProvidersCache } from "../shared";
|
||||
import { getOpenCodeConfigPaths } from "../shared/opencode-config-dir";
|
||||
import { migrateAgentConfig } from "../shared/permission-compat";
|
||||
import { AGENT_NAME_MAP } from "../shared/migration";
|
||||
import { resolveModelWithFallback } from "../shared/model-resolver";
|
||||
import { AGENT_MODEL_REQUIREMENTS } from "../shared/model-requirements";
|
||||
import { PROMETHEUS_SYSTEM_PROMPT, PROMETHEUS_PERMISSION } from "../agents/prometheus-prompt";
|
||||
import { DEFAULT_CATEGORIES } from "../tools/delegate-task/constants";
|
||||
import type { ModelCacheState } from "../plugin-state";
|
||||
@@ -105,41 +107,6 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
|
||||
log(`Plugin load errors`, { errors: pluginComponents.errors });
|
||||
}
|
||||
|
||||
if (!(config.model as string | undefined)?.trim()) {
|
||||
let fallbackModel: string | undefined
|
||||
|
||||
for (const agentConfig of Object.values(pluginConfig.agents ?? {})) {
|
||||
const model = (agentConfig as { model?: string })?.model
|
||||
if (model && typeof model === 'string' && model.trim()) {
|
||||
fallbackModel = model.trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!fallbackModel) {
|
||||
for (const categoryConfig of Object.values(pluginConfig.categories ?? {})) {
|
||||
const model = (categoryConfig as { model?: string })?.model
|
||||
if (model && typeof model === 'string' && model.trim()) {
|
||||
fallbackModel = model.trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fallbackModel) {
|
||||
config.model = fallbackModel
|
||||
log(`No default model specified, using fallback from config: ${fallbackModel}`)
|
||||
} else {
|
||||
const paths = getOpenCodeConfigPaths({ binary: "opencode", version: null })
|
||||
throw new Error(
|
||||
'oh-my-opencode requires a default model.\n\n' +
|
||||
`Add this to ${paths.configJsonc}:\n\n` +
|
||||
' "model": "anthropic/claude-sonnet-4-5"\n\n' +
|
||||
'(Replace with your preferred provider/model)'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate disabled_agents from old names to new names
|
||||
const migratedDisabledAgents = (pluginConfig.disabled_agents ?? []).map(agent => {
|
||||
return AGENT_NAME_MAP[agent.toLowerCase()] ?? AGENT_NAME_MAP[agent] ?? agent
|
||||
@@ -256,13 +223,10 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
|
||||
);
|
||||
const prometheusOverride =
|
||||
pluginConfig.agents?.["prometheus"] as
|
||||
| (Record<string, unknown> & { category?: string; model?: string })
|
||||
| (Record<string, unknown> & { category?: string; model?: string; variant?: string })
|
||||
| undefined;
|
||||
const defaultModel = config.model as string | undefined;
|
||||
|
||||
// Resolve full category config (model, temperature, top_p, tools, etc.)
|
||||
// Apply all category properties when category is specified, but explicit
|
||||
// overrides (model, temperature, etc.) will take precedence during merge
|
||||
const categoryConfig = prometheusOverride?.category
|
||||
? resolveCategoryConfig(
|
||||
prometheusOverride.category,
|
||||
@@ -270,19 +234,31 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
|
||||
)
|
||||
: undefined;
|
||||
|
||||
// Model resolution: explicit override → category config → OpenCode default
|
||||
// No hardcoded fallback - OpenCode config.model is the terminal fallback
|
||||
const resolvedModel = prometheusOverride?.model ?? categoryConfig?.model ?? defaultModel;
|
||||
const prometheusRequirement = AGENT_MODEL_REQUIREMENTS["prometheus"];
|
||||
const connectedProviders = readConnectedProvidersCache();
|
||||
const availableModels = ctx.client
|
||||
? await fetchAvailableModels(ctx.client, { connectedProviders: connectedProviders ?? undefined })
|
||||
: new Set<string>();
|
||||
|
||||
const modelResolution = resolveModelWithFallback({
|
||||
userModel: prometheusOverride?.model ?? categoryConfig?.model,
|
||||
fallbackChain: prometheusRequirement?.fallbackChain,
|
||||
availableModels,
|
||||
systemDefaultModel: defaultModel ?? "",
|
||||
});
|
||||
const resolvedModel = modelResolution?.model;
|
||||
const resolvedVariant = modelResolution?.variant;
|
||||
|
||||
const variantToUse = prometheusOverride?.variant ?? resolvedVariant;
|
||||
const prometheusBase = {
|
||||
// Only include model if one was resolved - let OpenCode apply its own default if none
|
||||
name: "prometheus",
|
||||
...(resolvedModel ? { model: resolvedModel } : {}),
|
||||
...(variantToUse ? { variant: variantToUse } : {}),
|
||||
mode: "primary" as const,
|
||||
prompt: PROMETHEUS_SYSTEM_PROMPT,
|
||||
permission: PROMETHEUS_PERMISSION,
|
||||
description: `${configAgent?.plan?.description ?? "Plan agent"} (Prometheus - OhMyOpenCode)`,
|
||||
color: (configAgent?.plan?.color as string) ?? "#FF6347",
|
||||
// Apply category properties (temperature, top_p, tools, etc.)
|
||||
...(categoryConfig?.temperature !== undefined
|
||||
? { temperature: categoryConfig.temperature }
|
||||
: {}),
|
||||
@@ -330,8 +306,8 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
|
||||
? migrateAgentConfig(configAgent.build as Record<string, unknown>)
|
||||
: {};
|
||||
|
||||
const planDemoteConfig = replacePlan
|
||||
? { mode: "subagent" as const }
|
||||
const planDemoteConfig = replacePlan && agentConfig["prometheus"]
|
||||
? { ...agentConfig["prometheus"], name: "plan", mode: "subagent" as const }
|
||||
: undefined;
|
||||
|
||||
config.agent = {
|
||||
|
||||
@@ -128,8 +128,8 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result.source).toBe("override")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result!.source).toBe("override")
|
||||
expect(logSpy).toHaveBeenCalledWith("Model resolved via override", { model: "anthropic/claude-opus-4-5" })
|
||||
})
|
||||
|
||||
@@ -148,8 +148,8 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("custom/my-model")
|
||||
expect(result.source).toBe("override")
|
||||
expect(result!.model).toBe("custom/my-model")
|
||||
expect(result!.source).toBe("override")
|
||||
})
|
||||
|
||||
test("whitespace-only userModel is treated as not provided", () => {
|
||||
@@ -167,7 +167,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.source).not.toBe("override")
|
||||
expect(result!.source).not.toBe("override")
|
||||
})
|
||||
|
||||
test("empty string userModel is treated as not provided", () => {
|
||||
@@ -185,7 +185,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.source).not.toBe("override")
|
||||
expect(result!.source).not.toBe("override")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -204,8 +204,8 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("github-copilot/claude-opus-4-5-preview")
|
||||
expect(result.source).toBe("provider-fallback")
|
||||
expect(result!.model).toBe("github-copilot/claude-opus-4-5-preview")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
expect(logSpy).toHaveBeenCalledWith("Model resolved via fallback chain (availability confirmed)", {
|
||||
provider: "github-copilot",
|
||||
model: "claude-opus-4-5",
|
||||
@@ -228,8 +228,8 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("openai/gpt-5.2")
|
||||
expect(result.source).toBe("provider-fallback")
|
||||
expect(result!.model).toBe("openai/gpt-5.2")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
test("tries next provider when first provider has no match", () => {
|
||||
@@ -246,8 +246,8 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("opencode/gpt-5-nano")
|
||||
expect(result.source).toBe("provider-fallback")
|
||||
expect(result!.model).toBe("opencode/gpt-5-nano")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
test("uses fuzzy matching within provider", () => {
|
||||
@@ -264,8 +264,8 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result.source).toBe("provider-fallback")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
test("skips fallback chain when not provided", () => {
|
||||
@@ -279,7 +279,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.source).toBe("system-default")
|
||||
expect(result!.source).toBe("system-default")
|
||||
})
|
||||
|
||||
test("skips fallback chain when empty", () => {
|
||||
@@ -294,7 +294,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.source).toBe("system-default")
|
||||
expect(result!.source).toBe("system-default")
|
||||
})
|
||||
|
||||
test("case-insensitive fuzzy matching", () => {
|
||||
@@ -311,8 +311,8 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result.source).toBe("provider-fallback")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -331,8 +331,8 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("google/gemini-3-pro")
|
||||
expect(result.source).toBe("system-default")
|
||||
expect(result!.model).toBe("google/gemini-3-pro")
|
||||
expect(result!.source).toBe("system-default")
|
||||
expect(logSpy).toHaveBeenCalledWith("No available model found in fallback chain, falling through to system default")
|
||||
})
|
||||
|
||||
@@ -350,8 +350,8 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then - should use first fallback entry, not system default
|
||||
expect(result.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result.source).toBe("provider-fallback")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
test("returns system default when fallbackChain is not provided", () => {
|
||||
@@ -365,8 +365,8 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("google/gemini-3-pro")
|
||||
expect(result.source).toBe("system-default")
|
||||
expect(result!.model).toBe("google/gemini-3-pro")
|
||||
expect(result!.source).toBe("system-default")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -386,8 +386,8 @@ describe("resolveModelWithFallback", () => {
|
||||
})
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result.source).toBe("provider-fallback")
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
test("tries all providers in first entry before moving to second entry", () => {
|
||||
@@ -405,8 +405,8 @@ describe("resolveModelWithFallback", () => {
|
||||
})
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("google/gemini-3-pro")
|
||||
expect(result.source).toBe("provider-fallback")
|
||||
expect(result!.model).toBe("google/gemini-3-pro")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
test("returns first matching entry even if later entries have better matches", () => {
|
||||
@@ -427,8 +427,8 @@ describe("resolveModelWithFallback", () => {
|
||||
})
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("openai/gpt-5.2")
|
||||
expect(result.source).toBe("provider-fallback")
|
||||
expect(result!.model).toBe("openai/gpt-5.2")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
test("falls through to system default when none match availability", () => {
|
||||
@@ -447,8 +447,8 @@ describe("resolveModelWithFallback", () => {
|
||||
})
|
||||
|
||||
// #then
|
||||
expect(result.model).toBe("system/default")
|
||||
expect(result.source).toBe("system-default")
|
||||
expect(result!.model).toBe("system/default")
|
||||
expect(result!.source).toBe("system-default")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -462,11 +462,81 @@ describe("resolveModelWithFallback", () => {
|
||||
}
|
||||
|
||||
// #when
|
||||
const result: ModelResolutionResult = resolveModelWithFallback(input)
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(typeof result.model).toBe("string")
|
||||
expect(["override", "provider-fallback", "system-default"]).toContain(result.source)
|
||||
expect(result).toBeDefined()
|
||||
expect(typeof result!.model).toBe("string")
|
||||
expect(["override", "provider-fallback", "system-default"]).toContain(result!.source)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Optional systemDefaultModel", () => {
|
||||
test("returns undefined when systemDefaultModel is undefined and no fallback found", () => {
|
||||
// #given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "nonexistent-model" },
|
||||
],
|
||||
availableModels: new Set(["openai/gpt-5.2"]),
|
||||
systemDefaultModel: undefined,
|
||||
}
|
||||
|
||||
// #when
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
test("returns undefined when no fallbackChain and systemDefaultModel is undefined", () => {
|
||||
// #given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
availableModels: new Set(["openai/gpt-5.2"]),
|
||||
systemDefaultModel: undefined,
|
||||
}
|
||||
|
||||
// #when
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
test("still returns override when userModel provided even if systemDefaultModel undefined", () => {
|
||||
// #given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-5",
|
||||
availableModels: new Set(),
|
||||
systemDefaultModel: undefined,
|
||||
}
|
||||
|
||||
// #when
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result).toBeDefined()
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result!.source).toBe("override")
|
||||
})
|
||||
|
||||
test("still returns fallback match when systemDefaultModel undefined", () => {
|
||||
// #given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-5" },
|
||||
],
|
||||
availableModels: new Set(["anthropic/claude-opus-4-5"]),
|
||||
systemDefaultModel: undefined,
|
||||
}
|
||||
|
||||
// #when
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// #then
|
||||
expect(result).toBeDefined()
|
||||
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ import { readConnectedProvidersCache } from "./connected-providers-cache"
|
||||
export type ModelResolutionInput = {
|
||||
userModel?: string
|
||||
inheritedModel?: string
|
||||
systemDefault: string
|
||||
systemDefault?: string
|
||||
}
|
||||
|
||||
export type ModelSource =
|
||||
@@ -24,7 +24,7 @@ export type ExtendedModelResolutionInput = {
|
||||
userModel?: string
|
||||
fallbackChain?: FallbackEntry[]
|
||||
availableModels: Set<string>
|
||||
systemDefaultModel: string
|
||||
systemDefaultModel?: string
|
||||
}
|
||||
|
||||
function normalizeModel(model?: string): string | undefined {
|
||||
@@ -32,7 +32,7 @@ function normalizeModel(model?: string): string | undefined {
|
||||
return trimmed || undefined
|
||||
}
|
||||
|
||||
export function resolveModel(input: ModelResolutionInput): string {
|
||||
export function resolveModel(input: ModelResolutionInput): string | undefined {
|
||||
return (
|
||||
normalizeModel(input.userModel) ??
|
||||
normalizeModel(input.inheritedModel) ??
|
||||
@@ -42,7 +42,7 @@ export function resolveModel(input: ModelResolutionInput): string {
|
||||
|
||||
export function resolveModelWithFallback(
|
||||
input: ExtendedModelResolutionInput,
|
||||
): ModelResolutionResult {
|
||||
): ModelResolutionResult | undefined {
|
||||
const { userModel, fallbackChain, availableModels, systemDefaultModel } = input
|
||||
|
||||
// Step 1: Override
|
||||
@@ -92,7 +92,12 @@ export function resolveModelWithFallback(
|
||||
log("No available model found in fallback chain, falling through to system default")
|
||||
}
|
||||
|
||||
// Step 4: System default
|
||||
// Step 3: System default (if provided)
|
||||
if (systemDefaultModel === undefined) {
|
||||
log("No model resolved - systemDefaultModel not configured")
|
||||
return undefined
|
||||
}
|
||||
|
||||
log("Model resolved via system default", { model: systemDefaultModel })
|
||||
return { model: systemDefaultModel, source: "system-default" }
|
||||
}
|
||||
|
||||
@@ -185,4 +185,237 @@ export const CATEGORY_DESCRIPTIONS: Record<string, string> = {
|
||||
writing: "Documentation, prose, technical writing",
|
||||
}
|
||||
|
||||
/**
|
||||
* System prompt prepended to plan agent invocations.
|
||||
* Instructs the plan agent to first gather context via explore/librarian agents,
|
||||
* then summarize user requirements and clarify uncertainties before proceeding.
|
||||
* Also MANDATES dependency graphs, parallel execution analysis, and category+skill recommendations.
|
||||
*/
|
||||
export const PLAN_AGENT_SYSTEM_PREPEND = `<system>
|
||||
BEFORE you begin planning, you MUST first understand the user's request deeply.
|
||||
|
||||
MANDATORY CONTEXT GATHERING PROTOCOL:
|
||||
1. Launch background agents to gather context:
|
||||
- call_omo_agent(description="Explore codebase patterns", subagent_type="explore", run_in_background=true, prompt="<search for relevant patterns, files, and implementations in the codebase related to user's request>")
|
||||
- call_omo_agent(description="Research documentation", subagent_type="librarian", run_in_background=true, prompt="<search for external documentation, examples, and best practices related to user's request>")
|
||||
|
||||
2. After gathering context, ALWAYS present:
|
||||
- **User Request Summary**: Concise restatement of what the user is asking for
|
||||
- **Uncertainties**: List of unclear points, ambiguities, or assumptions you're making
|
||||
- **Clarifying Questions**: Specific questions to resolve the uncertainties
|
||||
|
||||
3. ITERATE until ALL requirements are crystal clear:
|
||||
- Do NOT proceed to planning until you have 100% clarity
|
||||
- Ask the user to confirm your understanding
|
||||
- Resolve every ambiguity before generating the work plan
|
||||
|
||||
REMEMBER: Vague requirements lead to failed implementations. Take the time to understand thoroughly.
|
||||
</system>
|
||||
|
||||
<CRITICAL_REQUIREMENT_DEPENDENCY_PARALLEL_EXECUTION_CATEGORY_SKILLS>
|
||||
#####################################################################
|
||||
# #
|
||||
# ██████╗ ███████╗ ██████╗ ██╗ ██╗██╗██████╗ ███████╗██████╗ #
|
||||
# ██╔══██╗██╔════╝██╔═══██╗██║ ██║██║██╔══██╗██╔════╝██╔══██╗ #
|
||||
# ██████╔╝█████╗ ██║ ██║██║ ██║██║██████╔╝█████╗ ██║ ██║ #
|
||||
# ██╔══██╗██╔══╝ ██║▄▄ ██║██║ ██║██║██╔══██╗██╔══╝ ██║ ██║ #
|
||||
# ██<E29688><E29688> ██║███████╗╚██████╔╝╚██████╔╝██║██║ ██║███████╗██████╔╝ #
|
||||
# ╚═╝ ╚═╝╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═════╝ #
|
||||
# #
|
||||
#####################################################################
|
||||
|
||||
YOU MUST INCLUDE THE FOLLOWING SECTIONS IN YOUR PLAN OUTPUT.
|
||||
THIS IS NON-NEGOTIABLE. FAILURE TO INCLUDE THESE SECTIONS = INCOMPLETE PLAN.
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
█ SECTION 1: TASK DEPENDENCY GRAPH (MANDATORY) █
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
YOU MUST ANALYZE AND DOCUMENT TASK DEPENDENCIES.
|
||||
|
||||
For EVERY task in your plan, you MUST specify:
|
||||
- Which tasks it DEPENDS ON (blockers)
|
||||
- Which tasks DEPEND ON IT (dependents)
|
||||
- The REASON for each dependency
|
||||
|
||||
Example format:
|
||||
\`\`\`
|
||||
## Task Dependency Graph
|
||||
|
||||
| Task | Depends On | Reason |
|
||||
|------|------------|--------|
|
||||
| Task 1 | None | Starting point, no prerequisites |
|
||||
| Task 2 | Task 1 | Requires output/artifact from Task 1 |
|
||||
| Task 3 | Task 1 | Uses same foundation established in Task 1 |
|
||||
| Task 4 | Task 2, Task 3 | Integrates results from both tasks |
|
||||
\`\`\`
|
||||
|
||||
WHY THIS MATTERS:
|
||||
- Executors need to know execution ORDER
|
||||
- Prevents blocked work from starting prematurely
|
||||
- Identifies critical path for project timeline
|
||||
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
█ SECTION 2: PARALLEL EXECUTION GRAPH (MANDATORY) █
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
YOU MUST IDENTIFY WHICH TASKS CAN RUN IN PARALLEL.
|
||||
|
||||
Analyze your dependency graph and group tasks into PARALLEL EXECUTION WAVES:
|
||||
|
||||
Example format:
|
||||
\`\`\`
|
||||
## Parallel Execution Graph
|
||||
|
||||
Wave 1 (Start immediately):
|
||||
├── Task 1: [description] (no dependencies)
|
||||
└── Task 5: [description] (no dependencies)
|
||||
|
||||
Wave 2 (After Wave 1 completes):
|
||||
├── Task 2: [description] (depends: Task 1)
|
||||
├── Task 3: [description] (depends: Task 1)
|
||||
└── Task 6: [description] (depends: Task 5)
|
||||
|
||||
Wave 3 (After Wave 2 completes):
|
||||
└── Task 4: [description] (depends: Task 2, Task 3)
|
||||
|
||||
Critical Path: Task 1 → Task 2 → Task 4
|
||||
Estimated Parallel Speedup: 40% faster than sequential
|
||||
\`\`\`
|
||||
|
||||
WHY THIS MATTERS:
|
||||
- MASSIVE time savings through parallelization
|
||||
- Executors can dispatch multiple agents simultaneously
|
||||
- Identifies bottlenecks in the execution plan
|
||||
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
█ SECTION 3: CATEGORY + SKILLS RECOMMENDATIONS (MANDATORY) █
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
FOR EVERY TASK, YOU MUST RECOMMEND:
|
||||
1. Which CATEGORY to use for delegation
|
||||
2. Which SKILLS to load for the delegated agent
|
||||
|
||||
### AVAILABLE CATEGORIES
|
||||
|
||||
| Category | Best For | Model |
|
||||
|----------|----------|-------|
|
||||
| \`visual-engineering\` | Frontend, UI/UX, design, styling, animation | google/gemini-3-pro |
|
||||
| \`ultrabrain\` | Complex architecture, deep logical reasoning | openai/gpt-5.2-codex |
|
||||
| \`artistry\` | Highly creative/artistic tasks, novel ideas | google/gemini-3-pro |
|
||||
| \`quick\` | Trivial tasks - single file, typo fixes | anthropic/claude-haiku-4-5 |
|
||||
| \`unspecified-low\` | Moderate effort, doesn't fit other categories | anthropic/claude-sonnet-4-5 |
|
||||
| \`unspecified-high\` | High effort, doesn't fit other categories | anthropic/claude-opus-4-5 |
|
||||
| \`writing\` | Documentation, prose, technical writing | google/gemini-3-flash |
|
||||
|
||||
### AVAILABLE SKILLS (ALWAYS EVALUATE ALL)
|
||||
|
||||
Skills inject specialized expertise into the delegated agent.
|
||||
YOU MUST evaluate EVERY skill and justify inclusions/omissions.
|
||||
|
||||
| Skill | Domain |
|
||||
|-------|--------|
|
||||
| \`agent-browser\` | Browser automation, web testing |
|
||||
| \`frontend-ui-ux\` | Stunning UI/UX design |
|
||||
| \`git-master\` | Atomic commits, git operations |
|
||||
| \`dev-browser\` | Persistent browser state automation |
|
||||
| \`typescript-programmer\` | Production TypeScript code |
|
||||
| \`python-programmer\` | Production Python code |
|
||||
| \`svelte-programmer\` | Svelte components |
|
||||
| \`golang-tui-programmer\` | Go TUI with Charmbracelet |
|
||||
| \`python-debugger\` | Interactive Python debugging |
|
||||
| \`data-scientist\` | DuckDB/Polars data processing |
|
||||
| \`prompt-engineer\` | AI prompt optimization |
|
||||
|
||||
### REQUIRED OUTPUT FORMAT
|
||||
|
||||
For EACH task, include a recommendation block:
|
||||
|
||||
\`\`\`
|
||||
### Task N: [Task Title]
|
||||
|
||||
**Delegation Recommendation:**
|
||||
- Category: \`[category-name]\` - [reason for choice]
|
||||
- Skills: [\`skill-1\`, \`skill-2\`] - [reason each skill is needed]
|
||||
|
||||
**Skills Evaluation:**
|
||||
- INCLUDED \`skill-name\`: [reason]
|
||||
- OMITTED \`other-skill\`: [reason domain doesn't overlap]
|
||||
\`\`\`
|
||||
|
||||
WHY THIS MATTERS:
|
||||
- Category determines the MODEL used for execution
|
||||
- Skills inject SPECIALIZED KNOWLEDGE into the executor
|
||||
- Missing a relevant skill = suboptimal execution
|
||||
- Wrong category = wrong model = poor results
|
||||
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
█ RESPONSE FORMAT SPECIFICATION (MANDATORY) █
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
YOUR PLAN OUTPUT MUST FOLLOW THIS EXACT STRUCTURE:
|
||||
|
||||
\`\`\`markdown
|
||||
# [Plan Title]
|
||||
|
||||
## Context
|
||||
[User request summary, interview findings, research results]
|
||||
|
||||
## Task Dependency Graph
|
||||
[Dependency table - see Section 1]
|
||||
|
||||
## Parallel Execution Graph
|
||||
[Wave structure - see Section 2]
|
||||
|
||||
## Tasks
|
||||
|
||||
### Task 1: [Title]
|
||||
**Description**: [What to do]
|
||||
**Delegation Recommendation**:
|
||||
- Category: \`[category]\` - [reason]
|
||||
- Skills: [\`skill-1\`] - [reason]
|
||||
**Skills Evaluation**: [✅ included / ❌ omitted with reasons]
|
||||
**Depends On**: [Task IDs or "None"]
|
||||
**Acceptance Criteria**: [Verifiable conditions]
|
||||
|
||||
### Task 2: [Title]
|
||||
[Same structure...]
|
||||
|
||||
## Commit Strategy
|
||||
[How to commit changes atomically]
|
||||
|
||||
## Success Criteria
|
||||
[Final verification steps]
|
||||
\`\`\`
|
||||
|
||||
#####################################################################
|
||||
# #
|
||||
# FAILURE TO INCLUDE THESE SECTIONS = PLAN WILL BE REJECTED #
|
||||
# BY MOMUS REVIEW. DO NOT SKIP. DO NOT ABBREVIATE. #
|
||||
# #
|
||||
#####################################################################
|
||||
</CRITICAL_REQUIREMENT_DEPENDENCY_PARALLEL_EXECUTION_CATEGORY_SKILLS>
|
||||
|
||||
`
|
||||
|
||||
/**
|
||||
* List of agent names that should be treated as plan agents.
|
||||
* Case-insensitive matching is used.
|
||||
*/
|
||||
export const PLAN_AGENT_NAMES = ["plan", "prometheus", "planner"]
|
||||
|
||||
/**
|
||||
* Check if the given agent name is a plan agent.
|
||||
* @param agentName - The agent name to check
|
||||
* @returns true if the agent is a plan agent
|
||||
*/
|
||||
export function isPlanAgent(agentName: string | undefined): boolean {
|
||||
if (!agentName) return false
|
||||
const lowerName = agentName.toLowerCase().trim()
|
||||
return PLAN_AGENT_NAMES.some(name => lowerName === name || lowerName.includes(name))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, test, expect, beforeEach } from "bun:test"
|
||||
import { DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS, CATEGORY_DESCRIPTIONS } from "./constants"
|
||||
import { DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS, CATEGORY_DESCRIPTIONS, isPlanAgent, PLAN_AGENT_NAMES } from "./constants"
|
||||
import { resolveCategoryConfig } from "./tools"
|
||||
import type { CategoryConfig } from "../../config/schema"
|
||||
import { __resetModelCache } from "../../shared/model-availability"
|
||||
@@ -77,12 +77,93 @@ describe("sisyphus-task", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("isPlanAgent", () => {
|
||||
test("returns true for 'plan'", () => {
|
||||
// #given / #when
|
||||
const result = isPlanAgent("plan")
|
||||
|
||||
// #then
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for 'prometheus'", () => {
|
||||
// #given / #when
|
||||
const result = isPlanAgent("prometheus")
|
||||
|
||||
// #then
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for 'planner'", () => {
|
||||
// #given / #when
|
||||
const result = isPlanAgent("planner")
|
||||
|
||||
// #then
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for case-insensitive match 'PLAN'", () => {
|
||||
// #given / #when
|
||||
const result = isPlanAgent("PLAN")
|
||||
|
||||
// #then
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
test("returns true for case-insensitive match 'Prometheus'", () => {
|
||||
// #given / #when
|
||||
const result = isPlanAgent("Prometheus")
|
||||
|
||||
// #then
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
test("returns false for 'oracle'", () => {
|
||||
// #given / #when
|
||||
const result = isPlanAgent("oracle")
|
||||
|
||||
// #then
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for 'explore'", () => {
|
||||
// #given / #when
|
||||
const result = isPlanAgent("explore")
|
||||
|
||||
// #then
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for undefined", () => {
|
||||
// #given / #when
|
||||
const result = isPlanAgent(undefined)
|
||||
|
||||
// #then
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test("returns false for empty string", () => {
|
||||
// #given / #when
|
||||
const result = isPlanAgent("")
|
||||
|
||||
// #then
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test("PLAN_AGENT_NAMES contains expected values", () => {
|
||||
// #given / #when / #then
|
||||
expect(PLAN_AGENT_NAMES).toContain("plan")
|
||||
expect(PLAN_AGENT_NAMES).toContain("prometheus")
|
||||
expect(PLAN_AGENT_NAMES).toContain("planner")
|
||||
})
|
||||
})
|
||||
|
||||
describe("category delegation config validation", () => {
|
||||
test("returns error when systemDefaultModel is not configured", async () => {
|
||||
test("proceeds without error when systemDefaultModel is undefined", async () => {
|
||||
// #given a mock client with no model in config
|
||||
const { createDelegateTask } = require("./tools")
|
||||
|
||||
const mockManager = { launch: async () => ({}) }
|
||||
const mockManager = { launch: async () => ({ id: "task-123" }) }
|
||||
const mockClient = {
|
||||
app: { agents: async () => ({ data: [] }) },
|
||||
config: { get: async () => ({}) }, // No model configured
|
||||
@@ -111,14 +192,14 @@ describe("sisyphus-task", () => {
|
||||
description: "Test task",
|
||||
prompt: "Do something",
|
||||
category: "ultrabrain",
|
||||
run_in_background: false,
|
||||
load_skills: ["git-master"],
|
||||
run_in_background: true,
|
||||
load_skills: [],
|
||||
},
|
||||
toolContext
|
||||
)
|
||||
|
||||
// #then returns descriptive error message
|
||||
expect(result).toContain("oh-my-opencode requires a default model")
|
||||
// #then proceeds without error - uses fallback chain
|
||||
expect(result).not.toContain("oh-my-opencode requires a default model")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1481,6 +1562,87 @@ describe("sisyphus-task", () => {
|
||||
expect(result).toContain(categoryPromptAppend)
|
||||
expect(result).toContain("\n\n")
|
||||
})
|
||||
|
||||
test("prepends plan agent system prompt when agentName is 'plan'", () => {
|
||||
// #given
|
||||
const { buildSystemContent } = require("./tools")
|
||||
const { PLAN_AGENT_SYSTEM_PREPEND } = require("./constants")
|
||||
|
||||
// #when
|
||||
const result = buildSystemContent({ agentName: "plan" })
|
||||
|
||||
// #then
|
||||
expect(result).toContain("<system>")
|
||||
expect(result).toContain("MANDATORY CONTEXT GATHERING PROTOCOL")
|
||||
expect(result).toBe(PLAN_AGENT_SYSTEM_PREPEND)
|
||||
})
|
||||
|
||||
test("prepends plan agent system prompt when agentName is 'prometheus'", () => {
|
||||
// #given
|
||||
const { buildSystemContent } = require("./tools")
|
||||
const { PLAN_AGENT_SYSTEM_PREPEND } = require("./constants")
|
||||
|
||||
// #when
|
||||
const result = buildSystemContent({ agentName: "prometheus" })
|
||||
|
||||
// #then
|
||||
expect(result).toContain("<system>")
|
||||
expect(result).toBe(PLAN_AGENT_SYSTEM_PREPEND)
|
||||
})
|
||||
|
||||
test("prepends plan agent system prompt when agentName is 'Prometheus' (case insensitive)", () => {
|
||||
// #given
|
||||
const { buildSystemContent } = require("./tools")
|
||||
const { PLAN_AGENT_SYSTEM_PREPEND } = require("./constants")
|
||||
|
||||
// #when
|
||||
const result = buildSystemContent({ agentName: "Prometheus" })
|
||||
|
||||
// #then
|
||||
expect(result).toContain("<system>")
|
||||
expect(result).toBe(PLAN_AGENT_SYSTEM_PREPEND)
|
||||
})
|
||||
|
||||
test("combines plan agent prepend with skill content", () => {
|
||||
// #given
|
||||
const { buildSystemContent } = require("./tools")
|
||||
const { PLAN_AGENT_SYSTEM_PREPEND } = require("./constants")
|
||||
const skillContent = "You are a planning expert"
|
||||
|
||||
// #when
|
||||
const result = buildSystemContent({ skillContent, agentName: "plan" })
|
||||
|
||||
// #then
|
||||
expect(result).toContain(PLAN_AGENT_SYSTEM_PREPEND)
|
||||
expect(result).toContain(skillContent)
|
||||
expect(result!.indexOf(PLAN_AGENT_SYSTEM_PREPEND)).toBeLessThan(result!.indexOf(skillContent))
|
||||
})
|
||||
|
||||
test("does not prepend plan agent prompt for non-plan agents", () => {
|
||||
// #given
|
||||
const { buildSystemContent } = require("./tools")
|
||||
const skillContent = "You are an expert"
|
||||
|
||||
// #when
|
||||
const result = buildSystemContent({ skillContent, agentName: "oracle" })
|
||||
|
||||
// #then
|
||||
expect(result).toBe(skillContent)
|
||||
expect(result).not.toContain("<system>")
|
||||
})
|
||||
|
||||
test("does not prepend plan agent prompt when agentName is undefined", () => {
|
||||
// #given
|
||||
const { buildSystemContent } = require("./tools")
|
||||
const skillContent = "You are an expert"
|
||||
|
||||
// #when
|
||||
const result = buildSystemContent({ skillContent, agentName: undefined })
|
||||
|
||||
// #then
|
||||
expect(result).toBe(skillContent)
|
||||
expect(result).not.toContain("<system>")
|
||||
})
|
||||
})
|
||||
|
||||
describe("modelInfo detection via resolveCategoryConfig", () => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { join } from "node:path"
|
||||
import type { BackgroundManager } from "../../features/background-agent"
|
||||
import type { DelegateTaskArgs } from "./types"
|
||||
import type { CategoryConfig, CategoriesConfig, GitMasterConfig, BrowserAutomationProvider } from "../../config/schema"
|
||||
import { DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS, CATEGORY_DESCRIPTIONS } from "./constants"
|
||||
import { DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS, CATEGORY_DESCRIPTIONS, PLAN_AGENT_SYSTEM_PREPEND, isPlanAgent } from "./constants"
|
||||
import { findNearestMessageWithFields, findFirstMessageWithAgent, MESSAGE_STORAGE } from "../../features/hook-message-injector"
|
||||
import { resolveMultipleSkillsAsync } from "../../features/opencode-skill-loader/skill-content"
|
||||
import { discoverSkills } from "../../features/opencode-skill-loader"
|
||||
@@ -115,9 +115,9 @@ export function resolveCategoryConfig(
|
||||
options: {
|
||||
userCategories?: CategoriesConfig
|
||||
inheritedModel?: string
|
||||
systemDefaultModel: string
|
||||
systemDefaultModel?: string
|
||||
}
|
||||
): { config: CategoryConfig; promptAppend: string; model: string } | null {
|
||||
): { config: CategoryConfig; promptAppend: string; model: string | undefined } | null {
|
||||
const { userCategories, inheritedModel, systemDefaultModel } = options
|
||||
const defaultConfig = DEFAULT_CATEGORIES[categoryName]
|
||||
const userConfig = userCategories?.[categoryName]
|
||||
@@ -171,20 +171,33 @@ export interface DelegateTaskToolOptions {
|
||||
export interface BuildSystemContentInput {
|
||||
skillContent?: string
|
||||
categoryPromptAppend?: string
|
||||
agentName?: string
|
||||
}
|
||||
|
||||
export function buildSystemContent(input: BuildSystemContentInput): string | undefined {
|
||||
const { skillContent, categoryPromptAppend } = input
|
||||
const { skillContent, categoryPromptAppend, agentName } = input
|
||||
|
||||
if (!skillContent && !categoryPromptAppend) {
|
||||
const planAgentPrepend = isPlanAgent(agentName) ? PLAN_AGENT_SYSTEM_PREPEND : ""
|
||||
|
||||
if (!skillContent && !categoryPromptAppend && !planAgentPrepend) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (skillContent && categoryPromptAppend) {
|
||||
return `${skillContent}\n\n${categoryPromptAppend}`
|
||||
const parts: string[] = []
|
||||
|
||||
if (planAgentPrepend) {
|
||||
parts.push(planAgentPrepend)
|
||||
}
|
||||
|
||||
return skillContent || categoryPromptAppend
|
||||
if (skillContent) {
|
||||
parts.push(skillContent)
|
||||
}
|
||||
|
||||
if (categoryPromptAppend) {
|
||||
parts.push(categoryPromptAppend)
|
||||
}
|
||||
|
||||
return parts.join("\n\n") || undefined
|
||||
}
|
||||
|
||||
export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefinition {
|
||||
@@ -382,6 +395,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
|
||||
task: false,
|
||||
delegate_task: false,
|
||||
call_omo_agent: true,
|
||||
question: false,
|
||||
},
|
||||
parts: [{ type: "text", text: args.prompt }],
|
||||
},
|
||||
@@ -497,17 +511,6 @@ To continue this session: session_id="${args.session_id}"`
|
||||
let modelInfo: ModelFallbackInfo | undefined
|
||||
|
||||
if (args.category) {
|
||||
// Guard: require system default model for category delegation
|
||||
if (!systemDefaultModel) {
|
||||
const paths = getOpenCodeConfigPaths({ binary: "opencode", version: null })
|
||||
return (
|
||||
'oh-my-opencode requires a default model.\n\n' +
|
||||
`Add this to ${paths.configJsonc}:\n\n` +
|
||||
' "model": "anthropic/claude-sonnet-4-5"\n\n' +
|
||||
'(Replace with your preferred provider/model)'
|
||||
)
|
||||
}
|
||||
|
||||
const connectedProviders = readConnectedProvidersCache()
|
||||
const availableModels = await fetchAvailableModels(client, {
|
||||
connectedProviders: connectedProviders ?? undefined
|
||||
@@ -523,55 +526,60 @@ To continue this session: session_id="${args.session_id}"`
|
||||
}
|
||||
|
||||
const requirement = CATEGORY_MODEL_REQUIREMENTS[args.category]
|
||||
let actualModel: string
|
||||
let actualModel: string | undefined
|
||||
|
||||
if (!requirement) {
|
||||
actualModel = resolved.model
|
||||
modelInfo = { model: actualModel, type: "system-default", source: "system-default" }
|
||||
if (actualModel) {
|
||||
modelInfo = { model: actualModel, type: "system-default", source: "system-default" }
|
||||
}
|
||||
} else {
|
||||
const { model: resolvedModel, source, variant: resolvedVariant } = resolveModelWithFallback({
|
||||
const resolution = resolveModelWithFallback({
|
||||
userModel: userCategories?.[args.category]?.model ?? sisyphusJuniorModel,
|
||||
fallbackChain: requirement.fallbackChain,
|
||||
availableModels,
|
||||
systemDefaultModel,
|
||||
})
|
||||
|
||||
actualModel = resolvedModel
|
||||
if (resolution) {
|
||||
const { model: resolvedModel, source, variant: resolvedVariant } = resolution
|
||||
actualModel = resolvedModel
|
||||
|
||||
if (!parseModelString(actualModel)) {
|
||||
return `Invalid model format "${actualModel}". Expected "provider/model" format (e.g., "anthropic/claude-sonnet-4-5").`
|
||||
if (!parseModelString(actualModel)) {
|
||||
return `Invalid model format "${actualModel}". Expected "provider/model" format (e.g., "anthropic/claude-sonnet-4-5").`
|
||||
}
|
||||
|
||||
let type: "user-defined" | "inherited" | "category-default" | "system-default"
|
||||
switch (source) {
|
||||
case "override":
|
||||
type = "user-defined"
|
||||
break
|
||||
case "provider-fallback":
|
||||
type = "category-default"
|
||||
break
|
||||
case "system-default":
|
||||
type = "system-default"
|
||||
break
|
||||
}
|
||||
|
||||
modelInfo = { model: actualModel, type, source }
|
||||
|
||||
const parsedModel = parseModelString(actualModel)
|
||||
const variantToUse = userCategories?.[args.category]?.variant ?? resolvedVariant
|
||||
categoryModel = parsedModel
|
||||
? (variantToUse ? { ...parsedModel, variant: variantToUse } : parsedModel)
|
||||
: undefined
|
||||
}
|
||||
|
||||
let type: "user-defined" | "inherited" | "category-default" | "system-default"
|
||||
switch (source) {
|
||||
case "override":
|
||||
type = "user-defined"
|
||||
break
|
||||
case "provider-fallback":
|
||||
type = "category-default"
|
||||
break
|
||||
case "system-default":
|
||||
type = "system-default"
|
||||
break
|
||||
}
|
||||
|
||||
modelInfo = { model: actualModel, type, source }
|
||||
|
||||
const parsedModel = parseModelString(actualModel)
|
||||
const variantToUse = userCategories?.[args.category]?.variant ?? resolvedVariant
|
||||
categoryModel = parsedModel
|
||||
? (variantToUse ? { ...parsedModel, variant: variantToUse } : parsedModel)
|
||||
: undefined
|
||||
}
|
||||
|
||||
agentToUse = SISYPHUS_JUNIOR_AGENT
|
||||
if (!categoryModel) {
|
||||
if (!categoryModel && actualModel) {
|
||||
const parsedModel = parseModelString(actualModel)
|
||||
categoryModel = parsedModel ?? undefined
|
||||
}
|
||||
categoryPromptAppend = resolved.promptAppend || undefined
|
||||
|
||||
const isUnstableAgent = resolved.config.is_unstable_agent === true || actualModel.toLowerCase().includes("gemini")
|
||||
const isUnstableAgent = resolved.config.is_unstable_agent === true || (actualModel?.toLowerCase().includes("gemini") ?? false)
|
||||
// Handle both boolean false and string "false" due to potential serialization
|
||||
const isRunInBackgroundExplicitlyFalse = args.run_in_background === false || args.run_in_background === "false" as unknown as boolean
|
||||
|
||||
@@ -586,7 +594,7 @@ To continue this session: session_id="${args.session_id}"`
|
||||
})
|
||||
|
||||
if (isUnstableAgent && isRunInBackgroundExplicitlyFalse) {
|
||||
const systemContent = buildSystemContent({ skillContent, categoryPromptAppend })
|
||||
const systemContent = buildSystemContent({ skillContent, categoryPromptAppend, agentName: agentToUse })
|
||||
|
||||
try {
|
||||
const task = await manager.launch({
|
||||
@@ -778,7 +786,7 @@ Sisyphus-Junior is spawned automatically when you specify a category. Pick the a
|
||||
}
|
||||
}
|
||||
|
||||
const systemContent = buildSystemContent({ skillContent, categoryPromptAppend })
|
||||
const systemContent = buildSystemContent({ skillContent, categoryPromptAppend, agentName: agentToUse })
|
||||
|
||||
if (runInBackground) {
|
||||
try {
|
||||
@@ -909,6 +917,7 @@ To continue this session: session_id="${task.sessionID}"`
|
||||
task: false,
|
||||
delegate_task: false,
|
||||
call_omo_agent: true,
|
||||
question: false,
|
||||
},
|
||||
parts: [{ type: "text", text: args.prompt }],
|
||||
...(categoryModel ? { model: categoryModel } : {}),
|
||||
|
||||
Reference in New Issue
Block a user