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/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,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 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
+ ## 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
- - Ask a focused question only when missing inputs block delegation.
17762
- </communication>`;
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
- <forbidden>
17802
- - no external research
17803
- - no delegation
17804
- - no background work
17805
- - no multi-step planning
17806
- </forbidden>
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
- <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>
17901
+ ${QUESTION_PROTOCOL}
17813
17902
 
17814
17903
  <output>
17815
- Use the repository summary format with summary, changes, and verification sections.
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 applyQuestionPermission(agent) {
17855
- agent.config.permission = {
17856
- ...agent.config.permission ?? {},
17857
- question: "allow"
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
- calculateToolPermissions(agentName) {
18552
- const allowedSubagents = this.getSubagentRules(agentName);
18553
- if (allowedSubagents.length === 0) {
18554
- return { background_task: false, task: false };
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 { background_task: true, task: true };
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.directory }
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 toolPermissions = this.calculateToolPermissions(task.agent);
18584
- const promptQuery = { directory: this.directory };
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
- tools: toolPermissions,
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(directory, args, timeoutMs) {
19149
- const subprocess = Bun.spawn({
19150
- cmd: ["git", ...args],
19151
- cwd: directory,
19152
- stdout: "pipe",
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
- (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
- })(),
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
- return result;
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
- clearTimeout(timer);
19425
+ if (timer) {
19426
+ clearTimeout(timer);
19427
+ }
19181
19428
  }
19182
19429
  }
19183
- function buildProjectId(baseDirectory, hash2) {
19184
- const projectName = sanitizeProjectName(path3.basename(baseDirectory));
19185
- return `${projectName}-${hash2.slice(0, 12)}`;
19430
+ function buildProjectId(projectName, hash2) {
19431
+ const normalizedProjectName = sanitizeProjectName(projectName);
19432
+ return `${normalizedProjectName}-${hash2.slice(0, 12)}`;
19186
19433
  }
19187
- async function getProjectId(directory, timeoutMs = 5000) {
19188
- const normalizedDirectory = directory.trim();
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(repoRoot, rootCommit);
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(resolvedDirectory, hash2);
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
- directory;
19611
+ worktreeDirectory;
19612
+ projectName;
19613
+ shell;
19345
19614
  config;
19346
19615
  getActiveTaskIds;
19347
19616
  constructor(options) {
19348
- this.directory = options.directory;
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(directory = this.directory) {
19353
- return getProjectId(directory, this.config?.timeout);
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 exitPromise = proc.exited.then(() => "completed");
19899
- const result = await Promise.race([exitPromise, timeoutPromise]);
19900
- if (result === "timeout") {
20164
+ const installPromise = (async () => {
19901
20165
  try {
19902
- proc.kill();
19903
- } catch {}
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 proc.exitCode === 0;
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
- const sessionClient = this.client.session;
20475
- await sessionClient.promptAsync({
20515
+ await this.client.session.promptAsync({
20476
20516
  path: { id: sessionID },
20477
- body: { parts: lastUser.parts, model: ref }
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 mentioning a specialist, launch it in this same turn.`;
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: "brainstorming",
20773
- description: "Understand user intent and scope through structured clarification before implementation",
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/brainstorming"
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 normalizeText2(value) {
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 = normalizeText2(value);
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 = normalizeText2(rawSession.id) ?? "unknown-session";
21097
- const project = normalizeText2(rawSession.project) ?? "unknown-project";
21098
- const startedAt = normalizeText2(rawSession.started_at) ?? "unknown";
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 = normalizeText2(rawObservation.type) ?? "manual";
21111
- const title = normalizeText2(rawObservation.title) ?? "Untitled";
21112
- const createdAt = normalizeText2(rawObservation.created_at) ?? "unknown";
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 = normalizeText2(rawPrompt.created_at) ?? "unknown";
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 = "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.";
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:\`, and call \`mem_context\` if you need to restore recent memory.`;
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
- 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:
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
- 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.
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 topic_key convention:
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
- trackedRootSessions.add(sessionId);
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
- trackedRootSessions.add(session.id);
21385
- await thoth.memSessionStart(session.id);
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
- const systemPrompt = compactionReminder ? appendInstruction(memoryInstructions, compactionReminder) : memoryInstructions;
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: "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,
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: () => url2,
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 url2 = new URL(trimmed);
23999
+ const url3 = new URL(trimmed);
23872
24000
  if (def.hostname) {
23873
24001
  def.hostname.lastIndex = 0;
23874
- if (!def.hostname.test(url2.hostname)) {
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(url2.protocol.endsWith(":") ? url2.protocol.slice(0, -1) : url2.protocol)) {
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 = url2.href;
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 url2(params) {
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 config3 = loadPluginConfig(ctx.directory);
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: ctx.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: ctx.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: ctx.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(ctx.client, runtimeChains, config3.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
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"]) {