oh-my-opencode-lite 0.1.0 → 0.1.2

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/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
- <forbidden>
17424
- - no external research
17425
- - no delegation
17426
- - no background work
17427
- </forbidden>
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
- <workflow>
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
- Use the repository summary format with summary, changes, and verification sections.
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
- <allowed>
17474
- - local implementation tools
17475
- - direct UI changes
17476
- - browser-based visual verification
17477
- - focused code and style updates needed to complete the design
17478
- </allowed>
17479
-
17480
- <forbidden>
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
- - State what was implemented.
17496
- - Note visual verification status.
17497
- - Call out any remaining UX caveats.
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
- Use grep, glob, ast-grep, read, and LSP tools to find facts in the workspace quickly.
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
- <allowed>
17533
- - fast codebase search
17534
- - symbol and reference lookup
17535
- - locating files, patterns, and implementations
17536
- - repo mapping with the cartography skill when useful
17537
- </allowed>
17538
-
17539
- <forbidden>
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
- - Lead with findings.
17549
- - Every finding should reference an absolute path when possible.
17550
- - Include line numbers for code references.
17551
- - Keep conclusions short and evidence-backed.
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
- Use websearch, context7, and grep_app to gather authoritative external evidence.
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
- <allowed>
17588
- - external docs lookup
17589
- - version-sensitive API research
17590
- - public GitHub example search
17591
- - concise synthesis of sourced findings
17592
- </allowed>
17593
-
17594
- <forbidden>
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
- - Organize by finding.
17604
- - Include the source URL for each claim.
17578
+ ${RESPONSE_BUDGET}
17579
+ - Organize by finding. Include a source URL for every claim.
17605
17580
  - Distinguish official docs from community examples.
17606
- - Keep it concise and evidence-backed.
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 specific code locations.
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
- <allowed>
17642
- - read-only repository analysis
17643
- - debugging guidance
17644
- - architecture and tradeoff analysis
17645
- - code review and plan review
17646
- </allowed>
17647
-
17648
- <forbidden>
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
- - Cite the exact files and lines that support your advice.
17627
+ ${RESPONSE_BUDGET}
17628
+ - Cite exact files and lines \u2014 do not quote large code blocks.
17658
17629
  - Separate observations, risks, and recommendations.
17659
- - Be concise and decisive.
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,149 @@ 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 with task for synchronous write-capable agents and background_task for asynchronous read-only agents
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
- <delegate-first>
17690
- You are delegate-first. If a request requires repository inspection or repository mutation, delegate that work instead of doing it inline.
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
- <sdd>
17721
- You are SDD-aware. Route work by phase when applicable.
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
- - Proposal / planning / sequencing: coordinate directly or dispatch oracle for plan review.
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
- Keep orchestration lean. Sub-agent work stays isolated so your own context remains compact.
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
- <memory>
17735
- You own memory for the root session.
17736
- - Use thoth-mem tools for session recovery, prior context, decisions, and summaries.
17737
- - Child agents should not own memory state; you decide what to save.
17738
- - When delegations finish, integrate only the durable conclusions you need.
17739
- </memory>
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
- <anti-patterns>
17742
- Never do any of the following inline:
17743
- - reading files to inspect code
17744
- - editing files
17745
- - producing code patches
17746
- - running repository-wide searches to analyze code yourself
17747
- - doing architecture or debugging deep-dives that oracle should handle
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
- If you mention a specialist, dispatch that specialist in the same turn when execution is required.
17750
- </anti-patterns>
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
- <tooling>
17753
- Tool restrictions: full access is available, but your job is delegation rather than direct execution.
17754
- Use tools primarily to delegate, coordinate, and manage memory.
17755
- </tooling>
17757
+ <sdd>
17758
+ - Non-trivial work starts with requirements-interview. Skip it only for truly trivial, unambiguous work.
17759
+ - Use its result to choose direct implementation, accelerated SDD, or full SDD.
17760
+ - If persistence includes openspec and openspec/ is missing, run sdd-init first.
17761
+ - Phase order: propose -> spec -> design -> tasks -> [plan-review] -> apply -> verify -> archive.
17762
+ - Keep orchestration lean: sub-agents execute phases; you own sequencing, user gates, and progress.
17763
+ </sdd>
17764
+
17765
+ <sdd-dispatch>
17766
+ When dispatching an SDD phase, include:
17767
+ 1. Load skill \`sdd-{phase}\` and follow it exactly.
17768
+ 2. Persistence mode: thoth-mem / openspec / hybrid / none.
17769
+ 3. Change name.
17770
+ Sub-agents own phase execution. You own sequencing and progress.
17771
+ </sdd-dispatch>
17772
+
17773
+ <progress>
17774
+ - For multi-step work, maintain two layers: todowrite plus the persistent SDD artifact when SDD is active.
17775
+ - Before dispatch, MUST mark the task in progress in every active layer.
17776
+ - After each result, MUST immediately mark completed/skipped/failed in every active layer before the next dispatch.
17777
+ - Use one in-progress todo for sequential work; multiple only for truly parallel launches.
17778
+ - Keep todowrite top-level and lean. Skip it for trivial one-step work.
17779
+ </progress>
17780
+
17781
+ <memory>
17782
+ - You own root-session memory: decisions, discoveries, bug fixes, preferences, and session summaries.
17783
+ - Save durable conclusions immediately after meaningful decisions, bugs, discoveries, config changes, patterns, and user constraints.
17784
+ - Search before likely-repeat work: \`mem_context\` for broad recovery, then \`mem_search\` -> \`mem_timeline\` -> \`mem_get_observation\` for targeted recall.
17785
+ - Sub-agents may write their assigned SDD phase artifacts when the chosen mode allows it; execution-state artifacts remain orchestrator-owned.
17786
+ - End every session with \`mem_session_summary\`.
17787
+ </memory>
17756
17788
 
17757
17789
  <communication>
17790
+ - Always respond in the same language the user is speaking.
17758
17791
  - Be concise.
17759
17792
  - State the plan and delegate.
17760
17793
  - Summarize outcomes without redoing the work.
17761
- - Ask a focused question only when missing inputs block delegation.
17762
- </communication>`;
17794
+ - Distinguish evidence, inference, and uncertainty.
17795
+ - Never ask blocking questions in prose or delegate user-question handling.
17796
+ </communication>
17797
+
17798
+ ${QUESTION_PROTOCOL}`;
17763
17799
  function createOrchestratorAgent(model, customPrompt, customAppendPrompt) {
17764
17800
  const prompt = composeAgentPrompt({
17765
17801
  basePrompt: ORCHESTRATOR_PROMPT,
@@ -17771,7 +17807,9 @@ function createOrchestratorAgent(model, customPrompt, customAppendPrompt) {
17771
17807
  description: "Delegate-first coordinator for SDD workflow, specialist dispatch, and root-session memory ownership.",
17772
17808
  config: {
17773
17809
  temperature: 0.1,
17774
- prompt
17810
+ prompt,
17811
+ color: "primary",
17812
+ steps: 100
17775
17813
  }
17776
17814
  };
17777
17815
  if (Array.isArray(model)) {
@@ -17794,25 +17832,24 @@ You are quick.
17794
17832
  </mode>
17795
17833
 
17796
17834
  <responsibility>
17797
- Implement well-defined changes quickly.
17798
- Favor speed over exhaustive analysis when the task is narrow and the path is clear.
17835
+ Implement well-defined changes quickly. Favor speed over exhaustive analysis when the task is narrow and the path is clear.
17799
17836
  </responsibility>
17800
17837
 
17801
- <forbidden>
17802
- - no external research
17803
- - no delegation
17804
- - no background work
17805
- - no multi-step planning
17806
- </forbidden>
17838
+ <rules>
17839
+ ${SUBAGENT_RULES}
17840
+ - Optimize for fast execution on narrow, clear tasks.
17841
+ - Read only the context you need.
17842
+ - Avoid multi-step planning; if the task stops being bounded, surface it.
17843
+ - Ask only for implementation-local ambiguity, not orchestrator-level routing.
17844
+ </rules>
17807
17845
 
17808
- <workflow>
17809
- 1. Read only the context needed.
17810
- 2. Make the mechanical change.
17811
- 3. Run minimal verification that fits the task.
17812
- </workflow>
17846
+ ${QUESTION_PROTOCOL}
17813
17847
 
17814
17848
  <output>
17815
- Use the repository summary format with summary, changes, and verification sections.
17849
+ ${RESPONSE_BUDGET}
17850
+ For SDD tasks: use the Task Result envelope (Status, Task, What was done, Files changed, Verification, Issues).
17851
+ For non-SDD work: status + summary + files changed + issues. Nothing more.
17852
+ - Target: under 20 lines total.
17816
17853
  </output>`;
