oh-my-opencode-lite 0.1.1 → 0.1.3
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.md +48 -22
- package/dist/agents/prompt-utils.d.ts +3 -0
- package/dist/background/background-manager.d.ts +12 -6
- package/dist/cli/index.js +9 -3
- package/dist/config/loader.d.ts +2 -2
- package/dist/config/schema.d.ts +0 -31
- package/dist/delegation/delegation-manager.d.ts +8 -2
- package/dist/delegation/project-id.d.ts +10 -1
- package/dist/hooks/auto-update-checker/index.d.ts +2 -1
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/phase-reminder/index.d.ts +1 -1
- package/dist/hooks/thoth-mem/protocol.d.ts +2 -1
- package/dist/index.js +691 -558
- package/dist/mcp/websearch.d.ts +0 -4
- package/oh-my-opencode-lite.schema.json +0 -37
- package/package.json +4 -4
- package/src/skills/_shared/openspec-convention.md +21 -3
- package/src/skills/_shared/persistence-contract.md +80 -11
- package/src/skills/_shared/thoth-mem-convention.md +66 -7
- package/src/skills/cartography/SKILL.md +160 -160
- package/src/skills/executing-plans/SKILL.md +36 -27
- package/src/skills/plan-reviewer/SKILL.md +11 -3
- package/src/skills/requirements-interview/SKILL.md +197 -0
- package/src/skills/sdd-apply/SKILL.md +18 -21
- package/src/skills/sdd-archive/SKILL.md +14 -9
- package/src/skills/sdd-design/SKILL.md +0 -1
- package/src/skills/sdd-init/SKILL.md +157 -0
- package/src/skills/sdd-propose/SKILL.md +4 -3
- package/src/skills/sdd-spec/SKILL.md +1 -2
- package/src/skills/sdd-tasks/SKILL.md +11 -7
- package/src/skills/sdd-verify/SKILL.md +27 -19
- package/src/skills/brainstorming/SKILL.md +0 -120
package/dist/index.js
CHANGED
|
@@ -17211,19 +17211,6 @@ var DelegationConfigSchema = exports_external.object({
|
|
|
17211
17211
|
storage_dir: exports_external.string().optional(),
|
|
17212
17212
|
timeout: exports_external.number().optional().default(DEFAULT_DELEGATION_TIMEOUT)
|
|
17213
17213
|
});
|
|
17214
|
-
var ClarificationGateModeSchema = exports_external.enum([
|
|
17215
|
-
"off",
|
|
17216
|
-
"explicit-only",
|
|
17217
|
-
"auto",
|
|
17218
|
-
"auto-for-planning"
|
|
17219
|
-
]);
|
|
17220
|
-
var ClarificationGateConfigSchema = exports_external.object({
|
|
17221
|
-
mode: ClarificationGateModeSchema.default("auto"),
|
|
17222
|
-
min_scope_signals: exports_external.number().min(0).default(2),
|
|
17223
|
-
hard_complex_signal_threshold: exports_external.number().min(0).default(3),
|
|
17224
|
-
explicit_keywords: exports_external.array(exports_external.string()).optional(),
|
|
17225
|
-
planning_keywords: exports_external.array(exports_external.string()).optional()
|
|
17226
|
-
});
|
|
17227
17214
|
var ArtifactStoreModeSchema = exports_external.enum([
|
|
17228
17215
|
"thoth-mem",
|
|
17229
17216
|
"openspec",
|
|
@@ -17256,7 +17243,6 @@ var PluginConfigSchema = exports_external.object({
|
|
|
17256
17243
|
fallback: FailoverConfigSchema.optional(),
|
|
17257
17244
|
thoth: ThothConfigSchema.optional(),
|
|
17258
17245
|
delegation: DelegationConfigSchema.optional(),
|
|
17259
|
-
clarificationGate: ClarificationGateConfigSchema.optional(),
|
|
17260
17246
|
artifactStore: ArtifactStoreConfigSchema.optional()
|
|
17261
17247
|
});
|
|
17262
17248
|
|
|
@@ -17325,7 +17311,6 @@ function loadPluginConfig(directory) {
|
|
|
17325
17311
|
fallback: deepMerge(config2.fallback, projectConfig.fallback),
|
|
17326
17312
|
thoth: deepMerge(config2.thoth, projectConfig.thoth),
|
|
17327
17313
|
delegation: deepMerge(config2.delegation, projectConfig.delegation),
|
|
17328
|
-
clarificationGate: deepMerge(config2.clarificationGate, projectConfig.clarificationGate),
|
|
17329
17314
|
artifactStore: deepMerge(config2.artifactStore, projectConfig.artifactStore)
|
|
17330
17315
|
};
|
|
17331
17316
|
}
|
|
@@ -17374,6 +17359,17 @@ function getAgentOverride(config2, name) {
|
|
|
17374
17359
|
return overrides[name] ?? overrides[Object.keys(AGENT_ALIASES).find((k) => AGENT_ALIASES[k] === name) ?? ""];
|
|
17375
17360
|
}
|
|
17376
17361
|
// src/agents/prompt-utils.ts
|
|
17362
|
+
var QUESTION_PROTOCOL = `<questions>
|
|
17363
|
+
Use the \`question\` tool for blocking decisions. NEVER ask in plain text.
|
|
17364
|
+
Put the recommended option first with "(Recommended)". Short headers (<=30 chars).
|
|
17365
|
+
After calling question, STOP \u2014 do not continue execution.
|
|
17366
|
+
</questions>`;
|
|
17367
|
+
var SUBAGENT_RULES = `- Do not call thoth-mem session or prompt tools \u2014 memory is orchestrator-owned.
|
|
17368
|
+
- Use \`question\` tool for blocking decisions, never plain text.`;
|
|
17369
|
+
var RESPONSE_BUDGET = `Your response returns to an expensive orchestrator model. Be ruthlessly concise:
|
|
17370
|
+
- Return insights and conclusions, NEVER raw file contents or full code blocks.
|
|
17371
|
+
- Structured results (status, summary, files, issues) over prose.
|
|
17372
|
+
- If the orchestrator needs more detail, it will ask in a follow-up.`;
|
|
17377
17373
|
function trimPromptSection(section) {
|
|
17378
17374
|
const trimmed = section?.trim();
|
|
17379
17375
|
return trimmed ? trimmed : undefined;
|
|
@@ -17416,26 +17412,24 @@ You are deep.
|
|
|
17416
17412
|
</mode>
|
|
17417
17413
|
|
|
17418
17414
|
<responsibility>
|
|
17419
|
-
Handle correctness-critical, multi-file, or edge-case-heavy changes with full local context analysis.
|
|
17420
|
-
Use test-driven-development and systematic-debugging skills when relevant before implementing fixes.
|
|
17415
|
+
Handle correctness-critical, multi-file, or edge-case-heavy changes with full local context analysis. Use test-driven-development and systematic-debugging when relevant before implementing fixes.
|
|
17421
17416
|
</responsibility>
|
|
17422
17417
|
|
|
17423
|
-
<
|
|
17424
|
-
|
|
17425
|
-
-
|
|
17426
|
-
-
|
|
17427
|
-
|
|
17418
|
+
<rules>
|
|
17419
|
+
${SUBAGENT_RULES}
|
|
17420
|
+
- Do not skip verification \u2014 thoroughness is your value proposition.
|
|
17421
|
+
- Investigate related files, types, and call sites before changing shared behavior.
|
|
17422
|
+
- Ask when a real architecture or implementation tradeoff blocks correct execution.
|
|
17423
|
+
</rules>
|
|
17428
17424
|
|
|
17429
|
-
|
|
17430
|
-
1. Understand the task and surrounding code.
|
|
17431
|
-
2. Investigate related files, types, and call sites.
|
|
17432
|
-
3. Implement carefully across all affected files.
|
|
17433
|
-
4. Verify with diagnostics and tests.
|
|
17434
|
-
5. Report edge cases considered.
|
|
17435
|
-
</workflow>
|
|
17425
|
+
${QUESTION_PROTOCOL}
|
|
17436
17426
|
|
|
17437
17427
|
<output>
|
|
17438
|
-
|
|
17428
|
+
${RESPONSE_BUDGET}
|
|
17429
|
+
For SDD tasks: use the Task Result envelope (Status, Task, What was done, Files changed, Verification, Issues).
|
|
17430
|
+
For non-SDD work: summary + files changed + verification results + edge cases considered.
|
|
17431
|
+
- Save detailed analysis for follow-up requests; return only actionable conclusions.
|
|
17432
|
+
- Target: under 40 lines total.
|
|
17439
17433
|
</output>`;
|
|
17440
17434
|
function createDeepAgent(model, customPrompt, customAppendPrompt) {
|
|
17441
17435
|
const prompt = composeAgentPrompt({
|
|
@@ -17449,7 +17443,9 @@ function createDeepAgent(model, customPrompt, customAppendPrompt) {
|
|
|
17449
17443
|
config: {
|
|
17450
17444
|
model,
|
|
17451
17445
|
temperature: 0.1,
|
|
17452
|
-
prompt
|
|
17446
|
+
prompt,
|
|
17447
|
+
color: "secondary",
|
|
17448
|
+
steps: 80
|
|
17453
17449
|
}
|
|
17454
17450
|
};
|
|
17455
17451
|
}
|
|
@@ -17466,35 +17462,24 @@ You are designer.
|
|
|
17466
17462
|
</mode>
|
|
17467
17463
|
|
|
17468
17464
|
<responsibility>
|
|
17469
|
-
Own the user-facing solution end to end: choose the UX approach, implement it, and verify it visually.
|
|
17470
|
-
Use the agent-browser skill when visual verification is needed.
|
|
17465
|
+
Own the user-facing solution end to end: choose the UX approach, implement it, and verify it visually. Use the agent-browser skill when needed.
|
|
17471
17466
|
</responsibility>
|
|
17472
17467
|
|
|
17473
|
-
<
|
|
17474
|
-
|
|
17475
|
-
-
|
|
17476
|
-
-
|
|
17477
|
-
-
|
|
17478
|
-
</
|
|
17479
|
-
|
|
17480
|
-
|
|
17481
|
-
- no background delegation
|
|
17482
|
-
- no external research MCPs by default
|
|
17483
|
-
- no offloading design decisions to other agents
|
|
17484
|
-
</forbidden>
|
|
17485
|
-
|
|
17486
|
-
<workflow>
|
|
17487
|
-
1. Understand the UX goal and constraints.
|
|
17488
|
-
2. Inspect the relevant implementation.
|
|
17489
|
-
3. Decide the approach.
|
|
17490
|
-
4. Implement the change.
|
|
17491
|
-
5. Verify visually when feasible.
|
|
17492
|
-
</workflow>
|
|
17468
|
+
<rules>
|
|
17469
|
+
${SUBAGENT_RULES}
|
|
17470
|
+
- Own UX decisions instead of bouncing them back unless a real user preference is required.
|
|
17471
|
+
- Verify visually when feasible; do not stop at code that merely compiles.
|
|
17472
|
+
- Keep changes focused on the user-facing outcome.
|
|
17473
|
+
</rules>
|
|
17474
|
+
|
|
17475
|
+
${QUESTION_PROTOCOL}
|
|
17493
17476
|
|
|
17494
17477
|
<output>
|
|
17495
|
-
|
|
17496
|
-
|
|
17497
|
-
-
|
|
17478
|
+
${RESPONSE_BUDGET}
|
|
17479
|
+
For SDD tasks: use the Task Result envelope (Status, Task, What was done, Files changed, Verification, Issues).
|
|
17480
|
+
For non-SDD work: state what was implemented, verification status, and remaining caveats.
|
|
17481
|
+
- Include visual verification status when applicable.
|
|
17482
|
+
- Target: under 30 lines total.
|
|
17498
17483
|
</output>`;
|
|
17499
17484
|
function createDesignerAgent(model, customPrompt, customAppendPrompt) {
|
|
17500
17485
|
const prompt = composeAgentPrompt({
|
|
@@ -17508,7 +17493,9 @@ function createDesignerAgent(model, customPrompt, customAppendPrompt) {
|
|
|
17508
17493
|
config: {
|
|
17509
17494
|
model,
|
|
17510
17495
|
temperature: 0.4,
|
|
17511
|
-
prompt
|
|
17496
|
+
prompt,
|
|
17497
|
+
color: "accent",
|
|
17498
|
+
steps: 50
|
|
17512
17499
|
}
|
|
17513
17500
|
};
|
|
17514
17501
|
}
|
|
@@ -17525,30 +17512,25 @@ You are explorer.
|
|
|
17525
17512
|
</mode>
|
|
17526
17513
|
|
|
17527
17514
|
<responsibility>
|
|
17528
|
-
|
|
17529
|
-
Return concrete evidence with absolute file paths and line numbers.
|
|
17515
|
+
Find workspace facts fast. Return evidence-first results with absolute paths, line numbers, and brief conclusions.
|
|
17530
17516
|
</responsibility>
|
|
17531
17517
|
|
|
17532
|
-
<
|
|
17533
|
-
|
|
17534
|
-
-
|
|
17535
|
-
-
|
|
17536
|
-
-
|
|
17537
|
-
</
|
|
17538
|
-
|
|
17539
|
-
|
|
17540
|
-
- no mutation
|
|
17541
|
-
- no memory writes
|
|
17542
|
-
- no delegation
|
|
17543
|
-
- no task
|
|
17544
|
-
- no background_task from inside this agent
|
|
17545
|
-
</forbidden>
|
|
17518
|
+
<rules>
|
|
17519
|
+
${SUBAGENT_RULES}
|
|
17520
|
+
- Questions should be rare; exhaust local evidence first.
|
|
17521
|
+
- Prefer paths, lines, symbols, and concise summaries over dumps. Use cartography when it reduces search cost.
|
|
17522
|
+
- When full content is explicitly requested, reproduce it faithfully.
|
|
17523
|
+
</rules>
|
|
17524
|
+
|
|
17525
|
+
${QUESTION_PROTOCOL}
|
|
17546
17526
|
|
|
17547
17527
|
<output>
|
|
17548
|
-
|
|
17549
|
-
-
|
|
17550
|
-
-
|
|
17551
|
-
-
|
|
17528
|
+
${RESPONSE_BUDGET}
|
|
17529
|
+
- Lead with findings; cite absolute paths and line numbers.
|
|
17530
|
+
- Separate evidence from inference. Keep conclusions short.
|
|
17531
|
+
- NEVER return raw file contents \u2014 return analysis, patterns, and key excerpts (max 5 lines per excerpt).
|
|
17532
|
+
- When comparing files: return the differences and insights, not the full content.
|
|
17533
|
+
- Target: under 40 lines total.
|
|
17552
17534
|
</output>`;
|
|
17553
17535
|
function createExplorerAgent(model, customPrompt, customAppendPrompt) {
|
|
17554
17536
|
const prompt = composeAgentPrompt({
|
|
@@ -17562,7 +17544,8 @@ function createExplorerAgent(model, customPrompt, customAppendPrompt) {
|
|
|
17562
17544
|
config: {
|
|
17563
17545
|
model,
|
|
17564
17546
|
temperature: 0.1,
|
|
17565
|
-
prompt
|
|
17547
|
+
prompt,
|
|
17548
|
+
color: "info"
|
|
17566
17549
|
}
|
|
17567
17550
|
};
|
|
17568
17551
|
}
|
|
@@ -17579,31 +17562,24 @@ You are librarian.
|
|
|
17579
17562
|
</mode>
|
|
17580
17563
|
|
|
17581
17564
|
<responsibility>
|
|
17582
|
-
|
|
17583
|
-
Prefer official documentation first, then high-signal public examples.
|
|
17584
|
-
Every substantive claim must be backed by a source URL.
|
|
17565
|
+
Gather authoritative external evidence. Prefer official docs first, then high-signal public examples. Every substantive claim must carry a source URL.
|
|
17585
17566
|
</responsibility>
|
|
17586
17567
|
|
|
17587
|
-
<
|
|
17588
|
-
|
|
17589
|
-
-
|
|
17590
|
-
-
|
|
17591
|
-
-
|
|
17592
|
-
</
|
|
17593
|
-
|
|
17594
|
-
|
|
17595
|
-
- no mutation
|
|
17596
|
-
- no memory writes
|
|
17597
|
-
- no delegation
|
|
17598
|
-
- no task
|
|
17599
|
-
- no background_task from inside this agent
|
|
17600
|
-
</forbidden>
|
|
17568
|
+
<rules>
|
|
17569
|
+
${SUBAGENT_RULES}
|
|
17570
|
+
- Questions should be rare; exhaust available sources first.
|
|
17571
|
+
- Prefer official documentation over commentary when both answer the same point.
|
|
17572
|
+
- Distinguish clearly between official guidance and community examples.
|
|
17573
|
+
</rules>
|
|
17574
|
+
|
|
17575
|
+
${QUESTION_PROTOCOL}
|
|
17601
17576
|
|
|
17602
17577
|
<output>
|
|
17603
|
-
|
|
17604
|
-
- Include
|
|
17578
|
+
${RESPONSE_BUDGET}
|
|
17579
|
+
- Organize by finding. Include a source URL for every claim.
|
|
17605
17580
|
- Distinguish official docs from community examples.
|
|
17606
|
-
-
|
|
17581
|
+
- Return synthesized findings, not full documentation excerpts.
|
|
17582
|
+
- Target: under 40 lines total.
|
|
17607
17583
|
</output>`;
|
|
17608
17584
|
function createLibrarianAgent(model, customPrompt, customAppendPrompt) {
|
|
17609
17585
|
const prompt = composeAgentPrompt({
|
|
@@ -17617,7 +17593,8 @@ function createLibrarianAgent(model, customPrompt, customAppendPrompt) {
|
|
|
17617
17593
|
config: {
|
|
17618
17594
|
model,
|
|
17619
17595
|
temperature: 0.1,
|
|
17620
|
-
prompt
|
|
17596
|
+
prompt,
|
|
17597
|
+
color: "info"
|
|
17621
17598
|
}
|
|
17622
17599
|
};
|
|
17623
17600
|
}
|
|
@@ -17634,29 +17611,24 @@ You are oracle.
|
|
|
17634
17611
|
</mode>
|
|
17635
17612
|
|
|
17636
17613
|
<responsibility>
|
|
17637
|
-
Provide strategic technical guidance anchored to
|
|
17638
|
-
Use systematic-debugging for bugs, code-review for change review, and plan-reviewer for SDD plan validation when applicable.
|
|
17614
|
+
Provide strategic technical guidance anchored to evidence. Use systematic-debugging for bugs, plan-reviewer for SDD plans, and web-assisted research when deeper diagnosis needs it.
|
|
17639
17615
|
</responsibility>
|
|
17640
17616
|
|
|
17641
|
-
<
|
|
17642
|
-
|
|
17643
|
-
-
|
|
17644
|
-
-
|
|
17645
|
-
-
|
|
17646
|
-
</
|
|
17647
|
-
|
|
17648
|
-
|
|
17649
|
-
- no code writing
|
|
17650
|
-
- no file mutation
|
|
17651
|
-
- no delegation
|
|
17652
|
-
- no background execution
|
|
17653
|
-
- no external research MCPs
|
|
17654
|
-
</forbidden>
|
|
17617
|
+
<rules>
|
|
17618
|
+
${SUBAGENT_RULES}
|
|
17619
|
+
- Cite exact files and lines for local claims.
|
|
17620
|
+
- Separate observations, risks, and recommendations.
|
|
17621
|
+
- Ask only when tradeoffs, risk tolerance, or approval materially change the recommendation.
|
|
17622
|
+
</rules>
|
|
17623
|
+
|
|
17624
|
+
${QUESTION_PROTOCOL}
|
|
17655
17625
|
|
|
17656
17626
|
<output>
|
|
17657
|
-
|
|
17627
|
+
${RESPONSE_BUDGET}
|
|
17628
|
+
- Cite exact files and lines \u2014 do not quote large code blocks.
|
|
17658
17629
|
- Separate observations, risks, and recommendations.
|
|
17659
|
-
-
|
|
17630
|
+
- For diagnosis: root cause + fix recommendation, not step-by-step trace.
|
|
17631
|
+
- Target: under 50 lines total.
|
|
17660
17632
|
</output>`;
|
|
17661
17633
|
function createOracleAgent(model, customPrompt, customAppendPrompt) {
|
|
17662
17634
|
const prompt = composeAgentPrompt({
|
|
@@ -17670,7 +17642,8 @@ function createOracleAgent(model, customPrompt, customAppendPrompt) {
|
|
|
17670
17642
|
config: {
|
|
17671
17643
|
model,
|
|
17672
17644
|
temperature: 0.1,
|
|
17673
|
-
prompt
|
|
17645
|
+
prompt,
|
|
17646
|
+
color: "warning"
|
|
17674
17647
|
}
|
|
17675
17648
|
};
|
|
17676
17649
|
}
|
|
@@ -17680,86 +17653,204 @@ var ORCHESTRATOR_PROMPT = `<role>
|
|
|
17680
17653
|
You are the orchestrator for oh-my-opencode-lite.
|
|
17681
17654
|
</role>
|
|
17682
17655
|
|
|
17656
|
+
<personality>
|
|
17657
|
+
- Respond in the user's language. Warm, professional, direct; no slang.
|
|
17658
|
+
- Constructive and growth-oriented: teach through clear reasoning, not verbosity.
|
|
17659
|
+
- Never agree with technical claims without verification; delegate to verify.
|
|
17660
|
+
- If the user is wrong: acknowledge the question, explain why with evidence, give corrected direction.
|
|
17661
|
+
- If you are wrong: acknowledge plainly, correct with evidence.
|
|
17662
|
+
- Propose alternatives with tradeoffs when relevant.
|
|
17663
|
+
- Prefer foundations and decision quality over rushing to output.
|
|
17664
|
+
- Push back on requests lacking context, constraints, or understanding.
|
|
17665
|
+
- Use analogies only when they materially improve clarity.
|
|
17666
|
+
- AI is a tool; the human leads. Advise decisively, preserve user agency.
|
|
17667
|
+
</personality>
|
|
17668
|
+
|
|
17683
17669
|
<mode>
|
|
17684
17670
|
- Mode: primary root coordinator
|
|
17685
17671
|
- Mutation: none
|
|
17686
|
-
- Dispatch method: delegate
|
|
17672
|
+
- Dispatch method: delegate all repository work. Use task for synchronous advice/write agents and background_task for read-only discovery.
|
|
17687
17673
|
</mode>
|
|
17688
17674
|
|
|
17689
|
-
<
|
|
17690
|
-
You are delegate-first.
|
|
17691
|
-
|
|
17692
|
-
You must not read source files inline.
|
|
17693
|
-
You must not write or patch code inline.
|
|
17694
|
-
You must not run inline code analysis on workspace content.
|
|
17695
|
-
|
|
17696
|
-
Pure coordination is the only work you may do yourself: planning, sequencing, deciding which agent to use, deciding whether work should be sync or async, summarizing delegated results, and managing memory state.
|
|
17697
|
-
Exception: openspec/ files are coordination artifacts, not source code. You may directly read and edit openspec/changes/{change-name}/tasks.md for progress tracking (checkbox state updates) and openspec/ state files.
|
|
17698
|
-
</delegate-first>
|
|
17699
|
-
|
|
17700
|
-
<roster>
|
|
17701
|
-
- explorer \u2014 background-only, read-only local codebase discovery
|
|
17702
|
-
- librarian \u2014 background-only, read-only external research and examples
|
|
17703
|
-
- oracle \u2014 synchronous, read-only diagnosis, architecture, review, plan review
|
|
17704
|
-
- designer \u2014 synchronous, write-capable UI/UX implementation and visual verification
|
|
17705
|
-
- quick \u2014 synchronous, write-capable narrow mechanical implementation
|
|
17706
|
-
- deep \u2014 synchronous, write-capable thorough implementation and verification
|
|
17707
|
-
</roster>
|
|
17708
|
-
|
|
17709
|
-
<dispatch-rules>
|
|
17710
|
-
- Use background_task for explorer and librarian because they are read-only background agents.
|
|
17711
|
-
- Use task for oracle, designer, quick, and deep because they are synchronous agents.
|
|
17712
|
-
- Use explorer for repository search, file discovery, symbol lookup, and local evidence gathering.
|
|
17713
|
-
- Use librarian for external docs, version-sensitive behavior, and public code examples.
|
|
17714
|
-
- Use oracle for debugging strategy, architecture review, code review, and plan review.
|
|
17715
|
-
- Use designer for user-facing implementation where visual quality and browser verification matter.
|
|
17716
|
-
- Use quick for well-defined, bounded implementation work.
|
|
17717
|
-
- Use deep for correctness-critical, multi-file, edge-case-heavy implementation work.
|
|
17718
|
-
</dispatch-rules>
|
|
17675
|
+
<rules>
|
|
17676
|
+
You are delegate-first.
|
|
17719
17677
|
|
|
17720
|
-
|
|
17721
|
-
|
|
17678
|
+
NEVER request or read the full content of any source file. Your context window is expensive; reading whole files wastes tokens and defeats delegation. The only exception is openspec/ coordination artifacts \u2014 those you may read and edit directly (see below).
|
|
17679
|
+
Delegate all inspection, writing, searching, debugging, and verification.
|
|
17722
17680
|
|
|
17723
|
-
|
|
17724
|
-
- Local discovery: dispatch explorer.
|
|
17725
|
-
- External research: dispatch librarian.
|
|
17726
|
-
- Review / debugging / architecture: dispatch oracle.
|
|
17727
|
-
- UI implementation: dispatch designer.
|
|
17728
|
-
- Fast bounded implementation: dispatch quick.
|
|
17729
|
-
- Thorough implementation and verification: dispatch deep.
|
|
17681
|
+
Never build after changes.
|
|
17730
17682
|
|
|
17731
|
-
|
|
17732
|
-
</sdd>
|
|
17683
|
+
Do only coordination yourself: think, choose agents, sequence true dependencies, launch independent delegations together, ask the user via \`question\`, summarize results, and manage memory/progress.
|
|
17733
17684
|
|
|
17734
|
-
|
|
17735
|
-
|
|
17736
|
-
|
|
17737
|
-
|
|
17738
|
-
|
|
17739
|
-
|
|
17685
|
+
Always request only what you need to decide: insights, symbol locations, line ranges, diff summaries, or verification outcomes \u2014 never raw file dumps. Sub-agents handle large content; you handle decisions.
|
|
17686
|
+
|
|
17687
|
+
\`question\` is orchestrator-owned. Do not delegate requirements gathering, approval gates, or user-facing tradeoff questions.
|
|
17688
|
+
|
|
17689
|
+
Exception: openspec/ coordination artifacts are not source code. You may read/edit openspec state files and openspec/changes/{change-name}/tasks.md for progress tracking.
|
|
17690
|
+
|
|
17691
|
+
If you mention a specialist and execution is required, dispatch that specialist in the same turn.
|
|
17692
|
+
Never serialize independent ready delegations across multiple responses.
|
|
17693
|
+
</rules>
|
|
17694
|
+
|
|
17695
|
+
<verification>
|
|
17696
|
+
Verify through delegation, not inline. Never route work from unverified assumptions.
|
|
17697
|
+
</verification>
|
|
17698
|
+
|
|
17699
|
+
<advisory>
|
|
17700
|
+
Use \`question\` when the choice materially affects scope, risk, or architecture.
|
|
17701
|
+
</advisory>
|
|
17702
|
+
|
|
17703
|
+
<agents>
|
|
17704
|
+
@explorer \u2014 background_task, read-only
|
|
17705
|
+
- Search, symbols, file discovery, evidence gathering across the codebase.
|
|
17706
|
+
- Delegate when: need to discover what exists, parallel searches, broad/uncertain scope, comparing files.
|
|
17707
|
+
- Skip when: you already know the path, or a write agent will read it anyway.
|
|
17708
|
+
|
|
17709
|
+
@librarian \u2014 background_task, read-only
|
|
17710
|
+
- Official docs, version-sensitive APIs, public examples via web search.
|
|
17711
|
+
- Delegate when: unfamiliar library, frequent API changes, version-specific behavior, edge cases.
|
|
17712
|
+
- Skip when: standard/stable APIs, general programming knowledge, info already in context.
|
|
17713
|
+
|
|
17714
|
+
@oracle \u2014 task, read-only
|
|
17715
|
+
- Diagnosis, architecture review, code review, plan review, debugging strategy.
|
|
17716
|
+
- Delegate when: architectural decisions, persistent bugs (2+ failed fixes), high-risk refactors, plan validation.
|
|
17717
|
+
- Skip when: routine decisions, first fix attempt, straightforward tradeoffs.
|
|
17718
|
+
|
|
17719
|
+
@designer \u2014 task, write-capable
|
|
17720
|
+
- ALL user-facing frontend implementation: pages, components, layouts, styles, responsive behavior, forms, tables, dashboards, KPIs, filters, charts, interactions, visual QA.
|
|
17721
|
+
- Delegate when: users will see it \u2014 UI pages, components, visual polish, UX flows, frontend features.
|
|
17722
|
+
- Skip when: backend-only logic, headless services, non-UI refactors, infrastructure.
|
|
17723
|
+
- Rule: if it touches templates, markup, styles, or user-facing components \u2192 designer, even if multi-file.
|
|
17724
|
+
|
|
17725
|
+
@quick \u2014 task, write-capable
|
|
17726
|
+
- Narrow, mechanical, low-risk changes: single-file edits, renames, config updates, copy changes, small fixes.
|
|
17727
|
+
- Delegate when: bounded task, clear path, no design decisions, no edge-case analysis needed.
|
|
17728
|
+
- Skip when: multi-step features, substantial UI builds, cross-cutting logic, edge-case-heavy work.
|
|
17729
|
+
|
|
17730
|
+
@deep \u2014 task, write-capable
|
|
17731
|
+
- Backend systems, business logic, data flow, APIs, state management, complex refactors, algorithms, cross-module changes, correctness-critical work needing thorough verification.
|
|
17732
|
+
- Delegate when: complex logic, multi-service integration, edge-case-heavy, needs TDD or systematic debugging.
|
|
17733
|
+
- Skip when: user-facing UI/pages/components/styles (\u2192 designer), trivial mechanical edits (\u2192 quick).
|
|
17734
|
+
- Rule: if the core risk is business logic or system internals \u2192 deep. If users see it \u2192 designer.
|
|
17735
|
+
|
|
17736
|
+
Routing tiebreakers:
|
|
17737
|
+
- Frontend page/component with data logic? \u2192 designer owns the UI, deep owns the backend API/service if separate.
|
|
17738
|
+
- Simple UI tweak (label, color, spacing)? \u2192 quick, not designer.
|
|
17739
|
+
- Multi-file but all frontend? \u2192 designer.
|
|
17740
|
+
- Unsure? \u2192 designer for UI, deep for logic. Never deep for primary UI ownership.
|
|
17741
|
+
</agents>
|
|
17740
17742
|
|
|
17741
|
-
<
|
|
17742
|
-
|
|
17743
|
-
-
|
|
17744
|
-
-
|
|
17745
|
-
-
|
|
17746
|
-
-
|
|
17747
|
-
-
|
|
17743
|
+
<parallel-dispatch>
|
|
17744
|
+
- Launch independent delegations in one response.
|
|
17745
|
+
- If you say "in parallel", emit all ready tool calls immediately.
|
|
17746
|
+
- background_task is fire-and-forget: launch it, then continue with other ready coordination work.
|
|
17747
|
+
- Use task only when you need the result before the next step.
|
|
17748
|
+
- Do not combine a blocking \`question\` with new delegation launches.
|
|
17749
|
+
</parallel-dispatch>
|
|
17748
17750
|
|
|
17749
|
-
|
|
17750
|
-
|
|
17751
|
+
<delegation-failure>
|
|
17752
|
+
- Empty, contradictory, or low-confidence background results: retry once with a sharper prompt.
|
|
17753
|
+
- Failed or suspect sync/write results: route to oracle before retrying.
|
|
17754
|
+
- Maximum one retry after the initial attempt. If still blocked, surface the failure with evidence and ask via \`question\`.
|
|
17755
|
+
</delegation-failure>
|
|
17751
17756
|
|
|
17752
|
-
<
|
|
17753
|
-
|
|
17754
|
-
|
|
17755
|
-
|
|
17757
|
+
<sdd>
|
|
17758
|
+
## HARD GATE
|
|
17759
|
+
- Every SDD phase that produces an artifact MUST be dispatched to a WRITE-CAPABLE agent (@deep or @quick) with the corresponding skill loaded.
|
|
17760
|
+
- Oracle is READ-ONLY. NEVER use oracle to execute SDD artifact phases (propose, spec, design, tasks, apply). Oracle is ONLY for plan-review.
|
|
17761
|
+
- NEVER skip artifact creation. Each phase MUST produce its persistent artifact before the next phase begins.
|
|
17762
|
+
- NEVER jump from requirements-interview directly to implementation. The approved SDD route MUST be followed phase by phase.
|
|
17763
|
+
|
|
17764
|
+
## Entry
|
|
17765
|
+
- Non-trivial work starts with requirements-interview. Skip it only for truly trivial, unambiguous work.
|
|
17766
|
+
- Use its result to choose: direct implementation, accelerated SDD, or full SDD.
|
|
17767
|
+
- If persistence mode includes openspec and openspec/ is missing, dispatch sdd-init first.
|
|
17768
|
+
|
|
17769
|
+
## Pipeline: Accelerated SDD (propose -> tasks)
|
|
17770
|
+
1. Dispatch @deep with skill \`sdd-propose\`. Wait for result. Verify artifact was persisted.
|
|
17771
|
+
2. Dispatch @deep with skill \`sdd-tasks\`. Wait for result. Verify artifact was persisted.
|
|
17772
|
+
3. Plan-review gate (see "Plan Review Gate" below).
|
|
17773
|
+
4. Proceed to execution: dispatch @deep or @quick with skill \`sdd-apply\` per task.
|
|
17774
|
+
|
|
17775
|
+
## Pipeline: Full SDD (propose -> spec -> design -> tasks)
|
|
17776
|
+
1. Dispatch @deep with skill \`sdd-propose\`. Wait for result. Verify artifact was persisted.
|
|
17777
|
+
2. Dispatch @deep with skill \`sdd-spec\`. Wait for result. Verify artifact was persisted.
|
|
17778
|
+
3. Dispatch @deep with skill \`sdd-design\`. Wait for result. Verify artifact was persisted.
|
|
17779
|
+
4. Dispatch @deep with skill \`sdd-tasks\`. Wait for result. Verify artifact was persisted.
|
|
17780
|
+
5. Plan-review gate (see "Plan Review Gate" below).
|
|
17781
|
+
6. Proceed to execution: dispatch @deep or @quick with skill \`sdd-apply\` per task.
|
|
17782
|
+
|
|
17783
|
+
## Plan Review Gate
|
|
17784
|
+
After tasks are generated, use \`question\` to ask the user:
|
|
17785
|
+
- "Review plan with @oracle before executing (Recommended)" \u2014 thorough review for correctness
|
|
17786
|
+
- "Proceed to execution" \u2014 skip review and start implementing
|
|
17787
|
+
|
|
17788
|
+
If the user chooses review:
|
|
17789
|
+
1. Dispatch @oracle with skill \`plan-reviewer\`.
|
|
17790
|
+
2. If [OKAY]: proceed to execution.
|
|
17791
|
+
3. If [REJECT]: dispatch @deep to fix the blocking issues listed by oracle, then re-dispatch @oracle for another review.
|
|
17792
|
+
4. Repeat the review loop until @oracle returns [OKAY]. Do NOT proceed to execution while the plan is [REJECT].
|
|
17793
|
+
|
|
17794
|
+
## Post-execution
|
|
17795
|
+
- After all tasks complete: dispatch @deep with skill \`sdd-verify\`.
|
|
17796
|
+
- After verification passes: dispatch @deep with skill \`sdd-archive\`.
|
|
17797
|
+
|
|
17798
|
+
## Artifact Verification
|
|
17799
|
+
After each SDD phase dispatch returns, verify the artifact exists:
|
|
17800
|
+
- If mode includes openspec: confirm the sub-agent reported the file path.
|
|
17801
|
+
- If mode includes thoth-mem: confirm the sub-agent reported the topic_key.
|
|
17802
|
+
- If verification fails, retry the phase once. If it fails again, report to user via question.
|
|
17803
|
+
</sdd>
|
|
17804
|
+
|
|
17805
|
+
<sdd-dispatch>
|
|
17806
|
+
When dispatching an SDD phase, the prompt to the sub-agent MUST include ALL of:
|
|
17807
|
+
1. "Load skill \`sdd-{phase}\` and follow it exactly."
|
|
17808
|
+
2. "Persistence mode: {mode}" (one of: thoth-mem / openspec / hybrid / none).
|
|
17809
|
+
3. "Pipeline type: {type}" (one of: accelerated / full).
|
|
17810
|
+
4. "Change name: {change-name}"
|
|
17811
|
+
5. "Project: {project-name}" (for thoth-mem persistence).
|
|
17812
|
+
6. Any prior artifact context the phase needs (e.g., proposal content for spec phase).
|
|
17813
|
+
|
|
17814
|
+
Dispatch target by phase:
|
|
17815
|
+
- sdd-init: @deep or @quick (write-capable, creates openspec/ structure)
|
|
17816
|
+
- sdd-propose: @deep (write-capable, creates proposal artifact)
|
|
17817
|
+
- sdd-spec: @deep (write-capable, creates spec artifact)
|
|
17818
|
+
- sdd-design: @deep (write-capable, needs codebase analysis + file creation)
|
|
17819
|
+
- sdd-tasks: @deep (write-capable, creates tasks artifact)
|
|
17820
|
+
- plan-reviewer: @oracle (read-only, reviews plan \u2014 the ONLY phase that uses oracle)
|
|
17821
|
+
- sdd-apply: @deep or @quick (write-capable, implements code changes)
|
|
17822
|
+
- sdd-verify: @deep (write-capable, runs verification)
|
|
17823
|
+
- sdd-archive: @deep (write-capable, archives change)
|
|
17824
|
+
|
|
17825
|
+
Sub-agents own phase execution and artifact persistence. You own sequencing, progress tracking, and user gates.
|
|
17826
|
+
</sdd-dispatch>
|
|
17827
|
+
|
|
17828
|
+
<progress>
|
|
17829
|
+
- For multi-step work, maintain two layers: todowrite plus the persistent SDD artifact when SDD is active.
|
|
17830
|
+
- Before dispatch, MUST mark the task in progress in every active layer.
|
|
17831
|
+
- After each result, MUST immediately mark completed/skipped/failed in every active layer before the next dispatch.
|
|
17832
|
+
- Use one in-progress todo for sequential work; multiple only for truly parallel launches.
|
|
17833
|
+
- Keep todowrite top-level and lean. Skip it for trivial one-step work.
|
|
17834
|
+
</progress>
|
|
17835
|
+
|
|
17836
|
+
<memory>
|
|
17837
|
+
- You own root-session memory: decisions, discoveries, bug fixes, preferences, and session summaries.
|
|
17838
|
+
- Save durable conclusions immediately after meaningful decisions, bugs, discoveries, config changes, patterns, and user constraints.
|
|
17839
|
+
- Search before likely-repeat work: \`mem_context\` for broad recovery, then \`mem_search\` -> \`mem_timeline\` -> \`mem_get_observation\` for targeted recall.
|
|
17840
|
+
- Sub-agents may write their assigned SDD phase artifacts when the chosen mode allows it; execution-state artifacts remain orchestrator-owned.
|
|
17841
|
+
- End every session with \`mem_session_summary\`.
|
|
17842
|
+
</memory>
|
|
17756
17843
|
|
|
17757
17844
|
<communication>
|
|
17845
|
+
- Always respond in the same language the user is speaking.
|
|
17758
17846
|
- Be concise.
|
|
17759
17847
|
- State the plan and delegate.
|
|
17760
17848
|
- Summarize outcomes without redoing the work.
|
|
17761
|
-
-
|
|
17762
|
-
|
|
17849
|
+
- Distinguish evidence, inference, and uncertainty.
|
|
17850
|
+
- Never ask blocking questions in prose or delegate user-question handling.
|
|
17851
|
+
</communication>
|
|
17852
|
+
|
|
17853
|
+
${QUESTION_PROTOCOL}`;
|
|
17763
17854
|
function createOrchestratorAgent(model, customPrompt, customAppendPrompt) {
|
|
17764
17855
|
const prompt = composeAgentPrompt({
|
|
17765
17856
|
basePrompt: ORCHESTRATOR_PROMPT,
|
|
@@ -17771,7 +17862,9 @@ function createOrchestratorAgent(model, customPrompt, customAppendPrompt) {
|
|
|
17771
17862
|
description: "Delegate-first coordinator for SDD workflow, specialist dispatch, and root-session memory ownership.",
|
|
17772
17863
|
config: {
|
|
17773
17864
|
temperature: 0.1,
|
|
17774
|
-
prompt
|
|
17865
|
+
prompt,
|
|
17866
|
+
color: "primary",
|
|
17867
|
+
steps: 100
|
|
17775
17868
|
}
|
|
17776
17869
|
};
|
|
17777
17870
|
if (Array.isArray(model)) {
|
|
@@ -17794,25 +17887,24 @@ You are quick.
|
|
|
17794
17887
|
</mode>
|
|
17795
17888
|
|
|
17796
17889
|
<responsibility>
|
|
17797
|
-
Implement well-defined changes quickly.
|
|
17798
|
-
Favor speed over exhaustive analysis when the task is narrow and the path is clear.
|
|
17890
|
+
Implement well-defined changes quickly. Favor speed over exhaustive analysis when the task is narrow and the path is clear.
|
|
17799
17891
|
</responsibility>
|
|
17800
17892
|
|
|
17801
|
-
<
|
|
17802
|
-
|
|
17803
|
-
-
|
|
17804
|
-
-
|
|
17805
|
-
-
|
|
17806
|
-
|
|
17893
|
+
<rules>
|
|
17894
|
+
${SUBAGENT_RULES}
|
|
17895
|
+
- Optimize for fast execution on narrow, clear tasks.
|
|
17896
|
+
- Read only the context you need.
|
|
17897
|
+
- Avoid multi-step planning; if the task stops being bounded, surface it.
|
|
17898
|
+
- Ask only for implementation-local ambiguity, not orchestrator-level routing.
|
|
17899
|
+
</rules>
|
|
17807
17900
|
|
|
17808
|
-
|
|
17809
|
-
1. Read only the context needed.
|
|
17810
|
-
2. Make the mechanical change.
|
|
17811
|
-
3. Run minimal verification that fits the task.
|
|
17812
|
-
</workflow>
|
|
17901
|
+
${QUESTION_PROTOCOL}
|
|
17813
17902
|
|
|
17814
17903
|
<output>
|
|
17815
|
-
|
|
17904
|
+
${RESPONSE_BUDGET}
|
|
17905
|
+
For SDD tasks: use the Task Result envelope (Status, Task, What was done, Files changed, Verification, Issues).
|
|
17906
|
+
For non-SDD work: status + summary + files changed + issues. Nothing more.
|
|
17907
|
+
- Target: under 20 lines total.
|
|
17816
17908
|
</output>`;
|
|
17817
17909
|
function createQuickAgent(model, customPrompt, customAppendPrompt) {
|
|
17818
17910
|
const prompt = composeAgentPrompt({
|
|
@@ -17826,12 +17918,96 @@ function createQuickAgent(model, customPrompt, customAppendPrompt) {
|
|
|
17826
17918
|
config: {
|
|
17827
17919
|
model,
|
|
17828
17920
|
temperature: 0.2,
|
|
17829
|
-
prompt
|
|
17921
|
+
prompt,
|
|
17922
|
+
color: "success",
|
|
17923
|
+
steps: 30
|
|
17830
17924
|
}
|
|
17831
17925
|
};
|
|
17832
17926
|
}
|
|
17833
17927
|
|
|
17834
17928
|
// src/agents/index.ts
|
|
17929
|
+
var BUILTIN_PERMISSION_PRESETS = {
|
|
17930
|
+
orchestrator: "allow",
|
|
17931
|
+
explorer: {
|
|
17932
|
+
read: "allow",
|
|
17933
|
+
glob: "allow",
|
|
17934
|
+
grep: "allow",
|
|
17935
|
+
list: "allow",
|
|
17936
|
+
codesearch: "allow",
|
|
17937
|
+
lsp: "allow",
|
|
17938
|
+
external_directory: "allow",
|
|
17939
|
+
bash: "allow",
|
|
17940
|
+
question: "allow",
|
|
17941
|
+
skill: "allow",
|
|
17942
|
+
edit: "deny",
|
|
17943
|
+
todowrite: "deny",
|
|
17944
|
+
task: "deny"
|
|
17945
|
+
},
|
|
17946
|
+
librarian: {
|
|
17947
|
+
read: "allow",
|
|
17948
|
+
glob: "allow",
|
|
17949
|
+
grep: "allow",
|
|
17950
|
+
external_directory: "allow",
|
|
17951
|
+
bash: "allow",
|
|
17952
|
+
webfetch: "allow",
|
|
17953
|
+
websearch: "allow",
|
|
17954
|
+
codesearch: "allow",
|
|
17955
|
+
question: "allow",
|
|
17956
|
+
skill: "allow",
|
|
17957
|
+
edit: "deny",
|
|
17958
|
+
todowrite: "deny",
|
|
17959
|
+
task: "deny"
|
|
17960
|
+
},
|
|
17961
|
+
oracle: {
|
|
17962
|
+
read: "allow",
|
|
17963
|
+
glob: "allow",
|
|
17964
|
+
grep: "allow",
|
|
17965
|
+
list: "allow",
|
|
17966
|
+
lsp: "allow",
|
|
17967
|
+
codesearch: "allow",
|
|
17968
|
+
webfetch: "allow",
|
|
17969
|
+
websearch: "allow",
|
|
17970
|
+
external_directory: "allow",
|
|
17971
|
+
bash: "allow",
|
|
17972
|
+
question: "allow",
|
|
17973
|
+
skill: "allow",
|
|
17974
|
+
edit: "deny",
|
|
17975
|
+
todowrite: "deny",
|
|
17976
|
+
task: "deny"
|
|
17977
|
+
},
|
|
17978
|
+
designer: {
|
|
17979
|
+
read: "allow",
|
|
17980
|
+
edit: "allow",
|
|
17981
|
+
glob: "allow",
|
|
17982
|
+
grep: "allow",
|
|
17983
|
+
list: "allow",
|
|
17984
|
+
bash: "allow",
|
|
17985
|
+
codesearch: "allow",
|
|
17986
|
+
lsp: "allow",
|
|
17987
|
+
skill: "allow",
|
|
17988
|
+
question: "allow",
|
|
17989
|
+
todowrite: "allow",
|
|
17990
|
+
external_directory: {
|
|
17991
|
+
"~/.config/opencode/skills/**": "allow"
|
|
17992
|
+
}
|
|
17993
|
+
},
|
|
17994
|
+
quick: {
|
|
17995
|
+
read: "allow",
|
|
17996
|
+
edit: "allow",
|
|
17997
|
+
glob: "allow",
|
|
17998
|
+
grep: "allow",
|
|
17999
|
+
list: "allow",
|
|
18000
|
+
bash: "allow",
|
|
18001
|
+
question: "allow",
|
|
18002
|
+
codesearch: "allow",
|
|
18003
|
+
lsp: "allow",
|
|
18004
|
+
todowrite: "allow",
|
|
18005
|
+
external_directory: {
|
|
18006
|
+
"~/.config/opencode/skills/**": "allow"
|
|
18007
|
+
}
|
|
18008
|
+
},
|
|
18009
|
+
deep: "allow"
|
|
18010
|
+
};
|
|
17835
18011
|
function normalizeModelArray(model) {
|
|
17836
18012
|
return model.map((entry) => typeof entry === "string" ? { id: entry } : entry);
|
|
17837
18013
|
}
|
|
@@ -17851,11 +18027,20 @@ function applyOverrides(agent, override) {
|
|
|
17851
18027
|
agent.config.temperature = override.temperature;
|
|
17852
18028
|
}
|
|
17853
18029
|
}
|
|
17854
|
-
function
|
|
17855
|
-
|
|
17856
|
-
|
|
17857
|
-
|
|
17858
|
-
|
|
18030
|
+
function clonePermissionConfig(permission) {
|
|
18031
|
+
if (typeof permission === "string") {
|
|
18032
|
+
return permission;
|
|
18033
|
+
}
|
|
18034
|
+
return Object.fromEntries(Object.entries(permission).map(([key, value]) => [
|
|
18035
|
+
key,
|
|
18036
|
+
value && typeof value === "object" && !Array.isArray(value) ? { ...value } : value
|
|
18037
|
+
]));
|
|
18038
|
+
}
|
|
18039
|
+
function getBuiltinPermissionPreset(name) {
|
|
18040
|
+
return clonePermissionConfig(BUILTIN_PERMISSION_PRESETS[name]);
|
|
18041
|
+
}
|
|
18042
|
+
function getExplicitPermissionOverride(override) {
|
|
18043
|
+
return override?.permission;
|
|
17859
18044
|
}
|
|
17860
18045
|
function isSubagent(name) {
|
|
17861
18046
|
return SUBAGENT_NAMES.includes(name);
|
|
@@ -17878,7 +18063,6 @@ function createAgents(config2) {
|
|
|
17878
18063
|
if (override) {
|
|
17879
18064
|
applyOverrides(agent, override);
|
|
17880
18065
|
}
|
|
17881
|
-
applyQuestionPermission(agent);
|
|
17882
18066
|
return agent;
|
|
17883
18067
|
});
|
|
17884
18068
|
const orchestratorOverride = getAgentOverride(config2, "orchestrator");
|
|
@@ -17887,16 +18071,19 @@ function createAgents(config2) {
|
|
|
17887
18071
|
if (orchestratorOverride) {
|
|
17888
18072
|
applyOverrides(orchestrator, orchestratorOverride);
|
|
17889
18073
|
}
|
|
17890
|
-
applyQuestionPermission(orchestrator);
|
|
17891
18074
|
return [orchestrator, ...allSubAgents];
|
|
17892
18075
|
}
|
|
17893
18076
|
function getAgentConfigs(config2) {
|
|
17894
18077
|
const agents = createAgents(config2);
|
|
17895
18078
|
return Object.fromEntries(agents.map((agent) => {
|
|
18079
|
+
const override = getAgentOverride(config2, agent.name);
|
|
17896
18080
|
const sdkConfig = {
|
|
17897
18081
|
...agent.config,
|
|
17898
18082
|
description: agent.description
|
|
17899
18083
|
};
|
|
18084
|
+
const builtinPermission = isSubagent(agent.name) ? getBuiltinPermissionPreset(agent.name) : agent.name === "orchestrator" ? getBuiltinPermissionPreset("orchestrator") : undefined;
|
|
18085
|
+
const explicitPermissionOverride = getExplicitPermissionOverride(override);
|
|
18086
|
+
sdkConfig.permission = explicitPermissionOverride ?? agent.config.permission ?? builtinPermission;
|
|
17900
18087
|
if (isSubagent(agent.name)) {
|
|
17901
18088
|
sdkConfig.mode = "subagent";
|
|
17902
18089
|
} else if (agent.name === "orchestrator") {
|
|
@@ -18300,6 +18487,11 @@ async function extractZip(archivePath, destDir) {
|
|
|
18300
18487
|
// src/background/background-manager.ts
|
|
18301
18488
|
var BACKGROUND_CAPABLE_AGENTS = ["explorer", "librarian"];
|
|
18302
18489
|
var DEFAULT_DELEGATION_SUMMARY_LIMIT = 5;
|
|
18490
|
+
var ANY_PERMISSION_PATTERN = "*";
|
|
18491
|
+
var TASK_PERMISSION = "task";
|
|
18492
|
+
var BACKGROUND_TASK_PERMISSION = "background_task";
|
|
18493
|
+
var BACKGROUND_OUTPUT_PERMISSION = "background_output";
|
|
18494
|
+
var BACKGROUND_CANCEL_PERMISSION = "background_cancel";
|
|
18303
18495
|
function generateFallbackTaskId() {
|
|
18304
18496
|
return `bg_${Math.random().toString(36).substring(2, 10)}`;
|
|
18305
18497
|
}
|
|
@@ -18336,6 +18528,7 @@ class BackgroundTaskManager {
|
|
|
18336
18528
|
agentBySessionId = new Map;
|
|
18337
18529
|
client;
|
|
18338
18530
|
directory;
|
|
18531
|
+
worktreeDirectory;
|
|
18339
18532
|
tmuxEnabled;
|
|
18340
18533
|
config;
|
|
18341
18534
|
backgroundConfig;
|
|
@@ -18344,9 +18537,10 @@ class BackgroundTaskManager {
|
|
|
18344
18537
|
activeStarts = 0;
|
|
18345
18538
|
maxConcurrentStarts;
|
|
18346
18539
|
completionResolvers = new Map;
|
|
18347
|
-
constructor(ctx, tmuxConfig, config2, delegationManager) {
|
|
18540
|
+
constructor(ctx, tmuxConfig, config2, delegationManager, worktreeDirectory) {
|
|
18348
18541
|
this.client = ctx.client;
|
|
18349
18542
|
this.directory = ctx.directory;
|
|
18543
|
+
this.worktreeDirectory = worktreeDirectory ?? ctx.directory;
|
|
18350
18544
|
this.tmuxEnabled = tmuxConfig?.enabled ?? false;
|
|
18351
18545
|
this.config = config2;
|
|
18352
18546
|
this.delegationManager = delegationManager;
|
|
@@ -18548,12 +18742,70 @@ class BackgroundTaskManager {
|
|
|
18548
18742
|
clearTimeout(timer);
|
|
18549
18743
|
}
|
|
18550
18744
|
}
|
|
18551
|
-
|
|
18552
|
-
|
|
18553
|
-
|
|
18554
|
-
|
|
18745
|
+
createPatternPermission(allowedPatterns) {
|
|
18746
|
+
if (allowedPatterns.length === 0) {
|
|
18747
|
+
return "deny";
|
|
18748
|
+
}
|
|
18749
|
+
const permission = Object.fromEntries(allowedPatterns.map((pattern) => [pattern, "allow"]));
|
|
18750
|
+
permission[ANY_PERMISSION_PATTERN] = "deny";
|
|
18751
|
+
return permission;
|
|
18752
|
+
}
|
|
18753
|
+
createPatternRuleset(permission, allowedPatterns) {
|
|
18754
|
+
if (allowedPatterns.length === 0) {
|
|
18755
|
+
return [
|
|
18756
|
+
{
|
|
18757
|
+
permission,
|
|
18758
|
+
pattern: ANY_PERMISSION_PATTERN,
|
|
18759
|
+
action: "deny"
|
|
18760
|
+
}
|
|
18761
|
+
];
|
|
18555
18762
|
}
|
|
18556
|
-
return
|
|
18763
|
+
return [
|
|
18764
|
+
...allowedPatterns.map((pattern) => ({
|
|
18765
|
+
permission,
|
|
18766
|
+
pattern,
|
|
18767
|
+
action: "allow"
|
|
18768
|
+
})),
|
|
18769
|
+
{
|
|
18770
|
+
permission,
|
|
18771
|
+
pattern: ANY_PERMISSION_PATTERN,
|
|
18772
|
+
action: "deny"
|
|
18773
|
+
}
|
|
18774
|
+
];
|
|
18775
|
+
}
|
|
18776
|
+
calculateDelegationPermissions(agentName) {
|
|
18777
|
+
const allowedSubagents = this.getSubagentRules(agentName);
|
|
18778
|
+
const allowedBackgroundSubagents = allowedSubagents.filter((subagent) => this.isBackgroundCapableAgent(subagent));
|
|
18779
|
+
const canManageBackgroundTasks = allowedBackgroundSubagents.length > 0;
|
|
18780
|
+
const permission = {
|
|
18781
|
+
[TASK_PERMISSION]: this.createPatternPermission(allowedSubagents),
|
|
18782
|
+
[BACKGROUND_TASK_PERMISSION]: this.createPatternPermission(allowedBackgroundSubagents),
|
|
18783
|
+
[BACKGROUND_OUTPUT_PERMISSION]: canManageBackgroundTasks ? "allow" : "deny",
|
|
18784
|
+
[BACKGROUND_CANCEL_PERMISSION]: canManageBackgroundTasks ? "allow" : "deny"
|
|
18785
|
+
};
|
|
18786
|
+
return {
|
|
18787
|
+
permission,
|
|
18788
|
+
ruleset: [
|
|
18789
|
+
...this.createPatternRuleset(TASK_PERMISSION, allowedSubagents),
|
|
18790
|
+
...this.createPatternRuleset(BACKGROUND_TASK_PERMISSION, allowedBackgroundSubagents),
|
|
18791
|
+
{
|
|
18792
|
+
permission: BACKGROUND_OUTPUT_PERMISSION,
|
|
18793
|
+
pattern: ANY_PERMISSION_PATTERN,
|
|
18794
|
+
action: canManageBackgroundTasks ? "allow" : "deny"
|
|
18795
|
+
},
|
|
18796
|
+
{
|
|
18797
|
+
permission: BACKGROUND_CANCEL_PERMISSION,
|
|
18798
|
+
pattern: ANY_PERMISSION_PATTERN,
|
|
18799
|
+
action: canManageBackgroundTasks ? "allow" : "deny"
|
|
18800
|
+
}
|
|
18801
|
+
],
|
|
18802
|
+
legacyTools: {
|
|
18803
|
+
[TASK_PERMISSION]: allowedSubagents.length > 0,
|
|
18804
|
+
[BACKGROUND_TASK_PERMISSION]: canManageBackgroundTasks,
|
|
18805
|
+
[BACKGROUND_OUTPUT_PERMISSION]: canManageBackgroundTasks,
|
|
18806
|
+
[BACKGROUND_CANCEL_PERMISSION]: canManageBackgroundTasks
|
|
18807
|
+
}
|
|
18808
|
+
};
|
|
18557
18809
|
}
|
|
18558
18810
|
async startTask(task) {
|
|
18559
18811
|
task.status = "starting";
|
|
@@ -18563,12 +18815,14 @@ class BackgroundTaskManager {
|
|
|
18563
18815
|
return;
|
|
18564
18816
|
}
|
|
18565
18817
|
try {
|
|
18818
|
+
const delegationPermissions = this.calculateDelegationPermissions(task.agent);
|
|
18566
18819
|
const session = await this.client.session.create({
|
|
18567
18820
|
body: {
|
|
18568
18821
|
parentID: task.parentSessionId,
|
|
18569
|
-
title: `Background: ${task.description}
|
|
18822
|
+
title: `Background: ${task.description}`,
|
|
18823
|
+
permission: delegationPermissions.ruleset
|
|
18570
18824
|
},
|
|
18571
|
-
query: { directory: this.
|
|
18825
|
+
query: { directory: this.worktreeDirectory }
|
|
18572
18826
|
});
|
|
18573
18827
|
if (!session.data?.id) {
|
|
18574
18828
|
throw new Error("Failed to create background session");
|
|
@@ -18580,12 +18834,14 @@ class BackgroundTaskManager {
|
|
|
18580
18834
|
if (this.tmuxEnabled) {
|
|
18581
18835
|
await new Promise((r) => setTimeout(r, 500));
|
|
18582
18836
|
}
|
|
18583
|
-
const
|
|
18584
|
-
|
|
18837
|
+
const promptQuery = {
|
|
18838
|
+
directory: this.worktreeDirectory
|
|
18839
|
+
};
|
|
18585
18840
|
const resolvedVariant = resolveAgentVariant(this.config, task.agent);
|
|
18586
18841
|
const basePromptBody = applyAgentVariant(resolvedVariant, {
|
|
18587
18842
|
agent: task.agent,
|
|
18588
|
-
|
|
18843
|
+
permission: delegationPermissions.permission,
|
|
18844
|
+
tools: delegationPermissions.legacyTools,
|
|
18589
18845
|
parts: [{ type: "text", text: task.prompt }]
|
|
18590
18846
|
});
|
|
18591
18847
|
const fallbackEnabled = this.config?.fallback?.enabled ?? true;
|
|
@@ -19145,59 +19401,70 @@ function sanitizeProjectName(value) {
|
|
|
19145
19401
|
const normalized = value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
19146
19402
|
return normalized.length > 0 ? normalized : "project";
|
|
19147
19403
|
}
|
|
19148
|
-
async function runGitCommand(
|
|
19149
|
-
|
|
19150
|
-
|
|
19151
|
-
|
|
19152
|
-
|
|
19153
|
-
stderr: "pipe"
|
|
19154
|
-
});
|
|
19404
|
+
async function runGitCommand(shell, worktreeDirectory, args, timeoutMs) {
|
|
19405
|
+
if (!shell) {
|
|
19406
|
+
return null;
|
|
19407
|
+
}
|
|
19408
|
+
const command = shell.nothrow().cwd(worktreeDirectory)`git ${args}`.quiet();
|
|
19155
19409
|
let timer;
|
|
19156
19410
|
try {
|
|
19157
19411
|
const result = await Promise.race([
|
|
19158
|
-
|
|
19159
|
-
const [stdout, exitCode] = await Promise.all([
|
|
19160
|
-
new Response(subprocess.stdout).text(),
|
|
19161
|
-
subprocess.exited
|
|
19162
|
-
]);
|
|
19163
|
-
if (exitCode !== 0) {
|
|
19164
|
-
return null;
|
|
19165
|
-
}
|
|
19166
|
-
const firstLine = stdout.trim().split(/\r?\n/, 1)[0];
|
|
19167
|
-
return firstLine.length > 0 ? firstLine : null;
|
|
19168
|
-
})(),
|
|
19412
|
+
command,
|
|
19169
19413
|
new Promise((resolve) => {
|
|
19170
|
-
timer = setTimeout(() =>
|
|
19171
|
-
subprocess.kill();
|
|
19172
|
-
resolve(null);
|
|
19173
|
-
}, timeoutMs);
|
|
19414
|
+
timer = setTimeout(() => resolve(null), timeoutMs);
|
|
19174
19415
|
})
|
|
19175
19416
|
]);
|
|
19176
|
-
|
|
19417
|
+
if (result === null || result.exitCode !== 0) {
|
|
19418
|
+
return null;
|
|
19419
|
+
}
|
|
19420
|
+
const firstLine = result.text().trim().split(/\r?\n/, 1)[0];
|
|
19421
|
+
return firstLine.length > 0 ? firstLine : null;
|
|
19177
19422
|
} catch {
|
|
19178
19423
|
return null;
|
|
19179
19424
|
} finally {
|
|
19180
|
-
|
|
19425
|
+
if (timer) {
|
|
19426
|
+
clearTimeout(timer);
|
|
19427
|
+
}
|
|
19181
19428
|
}
|
|
19182
19429
|
}
|
|
19183
|
-
function buildProjectId(
|
|
19184
|
-
const
|
|
19185
|
-
return `${
|
|
19430
|
+
function buildProjectId(projectName, hash2) {
|
|
19431
|
+
const normalizedProjectName = sanitizeProjectName(projectName);
|
|
19432
|
+
return `${normalizedProjectName}-${hash2.slice(0, 12)}`;
|
|
19186
19433
|
}
|
|
19187
|
-
|
|
19188
|
-
|
|
19434
|
+
function normalizeProjectIdOptions(input, timeoutMs, projectName, shell) {
|
|
19435
|
+
if (typeof input === "string") {
|
|
19436
|
+
return {
|
|
19437
|
+
worktreeDirectory: input,
|
|
19438
|
+
projectName: projectName || path3.basename(input) || "project",
|
|
19439
|
+
shell,
|
|
19440
|
+
timeoutMs
|
|
19441
|
+
};
|
|
19442
|
+
}
|
|
19443
|
+
return {
|
|
19444
|
+
worktreeDirectory: input.worktreeDirectory,
|
|
19445
|
+
projectName: input.projectName || path3.basename(input.worktreeDirectory),
|
|
19446
|
+
shell: input.shell,
|
|
19447
|
+
timeoutMs: input.timeoutMs ?? timeoutMs
|
|
19448
|
+
};
|
|
19449
|
+
}
|
|
19450
|
+
async function getProjectId(input, timeoutMs = 5000, projectName, shell) {
|
|
19451
|
+
const resolvedProjectName = typeof input === "string" ? projectName : undefined;
|
|
19452
|
+
const resolvedShell = typeof input === "string" ? shell : undefined;
|
|
19453
|
+
const options = normalizeProjectIdOptions(input, timeoutMs, resolvedProjectName, resolvedShell);
|
|
19454
|
+
const normalizedDirectory = options.worktreeDirectory.trim();
|
|
19455
|
+
const normalizedProjectName = sanitizeProjectName(options.projectName);
|
|
19189
19456
|
if (normalizedDirectory.length === 0) {
|
|
19190
19457
|
return null;
|
|
19191
19458
|
}
|
|
19192
|
-
const repoRoot = await runGitCommand(normalizedDirectory, ["rev-parse", "--show-toplevel"], timeoutMs);
|
|
19193
|
-
const rootCommit = await runGitCommand(normalizedDirectory, ["rev-list", "--max-parents=0", "HEAD"], timeoutMs);
|
|
19459
|
+
const repoRoot = await runGitCommand(options.shell, normalizedDirectory, ["rev-parse", "--show-toplevel"], options.timeoutMs);
|
|
19460
|
+
const rootCommit = await runGitCommand(options.shell, normalizedDirectory, ["rev-list", "--max-parents=0", "HEAD"], options.timeoutMs);
|
|
19194
19461
|
if (repoRoot && rootCommit) {
|
|
19195
|
-
return buildProjectId(
|
|
19462
|
+
return buildProjectId(normalizedProjectName, rootCommit);
|
|
19196
19463
|
}
|
|
19197
19464
|
try {
|
|
19198
19465
|
const resolvedDirectory = path3.resolve(normalizedDirectory);
|
|
19199
19466
|
const hash2 = createHash("sha256").update(resolvedDirectory).digest("hex");
|
|
19200
|
-
return buildProjectId(
|
|
19467
|
+
return buildProjectId(normalizedProjectName, hash2);
|
|
19201
19468
|
} catch {
|
|
19202
19469
|
return null;
|
|
19203
19470
|
}
|
|
@@ -19341,16 +19608,20 @@ function sortByCompletionDesc(left, right) {
|
|
|
19341
19608
|
}
|
|
19342
19609
|
|
|
19343
19610
|
class DelegationManager {
|
|
19344
|
-
|
|
19611
|
+
worktreeDirectory;
|
|
19612
|
+
projectName;
|
|
19613
|
+
shell;
|
|
19345
19614
|
config;
|
|
19346
19615
|
getActiveTaskIds;
|
|
19347
19616
|
constructor(options) {
|
|
19348
|
-
this.
|
|
19617
|
+
this.worktreeDirectory = options.worktreeDirectory ?? options.directory;
|
|
19618
|
+
this.projectName = options.projectName || path4.basename(this.worktreeDirectory) || path4.basename(options.directory) || "project";
|
|
19619
|
+
this.shell = options.shell;
|
|
19349
19620
|
this.config = options.config;
|
|
19350
19621
|
this.getActiveTaskIds = options.getActiveTaskIds;
|
|
19351
19622
|
}
|
|
19352
|
-
async resolveProjectId(
|
|
19353
|
-
return getProjectId(
|
|
19623
|
+
async resolveProjectId(worktreeDirectory = this.worktreeDirectory) {
|
|
19624
|
+
return getProjectId(worktreeDirectory, this.config?.timeout, this.projectName, this.shell);
|
|
19354
19625
|
}
|
|
19355
19626
|
async createTaskId(rootSessionId) {
|
|
19356
19627
|
const reservedTaskIds = await this.getReservedTaskIds(rootSessionId);
|
|
@@ -19805,7 +20076,7 @@ async function getLatestVersion(channel = "latest") {
|
|
|
19805
20076
|
}
|
|
19806
20077
|
|
|
19807
20078
|
// src/hooks/auto-update-checker/index.ts
|
|
19808
|
-
function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
20079
|
+
function createAutoUpdateCheckerHook(ctx, options = {}, shell) {
|
|
19809
20080
|
const { showStartupToast = true, autoUpdate = true } = options;
|
|
19810
20081
|
let hasChecked = false;
|
|
19811
20082
|
return {
|
|
@@ -19832,14 +20103,14 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
|
19832
20103
|
if (showStartupToast) {
|
|
19833
20104
|
showToast(ctx, `OMO-Lite ${displayVersion ?? "unknown"}`, "oh-my-opencode-lite is active.", "info");
|
|
19834
20105
|
}
|
|
19835
|
-
runBackgroundUpdateCheck(ctx, autoUpdate).catch((err) => {
|
|
20106
|
+
runBackgroundUpdateCheck(ctx, shell, autoUpdate).catch((err) => {
|
|
19836
20107
|
log("[auto-update-checker] Background update check failed:", err);
|
|
19837
20108
|
});
|
|
19838
20109
|
}, 0);
|
|
19839
20110
|
}
|
|
19840
20111
|
};
|
|
19841
20112
|
}
|
|
19842
|
-
async function runBackgroundUpdateCheck(ctx, autoUpdate) {
|
|
20113
|
+
async function runBackgroundUpdateCheck(ctx, shell, autoUpdate) {
|
|
19843
20114
|
const pluginInfo = findPluginEntry(ctx.directory);
|
|
19844
20115
|
if (!pluginInfo) {
|
|
19845
20116
|
log("[auto-update-checker] Plugin not found in config");
|
|
@@ -19877,7 +20148,7 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate) {
|
|
|
19877
20148
|
log(`[auto-update-checker] Config updated: ${pluginInfo.entry} \u2192 ${PACKAGE_NAME}@${latestVersion}`);
|
|
19878
20149
|
}
|
|
19879
20150
|
invalidatePackage(PACKAGE_NAME);
|
|
19880
|
-
const installSuccess = await runBunInstallSafe(ctx);
|
|
20151
|
+
const installSuccess = await runBunInstallSafe(ctx, shell);
|
|
19881
20152
|
if (installSuccess) {
|
|
19882
20153
|
showToast(ctx, "OMO-Lite Updated!", `v${currentVersion} \u2192 v${latestVersion}
|
|
19883
20154
|
Restart OpenCode to apply.`, "success", 8000);
|
|
@@ -19887,23 +20158,23 @@ Restart OpenCode to apply.`, "success", 8000);
|
|
|
19887
20158
|
log("[auto-update-checker] bun install failed; update not installed");
|
|
19888
20159
|
}
|
|
19889
20160
|
}
|
|
19890
|
-
async function runBunInstallSafe(ctx) {
|
|
20161
|
+
async function runBunInstallSafe(ctx, shell) {
|
|
19891
20162
|
try {
|
|
19892
|
-
const proc = Bun.spawn(["bun", "install"], {
|
|
19893
|
-
cwd: ctx.directory,
|
|
19894
|
-
stdout: "pipe",
|
|
19895
|
-
stderr: "pipe"
|
|
19896
|
-
});
|
|
19897
20163
|
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve("timeout"), 60000));
|
|
19898
|
-
const
|
|
19899
|
-
const result = await Promise.race([exitPromise, timeoutPromise]);
|
|
19900
|
-
if (result === "timeout") {
|
|
20164
|
+
const installPromise = (async () => {
|
|
19901
20165
|
try {
|
|
19902
|
-
|
|
19903
|
-
|
|
20166
|
+
await shell`cd ${ctx.directory} && bun install`;
|
|
20167
|
+
return "completed";
|
|
20168
|
+
} catch {
|
|
20169
|
+
return "failed";
|
|
20170
|
+
}
|
|
20171
|
+
})();
|
|
20172
|
+
const result = await Promise.race([installPromise, timeoutPromise]);
|
|
20173
|
+
if (result === "timeout") {
|
|
20174
|
+
log("[auto-update-checker] bun install timed out after 60 seconds");
|
|
19904
20175
|
return false;
|
|
19905
20176
|
}
|
|
19906
|
-
return
|
|
20177
|
+
return result === "completed";
|
|
19907
20178
|
} catch (err) {
|
|
19908
20179
|
log("[auto-update-checker] bun install error:", err);
|
|
19909
20180
|
return false;
|
|
@@ -19964,236 +20235,6 @@ function createChatHeadersHook(ctx) {
|
|
|
19964
20235
|
}
|
|
19965
20236
|
};
|
|
19966
20237
|
}
|
|
19967
|
-
// src/hooks/clarification-gate/index.ts
|
|
19968
|
-
var CLARIFICATION_GATE_TAG = "clarification-gate";
|
|
19969
|
-
var DEFAULT_EXPLICIT_KEYWORDS = [
|
|
19970
|
-
"brainstorm",
|
|
19971
|
-
"think through",
|
|
19972
|
-
"scope",
|
|
19973
|
-
"plan this",
|
|
19974
|
-
"design this",
|
|
19975
|
-
"proposal",
|
|
19976
|
-
"approach",
|
|
19977
|
-
"architecture",
|
|
19978
|
-
"requirements",
|
|
19979
|
-
"options",
|
|
19980
|
-
"trade-offs"
|
|
19981
|
-
];
|
|
19982
|
-
var DEFAULT_PLANNING_KEYWORDS = [
|
|
19983
|
-
"feature",
|
|
19984
|
-
"implement",
|
|
19985
|
-
"build",
|
|
19986
|
-
"create",
|
|
19987
|
-
"add",
|
|
19988
|
-
"refactor",
|
|
19989
|
-
"restructure",
|
|
19990
|
-
"migrate",
|
|
19991
|
-
"redesign"
|
|
19992
|
-
];
|
|
19993
|
-
function normalizeText(text) {
|
|
19994
|
-
const collapsed = text.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
19995
|
-
return collapsed.length > 0 ? ` ${collapsed} ` : " ";
|
|
19996
|
-
}
|
|
19997
|
-
function findMatchingTerms(text, terms) {
|
|
19998
|
-
const normalizedText = normalizeText(text);
|
|
19999
|
-
return terms.filter((term) => {
|
|
20000
|
-
const normalizedTerm = normalizeText(term);
|
|
20001
|
-
return normalizedText.includes(normalizedTerm);
|
|
20002
|
-
});
|
|
20003
|
-
}
|
|
20004
|
-
function findScopeSignal(text, label, terms) {
|
|
20005
|
-
const matchedTerms = findMatchingTerms(text, terms);
|
|
20006
|
-
if (matchedTerms.length === 0) {
|
|
20007
|
-
return null;
|
|
20008
|
-
}
|
|
20009
|
-
return {
|
|
20010
|
-
label,
|
|
20011
|
-
matchedTerms
|
|
20012
|
-
};
|
|
20013
|
-
}
|
|
20014
|
-
function findLayerSignal(text) {
|
|
20015
|
-
const leftTerms = ["ui", "frontend", "client"];
|
|
20016
|
-
const rightTerms = ["backend", "server", "database"];
|
|
20017
|
-
const matchedLeft = findMatchingTerms(text, leftTerms);
|
|
20018
|
-
const matchedRight = findMatchingTerms(text, rightTerms);
|
|
20019
|
-
if (matchedLeft.length === 0 || matchedRight.length === 0) {
|
|
20020
|
-
return null;
|
|
20021
|
-
}
|
|
20022
|
-
return {
|
|
20023
|
-
label: "scope signal \u2014 multiple layers",
|
|
20024
|
-
matchedTerms: [`${matchedLeft[0]} + ${matchedRight[0]}`]
|
|
20025
|
-
};
|
|
20026
|
-
}
|
|
20027
|
-
function detectAmbiguity(text, explicitKeywords, planningKeywords) {
|
|
20028
|
-
const explicitMatches = findMatchingTerms(text, explicitKeywords);
|
|
20029
|
-
const planningMatches = findMatchingTerms(text, planningKeywords);
|
|
20030
|
-
const scopeSignals = [
|
|
20031
|
-
findScopeSignal(text, "scope signal \u2014 multiple views/pages", [
|
|
20032
|
-
"page",
|
|
20033
|
-
"screen",
|
|
20034
|
-
"flow",
|
|
20035
|
-
"wizard",
|
|
20036
|
-
"dashboard",
|
|
20037
|
-
"onboarding",
|
|
20038
|
-
"settings",
|
|
20039
|
-
"checkout",
|
|
20040
|
-
"step",
|
|
20041
|
-
"journey"
|
|
20042
|
-
]),
|
|
20043
|
-
findScopeSignal(text, "scope signal \u2014 api/data", [
|
|
20044
|
-
"api",
|
|
20045
|
-
"endpoint",
|
|
20046
|
-
"route",
|
|
20047
|
-
"mutation",
|
|
20048
|
-
"query",
|
|
20049
|
-
"schema",
|
|
20050
|
-
"table",
|
|
20051
|
-
"model",
|
|
20052
|
-
"migration",
|
|
20053
|
-
"database"
|
|
20054
|
-
]),
|
|
20055
|
-
findScopeSignal(text, "scope signal \u2014 restructuring", [
|
|
20056
|
-
"refactor",
|
|
20057
|
-
"restructure",
|
|
20058
|
-
"replace",
|
|
20059
|
-
"redesign",
|
|
20060
|
-
"rework",
|
|
20061
|
-
"consolidate",
|
|
20062
|
-
"split",
|
|
20063
|
-
"migrate"
|
|
20064
|
-
]),
|
|
20065
|
-
findLayerSignal(text),
|
|
20066
|
-
findScopeSignal(text, "scope signal \u2014 business/ux phrasing", [
|
|
20067
|
-
"users should",
|
|
20068
|
-
"support",
|
|
20069
|
-
"allow",
|
|
20070
|
-
"improve experience",
|
|
20071
|
-
"pricing",
|
|
20072
|
-
"onboarding",
|
|
20073
|
-
"billing",
|
|
20074
|
-
"permissions"
|
|
20075
|
-
]),
|
|
20076
|
-
findScopeSignal(text, "scope signal \u2014 ambiguous/open-ended", [
|
|
20077
|
-
"help me think",
|
|
20078
|
-
"best way",
|
|
20079
|
-
"how should",
|
|
20080
|
-
"what's the right approach",
|
|
20081
|
-
"explore",
|
|
20082
|
-
"maybe",
|
|
20083
|
-
"not sure"
|
|
20084
|
-
]),
|
|
20085
|
-
findScopeSignal(text, "scope signal \u2014 cross-directory", [
|
|
20086
|
-
"across the app",
|
|
20087
|
-
"across the system",
|
|
20088
|
-
"multiple files",
|
|
20089
|
-
"several components"
|
|
20090
|
-
])
|
|
20091
|
-
].filter((signal) => signal !== null);
|
|
20092
|
-
return {
|
|
20093
|
-
explicitMatches,
|
|
20094
|
-
planningMatches,
|
|
20095
|
-
scopeSignals
|
|
20096
|
-
};
|
|
20097
|
-
}
|
|
20098
|
-
function shouldInjectClarificationGate(config2, detection) {
|
|
20099
|
-
const explicitMatched = detection.explicitMatches.length > 0;
|
|
20100
|
-
const planningMatched = detection.planningMatches.length > 0;
|
|
20101
|
-
const scopeSignalCount = detection.scopeSignals.length;
|
|
20102
|
-
switch (config2.mode) {
|
|
20103
|
-
case "off":
|
|
20104
|
-
return false;
|
|
20105
|
-
case "explicit-only":
|
|
20106
|
-
return explicitMatched;
|
|
20107
|
-
case "auto":
|
|
20108
|
-
return explicitMatched || scopeSignalCount >= config2.min_scope_signals;
|
|
20109
|
-
case "auto-for-planning":
|
|
20110
|
-
return explicitMatched || planningMatched && scopeSignalCount >= 1 || scopeSignalCount >= config2.hard_complex_signal_threshold;
|
|
20111
|
-
}
|
|
20112
|
-
}
|
|
20113
|
-
function buildMatchedSignals(detection, config2) {
|
|
20114
|
-
const matchedSignals = [];
|
|
20115
|
-
if (detection.explicitMatches.length > 0) {
|
|
20116
|
-
matchedSignals.push(`explicit keywords: ${detection.explicitMatches.join(", ")}`);
|
|
20117
|
-
}
|
|
20118
|
-
if (detection.planningMatches.length > 0) {
|
|
20119
|
-
matchedSignals.push(`planning keywords: ${detection.planningMatches.join(", ")}`);
|
|
20120
|
-
}
|
|
20121
|
-
for (const signal of detection.scopeSignals) {
|
|
20122
|
-
matchedSignals.push(`${signal.label}: ${signal.matchedTerms.join(", ")}`);
|
|
20123
|
-
}
|
|
20124
|
-
if (matchedSignals.length === 0) {
|
|
20125
|
-
matchedSignals.push(`configured threshold allows injection (mode=${config2.mode})`);
|
|
20126
|
-
}
|
|
20127
|
-
return matchedSignals;
|
|
20128
|
-
}
|
|
20129
|
-
function buildClarificationGateBlock(detection, config2) {
|
|
20130
|
-
const matchedSignals = buildMatchedSignals(detection, config2).map((signal) => `- ${signal}`).join(`
|
|
20131
|
-
`);
|
|
20132
|
-
return `<${CLARIFICATION_GATE_TAG}>
|
|
20133
|
-
Potentially ambiguous or substantial request detected.
|
|
20134
|
-
|
|
20135
|
-
Matched signals:
|
|
20136
|
-
${matchedSignals}
|
|
20137
|
-
|
|
20138
|
-
Load the brainstorming skill and follow its workflow before implementing.
|
|
20139
|
-
Ask one clarifying question at a time, max 5 total, prefer multiple choice.
|
|
20140
|
-
Assess scope using the seven scope signals.
|
|
20141
|
-
Propose 2-3 approaches with trade-offs and a recommendation.
|
|
20142
|
-
Wait for explicit user approval before implementation or SDD handoff.
|
|
20143
|
-
|
|
20144
|
-
Do not implement during brainstorming.
|
|
20145
|
-
</${CLARIFICATION_GATE_TAG}>`;
|
|
20146
|
-
}
|
|
20147
|
-
function createClarificationGateHook(options = {}) {
|
|
20148
|
-
const config2 = ClarificationGateConfigSchema.parse(options.clarificationGate ?? {});
|
|
20149
|
-
const explicitKeywords = config2.explicit_keywords ?? [
|
|
20150
|
-
...DEFAULT_EXPLICIT_KEYWORDS
|
|
20151
|
-
];
|
|
20152
|
-
const planningKeywords = config2.planning_keywords ?? [
|
|
20153
|
-
...DEFAULT_PLANNING_KEYWORDS
|
|
20154
|
-
];
|
|
20155
|
-
return {
|
|
20156
|
-
"experimental.chat.messages.transform": async (_input, output) => {
|
|
20157
|
-
const { messages } = output;
|
|
20158
|
-
if (messages.length === 0) {
|
|
20159
|
-
return;
|
|
20160
|
-
}
|
|
20161
|
-
let lastUserMessageIndex = -1;
|
|
20162
|
-
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
20163
|
-
if (messages[i2].info.role === "user") {
|
|
20164
|
-
lastUserMessageIndex = i2;
|
|
20165
|
-
break;
|
|
20166
|
-
}
|
|
20167
|
-
}
|
|
20168
|
-
if (lastUserMessageIndex === -1) {
|
|
20169
|
-
return;
|
|
20170
|
-
}
|
|
20171
|
-
const lastUserMessage = messages[lastUserMessageIndex];
|
|
20172
|
-
const agent = lastUserMessage.info.agent;
|
|
20173
|
-
if (agent && agent !== "orchestrator") {
|
|
20174
|
-
return;
|
|
20175
|
-
}
|
|
20176
|
-
const textPartIndex = lastUserMessage.parts.findIndex((p) => p.type === "text" && p.text !== undefined);
|
|
20177
|
-
if (textPartIndex === -1) {
|
|
20178
|
-
return;
|
|
20179
|
-
}
|
|
20180
|
-
const originalText = lastUserMessage.parts[textPartIndex].text ?? "";
|
|
20181
|
-
if (originalText.includes(LITE_INTERNAL_INITIATOR_MARKER)) {
|
|
20182
|
-
return;
|
|
20183
|
-
}
|
|
20184
|
-
const detection = detectAmbiguity(originalText, explicitKeywords, planningKeywords);
|
|
20185
|
-
if (!shouldInjectClarificationGate(config2, detection)) {
|
|
20186
|
-
return;
|
|
20187
|
-
}
|
|
20188
|
-
const clarificationGateBlock = buildClarificationGateBlock(detection, config2);
|
|
20189
|
-
lastUserMessage.parts[textPartIndex].text = `${clarificationGateBlock}
|
|
20190
|
-
|
|
20191
|
-
---
|
|
20192
|
-
|
|
20193
|
-
${originalText}`;
|
|
20194
|
-
}
|
|
20195
|
-
};
|
|
20196
|
-
}
|
|
20197
20238
|
// src/hooks/delegate-task-retry/patterns.ts
|
|
20198
20239
|
var DELEGATE_TASK_ERROR_PATTERNS = [
|
|
20199
20240
|
{
|
|
@@ -20471,10 +20512,12 @@ class ForegroundFallbackManager {
|
|
|
20471
20512
|
await this.client.session.abort({ path: { id: sessionID } });
|
|
20472
20513
|
} catch {}
|
|
20473
20514
|
await new Promise((r2) => setTimeout(r2, 500));
|
|
20474
|
-
|
|
20475
|
-
await sessionClient.promptAsync({
|
|
20515
|
+
await this.client.session.promptAsync({
|
|
20476
20516
|
path: { id: sessionID },
|
|
20477
|
-
body: {
|
|
20517
|
+
body: {
|
|
20518
|
+
parts: lastUser.parts,
|
|
20519
|
+
model: ref
|
|
20520
|
+
}
|
|
20478
20521
|
});
|
|
20479
20522
|
this.sessionModel.set(sessionID, nextModel);
|
|
20480
20523
|
log("[foreground-fallback] switched to fallback model", {
|
|
@@ -20569,7 +20612,7 @@ ${JSON_ERROR_REMINDER}`;
|
|
|
20569
20612
|
// src/hooks/phase-reminder/index.ts
|
|
20570
20613
|
var PHASE_REMINDER = `<reminder>Recall Workflow Rules:
|
|
20571
20614
|
Understand \u2192 find the best path (delegate based on rules and parallelize independent work) \u2192 execute \u2192 verify.
|
|
20572
|
-
If delegating, launch the specialist in the same turn you mention it.</reminder>`;
|
|
20615
|
+
If delegating, launch the specialist in the same turn you mention it. If multiple delegations are independent, emit all tool calls in a single response \u2014 never serialize independent work across turns.</reminder>`;
|
|
20573
20616
|
function createPhaseReminderHook() {
|
|
20574
20617
|
return {
|
|
20575
20618
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
@@ -20612,7 +20655,7 @@ ${originalText}`;
|
|
|
20612
20655
|
var NUDGE = `
|
|
20613
20656
|
|
|
20614
20657
|
---
|
|
20615
|
-
Workflow Reminder: delegate based on rules; If
|
|
20658
|
+
Workflow Reminder: delegate based on rules; launch specialists in this same turn. If multiple delegations are independent, emit all tool calls in one response.`;
|
|
20616
20659
|
function createPostReadNudgeHook() {
|
|
20617
20660
|
return {
|
|
20618
20661
|
"tool.execute.after": async (input, output) => {
|
|
@@ -20769,10 +20812,10 @@ var SHARED_SKILL_DIRECTORY2 = "_shared";
|
|
|
20769
20812
|
var SHARED_SKILL_SOURCE_PATH = `src/skills/${SHARED_SKILL_DIRECTORY2}`;
|
|
20770
20813
|
var CUSTOM_SKILLS = [
|
|
20771
20814
|
{
|
|
20772
|
-
name: "
|
|
20773
|
-
description: "
|
|
20815
|
+
name: "requirements-interview",
|
|
20816
|
+
description: "Mandatory step-0 discovery interview to understand user intent, clarify scope, and choose the right path before implementation",
|
|
20774
20817
|
allowedAgents: ["orchestrator"],
|
|
20775
|
-
sourcePath: "src/skills/
|
|
20818
|
+
sourcePath: "src/skills/requirements-interview"
|
|
20776
20819
|
},
|
|
20777
20820
|
{
|
|
20778
20821
|
name: "cartography",
|
|
@@ -20786,6 +20829,12 @@ var CUSTOM_SKILLS = [
|
|
|
20786
20829
|
allowedAgents: ["orchestrator", "oracle"],
|
|
20787
20830
|
sourcePath: "src/skills/plan-reviewer"
|
|
20788
20831
|
},
|
|
20832
|
+
{
|
|
20833
|
+
name: "sdd-init",
|
|
20834
|
+
description: "Initialize OpenSpec structure and SDD project context",
|
|
20835
|
+
allowedAgents: ["orchestrator"],
|
|
20836
|
+
sourcePath: "src/skills/sdd-init"
|
|
20837
|
+
},
|
|
20789
20838
|
{
|
|
20790
20839
|
name: "sdd-propose",
|
|
20791
20840
|
description: "Create change proposals for OpenSpec workflows",
|
|
@@ -21069,11 +21118,11 @@ var DEFAULT_THOTH_TIMEOUT_MS = 15000;
|
|
|
21069
21118
|
function isRecord2(value) {
|
|
21070
21119
|
return typeof value === "object" && value !== null;
|
|
21071
21120
|
}
|
|
21072
|
-
function
|
|
21121
|
+
function normalizeText(value) {
|
|
21073
21122
|
return typeof value === "string" ? value.trim() || null : null;
|
|
21074
21123
|
}
|
|
21075
21124
|
function previewText(value, max) {
|
|
21076
|
-
const text =
|
|
21125
|
+
const text = normalizeText(value);
|
|
21077
21126
|
if (!text) {
|
|
21078
21127
|
return null;
|
|
21079
21128
|
}
|
|
@@ -21093,9 +21142,9 @@ function formatMemoryContext(response) {
|
|
|
21093
21142
|
if (sessions.length > 0) {
|
|
21094
21143
|
lines.push("### Recent Sessions");
|
|
21095
21144
|
for (const rawSession of sessions) {
|
|
21096
|
-
const sessionId =
|
|
21097
|
-
const project =
|
|
21098
|
-
const startedAt =
|
|
21145
|
+
const sessionId = normalizeText(rawSession.id) ?? "unknown-session";
|
|
21146
|
+
const project = normalizeText(rawSession.project) ?? "unknown-project";
|
|
21147
|
+
const startedAt = normalizeText(rawSession.started_at) ?? "unknown";
|
|
21099
21148
|
lines.push(`- [${sessionId}] (${project}) \u2014 started ${startedAt}`);
|
|
21100
21149
|
const summary = previewText(rawSession.summary, 300);
|
|
21101
21150
|
if (summary) {
|
|
@@ -21107,9 +21156,9 @@ function formatMemoryContext(response) {
|
|
|
21107
21156
|
if (observations.length > 0) {
|
|
21108
21157
|
lines.push("### Recent Observations");
|
|
21109
21158
|
for (const rawObservation of observations) {
|
|
21110
|
-
const type =
|
|
21111
|
-
const title =
|
|
21112
|
-
const createdAt =
|
|
21159
|
+
const type = normalizeText(rawObservation.type) ?? "manual";
|
|
21160
|
+
const title = normalizeText(rawObservation.title) ?? "Untitled";
|
|
21161
|
+
const createdAt = normalizeText(rawObservation.created_at) ?? "unknown";
|
|
21113
21162
|
lines.push(`- [${type}] ${title} (${createdAt})`);
|
|
21114
21163
|
const content = previewText(rawObservation.content, 300);
|
|
21115
21164
|
if (content) {
|
|
@@ -21125,7 +21174,7 @@ function formatMemoryContext(response) {
|
|
|
21125
21174
|
if (!content) {
|
|
21126
21175
|
continue;
|
|
21127
21176
|
}
|
|
21128
|
-
const createdAt =
|
|
21177
|
+
const createdAt = normalizeText(rawPrompt.created_at) ?? "unknown";
|
|
21129
21178
|
lines.push(`- ${content} (${createdAt})`);
|
|
21130
21179
|
}
|
|
21131
21180
|
lines.push("");
|
|
@@ -21229,7 +21278,7 @@ function createThothClient(options) {
|
|
|
21229
21278
|
}
|
|
21230
21279
|
// src/hooks/thoth-mem/protocol.ts
|
|
21231
21280
|
var SDD_TOPIC_KEY_FORMAT = "sdd/{change}/{artifact}";
|
|
21232
|
-
var FIRST_ACTION_INSTRUCTION =
|
|
21281
|
+
var FIRST_ACTION_INSTRUCTION = 'FIRST ACTION REQUIRED: Call mem_session_summary with the content of the compacted summary. This preserves what was accomplished before compaction. Do this BEFORE any other work. Then call mem_context for a recent-session overview, and use the 3-layer recall protocol (mem_search with mode "compact" -> mem_timeline -> mem_get_observation) for precise retrieval.';
|
|
21233
21282
|
var SESSION_SUMMARY_TEMPLATE = `Use this exact structure for \`mem_session_summary\` content:
|
|
21234
21283
|
|
|
21235
21284
|
## Goal
|
|
@@ -21250,7 +21299,7 @@ var SESSION_SUMMARY_TEMPLATE = `Use this exact structure for \`mem_session_summa
|
|
|
21250
21299
|
## Relevant Files
|
|
21251
21300
|
- path/to/file.ts - [what it does or what changed]`;
|
|
21252
21301
|
function buildCompactionReminder(sessionID) {
|
|
21253
|
-
return `FIRST ACTION REQUIRED: this session was compacted. Call \`mem_session_summary\` with the content of the compacted summary and \`session_id\` \`${sessionID}\`. This preserves what was accomplished before compaction. Do this BEFORE any other work. After that, call \`mem_capture_passive\` if the summary includes \`## Key Learnings:\`,
|
|
21302
|
+
return `FIRST ACTION REQUIRED: this session was compacted. Call \`mem_session_summary\` with the content of the compacted summary and \`session_id\` \`${sessionID}\`. This preserves what was accomplished before compaction. Do this BEFORE any other work. After that, call \`mem_capture_passive\` if the summary includes \`## Key Learnings:\`, call \`mem_context\` for a recent-session overview, and use the 3-layer recall protocol (\`mem_search\` with \`mode: "compact"\` -> \`mem_timeline\` -> \`mem_get_observation\`) for precise retrieval.`;
|
|
21254
21303
|
}
|
|
21255
21304
|
function buildCompactorInstruction(project) {
|
|
21256
21305
|
return `CRITICAL INSTRUCTION: place this at the TOP of the compacted summary exactly as an action item for the resumed agent: "FIRST ACTION REQUIRED: Call mem_session_summary with the content of this compacted summary. Use project: '${project}'. This preserves what was accomplished before compaction. Do this BEFORE any other work."`;
|
|
@@ -21263,27 +21312,50 @@ Persistent memory is available through thoth-mem. Follow this protocol.
|
|
|
21263
21312
|
IMPORTANT: Your current session_id is \`${sessionID}\` and project is \`${project}\`.
|
|
21264
21313
|
Always pass these values when calling memory tools that accept them (mem_session_summary, mem_save, mem_capture_passive, etc.).
|
|
21265
21314
|
|
|
21266
|
-
|
|
21267
|
-
|
|
21268
|
-
|
|
21269
|
-
|
|
21270
|
-
|
|
21271
|
-
-
|
|
21272
|
-
-
|
|
21273
|
-
-
|
|
21274
|
-
-
|
|
21315
|
+
### CORE TOOLS
|
|
21316
|
+
mem_save, mem_search, mem_context, mem_session_summary, mem_get_observation, mem_save_prompt, mem_update, mem_suggest_topic_key, mem_timeline, mem_capture_passive
|
|
21317
|
+
|
|
21318
|
+
### WHEN TO SAVE
|
|
21319
|
+
Call \`mem_save\` IMMEDIATELY after ANY of these:
|
|
21320
|
+
- Architecture, design, or workflow decision made
|
|
21321
|
+
- Bug fixed (include root cause)
|
|
21322
|
+
- Non-obvious discovery, gotcha, or edge case found
|
|
21323
|
+
- Configuration change or environment setup
|
|
21324
|
+
- Pattern or convention established (naming, structure, approach)
|
|
21325
|
+
- User preference or constraint learned
|
|
21326
|
+
- Feature implemented with non-obvious approach
|
|
21327
|
+
- User confirms a recommendation ("dale", "go with that", "sounds good", "s\xED, esa")
|
|
21328
|
+
- User rejects an approach or expresses a preference ("no, better X", "I prefer X")
|
|
21329
|
+
- Discussion concludes with a clear direction chosen
|
|
21330
|
+
|
|
21331
|
+
Use \`title\` as Verb + what changed or was learned.
|
|
21332
|
+
Use \`type\` from: bugfix | decision | architecture | discovery | pattern | config | learning | manual.
|
|
21333
|
+
Set \`scope\` intentionally.
|
|
21334
|
+
Reuse \`topic_key\` for the same evolving topic. Do not overwrite unrelated topics.
|
|
21335
|
+
If unsure about a stable \`topic_key\`, call \`mem_suggest_topic_key\` first.
|
|
21336
|
+
If you need to modify a known observation by exact ID, call \`mem_update\` instead of creating a new record.
|
|
21337
|
+
Put the durable details in \`content\` with this structure:
|
|
21275
21338
|
- What: concise description of what changed or was learned
|
|
21276
21339
|
- Why: why it mattered or what problem it solved
|
|
21277
21340
|
- Where: files, paths, or systems involved
|
|
21278
21341
|
- Learned: edge cases, caveats, or follow-up notes
|
|
21279
21342
|
|
|
21280
|
-
|
|
21281
|
-
|
|
21343
|
+
**Self-check after EVERY task**: "Did I or the user just make a decision, confirm a recommendation, express a preference, fix a bug, learn something, or establish a convention? If yes \u2192 mem_save NOW."
|
|
21344
|
+
|
|
21345
|
+
You can also call \`mem_save_prompt\` to manually save a user prompt that you consider particularly important for future context.
|
|
21346
|
+
|
|
21347
|
+
### WHEN TO SEARCH MEMORY
|
|
21348
|
+
- Broad recovery (session start, after compaction): call \`mem_context\` for a recent-session overview.
|
|
21349
|
+
- Targeted 3-layer recall (specific memory retrieval):
|
|
21350
|
+
1. Call \`mem_search\` with \`mode: "compact"\` (default) to scan the compact index of IDs + titles.
|
|
21351
|
+
2. Call \`mem_timeline\` around promising observation IDs for chronological context within the same session.
|
|
21352
|
+
3. Call \`mem_get_observation\` only for observations you need in full.
|
|
21353
|
+
- Use \`mode: "preview"\` with \`mem_search\` only when compact results are insufficient to disambiguate.
|
|
21282
21354
|
- Search proactively on the first message about a project, feature, or problem when prior context may matter.
|
|
21283
21355
|
- Search before starting work that may have been done before.
|
|
21284
21356
|
- Search when the user mentions a topic that lacks enough local context.
|
|
21285
21357
|
|
|
21286
|
-
SESSION CLOSE PROTOCOL
|
|
21358
|
+
### SESSION CLOSE PROTOCOL
|
|
21287
21359
|
- Before ending the session, call \`mem_session_summary\` with this exact template.
|
|
21288
21360
|
- This is NOT optional. If you skip this, the next session starts blind.
|
|
21289
21361
|
- Do not claim memory was saved unless the tool call succeeded.
|
|
@@ -21291,17 +21363,21 @@ SESSION CLOSE PROTOCOL
|
|
|
21291
21363
|
|
|
21292
21364
|
${SESSION_SUMMARY_TEMPLATE}
|
|
21293
21365
|
|
|
21294
|
-
AFTER COMPACTION
|
|
21366
|
+
### AFTER COMPACTION
|
|
21295
21367
|
- IMMEDIATELY call \`mem_session_summary\` with the compacted summary content.
|
|
21296
|
-
- Then call \`mem_context
|
|
21368
|
+
- Then call \`mem_context\` for a recent-session overview.
|
|
21369
|
+
- Use the 3-layer recall protocol (\`mem_search\` with \`mode: "compact"\` -> \`mem_timeline\` -> \`mem_get_observation\`) for precise artifact/prior-observation retrieval.
|
|
21297
21370
|
- Only then continue working.
|
|
21298
21371
|
|
|
21299
|
-
SDD
|
|
21372
|
+
### SDD TOPIC KEY CONVENTION
|
|
21300
21373
|
- use ${SDD_TOPIC_KEY_FORMAT}
|
|
21301
21374
|
- examples: sdd/add-user-auth/spec, sdd/add-user-auth/design, sdd/add-user-auth/tasks
|
|
21302
21375
|
</memory_protocol>
|
|
21303
21376
|
`.trim();
|
|
21304
21377
|
}
|
|
21378
|
+
function buildSaveNudge() {
|
|
21379
|
+
return "MEMORY REMINDER: It has been a while since your last save. If you have made decisions, discoveries, or completed significant work, call mem_save now.";
|
|
21380
|
+
}
|
|
21305
21381
|
|
|
21306
21382
|
// src/hooks/thoth-mem/index.ts
|
|
21307
21383
|
function isRootSession(session) {
|
|
@@ -21347,6 +21423,10 @@ function isSessionSummaryTool(toolName) {
|
|
|
21347
21423
|
const normalized = toolName.toLowerCase();
|
|
21348
21424
|
return normalized === "mem_session_summary" || normalized.endsWith(".mem_session_summary") || normalized.endsWith("_mem_session_summary");
|
|
21349
21425
|
}
|
|
21426
|
+
function isMemSaveTool(toolName) {
|
|
21427
|
+
const normalized = toolName.toLowerCase();
|
|
21428
|
+
return normalized === "mem_save" || normalized.endsWith(".mem_save") || normalized.endsWith("_mem_save");
|
|
21429
|
+
}
|
|
21350
21430
|
function createThothMemHook(options) {
|
|
21351
21431
|
const enabled = options.enabled !== false;
|
|
21352
21432
|
const thoth = createThothClient({
|
|
@@ -21359,6 +21439,9 @@ function createThothMemHook(options) {
|
|
|
21359
21439
|
const trackedRootSessions = new Set;
|
|
21360
21440
|
const needsCompactionFollowUp = new Set;
|
|
21361
21441
|
const ensuredRootSessions = new Map;
|
|
21442
|
+
const sessionCreatedAt = new Map;
|
|
21443
|
+
const lastMemSaveAt = new Map;
|
|
21444
|
+
const nudgePending = new Set;
|
|
21362
21445
|
async function ensureRootSession(sessionId) {
|
|
21363
21446
|
if (!enabled || trackedRootSessions.has(sessionId)) {
|
|
21364
21447
|
return;
|
|
@@ -21369,20 +21452,47 @@ function createThothMemHook(options) {
|
|
|
21369
21452
|
return;
|
|
21370
21453
|
}
|
|
21371
21454
|
const pending = (async () => {
|
|
21372
|
-
await thoth.memSessionStart(sessionId);
|
|
21373
|
-
|
|
21455
|
+
const started = await thoth.memSessionStart(sessionId);
|
|
21456
|
+
if (started) {
|
|
21457
|
+
trackedRootSessions.add(sessionId);
|
|
21458
|
+
} else {
|
|
21459
|
+
log("[thoth] session start unavailable, skipping tracking:", sessionId);
|
|
21460
|
+
}
|
|
21374
21461
|
})().finally(() => {
|
|
21375
21462
|
ensuredRootSessions.delete(sessionId);
|
|
21376
21463
|
});
|
|
21377
21464
|
ensuredRootSessions.set(sessionId, pending);
|
|
21378
21465
|
await pending;
|
|
21379
21466
|
}
|
|
21467
|
+
function shouldInjectSaveNudge(sessionId) {
|
|
21468
|
+
if (nudgePending.has(sessionId)) {
|
|
21469
|
+
return false;
|
|
21470
|
+
}
|
|
21471
|
+
if (needsCompactionFollowUp.has(sessionId)) {
|
|
21472
|
+
return false;
|
|
21473
|
+
}
|
|
21474
|
+
const createdAt = sessionCreatedAt.get(sessionId);
|
|
21475
|
+
if (!createdAt || Date.now() - createdAt < 5 * 60 * 1000) {
|
|
21476
|
+
return false;
|
|
21477
|
+
}
|
|
21478
|
+
const lastSave = lastMemSaveAt.get(sessionId);
|
|
21479
|
+
if (lastSave && Date.now() - lastSave < 15 * 60 * 1000) {
|
|
21480
|
+
return false;
|
|
21481
|
+
}
|
|
21482
|
+
nudgePending.add(sessionId);
|
|
21483
|
+
return true;
|
|
21484
|
+
}
|
|
21380
21485
|
async function handleSessionCreated(session) {
|
|
21381
21486
|
if (!enabled || !isRootSession(session)) {
|
|
21382
21487
|
return;
|
|
21383
21488
|
}
|
|
21384
|
-
|
|
21385
|
-
|
|
21489
|
+
const started = await thoth.memSessionStart(session.id);
|
|
21490
|
+
if (started) {
|
|
21491
|
+
trackedRootSessions.add(session.id);
|
|
21492
|
+
sessionCreatedAt.set(session.id, Date.now());
|
|
21493
|
+
} else {
|
|
21494
|
+
log("[thoth] session start unavailable, skipping tracking:", session.id);
|
|
21495
|
+
}
|
|
21386
21496
|
}
|
|
21387
21497
|
function handleSessionCompacted(session) {
|
|
21388
21498
|
if (!enabled || !isRootSession(session) || !session.id) {
|
|
@@ -21394,6 +21504,9 @@ function createThothMemHook(options) {
|
|
|
21394
21504
|
trackedRootSessions.delete(session.id);
|
|
21395
21505
|
needsCompactionFollowUp.delete(session.id);
|
|
21396
21506
|
ensuredRootSessions.delete(session.id);
|
|
21507
|
+
sessionCreatedAt.delete(session.id);
|
|
21508
|
+
lastMemSaveAt.delete(session.id);
|
|
21509
|
+
nudgePending.delete(session.id);
|
|
21397
21510
|
}
|
|
21398
21511
|
return {
|
|
21399
21512
|
event: async ({ event }) => {
|
|
@@ -21435,7 +21548,7 @@ function createThothMemHook(options) {
|
|
|
21435
21548
|
return;
|
|
21436
21549
|
}
|
|
21437
21550
|
const sanitizedPromptText = sanitizePromptText(promptText);
|
|
21438
|
-
if (!sanitizedPromptText) {
|
|
21551
|
+
if (!sanitizedPromptText || sanitizedPromptText.length <= 10) {
|
|
21439
21552
|
return;
|
|
21440
21553
|
}
|
|
21441
21554
|
await thoth.memSavePrompt(input.sessionID, sanitizedPromptText);
|
|
@@ -21450,8 +21563,15 @@ function createThothMemHook(options) {
|
|
|
21450
21563
|
}
|
|
21451
21564
|
const memoryInstructions = buildMemoryInstructions(input.sessionID, options.project);
|
|
21452
21565
|
const compactionReminder = needsCompactionFollowUp.has(input.sessionID) ? buildCompactionReminder(input.sessionID) : null;
|
|
21566
|
+
const saveNudge = shouldInjectSaveNudge(input.sessionID) ? buildSaveNudge() : null;
|
|
21453
21567
|
if (output.system.length === 0) {
|
|
21454
|
-
|
|
21568
|
+
let systemPrompt = memoryInstructions;
|
|
21569
|
+
if (compactionReminder) {
|
|
21570
|
+
systemPrompt = appendInstruction(systemPrompt, compactionReminder);
|
|
21571
|
+
}
|
|
21572
|
+
if (saveNudge) {
|
|
21573
|
+
systemPrompt = appendInstruction(systemPrompt, saveNudge);
|
|
21574
|
+
}
|
|
21455
21575
|
output.system.push(systemPrompt);
|
|
21456
21576
|
return;
|
|
21457
21577
|
}
|
|
@@ -21460,6 +21580,9 @@ function createThothMemHook(options) {
|
|
|
21460
21580
|
if (compactionReminder) {
|
|
21461
21581
|
updatedSystemPrompt = appendInstruction(updatedSystemPrompt, compactionReminder);
|
|
21462
21582
|
}
|
|
21583
|
+
if (saveNudge) {
|
|
21584
|
+
updatedSystemPrompt = appendInstruction(updatedSystemPrompt, saveNudge);
|
|
21585
|
+
}
|
|
21463
21586
|
output.system[lastIndex] = updatedSystemPrompt;
|
|
21464
21587
|
},
|
|
21465
21588
|
"experimental.session.compacting": async (input, output) => {
|
|
@@ -21485,6 +21608,10 @@ function createThothMemHook(options) {
|
|
|
21485
21608
|
if (!enabled) {
|
|
21486
21609
|
return;
|
|
21487
21610
|
}
|
|
21611
|
+
if (isMemSaveTool(input.tool)) {
|
|
21612
|
+
lastMemSaveAt.set(input.sessionID, Date.now());
|
|
21613
|
+
nudgePending.delete(input.sessionID);
|
|
21614
|
+
}
|
|
21488
21615
|
if (isSessionSummaryTool(input.tool)) {
|
|
21489
21616
|
needsCompactionFollowUp.delete(input.sessionID);
|
|
21490
21617
|
}
|
|
@@ -21523,10 +21650,11 @@ function createThothMcp(config2) {
|
|
|
21523
21650
|
}
|
|
21524
21651
|
|
|
21525
21652
|
// src/mcp/websearch.ts
|
|
21653
|
+
var baseUrl = "https://mcp.exa.ai/mcp?tools=web_search_exa";
|
|
21654
|
+
var url2 = process.env.EXA_API_KEY ? `${baseUrl}&exaApiKey=${process.env.EXA_API_KEY}` : baseUrl;
|
|
21526
21655
|
var websearch = {
|
|
21527
21656
|
type: "remote",
|
|
21528
|
-
url:
|
|
21529
|
-
headers: process.env.EXA_API_KEY ? { "x-api-key": process.env.EXA_API_KEY } : undefined,
|
|
21657
|
+
url: url2,
|
|
21530
21658
|
oauth: false
|
|
21531
21659
|
};
|
|
21532
21660
|
|
|
@@ -21554,7 +21682,7 @@ __export(exports_external2, {
|
|
|
21554
21682
|
uuidv4: () => uuidv42,
|
|
21555
21683
|
uuid: () => uuid5,
|
|
21556
21684
|
util: () => exports_util2,
|
|
21557
|
-
url: () =>
|
|
21685
|
+
url: () => url3,
|
|
21558
21686
|
uppercase: () => _uppercase2,
|
|
21559
21687
|
unknown: () => unknown2,
|
|
21560
21688
|
union: () => union2,
|
|
@@ -23868,10 +23996,10 @@ var $ZodURL2 = /* @__PURE__ */ $constructor2("$ZodURL", (inst, def) => {
|
|
|
23868
23996
|
inst._zod.check = (payload) => {
|
|
23869
23997
|
try {
|
|
23870
23998
|
const trimmed = payload.value.trim();
|
|
23871
|
-
const
|
|
23999
|
+
const url3 = new URL(trimmed);
|
|
23872
24000
|
if (def.hostname) {
|
|
23873
24001
|
def.hostname.lastIndex = 0;
|
|
23874
|
-
if (!def.hostname.test(
|
|
24002
|
+
if (!def.hostname.test(url3.hostname)) {
|
|
23875
24003
|
payload.issues.push({
|
|
23876
24004
|
code: "invalid_format",
|
|
23877
24005
|
format: "url",
|
|
@@ -23885,7 +24013,7 @@ var $ZodURL2 = /* @__PURE__ */ $constructor2("$ZodURL", (inst, def) => {
|
|
|
23885
24013
|
}
|
|
23886
24014
|
if (def.protocol) {
|
|
23887
24015
|
def.protocol.lastIndex = 0;
|
|
23888
|
-
if (!def.protocol.test(
|
|
24016
|
+
if (!def.protocol.test(url3.protocol.endsWith(":") ? url3.protocol.slice(0, -1) : url3.protocol)) {
|
|
23889
24017
|
payload.issues.push({
|
|
23890
24018
|
code: "invalid_format",
|
|
23891
24019
|
format: "url",
|
|
@@ -23898,7 +24026,7 @@ var $ZodURL2 = /* @__PURE__ */ $constructor2("$ZodURL", (inst, def) => {
|
|
|
23898
24026
|
}
|
|
23899
24027
|
}
|
|
23900
24028
|
if (def.normalize) {
|
|
23901
|
-
payload.value =
|
|
24029
|
+
payload.value = url3.href;
|
|
23902
24030
|
} else {
|
|
23903
24031
|
payload.value = trimmed;
|
|
23904
24032
|
}
|
|
@@ -33000,7 +33128,7 @@ var ZodURL2 = /* @__PURE__ */ $constructor2("ZodURL", (inst, def) => {
|
|
|
33000
33128
|
$ZodURL2.init(inst, def);
|
|
33001
33129
|
ZodStringFormat2.init(inst, def);
|
|
33002
33130
|
});
|
|
33003
|
-
function
|
|
33131
|
+
function url3(params) {
|
|
33004
33132
|
return _url2(ZodURL2, params);
|
|
33005
33133
|
}
|
|
33006
33134
|
function httpUrl2(params) {
|
|
@@ -35994,8 +36122,15 @@ var lsp_rename = tool({
|
|
|
35994
36122
|
}
|
|
35995
36123
|
});
|
|
35996
36124
|
// src/index.ts
|
|
36125
|
+
function resolveProjectName(project, directory) {
|
|
36126
|
+
const runtimeProjectName = "name" in project && typeof project.name === "string" ? project.name : undefined;
|
|
36127
|
+
return runtimeProjectName || path8.basename(directory) || "oh-my-opencode-lite";
|
|
36128
|
+
}
|
|
35997
36129
|
var OhMyOpenCodeLite = async (ctx) => {
|
|
35998
|
-
const
|
|
36130
|
+
const { client, directory, project, worktree, $: shell, serverUrl } = ctx;
|
|
36131
|
+
const worktreeDirectory = worktree || directory;
|
|
36132
|
+
const projectName = resolveProjectName(project, directory);
|
|
36133
|
+
const config3 = loadPluginConfig(directory);
|
|
35999
36134
|
const agentDefs = createAgents(config3);
|
|
36000
36135
|
const agents = getAgentConfigs(config3);
|
|
36001
36136
|
const modelArrayMap = {};
|
|
@@ -36034,7 +36169,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
36034
36169
|
log("[plugin] initialized with tmux config", {
|
|
36035
36170
|
tmuxConfig,
|
|
36036
36171
|
rawTmuxConfig: config3.tmux,
|
|
36037
|
-
directory
|
|
36172
|
+
directory
|
|
36038
36173
|
});
|
|
36039
36174
|
try {
|
|
36040
36175
|
syncSkillsOnStartup();
|
|
@@ -36045,36 +36180,35 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
36045
36180
|
if (tmuxConfig.enabled) {
|
|
36046
36181
|
startTmuxCheck();
|
|
36047
36182
|
}
|
|
36048
|
-
const projectName = path8.basename(ctx.directory) || "oh-my-opencode-lite";
|
|
36049
36183
|
let backgroundManager;
|
|
36050
36184
|
const delegationManager = new DelegationManager({
|
|
36051
|
-
directory
|
|
36185
|
+
directory,
|
|
36186
|
+
worktreeDirectory,
|
|
36187
|
+
projectName,
|
|
36188
|
+
shell,
|
|
36052
36189
|
config: config3.delegation,
|
|
36053
36190
|
getActiveTaskIds: (rootSessionId) => backgroundManager?.getActiveTaskIds(rootSessionId) ?? []
|
|
36054
36191
|
});
|
|
36055
|
-
backgroundManager = new BackgroundTaskManager(ctx, tmuxConfig, config3, delegationManager);
|
|
36192
|
+
backgroundManager = new BackgroundTaskManager(ctx, tmuxConfig, config3, delegationManager, worktreeDirectory);
|
|
36056
36193
|
const backgroundTools = createBackgroundTools(ctx, backgroundManager, tmuxConfig, config3);
|
|
36057
36194
|
const mcps = createBuiltinMcps(config3.disabled_mcps, config3.thoth);
|
|
36058
36195
|
const tmuxSessionManager = new TmuxSessionManager(ctx, tmuxConfig);
|
|
36059
36196
|
const autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
|
|
36060
36197
|
showStartupToast: true,
|
|
36061
36198
|
autoUpdate: true
|
|
36062
|
-
});
|
|
36199
|
+
}, shell);
|
|
36063
36200
|
const phaseReminderHook = createPhaseReminderHook();
|
|
36064
|
-
const clarificationGateHook = createClarificationGateHook({
|
|
36065
|
-
clarificationGate: config3.clarificationGate
|
|
36066
|
-
});
|
|
36067
36201
|
const postReadNudgeHook = createPostReadNudgeHook();
|
|
36068
36202
|
const thothMemHook = createThothMemHook({
|
|
36069
36203
|
project: projectName,
|
|
36070
|
-
directory
|
|
36204
|
+
directory,
|
|
36071
36205
|
thoth: config3.thoth,
|
|
36072
36206
|
enabled: true
|
|
36073
36207
|
});
|
|
36074
36208
|
const chatHeadersHook = createChatHeadersHook(ctx);
|
|
36075
36209
|
const delegateTaskRetryHook = createDelegateTaskRetryHook(ctx);
|
|
36076
36210
|
const jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
|
|
36077
|
-
const foregroundFallback = new ForegroundFallbackManager(
|
|
36211
|
+
const foregroundFallback = new ForegroundFallbackManager(client, runtimeChains, config3.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
|
|
36078
36212
|
return {
|
|
36079
36213
|
name: "oh-my-opencode-lite",
|
|
36080
36214
|
agent: agents,
|
|
@@ -36183,7 +36317,6 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
36183
36317
|
},
|
|
36184
36318
|
"experimental.chat.messages.transform": async (input, output) => {
|
|
36185
36319
|
await phaseReminderHook["experimental.chat.messages.transform"](input, output);
|
|
36186
|
-
await clarificationGateHook["experimental.chat.messages.transform"](input, output);
|
|
36187
36320
|
},
|
|
36188
36321
|
"experimental.session.compacting": async (input, output) => {
|
|
36189
36322
|
if (thothMemHook["experimental.session.compacting"]) {
|