oh-my-opencode 3.0.0-beta.2 → 3.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +49 -38
- package/README.md +32 -27
- package/README.zh-cn.md +50 -41
- package/dist/agents/orchestrator-sisyphus.d.ts +1 -1
- package/dist/cli/index.js +10 -6
- package/dist/features/background-agent/manager.d.ts +15 -0
- package/dist/features/background-agent/types.d.ts +4 -0
- package/dist/features/context-injector/injector.d.ts +1 -1
- package/dist/hooks/background-compaction/index.d.ts +19 -0
- package/dist/hooks/background-notification/index.d.ts +6 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/keyword-detector/index.d.ts +2 -1
- package/dist/hooks/prometheus-md-only/constants.d.ts +1 -1
- package/dist/hooks/prometheus-md-only/index.d.ts +1 -1
- package/dist/index.js +780 -468
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/lsp/client.d.ts +1 -0
- package/dist/tools/skill/tools.d.ts +1 -7
- package/dist/tools/slashcommand/tools.d.ts +1 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -818,7 +818,7 @@ var require_scan = __commonJS((exports, module) => {
|
|
|
818
818
|
|
|
819
819
|
// node_modules/picomatch/lib/parse.js
|
|
820
820
|
var require_parse = __commonJS((exports, module) => {
|
|
821
|
-
var
|
|
821
|
+
var constants = require_constants();
|
|
822
822
|
var utils = require_utils();
|
|
823
823
|
var {
|
|
824
824
|
MAX_LENGTH,
|
|
@@ -826,7 +826,7 @@ var require_parse = __commonJS((exports, module) => {
|
|
|
826
826
|
REGEX_NON_SPECIAL_CHARS,
|
|
827
827
|
REGEX_SPECIAL_CHARS_BACKREF,
|
|
828
828
|
REPLACEMENTS
|
|
829
|
-
} =
|
|
829
|
+
} = constants;
|
|
830
830
|
var expandRange = (args, options) => {
|
|
831
831
|
if (typeof options.expandRange === "function") {
|
|
832
832
|
return options.expandRange(...args, options);
|
|
@@ -857,8 +857,8 @@ var require_parse = __commonJS((exports, module) => {
|
|
|
857
857
|
const bos = { type: "bos", value: "", output: opts.prepend || "" };
|
|
858
858
|
const tokens = [bos];
|
|
859
859
|
const capture = opts.capture ? "" : "?:";
|
|
860
|
-
const PLATFORM_CHARS =
|
|
861
|
-
const EXTGLOB_CHARS =
|
|
860
|
+
const PLATFORM_CHARS = constants.globChars(opts.windows);
|
|
861
|
+
const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
|
|
862
862
|
const {
|
|
863
863
|
DOT_LITERAL,
|
|
864
864
|
PLUS_LITERAL,
|
|
@@ -1536,7 +1536,7 @@ var require_parse = __commonJS((exports, module) => {
|
|
|
1536
1536
|
NO_DOTS_SLASH,
|
|
1537
1537
|
STAR,
|
|
1538
1538
|
START_ANCHOR
|
|
1539
|
-
} =
|
|
1539
|
+
} = constants.globChars(opts.windows);
|
|
1540
1540
|
const nodot = opts.dot ? NO_DOTS : NO_DOT;
|
|
1541
1541
|
const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
|
|
1542
1542
|
const capture = opts.capture ? "" : "?:";
|
|
@@ -1594,7 +1594,7 @@ var require_picomatch = __commonJS((exports, module) => {
|
|
|
1594
1594
|
var scan = require_scan();
|
|
1595
1595
|
var parse3 = require_parse();
|
|
1596
1596
|
var utils = require_utils();
|
|
1597
|
-
var
|
|
1597
|
+
var constants = require_constants();
|
|
1598
1598
|
var isObject2 = (val) => val && typeof val === "object" && !Array.isArray(val);
|
|
1599
1599
|
var picomatch = (glob, options, returnState = false) => {
|
|
1600
1600
|
if (Array.isArray(glob)) {
|
|
@@ -1725,7 +1725,7 @@ var require_picomatch = __commonJS((exports, module) => {
|
|
|
1725
1725
|
return /$^/;
|
|
1726
1726
|
}
|
|
1727
1727
|
};
|
|
1728
|
-
picomatch.constants =
|
|
1728
|
+
picomatch.constants = constants;
|
|
1729
1729
|
module.exports = picomatch;
|
|
1730
1730
|
});
|
|
1731
1731
|
|
|
@@ -4757,7 +4757,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
4757
4757
|
const schOrFunc = root.refs[ref];
|
|
4758
4758
|
if (schOrFunc)
|
|
4759
4759
|
return schOrFunc;
|
|
4760
|
-
let _sch =
|
|
4760
|
+
let _sch = resolve10.call(this, root, ref);
|
|
4761
4761
|
if (_sch === undefined) {
|
|
4762
4762
|
const schema2 = (_a = root.localRefs) === null || _a === undefined ? undefined : _a[ref];
|
|
4763
4763
|
const { schemaId } = this.opts;
|
|
@@ -4784,7 +4784,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
4784
4784
|
function sameSchemaEnv(s1, s2) {
|
|
4785
4785
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
4786
4786
|
}
|
|
4787
|
-
function
|
|
4787
|
+
function resolve10(root, ref) {
|
|
4788
4788
|
let sch;
|
|
4789
4789
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
4790
4790
|
ref = sch;
|
|
@@ -5314,55 +5314,55 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
5314
5314
|
}
|
|
5315
5315
|
return uri;
|
|
5316
5316
|
}
|
|
5317
|
-
function
|
|
5317
|
+
function resolve10(baseURI, relativeURI, options) {
|
|
5318
5318
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
5319
5319
|
const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
5320
5320
|
schemelessOptions.skipEscape = true;
|
|
5321
5321
|
return serialize(resolved, schemelessOptions);
|
|
5322
5322
|
}
|
|
5323
|
-
function resolveComponent(base,
|
|
5323
|
+
function resolveComponent(base, relative5, options, skipNormalization) {
|
|
5324
5324
|
const target = {};
|
|
5325
5325
|
if (!skipNormalization) {
|
|
5326
5326
|
base = parse7(serialize(base, options), options);
|
|
5327
|
-
|
|
5327
|
+
relative5 = parse7(serialize(relative5, options), options);
|
|
5328
5328
|
}
|
|
5329
5329
|
options = options || {};
|
|
5330
|
-
if (!options.tolerant &&
|
|
5331
|
-
target.scheme =
|
|
5332
|
-
target.userinfo =
|
|
5333
|
-
target.host =
|
|
5334
|
-
target.port =
|
|
5335
|
-
target.path = removeDotSegments(
|
|
5336
|
-
target.query =
|
|
5330
|
+
if (!options.tolerant && relative5.scheme) {
|
|
5331
|
+
target.scheme = relative5.scheme;
|
|
5332
|
+
target.userinfo = relative5.userinfo;
|
|
5333
|
+
target.host = relative5.host;
|
|
5334
|
+
target.port = relative5.port;
|
|
5335
|
+
target.path = removeDotSegments(relative5.path || "");
|
|
5336
|
+
target.query = relative5.query;
|
|
5337
5337
|
} else {
|
|
5338
|
-
if (
|
|
5339
|
-
target.userinfo =
|
|
5340
|
-
target.host =
|
|
5341
|
-
target.port =
|
|
5342
|
-
target.path = removeDotSegments(
|
|
5343
|
-
target.query =
|
|
5338
|
+
if (relative5.userinfo !== undefined || relative5.host !== undefined || relative5.port !== undefined) {
|
|
5339
|
+
target.userinfo = relative5.userinfo;
|
|
5340
|
+
target.host = relative5.host;
|
|
5341
|
+
target.port = relative5.port;
|
|
5342
|
+
target.path = removeDotSegments(relative5.path || "");
|
|
5343
|
+
target.query = relative5.query;
|
|
5344
5344
|
} else {
|
|
5345
|
-
if (!
|
|
5345
|
+
if (!relative5.path) {
|
|
5346
5346
|
target.path = base.path;
|
|
5347
|
-
if (
|
|
5348
|
-
target.query =
|
|
5347
|
+
if (relative5.query !== undefined) {
|
|
5348
|
+
target.query = relative5.query;
|
|
5349
5349
|
} else {
|
|
5350
5350
|
target.query = base.query;
|
|
5351
5351
|
}
|
|
5352
5352
|
} else {
|
|
5353
|
-
if (
|
|
5354
|
-
target.path = removeDotSegments(
|
|
5353
|
+
if (relative5.path[0] === "/") {
|
|
5354
|
+
target.path = removeDotSegments(relative5.path);
|
|
5355
5355
|
} else {
|
|
5356
5356
|
if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) {
|
|
5357
|
-
target.path = "/" +
|
|
5357
|
+
target.path = "/" + relative5.path;
|
|
5358
5358
|
} else if (!base.path) {
|
|
5359
|
-
target.path =
|
|
5359
|
+
target.path = relative5.path;
|
|
5360
5360
|
} else {
|
|
5361
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
5361
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative5.path;
|
|
5362
5362
|
}
|
|
5363
5363
|
target.path = removeDotSegments(target.path);
|
|
5364
5364
|
}
|
|
5365
|
-
target.query =
|
|
5365
|
+
target.query = relative5.query;
|
|
5366
5366
|
}
|
|
5367
5367
|
target.userinfo = base.userinfo;
|
|
5368
5368
|
target.host = base.host;
|
|
@@ -5370,7 +5370,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
5370
5370
|
}
|
|
5371
5371
|
target.scheme = base.scheme;
|
|
5372
5372
|
}
|
|
5373
|
-
target.fragment =
|
|
5373
|
+
target.fragment = relative5.fragment;
|
|
5374
5374
|
return target;
|
|
5375
5375
|
}
|
|
5376
5376
|
function equal(uriA, uriB, options) {
|
|
@@ -5542,7 +5542,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
5542
5542
|
var fastUri = {
|
|
5543
5543
|
SCHEMES,
|
|
5544
5544
|
normalize,
|
|
5545
|
-
resolve:
|
|
5545
|
+
resolve: resolve10,
|
|
5546
5546
|
resolveComponent,
|
|
5547
5547
|
equal,
|
|
5548
5548
|
serialize,
|
|
@@ -8423,12 +8423,12 @@ var require_isexe = __commonJS((exports, module) => {
|
|
|
8423
8423
|
if (typeof Promise !== "function") {
|
|
8424
8424
|
throw new TypeError("callback not provided");
|
|
8425
8425
|
}
|
|
8426
|
-
return new Promise(function(
|
|
8426
|
+
return new Promise(function(resolve10, reject) {
|
|
8427
8427
|
isexe(path9, options || {}, function(er, is) {
|
|
8428
8428
|
if (er) {
|
|
8429
8429
|
reject(er);
|
|
8430
8430
|
} else {
|
|
8431
|
-
|
|
8431
|
+
resolve10(is);
|
|
8432
8432
|
}
|
|
8433
8433
|
});
|
|
8434
8434
|
});
|
|
@@ -8490,27 +8490,27 @@ var require_which = __commonJS((exports, module) => {
|
|
|
8490
8490
|
opt = {};
|
|
8491
8491
|
const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
|
|
8492
8492
|
const found = [];
|
|
8493
|
-
const step = (i2) => new Promise((
|
|
8493
|
+
const step = (i2) => new Promise((resolve10, reject) => {
|
|
8494
8494
|
if (i2 === pathEnv.length)
|
|
8495
|
-
return opt.all && found.length ?
|
|
8495
|
+
return opt.all && found.length ? resolve10(found) : reject(getNotFoundError(cmd));
|
|
8496
8496
|
const ppRaw = pathEnv[i2];
|
|
8497
8497
|
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
8498
8498
|
const pCmd = path9.join(pathPart, cmd);
|
|
8499
8499
|
const p2 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
8500
|
-
|
|
8500
|
+
resolve10(subStep(p2, i2, 0));
|
|
8501
8501
|
});
|
|
8502
|
-
const subStep = (p2, i2, ii) => new Promise((
|
|
8502
|
+
const subStep = (p2, i2, ii) => new Promise((resolve10, reject) => {
|
|
8503
8503
|
if (ii === pathExt.length)
|
|
8504
|
-
return
|
|
8504
|
+
return resolve10(step(i2 + 1));
|
|
8505
8505
|
const ext = pathExt[ii];
|
|
8506
8506
|
isexe(p2 + ext, { pathExt: pathExtExe }, (er, is) => {
|
|
8507
8507
|
if (!er && is) {
|
|
8508
8508
|
if (opt.all)
|
|
8509
8509
|
found.push(p2 + ext);
|
|
8510
8510
|
else
|
|
8511
|
-
return
|
|
8511
|
+
return resolve10(p2 + ext);
|
|
8512
8512
|
}
|
|
8513
|
-
return
|
|
8513
|
+
return resolve10(subStep(p2, i2, ii + 1));
|
|
8514
8514
|
});
|
|
8515
8515
|
});
|
|
8516
8516
|
return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
|
|
@@ -17053,7 +17053,7 @@ function migrateConfigFile(configPath, rawConfig) {
|
|
|
17053
17053
|
// src/shared/opencode-config-dir.ts
|
|
17054
17054
|
import { existsSync as existsSync21 } from "fs";
|
|
17055
17055
|
import { homedir as homedir6 } from "os";
|
|
17056
|
-
import { join as join26 } from "path";
|
|
17056
|
+
import { join as join26, resolve as resolve4 } from "path";
|
|
17057
17057
|
var TAURI_APP_IDENTIFIER = "ai.opencode.desktop";
|
|
17058
17058
|
var TAURI_APP_IDENTIFIER_DEV = "ai.opencode.desktop.dev";
|
|
17059
17059
|
function isDevBuild(version) {
|
|
@@ -17078,6 +17078,10 @@ function getTauriConfigDir(identifier) {
|
|
|
17078
17078
|
}
|
|
17079
17079
|
}
|
|
17080
17080
|
function getCliConfigDir() {
|
|
17081
|
+
const envConfigDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
17082
|
+
if (envConfigDir) {
|
|
17083
|
+
return resolve4(envConfigDir);
|
|
17084
|
+
}
|
|
17081
17085
|
if (process.platform === "win32") {
|
|
17082
17086
|
const crossPlatformDir = join26(homedir6(), ".config", "opencode");
|
|
17083
17087
|
const crossPlatformConfig = join26(crossPlatformDir, "opencode.json");
|
|
@@ -18110,278 +18114,6 @@ setInterval(() => {
|
|
|
18110
18114
|
}
|
|
18111
18115
|
}, CACHE_TTL);
|
|
18112
18116
|
|
|
18113
|
-
// src/hooks/keyword-detector/constants.ts
|
|
18114
|
-
var CODE_BLOCK_PATTERN2 = /```[\s\S]*?```/g;
|
|
18115
|
-
var INLINE_CODE_PATTERN2 = /`[^`]+`/g;
|
|
18116
|
-
var ULTRAWORK_PLANNER_SECTION = `## CRITICAL: YOU ARE A PLANNER, NOT AN IMPLEMENTER
|
|
18117
|
-
|
|
18118
|
-
**IDENTITY CONSTRAINT (NON-NEGOTIABLE):**
|
|
18119
|
-
You ARE the planner. You ARE NOT an implementer. You DO NOT write code. You DO NOT execute tasks.
|
|
18120
|
-
|
|
18121
|
-
**TOOL RESTRICTIONS (SYSTEM-ENFORCED):**
|
|
18122
|
-
| Tool | Allowed | Blocked |
|
|
18123
|
-
|------|---------|---------|
|
|
18124
|
-
| Write/Edit | \`.sisyphus/**/*.md\` ONLY | Everything else |
|
|
18125
|
-
| Read | All files | - |
|
|
18126
|
-
| Bash | Research commands only | Implementation commands |
|
|
18127
|
-
| sisyphus_task | explore, librarian | - |
|
|
18128
|
-
|
|
18129
|
-
**IF YOU TRY TO WRITE/EDIT OUTSIDE \`.sisyphus/\`:**
|
|
18130
|
-
- System will BLOCK your action
|
|
18131
|
-
- You will receive an error
|
|
18132
|
-
- DO NOT retry - you are not supposed to implement
|
|
18133
|
-
|
|
18134
|
-
**YOUR ONLY WRITABLE PATHS:**
|
|
18135
|
-
- \`.sisyphus/plans/*.md\` - Final work plans
|
|
18136
|
-
- \`.sisyphus/drafts/*.md\` - Working drafts during interview
|
|
18137
|
-
|
|
18138
|
-
**WHEN USER ASKS YOU TO IMPLEMENT:**
|
|
18139
|
-
REFUSE. Say: "I'm a planner. I create work plans, not implementations. Run \`/start-work\` after I finish planning."
|
|
18140
|
-
|
|
18141
|
-
---
|
|
18142
|
-
|
|
18143
|
-
## CONTEXT GATHERING (MANDATORY BEFORE PLANNING)
|
|
18144
|
-
|
|
18145
|
-
You ARE the planner. Your job: create bulletproof work plans.
|
|
18146
|
-
**Before drafting ANY plan, gather context via explore/librarian agents.**
|
|
18147
|
-
|
|
18148
|
-
### Research Protocol
|
|
18149
|
-
1. **Fire parallel background agents** for comprehensive context:
|
|
18150
|
-
\`\`\`
|
|
18151
|
-
sisyphus_task(agent="explore", prompt="Find existing patterns for [topic] in codebase", background=true)
|
|
18152
|
-
sisyphus_task(agent="explore", prompt="Find test infrastructure and conventions", background=true)
|
|
18153
|
-
sisyphus_task(agent="librarian", prompt="Find official docs and best practices for [technology]", background=true)
|
|
18154
|
-
\`\`\`
|
|
18155
|
-
2. **Wait for results** before planning - rushed plans fail
|
|
18156
|
-
3. **Synthesize findings** into informed requirements
|
|
18157
|
-
|
|
18158
|
-
### What to Research
|
|
18159
|
-
- Existing codebase patterns and conventions
|
|
18160
|
-
- Test infrastructure (TDD possible?)
|
|
18161
|
-
- External library APIs and constraints
|
|
18162
|
-
- Similar implementations in OSS (via librarian)
|
|
18163
|
-
|
|
18164
|
-
**NEVER plan blind. Context first, plan second.**`;
|
|
18165
|
-
function isPlannerAgent(agentName) {
|
|
18166
|
-
if (!agentName)
|
|
18167
|
-
return false;
|
|
18168
|
-
const lowerName = agentName.toLowerCase();
|
|
18169
|
-
return lowerName.includes("prometheus") || lowerName.includes("planner") || lowerName === "plan";
|
|
18170
|
-
}
|
|
18171
|
-
function getUltraworkMessage(agentName) {
|
|
18172
|
-
const isPlanner = isPlannerAgent(agentName);
|
|
18173
|
-
if (isPlanner) {
|
|
18174
|
-
return `<ultrawork-mode>
|
|
18175
|
-
|
|
18176
|
-
**MANDATORY**: You MUST say "ULTRAWORK MODE ENABLED!" to the user as your first response when this mode activates. This is non-negotiable.
|
|
18177
|
-
|
|
18178
|
-
${ULTRAWORK_PLANNER_SECTION}
|
|
18179
|
-
|
|
18180
|
-
</ultrawork-mode>
|
|
18181
|
-
|
|
18182
|
-
---
|
|
18183
|
-
|
|
18184
|
-
`;
|
|
18185
|
-
}
|
|
18186
|
-
return `<ultrawork-mode>
|
|
18187
|
-
|
|
18188
|
-
**MANDATORY**: You MUST say "ULTRAWORK MODE ENABLED!" to the user as your first response when this mode activates. This is non-negotiable.
|
|
18189
|
-
|
|
18190
|
-
[CODE RED] Maximum precision required. Ultrathink before acting.
|
|
18191
|
-
|
|
18192
|
-
YOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.
|
|
18193
|
-
TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
|
|
18194
|
-
|
|
18195
|
-
## AGENT UTILIZATION PRINCIPLES (by capability, not by name)
|
|
18196
|
-
- **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS for file patterns, internal implementations, project structure
|
|
18197
|
-
- **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS for API references, examples, external library docs
|
|
18198
|
-
- **Planning & Strategy**: NEVER plan yourself - ALWAYS spawn a dedicated planning agent for work breakdown
|
|
18199
|
-
- **High-IQ Reasoning**: Leverage specialized agents for architecture decisions, code review, strategic planning
|
|
18200
|
-
- **Frontend/UI Tasks**: Delegate to UI-specialized agents for design and implementation
|
|
18201
|
-
|
|
18202
|
-
## EXECUTION RULES
|
|
18203
|
-
- **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each.
|
|
18204
|
-
- **PARALLEL**: Fire independent agent calls simultaneously via sisyphus_task(background=true) - NEVER wait sequentially.
|
|
18205
|
-
- **BACKGROUND FIRST**: Use sisyphus_task for exploration/research agents (10+ concurrent if needed).
|
|
18206
|
-
- **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done.
|
|
18207
|
-
- **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths.
|
|
18208
|
-
|
|
18209
|
-
## WORKFLOW
|
|
18210
|
-
1. Analyze the request and identify required capabilities
|
|
18211
|
-
2. Spawn exploration/librarian agents via sisyphus_task(background=true) in PARALLEL (10+ if needed)
|
|
18212
|
-
3. Always Use Plan agent with gathered context to create detailed work breakdown
|
|
18213
|
-
4. Execute with continuous verification against original requirements
|
|
18214
|
-
|
|
18215
|
-
## VERIFICATION GUARANTEE (NON-NEGOTIABLE)
|
|
18216
|
-
|
|
18217
|
-
**NOTHING is "done" without PROOF it works.**
|
|
18218
|
-
|
|
18219
|
-
### Pre-Implementation: Define Success Criteria
|
|
18220
|
-
|
|
18221
|
-
BEFORE writing ANY code, you MUST define:
|
|
18222
|
-
|
|
18223
|
-
| Criteria Type | Description | Example |
|
|
18224
|
-
|---------------|-------------|---------|
|
|
18225
|
-
| **Functional** | What specific behavior must work | "Button click triggers API call" |
|
|
18226
|
-
| **Observable** | What can be measured/seen | "Console shows 'success', no errors" |
|
|
18227
|
-
| **Pass/Fail** | Binary, no ambiguity | "Returns 200 OK" not "should work" |
|
|
18228
|
-
|
|
18229
|
-
Write these criteria explicitly. Share with user if scope is non-trivial.
|
|
18230
|
-
|
|
18231
|
-
### Test Plan Template (MANDATORY for non-trivial tasks)
|
|
18232
|
-
|
|
18233
|
-
\`\`\`
|
|
18234
|
-
## Test Plan
|
|
18235
|
-
### Objective: [What we're verifying]
|
|
18236
|
-
### Prerequisites: [Setup needed]
|
|
18237
|
-
### Test Cases:
|
|
18238
|
-
1. [Test Name]: [Input] \u2192 [Expected Output] \u2192 [How to verify]
|
|
18239
|
-
2. ...
|
|
18240
|
-
### Success Criteria: ALL test cases pass
|
|
18241
|
-
### How to Execute: [Exact commands/steps]
|
|
18242
|
-
\`\`\`
|
|
18243
|
-
|
|
18244
|
-
### Execution & Evidence Requirements
|
|
18245
|
-
|
|
18246
|
-
| Phase | Action | Required Evidence |
|
|
18247
|
-
|-------|--------|-------------------|
|
|
18248
|
-
| **Build** | Run build command | Exit code 0, no errors |
|
|
18249
|
-
| **Test** | Execute test suite | All tests pass (screenshot/output) |
|
|
18250
|
-
| **Manual Verify** | Test the actual feature | Demonstrate it works (describe what you observed) |
|
|
18251
|
-
| **Regression** | Ensure nothing broke | Existing tests still pass |
|
|
18252
|
-
|
|
18253
|
-
**WITHOUT evidence = NOT verified = NOT done.**
|
|
18254
|
-
|
|
18255
|
-
### TDD Workflow (when test infrastructure exists)
|
|
18256
|
-
|
|
18257
|
-
1. **SPEC**: Define what "working" means (success criteria above)
|
|
18258
|
-
2. **RED**: Write failing test \u2192 Run it \u2192 Confirm it FAILS
|
|
18259
|
-
3. **GREEN**: Write minimal code \u2192 Run test \u2192 Confirm it PASSES
|
|
18260
|
-
4. **REFACTOR**: Clean up \u2192 Tests MUST stay green
|
|
18261
|
-
5. **VERIFY**: Run full test suite, confirm no regressions
|
|
18262
|
-
6. **EVIDENCE**: Report what you ran and what output you saw
|
|
18263
|
-
|
|
18264
|
-
### Verification Anti-Patterns (BLOCKING)
|
|
18265
|
-
|
|
18266
|
-
| Violation | Why It Fails |
|
|
18267
|
-
|-----------|--------------|
|
|
18268
|
-
| "It should work now" | No evidence. Run it. |
|
|
18269
|
-
| "I added the tests" | Did they pass? Show output. |
|
|
18270
|
-
| "Fixed the bug" | How do you know? What did you test? |
|
|
18271
|
-
| "Implementation complete" | Did you verify against success criteria? |
|
|
18272
|
-
| Skipping test execution | Tests exist to be RUN, not just written |
|
|
18273
|
-
|
|
18274
|
-
**CLAIM NOTHING WITHOUT PROOF. EXECUTE. VERIFY. SHOW EVIDENCE.**
|
|
18275
|
-
|
|
18276
|
-
## ZERO TOLERANCE FAILURES
|
|
18277
|
-
- **NO Scope Reduction**: Never make "demo", "skeleton", "simplified", "basic" versions - deliver FULL implementation
|
|
18278
|
-
- **NO MockUp Work**: When user asked you to do "port A", you must "port A", fully, 100%. No Extra feature, No reduced feature, no mock data, fully working 100% port.
|
|
18279
|
-
- **NO Partial Completion**: Never stop at 60-80% saying "you can extend this..." - finish 100%
|
|
18280
|
-
- **NO Assumed Shortcuts**: Never skip requirements you deem "optional" or "can be added later"
|
|
18281
|
-
- **NO Premature Stopping**: Never declare done until ALL TODOs are completed and verified
|
|
18282
|
-
- **NO TEST DELETION**: Never delete or skip failing tests to make the build pass. Fix the code, not the tests.
|
|
18283
|
-
|
|
18284
|
-
THE USER ASKED FOR X. DELIVER EXACTLY X. NOT A SUBSET. NOT A DEMO. NOT A STARTING POINT.
|
|
18285
|
-
|
|
18286
|
-
</ultrawork-mode>
|
|
18287
|
-
|
|
18288
|
-
---
|
|
18289
|
-
|
|
18290
|
-
`;
|
|
18291
|
-
}
|
|
18292
|
-
var KEYWORD_DETECTORS = [
|
|
18293
|
-
{
|
|
18294
|
-
pattern: /(ultrawork|ulw)/i,
|
|
18295
|
-
message: getUltraworkMessage
|
|
18296
|
-
},
|
|
18297
|
-
{
|
|
18298
|
-
pattern: /\b(search|find|locate|lookup|look\s*up|explore|discover|scan|grep|query|browse|detect|trace|seek|track|pinpoint|hunt)\b|where\s+is|show\s+me|list\s+all|\uAC80\uC0C9|\uCC3E\uC544|\uD0D0\uC0C9|\uC870\uD68C|\uC2A4\uCE94|\uC11C\uCE58|\uB4A4\uC838|\uCC3E\uAE30|\uC5B4\uB514|\uCD94\uC801|\uD0D0\uC9C0|\uCC3E\uC544\uBD10|\uCC3E\uC544\uB0B4|\uBCF4\uC5EC\uC918|\uBAA9\uB85D|\u691C\u7D22|\u63A2\u3057\u3066|\u898B\u3064\u3051\u3066|\u30B5\u30FC\u30C1|\u63A2\u7D22|\u30B9\u30AD\u30E3\u30F3|\u3069\u3053|\u767A\u898B|\u635C\u7D22|\u898B\u3064\u3051\u51FA\u3059|\u4E00\u89A7|\u641C\u7D22|\u67E5\u627E|\u5BFB\u627E|\u67E5\u8BE2|\u68C0\u7D22|\u5B9A\u4F4D|\u626B\u63CF|\u53D1\u73B0|\u5728\u54EA\u91CC|\u627E\u51FA\u6765|\u5217\u51FA|t\u00ECm ki\u1EBFm|tra c\u1EE9u|\u0111\u1ECBnh v\u1ECB|qu\u00E9t|ph\u00E1t hi\u1EC7n|truy t\u00ECm|t\u00ECm ra|\u1EDF \u0111\u00E2u|li\u1EC7t k\u00EA/i,
|
|
18299
|
-
message: `[search-mode]
|
|
18300
|
-
MAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:
|
|
18301
|
-
- explore agents (codebase patterns, file structures, ast-grep)
|
|
18302
|
-
- librarian agents (remote repos, official docs, GitHub examples)
|
|
18303
|
-
Plus direct tools: Grep, ripgrep (rg), ast-grep (sg)
|
|
18304
|
-
NEVER stop at first result - be exhaustive.`
|
|
18305
|
-
},
|
|
18306
|
-
{
|
|
18307
|
-
pattern: /\b(analyze|analyse|investigate|examine|research|study|deep[\s-]?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to|\uBD84\uC11D|\uC870\uC0AC|\uD30C\uC545|\uC5F0\uAD6C|\uAC80\uD1A0|\uC9C4\uB2E8|\uC774\uD574|\uC124\uBA85|\uC6D0\uC778|\uC774\uC720|\uB72F\uC5B4\uBD10|\uB530\uC838\uBD10|\uD3C9\uAC00|\uD574\uC11D|\uB514\uBC84\uAE45|\uB514\uBC84\uADF8|\uC5B4\uB5BB\uAC8C|\uC65C|\uC0B4\uD3B4|\u5206\u6790|\u8ABF\u67FB|\u89E3\u6790|\u691C\u8A0E|\u7814\u7A76|\u8A3A\u65AD|\u7406\u89E3|\u8AAC\u660E|\u691C\u8A3C|\u7CBE\u67FB|\u7A76\u660E|\u30C7\u30D0\u30C3\u30B0|\u306A\u305C|\u3069\u3046|\u4ED5\u7D44\u307F|\u8C03\u67E5|\u68C0\u67E5|\u5256\u6790|\u6DF1\u5165|\u8BCA\u65AD|\u89E3\u91CA|\u8C03\u8BD5|\u4E3A\u4EC0\u4E48|\u539F\u7406|\u641E\u6E05\u695A|\u5F04\u660E\u767D|ph\u00E2n t\u00EDch|\u0111i\u1EC1u tra|nghi\u00EAn c\u1EE9u|ki\u1EC3m tra|xem x\u00E9t|ch\u1EA9n \u0111o\u00E1n|gi\u1EA3i th\u00EDch|t\u00ECm hi\u1EC3u|g\u1EE1 l\u1ED7i|t\u1EA1i sao/i,
|
|
18308
|
-
message: `[analyze-mode]
|
|
18309
|
-
ANALYSIS MODE. Gather context before diving deep:
|
|
18310
|
-
|
|
18311
|
-
CONTEXT GATHERING (parallel):
|
|
18312
|
-
- 1-2 explore agents (codebase patterns, implementations)
|
|
18313
|
-
- 1-2 librarian agents (if external library involved)
|
|
18314
|
-
- Direct tools: Grep, AST-grep, LSP for targeted searches
|
|
18315
|
-
|
|
18316
|
-
IF COMPLEX (architecture, multi-system, debugging after 2+ failures):
|
|
18317
|
-
- Consult oracle for strategic guidance
|
|
18318
|
-
|
|
18319
|
-
SYNTHESIZE findings before proceeding.`
|
|
18320
|
-
}
|
|
18321
|
-
];
|
|
18322
|
-
|
|
18323
|
-
// src/hooks/keyword-detector/detector.ts
|
|
18324
|
-
function removeCodeBlocks2(text) {
|
|
18325
|
-
return text.replace(CODE_BLOCK_PATTERN2, "").replace(INLINE_CODE_PATTERN2, "");
|
|
18326
|
-
}
|
|
18327
|
-
function resolveMessage(message, agentName) {
|
|
18328
|
-
return typeof message === "function" ? message(agentName) : message;
|
|
18329
|
-
}
|
|
18330
|
-
function detectKeywordsWithType(text, agentName) {
|
|
18331
|
-
const textWithoutCode = removeCodeBlocks2(text);
|
|
18332
|
-
const types3 = ["ultrawork", "search", "analyze"];
|
|
18333
|
-
return KEYWORD_DETECTORS.map(({ pattern, message }, index) => ({
|
|
18334
|
-
matches: pattern.test(textWithoutCode),
|
|
18335
|
-
type: types3[index],
|
|
18336
|
-
message: resolveMessage(message, agentName)
|
|
18337
|
-
})).filter((result) => result.matches).map(({ type: type2, message }) => ({ type: type2, message }));
|
|
18338
|
-
}
|
|
18339
|
-
function extractPromptText2(parts) {
|
|
18340
|
-
return parts.filter((p) => p.type === "text").map((p) => p.text || "").join(" ");
|
|
18341
|
-
}
|
|
18342
|
-
|
|
18343
|
-
// src/hooks/keyword-detector/index.ts
|
|
18344
|
-
function createKeywordDetectorHook(ctx) {
|
|
18345
|
-
return {
|
|
18346
|
-
"chat.message": async (input, output) => {
|
|
18347
|
-
const promptText = extractPromptText2(output.parts);
|
|
18348
|
-
let detectedKeywords = detectKeywordsWithType(removeCodeBlocks2(promptText), input.agent);
|
|
18349
|
-
if (detectedKeywords.length === 0) {
|
|
18350
|
-
return;
|
|
18351
|
-
}
|
|
18352
|
-
const mainSessionID2 = getMainSessionID();
|
|
18353
|
-
const isNonMainSession = mainSessionID2 && input.sessionID !== mainSessionID2;
|
|
18354
|
-
if (isNonMainSession) {
|
|
18355
|
-
detectedKeywords = detectedKeywords.filter((k) => k.type === "ultrawork");
|
|
18356
|
-
if (detectedKeywords.length === 0) {
|
|
18357
|
-
log(`[keyword-detector] Skipping non-ultrawork keywords in non-main session`, {
|
|
18358
|
-
sessionID: input.sessionID,
|
|
18359
|
-
mainSessionID: mainSessionID2
|
|
18360
|
-
});
|
|
18361
|
-
return;
|
|
18362
|
-
}
|
|
18363
|
-
}
|
|
18364
|
-
const hasUltrawork = detectedKeywords.some((k) => k.type === "ultrawork");
|
|
18365
|
-
if (hasUltrawork) {
|
|
18366
|
-
log(`[keyword-detector] Ultrawork mode activated`, { sessionID: input.sessionID });
|
|
18367
|
-
output.message.variant = "max";
|
|
18368
|
-
ctx.client.tui.showToast({
|
|
18369
|
-
body: {
|
|
18370
|
-
title: "Ultrawork Mode Activated",
|
|
18371
|
-
message: "Maximum precision engaged. All agents at your disposal.",
|
|
18372
|
-
variant: "success",
|
|
18373
|
-
duration: 3000
|
|
18374
|
-
}
|
|
18375
|
-
}).catch((err) => log(`[keyword-detector] Failed to show toast`, { error: err, sessionID: input.sessionID }));
|
|
18376
|
-
}
|
|
18377
|
-
log(`[keyword-detector] Detected ${detectedKeywords.length} keywords`, {
|
|
18378
|
-
sessionID: input.sessionID,
|
|
18379
|
-
types: detectedKeywords.map((k) => k.type)
|
|
18380
|
-
});
|
|
18381
|
-
}
|
|
18382
|
-
};
|
|
18383
|
-
}
|
|
18384
|
-
|
|
18385
18117
|
// src/hooks/claude-code-hooks/index.ts
|
|
18386
18118
|
var sessionFirstMessageProcessed = new Set;
|
|
18387
18119
|
var sessionErrorState = new Map;
|
|
@@ -18456,24 +18188,11 @@ function createClaudeCodeHooksHook(ctx, config = {}, contextCollector) {
|
|
|
18456
18188
|
log("chat.message injection skipped - interrupted during hooks", { sessionID: input.sessionID });
|
|
18457
18189
|
return;
|
|
18458
18190
|
}
|
|
18459
|
-
|
|
18460
|
-
|
|
18461
|
-
const detectedKeywords = detectKeywordsWithType(removeCodeBlocks2(prompt), input.agent);
|
|
18462
|
-
keywordMessages.push(...detectedKeywords.map((k) => k.message));
|
|
18463
|
-
if (keywordMessages.length > 0) {
|
|
18464
|
-
log("[claude-code-hooks] Detected keywords", {
|
|
18465
|
-
sessionID: input.sessionID,
|
|
18466
|
-
keywordCount: keywordMessages.length,
|
|
18467
|
-
types: detectedKeywords.map((k) => k.type)
|
|
18468
|
-
});
|
|
18469
|
-
}
|
|
18470
|
-
}
|
|
18471
|
-
const allMessages = [...keywordMessages, ...result.messages];
|
|
18472
|
-
if (allMessages.length > 0) {
|
|
18473
|
-
const hookContent = allMessages.join(`
|
|
18191
|
+
if (result.messages.length > 0) {
|
|
18192
|
+
const hookContent = result.messages.join(`
|
|
18474
18193
|
|
|
18475
18194
|
`);
|
|
18476
|
-
log(`[claude-code-hooks] Injecting ${
|
|
18195
|
+
log(`[claude-code-hooks] Injecting ${result.messages.length} hook messages`, { sessionID: input.sessionID, contentLength: hookContent.length, isFirstMessage });
|
|
18477
18196
|
if (isFirstMessage) {
|
|
18478
18197
|
const idx = output.parts.findIndex((p) => p.type === "text" && p.text);
|
|
18479
18198
|
if (idx >= 0) {
|
|
@@ -18683,7 +18402,7 @@ ${result.message}`;
|
|
|
18683
18402
|
// src/hooks/rules-injector/index.ts
|
|
18684
18403
|
import { readFileSync as readFileSync16 } from "fs";
|
|
18685
18404
|
import { homedir as homedir9 } from "os";
|
|
18686
|
-
import { relative as relative3, resolve as
|
|
18405
|
+
import { relative as relative3, resolve as resolve5 } from "path";
|
|
18687
18406
|
|
|
18688
18407
|
// src/hooks/rules-injector/finder.ts
|
|
18689
18408
|
import {
|
|
@@ -19078,7 +18797,7 @@ function createRulesInjectorHook(ctx) {
|
|
|
19078
18797
|
return null;
|
|
19079
18798
|
if (path5.startsWith("/"))
|
|
19080
18799
|
return path5;
|
|
19081
|
-
return
|
|
18800
|
+
return resolve5(ctx.directory, path5);
|
|
19082
18801
|
}
|
|
19083
18802
|
async function processFilePathForInjection(filePath, sessionID, output) {
|
|
19084
18803
|
const resolved = resolveFilePath2(filePath);
|
|
@@ -19535,7 +19254,7 @@ async function runBunInstallWithDetails() {
|
|
|
19535
19254
|
stdout: "pipe",
|
|
19536
19255
|
stderr: "pipe"
|
|
19537
19256
|
});
|
|
19538
|
-
const timeoutPromise = new Promise((
|
|
19257
|
+
const timeoutPromise = new Promise((resolve6) => setTimeout(() => resolve6("timeout"), BUN_INSTALL_TIMEOUT_MS));
|
|
19539
19258
|
const exitPromise = proc.exited.then(() => "completed");
|
|
19540
19259
|
const result = await Promise.race([exitPromise, timeoutPromise]);
|
|
19541
19260
|
if (result === "timeout") {
|
|
@@ -19722,7 +19441,7 @@ async function showSpinnerToast(ctx, version, message) {
|
|
|
19722
19441
|
duration: frameInterval + 50
|
|
19723
19442
|
}
|
|
19724
19443
|
}).catch(() => {});
|
|
19725
|
-
await new Promise((
|
|
19444
|
+
await new Promise((resolve6) => setTimeout(resolve6, frameInterval));
|
|
19726
19445
|
}
|
|
19727
19446
|
}
|
|
19728
19447
|
async function showUpdateAvailableToast(ctx, latestVersion, getToastMessage) {
|
|
@@ -19905,6 +19624,287 @@ function createAgentUsageReminderHook(_ctx) {
|
|
|
19905
19624
|
event: eventHandler
|
|
19906
19625
|
};
|
|
19907
19626
|
}
|
|
19627
|
+
// src/hooks/keyword-detector/constants.ts
|
|
19628
|
+
var CODE_BLOCK_PATTERN2 = /```[\s\S]*?```/g;
|
|
19629
|
+
var INLINE_CODE_PATTERN2 = /`[^`]+`/g;
|
|
19630
|
+
var ULTRAWORK_PLANNER_SECTION = `## CRITICAL: YOU ARE A PLANNER, NOT AN IMPLEMENTER
|
|
19631
|
+
|
|
19632
|
+
**IDENTITY CONSTRAINT (NON-NEGOTIABLE):**
|
|
19633
|
+
You ARE the planner. You ARE NOT an implementer. You DO NOT write code. You DO NOT execute tasks.
|
|
19634
|
+
|
|
19635
|
+
**TOOL RESTRICTIONS (SYSTEM-ENFORCED):**
|
|
19636
|
+
| Tool | Allowed | Blocked |
|
|
19637
|
+
|------|---------|---------|
|
|
19638
|
+
| Write/Edit | \`.sisyphus/**/*.md\` ONLY | Everything else |
|
|
19639
|
+
| Read | All files | - |
|
|
19640
|
+
| Bash | Research commands only | Implementation commands |
|
|
19641
|
+
| sisyphus_task | explore, librarian | - |
|
|
19642
|
+
|
|
19643
|
+
**IF YOU TRY TO WRITE/EDIT OUTSIDE \`.sisyphus/\`:**
|
|
19644
|
+
- System will BLOCK your action
|
|
19645
|
+
- You will receive an error
|
|
19646
|
+
- DO NOT retry - you are not supposed to implement
|
|
19647
|
+
|
|
19648
|
+
**YOUR ONLY WRITABLE PATHS:**
|
|
19649
|
+
- \`.sisyphus/plans/*.md\` - Final work plans
|
|
19650
|
+
- \`.sisyphus/drafts/*.md\` - Working drafts during interview
|
|
19651
|
+
|
|
19652
|
+
**WHEN USER ASKS YOU TO IMPLEMENT:**
|
|
19653
|
+
REFUSE. Say: "I'm a planner. I create work plans, not implementations. Run \`/start-work\` after I finish planning."
|
|
19654
|
+
|
|
19655
|
+
---
|
|
19656
|
+
|
|
19657
|
+
## CONTEXT GATHERING (MANDATORY BEFORE PLANNING)
|
|
19658
|
+
|
|
19659
|
+
You ARE the planner. Your job: create bulletproof work plans.
|
|
19660
|
+
**Before drafting ANY plan, gather context via explore/librarian agents.**
|
|
19661
|
+
|
|
19662
|
+
### Research Protocol
|
|
19663
|
+
1. **Fire parallel background agents** for comprehensive context:
|
|
19664
|
+
\`\`\`
|
|
19665
|
+
sisyphus_task(agent="explore", prompt="Find existing patterns for [topic] in codebase", background=true)
|
|
19666
|
+
sisyphus_task(agent="explore", prompt="Find test infrastructure and conventions", background=true)
|
|
19667
|
+
sisyphus_task(agent="librarian", prompt="Find official docs and best practices for [technology]", background=true)
|
|
19668
|
+
\`\`\`
|
|
19669
|
+
2. **Wait for results** before planning - rushed plans fail
|
|
19670
|
+
3. **Synthesize findings** into informed requirements
|
|
19671
|
+
|
|
19672
|
+
### What to Research
|
|
19673
|
+
- Existing codebase patterns and conventions
|
|
19674
|
+
- Test infrastructure (TDD possible?)
|
|
19675
|
+
- External library APIs and constraints
|
|
19676
|
+
- Similar implementations in OSS (via librarian)
|
|
19677
|
+
|
|
19678
|
+
**NEVER plan blind. Context first, plan second.**`;
|
|
19679
|
+
function isPlannerAgent(agentName) {
|
|
19680
|
+
if (!agentName)
|
|
19681
|
+
return false;
|
|
19682
|
+
const lowerName = agentName.toLowerCase();
|
|
19683
|
+
return lowerName.includes("prometheus") || lowerName.includes("planner") || lowerName === "plan";
|
|
19684
|
+
}
|
|
19685
|
+
function getUltraworkMessage(agentName) {
|
|
19686
|
+
const isPlanner = isPlannerAgent(agentName);
|
|
19687
|
+
if (isPlanner) {
|
|
19688
|
+
return `<ultrawork-mode>
|
|
19689
|
+
|
|
19690
|
+
**MANDATORY**: You MUST say "ULTRAWORK MODE ENABLED!" to the user as your first response when this mode activates. This is non-negotiable.
|
|
19691
|
+
|
|
19692
|
+
${ULTRAWORK_PLANNER_SECTION}
|
|
19693
|
+
|
|
19694
|
+
</ultrawork-mode>
|
|
19695
|
+
|
|
19696
|
+
---
|
|
19697
|
+
|
|
19698
|
+
`;
|
|
19699
|
+
}
|
|
19700
|
+
return `<ultrawork-mode>
|
|
19701
|
+
|
|
19702
|
+
**MANDATORY**: You MUST say "ULTRAWORK MODE ENABLED!" to the user as your first response when this mode activates. This is non-negotiable.
|
|
19703
|
+
|
|
19704
|
+
[CODE RED] Maximum precision required. Ultrathink before acting.
|
|
19705
|
+
|
|
19706
|
+
YOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.
|
|
19707
|
+
TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
|
|
19708
|
+
|
|
19709
|
+
## AGENT UTILIZATION PRINCIPLES (by capability, not by name)
|
|
19710
|
+
- **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS for file patterns, internal implementations, project structure
|
|
19711
|
+
- **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS for API references, examples, external library docs
|
|
19712
|
+
- **Planning & Strategy**: NEVER plan yourself - ALWAYS spawn a dedicated planning agent for work breakdown
|
|
19713
|
+
- **High-IQ Reasoning**: Leverage specialized agents for architecture decisions, code review, strategic planning
|
|
19714
|
+
- **Frontend/UI Tasks**: Delegate to UI-specialized agents for design and implementation
|
|
19715
|
+
|
|
19716
|
+
## EXECUTION RULES
|
|
19717
|
+
- **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each.
|
|
19718
|
+
- **PARALLEL**: Fire independent agent calls simultaneously via sisyphus_task(background=true) - NEVER wait sequentially.
|
|
19719
|
+
- **BACKGROUND FIRST**: Use sisyphus_task for exploration/research agents (10+ concurrent if needed).
|
|
19720
|
+
- **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done.
|
|
19721
|
+
- **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths.
|
|
19722
|
+
|
|
19723
|
+
## WORKFLOW
|
|
19724
|
+
1. Analyze the request and identify required capabilities
|
|
19725
|
+
2. Spawn exploration/librarian agents via sisyphus_task(background=true) in PARALLEL (10+ if needed)
|
|
19726
|
+
3. Always Use Plan agent with gathered context to create detailed work breakdown
|
|
19727
|
+
4. Execute with continuous verification against original requirements
|
|
19728
|
+
|
|
19729
|
+
## VERIFICATION GUARANTEE (NON-NEGOTIABLE)
|
|
19730
|
+
|
|
19731
|
+
**NOTHING is "done" without PROOF it works.**
|
|
19732
|
+
|
|
19733
|
+
### Pre-Implementation: Define Success Criteria
|
|
19734
|
+
|
|
19735
|
+
BEFORE writing ANY code, you MUST define:
|
|
19736
|
+
|
|
19737
|
+
| Criteria Type | Description | Example |
|
|
19738
|
+
|---------------|-------------|---------|
|
|
19739
|
+
| **Functional** | What specific behavior must work | "Button click triggers API call" |
|
|
19740
|
+
| **Observable** | What can be measured/seen | "Console shows 'success', no errors" |
|
|
19741
|
+
| **Pass/Fail** | Binary, no ambiguity | "Returns 200 OK" not "should work" |
|
|
19742
|
+
|
|
19743
|
+
Write these criteria explicitly. Share with user if scope is non-trivial.
|
|
19744
|
+
|
|
19745
|
+
### Test Plan Template (MANDATORY for non-trivial tasks)
|
|
19746
|
+
|
|
19747
|
+
\`\`\`
|
|
19748
|
+
## Test Plan
|
|
19749
|
+
### Objective: [What we're verifying]
|
|
19750
|
+
### Prerequisites: [Setup needed]
|
|
19751
|
+
### Test Cases:
|
|
19752
|
+
1. [Test Name]: [Input] \u2192 [Expected Output] \u2192 [How to verify]
|
|
19753
|
+
2. ...
|
|
19754
|
+
### Success Criteria: ALL test cases pass
|
|
19755
|
+
### How to Execute: [Exact commands/steps]
|
|
19756
|
+
\`\`\`
|
|
19757
|
+
|
|
19758
|
+
### Execution & Evidence Requirements
|
|
19759
|
+
|
|
19760
|
+
| Phase | Action | Required Evidence |
|
|
19761
|
+
|-------|--------|-------------------|
|
|
19762
|
+
| **Build** | Run build command | Exit code 0, no errors |
|
|
19763
|
+
| **Test** | Execute test suite | All tests pass (screenshot/output) |
|
|
19764
|
+
| **Manual Verify** | Test the actual feature | Demonstrate it works (describe what you observed) |
|
|
19765
|
+
| **Regression** | Ensure nothing broke | Existing tests still pass |
|
|
19766
|
+
|
|
19767
|
+
**WITHOUT evidence = NOT verified = NOT done.**
|
|
19768
|
+
|
|
19769
|
+
### TDD Workflow (when test infrastructure exists)
|
|
19770
|
+
|
|
19771
|
+
1. **SPEC**: Define what "working" means (success criteria above)
|
|
19772
|
+
2. **RED**: Write failing test \u2192 Run it \u2192 Confirm it FAILS
|
|
19773
|
+
3. **GREEN**: Write minimal code \u2192 Run test \u2192 Confirm it PASSES
|
|
19774
|
+
4. **REFACTOR**: Clean up \u2192 Tests MUST stay green
|
|
19775
|
+
5. **VERIFY**: Run full test suite, confirm no regressions
|
|
19776
|
+
6. **EVIDENCE**: Report what you ran and what output you saw
|
|
19777
|
+
|
|
19778
|
+
### Verification Anti-Patterns (BLOCKING)
|
|
19779
|
+
|
|
19780
|
+
| Violation | Why It Fails |
|
|
19781
|
+
|-----------|--------------|
|
|
19782
|
+
| "It should work now" | No evidence. Run it. |
|
|
19783
|
+
| "I added the tests" | Did they pass? Show output. |
|
|
19784
|
+
| "Fixed the bug" | How do you know? What did you test? |
|
|
19785
|
+
| "Implementation complete" | Did you verify against success criteria? |
|
|
19786
|
+
| Skipping test execution | Tests exist to be RUN, not just written |
|
|
19787
|
+
|
|
19788
|
+
**CLAIM NOTHING WITHOUT PROOF. EXECUTE. VERIFY. SHOW EVIDENCE.**
|
|
19789
|
+
|
|
19790
|
+
## ZERO TOLERANCE FAILURES
|
|
19791
|
+
- **NO Scope Reduction**: Never make "demo", "skeleton", "simplified", "basic" versions - deliver FULL implementation
|
|
19792
|
+
- **NO MockUp Work**: When user asked you to do "port A", you must "port A", fully, 100%. No Extra feature, No reduced feature, no mock data, fully working 100% port.
|
|
19793
|
+
- **NO Partial Completion**: Never stop at 60-80% saying "you can extend this..." - finish 100%
|
|
19794
|
+
- **NO Assumed Shortcuts**: Never skip requirements you deem "optional" or "can be added later"
|
|
19795
|
+
- **NO Premature Stopping**: Never declare done until ALL TODOs are completed and verified
|
|
19796
|
+
- **NO TEST DELETION**: Never delete or skip failing tests to make the build pass. Fix the code, not the tests.
|
|
19797
|
+
|
|
19798
|
+
THE USER ASKED FOR X. DELIVER EXACTLY X. NOT A SUBSET. NOT A DEMO. NOT A STARTING POINT.
|
|
19799
|
+
|
|
19800
|
+
</ultrawork-mode>
|
|
19801
|
+
|
|
19802
|
+
---
|
|
19803
|
+
|
|
19804
|
+
`;
|
|
19805
|
+
}
|
|
19806
|
+
var KEYWORD_DETECTORS = [
|
|
19807
|
+
{
|
|
19808
|
+
pattern: /(ultrawork|ulw)/i,
|
|
19809
|
+
message: getUltraworkMessage
|
|
19810
|
+
},
|
|
19811
|
+
{
|
|
19812
|
+
pattern: /\b(search|find|locate|lookup|look\s*up|explore|discover|scan|grep|query|browse|detect|trace|seek|track|pinpoint|hunt)\b|where\s+is|show\s+me|list\s+all|\uAC80\uC0C9|\uCC3E\uC544|\uD0D0\uC0C9|\uC870\uD68C|\uC2A4\uCE94|\uC11C\uCE58|\uB4A4\uC838|\uCC3E\uAE30|\uC5B4\uB514|\uCD94\uC801|\uD0D0\uC9C0|\uCC3E\uC544\uBD10|\uCC3E\uC544\uB0B4|\uBCF4\uC5EC\uC918|\uBAA9\uB85D|\u691C\u7D22|\u63A2\u3057\u3066|\u898B\u3064\u3051\u3066|\u30B5\u30FC\u30C1|\u63A2\u7D22|\u30B9\u30AD\u30E3\u30F3|\u3069\u3053|\u767A\u898B|\u635C\u7D22|\u898B\u3064\u3051\u51FA\u3059|\u4E00\u89A7|\u641C\u7D22|\u67E5\u627E|\u5BFB\u627E|\u67E5\u8BE2|\u68C0\u7D22|\u5B9A\u4F4D|\u626B\u63CF|\u53D1\u73B0|\u5728\u54EA\u91CC|\u627E\u51FA\u6765|\u5217\u51FA|t\u00ECm ki\u1EBFm|tra c\u1EE9u|\u0111\u1ECBnh v\u1ECB|qu\u00E9t|ph\u00E1t hi\u1EC7n|truy t\u00ECm|t\u00ECm ra|\u1EDF \u0111\u00E2u|li\u1EC7t k\u00EA/i,
|
|
19813
|
+
message: `[search-mode]
|
|
19814
|
+
MAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:
|
|
19815
|
+
- explore agents (codebase patterns, file structures, ast-grep)
|
|
19816
|
+
- librarian agents (remote repos, official docs, GitHub examples)
|
|
19817
|
+
Plus direct tools: Grep, ripgrep (rg), ast-grep (sg)
|
|
19818
|
+
NEVER stop at first result - be exhaustive.`
|
|
19819
|
+
},
|
|
19820
|
+
{
|
|
19821
|
+
pattern: /\b(analyze|analyse|investigate|examine|research|study|deep[\s-]?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to|\uBD84\uC11D|\uC870\uC0AC|\uD30C\uC545|\uC5F0\uAD6C|\uAC80\uD1A0|\uC9C4\uB2E8|\uC774\uD574|\uC124\uBA85|\uC6D0\uC778|\uC774\uC720|\uB72F\uC5B4\uBD10|\uB530\uC838\uBD10|\uD3C9\uAC00|\uD574\uC11D|\uB514\uBC84\uAE45|\uB514\uBC84\uADF8|\uC5B4\uB5BB\uAC8C|\uC65C|\uC0B4\uD3B4|\u5206\u6790|\u8ABF\u67FB|\u89E3\u6790|\u691C\u8A0E|\u7814\u7A76|\u8A3A\u65AD|\u7406\u89E3|\u8AAC\u660E|\u691C\u8A3C|\u7CBE\u67FB|\u7A76\u660E|\u30C7\u30D0\u30C3\u30B0|\u306A\u305C|\u3069\u3046|\u4ED5\u7D44\u307F|\u8C03\u67E5|\u68C0\u67E5|\u5256\u6790|\u6DF1\u5165|\u8BCA\u65AD|\u89E3\u91CA|\u8C03\u8BD5|\u4E3A\u4EC0\u4E48|\u539F\u7406|\u641E\u6E05\u695A|\u5F04\u660E\u767D|ph\u00E2n t\u00EDch|\u0111i\u1EC1u tra|nghi\u00EAn c\u1EE9u|ki\u1EC3m tra|xem x\u00E9t|ch\u1EA9n \u0111o\u00E1n|gi\u1EA3i th\u00EDch|t\u00ECm hi\u1EC3u|g\u1EE1 l\u1ED7i|t\u1EA1i sao/i,
|
|
19822
|
+
message: `[analyze-mode]
|
|
19823
|
+
ANALYSIS MODE. Gather context before diving deep:
|
|
19824
|
+
|
|
19825
|
+
CONTEXT GATHERING (parallel):
|
|
19826
|
+
- 1-2 explore agents (codebase patterns, implementations)
|
|
19827
|
+
- 1-2 librarian agents (if external library involved)
|
|
19828
|
+
- Direct tools: Grep, AST-grep, LSP for targeted searches
|
|
19829
|
+
|
|
19830
|
+
IF COMPLEX (architecture, multi-system, debugging after 2+ failures):
|
|
19831
|
+
- Consult oracle for strategic guidance
|
|
19832
|
+
|
|
19833
|
+
SYNTHESIZE findings before proceeding.`
|
|
19834
|
+
}
|
|
19835
|
+
];
|
|
19836
|
+
|
|
19837
|
+
// src/hooks/keyword-detector/detector.ts
|
|
19838
|
+
function removeCodeBlocks2(text) {
|
|
19839
|
+
return text.replace(CODE_BLOCK_PATTERN2, "").replace(INLINE_CODE_PATTERN2, "");
|
|
19840
|
+
}
|
|
19841
|
+
function resolveMessage(message, agentName) {
|
|
19842
|
+
return typeof message === "function" ? message(agentName) : message;
|
|
19843
|
+
}
|
|
19844
|
+
function detectKeywordsWithType(text, agentName) {
|
|
19845
|
+
const textWithoutCode = removeCodeBlocks2(text);
|
|
19846
|
+
const types3 = ["ultrawork", "search", "analyze"];
|
|
19847
|
+
return KEYWORD_DETECTORS.map(({ pattern, message }, index) => ({
|
|
19848
|
+
matches: pattern.test(textWithoutCode),
|
|
19849
|
+
type: types3[index],
|
|
19850
|
+
message: resolveMessage(message, agentName)
|
|
19851
|
+
})).filter((result) => result.matches).map(({ type: type2, message }) => ({ type: type2, message }));
|
|
19852
|
+
}
|
|
19853
|
+
function extractPromptText2(parts) {
|
|
19854
|
+
return parts.filter((p) => p.type === "text").map((p) => p.text || "").join(" ");
|
|
19855
|
+
}
|
|
19856
|
+
|
|
19857
|
+
// src/hooks/keyword-detector/index.ts
|
|
19858
|
+
function createKeywordDetectorHook(ctx, collector) {
|
|
19859
|
+
return {
|
|
19860
|
+
"chat.message": async (input, output) => {
|
|
19861
|
+
const promptText = extractPromptText2(output.parts);
|
|
19862
|
+
let detectedKeywords = detectKeywordsWithType(removeCodeBlocks2(promptText), input.agent);
|
|
19863
|
+
if (detectedKeywords.length === 0) {
|
|
19864
|
+
return;
|
|
19865
|
+
}
|
|
19866
|
+
const mainSessionID2 = getMainSessionID();
|
|
19867
|
+
const isNonMainSession = mainSessionID2 && input.sessionID !== mainSessionID2;
|
|
19868
|
+
if (isNonMainSession) {
|
|
19869
|
+
detectedKeywords = detectedKeywords.filter((k) => k.type === "ultrawork");
|
|
19870
|
+
if (detectedKeywords.length === 0) {
|
|
19871
|
+
log(`[keyword-detector] Skipping non-ultrawork keywords in non-main session`, {
|
|
19872
|
+
sessionID: input.sessionID,
|
|
19873
|
+
mainSessionID: mainSessionID2
|
|
19874
|
+
});
|
|
19875
|
+
return;
|
|
19876
|
+
}
|
|
19877
|
+
}
|
|
19878
|
+
const hasUltrawork = detectedKeywords.some((k) => k.type === "ultrawork");
|
|
19879
|
+
if (hasUltrawork) {
|
|
19880
|
+
log(`[keyword-detector] Ultrawork mode activated`, { sessionID: input.sessionID });
|
|
19881
|
+
output.message.variant = "max";
|
|
19882
|
+
ctx.client.tui.showToast({
|
|
19883
|
+
body: {
|
|
19884
|
+
title: "Ultrawork Mode Activated",
|
|
19885
|
+
message: "Maximum precision engaged. All agents at your disposal.",
|
|
19886
|
+
variant: "success",
|
|
19887
|
+
duration: 3000
|
|
19888
|
+
}
|
|
19889
|
+
}).catch((err) => log(`[keyword-detector] Failed to show toast`, { error: err, sessionID: input.sessionID }));
|
|
19890
|
+
}
|
|
19891
|
+
if (collector) {
|
|
19892
|
+
for (const keyword of detectedKeywords) {
|
|
19893
|
+
collector.register(input.sessionID, {
|
|
19894
|
+
id: `keyword-${keyword.type}`,
|
|
19895
|
+
source: "keyword-detector",
|
|
19896
|
+
content: keyword.message,
|
|
19897
|
+
priority: keyword.type === "ultrawork" ? "critical" : "high"
|
|
19898
|
+
});
|
|
19899
|
+
}
|
|
19900
|
+
}
|
|
19901
|
+
log(`[keyword-detector] Detected ${detectedKeywords.length} keywords`, {
|
|
19902
|
+
sessionID: input.sessionID,
|
|
19903
|
+
types: detectedKeywords.map((k) => k.type)
|
|
19904
|
+
});
|
|
19905
|
+
}
|
|
19906
|
+
};
|
|
19907
|
+
}
|
|
19908
19908
|
// src/hooks/non-interactive-env/constants.ts
|
|
19909
19909
|
var HOOK_NAME2 = "non-interactive-env";
|
|
19910
19910
|
var NON_INTERACTIVE_ENV = {
|
|
@@ -21021,7 +21021,7 @@ async function discoverOpencodeProjectSkills() {
|
|
|
21021
21021
|
}
|
|
21022
21022
|
// src/features/opencode-skill-loader/merger.ts
|
|
21023
21023
|
import { readFileSync as readFileSync23, existsSync as existsSync35 } from "fs";
|
|
21024
|
-
import { dirname as dirname7, resolve as
|
|
21024
|
+
import { dirname as dirname7, resolve as resolve6, isAbsolute as isAbsolute2 } from "path";
|
|
21025
21025
|
import { homedir as homedir13 } from "os";
|
|
21026
21026
|
var SCOPE_PRIORITY = {
|
|
21027
21027
|
builtin: 1,
|
|
@@ -21058,13 +21058,13 @@ function resolveFilePath2(from, configDir) {
|
|
|
21058
21058
|
filePath = filePath.slice(6, -1);
|
|
21059
21059
|
}
|
|
21060
21060
|
if (filePath.startsWith("~/")) {
|
|
21061
|
-
return
|
|
21061
|
+
return resolve6(homedir13(), filePath.slice(2));
|
|
21062
21062
|
}
|
|
21063
21063
|
if (isAbsolute2(filePath)) {
|
|
21064
21064
|
return filePath;
|
|
21065
21065
|
}
|
|
21066
21066
|
const baseDir = configDir || process.cwd();
|
|
21067
|
-
return
|
|
21067
|
+
return resolve6(baseDir, filePath);
|
|
21068
21068
|
}
|
|
21069
21069
|
function loadSkillFromFile(filePath) {
|
|
21070
21070
|
try {
|
|
@@ -22699,13 +22699,13 @@ ${EDIT_ERROR_REMINDER}`;
|
|
|
22699
22699
|
}
|
|
22700
22700
|
// src/hooks/prometheus-md-only/index.ts
|
|
22701
22701
|
import { existsSync as existsSync37, readdirSync as readdirSync12 } from "fs";
|
|
22702
|
-
import { join as join45 } from "path";
|
|
22702
|
+
import { join as join45, resolve as resolve7, relative as relative4, isAbsolute as isAbsolute3 } from "path";
|
|
22703
22703
|
|
|
22704
22704
|
// src/hooks/prometheus-md-only/constants.ts
|
|
22705
22705
|
var HOOK_NAME4 = "prometheus-md-only";
|
|
22706
22706
|
var PROMETHEUS_AGENTS = ["Prometheus (Planner)"];
|
|
22707
22707
|
var ALLOWED_EXTENSIONS = [".md"];
|
|
22708
|
-
var ALLOWED_PATH_PREFIX = ".sisyphus
|
|
22708
|
+
var ALLOWED_PATH_PREFIX = ".sisyphus";
|
|
22709
22709
|
var BLOCKED_TOOLS = ["Write", "Edit", "write", "edit"];
|
|
22710
22710
|
var PLANNING_CONSULT_WARNING = `
|
|
22711
22711
|
|
|
@@ -22729,10 +22729,21 @@ Return your findings and recommendations. The actual implementation will be hand
|
|
|
22729
22729
|
`;
|
|
22730
22730
|
|
|
22731
22731
|
// src/hooks/prometheus-md-only/index.ts
|
|
22732
|
-
function isAllowedFile(filePath) {
|
|
22733
|
-
const
|
|
22734
|
-
const
|
|
22735
|
-
|
|
22732
|
+
function isAllowedFile(filePath, workspaceRoot) {
|
|
22733
|
+
const resolved = resolve7(workspaceRoot, filePath);
|
|
22734
|
+
const rel = relative4(workspaceRoot, resolved);
|
|
22735
|
+
if (rel.startsWith("..") || isAbsolute3(rel)) {
|
|
22736
|
+
return false;
|
|
22737
|
+
}
|
|
22738
|
+
const segments = rel.split(/[/\\]/);
|
|
22739
|
+
if (!segments[0] || segments[0].toLowerCase() !== ALLOWED_PATH_PREFIX.toLowerCase()) {
|
|
22740
|
+
return false;
|
|
22741
|
+
}
|
|
22742
|
+
const hasAllowedExtension = ALLOWED_EXTENSIONS.some((ext) => resolved.toLowerCase().endsWith(ext.toLowerCase()));
|
|
22743
|
+
if (!hasAllowedExtension) {
|
|
22744
|
+
return false;
|
|
22745
|
+
}
|
|
22746
|
+
return true;
|
|
22736
22747
|
}
|
|
22737
22748
|
function getMessageDir9(sessionID) {
|
|
22738
22749
|
if (!existsSync37(MESSAGE_STORAGE))
|
|
@@ -22754,7 +22765,7 @@ function getAgentFromSession(sessionID) {
|
|
|
22754
22765
|
return;
|
|
22755
22766
|
return findNearestMessageWithFields(messageDir)?.agent;
|
|
22756
22767
|
}
|
|
22757
|
-
function createPrometheusMdOnlyHook(
|
|
22768
|
+
function createPrometheusMdOnlyHook(ctx) {
|
|
22758
22769
|
return {
|
|
22759
22770
|
"tool.execute.before": async (input, output) => {
|
|
22760
22771
|
const agentName = getAgentFromSession(input.sessionID);
|
|
@@ -22781,7 +22792,7 @@ function createPrometheusMdOnlyHook(_ctx) {
|
|
|
22781
22792
|
if (!filePath) {
|
|
22782
22793
|
return;
|
|
22783
22794
|
}
|
|
22784
|
-
if (!isAllowedFile(filePath)) {
|
|
22795
|
+
if (!isAllowedFile(filePath, ctx.directory)) {
|
|
22785
22796
|
log(`[${HOOK_NAME4}] Blocked: Prometheus can only write to .sisyphus/*.md`, {
|
|
22786
22797
|
sessionID: input.sessionID,
|
|
22787
22798
|
tool: toolName,
|
|
@@ -23048,7 +23059,9 @@ import { execSync as execSync2 } from "child_process";
|
|
|
23048
23059
|
import { existsSync as existsSync39, readdirSync as readdirSync14 } from "fs";
|
|
23049
23060
|
import { join as join47 } from "path";
|
|
23050
23061
|
var HOOK_NAME6 = "sisyphus-orchestrator";
|
|
23051
|
-
|
|
23062
|
+
function isSisyphusPath(filePath) {
|
|
23063
|
+
return /\.sisyphus[/\\]/.test(filePath);
|
|
23064
|
+
}
|
|
23052
23065
|
var WRITE_EDIT_TOOLS = ["Write", "Edit", "write", "edit"];
|
|
23053
23066
|
var DIRECT_WORK_REMINDER = `
|
|
23054
23067
|
|
|
@@ -23493,7 +23506,7 @@ function createSisyphusOrchestratorHook(ctx, options) {
|
|
|
23493
23506
|
}
|
|
23494
23507
|
if (WRITE_EDIT_TOOLS.includes(input.tool)) {
|
|
23495
23508
|
const filePath = output.args.filePath ?? output.args.path ?? output.args.file;
|
|
23496
|
-
if (filePath && !filePath
|
|
23509
|
+
if (filePath && !isSisyphusPath(filePath)) {
|
|
23497
23510
|
if (input.callID) {
|
|
23498
23511
|
pendingFilePaths.set(input.callID, filePath);
|
|
23499
23512
|
}
|
|
@@ -23530,7 +23543,7 @@ function createSisyphusOrchestratorHook(ctx, options) {
|
|
|
23530
23543
|
if (!filePath) {
|
|
23531
23544
|
filePath = output.metadata?.filePath;
|
|
23532
23545
|
}
|
|
23533
|
-
if (filePath && !filePath
|
|
23546
|
+
if (filePath && !isSisyphusPath(filePath)) {
|
|
23534
23547
|
output.output = (output.output || "") + DIRECT_WORK_REMINDER;
|
|
23535
23548
|
log(`[${HOOK_NAME6}] Direct work reminder appended`, {
|
|
23536
23549
|
sessionID: input.sessionID,
|
|
@@ -23659,9 +23672,37 @@ class ContextCollector {
|
|
|
23659
23672
|
}
|
|
23660
23673
|
var contextCollector = new ContextCollector;
|
|
23661
23674
|
// src/features/context-injector/injector.ts
|
|
23675
|
+
function injectPendingContext(collector, sessionID, parts) {
|
|
23676
|
+
if (!collector.hasPending(sessionID)) {
|
|
23677
|
+
return { injected: false, contextLength: 0 };
|
|
23678
|
+
}
|
|
23679
|
+
const textPartIndex = parts.findIndex((p) => p.type === "text" && p.text !== undefined);
|
|
23680
|
+
if (textPartIndex === -1) {
|
|
23681
|
+
return { injected: false, contextLength: 0 };
|
|
23682
|
+
}
|
|
23683
|
+
const pending = collector.consume(sessionID);
|
|
23684
|
+
const originalText = parts[textPartIndex].text ?? "";
|
|
23685
|
+
parts[textPartIndex].text = `${pending.merged}
|
|
23686
|
+
|
|
23687
|
+
---
|
|
23688
|
+
|
|
23689
|
+
${originalText}`;
|
|
23690
|
+
return {
|
|
23691
|
+
injected: true,
|
|
23692
|
+
contextLength: pending.merged.length
|
|
23693
|
+
};
|
|
23694
|
+
}
|
|
23662
23695
|
function createContextInjectorHook(collector) {
|
|
23663
23696
|
return {
|
|
23664
|
-
"chat.message": async (
|
|
23697
|
+
"chat.message": async (input, output) => {
|
|
23698
|
+
const result = injectPendingContext(collector, input.sessionID, output.parts);
|
|
23699
|
+
if (result.injected) {
|
|
23700
|
+
log("[context-injector] Injected pending context via chat.message", {
|
|
23701
|
+
sessionID: input.sessionID,
|
|
23702
|
+
contextLength: result.contextLength
|
|
23703
|
+
});
|
|
23704
|
+
}
|
|
23705
|
+
}
|
|
23665
23706
|
};
|
|
23666
23707
|
}
|
|
23667
23708
|
function createContextInjectorMessagesTransformHook(collector) {
|
|
@@ -23977,8 +24018,8 @@ function startCallbackServer(timeoutMs = 5 * 60 * 1000) {
|
|
|
23977
24018
|
const actualPort = server.port;
|
|
23978
24019
|
const redirectUri = `http://localhost:${actualPort}/oauth-callback`;
|
|
23979
24020
|
const waitForCallback = () => {
|
|
23980
|
-
return new Promise((
|
|
23981
|
-
resolveCallback =
|
|
24021
|
+
return new Promise((resolve8, reject) => {
|
|
24022
|
+
resolveCallback = resolve8;
|
|
23982
24023
|
rejectCallback = reject;
|
|
23983
24024
|
timeoutId = setTimeout(() => {
|
|
23984
24025
|
cleanup();
|
|
@@ -24100,7 +24141,7 @@ async function refreshAccessToken(refreshToken, clientId = ANTIGRAVITY_CLIENT_ID
|
|
|
24100
24141
|
}
|
|
24101
24142
|
if (attempt < MAX_REFRESH_RETRIES) {
|
|
24102
24143
|
const delay = calculateRetryDelay(attempt);
|
|
24103
|
-
await new Promise((
|
|
24144
|
+
await new Promise((resolve8) => setTimeout(resolve8, delay));
|
|
24104
24145
|
}
|
|
24105
24146
|
} catch (error) {
|
|
24106
24147
|
if (error instanceof AntigravityTokenRefreshError) {
|
|
@@ -24113,7 +24154,7 @@ async function refreshAccessToken(refreshToken, clientId = ANTIGRAVITY_CLIENT_ID
|
|
|
24113
24154
|
});
|
|
24114
24155
|
if (attempt < MAX_REFRESH_RETRIES) {
|
|
24115
24156
|
const delay = calculateRetryDelay(attempt);
|
|
24116
|
-
await new Promise((
|
|
24157
|
+
await new Promise((resolve8) => setTimeout(resolve8, delay));
|
|
24117
24158
|
}
|
|
24118
24159
|
}
|
|
24119
24160
|
}
|
|
@@ -24179,7 +24220,7 @@ function isFreeTier(tierId) {
|
|
|
24179
24220
|
return lower === "free" || lower === "free-tier" || lower.startsWith("free");
|
|
24180
24221
|
}
|
|
24181
24222
|
function wait(ms) {
|
|
24182
|
-
return new Promise((
|
|
24223
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
24183
24224
|
}
|
|
24184
24225
|
async function callLoadCodeAssistAPI(accessToken, projectId) {
|
|
24185
24226
|
const metadata = { ...CODE_ASSIST_METADATA };
|
|
@@ -25596,7 +25637,7 @@ async function attemptFetch(options) {
|
|
|
25596
25637
|
if (attempt < maxPermissionRetries) {
|
|
25597
25638
|
const delay = calculateRetryDelay2(attempt);
|
|
25598
25639
|
debugLog7(`[RETRY] GCP permission error, retry ${attempt + 1}/${maxPermissionRetries} after ${delay}ms`);
|
|
25599
|
-
await new Promise((
|
|
25640
|
+
await new Promise((resolve8) => setTimeout(resolve8, delay));
|
|
25600
25641
|
continue;
|
|
25601
25642
|
}
|
|
25602
25643
|
debugLog7(`[RETRY] GCP permission error, max retries exceeded`);
|
|
@@ -26978,19 +27019,19 @@ var baseOpen = async (options) => {
|
|
|
26978
27019
|
}
|
|
26979
27020
|
const subprocess = childProcess3.spawn(command, cliArguments, childProcessOptions);
|
|
26980
27021
|
if (options.wait) {
|
|
26981
|
-
return new Promise((
|
|
27022
|
+
return new Promise((resolve8, reject) => {
|
|
26982
27023
|
subprocess.once("error", reject);
|
|
26983
27024
|
subprocess.once("close", (exitCode) => {
|
|
26984
27025
|
if (!options.allowNonzeroExitCode && exitCode !== 0) {
|
|
26985
27026
|
reject(new Error(`Exited with code ${exitCode}`));
|
|
26986
27027
|
return;
|
|
26987
27028
|
}
|
|
26988
|
-
|
|
27029
|
+
resolve8(subprocess);
|
|
26989
27030
|
});
|
|
26990
27031
|
});
|
|
26991
27032
|
}
|
|
26992
27033
|
if (isFallbackAttempt) {
|
|
26993
|
-
return new Promise((
|
|
27034
|
+
return new Promise((resolve8, reject) => {
|
|
26994
27035
|
subprocess.once("error", reject);
|
|
26995
27036
|
subprocess.once("spawn", () => {
|
|
26996
27037
|
subprocess.once("close", (exitCode) => {
|
|
@@ -27000,17 +27041,17 @@ var baseOpen = async (options) => {
|
|
|
27000
27041
|
return;
|
|
27001
27042
|
}
|
|
27002
27043
|
subprocess.unref();
|
|
27003
|
-
|
|
27044
|
+
resolve8(subprocess);
|
|
27004
27045
|
});
|
|
27005
27046
|
});
|
|
27006
27047
|
});
|
|
27007
27048
|
}
|
|
27008
27049
|
subprocess.unref();
|
|
27009
|
-
return new Promise((
|
|
27050
|
+
return new Promise((resolve8, reject) => {
|
|
27010
27051
|
subprocess.once("error", reject);
|
|
27011
27052
|
subprocess.once("spawn", () => {
|
|
27012
27053
|
subprocess.off("error", reject);
|
|
27013
|
-
|
|
27054
|
+
resolve8(subprocess);
|
|
27014
27055
|
});
|
|
27015
27056
|
});
|
|
27016
27057
|
};
|
|
@@ -28143,7 +28184,7 @@ function getAllServers() {
|
|
|
28143
28184
|
// src/tools/lsp/client.ts
|
|
28144
28185
|
var {spawn: spawn5 } = globalThis.Bun;
|
|
28145
28186
|
import { readFileSync as readFileSync28 } from "fs";
|
|
28146
|
-
import { extname, resolve as
|
|
28187
|
+
import { extname, resolve as resolve8 } from "path";
|
|
28147
28188
|
class LSPServerManager {
|
|
28148
28189
|
static instance;
|
|
28149
28190
|
clients = new Map;
|
|
@@ -28289,6 +28330,25 @@ class LSPServerManager {
|
|
|
28289
28330
|
this.cleanupInterval = null;
|
|
28290
28331
|
}
|
|
28291
28332
|
}
|
|
28333
|
+
async cleanupTempDirectoryClients() {
|
|
28334
|
+
const keysToRemove = [];
|
|
28335
|
+
for (const [key, managed] of this.clients.entries()) {
|
|
28336
|
+
const isTempDir = key.startsWith("/tmp/") || key.startsWith("/var/folders/");
|
|
28337
|
+
const isIdle = managed.refCount === 0;
|
|
28338
|
+
if (isTempDir && isIdle) {
|
|
28339
|
+
keysToRemove.push(key);
|
|
28340
|
+
}
|
|
28341
|
+
}
|
|
28342
|
+
for (const key of keysToRemove) {
|
|
28343
|
+
const managed = this.clients.get(key);
|
|
28344
|
+
if (managed) {
|
|
28345
|
+
this.clients.delete(key);
|
|
28346
|
+
try {
|
|
28347
|
+
await managed.client.stop();
|
|
28348
|
+
} catch {}
|
|
28349
|
+
}
|
|
28350
|
+
}
|
|
28351
|
+
}
|
|
28292
28352
|
}
|
|
28293
28353
|
var lspManager = LSPServerManager.getInstance();
|
|
28294
28354
|
|
|
@@ -28323,7 +28383,7 @@ class LSPClient {
|
|
|
28323
28383
|
}
|
|
28324
28384
|
this.startReading();
|
|
28325
28385
|
this.startStderrReading();
|
|
28326
|
-
await new Promise((
|
|
28386
|
+
await new Promise((resolve9) => setTimeout(resolve9, 100));
|
|
28327
28387
|
if (this.proc.exitCode !== null) {
|
|
28328
28388
|
const stderr = this.stderrBuffer.join(`
|
|
28329
28389
|
`);
|
|
@@ -28460,8 +28520,8 @@ stderr: ${stderr}` : ""));
|
|
|
28460
28520
|
\r
|
|
28461
28521
|
`;
|
|
28462
28522
|
this.proc.stdin.write(header + msg);
|
|
28463
|
-
return new Promise((
|
|
28464
|
-
this.pending.set(id, { resolve:
|
|
28523
|
+
return new Promise((resolve9, reject) => {
|
|
28524
|
+
this.pending.set(id, { resolve: resolve9, reject });
|
|
28465
28525
|
setTimeout(() => {
|
|
28466
28526
|
if (this.pending.has(id)) {
|
|
28467
28527
|
this.pending.delete(id);
|
|
@@ -28569,7 +28629,7 @@ ${msg}`);
|
|
|
28569
28629
|
await new Promise((r2) => setTimeout(r2, 300));
|
|
28570
28630
|
}
|
|
28571
28631
|
async openFile(filePath) {
|
|
28572
|
-
const absPath =
|
|
28632
|
+
const absPath = resolve8(filePath);
|
|
28573
28633
|
if (this.openedFiles.has(absPath))
|
|
28574
28634
|
return;
|
|
28575
28635
|
const text = readFileSync28(absPath, "utf-8");
|
|
@@ -28587,7 +28647,7 @@ ${msg}`);
|
|
|
28587
28647
|
await new Promise((r2) => setTimeout(r2, 1000));
|
|
28588
28648
|
}
|
|
28589
28649
|
async hover(filePath, line, character) {
|
|
28590
|
-
const absPath =
|
|
28650
|
+
const absPath = resolve8(filePath);
|
|
28591
28651
|
await this.openFile(absPath);
|
|
28592
28652
|
return this.send("textDocument/hover", {
|
|
28593
28653
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -28595,7 +28655,7 @@ ${msg}`);
|
|
|
28595
28655
|
});
|
|
28596
28656
|
}
|
|
28597
28657
|
async definition(filePath, line, character) {
|
|
28598
|
-
const absPath =
|
|
28658
|
+
const absPath = resolve8(filePath);
|
|
28599
28659
|
await this.openFile(absPath);
|
|
28600
28660
|
return this.send("textDocument/definition", {
|
|
28601
28661
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -28603,7 +28663,7 @@ ${msg}`);
|
|
|
28603
28663
|
});
|
|
28604
28664
|
}
|
|
28605
28665
|
async references(filePath, line, character, includeDeclaration = true) {
|
|
28606
|
-
const absPath =
|
|
28666
|
+
const absPath = resolve8(filePath);
|
|
28607
28667
|
await this.openFile(absPath);
|
|
28608
28668
|
return this.send("textDocument/references", {
|
|
28609
28669
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -28612,7 +28672,7 @@ ${msg}`);
|
|
|
28612
28672
|
});
|
|
28613
28673
|
}
|
|
28614
28674
|
async documentSymbols(filePath) {
|
|
28615
|
-
const absPath =
|
|
28675
|
+
const absPath = resolve8(filePath);
|
|
28616
28676
|
await this.openFile(absPath);
|
|
28617
28677
|
return this.send("textDocument/documentSymbol", {
|
|
28618
28678
|
textDocument: { uri: `file://${absPath}` }
|
|
@@ -28622,7 +28682,7 @@ ${msg}`);
|
|
|
28622
28682
|
return this.send("workspace/symbol", { query });
|
|
28623
28683
|
}
|
|
28624
28684
|
async diagnostics(filePath) {
|
|
28625
|
-
const absPath =
|
|
28685
|
+
const absPath = resolve8(filePath);
|
|
28626
28686
|
const uri = `file://${absPath}`;
|
|
28627
28687
|
await this.openFile(absPath);
|
|
28628
28688
|
await new Promise((r2) => setTimeout(r2, 500));
|
|
@@ -28637,7 +28697,7 @@ ${msg}`);
|
|
|
28637
28697
|
return { items: this.diagnosticsStore.get(uri) ?? [] };
|
|
28638
28698
|
}
|
|
28639
28699
|
async prepareRename(filePath, line, character) {
|
|
28640
|
-
const absPath =
|
|
28700
|
+
const absPath = resolve8(filePath);
|
|
28641
28701
|
await this.openFile(absPath);
|
|
28642
28702
|
return this.send("textDocument/prepareRename", {
|
|
28643
28703
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -28645,7 +28705,7 @@ ${msg}`);
|
|
|
28645
28705
|
});
|
|
28646
28706
|
}
|
|
28647
28707
|
async rename(filePath, line, character, newName) {
|
|
28648
|
-
const absPath =
|
|
28708
|
+
const absPath = resolve8(filePath);
|
|
28649
28709
|
await this.openFile(absPath);
|
|
28650
28710
|
return this.send("textDocument/rename", {
|
|
28651
28711
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -28654,7 +28714,7 @@ ${msg}`);
|
|
|
28654
28714
|
});
|
|
28655
28715
|
}
|
|
28656
28716
|
async codeAction(filePath, startLine, startChar, endLine, endChar, only) {
|
|
28657
|
-
const absPath =
|
|
28717
|
+
const absPath = resolve8(filePath);
|
|
28658
28718
|
await this.openFile(absPath);
|
|
28659
28719
|
return this.send("textDocument/codeAction", {
|
|
28660
28720
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -28686,11 +28746,11 @@ ${msg}`);
|
|
|
28686
28746
|
}
|
|
28687
28747
|
}
|
|
28688
28748
|
// src/tools/lsp/utils.ts
|
|
28689
|
-
import { extname as extname2, resolve as
|
|
28749
|
+
import { extname as extname2, resolve as resolve9 } from "path";
|
|
28690
28750
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
28691
28751
|
import { existsSync as existsSync42, readFileSync as readFileSync29, writeFileSync as writeFileSync16 } from "fs";
|
|
28692
28752
|
function findWorkspaceRoot(filePath) {
|
|
28693
|
-
let dir =
|
|
28753
|
+
let dir = resolve9(filePath);
|
|
28694
28754
|
if (!existsSync42(dir) || !__require("fs").statSync(dir).isDirectory()) {
|
|
28695
28755
|
dir = __require("path").dirname(dir);
|
|
28696
28756
|
}
|
|
@@ -28703,7 +28763,7 @@ function findWorkspaceRoot(filePath) {
|
|
|
28703
28763
|
}
|
|
28704
28764
|
dir = __require("path").dirname(dir);
|
|
28705
28765
|
}
|
|
28706
|
-
return __require("path").dirname(
|
|
28766
|
+
return __require("path").dirname(resolve9(filePath));
|
|
28707
28767
|
}
|
|
28708
28768
|
function uriToPath(uri) {
|
|
28709
28769
|
return fileURLToPath3(uri);
|
|
@@ -28743,7 +28803,7 @@ function formatServerLookupError(result) {
|
|
|
28743
28803
|
`);
|
|
28744
28804
|
}
|
|
28745
28805
|
async function withLspClient(filePath, fn) {
|
|
28746
|
-
const absPath =
|
|
28806
|
+
const absPath = resolve9(filePath);
|
|
28747
28807
|
const ext = extname2(absPath);
|
|
28748
28808
|
const result = findServerForExtension(ext);
|
|
28749
28809
|
if (result.status !== "found") {
|
|
@@ -44079,7 +44139,7 @@ function formatDuration(start, end) {
|
|
|
44079
44139
|
}
|
|
44080
44140
|
}
|
|
44081
44141
|
function delay(ms) {
|
|
44082
|
-
return new Promise((
|
|
44142
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
44083
44143
|
}
|
|
44084
44144
|
function truncateText(text, maxLength) {
|
|
44085
44145
|
if (text.length <= maxLength)
|
|
@@ -44153,8 +44213,8 @@ Session ID: ${task.sessionID}
|
|
|
44153
44213
|
|
|
44154
44214
|
(No messages found)`;
|
|
44155
44215
|
}
|
|
44156
|
-
const
|
|
44157
|
-
if (
|
|
44216
|
+
const relevantMessages = messages.filter((m2) => m2.info?.role === "assistant" || m2.info?.role === "tool");
|
|
44217
|
+
if (relevantMessages.length === 0) {
|
|
44158
44218
|
return `Task Result
|
|
44159
44219
|
|
|
44160
44220
|
Task ID: ${task.id}
|
|
@@ -44164,11 +44224,34 @@ Session ID: ${task.sessionID}
|
|
|
44164
44224
|
|
|
44165
44225
|
---
|
|
44166
44226
|
|
|
44167
|
-
(No assistant response found)`;
|
|
44227
|
+
(No assistant or tool response found)`;
|
|
44168
44228
|
}
|
|
44169
|
-
const
|
|
44170
|
-
|
|
44171
|
-
|
|
44229
|
+
const sortedMessages = [...relevantMessages].sort((a, b3) => {
|
|
44230
|
+
const timeA = String(a.info?.time ?? "");
|
|
44231
|
+
const timeB = String(b3.info?.time ?? "");
|
|
44232
|
+
return timeA.localeCompare(timeB);
|
|
44233
|
+
});
|
|
44234
|
+
const extractedContent = [];
|
|
44235
|
+
for (const message of sortedMessages) {
|
|
44236
|
+
for (const part of message.parts ?? []) {
|
|
44237
|
+
if ((part.type === "text" || part.type === "reasoning") && part.text) {
|
|
44238
|
+
extractedContent.push(part.text);
|
|
44239
|
+
} else if (part.type === "tool_result") {
|
|
44240
|
+
const toolResult = part;
|
|
44241
|
+
if (typeof toolResult.content === "string" && toolResult.content) {
|
|
44242
|
+
extractedContent.push(toolResult.content);
|
|
44243
|
+
} else if (Array.isArray(toolResult.content)) {
|
|
44244
|
+
for (const block of toolResult.content) {
|
|
44245
|
+
if ((block.type === "text" || block.type === "reasoning") && block.text) {
|
|
44246
|
+
extractedContent.push(block.text);
|
|
44247
|
+
}
|
|
44248
|
+
}
|
|
44249
|
+
}
|
|
44250
|
+
}
|
|
44251
|
+
}
|
|
44252
|
+
}
|
|
44253
|
+
const textContent = extractedContent.filter((text) => text.length > 0).join(`
|
|
44254
|
+
|
|
44172
44255
|
`);
|
|
44173
44256
|
const duration3 = formatDuration(task.startedAt, task.completedAt);
|
|
44174
44257
|
return `Task Result
|
|
@@ -44431,19 +44514,43 @@ session_id: ${sessionID}
|
|
|
44431
44514
|
}
|
|
44432
44515
|
const messages = messagesResult.data;
|
|
44433
44516
|
log(`[call_omo_agent] Got ${messages.length} messages`);
|
|
44434
|
-
const
|
|
44435
|
-
if (
|
|
44436
|
-
log(`[call_omo_agent] No assistant
|
|
44517
|
+
const relevantMessages = messages.filter((m2) => m2.info?.role === "assistant" || m2.info?.role === "tool");
|
|
44518
|
+
if (relevantMessages.length === 0) {
|
|
44519
|
+
log(`[call_omo_agent] No assistant or tool messages found`);
|
|
44437
44520
|
log(`[call_omo_agent] All messages:`, JSON.stringify(messages, null, 2));
|
|
44438
|
-
return `Error: No assistant response found
|
|
44521
|
+
return `Error: No assistant or tool response found
|
|
44439
44522
|
|
|
44440
44523
|
<task_metadata>
|
|
44441
44524
|
session_id: ${sessionID}
|
|
44442
44525
|
</task_metadata>`;
|
|
44443
44526
|
}
|
|
44444
|
-
log(`[call_omo_agent] Found
|
|
44445
|
-
const
|
|
44446
|
-
|
|
44527
|
+
log(`[call_omo_agent] Found ${relevantMessages.length} relevant messages`);
|
|
44528
|
+
const sortedMessages = [...relevantMessages].sort((a, b3) => {
|
|
44529
|
+
const timeA = a.info?.time?.created ?? 0;
|
|
44530
|
+
const timeB = b3.info?.time?.created ?? 0;
|
|
44531
|
+
return timeA - timeB;
|
|
44532
|
+
});
|
|
44533
|
+
const extractedContent = [];
|
|
44534
|
+
for (const message of sortedMessages) {
|
|
44535
|
+
for (const part of message.parts ?? []) {
|
|
44536
|
+
if ((part.type === "text" || part.type === "reasoning") && part.text) {
|
|
44537
|
+
extractedContent.push(part.text);
|
|
44538
|
+
} else if (part.type === "tool_result") {
|
|
44539
|
+
const toolResult = part;
|
|
44540
|
+
if (typeof toolResult.content === "string" && toolResult.content) {
|
|
44541
|
+
extractedContent.push(toolResult.content);
|
|
44542
|
+
} else if (Array.isArray(toolResult.content)) {
|
|
44543
|
+
for (const block of toolResult.content) {
|
|
44544
|
+
if ((block.type === "text" || block.type === "reasoning") && block.text) {
|
|
44545
|
+
extractedContent.push(block.text);
|
|
44546
|
+
}
|
|
44547
|
+
}
|
|
44548
|
+
}
|
|
44549
|
+
}
|
|
44550
|
+
}
|
|
44551
|
+
}
|
|
44552
|
+
const responseText = extractedContent.filter((text) => text.length > 0).join(`
|
|
44553
|
+
|
|
44447
44554
|
`);
|
|
44448
44555
|
log(`[call_omo_agent] Got response, length: ${responseText.length}`);
|
|
44449
44556
|
const output = responseText + `
|
|
@@ -44878,6 +44985,29 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`;
|
|
|
44878
44985
|
|
|
44879
44986
|
Session ID: ${args.resume}`;
|
|
44880
44987
|
}
|
|
44988
|
+
const POLL_INTERVAL_MS = 500;
|
|
44989
|
+
const MIN_STABILITY_TIME_MS = 5000;
|
|
44990
|
+
const STABILITY_POLLS_REQUIRED = 3;
|
|
44991
|
+
const pollStart = Date.now();
|
|
44992
|
+
let lastMsgCount = 0;
|
|
44993
|
+
let stablePolls = 0;
|
|
44994
|
+
while (Date.now() - pollStart < 60000) {
|
|
44995
|
+
await new Promise((resolve10) => setTimeout(resolve10, POLL_INTERVAL_MS));
|
|
44996
|
+
const elapsed = Date.now() - pollStart;
|
|
44997
|
+
if (elapsed < MIN_STABILITY_TIME_MS)
|
|
44998
|
+
continue;
|
|
44999
|
+
const messagesCheck = await client2.session.messages({ path: { id: args.resume } });
|
|
45000
|
+
const msgs = messagesCheck.data ?? messagesCheck;
|
|
45001
|
+
const currentMsgCount = msgs.length;
|
|
45002
|
+
if (currentMsgCount > 0 && currentMsgCount === lastMsgCount) {
|
|
45003
|
+
stablePolls++;
|
|
45004
|
+
if (stablePolls >= STABILITY_POLLS_REQUIRED)
|
|
45005
|
+
break;
|
|
45006
|
+
} else {
|
|
45007
|
+
stablePolls = 0;
|
|
45008
|
+
lastMsgCount = currentMsgCount;
|
|
45009
|
+
}
|
|
45010
|
+
}
|
|
44881
45011
|
const messagesResult = await client2.session.messages({
|
|
44882
45012
|
path: { id: args.resume }
|
|
44883
45013
|
});
|
|
@@ -44900,7 +45030,7 @@ Session ID: ${args.resume}`;
|
|
|
44900
45030
|
|
|
44901
45031
|
Session ID: ${args.resume}`;
|
|
44902
45032
|
}
|
|
44903
|
-
const textParts = lastMessage?.parts?.filter((p2) => p2.type === "text") ?? [];
|
|
45033
|
+
const textParts = lastMessage?.parts?.filter((p2) => p2.type === "text" || p2.type === "reasoning") ?? [];
|
|
44904
45034
|
const textContent = textParts.map((p2) => p2.text ?? "").filter(Boolean).join(`
|
|
44905
45035
|
`);
|
|
44906
45036
|
const duration3 = formatDuration2(startTime);
|
|
@@ -45014,11 +45144,10 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id
|
|
|
45014
45144
|
metadata: { sessionId: sessionID, category: args.category, sync: true }
|
|
45015
45145
|
});
|
|
45016
45146
|
let promptError;
|
|
45017
|
-
|
|
45147
|
+
client2.session.prompt({
|
|
45018
45148
|
path: { id: sessionID },
|
|
45019
45149
|
body: {
|
|
45020
45150
|
agent: agentToUse,
|
|
45021
|
-
model: categoryModel,
|
|
45022
45151
|
system: systemContent,
|
|
45023
45152
|
tools: {
|
|
45024
45153
|
task: false,
|
|
@@ -45029,6 +45158,7 @@ System notifies on completion. Use \`background_output\` with task_id="${task.id
|
|
|
45029
45158
|
}).catch((error45) => {
|
|
45030
45159
|
promptError = error45 instanceof Error ? error45 : new Error(String(error45));
|
|
45031
45160
|
});
|
|
45161
|
+
await new Promise((resolve10) => setTimeout(resolve10, 100));
|
|
45032
45162
|
if (promptError) {
|
|
45033
45163
|
if (toastManager && taskId !== undefined) {
|
|
45034
45164
|
toastManager.removeTask(taskId);
|
|
@@ -45045,14 +45175,51 @@ Session ID: ${sessionID}`;
|
|
|
45045
45175
|
}
|
|
45046
45176
|
const POLL_INTERVAL_MS = 500;
|
|
45047
45177
|
const MAX_POLL_TIME_MS = 10 * 60 * 1000;
|
|
45178
|
+
const MIN_STABILITY_TIME_MS = 1e4;
|
|
45179
|
+
const STABILITY_POLLS_REQUIRED = 3;
|
|
45048
45180
|
const pollStart = Date.now();
|
|
45181
|
+
let lastMsgCount = 0;
|
|
45182
|
+
let stablePolls = 0;
|
|
45049
45183
|
while (Date.now() - pollStart < MAX_POLL_TIME_MS) {
|
|
45050
|
-
await new Promise((
|
|
45184
|
+
await new Promise((resolve10) => setTimeout(resolve10, POLL_INTERVAL_MS));
|
|
45185
|
+
const asyncError = promptError;
|
|
45186
|
+
if (asyncError) {
|
|
45187
|
+
if (toastManager && taskId !== undefined) {
|
|
45188
|
+
toastManager.removeTask(taskId);
|
|
45189
|
+
}
|
|
45190
|
+
const errorMessage = asyncError.message;
|
|
45191
|
+
if (errorMessage.includes("agent.name") || errorMessage.includes("undefined")) {
|
|
45192
|
+
return `\u274C Agent "${agentToUse}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.
|
|
45193
|
+
|
|
45194
|
+
Session ID: ${sessionID}`;
|
|
45195
|
+
}
|
|
45196
|
+
return `\u274C Failed to send prompt: ${errorMessage}
|
|
45197
|
+
|
|
45198
|
+
Session ID: ${sessionID}`;
|
|
45199
|
+
}
|
|
45051
45200
|
const statusResult = await client2.session.status();
|
|
45052
45201
|
const allStatuses = statusResult.data ?? {};
|
|
45053
45202
|
const sessionStatus = allStatuses[sessionID];
|
|
45054
|
-
if (
|
|
45055
|
-
|
|
45203
|
+
if (sessionStatus && sessionStatus.type !== "idle") {
|
|
45204
|
+
stablePolls = 0;
|
|
45205
|
+
lastMsgCount = 0;
|
|
45206
|
+
continue;
|
|
45207
|
+
}
|
|
45208
|
+
const elapsed = Date.now() - pollStart;
|
|
45209
|
+
if (elapsed < MIN_STABILITY_TIME_MS) {
|
|
45210
|
+
continue;
|
|
45211
|
+
}
|
|
45212
|
+
const messagesCheck = await client2.session.messages({ path: { id: sessionID } });
|
|
45213
|
+
const msgs = messagesCheck.data ?? messagesCheck;
|
|
45214
|
+
const currentMsgCount = msgs.length;
|
|
45215
|
+
if (currentMsgCount > 0 && currentMsgCount === lastMsgCount) {
|
|
45216
|
+
stablePolls++;
|
|
45217
|
+
if (stablePolls >= STABILITY_POLLS_REQUIRED) {
|
|
45218
|
+
break;
|
|
45219
|
+
}
|
|
45220
|
+
} else {
|
|
45221
|
+
stablePolls = 0;
|
|
45222
|
+
lastMsgCount = currentMsgCount;
|
|
45056
45223
|
}
|
|
45057
45224
|
}
|
|
45058
45225
|
const messagesResult = await client2.session.messages({
|
|
@@ -45071,7 +45238,7 @@ Session ID: ${sessionID}`;
|
|
|
45071
45238
|
|
|
45072
45239
|
Session ID: ${sessionID}`;
|
|
45073
45240
|
}
|
|
45074
|
-
const textParts = lastMessage?.parts?.filter((p2) => p2.type === "text") ?? [];
|
|
45241
|
+
const textParts = lastMessage?.parts?.filter((p2) => p2.type === "text" || p2.type === "reasoning") ?? [];
|
|
45075
45242
|
const textContent = textParts.map((p2) => p2.text ?? "").filter(Boolean).join(`
|
|
45076
45243
|
`);
|
|
45077
45244
|
const duration3 = formatDuration2(startTime);
|
|
@@ -45103,7 +45270,6 @@ ${textContent || "(No text output)"}`;
|
|
|
45103
45270
|
|
|
45104
45271
|
// src/tools/sisyphus-task/index.ts
|
|
45105
45272
|
init_constants();
|
|
45106
|
-
|
|
45107
45273
|
// src/tools/index.ts
|
|
45108
45274
|
function createBackgroundTools(manager, client2) {
|
|
45109
45275
|
return {
|
|
@@ -45166,9 +45332,9 @@ class ConcurrencyManager {
|
|
|
45166
45332
|
this.counts.set(model, current + 1);
|
|
45167
45333
|
return;
|
|
45168
45334
|
}
|
|
45169
|
-
return new Promise((
|
|
45335
|
+
return new Promise((resolve10) => {
|
|
45170
45336
|
const queue = this.queues.get(model) ?? [];
|
|
45171
|
-
queue.push(
|
|
45337
|
+
queue.push(resolve10);
|
|
45172
45338
|
this.queues.set(model, queue);
|
|
45173
45339
|
});
|
|
45174
45340
|
}
|
|
@@ -45193,10 +45359,12 @@ class ConcurrencyManager {
|
|
|
45193
45359
|
|
|
45194
45360
|
// src/features/background-agent/manager.ts
|
|
45195
45361
|
var TASK_TTL_MS = 30 * 60 * 1000;
|
|
45362
|
+
var MIN_STABILITY_TIME_MS = 10 * 1000;
|
|
45196
45363
|
|
|
45197
45364
|
class BackgroundManager {
|
|
45198
45365
|
tasks;
|
|
45199
45366
|
notifications;
|
|
45367
|
+
pendingByParent;
|
|
45200
45368
|
client;
|
|
45201
45369
|
directory;
|
|
45202
45370
|
pollingInterval;
|
|
@@ -45204,11 +45372,18 @@ class BackgroundManager {
|
|
|
45204
45372
|
constructor(ctx, config3) {
|
|
45205
45373
|
this.tasks = new Map;
|
|
45206
45374
|
this.notifications = new Map;
|
|
45375
|
+
this.pendingByParent = new Map;
|
|
45207
45376
|
this.client = ctx.client;
|
|
45208
45377
|
this.directory = ctx.directory;
|
|
45209
45378
|
this.concurrencyManager = new ConcurrencyManager(config3);
|
|
45210
45379
|
}
|
|
45211
45380
|
async launch(input) {
|
|
45381
|
+
log("[background-agent] launch() called with:", {
|
|
45382
|
+
agent: input.agent,
|
|
45383
|
+
model: input.model,
|
|
45384
|
+
description: input.description,
|
|
45385
|
+
parentSessionID: input.parentSessionID
|
|
45386
|
+
});
|
|
45212
45387
|
if (!input.agent || input.agent.trim() === "") {
|
|
45213
45388
|
throw new Error("Agent parameter is required");
|
|
45214
45389
|
}
|
|
@@ -45250,6 +45425,9 @@ class BackgroundManager {
|
|
|
45250
45425
|
};
|
|
45251
45426
|
this.tasks.set(task.id, task);
|
|
45252
45427
|
this.startPolling();
|
|
45428
|
+
const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
|
|
45429
|
+
pending.add(task.id);
|
|
45430
|
+
this.pendingByParent.set(input.parentSessionID, pending);
|
|
45253
45431
|
log("[background-agent] Launching task:", { taskId: task.id, sessionID, agent: input.agent });
|
|
45254
45432
|
const toastManager = getTaskToastManager();
|
|
45255
45433
|
if (toastManager) {
|
|
@@ -45261,10 +45439,18 @@ class BackgroundManager {
|
|
|
45261
45439
|
skills: input.skills
|
|
45262
45440
|
});
|
|
45263
45441
|
}
|
|
45264
|
-
|
|
45442
|
+
log("[background-agent] Calling prompt (fire-and-forget) for launch with:", {
|
|
45443
|
+
sessionID,
|
|
45444
|
+
agent: input.agent,
|
|
45445
|
+
model: input.model,
|
|
45446
|
+
hasSkillContent: !!input.skillContent,
|
|
45447
|
+
promptLength: input.prompt.length
|
|
45448
|
+
});
|
|
45449
|
+
this.client.session.prompt({
|
|
45265
45450
|
path: { id: sessionID },
|
|
45266
45451
|
body: {
|
|
45267
45452
|
agent: input.agent,
|
|
45453
|
+
...input.model ? { model: input.model } : {},
|
|
45268
45454
|
system: input.skillContent,
|
|
45269
45455
|
tools: {
|
|
45270
45456
|
task: false,
|
|
@@ -45288,7 +45474,9 @@ class BackgroundManager {
|
|
|
45288
45474
|
this.concurrencyManager.release(existingTask.concurrencyKey);
|
|
45289
45475
|
}
|
|
45290
45476
|
this.markForNotification(existingTask);
|
|
45291
|
-
this.notifyParentSession(existingTask)
|
|
45477
|
+
this.notifyParentSession(existingTask).catch((err) => {
|
|
45478
|
+
log("[background-agent] Failed to notify on error:", err);
|
|
45479
|
+
});
|
|
45292
45480
|
}
|
|
45293
45481
|
});
|
|
45294
45482
|
return task;
|
|
@@ -45337,11 +45525,15 @@ class BackgroundManager {
|
|
|
45337
45525
|
progress: {
|
|
45338
45526
|
toolCalls: 0,
|
|
45339
45527
|
lastUpdate: new Date
|
|
45340
|
-
}
|
|
45528
|
+
},
|
|
45529
|
+
parentAgent: input.parentAgent
|
|
45341
45530
|
};
|
|
45342
45531
|
this.tasks.set(task.id, task);
|
|
45343
45532
|
subagentSessions.add(input.sessionID);
|
|
45344
45533
|
this.startPolling();
|
|
45534
|
+
const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
|
|
45535
|
+
pending.add(task.id);
|
|
45536
|
+
this.pendingByParent.set(input.parentSessionID, pending);
|
|
45345
45537
|
log("[background-agent] Registered external task:", { taskId: task.id, sessionID: input.sessionID });
|
|
45346
45538
|
return task;
|
|
45347
45539
|
}
|
|
@@ -45363,6 +45555,9 @@ class BackgroundManager {
|
|
|
45363
45555
|
};
|
|
45364
45556
|
this.startPolling();
|
|
45365
45557
|
subagentSessions.add(existingTask.sessionID);
|
|
45558
|
+
const pending = this.pendingByParent.get(input.parentSessionID) ?? new Set;
|
|
45559
|
+
pending.add(existingTask.id);
|
|
45560
|
+
this.pendingByParent.set(input.parentSessionID, pending);
|
|
45366
45561
|
const toastManager = getTaskToastManager();
|
|
45367
45562
|
if (toastManager) {
|
|
45368
45563
|
toastManager.addTask({
|
|
@@ -45373,7 +45568,12 @@ class BackgroundManager {
|
|
|
45373
45568
|
});
|
|
45374
45569
|
}
|
|
45375
45570
|
log("[background-agent] Resuming task:", { taskId: existingTask.id, sessionID: existingTask.sessionID });
|
|
45376
|
-
|
|
45571
|
+
log("[background-agent] Resuming task - calling prompt (fire-and-forget) with:", {
|
|
45572
|
+
sessionID: existingTask.sessionID,
|
|
45573
|
+
agent: existingTask.agent,
|
|
45574
|
+
promptLength: input.prompt.length
|
|
45575
|
+
});
|
|
45576
|
+
this.client.session.prompt({
|
|
45377
45577
|
path: { id: existingTask.sessionID },
|
|
45378
45578
|
body: {
|
|
45379
45579
|
agent: existingTask.agent,
|
|
@@ -45384,13 +45584,15 @@ class BackgroundManager {
|
|
|
45384
45584
|
parts: [{ type: "text", text: input.prompt }]
|
|
45385
45585
|
}
|
|
45386
45586
|
}).catch((error45) => {
|
|
45387
|
-
log("[background-agent] resume
|
|
45587
|
+
log("[background-agent] resume prompt error:", error45);
|
|
45388
45588
|
existingTask.status = "error";
|
|
45389
45589
|
const errorMessage = error45 instanceof Error ? error45.message : String(error45);
|
|
45390
45590
|
existingTask.error = errorMessage;
|
|
45391
45591
|
existingTask.completedAt = new Date;
|
|
45392
45592
|
this.markForNotification(existingTask);
|
|
45393
|
-
this.notifyParentSession(existingTask)
|
|
45593
|
+
this.notifyParentSession(existingTask).catch((err) => {
|
|
45594
|
+
log("[background-agent] Failed to notify on resume error:", err);
|
|
45595
|
+
});
|
|
45394
45596
|
});
|
|
45395
45597
|
return existingTask;
|
|
45396
45598
|
}
|
|
@@ -45439,7 +45641,18 @@ class BackgroundManager {
|
|
|
45439
45641
|
const task = this.findBySession(sessionID);
|
|
45440
45642
|
if (!task || task.status !== "running")
|
|
45441
45643
|
return;
|
|
45442
|
-
|
|
45644
|
+
const elapsedMs = Date.now() - task.startedAt.getTime();
|
|
45645
|
+
const MIN_IDLE_TIME_MS = 5000;
|
|
45646
|
+
if (elapsedMs < MIN_IDLE_TIME_MS) {
|
|
45647
|
+
log("[background-agent] Ignoring early session.idle, elapsed:", { elapsedMs, taskId: task.id });
|
|
45648
|
+
return;
|
|
45649
|
+
}
|
|
45650
|
+
this.validateSessionHasOutput(sessionID).then(async (hasValidOutput) => {
|
|
45651
|
+
if (!hasValidOutput) {
|
|
45652
|
+
log("[background-agent] Session.idle but no valid output yet, waiting:", task.id);
|
|
45653
|
+
return;
|
|
45654
|
+
}
|
|
45655
|
+
const hasIncompleteTodos2 = await this.checkSessionTodos(sessionID);
|
|
45443
45656
|
if (hasIncompleteTodos2) {
|
|
45444
45657
|
log("[background-agent] Task has incomplete todos, waiting for todo-continuation:", task.id);
|
|
45445
45658
|
return;
|
|
@@ -45447,8 +45660,10 @@ class BackgroundManager {
|
|
|
45447
45660
|
task.status = "completed";
|
|
45448
45661
|
task.completedAt = new Date;
|
|
45449
45662
|
this.markForNotification(task);
|
|
45450
|
-
this.notifyParentSession(task);
|
|
45663
|
+
await this.notifyParentSession(task);
|
|
45451
45664
|
log("[background-agent] Task completed via session.idle event:", task.id);
|
|
45665
|
+
}).catch((err) => {
|
|
45666
|
+
log("[background-agent] Error in session.idle handler:", err);
|
|
45452
45667
|
});
|
|
45453
45668
|
}
|
|
45454
45669
|
if (event.type === "session.deleted") {
|
|
@@ -45483,6 +45698,33 @@ class BackgroundManager {
|
|
|
45483
45698
|
clearNotifications(sessionID) {
|
|
45484
45699
|
this.notifications.delete(sessionID);
|
|
45485
45700
|
}
|
|
45701
|
+
async validateSessionHasOutput(sessionID) {
|
|
45702
|
+
try {
|
|
45703
|
+
const response2 = await this.client.session.messages({
|
|
45704
|
+
path: { id: sessionID }
|
|
45705
|
+
});
|
|
45706
|
+
const messages = response2.data ?? [];
|
|
45707
|
+
const hasAssistantOrToolMessage = messages.some((m2) => m2.info?.role === "assistant" || m2.info?.role === "tool");
|
|
45708
|
+
if (!hasAssistantOrToolMessage) {
|
|
45709
|
+
log("[background-agent] No assistant/tool messages found in session:", sessionID);
|
|
45710
|
+
return false;
|
|
45711
|
+
}
|
|
45712
|
+
const hasContent2 = messages.some((m2) => {
|
|
45713
|
+
if (m2.info?.role !== "assistant" && m2.info?.role !== "tool")
|
|
45714
|
+
return false;
|
|
45715
|
+
const parts = m2.parts ?? [];
|
|
45716
|
+
return parts.some((p2) => p2.type === "text" && p2.text && p2.text.trim().length > 0 || p2.type === "reasoning" && p2.text && p2.text.trim().length > 0 || p2.type === "tool" || p2.type === "tool_result" && p2.content && (typeof p2.content === "string" ? p2.content.trim().length > 0 : p2.content.length > 0));
|
|
45717
|
+
});
|
|
45718
|
+
if (!hasContent2) {
|
|
45719
|
+
log("[background-agent] Messages exist but no content found in session:", sessionID);
|
|
45720
|
+
return false;
|
|
45721
|
+
}
|
|
45722
|
+
return true;
|
|
45723
|
+
} catch (error45) {
|
|
45724
|
+
log("[background-agent] Error validating session output:", error45);
|
|
45725
|
+
return true;
|
|
45726
|
+
}
|
|
45727
|
+
}
|
|
45486
45728
|
clearNotificationsForTask(taskId) {
|
|
45487
45729
|
for (const [sessionID, tasks] of this.notifications.entries()) {
|
|
45488
45730
|
const filtered = tasks.filter((t) => t.id !== taskId);
|
|
@@ -45511,8 +45753,15 @@ class BackgroundManager {
|
|
|
45511
45753
|
this.stopPolling();
|
|
45512
45754
|
this.tasks.clear();
|
|
45513
45755
|
this.notifications.clear();
|
|
45756
|
+
this.pendingByParent.clear();
|
|
45757
|
+
}
|
|
45758
|
+
getRunningTasks() {
|
|
45759
|
+
return Array.from(this.tasks.values()).filter((t) => t.status === "running");
|
|
45514
45760
|
}
|
|
45515
|
-
|
|
45761
|
+
getCompletedTasks() {
|
|
45762
|
+
return Array.from(this.tasks.values()).filter((t) => t.status !== "running");
|
|
45763
|
+
}
|
|
45764
|
+
async notifyParentSession(task) {
|
|
45516
45765
|
const duration3 = this.formatDuration(task.startedAt, task.completedAt);
|
|
45517
45766
|
log("[background-agent] notifyParentSession called for task:", task.id);
|
|
45518
45767
|
const toastManager = getTaskToastManager();
|
|
@@ -45523,33 +45772,70 @@ class BackgroundManager {
|
|
|
45523
45772
|
duration: duration3
|
|
45524
45773
|
});
|
|
45525
45774
|
}
|
|
45526
|
-
const
|
|
45527
|
-
|
|
45775
|
+
const pendingSet = this.pendingByParent.get(task.parentSessionID);
|
|
45776
|
+
if (pendingSet) {
|
|
45777
|
+
pendingSet.delete(task.id);
|
|
45778
|
+
if (pendingSet.size === 0) {
|
|
45779
|
+
this.pendingByParent.delete(task.parentSessionID);
|
|
45780
|
+
}
|
|
45781
|
+
}
|
|
45782
|
+
const allComplete = !pendingSet || pendingSet.size === 0;
|
|
45783
|
+
const remainingCount = pendingSet?.size ?? 0;
|
|
45784
|
+
const statusText = task.status === "error" ? "FAILED" : "COMPLETED";
|
|
45785
|
+
const errorInfo = task.error ? `
|
|
45786
|
+
**Error:** ${task.error}` : "";
|
|
45787
|
+
let notification;
|
|
45788
|
+
if (allComplete) {
|
|
45789
|
+
const completedTasks = Array.from(this.tasks.values()).filter((t) => t.parentSessionID === task.parentSessionID && t.status !== "running").map((t) => `- \`${t.id}\`: ${t.description}`).join(`
|
|
45790
|
+
`);
|
|
45791
|
+
notification = `<system-reminder>
|
|
45792
|
+
[ALL BACKGROUND TASKS COMPLETE]
|
|
45793
|
+
|
|
45794
|
+
**Completed:**
|
|
45795
|
+
${completedTasks || `- \`${task.id}\`: ${task.description}`}
|
|
45796
|
+
|
|
45797
|
+
Use \`background_output(task_id="<id>")\` to retrieve each result.
|
|
45798
|
+
</system-reminder>`;
|
|
45799
|
+
} else {
|
|
45800
|
+
notification = `<system-reminder>
|
|
45801
|
+
[BACKGROUND TASK ${statusText}]
|
|
45802
|
+
**ID:** \`${task.id}\`
|
|
45803
|
+
**Description:** ${task.description}
|
|
45804
|
+
**Duration:** ${duration3}${errorInfo}
|
|
45805
|
+
|
|
45806
|
+
**${remainingCount} task${remainingCount === 1 ? "" : "s"} still in progress.** You WILL be notified when ALL complete.
|
|
45807
|
+
Do NOT poll - continue productive work.
|
|
45808
|
+
|
|
45809
|
+
Use \`background_output(task_id="${task.id}")\` to retrieve this result when ready.
|
|
45810
|
+
</system-reminder>`;
|
|
45811
|
+
}
|
|
45812
|
+
try {
|
|
45813
|
+
await this.client.session.prompt({
|
|
45814
|
+
path: { id: task.parentSessionID },
|
|
45815
|
+
body: {
|
|
45816
|
+
noReply: !allComplete,
|
|
45817
|
+
agent: task.parentAgent,
|
|
45818
|
+
parts: [{ type: "text", text: notification }]
|
|
45819
|
+
}
|
|
45820
|
+
});
|
|
45821
|
+
log("[background-agent] Sent notification to parent session:", {
|
|
45822
|
+
taskId: task.id,
|
|
45823
|
+
allComplete,
|
|
45824
|
+
noReply: !allComplete
|
|
45825
|
+
});
|
|
45826
|
+
} catch (error45) {
|
|
45827
|
+
log("[background-agent] Failed to send notification:", error45);
|
|
45828
|
+
}
|
|
45528
45829
|
const taskId = task.id;
|
|
45529
|
-
setTimeout(
|
|
45830
|
+
setTimeout(() => {
|
|
45530
45831
|
if (task.concurrencyKey) {
|
|
45531
45832
|
this.concurrencyManager.release(task.concurrencyKey);
|
|
45833
|
+
task.concurrencyKey = undefined;
|
|
45532
45834
|
}
|
|
45533
|
-
|
|
45534
|
-
|
|
45535
|
-
|
|
45536
|
-
|
|
45537
|
-
body: {
|
|
45538
|
-
agent: task.parentAgent,
|
|
45539
|
-
model: modelField,
|
|
45540
|
-
parts: [{ type: "text", text: message }]
|
|
45541
|
-
},
|
|
45542
|
-
query: { directory: this.directory }
|
|
45543
|
-
});
|
|
45544
|
-
log("[background-agent] Successfully sent prompt to parent session:", { parentSessionID: task.parentSessionID });
|
|
45545
|
-
} catch (error45) {
|
|
45546
|
-
log("[background-agent] prompt failed:", String(error45));
|
|
45547
|
-
} finally {
|
|
45548
|
-
this.clearNotificationsForTask(taskId);
|
|
45549
|
-
this.tasks.delete(taskId);
|
|
45550
|
-
log("[background-agent] Removed completed task from memory:", taskId);
|
|
45551
|
-
}
|
|
45552
|
-
}, 200);
|
|
45835
|
+
this.clearNotificationsForTask(taskId);
|
|
45836
|
+
this.tasks.delete(taskId);
|
|
45837
|
+
log("[background-agent] Removed completed task from memory:", taskId);
|
|
45838
|
+
}, 5 * 60 * 1000);
|
|
45553
45839
|
}
|
|
45554
45840
|
formatDuration(start, end) {
|
|
45555
45841
|
const duration3 = (end ?? new Date).getTime() - start.getTime();
|
|
@@ -45612,11 +45898,12 @@ class BackgroundManager {
|
|
|
45612
45898
|
continue;
|
|
45613
45899
|
try {
|
|
45614
45900
|
const sessionStatus = allStatuses[task.sessionID];
|
|
45615
|
-
if (
|
|
45616
|
-
|
|
45617
|
-
|
|
45618
|
-
|
|
45619
|
-
|
|
45901
|
+
if (sessionStatus?.type === "idle") {
|
|
45902
|
+
const hasValidOutput = await this.validateSessionHasOutput(task.sessionID);
|
|
45903
|
+
if (!hasValidOutput) {
|
|
45904
|
+
log("[background-agent] Polling idle but no valid output yet, waiting:", task.id);
|
|
45905
|
+
continue;
|
|
45906
|
+
}
|
|
45620
45907
|
const hasIncompleteTodos2 = await this.checkSessionTodos(task.sessionID);
|
|
45621
45908
|
if (hasIncompleteTodos2) {
|
|
45622
45909
|
log("[background-agent] Task has incomplete todos via polling, waiting:", task.id);
|
|
@@ -45625,7 +45912,7 @@ class BackgroundManager {
|
|
|
45625
45912
|
task.status = "completed";
|
|
45626
45913
|
task.completedAt = new Date;
|
|
45627
45914
|
this.markForNotification(task);
|
|
45628
|
-
this.notifyParentSession(task);
|
|
45915
|
+
await this.notifyParentSession(task);
|
|
45629
45916
|
log("[background-agent] Task completed via polling:", task.id);
|
|
45630
45917
|
continue;
|
|
45631
45918
|
}
|
|
@@ -45660,6 +45947,32 @@ class BackgroundManager {
|
|
|
45660
45947
|
task.progress.lastMessage = lastMessage;
|
|
45661
45948
|
task.progress.lastMessageAt = new Date;
|
|
45662
45949
|
}
|
|
45950
|
+
const currentMsgCount = messages.length;
|
|
45951
|
+
const elapsedMs = Date.now() - task.startedAt.getTime();
|
|
45952
|
+
if (elapsedMs >= MIN_STABILITY_TIME_MS) {
|
|
45953
|
+
if (task.lastMsgCount === currentMsgCount) {
|
|
45954
|
+
task.stablePolls = (task.stablePolls ?? 0) + 1;
|
|
45955
|
+
if (task.stablePolls >= 3) {
|
|
45956
|
+
const hasValidOutput = await this.validateSessionHasOutput(task.sessionID);
|
|
45957
|
+
if (!hasValidOutput) {
|
|
45958
|
+
log("[background-agent] Stability reached but no valid output, waiting:", task.id);
|
|
45959
|
+
continue;
|
|
45960
|
+
}
|
|
45961
|
+
const hasIncompleteTodos2 = await this.checkSessionTodos(task.sessionID);
|
|
45962
|
+
if (!hasIncompleteTodos2) {
|
|
45963
|
+
task.status = "completed";
|
|
45964
|
+
task.completedAt = new Date;
|
|
45965
|
+
this.markForNotification(task);
|
|
45966
|
+
await this.notifyParentSession(task);
|
|
45967
|
+
log("[background-agent] Task completed via stability detection:", task.id);
|
|
45968
|
+
continue;
|
|
45969
|
+
}
|
|
45970
|
+
}
|
|
45971
|
+
} else {
|
|
45972
|
+
task.stablePolls = 0;
|
|
45973
|
+
}
|
|
45974
|
+
}
|
|
45975
|
+
task.lastMsgCount = currentMsgCount;
|
|
45663
45976
|
}
|
|
45664
45977
|
} catch (error45) {
|
|
45665
45978
|
log("[background-agent] Poll error for task:", { taskId: task.id, error: error45 });
|
|
@@ -47039,7 +47352,7 @@ class Protocol {
|
|
|
47039
47352
|
return;
|
|
47040
47353
|
}
|
|
47041
47354
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1000;
|
|
47042
|
-
await new Promise((
|
|
47355
|
+
await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
|
|
47043
47356
|
options?.signal?.throwIfAborted();
|
|
47044
47357
|
}
|
|
47045
47358
|
} catch (error45) {
|
|
@@ -47051,7 +47364,7 @@ class Protocol {
|
|
|
47051
47364
|
}
|
|
47052
47365
|
request(request2, resultSchema, options) {
|
|
47053
47366
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
47054
|
-
return new Promise((
|
|
47367
|
+
return new Promise((resolve10, reject) => {
|
|
47055
47368
|
const earlyReject = (error45) => {
|
|
47056
47369
|
reject(error45);
|
|
47057
47370
|
};
|
|
@@ -47129,7 +47442,7 @@ class Protocol {
|
|
|
47129
47442
|
if (!parseResult.success) {
|
|
47130
47443
|
reject(parseResult.error);
|
|
47131
47444
|
} else {
|
|
47132
|
-
|
|
47445
|
+
resolve10(parseResult.data);
|
|
47133
47446
|
}
|
|
47134
47447
|
} catch (error45) {
|
|
47135
47448
|
reject(error45);
|
|
@@ -47320,12 +47633,12 @@ class Protocol {
|
|
|
47320
47633
|
interval = task.pollInterval;
|
|
47321
47634
|
}
|
|
47322
47635
|
} catch {}
|
|
47323
|
-
return new Promise((
|
|
47636
|
+
return new Promise((resolve10, reject) => {
|
|
47324
47637
|
if (signal.aborted) {
|
|
47325
47638
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
47326
47639
|
return;
|
|
47327
47640
|
}
|
|
47328
|
-
const timeoutId = setTimeout(
|
|
47641
|
+
const timeoutId = setTimeout(resolve10, interval);
|
|
47329
47642
|
signal.addEventListener("abort", () => {
|
|
47330
47643
|
clearTimeout(timeoutId);
|
|
47331
47644
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -48102,7 +48415,7 @@ class StdioClientTransport {
|
|
|
48102
48415
|
if (this._process) {
|
|
48103
48416
|
throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
|
|
48104
48417
|
}
|
|
48105
|
-
return new Promise((
|
|
48418
|
+
return new Promise((resolve10, reject) => {
|
|
48106
48419
|
this._process = import_cross_spawn.default(this._serverParams.command, this._serverParams.args ?? [], {
|
|
48107
48420
|
env: {
|
|
48108
48421
|
...getDefaultEnvironment(),
|
|
@@ -48118,7 +48431,7 @@ class StdioClientTransport {
|
|
|
48118
48431
|
this.onerror?.(error45);
|
|
48119
48432
|
});
|
|
48120
48433
|
this._process.on("spawn", () => {
|
|
48121
|
-
|
|
48434
|
+
resolve10();
|
|
48122
48435
|
});
|
|
48123
48436
|
this._process.on("close", (_code) => {
|
|
48124
48437
|
this._process = undefined;
|
|
@@ -48165,20 +48478,20 @@ class StdioClientTransport {
|
|
|
48165
48478
|
if (this._process) {
|
|
48166
48479
|
const processToClose = this._process;
|
|
48167
48480
|
this._process = undefined;
|
|
48168
|
-
const closePromise = new Promise((
|
|
48481
|
+
const closePromise = new Promise((resolve10) => {
|
|
48169
48482
|
processToClose.once("close", () => {
|
|
48170
|
-
|
|
48483
|
+
resolve10();
|
|
48171
48484
|
});
|
|
48172
48485
|
});
|
|
48173
48486
|
try {
|
|
48174
48487
|
processToClose.stdin?.end();
|
|
48175
48488
|
} catch {}
|
|
48176
|
-
await Promise.race([closePromise, new Promise((
|
|
48489
|
+
await Promise.race([closePromise, new Promise((resolve10) => setTimeout(resolve10, 2000).unref())]);
|
|
48177
48490
|
if (processToClose.exitCode === null) {
|
|
48178
48491
|
try {
|
|
48179
48492
|
processToClose.kill("SIGTERM");
|
|
48180
48493
|
} catch {}
|
|
48181
|
-
await Promise.race([closePromise, new Promise((
|
|
48494
|
+
await Promise.race([closePromise, new Promise((resolve10) => setTimeout(resolve10, 2000).unref())]);
|
|
48182
48495
|
}
|
|
48183
48496
|
if (processToClose.exitCode === null) {
|
|
48184
48497
|
try {
|
|
@@ -48189,15 +48502,15 @@ class StdioClientTransport {
|
|
|
48189
48502
|
this._readBuffer.clear();
|
|
48190
48503
|
}
|
|
48191
48504
|
send(message) {
|
|
48192
|
-
return new Promise((
|
|
48505
|
+
return new Promise((resolve10) => {
|
|
48193
48506
|
if (!this._process?.stdin) {
|
|
48194
48507
|
throw new Error("Not connected");
|
|
48195
48508
|
}
|
|
48196
48509
|
const json3 = serializeMessage(message);
|
|
48197
48510
|
if (this._process.stdin.write(json3)) {
|
|
48198
|
-
|
|
48511
|
+
resolve10();
|
|
48199
48512
|
} else {
|
|
48200
|
-
this._process.stdin.once("drain",
|
|
48513
|
+
this._process.stdin.once("drain", resolve10);
|
|
48201
48514
|
}
|
|
48202
48515
|
});
|
|
48203
48516
|
}
|
|
@@ -49084,7 +49397,6 @@ ${patterns.join(`
|
|
|
49084
49397
|
var DEFAULT_MODEL = "anthropic/claude-opus-4-5";
|
|
49085
49398
|
var SISYPHUS_ROLE_SECTION = `<Role>
|
|
49086
49399
|
You are "Sisyphus" - Powerful AI Agent with orchestration capabilities from OhMyOpenCode.
|
|
49087
|
-
Named by [YeonGyu Kim](https://github.com/code-yeongyu).
|
|
49088
49400
|
|
|
49089
49401
|
**Why Sisyphus?**: Humans roll their boulder every day. So do you. We're not so different\u2014your code should be indistinguishable from a senior engineer's.
|
|
49090
49402
|
|
|
@@ -49787,7 +50099,7 @@ function createOracleAgent(model = DEFAULT_MODEL2) {
|
|
|
49787
50099
|
var oracleAgent = createOracleAgent();
|
|
49788
50100
|
|
|
49789
50101
|
// src/agents/librarian.ts
|
|
49790
|
-
var DEFAULT_MODEL3 = "
|
|
50102
|
+
var DEFAULT_MODEL3 = "opencode/glm-4.7-free";
|
|
49791
50103
|
var LIBRARIAN_PROMPT_METADATA = {
|
|
49792
50104
|
category: "exploration",
|
|
49793
50105
|
cost: "CHEAP",
|
|
@@ -49912,15 +50224,15 @@ Tool 3: grep_app_searchGitHub(query: "usage pattern", language: ["TypeScript"])
|
|
|
49912
50224
|
\`\`\`
|
|
49913
50225
|
Step 1: Clone to temp directory
|
|
49914
50226
|
gh repo clone owner/repo \${TMPDIR:-/tmp}/repo-name -- --depth 1
|
|
49915
|
-
|
|
50227
|
+
|
|
49916
50228
|
Step 2: Get commit SHA for permalinks
|
|
49917
50229
|
cd \${TMPDIR:-/tmp}/repo-name && git rev-parse HEAD
|
|
49918
|
-
|
|
50230
|
+
|
|
49919
50231
|
Step 3: Find the implementation
|
|
49920
50232
|
- grep/ast_grep_search for function/class
|
|
49921
50233
|
- read the specific file
|
|
49922
50234
|
- git blame for context if needed
|
|
49923
|
-
|
|
50235
|
+
|
|
49924
50236
|
Step 4: Construct permalink
|
|
49925
50237
|
https://github.com/owner/repo/blob/<sha>/path/to/file#L10-L20
|
|
49926
50238
|
\`\`\`
|
|
@@ -50055,7 +50367,7 @@ Use OS-appropriate temp directory:
|
|
|
50055
50367
|
| TYPE B (Implementation) | 2-3 NO |
|
|
50056
50368
|
| TYPE C (Context) | 2-3 NO |
|
|
50057
50369
|
| TYPE D (Comprehensive) | 3-5 | YES (Phase 0.5 first) |
|
|
50058
|
-
| Request Type | Minimum Parallel Calls
|
|
50370
|
+
| Request Type | Minimum Parallel Calls
|
|
50059
50371
|
|
|
50060
50372
|
**Doc Discovery is SEQUENTIAL** (websearch \u2192 version check \u2192 sitemap \u2192 investigate).
|
|
50061
50373
|
**Main phase is PARALLEL** once you know where to look.
|
|
@@ -50091,7 +50403,7 @@ grep_app_searchGitHub(query: "useQuery")
|
|
|
50091
50403
|
## COMMUNICATION RULES
|
|
50092
50404
|
|
|
50093
50405
|
1. **NO TOOL NAMES**: Say "I'll search the codebase" not "I'll use grep_app"
|
|
50094
|
-
2. **NO PREAMBLE**: Answer directly, skip "I'll help you with..."
|
|
50406
|
+
2. **NO PREAMBLE**: Answer directly, skip "I'll help you with..."
|
|
50095
50407
|
3. **ALWAYS CITE**: Every code claim needs a permalink
|
|
50096
50408
|
4. **USE MARKDOWN**: Code blocks with language identifiers
|
|
50097
50409
|
5. **BE CONCISE**: Facts > opinions, evidence > speculation
|
|
@@ -50977,7 +51289,6 @@ ${rows.join(`
|
|
|
50977
51289
|
**NEVER provide both category AND agent - they are mutually exclusive.**`;
|
|
50978
51290
|
}
|
|
50979
51291
|
var ORCHESTRATOR_SISYPHUS_SYSTEM_PROMPT = `You are "Sisyphus" - Powerful AI Agent with orchestration capabilities from OhMyOpenCode.
|
|
50980
|
-
Named by [YeonGyu Kim](https://github.com/code-yeongyu).
|
|
50981
51292
|
|
|
50982
51293
|
**Why Sisyphus?**: Humans roll their boulder every day. So do you. We're not so different\u2014your code should be indistinguishable from a senior engineer's.
|
|
50983
51294
|
|
|
@@ -55792,7 +56103,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
55792
56103
|
isSisyphusEnabled: pluginConfig.sisyphus_agent?.disabled !== true,
|
|
55793
56104
|
autoUpdate: pluginConfig.auto_update ?? true
|
|
55794
56105
|
}) : null;
|
|
55795
|
-
const keywordDetector = isHookEnabled("keyword-detector") ? createKeywordDetectorHook(ctx) : null;
|
|
56106
|
+
const keywordDetector = isHookEnabled("keyword-detector") ? createKeywordDetectorHook(ctx, contextCollector) : null;
|
|
55796
56107
|
const contextInjector = createContextInjectorHook(contextCollector);
|
|
55797
56108
|
const contextInjectorMessagesTransform = createContextInjectorMessagesTransformHook(contextCollector);
|
|
55798
56109
|
const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
|
|
@@ -55884,8 +56195,8 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
55884
56195
|
interactive_bash
|
|
55885
56196
|
},
|
|
55886
56197
|
"chat.message": async (input, output) => {
|
|
55887
|
-
await claudeCodeHooks["chat.message"]?.(input, output);
|
|
55888
56198
|
await keywordDetector?.["chat.message"]?.(input, output);
|
|
56199
|
+
await claudeCodeHooks["chat.message"]?.(input, output);
|
|
55889
56200
|
await contextInjector["chat.message"]?.(input, output);
|
|
55890
56201
|
await autoSlashCommand?.["chat.message"]?.(input, output);
|
|
55891
56202
|
await startWork?.["chat.message"]?.(input, output);
|
|
@@ -55956,6 +56267,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
55956
56267
|
}
|
|
55957
56268
|
if (sessionInfo?.id) {
|
|
55958
56269
|
await skillMcpManager.disconnectSession(sessionInfo.id);
|
|
56270
|
+
await lspManager.cleanupTempDirectoryClients();
|
|
55959
56271
|
}
|
|
55960
56272
|
}
|
|
55961
56273
|
if (event.type === "session.error") {
|