17817
17854
  function createQuickAgent(model, customPrompt, customAppendPrompt) {
17818
17855
  const prompt = composeAgentPrompt({
@@ -17826,12 +17863,96 @@ function createQuickAgent(model, customPrompt, customAppendPrompt) {
17826
17863
  config: {
17827
17864
  model,
17828
17865
  temperature: 0.2,
17829
- prompt
17866
+ prompt,
17867
+ color: "success",
17868
+ steps: 30
17830
17869
  }
17831
17870
  };
17832
17871
  }
17833
17872
 
17834
17873
  // src/agents/index.ts
17874
+ var BUILTIN_PERMISSION_PRESETS = {
17875
+ orchestrator: "allow",
17876
+ explorer: {
17877
+ read: "allow",
17878
+ glob: "allow",
17879
+ grep: "allow",
17880
+ list: "allow",
17881
+ codesearch: "allow",
17882
+ lsp: "allow",
17883
+ external_directory: "allow",
17884
+ bash: "allow",
17885
+ question: "allow",
17886
+ skill: "allow",
17887
+ edit: "deny",
17888
+ todowrite: "deny",
17889
+ task: "deny"
17890
+ },
17891
+ librarian: {
17892
+ read: "allow",
17893
+ glob: "allow",
17894
+ grep: "allow",
17895
+ external_directory: "allow",
17896
+ bash: "allow",
17897
+ webfetch: "allow",
17898
+ websearch: "allow",
17899
+ codesearch: "allow",
17900
+ question: "allow",
17901
+ skill: "allow",
17902
+ edit: "deny",
17903
+ todowrite: "deny",
17904
+ task: "deny"
17905
+ },
17906
+ oracle: {
17907
+ read: "allow",
17908
+ glob: "allow",
17909
+ grep: "allow",
17910
+ list: "allow",
17911
+ lsp: "allow",
17912
+ codesearch: "allow",
17913
+ webfetch: "allow",
17914
+ websearch: "allow",
17915
+ external_directory: "allow",
17916
+ bash: "allow",
17917
+ question: "allow",
17918
+ skill: "allow",
17919
+ edit: "deny",
17920
+ todowrite: "deny",
17921
+ task: "deny"
17922
+ },
17923
+ designer: {
17924
+ read: "allow",
17925
+ edit: "allow",
17926
+ glob: "allow",
17927
+ grep: "allow",
17928
+ list: "allow",
17929
+ bash: "allow",
17930
+ codesearch: "allow",
17931
+ lsp: "allow",
17932
+ skill: "allow",
17933
+ question: "allow",
17934
+ todowrite: "allow",
17935
+ external_directory: {
17936
+ "~/.config/opencode/skills/**": "allow"
17937
+ }
17938
+ },
17939
+ quick: {
17940
+ read: "allow",
17941
+ edit: "allow",
17942
+ glob: "allow",
17943
+ grep: "allow",
17944
+ list: "allow",
17945
+ bash: "allow",
17946
+ question: "allow",
17947
+ codesearch: "allow",
17948
+ lsp: "allow",
17949
+ todowrite: "allow",
17950
+ external_directory: {
17951
+ "~/.config/opencode/skills/**": "allow"
17952
+ }
17953
+ },
17954
+ deep: "allow"
17955
+ };
17835
17956
  function normalizeModelArray(model) {
17836
17957
  return model.map((entry) => typeof entry === "string" ? { id: entry } : entry);
17837
17958
  }
@@ -17851,11 +17972,20 @@ function applyOverrides(agent, override) {
17851
17972
  agent.config.temperature = override.temperature;
17852
17973
  }
17853
17974
  }
17854
- function applyQuestionPermission(agent) {
17855
- agent.config.permission = {
17856
- ...agent.config.permission ?? {},
17857
- question: "allow"
17858
- };
17975
+ function clonePermissionConfig(permission) {
17976
+ if (typeof permission === "string") {
17977
+ return permission;
17978
+ }
17979
+ return Object.fromEntries(Object.entries(permission).map(([key, value]) => [
17980
+ key,
17981
+ value && typeof value === "object" && !Array.isArray(value) ? { ...value } : value
17982
+ ]));
17983
+ }
17984
+ function getBuiltinPermissionPreset(name) {
17985
+ return clonePermissionConfig(BUILTIN_PERMISSION_PRESETS[name]);
17986
+ }
17987
+ function getExplicitPermissionOverride(override) {
17988
+ return override?.permission;
17859
17989
  }
17860
17990
  function isSubagent(name) {
17861
17991
  return SUBAGENT_NAMES.includes(name);
@@ -17878,7 +18008,6 @@ function createAgents(config2) {
17878
18008
  if (override) {
17879
18009
  applyOverrides(agent, override);
17880
18010
  }
17881
- applyQuestionPermission(agent);
17882
18011
  return agent;
17883
18012
  });
17884
18013
  const orchestratorOverride = getAgentOverride(config2, "orchestrator");
@@ -17887,16 +18016,19 @@ function createAgents(config2) {
17887
18016
  if (orchestratorOverride) {
17888
18017
  applyOverrides(orchestrator, orchestratorOverride);
17889
18018
  }
17890
- applyQuestionPermission(orchestrator);
17891
18019
  return [orchestrator, ...allSubAgents];
17892
18020
  }
17893
18021
  function getAgentConfigs(config2) {
17894
18022
  const agents = createAgents(config2);
17895
18023
  return Object.fromEntries(agents.map((agent) => {
18024
+ const override = getAgentOverride(config2, agent.name);
17896
18025
  const sdkConfig = {
17897
18026
  ...agent.config,
17898
18027
  description: agent.description
17899
18028
  };
18029
+ const builtinPermission = isSubagent(agent.name) ? getBuiltinPermissionPreset(agent.name) : agent.name === "orchestrator" ? getBuiltinPermissionPreset("orchestrator") : undefined;
18030
+ const explicitPermissionOverride = getExplicitPermissionOverride(override);
18031
+ sdkConfig.permission = explicitPermissionOverride ?? agent.config.permission ?? builtinPermission;
17900
18032
  if (isSubagent(agent.name)) {
17901
18033
  sdkConfig.mode = "subagent";
17902
18034
  } else if (agent.name === "orchestrator") {
@@ -18300,6 +18432,11 @@ async function extractZip(archivePath, destDir) {
18300
18432
  // src/background/background-manager.ts
18301
18433
  var BACKGROUND_CAPABLE_AGENTS = ["explorer", "librarian"];
18302
18434
  var DEFAULT_DELEGATION_SUMMARY_LIMIT = 5;
18435
+ var ANY_PERMISSION_PATTERN = "*";
18436
+ var TASK_PERMISSION = "task";
18437
+ var BACKGROUND_TASK_PERMISSION = "background_task";
18438
+ var BACKGROUND_OUTPUT_PERMISSION = "background_output";
18439
+ var BACKGROUND_CANCEL_PERMISSION = "background_cancel";
18303
18440
  function generateFallbackTaskId() {
18304
18441
  return `bg_${Math.random().toString(36).substring(2, 10)}`;
18305
18442
  }
@@ -18336,6 +18473,7 @@ class BackgroundTaskManager {
18336
18473
  agentBySessionId = new Map;
18337
18474
  client;
18338
18475
  directory;
18476
+ worktreeDirectory;
18339
18477
  tmuxEnabled;
18340
18478
  config;
18341
18479
  backgroundConfig;
@@ -18344,9 +18482,10 @@ class BackgroundTaskManager {
18344
18482
  activeStarts = 0;
18345
18483
  maxConcurrentStarts;
18346
18484
  completionResolvers = new Map;
18347
- constructor(ctx, tmuxConfig, config2, delegationManager) {
18485
+ constructor(ctx, tmuxConfig, config2, delegationManager, worktreeDirectory) {
18348
18486
  this.client = ctx.client;
18349
18487
  this.directory = ctx.directory;
18488
+ this.worktreeDirectory = worktreeDirectory ?? ctx.directory;
18350
18489
  this.tmuxEnabled = tmuxConfig?.enabled ?? false;
18351
18490
  this.config = config2;
18352
18491
  this.delegationManager = delegationManager;
@@ -18548,12 +18687,70 @@ class BackgroundTaskManager {
18548
18687
  clearTimeout(timer);
18549
18688
  }
18550
18689
  }
18551
- calculateToolPermissions(agentName) {
18552
- const allowedSubagents = this.getSubagentRules(agentName);
18553
- if (allowedSubagents.length === 0) {
18554
- return { background_task: false, task: false };
18690
+ createPatternPermission(allowedPatterns) {
18691
+ if (allowedPatterns.length === 0) {
18692
+ return "deny";
18693
+ }
18694
+ const permission = Object.fromEntries(allowedPatterns.map((pattern) => [pattern, "allow"]));
18695
+ permission[ANY_PERMISSION_PATTERN] = "deny";
18696
+ return permission;
18697
+ }
18698
+ createPatternRuleset(permission, allowedPatterns) {
18699
+ if (allowedPatterns.length === 0) {
18700
+ return [
18701
+ {
18702
+ permission,
18703
+ pattern: ANY_PERMISSION_PATTERN,
18704
+ action: "deny"
18705
+ }
18706
+ ];
18555
18707
  }
18556
- return { background_task: true, task: true };
18708
+ return [
18709
+ ...allowedPatterns.map((pattern) => ({
18710
+ permission,
18711
+ pattern,
18712
+ action: "allow"
18713
+ })),
18714
+ {
18715
+ permission,
18716
+ pattern: ANY_PERMISSION_PATTERN,
18717
+ action: "deny"
18718
+ }
18719
+ ];
18720
+ }
18721
+ calculateDelegationPermissions(agentName) {
18722
+ const allowedSubagents = this.getSubagentRules(agentName);
18723
+ const allowedBackgroundSubagents = allowedSubagents.filter((subagent) => this.isBackgroundCapableAgent(subagent));
18724
+ const canManageBackgroundTasks = allowedBackgroundSubagents.length > 0;
18725
+ const permission = {
18726
+ [TASK_PERMISSION]: this.createPatternPermission(allowedSubagents),
18727
+ [BACKGROUND_TASK_PERMISSION]: this.createPatternPermission(allowedBackgroundSubagents),
18728
+ [BACKGROUND_OUTPUT_PERMISSION]: canManageBackgroundTasks ? "allow" : "deny",
18729
+ [BACKGROUND_CANCEL_PERMISSION]: canManageBackgroundTasks ? "allow" : "deny"
18730
+ };
18731
+ return {
18732
+ permission,
18733
+ ruleset: [
18734
+ ...this.createPatternRuleset(TASK_PERMISSION, allowedSubagents),
18735
+ ...this.createPatternRuleset(BACKGROUND_TASK_PERMISSION, allowedBackgroundSubagents),
18736
+ {
18737
+ permission: BACKGROUND_OUTPUT_PERMISSION,
18738
+ pattern: ANY_PERMISSION_PATTERN,
18739
+ action: canManageBackgroundTasks ? "allow" : "deny"
18740
+ },
18741
+ {
18742
+ permission: BACKGROUND_CANCEL_PERMISSION,
18743
+ pattern: ANY_PERMISSION_PATTERN,
18744
+ action: canManageBackgroundTasks ? "allow" : "deny"
18745
+ }
18746
+ ],
18747
+ legacyTools: {
18748
+ [TASK_PERMISSION]: allowedSubagents.length > 0,
18749
+ [BACKGROUND_TASK_PERMISSION]: canManageBackgroundTasks,
18750
+ [BACKGROUND_OUTPUT_PERMISSION]: canManageBackgroundTasks,
18751
+ [BACKGROUND_CANCEL_PERMISSION]: canManageBackgroundTasks
18752
+ }
18753
+ };
18557
18754
  }
18558
18755
  async startTask(task) {
18559
18756
  task.status = "starting";
@@ -18563,12 +18760,14 @@ class BackgroundTaskManager {
18563
18760
  return;
18564
18761
  }
18565
18762
  try {
18763
+ const delegationPermissions = this.calculateDelegationPermissions(task.agent);
18566
18764
  const session = await this.client.session.create({
18567
18765
  body: {
18568
18766
  parentID: task.parentSessionId,
18569
- title: `Background: ${task.description}`
18767
+ title: `Background: ${task.description}`,
18768
+ permission: delegationPermissions.ruleset
18570
18769
  },
18571
- query: { directory: this.directory }
18770
+ query: { directory: this.worktreeDirectory }
18572
18771
  });
18573
18772
  if (!session.data?.id) {
18574
18773
  throw new Error("Failed to create background session");
@@ -18580,12 +18779,14 @@ class BackgroundTaskManager {
18580
18779
  if (this.tmuxEnabled) {
18581
18780
  await new Promise((r) => setTimeout(r, 500));
18582
18781
  }
18583
- const toolPermissions = this.calculateToolPermissions(task.agent);
18584
- const promptQuery = { directory: this.directory };
18782
+ const promptQuery = {
18783
+ directory: this.worktreeDirectory
18784
+ };
18585
18785
  const resolvedVariant = resolveAgentVariant(this.config, task.agent);
18586
18786
  const basePromptBody = applyAgentVariant(resolvedVariant, {
18587
18787
  agent: task.agent,
18588
- tools: toolPermissions,
18788
+ permission: delegationPermissions.permission,
18789
+ tools: delegationPermissions.legacyTools,
18589
18790
  parts: [{ type: "text", text: task.prompt }]
18590
18791
  });
18591
18792
  const fallbackEnabled = this.config?.fallback?.enabled ?? true;
@@ -19145,59 +19346,70 @@ function sanitizeProjectName(value) {
19145
19346
  const normalized = value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-");
19146
19347
  return normalized.length > 0 ? normalized : "project";
19147
19348
  }
19148
- async function runGitCommand(directory, args, timeoutMs) {
19149
- const subprocess = Bun.spawn({
19150
- cmd: ["git", ...args],
19151
- cwd: directory,
19152
- stdout: "pipe",
19153
- stderr: "pipe"
19154
- });
19349
+ async function runGitCommand(shell, worktreeDirectory, args, timeoutMs) {
19350
+ if (!shell) {
19351
+ return null;
19352
+ }
19353
+ const command = shell.nothrow().cwd(worktreeDirectory)`git ${args}`.quiet();
19155
19354
  let timer;
19156
19355
  try {
19157
19356
  const result = await Promise.race([
19158
- (async () => {
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
- })(),
19357
+ command,
19169
19358
  new Promise((resolve) => {
19170
- timer = setTimeout(() => {
19171
- subprocess.kill();
19172
- resolve(null);
19173
- }, timeoutMs);
19359
+ timer = setTimeout(() => resolve(null), timeoutMs);
19174
19360
  })
19175
19361
  ]);
19176
- return result;
19362
+ if (result === null || result.exitCode !== 0) {
19363
+ return null;
19364
+ }
19365
+ const firstLine = result.text().trim().split(/\r?\n/, 1)[0];
19366
+ return firstLine.length > 0 ? firstLine : null;
19177
19367
  } catch {
19178
19368
  return null;
19179
19369
  } finally {
19180
- clearTimeout(timer);
19370
+ if (timer) {
19371
+ clearTimeout(timer);
19372
+ }
19181
19373
  }
19182
19374
  }
19183
- function buildProjectId(baseDirectory, hash2) {
19184
- const projectName = sanitizeProjectName(path3.basename(baseDirectory));
19185
- return `${projectName}-${hash2.slice(0, 12)}`;
19375
+ function buildProjectId(projectName, hash2) {
19376
+ const normalizedProjectName = sanitizeProjectName(projectName);
19377
+ return `${normalizedProjectName}-${hash2.slice(0, 12)}`;
19186
19378
  }
19187
- async function getProjectId(directory, timeoutMs = 5000) {
19188
- const normalizedDirectory = directory.trim();
19379
+ function normalizeProjectIdOptions(input, timeoutMs, projectName, shell) {
19380
+ if (typeof input === "string") {
19381
+ return {
19382
+ worktreeDirectory: input,
19383
+ projectName: projectName || path3.basename(input) || "project",
19384
+ shell,
19385
+ timeoutMs
19386
+ };
19387
+ }
19388
+ return {
19389
+ worktreeDirectory: input.worktreeDirectory,
19390
+ projectName: input.projectName || path3.basename(input.worktreeDirectory),
19391
+ shell: input.shell,
19392
+ timeoutMs: input.timeoutMs ?? timeoutMs
19393
+ };
19394
+ }
19395
+ async function getProjectId(input, timeoutMs = 5000, projectName, shell) {
19396
+ const resolvedProjectName = typeof input === "string" ? projectName : undefined;
19397
+ const resolvedShell = typeof input === "string" ? shell : undefined;
19398
+ const options = normalizeProjectIdOptions(input, timeoutMs, resolvedProjectName, resolvedShell);
19399
+ const normalizedDirectory = options.worktreeDirectory.trim();
19400
+ const normalizedProjectName = sanitizeProjectName(options.projectName);
19189
19401
  if (normalizedDirectory.length === 0) {
19190
19402
  return null;
19191
19403
  }
19192
- const repoRoot = await runGitCommand(normalizedDirectory, ["rev-parse", "--show-toplevel"], timeoutMs);
19193
- const rootCommit = await runGitCommand(normalizedDirectory, ["rev-list", "--max-parents=0", "HEAD"], timeoutMs);
19404
+ const repoRoot = await runGitCommand(options.shell, normalizedDirectory, ["rev-parse", "--show-toplevel"], options.timeoutMs);
19405
+ const rootCommit = await runGitCommand(options.shell, normalizedDirectory, ["rev-list", "--max-parents=0", "HEAD"], options.timeoutMs);
19194
19406
  if (repoRoot && rootCommit) {
19195
- return buildProjectId(repoRoot, rootCommit);
19407
+ return buildProjectId(normalizedProjectName, rootCommit);
19196
19408
  }
19197
19409
  try {
19198
19410
  const resolvedDirectory = path3.resolve(normalizedDirectory);
19199
19411
  const hash2 = createHash("sha256").update(resolvedDirectory).digest("hex");
19200
- return buildProjectId(resolvedDirectory, hash2);
19412
+ return buildProjectId(normalizedProjectName, hash2);
19201
19413
  } catch {
19202
19414
  return null;
19203
19415
  }
@@ -19341,16 +19553,20 @@ function sortByCompletionDesc(left, right) {
19341
19553
  }
19342
19554
 
19343
19555
  class DelegationManager {
19344
- directory;
19556
+ worktreeDirectory;
19557
+ projectName;
19558
+ shell;
19345
19559
  config;
19346
19560
  getActiveTaskIds;
19347
19561
  constructor(options) {
19348
- this.directory = options.directory;
19562
+ this.worktreeDirectory = options.worktreeDirectory ?? options.directory;
19563
+ this.projectName = options.projectName || path4.basename(this.worktreeDirectory) || path4.basename(options.directory) || "project";
19564
+ this.shell = options.shell;
19349
19565
  this.config = options.config;
19350
19566
  this.getActiveTaskIds = options.getActiveTaskIds;
19351
19567
  }
19352
- async resolveProjectId(directory = this.directory) {
19353
- return getProjectId(directory, this.config?.timeout);
19568
+ async resolveProjectId(worktreeDirectory = this.worktreeDirectory) {
19569
+ return getProjectId(worktreeDirectory, this.config?.timeout, this.projectName, this.shell);
19354
19570
  }
19355
19571
  async createTaskId(rootSessionId) {
19356
19572
  const reservedTaskIds = await this.getReservedTaskIds(rootSessionId);
@@ -19805,7 +20021,7 @@ async function getLatestVersion(channel = "latest") {
19805
20021
  }
19806
20022
 
19807
20023
  // src/hooks/auto-update-checker/index.ts
19808
- function createAutoUpdateCheckerHook(ctx, options = {}) {
20024
+ function createAutoUpdateCheckerHook(ctx, options = {}, shell) {
19809
20025
  const { showStartupToast = true, autoUpdate = true } = options;
19810
20026
  let hasChecked = false;
19811
20027
  return {
@@ -19832,14 +20048,14 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
19832
20048
  if (showStartupToast) {
19833
20049
  showToast(ctx, `OMO-Lite ${displayVersion ?? "unknown"}`, "oh-my-opencode-lite is active.", "info");
19834
20050
  }
19835
- runBackgroundUpdateCheck(ctx, autoUpdate).catch((err) => {
20051
+ runBackgroundUpdateCheck(ctx, shell, autoUpdate).catch((err) => {
19836
20052
  log("[auto-update-checker] Background update check failed:", err);
19837
20053
  });
19838
20054
  }, 0);
19839
20055
  }
19840
20056
  };
19841
20057
  }
19842
- async function runBackgroundUpdateCheck(ctx, autoUpdate) {
20058
+ async function runBackgroundUpdateCheck(ctx, shell, autoUpdate) {
19843
20059
  const pluginInfo = findPluginEntry(ctx.directory);
19844
20060
  if (!pluginInfo) {
19845
20061
  log("[auto-update-checker] Plugin not found in config");
@@ -19877,7 +20093,7 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate) {
19877
20093
  log(`[auto-update-checker] Config updated: ${pluginInfo.entry} \u2192 ${PACKAGE_NAME}@${latestVersion}`);
19878
20094
  }
19879
20095
  invalidatePackage(PACKAGE_NAME);
19880
- const installSuccess = await runBunInstallSafe(ctx);
20096
+ const installSuccess = await runBunInstallSafe(ctx, shell);
19881
20097
  if (installSuccess) {
19882
20098
  showToast(ctx, "OMO-Lite Updated!", `v${currentVersion} \u2192 v${latestVersion}
19883
20099
  Restart OpenCode to apply.`, "success", 8000);
@@ -19887,23 +20103,23 @@ Restart OpenCode to apply.`, "success", 8000);
19887
20103
  log("[auto-update-checker] bun install failed; update not installed");
19888
20104
  }
19889
20105
  }
19890
- async function runBunInstallSafe(ctx) {
20106
+ async function runBunInstallSafe(ctx, shell) {
19891
20107
  try {
19892
- const proc = Bun.spawn(["bun", "install"], {
19893
- cwd: ctx.directory,
19894
- stdout: "pipe",
19895
- stderr: "pipe"
19896
- });
19897
20108
  const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve("timeout"), 60000));
19898
- const exitPromise = proc.exited.then(() => "completed");
19899
- const result = await Promise.race([exitPromise, timeoutPromise]);
19900
- if (result === "timeout") {
20109
+ const installPromise = (async () => {
19901
20110
  try {
19902
- proc.kill();
19903
- } catch {}
20111
+ await shell`cd ${ctx.directory} && bun install`;
20112
+ return "completed";
20113
+ } catch {
20114
+ return "failed";
20115
+ }
20116
+ })();
20117
+ const result = await Promise.race([installPromise, timeoutPromise]);
20118
+ if (result === "timeout") {
20119
+ log("[auto-update-checker] bun install timed out after 60 seconds");
19904
20120
  return false;
19905
20121
  }
19906
- return proc.exitCode === 0;
20122
+ return result === "completed";
19907
20123
  } catch (err) {
19908
20124
  log("[auto-update-checker] bun install error:", err);
19909
20125
  return false;
@@ -19964,236 +20180,6 @@ function createChatHeadersHook(ctx) {
19964
20180
  }
19965
20181
  };
19966
20182
  }
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
20183
  // src/hooks/delegate-task-retry/patterns.ts
20198
20184
  var DELEGATE_TASK_ERROR_PATTERNS = [
20199
20185
  {
@@ -20471,10 +20457,12 @@ class ForegroundFallbackManager {
20471
20457
  await this.client.session.abort({ path: { id: sessionID } });
20472
20458
  } catch {}
20473
20459
  await new Promise((r2) => setTimeout(r2, 500));
20474
- const sessionClient = this.client.session;
20475
- await sessionClient.promptAsync({
20460
+ await this.client.session.promptAsync({
20476
20461
  path: { id: sessionID },
20477
- body: { parts: lastUser.parts, model: ref }
20462
+ body: {
20463
+ parts: lastUser.parts,
20464
+ model: ref
20465
+ }
20478
20466
  });
20479
20467
  this.sessionModel.set(sessionID, nextModel);
20480
20468
  log("[foreground-fallback] switched to fallback model", {
@@ -20569,7 +20557,7 @@ ${JSON_ERROR_REMINDER}`;
20569
20557
  // src/hooks/phase-reminder/index.ts
20570
20558
  var PHASE_REMINDER = `<reminder>Recall Workflow Rules:
20571
20559
  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>`;
20560
+ 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
20561
  function createPhaseReminderHook() {
20574
20562
  return {
20575
20563
  "experimental.chat.messages.transform": async (_input, output) => {
@@ -20612,7 +20600,7 @@ ${originalText}`;
20612
20600
  var NUDGE = `
20613
20601
 
20614
20602
  ---
20615
- Workflow Reminder: delegate based on rules; If mentioning a specialist, launch it in this same turn.`;
20603
+ 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
20604
  function createPostReadNudgeHook() {
20617
20605
  return {
20618
20606
  "tool.execute.after": async (input, output) => {
@@ -20769,10 +20757,10 @@ var SHARED_SKILL_DIRECTORY2 = "_shared";
20769
20757
  var SHARED_SKILL_SOURCE_PATH = `src/skills/${SHARED_SKILL_DIRECTORY2}`;
20770
20758
  var CUSTOM_SKILLS = [
20771
20759
  {
20772
- name: "brainstorming",
20773
- description: "Understand user intent and scope through structured clarification before implementation",
20760
+ name: "requirements-interview",
20761
+ description: "Mandatory step-0 discovery interview to understand user intent, clarify scope, and choose the right path before implementation",
20774
20762
  allowedAgents: ["orchestrator"],
20775
- sourcePath: "src/skills/brainstorming"
20763
+ sourcePath: "src/skills/requirements-interview"
20776
20764
  },
20777
20765
  {
20778
20766
  name: "cartography",
@@ -20786,6 +20774,12 @@ var CUSTOM_SKILLS = [
20786
20774
  allowedAgents: ["orchestrator", "oracle"],
20787
20775
  sourcePath: "src/skills/plan-reviewer"
20788
20776
  },
20777
+ {
20778
+ name: "sdd-init",
20779
+ description: "Initialize OpenSpec structure and SDD project context",
20780
+ allowedAgents: ["orchestrator"],
20781
+ sourcePath: "src/skills/sdd-init"
20782
+ },
20789
20783
  {
20790
20784
  name: "sdd-propose",
20791
20785
  description: "Create change proposals for OpenSpec workflows",
@@ -21069,11 +21063,11 @@ var DEFAULT_THOTH_TIMEOUT_MS = 15000;
21069
21063
  function isRecord2(value) {
21070
21064
  return typeof value === "object" && value !== null;
21071
21065
  }
21072
- function normalizeText2(value) {
21066
+ function normalizeText(value) {
21073
21067
  return typeof value === "string" ? value.trim() || null : null;
21074
21068
  }
21075
21069
  function previewText(value, max) {
21076
- const text = normalizeText2(value);
21070
+ const text = normalizeText(value);
21077
21071
  if (!text) {
21078
21072
  return null;
21079
21073
  }
@@ -21093,9 +21087,9 @@ function formatMemoryContext(response) {
21093
21087
  if (sessions.length > 0) {
21094
21088
  lines.push("### Recent Sessions");
21095
21089
  for (const rawSession of sessions) {
21096
- const sessionId = normalizeText2(rawSession.id) ?? "unknown-session";
21097
- const project = normalizeText2(rawSession.project) ?? "unknown-project";
21098
- const startedAt = normalizeText2(rawSession.started_at) ?? "unknown";
21090
+ const sessionId = normalizeText(rawSession.id) ?? "unknown-session";
21091
+ const project = normalizeText(rawSession.project) ?? "unknown-project";
21092
+ const startedAt = normalizeText(rawSession.started_at) ?? "unknown";
21099
21093
  lines.push(`- [${sessionId}] (${project}) \u2014 started ${startedAt}`);
21100
21094
  const summary = previewText(rawSession.summary, 300);
21101
21095
  if (summary) {
@@ -21107,9 +21101,9 @@ function formatMemoryContext(response) {
21107
21101
  if (observations.length > 0) {
21108
21102
  lines.push("### Recent Observations");
21109
21103
  for (const rawObservation of observations) {
21110
- const type = normalizeText2(rawObservation.type) ?? "manual";
21111
- const title = normalizeText2(rawObservation.title) ?? "Untitled";
21112
- const createdAt = normalizeText2(rawObservation.created_at) ?? "unknown";
21104
+ const type = normalizeText(rawObservation.type) ?? "manual";
21105
+ const title = normalizeText(rawObservation.title) ?? "Untitled";
21106
+ const createdAt = normalizeText(rawObservation.created_at) ?? "unknown";
21113
21107
  lines.push(`- [${type}] ${title} (${createdAt})`);
21114
21108
  const content = previewText(rawObservation.content, 300);
21115
21109
  if (content) {
@@ -21125,7 +21119,7 @@ function formatMemoryContext(response) {
21125
21119
  if (!content) {
21126
21120
  continue;
21127
21121
  }
21128
- const createdAt = normalizeText2(rawPrompt.created_at) ?? "unknown";
21122
+ const createdAt = normalizeText(rawPrompt.created_at) ?? "unknown";
21129
21123
  lines.push(`- ${content} (${createdAt})`);
21130
21124
  }
21131
21125
  lines.push("");
@@ -21229,7 +21223,7 @@ function createThothClient(options) {
21229
21223
  }
21230
21224
  // src/hooks/thoth-mem/protocol.ts
21231
21225
  var SDD_TOPIC_KEY_FORMAT = "sdd/{change}/{artifact}";
21232
- 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 to recover additional context from previous sessions.";
21226
+ 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
21227
  var SESSION_SUMMARY_TEMPLATE = `Use this exact structure for \`mem_session_summary\` content:
21234
21228
 
21235
21229
  ## Goal
@@ -21250,7 +21244,7 @@ var SESSION_SUMMARY_TEMPLATE = `Use this exact structure for \`mem_session_summa
21250
21244
  ## Relevant Files
21251
21245
  - path/to/file.ts - [what it does or what changed]`;
21252
21246
  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:\`, and call \`mem_context\` if you need to restore recent memory.`;
21247
+ 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
21248
  }
21255
21249
  function buildCompactorInstruction(project) {
21256
21250
  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."`;
@@ -21261,29 +21255,52 @@ function buildMemoryInstructions(sessionID, project) {
21261
21255
  Persistent memory is available through thoth-mem. Follow this protocol.
21262
21256
 
21263
21257
  IMPORTANT: Your current session_id is \`${sessionID}\` and project is \`${project}\`.
21264
- Always pass these values when calling memory tools that accept them (mem_session_summary, mem_save, mem_save_prompt, mem_capture_passive, etc.).
21265
-
21266
- WHEN TO SAVE
21267
- - Call \`mem_save\` IMMEDIATELY after bug fixes, architecture decisions, discoveries, config changes, reusable patterns, and user preferences.
21268
- - Use \`title\` as Verb + what changed or was learned.
21269
- - Use \`type\` from: bugfix | decision | architecture | discovery | pattern | config | learning | manual.
21270
- - Set \`scope\` intentionally.
21271
- - Reuse \`topic_key\` for the same evolving topic. Do not overwrite unrelated topics.
21272
- - If unsure about a stable \`topic_key\`, call \`mem_suggest_topic_key\` first.
21273
- - If you need to modify a known observation by exact ID, call \`mem_update\` instead of creating a new record.
21274
- - Put the durable details in \`content\` with this structure:
21258
+ Always pass these values when calling memory tools that accept them (mem_session_summary, mem_save, mem_capture_passive, etc.).
21259
+
21260
+ ### CORE TOOLS
21261
+ 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
21262
+
21263
+ ### WHEN TO SAVE
21264
+ Call \`mem_save\` IMMEDIATELY after ANY of these:
21265
+ - Architecture, design, or workflow decision made
21266
+ - Bug fixed (include root cause)
21267
+ - Non-obvious discovery, gotcha, or edge case found
21268
+ - Configuration change or environment setup
21269
+ - Pattern or convention established (naming, structure, approach)
21270
+ - User preference or constraint learned
21271
+ - Feature implemented with non-obvious approach
21272
+ - User confirms a recommendation ("dale", "go with that", "sounds good", "s\xED, esa")
21273
+ - User rejects an approach or expresses a preference ("no, better X", "I prefer X")
21274
+ - Discussion concludes with a clear direction chosen
21275
+
21276
+ Use \`title\` as Verb + what changed or was learned.
21277
+ Use \`type\` from: bugfix | decision | architecture | discovery | pattern | config | learning | manual.
21278
+ Set \`scope\` intentionally.
21279
+ Reuse \`topic_key\` for the same evolving topic. Do not overwrite unrelated topics.
21280
+ If unsure about a stable \`topic_key\`, call \`mem_suggest_topic_key\` first.
21281
+ If you need to modify a known observation by exact ID, call \`mem_update\` instead of creating a new record.
21282
+ Put the durable details in \`content\` with this structure:
21275
21283
  - What: concise description of what changed or was learned
21276
21284
  - Why: why it mattered or what problem it solved
21277
21285
  - Where: files, paths, or systems involved
21278
21286
  - Learned: edge cases, caveats, or follow-up notes
21279
21287
 
21280
- WHEN TO SEARCH MEMORY
21281
- - If the user asks to recall prior work, call \`mem_context\` first, then \`mem_search\`, then \`mem_get_observation\` for exact records you need.
21288
+ **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."
21289
+
21290
+ You can also call \`mem_save_prompt\` to manually save a user prompt that you consider particularly important for future context.
21291
+
21292
+ ### WHEN TO SEARCH MEMORY
21293
+ - Broad recovery (session start, after compaction): call \`mem_context\` for a recent-session overview.
21294
+ - Targeted 3-layer recall (specific memory retrieval):
21295
+ 1. Call \`mem_search\` with \`mode: "compact"\` (default) to scan the compact index of IDs + titles.
21296
+ 2. Call \`mem_timeline\` around promising observation IDs for chronological context within the same session.
21297
+ 3. Call \`mem_get_observation\` only for observations you need in full.
21298
+ - Use \`mode: "preview"\` with \`mem_search\` only when compact results are insufficient to disambiguate.
21282
21299
  - Search proactively on the first message about a project, feature, or problem when prior context may matter.
21283
21300
  - Search before starting work that may have been done before.
21284
21301
  - Search when the user mentions a topic that lacks enough local context.
21285
21302
 
21286
- SESSION CLOSE PROTOCOL
21303
+ ### SESSION CLOSE PROTOCOL
21287
21304
  - Before ending the session, call \`mem_session_summary\` with this exact template.
21288
21305
  - This is NOT optional. If you skip this, the next session starts blind.
21289
21306
  - Do not claim memory was saved unless the tool call succeeded.
@@ -21291,17 +21308,21 @@ SESSION CLOSE PROTOCOL
21291
21308
 
21292
21309
  ${SESSION_SUMMARY_TEMPLATE}
21293
21310
 
21294
- AFTER COMPACTION
21311
+ ### AFTER COMPACTION
21295
21312
  - IMMEDIATELY call \`mem_session_summary\` with the compacted summary content.
21296
- - Then call \`mem_context\`.
21313
+ - Then call \`mem_context\` for a recent-session overview.
21314
+ - Use the 3-layer recall protocol (\`mem_search\` with \`mode: "compact"\` -> \`mem_timeline\` -> \`mem_get_observation\`) for precise artifact/prior-observation retrieval.
21297
21315
  - Only then continue working.
21298
21316
 
21299
- SDD topic_key convention:
21317
+ ### SDD TOPIC KEY CONVENTION
21300
21318
  - use ${SDD_TOPIC_KEY_FORMAT}
21301
21319
  - examples: sdd/add-user-auth/spec, sdd/add-user-auth/design, sdd/add-user-auth/tasks
21302
21320
  </memory_protocol>
21303
21321
  `.trim();
21304
21322
  }
21323
+ function buildSaveNudge() {
21324
+ 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.";
21325
+ }
21305
21326
 
21306
21327
  // src/hooks/thoth-mem/index.ts
21307
21328
  function isRootSession(session) {
@@ -21347,6 +21368,10 @@ function isSessionSummaryTool(toolName) {
21347
21368
  const normalized = toolName.toLowerCase();
21348
21369
  return normalized === "mem_session_summary" || normalized.endsWith(".mem_session_summary") || normalized.endsWith("_mem_session_summary");
21349
21370
  }
21371
+ function isMemSaveTool(toolName) {
21372
+ const normalized = toolName.toLowerCase();
21373
+ return normalized === "mem_save" || normalized.endsWith(".mem_save") || normalized.endsWith("_mem_save");
21374
+ }
21350
21375
  function createThothMemHook(options) {
21351
21376
  const enabled = options.enabled !== false;
21352
21377
  const thoth = createThothClient({
@@ -21359,6 +21384,9 @@ function createThothMemHook(options) {
21359
21384
  const trackedRootSessions = new Set;
21360
21385
  const needsCompactionFollowUp = new Set;
21361
21386
  const ensuredRootSessions = new Map;
21387
+ const sessionCreatedAt = new Map;
21388
+ const lastMemSaveAt = new Map;
21389
+ const nudgePending = new Set;
21362
21390
  async function ensureRootSession(sessionId) {
21363
21391
  if (!enabled || trackedRootSessions.has(sessionId)) {
21364
21392
  return;
@@ -21369,20 +21397,47 @@ function createThothMemHook(options) {
21369
21397
  return;
21370
21398
  }
21371
21399
  const pending = (async () => {
21372
- await thoth.memSessionStart(sessionId);
21373
- trackedRootSessions.add(sessionId);
21400
+ const started = await thoth.memSessionStart(sessionId);
21401
+ if (started) {
21402
+ trackedRootSessions.add(sessionId);
21403
+ } else {
21404
+ log("[thoth] session start unavailable, skipping tracking:", sessionId);
21405
+ }
21374
21406
  })().finally(() => {
21375
21407
  ensuredRootSessions.delete(sessionId);
21376
21408
  });
21377
21409
  ensuredRootSessions.set(sessionId, pending);
21378
21410
  await pending;
21379
21411
  }
21412
+ function shouldInjectSaveNudge(sessionId) {
21413
+ if (nudgePending.has(sessionId)) {
21414
+ return false;
21415
+ }
21416
+ if (needsCompactionFollowUp.has(sessionId)) {
21417
+ return false;
21418
+ }
21419
+ const createdAt = sessionCreatedAt.get(sessionId);
21420
+ if (!createdAt || Date.now() - createdAt < 5 * 60 * 1000) {
21421
+ return false;
21422
+ }
21423
+ const lastSave = lastMemSaveAt.get(sessionId);
21424
+ if (lastSave && Date.now() - lastSave < 15 * 60 * 1000) {
21425
+ return false;
21426
+ }
21427
+ nudgePending.add(sessionId);
21428
+ return true;
21429
+ }
21380
21430
  async function handleSessionCreated(session) {
21381
21431
  if (!enabled || !isRootSession(session)) {
21382
21432
  return;
21383
21433
  }
21384
- trackedRootSessions.add(session.id);
21385
- await thoth.memSessionStart(session.id);
21434
+ const started = await thoth.memSessionStart(session.id);
21435
+ if (started) {
21436
+ trackedRootSessions.add(session.id);
21437
+ sessionCreatedAt.set(session.id, Date.now());
21438
+ } else {
21439
+ log("[thoth] session start unavailable, skipping tracking:", session.id);
21440
+ }
21386
21441
  }
21387
21442
  function handleSessionCompacted(session) {
21388
21443
  if (!enabled || !isRootSession(session) || !session.id) {
@@ -21394,6 +21449,9 @@ function createThothMemHook(options) {
21394
21449
  trackedRootSessions.delete(session.id);
21395
21450
  needsCompactionFollowUp.delete(session.id);
21396
21451
  ensuredRootSessions.delete(session.id);
21452
+ sessionCreatedAt.delete(session.id);
21453
+ lastMemSaveAt.delete(session.id);
21454
+ nudgePending.delete(session.id);
21397
21455
  }
21398
21456
  return {
21399
21457
  event: async ({ event }) => {
@@ -21435,7 +21493,7 @@ function createThothMemHook(options) {
21435
21493
  return;
21436
21494
  }
21437
21495
  const sanitizedPromptText = sanitizePromptText(promptText);
21438
- if (!sanitizedPromptText) {
21496
+ if (!sanitizedPromptText || sanitizedPromptText.length <= 10) {
21439
21497
  return;
21440
21498
  }
21441
21499
  await thoth.memSavePrompt(input.sessionID, sanitizedPromptText);
@@ -21450,8 +21508,15 @@ function createThothMemHook(options) {
21450
21508
  }
21451
21509
  const memoryInstructions = buildMemoryInstructions(input.sessionID, options.project);
21452
21510
  const compactionReminder = needsCompactionFollowUp.has(input.sessionID) ? buildCompactionReminder(input.sessionID) : null;
21511
+ const saveNudge = shouldInjectSaveNudge(input.sessionID) ? buildSaveNudge() : null;
21453
21512
  if (output.system.length === 0) {
21454
- const systemPrompt = compactionReminder ? appendInstruction(memoryInstructions, compactionReminder) : memoryInstructions;
21513
+ let systemPrompt = memoryInstructions;
21514
+ if (compactionReminder) {
21515
+ systemPrompt = appendInstruction(systemPrompt, compactionReminder);
21516
+ }
21517
+ if (saveNudge) {
21518
+ systemPrompt = appendInstruction(systemPrompt, saveNudge);
21519
+ }
21455
21520
  output.system.push(systemPrompt);
21456
21521
  return;
21457
21522
  }
@@ -21460,6 +21525,9 @@ function createThothMemHook(options) {
21460
21525
  if (compactionReminder) {
21461
21526
  updatedSystemPrompt = appendInstruction(updatedSystemPrompt, compactionReminder);
21462
21527
  }
21528
+ if (saveNudge) {
21529
+ updatedSystemPrompt = appendInstruction(updatedSystemPrompt, saveNudge);
21530
+ }
21463
21531
  output.system[lastIndex] = updatedSystemPrompt;
21464
21532
  },
21465
21533
  "experimental.session.compacting": async (input, output) => {
@@ -21485,6 +21553,10 @@ function createThothMemHook(options) {
21485
21553
  if (!enabled) {
21486
21554
  return;
21487
21555
  }
21556
+ if (isMemSaveTool(input.tool)) {
21557
+ lastMemSaveAt.set(input.sessionID, Date.now());
21558
+ nudgePending.delete(input.sessionID);
21559
+ }
21488
21560
  if (isSessionSummaryTool(input.tool)) {
21489
21561
  needsCompactionFollowUp.delete(input.sessionID);
21490
21562
  }
@@ -21523,10 +21595,11 @@ function createThothMcp(config2) {
21523
21595
  }
21524
21596
 
21525
21597
  // src/mcp/websearch.ts
21598
+ var baseUrl = "https://mcp.exa.ai/mcp?tools=web_search_exa";
21599
+ var url2 = process.env.EXA_API_KEY ? `${baseUrl}&exaApiKey=${process.env.EXA_API_KEY}` : baseUrl;
21526
21600
  var websearch = {
21527
21601
  type: "remote",
21528
- url: "https://mcp.exa.ai/mcp?tools=web_search_exa",
21529
- headers: process.env.EXA_API_KEY ? { "x-api-key": process.env.EXA_API_KEY } : undefined,
21602
+ url: url2,
21530
21603
  oauth: false
21531
21604
  };
21532
21605
 
@@ -21554,7 +21627,7 @@ __export(exports_external2, {
21554
21627
  uuidv4: () => uuidv42,
21555
21628
  uuid: () => uuid5,
21556
21629
  util: () => exports_util2,
21557
- url: () => url2,
21630
+ url: () => url3,
21558
21631
  uppercase: () => _uppercase2,
21559
21632
  unknown: () => unknown2,
21560
21633
  union: () => union2,
@@ -23868,10 +23941,10 @@ var $ZodURL2 = /* @__PURE__ */ $constructor2("$ZodURL", (inst, def) => {
23868
23941
  inst._zod.check = (payload) => {
23869
23942
  try {
23870
23943
  const trimmed = payload.value.trim();
23871
- const url2 = new URL(trimmed);
23944
+ const url3 = new URL(trimmed);
23872
23945
  if (def.hostname) {
23873
23946
  def.hostname.lastIndex = 0;
23874
- if (!def.hostname.test(url2.hostname)) {
23947
+ if (!def.hostname.test(url3.hostname)) {
23875
23948
  payload.issues.push({
23876
23949
  code: "invalid_format",
23877
23950
  format: "url",
@@ -23885,7 +23958,7 @@ var $ZodURL2 = /* @__PURE__ */ $constructor2("$ZodURL", (inst, def) => {
23885
23958
  }
23886
23959
  if (def.protocol) {
23887
23960
  def.protocol.lastIndex = 0;
23888
- if (!def.protocol.test(url2.protocol.endsWith(":") ? url2.protocol.slice(0, -1) : url2.protocol)) {
23961
+ if (!def.protocol.test(url3.protocol.endsWith(":") ? url3.protocol.slice(0, -1) : url3.protocol)) {
23889
23962
  payload.issues.push({
23890
23963
  code: "invalid_format",
23891
23964
  format: "url",
@@ -23898,7 +23971,7 @@ var $ZodURL2 = /* @__PURE__ */ $constructor2("$ZodURL", (inst, def) => {
23898
23971
  }
23899
23972
  }
23900
23973
  if (def.normalize) {
23901
- payload.value = url2.href;
23974
+ payload.value = url3.href;
23902
23975
  } else {
23903
23976
  payload.value = trimmed;
23904
23977
  }
@@ -33000,7 +33073,7 @@ var ZodURL2 = /* @__PURE__ */ $constructor2("ZodURL", (inst, def) => {
33000
33073
  $ZodURL2.init(inst, def);
33001
33074
  ZodStringFormat2.init(inst, def);
33002
33075
  });
33003
- function url2(params) {
33076
+ function url3(params) {
33004
33077
  return _url2(ZodURL2, params);
33005
33078
  }
33006
33079
  function httpUrl2(params) {
@@ -35994,8 +36067,15 @@ var lsp_rename = tool({
35994
36067
  }
35995
36068
  });
35996
36069
  // src/index.ts
36070
+ function resolveProjectName(project, directory) {
36071
+ const runtimeProjectName = "name" in project && typeof project.name === "string" ? project.name : undefined;
36072
+ return runtimeProjectName || path8.basename(directory) || "oh-my-opencode-lite";
36073
+ }
35997
36074
  var OhMyOpenCodeLite = async (ctx) => {
35998
- const config3 = loadPluginConfig(ctx.directory);
36075
+ const { client, directory, project, worktree, $: shell, serverUrl } = ctx;
36076
+ const worktreeDirectory = worktree || directory;
36077
+ const projectName = resolveProjectName(project, directory);
36078
+ const config3 = loadPluginConfig(directory);
35999
36079
  const agentDefs = createAgents(config3);
36000
36080
  const agents = getAgentConfigs(config3);
36001
36081
  const modelArrayMap = {};
@@ -36034,7 +36114,7 @@ var OhMyOpenCodeLite = async (ctx) => {
36034
36114
  log("[plugin] initialized with tmux config", {
36035
36115
  tmuxConfig,
36036
36116
  rawTmuxConfig: config3.tmux,
36037
- directory: ctx.directory
36117
+ directory
36038
36118
  });
36039
36119
  try {
36040
36120
  syncSkillsOnStartup();
@@ -36045,36 +36125,35 @@ var OhMyOpenCodeLite = async (ctx) => {
36045
36125
  if (tmuxConfig.enabled) {
36046
36126
  startTmuxCheck();
36047
36127
  }
36048
- const projectName = path8.basename(ctx.directory) || "oh-my-opencode-lite";
36049
36128
  let backgroundManager;
36050
36129
  const delegationManager = new DelegationManager({
36051
- directory: ctx.directory,
36130
+ directory,
36131
+ worktreeDirectory,
36132
+ projectName,
36133
+ shell,
36052
36134
  config: config3.delegation,
36053
36135
  getActiveTaskIds: (rootSessionId) => backgroundManager?.getActiveTaskIds(rootSessionId) ?? []
36054
36136
  });
36055
- backgroundManager = new BackgroundTaskManager(ctx, tmuxConfig, config3, delegationManager);
36137
+ backgroundManager = new BackgroundTaskManager(ctx, tmuxConfig, config3, delegationManager, worktreeDirectory);
36056
36138
  const backgroundTools = createBackgroundTools(ctx, backgroundManager, tmuxConfig, config3);
36057
36139
  const mcps = createBuiltinMcps(config3.disabled_mcps, config3.thoth);
36058
36140
  const tmuxSessionManager = new TmuxSessionManager(ctx, tmuxConfig);
36059
36141
  const autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
36060
36142
  showStartupToast: true,
36061
36143
  autoUpdate: true
36062
- });
36144
+ }, shell);
36063
36145
  const phaseReminderHook = createPhaseReminderHook();
36064
- const clarificationGateHook = createClarificationGateHook({
36065
- clarificationGate: config3.clarificationGate
36066
- });
36067
36146
  const postReadNudgeHook = createPostReadNudgeHook();
36068
36147
  const thothMemHook = createThothMemHook({
36069
36148
  project: projectName,
36070
- directory: ctx.directory,
36149
+ directory,
36071
36150
  thoth: config3.thoth,
36072
36151
  enabled: true
36073
36152
  });
36074
36153
  const chatHeadersHook = createChatHeadersHook(ctx);
36075
36154
  const delegateTaskRetryHook = createDelegateTaskRetryHook(ctx);
36076
36155
  const jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
36077
- const foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config3.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
36156
+ const foregroundFallback = new ForegroundFallbackManager(client, runtimeChains, config3.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
36078
36157
  return {
36079
36158
  name: "oh-my-opencode-lite",
36080
36159
  agent: agents,
@@ -36183,7 +36262,6 @@ var OhMyOpenCodeLite = async (ctx) => {
36183
36262
  },
36184
36263
  "experimental.chat.messages.transform": async (input, output) => {
36185
36264
  await phaseReminderHook["experimental.chat.messages.transform"](input, output);
36186
- await clarificationGateHook["experimental.chat.messages.transform"](input, output);
36187
36265
  },
36188
36266
  "experimental.session.compacting": async (input, output) => {
36189
36267
  if (thothMemHook["experimental.session.compacting"]) {