thoth-agents 0.2.11 → 0.3.1

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.
Files changed (149) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +21 -0
  3. package/README.md +301 -311
  4. package/dist/agents/configured-role-prompt.d.ts +9 -0
  5. package/dist/agents/prompt-sections.d.ts +8 -4
  6. package/dist/agents/prompt-utils.d.ts +0 -4
  7. package/dist/{chunk-ASKXWU6E.js → chunk-2MZ5C2RN.js} +1078 -1725
  8. package/dist/chunk-XO74DXBZ.js +1781 -0
  9. package/dist/{chunk-6BNAV534.js → chunk-YUCGIJPP.js} +12 -30
  10. package/dist/cli/claude-code-install.d.ts +24 -26
  11. package/dist/cli/claude-code-paths.d.ts +0 -28
  12. package/dist/cli/codex-install.d.ts +2 -2
  13. package/dist/cli/codex-paths.d.ts +0 -2
  14. package/dist/cli/index.js +279 -119
  15. package/dist/cli/install.d.ts +5 -1
  16. package/dist/cli/model-catalog/cache.d.ts +1 -1
  17. package/dist/cli/operations/claude-code.d.ts +6 -3
  18. package/dist/cli/operations/codex.d.ts +2 -1
  19. package/dist/cli/operations/opencode.d.ts +2 -1
  20. package/dist/cli/operations/types.d.ts +9 -1
  21. package/dist/cli/package-root.d.ts +1 -0
  22. package/dist/cli/providers.d.ts +6 -116
  23. package/dist/cli/skills.d.ts +19 -32
  24. package/dist/cli/thoth-mem-install.d.ts +45 -0
  25. package/dist/cli/tui/components/StatusView.d.ts +12 -1
  26. package/dist/cli/tui/index.js +256 -142
  27. package/dist/cli/tui/operations.d.ts +4 -3
  28. package/dist/cli/types.d.ts +0 -10
  29. package/dist/config/constants.d.ts +20 -16
  30. package/dist/config/index.d.ts +0 -1
  31. package/dist/config/loader.d.ts +1 -1
  32. package/dist/config/schema.d.ts +2 -38
  33. package/dist/config/utils.d.ts +1 -2
  34. package/dist/harness/adapters/codex-surfaces.d.ts +3 -3
  35. package/dist/harness/core/agent-pack.d.ts +37 -29
  36. package/dist/harness/core/memory-governance.d.ts +24 -48
  37. package/dist/harness/core/sdd.d.ts +364 -131
  38. package/dist/harness/generate-integration-packages.d.ts +20 -0
  39. package/dist/harness/opencode-init-command.d.ts +11 -0
  40. package/dist/harness/types.d.ts +14 -1
  41. package/dist/harness/writers/claude-code-subagent.d.ts +3 -1
  42. package/dist/hooks/index.d.ts +0 -5
  43. package/dist/index.js +562 -1314
  44. package/dist/mcp/index.d.ts +1 -2
  45. package/package.json +17 -12
  46. package/plugin/.claude-plugin/.thoth-agents-plugin-assets.json +50 -0
  47. package/plugin/.claude-plugin/plugin.json +8 -0
  48. package/plugin/.codex-plugin/.thoth-agents-plugin-assets.json +16 -0
  49. package/plugin/.codex-plugin/plugin.json +7 -0
  50. package/plugin/.mcp.json +19 -0
  51. package/plugin/agents/deep.md +89 -0
  52. package/plugin/agents/designer.md +89 -0
  53. package/plugin/agents/explorer.md +90 -0
  54. package/plugin/agents/librarian.md +90 -0
  55. package/plugin/agents/oracle.md +92 -0
  56. package/plugin/agents/orchestrator.md +128 -0
  57. package/plugin/agents/quick.md +89 -0
  58. package/plugin/codex.mcp.json +17 -0
  59. package/plugin/settings.json +3 -0
  60. package/plugin/skills/thoth-archive/SKILL.md +40 -0
  61. package/plugin/skills/thoth-archive/scripts/archive.mjs +748 -0
  62. package/plugin/skills/thoth-constitution/SKILL.md +45 -0
  63. package/plugin/skills/thoth-constitution/scripts/validate.mjs +167 -0
  64. package/plugin/skills/thoth-constitution/templates/constitution.md +54 -0
  65. package/plugin/skills/thoth-init/SKILL.md +30 -0
  66. package/plugin/skills/thoth-init/scripts/init.mjs +141 -0
  67. package/plugin/skills/thoth-sdd/SKILL.md +83 -0
  68. package/plugin/skills/thoth-sdd/references/phases/analyze.md +21 -0
  69. package/plugin/skills/thoth-sdd/references/phases/checklist.md +20 -0
  70. package/plugin/skills/thoth-sdd/references/phases/clarify.md +15 -0
  71. package/plugin/skills/thoth-sdd/references/phases/converge.md +14 -0
  72. package/plugin/skills/thoth-sdd/references/phases/explore.md +10 -0
  73. package/plugin/skills/thoth-sdd/references/phases/implement.md +19 -0
  74. package/plugin/skills/thoth-sdd/references/phases/plan.md +15 -0
  75. package/plugin/skills/thoth-sdd/references/phases/specify.md +21 -0
  76. package/plugin/skills/thoth-sdd/references/phases/tasks.md +20 -0
  77. package/plugin/skills/thoth-sdd/references/phases/verify.md +22 -0
  78. package/plugin/skills/thoth-sdd/scripts/validate.mjs +1117 -0
  79. package/plugin/skills/thoth-sdd/templates/archive-report.md +25 -0
  80. package/plugin/skills/thoth-sdd/templates/checklist.md +27 -0
  81. package/plugin/skills/thoth-sdd/templates/plan.md +38 -0
  82. package/plugin/skills/thoth-sdd/templates/spec.md +54 -0
  83. package/plugin/skills/thoth-sdd/templates/tasks.md +29 -0
  84. package/plugin/skills/thoth-sdd/templates/verify-report.md +28 -0
  85. package/skills/README.md +12 -0
  86. package/skills/thoth-archive/SKILL.md +40 -0
  87. package/skills/thoth-archive/scripts/archive.mjs +748 -0
  88. package/skills/thoth-constitution/SKILL.md +45 -0
  89. package/skills/thoth-constitution/scripts/validate.mjs +167 -0
  90. package/skills/thoth-constitution/templates/constitution.md +54 -0
  91. package/skills/thoth-init/SKILL.md +30 -0
  92. package/skills/thoth-init/scripts/init.mjs +141 -0
  93. package/skills/thoth-sdd/SKILL.md +83 -0
  94. package/skills/thoth-sdd/references/phases/analyze.md +21 -0
  95. package/skills/thoth-sdd/references/phases/checklist.md +20 -0
  96. package/skills/thoth-sdd/references/phases/clarify.md +15 -0
  97. package/skills/thoth-sdd/references/phases/converge.md +14 -0
  98. package/skills/thoth-sdd/references/phases/explore.md +10 -0
  99. package/skills/thoth-sdd/references/phases/implement.md +19 -0
  100. package/skills/thoth-sdd/references/phases/plan.md +15 -0
  101. package/skills/thoth-sdd/references/phases/specify.md +21 -0
  102. package/skills/thoth-sdd/references/phases/tasks.md +20 -0
  103. package/skills/thoth-sdd/references/phases/verify.md +22 -0
  104. package/skills/thoth-sdd/scripts/validate.mjs +1117 -0
  105. package/skills/thoth-sdd/templates/archive-report.md +25 -0
  106. package/skills/thoth-sdd/templates/checklist.md +27 -0
  107. package/skills/thoth-sdd/templates/plan.md +38 -0
  108. package/skills/thoth-sdd/templates/spec.md +54 -0
  109. package/skills/thoth-sdd/templates/tasks.md +29 -0
  110. package/skills/thoth-sdd/templates/verify-report.md +28 -0
  111. package/thoth-agents.schema.json +0 -39
  112. package/dist/chunk-OZE7JVGS.js +0 -2344
  113. package/dist/cli/custom-skills.d.ts +0 -49
  114. package/dist/cli/skill-manifest.d.ts +0 -32
  115. package/dist/harness/core/skills.d.ts +0 -211
  116. package/dist/harness/generate-codex-plugin.d.ts +0 -9
  117. package/dist/harness/writers/claude-code-skill-layout.d.ts +0 -16
  118. package/dist/harness/writers/fs-skill-collect.d.ts +0 -7
  119. package/dist/harness/writers/skill-layout.d.ts +0 -16
  120. package/dist/hooks/chat-headers.d.ts +0 -16
  121. package/dist/hooks/phase-reminder/index.d.ts +0 -28
  122. package/dist/hooks/post-read-nudge/index.d.ts +0 -18
  123. package/dist/hooks/skill-sync.d.ts +0 -10
  124. package/dist/hooks/thoth-mem/index.d.ts +0 -46
  125. package/dist/hooks/thoth-mem/protocol.d.ts +0 -7
  126. package/dist/mcp/thoth.d.ts +0 -3
  127. package/dist/sdd/artifact-governance/artifact-loader.d.ts +0 -130
  128. package/dist/sdd/artifact-governance/index.d.ts +0 -6
  129. package/dist/sdd/artifact-governance/tasks-validator.d.ts +0 -17
  130. package/dist/sdd/artifact-governance/types.d.ts +0 -52
  131. package/dist/thoth/client.d.ts +0 -14
  132. package/dist/thoth/index.d.ts +0 -2
  133. package/src/skills/_shared/openspec-convention.md +0 -350
  134. package/src/skills/_shared/persistence-contract.md +0 -163
  135. package/src/skills/_shared/thoth-mem-convention.md +0 -123
  136. package/src/skills/executing-plans/SKILL.md +0 -288
  137. package/src/skills/plan-reviewer/SKILL.md +0 -219
  138. package/src/skills/requirements-interview/SKILL.md +0 -233
  139. package/src/skills/sdd-apply/SKILL.md +0 -89
  140. package/src/skills/sdd-archive/SKILL.md +0 -92
  141. package/src/skills/sdd-clarify/SKILL.md +0 -105
  142. package/src/skills/sdd-constitution/SKILL.md +0 -107
  143. package/src/skills/sdd-design/SKILL.md +0 -155
  144. package/src/skills/sdd-init/SKILL.md +0 -233
  145. package/src/skills/sdd-propose/SKILL.md +0 -101
  146. package/src/skills/sdd-spec/SKILL.md +0 -136
  147. package/src/skills/sdd-tasks/SKILL.md +0 -232
  148. package/src/skills/sdd-verify/SKILL.md +0 -131
  149. package/src/skills/thoth-mem-agents/SKILL.md +0 -210
package/dist/index.js CHANGED
@@ -3,30 +3,66 @@ import {
3
3
  renderOpenCodeAgentConfigs,
4
4
  spawn,
5
5
  spawnSync
6
- } from "./chunk-6BNAV534.js";
6
+ } from "./chunk-YUCGIJPP.js";
7
7
  import {
8
- DEFAULT_THOTH_COMMAND,
9
8
  POLL_INTERVAL_BACKGROUND_MS,
10
9
  context7,
11
10
  exa,
12
11
  getOpenCodeConfigPaths,
13
12
  grep_app,
14
- installCustomSkills,
15
13
  loadPluginConfig,
16
14
  stripJsonComments
17
- } from "./chunk-OZE7JVGS.js";
15
+ } from "./chunk-XO74DXBZ.js";
18
16
 
19
17
  // src/index.ts
20
- import path4 from "path";
18
+ import { fileURLToPath as fileURLToPath3 } from "url";
19
+
20
+ // src/harness/opencode-init-command.ts
21
+ import { existsSync, readFileSync } from "fs";
22
+ import { join } from "path";
23
+ function createOpenCodeInitCommand({
24
+ projectRoot,
25
+ packageRoot
26
+ }) {
27
+ const skillPath = join(packageRoot, "skills", "thoth-init", "SKILL.md");
28
+ const scriptPath = join(
29
+ packageRoot,
30
+ "skills",
31
+ "thoth-init",
32
+ "scripts",
33
+ "init.mjs"
34
+ );
35
+ if (!existsSync(skillPath) || !existsSync(scriptPath)) {
36
+ throw new Error("The bundled thoth-init skill is incomplete.");
37
+ }
38
+ const contract = readFileSync(skillPath, "utf8");
39
+ return {
40
+ description: "Initialize thoth-agents project SDD governance",
41
+ agent: "orchestrator",
42
+ subtask: false,
43
+ template: `Initialize this project now using the bundled thoth-init skill.
44
+
45
+ The operation must stay project-scoped, idempotent, and offline. Run:
46
+
47
+ node "${scriptPath}" --project "${projectRoot}" --harness opencode --json
48
+
49
+ Inspect the JSON result, report created and preserved files, and do not download
50
+ skills or invoke the thoth-agents CLI.
51
+
52
+ Bundled skill contract:
53
+
54
+ ${contract}`
55
+ };
56
+ }
21
57
 
22
58
  // src/utils/logger.ts
23
59
  import { createRequire } from "module";
24
60
  var require2 = createRequire(import.meta.url);
25
61
  function getLogFile() {
26
62
  const os2 = require2("node:os");
27
- const path5 = require2("node:path");
63
+ const path4 = require2("node:path");
28
64
  const configuredPath = process.env.THOTH_AGENTS_LOG_FILE?.trim();
29
- return configuredPath || path5.join(os2.tmpdir(), "thoth-agents.log");
65
+ return configuredPath || path4.join(os2.tmpdir(), "thoth-agents.log");
30
66
  }
31
67
  function log(message, data) {
32
68
  try {
@@ -465,557 +501,154 @@ function showToast(ctx, title, message, variant = "info", duration = 3e3) {
465
501
  });
466
502
  }
467
503
 
468
- // src/utils/internal-initiator.ts
469
- var LITE_INTERNAL_INITIATOR_MARKER = "<!-- LITE_INTERNAL_INITIATOR -->";
470
- function isRecord(value) {
471
- return typeof value === "object" && value !== null;
472
- }
473
- function hasInternalInitiatorMarker(part) {
474
- if (!isRecord(part) || part.type !== "text") {
475
- return false;
504
+ // src/hooks/delegate-task-retry/patterns.ts
505
+ var DELEGATE_TASK_ERROR_PATTERNS = [
506
+ {
507
+ pattern: "load_skills",
508
+ errorType: "missing_load_skills",
509
+ fixHint: "Add load_skills=[] (empty array when no skill is needed)."
510
+ },
511
+ {
512
+ pattern: "category OR subagent_type",
513
+ errorType: "mutual_exclusion",
514
+ fixHint: 'Provide only one: category (e.g., "unspecified-low") OR subagent_type (e.g., "explorer").'
515
+ },
516
+ {
517
+ pattern: "Must provide either category or subagent_type",
518
+ errorType: "missing_category_or_agent",
519
+ fixHint: 'Add either category="unspecified-low" or subagent_type="explorer".'
520
+ },
521
+ {
522
+ pattern: "Unknown category",
523
+ errorType: "unknown_category",
524
+ fixHint: "Use a valid category listed in the error output."
525
+ },
526
+ {
527
+ pattern: "Unknown agent",
528
+ errorType: "unknown_agent",
529
+ fixHint: "Use a valid agent name from the available list."
530
+ },
531
+ {
532
+ pattern: "Skills not found",
533
+ errorType: "unknown_skills",
534
+ fixHint: "Use valid skill names listed in the error output."
535
+ },
536
+ {
537
+ pattern: "is not allowed. Allowed agents:",
538
+ errorType: "background_agent_not_allowed",
539
+ fixHint: "Use one of the allowed agents shown in the error or delegate from a parent agent that can call this subagent."
476
540
  }
477
- if (typeof part.text !== "string") {
478
- return false;
541
+ ];
542
+ function detectDelegateTaskError(output) {
543
+ if (!output || typeof output !== "string") return null;
544
+ const hasErrorSignal = output.includes("[ERROR]") || output.includes("Invalid arguments") || output.includes("is not allowed. Allowed agents:");
545
+ if (!hasErrorSignal) return null;
546
+ for (const pattern of DELEGATE_TASK_ERROR_PATTERNS) {
547
+ if (output.includes(pattern.pattern)) {
548
+ return {
549
+ errorType: pattern.errorType,
550
+ originalOutput: output
551
+ };
552
+ }
479
553
  }
480
- return part.text.includes(LITE_INTERNAL_INITIATOR_MARKER);
554
+ return null;
481
555
  }
482
556
 
483
- // src/utils/tmux.ts
484
- var tmuxPath = null;
485
- var tmuxChecked = false;
486
- var storedConfig = null;
487
- var serverAvailable = null;
488
- var serverCheckUrl = null;
489
- async function isServerRunning(serverUrl) {
490
- if (serverCheckUrl === serverUrl && serverAvailable === true) {
491
- return true;
557
+ // src/hooks/delegate-task-retry/guidance.ts
558
+ function extractAvailableList(output) {
559
+ const match = output.match(/Allowed agents:\s*(.+)$/m);
560
+ if (match) return match[1].trim();
561
+ const available = output.match(/Available[^:]*:\s*(.+)$/m);
562
+ if (available) return available[1].trim();
563
+ return null;
564
+ }
565
+ function buildRetryGuidance(errorInfo) {
566
+ const pattern = DELEGATE_TASK_ERROR_PATTERNS.find(
567
+ (p) => p.errorType === errorInfo.errorType
568
+ );
569
+ if (!pattern) {
570
+ return "\n[delegate-task retry] Fix parameters and retry with corrected arguments.";
492
571
  }
493
- const healthUrl = new URL("/health", serverUrl).toString();
494
- const timeoutMs = 3e3;
495
- const maxAttempts = 2;
496
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
497
- const controller = new AbortController();
498
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
499
- let response = null;
500
- try {
501
- response = await fetch(healthUrl, { signal: controller.signal }).catch(
502
- () => null
503
- );
504
- } finally {
505
- clearTimeout(timeout);
506
- }
507
- const available = response?.ok ?? false;
508
- if (available) {
509
- serverCheckUrl = serverUrl;
510
- serverAvailable = true;
511
- log("[tmux] isServerRunning: checked", { serverUrl, available, attempt });
512
- return true;
513
- }
514
- if (attempt < maxAttempts) {
515
- await new Promise((r) => setTimeout(r, 250));
516
- }
572
+ const available = extractAvailableList(errorInfo.originalOutput);
573
+ const lines = [
574
+ "",
575
+ "[delegate-task retry suggestion]",
576
+ `Error type: ${errorInfo.errorType}`,
577
+ `Fix: ${pattern.fixHint}`
578
+ ];
579
+ if (available) {
580
+ lines.push(`Available: ${available}`);
517
581
  }
518
- log("[tmux] isServerRunning: checked", { serverUrl, available: false });
519
- return false;
582
+ if (errorInfo.errorType === "background_agent_not_allowed") {
583
+ lines.push(
584
+ "Retry now with corrected parameters.",
585
+ "Use `background=true` only for `explorer` or `librarian`; otherwise retry without background.",
586
+ "If the host enables `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`, collect results with `task_status` before reporting completion.",
587
+ "Examples:",
588
+ 'task(description="...", prompt="...", subagent_type="explorer", background=true)',
589
+ 'task_status(task_id="ses_...", wait=true)'
590
+ );
591
+ return lines.join("\n");
592
+ }
593
+ lines.push(
594
+ "Retry now with corrected parameters. Example:",
595
+ 'task(description="...", prompt="...", subagent_type="explorer")'
596
+ );
597
+ return lines.join("\n");
520
598
  }
521
- async function findTmuxPath() {
522
- const isWindows = process.platform === "win32";
523
- const cmd = isWindows ? "where" : "which";
524
- try {
525
- const proc = spawn([cmd, "tmux"], {
526
- stdout: "pipe",
527
- stderr: "pipe"
528
- });
529
- const exitCode = await proc.exited;
530
- if (exitCode !== 0) {
531
- log("[tmux] findTmuxPath: 'which tmux' failed", { exitCode });
532
- return null;
533
- }
534
- const stdout = await new Response(proc.stdout).text();
535
- const path5 = stdout.trim().split("\n")[0];
536
- if (!path5) {
537
- log("[tmux] findTmuxPath: no path in output");
538
- return null;
539
- }
540
- const verifyProc = spawn([path5, "-V"], {
541
- stdout: "pipe",
542
- stderr: "pipe"
543
- });
544
- const verifyExit = await verifyProc.exited;
545
- if (verifyExit !== 0) {
546
- log("[tmux] findTmuxPath: tmux -V failed", { path: path5, verifyExit });
547
- return null;
599
+
600
+ // src/hooks/delegate-task-retry/hook.ts
601
+ function createDelegateTaskRetryHook(_ctx) {
602
+ return {
603
+ "tool.execute.after": async (input, output) => {
604
+ const toolName = input.tool.toLowerCase();
605
+ const isDelegateTool = toolName === "task";
606
+ if (!isDelegateTool) return;
607
+ if (typeof output.output !== "string") return;
608
+ const detected = detectDelegateTaskError(output.output);
609
+ if (!detected) return;
610
+ output.output += `
611
+ ${buildRetryGuidance(detected)}`;
548
612
  }
549
- log("[tmux] findTmuxPath: found tmux", { path: path5 });
550
- return path5;
551
- } catch (err) {
552
- log("[tmux] findTmuxPath: exception", { error: String(err) });
553
- return null;
554
- }
613
+ };
555
614
  }
556
- async function getTmuxPath() {
557
- if (tmuxChecked) {
558
- return tmuxPath;
559
- }
560
- tmuxPath = await findTmuxPath();
561
- tmuxChecked = true;
562
- log("[tmux] getTmuxPath: initialized", { tmuxPath });
563
- return tmuxPath;
615
+
616
+ // src/hooks/foreground-fallback/index.ts
617
+ var RATE_LIMIT_PATTERNS = [
618
+ /\b429\b/,
619
+ /rate.?limit/i,
620
+ /too many requests/i,
621
+ /quota.?exceeded/i,
622
+ /usage.?exceeded/i,
623
+ /usage limit/i,
624
+ /overloaded/i,
625
+ /resource.?exhausted/i,
626
+ /insufficient.?quota/i,
627
+ /high concurrency/i,
628
+ /reduce concurrency/i
629
+ ];
630
+ function isRateLimitError(error) {
631
+ if (!error || typeof error !== "object") return false;
632
+ const err = error;
633
+ const text = [
634
+ err.message ?? "",
635
+ String(err.data?.statusCode ?? ""),
636
+ err.data?.message ?? "",
637
+ err.data?.responseBody ?? ""
638
+ ].join(" ");
639
+ return RATE_LIMIT_PATTERNS.some((p) => p.test(text));
564
640
  }
565
- function isInsideTmux() {
566
- return !!process.env.TMUX;
641
+ function parseModel(model) {
642
+ const slash = model.indexOf("/");
643
+ if (slash <= 0 || slash >= model.length - 1) return null;
644
+ return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
567
645
  }
568
- async function applyLayout(tmux, layout, mainPaneSize) {
569
- try {
570
- const layoutProc = spawn([tmux, "select-layout", layout], {
571
- stdout: "pipe",
572
- stderr: "pipe"
573
- });
574
- await layoutProc.exited;
575
- if (layout === "main-horizontal" || layout === "main-vertical") {
576
- const sizeOption = layout === "main-horizontal" ? "main-pane-height" : "main-pane-width";
577
- const sizeProc = spawn(
578
- [tmux, "set-window-option", sizeOption, `${mainPaneSize}%`],
579
- {
580
- stdout: "pipe",
581
- stderr: "pipe"
582
- }
583
- );
584
- await sizeProc.exited;
585
- const reapplyProc = spawn([tmux, "select-layout", layout], {
586
- stdout: "pipe",
587
- stderr: "pipe"
588
- });
589
- await reapplyProc.exited;
590
- }
591
- log("[tmux] applyLayout: applied", { layout, mainPaneSize });
592
- } catch (err) {
593
- log("[tmux] applyLayout: exception", { error: String(err) });
594
- }
595
- }
596
- async function spawnTmuxPane(sessionId, description, config, serverUrl) {
597
- log("[tmux] spawnTmuxPane called", {
598
- sessionId,
599
- description,
600
- config,
601
- serverUrl
602
- });
603
- if (!config.enabled) {
604
- log("[tmux] spawnTmuxPane: config.enabled is false, skipping");
605
- return { success: false };
606
- }
607
- if (!isInsideTmux()) {
608
- log("[tmux] spawnTmuxPane: not inside tmux, skipping");
609
- return { success: false };
610
- }
611
- const serverRunning = await isServerRunning(serverUrl);
612
- if (!serverRunning) {
613
- const defaultPort = process.env.OPENCODE_PORT ?? "4096";
614
- log("[tmux] spawnTmuxPane: OpenCode server not running, skipping", {
615
- serverUrl,
616
- hint: `Start opencode with --port ${defaultPort}`
617
- });
618
- return { success: false };
619
- }
620
- const tmux = await getTmuxPath();
621
- if (!tmux) {
622
- log("[tmux] spawnTmuxPane: tmux binary not found, skipping");
623
- return { success: false };
624
- }
625
- storedConfig = config;
626
- try {
627
- const opencodeCmd = `opencode attach ${serverUrl} --session ${sessionId}`;
628
- const args = [
629
- "split-window",
630
- "-h",
631
- "-d",
632
- // Don't switch focus to new pane
633
- "-P",
634
- // Print pane info
635
- "-F",
636
- "#{pane_id}",
637
- // Format: just the pane ID
638
- opencodeCmd
639
- ];
640
- log("[tmux] spawnTmuxPane: executing", { tmux, args, opencodeCmd });
641
- const proc = spawn([tmux, ...args], {
642
- stdout: "pipe",
643
- stderr: "pipe"
644
- });
645
- const exitCode = await proc.exited;
646
- const stdout = await new Response(proc.stdout).text();
647
- const stderr = await new Response(proc.stderr).text();
648
- const paneId = stdout.trim();
649
- log("[tmux] spawnTmuxPane: split result", {
650
- exitCode,
651
- paneId,
652
- stderr: stderr.trim()
653
- });
654
- if (exitCode === 0 && paneId) {
655
- const renameProc = spawn(
656
- [tmux, "select-pane", "-t", paneId, "-T", description.slice(0, 30)],
657
- { stdout: "ignore", stderr: "ignore" }
658
- );
659
- await renameProc.exited;
660
- const layout = config.layout ?? "main-vertical";
661
- const mainPaneSize = config.main_pane_size ?? 60;
662
- await applyLayout(tmux, layout, mainPaneSize);
663
- log("[tmux] spawnTmuxPane: SUCCESS, pane created and layout applied", {
664
- paneId,
665
- layout
666
- });
667
- return { success: true, paneId };
668
- }
669
- return { success: false };
670
- } catch (err) {
671
- log("[tmux] spawnTmuxPane: exception", { error: String(err) });
672
- return { success: false };
673
- }
674
- }
675
- async function closeTmuxPane(paneId) {
676
- log("[tmux] closeTmuxPane called", { paneId });
677
- if (!paneId) {
678
- log("[tmux] closeTmuxPane: no paneId provided");
679
- return false;
680
- }
681
- const tmux = await getTmuxPath();
682
- if (!tmux) {
683
- log("[tmux] closeTmuxPane: tmux binary not found");
684
- return false;
685
- }
686
- try {
687
- log("[tmux] closeTmuxPane: sending Ctrl+C for graceful shutdown", {
688
- paneId
689
- });
690
- const ctrlCProc = spawn([tmux, "send-keys", "-t", paneId, "C-c"], {
691
- stdout: "pipe",
692
- stderr: "pipe"
693
- });
694
- await ctrlCProc.exited;
695
- await new Promise((r) => setTimeout(r, 250));
696
- log("[tmux] closeTmuxPane: killing pane", { paneId });
697
- const proc = spawn([tmux, "kill-pane", "-t", paneId], {
698
- stdout: "pipe",
699
- stderr: "pipe"
700
- });
701
- const exitCode = await proc.exited;
702
- const stderr = await new Response(proc.stderr).text();
703
- log("[tmux] closeTmuxPane: result", { exitCode, stderr: stderr.trim() });
704
- if (exitCode === 0) {
705
- log("[tmux] closeTmuxPane: SUCCESS, pane closed", { paneId });
706
- if (storedConfig) {
707
- const layout = storedConfig.layout ?? "main-vertical";
708
- const mainPaneSize = storedConfig.main_pane_size ?? 60;
709
- await applyLayout(tmux, layout, mainPaneSize);
710
- log("[tmux] closeTmuxPane: layout reapplied", { layout });
711
- }
712
- return true;
713
- }
714
- log("[tmux] closeTmuxPane: failed (pane may already be closed)", {
715
- paneId
716
- });
717
- return false;
718
- } catch (err) {
719
- log("[tmux] closeTmuxPane: exception", { error: String(err) });
720
- return false;
721
- }
722
- }
723
- function startTmuxCheck() {
724
- if (!tmuxChecked) {
725
- getTmuxPath().catch(() => {
726
- });
727
- }
728
- }
729
-
730
- // src/utils/zip-extractor.ts
731
- import { release } from "os";
732
- var WINDOWS_BUILD_WITH_TAR = 17134;
733
- function getWindowsBuildNumber() {
734
- if (process.platform !== "win32") return null;
735
- const parts = release().split(".");
736
- if (parts.length >= 3) {
737
- const build = parseInt(parts[2], 10);
738
- if (!Number.isNaN(build)) return build;
739
- }
740
- return null;
741
- }
742
- function isPwshAvailable() {
743
- if (process.platform !== "win32") return false;
744
- const result = spawnSync(["where", "pwsh"], {
745
- stdout: "pipe",
746
- stderr: "pipe"
747
- });
748
- return result.exitCode === 0;
749
- }
750
- function escapePowerShellPath(path5) {
751
- return path5.replace(/'/g, "''");
752
- }
753
- function getWindowsZipExtractor() {
754
- const buildNumber = getWindowsBuildNumber();
755
- if (buildNumber !== null && buildNumber >= WINDOWS_BUILD_WITH_TAR) {
756
- return "tar";
757
- }
758
- if (isPwshAvailable()) {
759
- return "pwsh";
760
- }
761
- return "powershell";
762
- }
763
- async function extractZip(archivePath, destDir) {
764
- let proc;
765
- if (process.platform === "win32") {
766
- const extractor = getWindowsZipExtractor();
767
- switch (extractor) {
768
- case "tar":
769
- proc = spawn(["tar", "-xf", archivePath, "-C", destDir], {
770
- stdout: "ignore",
771
- stderr: "pipe"
772
- });
773
- break;
774
- case "pwsh":
775
- proc = spawn(
776
- [
777
- "pwsh",
778
- "-Command",
779
- `Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
780
- ],
781
- {
782
- stdout: "ignore",
783
- stderr: "pipe"
784
- }
785
- );
786
- break;
787
- default:
788
- proc = spawn(
789
- [
790
- "powershell",
791
- "-Command",
792
- `Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
793
- ],
794
- {
795
- stdout: "ignore",
796
- stderr: "pipe"
797
- }
798
- );
799
- break;
800
- }
801
- } else {
802
- proc = spawn(["unzip", "-o", archivePath, "-d", destDir], {
803
- stdout: "ignore",
804
- stderr: "pipe"
805
- });
806
- }
807
- const exitCode = await proc.exited;
808
- if (exitCode !== 0) {
809
- const stderr = await new Response(proc.stderr).text();
810
- throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
811
- }
812
- }
813
-
814
- // src/hooks/chat-headers.ts
815
- var INTERNAL_MARKER_CACHE_LIMIT = 1e3;
816
- var internalMarkerCache = /* @__PURE__ */ new Map();
817
- function getProviderID(input) {
818
- return input.provider.info?.id || input.model.providerID;
819
- }
820
- function isCopilotProvider(providerID) {
821
- return providerID === "github-copilot" || providerID === "github-copilot-enterprise";
822
- }
823
- async function hasInternalMarker(client, sessionID, messageID) {
824
- const cacheKey = `${sessionID}:${messageID}`;
825
- const cached = internalMarkerCache.get(cacheKey);
826
- if (cached !== void 0) {
827
- return cached;
828
- }
829
- try {
830
- const response = await client.session.message({
831
- path: { id: sessionID, messageID }
832
- });
833
- const hasMarker = (response.data?.parts ?? []).some(
834
- hasInternalInitiatorMarker
835
- );
836
- if (hasMarker) {
837
- if (internalMarkerCache.size >= INTERNAL_MARKER_CACHE_LIMIT) {
838
- internalMarkerCache.clear();
839
- }
840
- internalMarkerCache.set(cacheKey, true);
841
- }
842
- return hasMarker;
843
- } catch {
844
- return false;
845
- }
846
- }
847
- function createChatHeadersHook(ctx) {
848
- return {
849
- "chat.headers": async (input, output) => {
850
- if (!isCopilotProvider(getProviderID(input))) {
851
- return;
852
- }
853
- if (input.model.api.npm === "@ai-sdk/github-copilot") {
854
- return;
855
- }
856
- if (!input.message.id || input.message.role !== "user") {
857
- return;
858
- }
859
- if (!await hasInternalMarker(
860
- ctx.client,
861
- input.sessionID,
862
- input.message.id
863
- )) {
864
- return;
865
- }
866
- output.headers["x-initiator"] = "agent";
867
- }
868
- };
869
- }
870
-
871
- // src/hooks/delegate-task-retry/patterns.ts
872
- var DELEGATE_TASK_ERROR_PATTERNS = [
873
- {
874
- pattern: "load_skills",
875
- errorType: "missing_load_skills",
876
- fixHint: "Add load_skills=[] (empty array when no skill is needed)."
877
- },
878
- {
879
- pattern: "category OR subagent_type",
880
- errorType: "mutual_exclusion",
881
- fixHint: 'Provide only one: category (e.g., "unspecified-low") OR subagent_type (e.g., "explorer").'
882
- },
883
- {
884
- pattern: "Must provide either category or subagent_type",
885
- errorType: "missing_category_or_agent",
886
- fixHint: 'Add either category="unspecified-low" or subagent_type="explorer".'
887
- },
888
- {
889
- pattern: "Unknown category",
890
- errorType: "unknown_category",
891
- fixHint: "Use a valid category listed in the error output."
892
- },
893
- {
894
- pattern: "Unknown agent",
895
- errorType: "unknown_agent",
896
- fixHint: "Use a valid agent name from the available list."
897
- },
898
- {
899
- pattern: "Skills not found",
900
- errorType: "unknown_skills",
901
- fixHint: "Use valid skill names listed in the error output."
902
- },
903
- {
904
- pattern: "is not allowed. Allowed agents:",
905
- errorType: "background_agent_not_allowed",
906
- fixHint: "Use one of the allowed agents shown in the error or delegate from a parent agent that can call this subagent."
907
- }
908
- ];
909
- function detectDelegateTaskError(output) {
910
- if (!output || typeof output !== "string") return null;
911
- const hasErrorSignal = output.includes("[ERROR]") || output.includes("Invalid arguments") || output.includes("is not allowed. Allowed agents:");
912
- if (!hasErrorSignal) return null;
913
- for (const pattern of DELEGATE_TASK_ERROR_PATTERNS) {
914
- if (output.includes(pattern.pattern)) {
915
- return {
916
- errorType: pattern.errorType,
917
- originalOutput: output
918
- };
919
- }
920
- }
921
- return null;
922
- }
923
-
924
- // src/hooks/delegate-task-retry/guidance.ts
925
- function extractAvailableList(output) {
926
- const match = output.match(/Allowed agents:\s*(.+)$/m);
927
- if (match) return match[1].trim();
928
- const available = output.match(/Available[^:]*:\s*(.+)$/m);
929
- if (available) return available[1].trim();
930
- return null;
931
- }
932
- function buildRetryGuidance(errorInfo) {
933
- const pattern = DELEGATE_TASK_ERROR_PATTERNS.find(
934
- (p) => p.errorType === errorInfo.errorType
935
- );
936
- if (!pattern) {
937
- return "\n[delegate-task retry] Fix parameters and retry with corrected arguments.";
938
- }
939
- const available = extractAvailableList(errorInfo.originalOutput);
940
- const lines = [
941
- "",
942
- "[delegate-task retry suggestion]",
943
- `Error type: ${errorInfo.errorType}`,
944
- `Fix: ${pattern.fixHint}`
945
- ];
946
- if (available) {
947
- lines.push(`Available: ${available}`);
948
- }
949
- if (errorInfo.errorType === "background_agent_not_allowed") {
950
- lines.push(
951
- "Retry now with corrected parameters.",
952
- "Use `background=true` only for `explorer` or `librarian`; otherwise retry without background.",
953
- "If the host enables `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`, collect results with `task_status` before reporting completion.",
954
- "Examples:",
955
- 'task(description="...", prompt="...", subagent_type="explorer", background=true)',
956
- 'task_status(task_id="ses_...", wait=true)'
957
- );
958
- return lines.join("\n");
959
- }
960
- lines.push(
961
- "Retry now with corrected parameters. Example:",
962
- 'task(description="...", prompt="...", subagent_type="explorer")'
963
- );
964
- return lines.join("\n");
965
- }
966
-
967
- // src/hooks/delegate-task-retry/hook.ts
968
- function createDelegateTaskRetryHook(_ctx) {
969
- return {
970
- "tool.execute.after": async (input, output) => {
971
- const toolName = input.tool.toLowerCase();
972
- const isDelegateTool = toolName === "task";
973
- if (!isDelegateTool) return;
974
- if (typeof output.output !== "string") return;
975
- const detected = detectDelegateTaskError(output.output);
976
- if (!detected) return;
977
- output.output += `
978
- ${buildRetryGuidance(detected)}`;
979
- }
980
- };
981
- }
982
-
983
- // src/hooks/foreground-fallback/index.ts
984
- var RATE_LIMIT_PATTERNS = [
985
- /\b429\b/,
986
- /rate.?limit/i,
987
- /too many requests/i,
988
- /quota.?exceeded/i,
989
- /usage.?exceeded/i,
990
- /usage limit/i,
991
- /overloaded/i,
992
- /resource.?exhausted/i,
993
- /insufficient.?quota/i,
994
- /high concurrency/i,
995
- /reduce concurrency/i
996
- ];
997
- function isRateLimitError(error) {
998
- if (!error || typeof error !== "object") return false;
999
- const err = error;
1000
- const text = [
1001
- err.message ?? "",
1002
- String(err.data?.statusCode ?? ""),
1003
- err.data?.message ?? "",
1004
- err.data?.responseBody ?? ""
1005
- ].join(" ");
1006
- return RATE_LIMIT_PATTERNS.some((p) => p.test(text));
1007
- }
1008
- function parseModel(model) {
1009
- const slash = model.indexOf("/");
1010
- if (slash <= 0 || slash >= model.length - 1) return null;
1011
- return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
1012
- }
1013
- var DEDUP_WINDOW_MS = 5e3;
1014
- var ForegroundFallbackManager = class {
1015
- constructor(client, chains, enabled) {
1016
- this.client = client;
1017
- this.chains = chains;
1018
- this.enabled = enabled;
646
+ var DEDUP_WINDOW_MS = 5e3;
647
+ var ForegroundFallbackManager = class {
648
+ constructor(client, chains, enabled) {
649
+ this.client = client;
650
+ this.chains = chains;
651
+ this.enabled = enabled;
1019
652
  }
1020
653
  client;
1021
654
  chains;
@@ -1259,690 +892,383 @@ function createJsonErrorRecoveryHook(_ctx) {
1259
892
  if (typeof output.output !== "string") return;
1260
893
  if (output.output.includes(JSON_ERROR_REMINDER_MARKER)) return;
1261
894
  const outputText = output.output;
1262
- const hasJsonError = JSON_ERROR_PATTERNS.some(
1263
- (pattern) => pattern.test(outputText)
1264
- );
1265
- if (hasJsonError) {
1266
- output.output += `
1267
- ${JSON_ERROR_REMINDER}`;
1268
- }
1269
- }
1270
- };
1271
- }
1272
-
1273
- // src/hooks/phase-reminder/index.ts
1274
- var PHASE_REMINDER = `<reminder>Recall Workflow Rules:
1275
- Understand \u2192 split discovery into surgical probes with explorer/librarian \u2192 synthesize the decision and internal handoff \u2192 execute \u2192 verify.
1276
- If delegating, write sub-agent prompts in English and launch the specialist in the same turn you mention it. If multiple delegations are independent, emit all tool calls in a single response.
1277
- Before write-capable dispatch, give concrete scope, anchors, steps, non-goals, and verification.
1278
- In SDD, after oracle returns [OKAY], give a deep approved-plan overview, then ask the user before implementation.</reminder>`;
1279
- var PHASE_REMINDER_SEPARATOR = "\n\n---\n\n";
1280
- function stripPhaseReminder(text) {
1281
- if (!text) {
1282
- return "";
1283
- }
1284
- const prefix = `${PHASE_REMINDER}${PHASE_REMINDER_SEPARATOR}`;
1285
- return text.startsWith(prefix) ? text.slice(prefix.length) : text;
1286
- }
1287
- function createPhaseReminderHook() {
1288
- return {
1289
- "experimental.chat.messages.transform": async (_input, output) => {
1290
- const { messages } = output;
1291
- if (messages.length === 0) {
1292
- return;
1293
- }
1294
- let lastUserMessageIndex = -1;
1295
- for (let i = messages.length - 1; i >= 0; i--) {
1296
- if (messages[i].info.role === "user") {
1297
- lastUserMessageIndex = i;
1298
- break;
1299
- }
1300
- }
1301
- if (lastUserMessageIndex === -1) {
1302
- return;
1303
- }
1304
- const lastUserMessage = messages[lastUserMessageIndex];
1305
- const agent = lastUserMessage.info.agent;
1306
- if (agent && agent !== "orchestrator") {
1307
- return;
1308
- }
1309
- const textPartIndex = lastUserMessage.parts.findIndex(
1310
- (p) => p.type === "text" && p.text !== void 0
895
+ const hasJsonError = JSON_ERROR_PATTERNS.some(
896
+ (pattern) => pattern.test(outputText)
1311
897
  );
1312
- if (textPartIndex === -1) {
1313
- return;
1314
- }
1315
- const originalText = lastUserMessage.parts[textPartIndex].text ?? "";
1316
- if (originalText.includes(LITE_INTERNAL_INITIATOR_MARKER)) {
1317
- return;
898
+ if (hasJsonError) {
899
+ output.output += `
900
+ ${JSON_ERROR_REMINDER}`;
1318
901
  }
1319
- lastUserMessage.parts[textPartIndex].text = `${PHASE_REMINDER}${PHASE_REMINDER_SEPARATOR}${originalText}`;
1320
902
  }
1321
903
  };
1322
904
  }
1323
905
 
1324
- // src/hooks/post-read-nudge/index.ts
1325
- var NUDGE = "\n\n---\nWorkflow Reminder: delegate based on rules; launch specialists in this same turn. If multiple delegations are independent, emit all tool calls in one response.";
1326
- function createPostReadNudgeHook() {
906
+ // src/mcp/index.ts
907
+ function createAllBuiltinMcps() {
1327
908
  return {
1328
- "tool.execute.after": async (input, output) => {
1329
- if (input.tool !== "Read" && input.tool !== "read") {
1330
- return;
1331
- }
1332
- output.output = output.output + NUDGE;
1333
- }
909
+ exa,
910
+ context7,
911
+ grep_app
1334
912
  };
1335
913
  }
914
+ function createBuiltinMcps(disabledMcps = []) {
915
+ const allBuiltinMcps = createAllBuiltinMcps();
916
+ return Object.fromEntries(
917
+ Object.entries(allBuiltinMcps).filter(
918
+ ([name]) => !disabledMcps.includes(name)
919
+ )
920
+ );
921
+ }
1336
922
 
1337
- // src/hooks/skill-sync.ts
1338
- function formatSkillSyncSummary(report) {
1339
- if (report.updatedSkills.length === 0 && report.removedSkills.length === 0 && report.failedSkills.length === 0) {
1340
- return "Skills up to date";
1341
- }
1342
- const parts = [];
1343
- if (report.updatedSkills.length > 0) {
1344
- parts.push(
1345
- `Updated ${report.updatedSkills.length} skill${report.updatedSkills.length === 1 ? "" : "s"}`
1346
- );
1347
- }
1348
- if (report.removedSkills.length > 0) {
1349
- parts.push(
1350
- `removed ${report.removedSkills.length} obsolete skill${report.removedSkills.length === 1 ? "" : "s"}`
1351
- );
923
+ // src/tools/ast-grep/tools.ts
924
+ import { tool } from "@opencode-ai/plugin/tool";
925
+
926
+ // src/tools/ast-grep/cli.ts
927
+ import { existsSync as existsSync6 } from "fs";
928
+
929
+ // src/tools/ast-grep/constants.ts
930
+ import { spawnSync as spawnSync2 } from "child_process";
931
+ import { existsSync as existsSync5, statSync as statSync2 } from "fs";
932
+ import { createRequire as createRequire3 } from "module";
933
+ import { dirname as dirname2, join as join6 } from "path";
934
+
935
+ // src/tools/ast-grep/downloader.ts
936
+ import { chmodSync, existsSync as existsSync4, mkdirSync, unlinkSync } from "fs";
937
+ import { createRequire as createRequire2 } from "module";
938
+ import { homedir as homedir2 } from "os";
939
+ import { join as join5 } from "path";
940
+
941
+ // src/utils/tmux.ts
942
+ var tmuxPath = null;
943
+ var tmuxChecked = false;
944
+ var storedConfig = null;
945
+ var serverAvailable = null;
946
+ var serverCheckUrl = null;
947
+ async function isServerRunning(serverUrl) {
948
+ if (serverCheckUrl === serverUrl && serverAvailable === true) {
949
+ return true;
1352
950
  }
1353
- if (report.failedSkills.length > 0) {
1354
- parts.push(
1355
- `failed ${report.failedSkills.length} skill${report.failedSkills.length === 1 ? "" : "s"}`
1356
- );
951
+ const healthUrl = new URL("/health", serverUrl).toString();
952
+ const timeoutMs = 3e3;
953
+ const maxAttempts = 2;
954
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
955
+ const controller = new AbortController();
956
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
957
+ let response = null;
958
+ try {
959
+ response = await fetch(healthUrl, { signal: controller.signal }).catch(
960
+ () => null
961
+ );
962
+ } finally {
963
+ clearTimeout(timeout);
964
+ }
965
+ const available = response?.ok ?? false;
966
+ if (available) {
967
+ serverCheckUrl = serverUrl;
968
+ serverAvailable = true;
969
+ log("[tmux] isServerRunning: checked", { serverUrl, available, attempt });
970
+ return true;
971
+ }
972
+ if (attempt < maxAttempts) {
973
+ await new Promise((r) => setTimeout(r, 250));
974
+ }
1357
975
  }
1358
- return parts.join(", ");
976
+ log("[tmux] isServerRunning: checked", { serverUrl, available: false });
977
+ return false;
1359
978
  }
1360
- function syncSkillsOnStartup(dependencies = {}) {
1361
- const install = dependencies.install ?? installCustomSkills;
1362
- const logger = dependencies.logger ?? log;
1363
- const warn = dependencies.warn ?? console.warn;
1364
- const errorLogger = dependencies.error ?? console.error;
979
+ async function findTmuxPath() {
980
+ const isWindows = process.platform === "win32";
981
+ const cmd = isWindows ? "where" : "which";
1365
982
  try {
1366
- const report = install();
1367
- const summary = formatSkillSyncSummary(report);
1368
- logger(`[skill-sync] ${summary}`, {
1369
- updatedSkills: report.updatedSkills.map(({ skill }) => skill.name),
1370
- removedSkills: report.removedSkills,
1371
- skippedSkills: report.skippedSkills.map((skill) => skill.name),
1372
- failedSkills: report.failedSkills.map(({ skill }) => skill.name)
983
+ const proc = spawn([cmd, "tmux"], {
984
+ stdout: "pipe",
985
+ stderr: "pipe"
1373
986
  });
1374
- if (report.failedSkills.length > 0) {
1375
- warn(
1376
- `[skill-sync] Failed to sync ${report.failedSkills.length} bundled skill${report.failedSkills.length === 1 ? "" : "s"}`
1377
- );
987
+ const exitCode = await proc.exited;
988
+ if (exitCode !== 0) {
989
+ log("[tmux] findTmuxPath: 'which tmux' failed", { exitCode });
990
+ return null;
991
+ }
992
+ const stdout = await new Response(proc.stdout).text();
993
+ const path4 = stdout.trim().split("\n")[0];
994
+ if (!path4) {
995
+ log("[tmux] findTmuxPath: no path in output");
996
+ return null;
1378
997
  }
1379
- } catch (error) {
1380
- errorLogger("[skill-sync] Failed to sync bundled skills on startup");
1381
- logger("[skill-sync] Skill sync failed during plugin startup", error);
998
+ const verifyProc = spawn([path4, "-V"], {
999
+ stdout: "pipe",
1000
+ stderr: "pipe"
1001
+ });
1002
+ const verifyExit = await verifyProc.exited;
1003
+ if (verifyExit !== 0) {
1004
+ log("[tmux] findTmuxPath: tmux -V failed", { path: path4, verifyExit });
1005
+ return null;
1006
+ }
1007
+ log("[tmux] findTmuxPath: found tmux", { path: path4 });
1008
+ return path4;
1009
+ } catch (err) {
1010
+ log("[tmux] findTmuxPath: exception", { error: String(err) });
1011
+ return null;
1382
1012
  }
1383
1013
  }
1384
-
1385
- // src/thoth/client.ts
1386
- var DEFAULT_THOTH_HTTP_PORT = 7438;
1387
- var DEFAULT_THOTH_TIMEOUT_MS = 15e3;
1388
- function isRecord2(value) {
1389
- return typeof value === "object" && value !== null;
1014
+ async function getTmuxPath() {
1015
+ if (tmuxChecked) {
1016
+ return tmuxPath;
1017
+ }
1018
+ tmuxPath = await findTmuxPath();
1019
+ tmuxChecked = true;
1020
+ log("[tmux] getTmuxPath: initialized", { tmuxPath });
1021
+ return tmuxPath;
1390
1022
  }
1391
- function normalizeText(value) {
1392
- return typeof value === "string" ? value.trim() || null : null;
1023
+ function isInsideTmux() {
1024
+ return !!process.env.TMUX;
1393
1025
  }
1394
- function previewText(value, max) {
1395
- const text = normalizeText(value);
1396
- if (!text) {
1397
- return null;
1026
+ async function applyLayout(tmux, layout, mainPaneSize) {
1027
+ try {
1028
+ const layoutProc = spawn([tmux, "select-layout", layout], {
1029
+ stdout: "pipe",
1030
+ stderr: "pipe"
1031
+ });
1032
+ await layoutProc.exited;
1033
+ if (layout === "main-horizontal" || layout === "main-vertical") {
1034
+ const sizeOption = layout === "main-horizontal" ? "main-pane-height" : "main-pane-width";
1035
+ const sizeProc = spawn(
1036
+ [tmux, "set-window-option", sizeOption, `${mainPaneSize}%`],
1037
+ {
1038
+ stdout: "pipe",
1039
+ stderr: "pipe"
1040
+ }
1041
+ );
1042
+ await sizeProc.exited;
1043
+ const reapplyProc = spawn([tmux, "select-layout", layout], {
1044
+ stdout: "pipe",
1045
+ stderr: "pipe"
1046
+ });
1047
+ await reapplyProc.exited;
1048
+ }
1049
+ log("[tmux] applyLayout: applied", { layout, mainPaneSize });
1050
+ } catch (err) {
1051
+ log("[tmux] applyLayout: exception", { error: String(err) });
1398
1052
  }
1399
- return text.length > max ? `${text.slice(0, max)}...` : text;
1400
1053
  }
1401
- function formatMemoryContext(response) {
1402
- if (!isRecord2(response)) {
1403
- return null;
1054
+ async function spawnTmuxPane(sessionId, description, config, serverUrl) {
1055
+ log("[tmux] spawnTmuxPane called", {
1056
+ sessionId,
1057
+ description,
1058
+ config,
1059
+ serverUrl
1060
+ });
1061
+ if (!config.enabled) {
1062
+ log("[tmux] spawnTmuxPane: config.enabled is false, skipping");
1063
+ return { success: false };
1404
1064
  }
1405
- const sessions = Array.isArray(response.sessions) ? response.sessions : [];
1406
- const observations = Array.isArray(response.observations) ? response.observations : [];
1407
- const prompts = Array.isArray(response.prompts) ? response.prompts : [];
1408
- if (sessions.length === 0 && observations.length === 0 && prompts.length === 0) {
1409
- return null;
1065
+ if (!isInsideTmux()) {
1066
+ log("[tmux] spawnTmuxPane: not inside tmux, skipping");
1067
+ return { success: false };
1410
1068
  }
1411
- const lines = ["## Memory Context", ""];
1412
- if (sessions.length > 0) {
1413
- lines.push("### Recent Sessions");
1414
- for (const rawSession of sessions) {
1415
- const sessionId = normalizeText(rawSession.id) ?? "unknown-session";
1416
- const project = normalizeText(rawSession.project) ?? "unknown-project";
1417
- const startedAt = normalizeText(rawSession.started_at) ?? "unknown";
1418
- lines.push(`- [${sessionId}] (${project}) \u2014 started ${startedAt}`);
1419
- const summary = previewText(rawSession.summary, 300);
1420
- if (summary) {
1421
- lines.push(` Summary: ${summary}`);
1422
- }
1423
- }
1424
- lines.push("");
1425
- }
1426
- if (observations.length > 0) {
1427
- lines.push("### Recent Observations");
1428
- for (const rawObservation of observations) {
1429
- const type = normalizeText(rawObservation.type) ?? "manual";
1430
- const title = normalizeText(rawObservation.title) ?? "Untitled";
1431
- const createdAt = normalizeText(rawObservation.created_at) ?? "unknown";
1432
- lines.push(`- [${type}] ${title} (${createdAt})`);
1433
- const content = previewText(rawObservation.content, 300);
1434
- if (content) {
1435
- lines.push(` ${content}`);
1436
- }
1437
- }
1438
- lines.push("");
1069
+ const serverRunning = await isServerRunning(serverUrl);
1070
+ if (!serverRunning) {
1071
+ const defaultPort = process.env.OPENCODE_PORT ?? "4096";
1072
+ log("[tmux] spawnTmuxPane: OpenCode server not running, skipping", {
1073
+ serverUrl,
1074
+ hint: `Start opencode with --port ${defaultPort}`
1075
+ });
1076
+ return { success: false };
1439
1077
  }
1440
- if (prompts.length > 0) {
1441
- lines.push("### Recent Prompts");
1442
- for (const rawPrompt of prompts) {
1443
- const content = previewText(rawPrompt.content, 200);
1444
- if (!content) {
1445
- continue;
1446
- }
1447
- const createdAt = normalizeText(rawPrompt.created_at) ?? "unknown";
1448
- lines.push(`- ${content} (${createdAt})`);
1449
- }
1450
- lines.push("");
1078
+ const tmux = await getTmuxPath();
1079
+ if (!tmux) {
1080
+ log("[tmux] spawnTmuxPane: tmux binary not found, skipping");
1081
+ return { success: false };
1451
1082
  }
1452
- const stats = isRecord2(response.stats) ? response.stats : void 0;
1453
- if (stats) {
1454
- lines.push("### Stats");
1455
- lines.push(
1456
- `- Sessions: ${typeof stats.sessions === "number" ? stats.sessions : 0}, Observations: ${typeof stats.observations === "number" ? stats.observations : 0}, Prompts: ${typeof stats.prompts === "number" ? stats.prompts : 0}`
1457
- );
1083
+ storedConfig = config;
1084
+ try {
1085
+ const opencodeCmd = `opencode attach ${serverUrl} --session ${sessionId}`;
1086
+ const args = [
1087
+ "split-window",
1088
+ "-h",
1089
+ "-d",
1090
+ // Don't switch focus to new pane
1091
+ "-P",
1092
+ // Print pane info
1093
+ "-F",
1094
+ "#{pane_id}",
1095
+ // Format: just the pane ID
1096
+ opencodeCmd
1097
+ ];
1098
+ log("[tmux] spawnTmuxPane: executing", { tmux, args, opencodeCmd });
1099
+ const proc = spawn([tmux, ...args], {
1100
+ stdout: "pipe",
1101
+ stderr: "pipe"
1102
+ });
1103
+ const exitCode = await proc.exited;
1104
+ const stdout = await new Response(proc.stdout).text();
1105
+ const stderr = await new Response(proc.stderr).text();
1106
+ const paneId = stdout.trim();
1107
+ log("[tmux] spawnTmuxPane: split result", {
1108
+ exitCode,
1109
+ paneId,
1110
+ stderr: stderr.trim()
1111
+ });
1112
+ if (exitCode === 0 && paneId) {
1113
+ const renameProc = spawn(
1114
+ [tmux, "select-pane", "-t", paneId, "-T", description.slice(0, 30)],
1115
+ { stdout: "ignore", stderr: "ignore" }
1116
+ );
1117
+ await renameProc.exited;
1118
+ const layout = config.layout ?? "main-vertical";
1119
+ const mainPaneSize = config.main_pane_size ?? 60;
1120
+ await applyLayout(tmux, layout, mainPaneSize);
1121
+ log("[tmux] spawnTmuxPane: SUCCESS, pane created and layout applied", {
1122
+ paneId,
1123
+ layout
1124
+ });
1125
+ return { success: true, paneId };
1126
+ }
1127
+ return { success: false };
1128
+ } catch (err) {
1129
+ log("[tmux] spawnTmuxPane: exception", { error: String(err) });
1130
+ return { success: false };
1458
1131
  }
1459
- return lines.join("\n");
1460
1132
  }
1461
- var HttpThothClient = class {
1462
- enabled;
1463
- baseUrl;
1464
- timeoutMs;
1465
- project;
1466
- directory;
1467
- constructor(options) {
1468
- this.enabled = options.enabled !== false;
1469
- const port = options.httpPort ?? DEFAULT_THOTH_HTTP_PORT;
1470
- this.baseUrl = `http://127.0.0.1:${port}`;
1471
- this.timeoutMs = options.timeoutMs ?? DEFAULT_THOTH_TIMEOUT_MS;
1472
- this.project = options.project;
1473
- this.directory = options.directory;
1474
- }
1475
- async memContext(sessionId, limit = 10) {
1476
- const params = new URLSearchParams({
1477
- project: this.project,
1478
- limit: String(limit)
1133
+ async function closeTmuxPane(paneId) {
1134
+ log("[tmux] closeTmuxPane called", { paneId });
1135
+ if (!paneId) {
1136
+ log("[tmux] closeTmuxPane: no paneId provided");
1137
+ return false;
1138
+ }
1139
+ const tmux = await getTmuxPath();
1140
+ if (!tmux) {
1141
+ log("[tmux] closeTmuxPane: tmux binary not found");
1142
+ return false;
1143
+ }
1144
+ try {
1145
+ log("[tmux] closeTmuxPane: sending Ctrl+C for graceful shutdown", {
1146
+ paneId
1147
+ });
1148
+ const ctrlCProc = spawn([tmux, "send-keys", "-t", paneId, "C-c"], {
1149
+ stdout: "pipe",
1150
+ stderr: "pipe"
1479
1151
  });
1480
- if (sessionId) {
1481
- params.set("session_id", sessionId);
1152
+ await ctrlCProc.exited;
1153
+ await new Promise((r) => setTimeout(r, 250));
1154
+ log("[tmux] closeTmuxPane: killing pane", { paneId });
1155
+ const proc = spawn([tmux, "kill-pane", "-t", paneId], {
1156
+ stdout: "pipe",
1157
+ stderr: "pipe"
1158
+ });
1159
+ const exitCode = await proc.exited;
1160
+ const stderr = await new Response(proc.stderr).text();
1161
+ log("[tmux] closeTmuxPane: result", { exitCode, stderr: stderr.trim() });
1162
+ if (exitCode === 0) {
1163
+ log("[tmux] closeTmuxPane: SUCCESS, pane closed", { paneId });
1164
+ if (storedConfig) {
1165
+ const layout = storedConfig.layout ?? "main-vertical";
1166
+ const mainPaneSize = storedConfig.main_pane_size ?? 60;
1167
+ await applyLayout(tmux, layout, mainPaneSize);
1168
+ log("[tmux] closeTmuxPane: layout reapplied", { layout });
1169
+ }
1170
+ return true;
1482
1171
  }
1483
- const response = await this.httpGet(`/context?${params.toString()}`);
1484
- return formatMemoryContext(response);
1485
- }
1486
- async memSessionStart(sessionId) {
1487
- const response = await this.httpPost("/sessions", {
1488
- id: sessionId,
1489
- project: this.project,
1490
- directory: this.directory
1172
+ log("[tmux] closeTmuxPane: failed (pane may already be closed)", {
1173
+ paneId
1491
1174
  });
1492
- return response !== null;
1175
+ return false;
1176
+ } catch (err) {
1177
+ log("[tmux] closeTmuxPane: exception", { error: String(err) });
1178
+ return false;
1493
1179
  }
1494
- async memSavePrompt(sessionId, content) {
1495
- const response = await this.httpPost("/prompts", {
1496
- session_id: sessionId,
1497
- content,
1498
- project: this.project
1180
+ }
1181
+ function startTmuxCheck() {
1182
+ if (!tmuxChecked) {
1183
+ getTmuxPath().catch(() => {
1499
1184
  });
1500
- return response !== null;
1501
- }
1502
- async httpPost(path5, body) {
1503
- if (!this.enabled) {
1504
- return null;
1505
- }
1506
- try {
1507
- const response = await fetch(`${this.baseUrl}${path5}`, {
1508
- method: "POST",
1509
- headers: { "Content-Type": "application/json" },
1510
- body: JSON.stringify(body),
1511
- signal: AbortSignal.timeout(this.timeoutMs)
1512
- });
1513
- if (!response.ok) {
1514
- return null;
1515
- }
1516
- return await response.json();
1517
- } catch (error) {
1518
- log("[thoth] HTTP POST unavailable", {
1519
- path: path5,
1520
- error: error instanceof Error ? error.message : String(error)
1521
- });
1522
- return null;
1523
- }
1524
- }
1525
- async httpGet(path5) {
1526
- if (!this.enabled) {
1527
- return null;
1528
- }
1529
- try {
1530
- const response = await fetch(`${this.baseUrl}${path5}`, {
1531
- signal: AbortSignal.timeout(this.timeoutMs)
1532
- });
1533
- if (!response.ok) {
1534
- return null;
1535
- }
1536
- return await response.json();
1537
- } catch (error) {
1538
- log("[thoth] HTTP GET unavailable", {
1539
- path: path5,
1540
- error: error instanceof Error ? error.message : String(error)
1541
- });
1542
- return null;
1543
- }
1544
1185
  }
1545
- };
1546
- function createThothClient(options) {
1547
- return new HttpThothClient(options);
1548
1186
  }
1549
1187
 
1550
- // src/hooks/thoth-mem/protocol.ts
1551
- var SDD_TOPIC_KEY_FORMAT = "sdd/{change}/{artifact}";
1552
- var FIRST_ACTION_INSTRUCTION = 'FIRST ACTION REQUIRED: Call mem_session(action="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(recall_query=...) for fused recent context, and use the recall funnel (mem_recall(mode="compact") -> mem_recall(mode="context") -> mem_get(...)) for precise retrieval.';
1553
- var SESSION_SUMMARY_TEMPLATE = `Use this exact structure for \`mem_session(action="summary")\` content:
1554
-
1555
- ## Goal
1556
- [What we were working on this session]
1557
-
1558
- ## Instructions
1559
- [User preferences or constraints discovered during this session]
1560
-
1561
- ## Discoveries
1562
- - [Technical findings, gotchas, non-obvious learnings]
1563
-
1564
- ## Accomplished
1565
- - [Completed items with key details]
1566
-
1567
- ## Next Steps
1568
- - [What remains to be done]
1569
-
1570
- ## Relevant Files
1571
- - path/to/file.ts - [what it does or what changed]`;
1572
- function buildCompactionReminder(sessionID) {
1573
- return `FIRST ACTION REQUIRED: this session was compacted. Call \`mem_session(action="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_context(recall_query=...)\` for fused recent context, and use the recall funnel (\`mem_recall(mode="compact")\` -> \`mem_recall(mode="context")\` -> \`mem_get(...)\`) for precise retrieval.`;
1574
- }
1575
- function buildCompactorInstruction(project) {
1576
- 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(action="summary") with the content of this compacted summary. Use project: '${project}'. This preserves what was accomplished before compaction. Do this BEFORE any other work."`;
1577
- }
1578
- function buildMemoryInstructions(sessionID, project) {
1579
- return `
1580
- <memory_protocol>
1581
- Persistent memory is available through thoth-mem. Follow this protocol.
1582
-
1583
- IMPORTANT: Your current session_id is \`${sessionID}\` and project is \`${project}\`.
1584
- Always pass these values when calling memory tools that accept them.
1585
-
1586
- ### CORE TOOLS
1587
- mem_save, mem_recall, mem_context, mem_get, mem_project, mem_session
1588
-
1589
- ### OWNERSHIP AND SUBAGENT HANDOFF
1590
- - Root owns \`mem_session(action="start"|"checkpoint"|"summary")\` and \`mem_save(kind="prompt"|"session_summary")\`.
1591
- - Start root memory-backed workflows with \`mem_session(action="start")\` before any other thoth-mem operation when tools and identity are available.
1592
- - Save the real user prompt with \`mem_save(kind="prompt")\`; never save generated subagent prompts as user intent.
1593
- - Before memory-dependent delegation, save the handoff body with \`mem_session(action="summary")\` or \`mem_save(kind="session_summary")\`.
1594
- - Subagent handoff prompts carry parent \`session_id\`, \`project\`, permissions, and recovery instructions only; do not ask subagents to own session lifecycle actions or save prompts.
1595
-
1596
- ### WHEN TO SAVE
1597
- Call \`mem_save(kind="observation")\` IMMEDIATELY after ANY of these:
1598
- - Architecture, design, or workflow decision made
1599
- - Bug fixed (include root cause)
1600
- - Non-obvious discovery, gotcha, or edge case found
1601
- - Configuration change or environment setup
1602
- - Pattern or convention established (naming, structure, approach)
1603
- - User preference or constraint learned
1604
- - Feature implemented with non-obvious approach
1605
- - User confirms a recommendation ("dale", "go with that", "sounds good", "s\xED, esa")
1606
- - User rejects an approach or expresses a preference ("no, better X", "I prefer X")
1607
- - Discussion concludes with a clear direction chosen
1608
-
1609
- Use \`title\` as Verb + what changed or was learned.
1610
- Use \`kind\` intentionally: \`observation\`, \`prompt\`, \`session_summary\`, or \`passive_learnings\`.
1611
- Use \`type\` from: bugfix | decision | architecture | discovery | pattern | config | learning | manual.
1612
- Set \`scope\` intentionally.
1613
- Reuse a stable \`topic_key\` for the same evolving topic. Do not overwrite unrelated topics.
1614
- Put durable observation details in \`content\` with this structure:
1615
- - What: concise description of what changed or was learned
1616
- - Why: why it mattered or what problem it solved
1617
- - Where: files, paths, or systems involved
1618
- - Learned: edge cases, caveats, or follow-up notes
1619
-
1620
- **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(kind="observation") NOW."
1621
-
1622
- ### RECALL FUNNEL
1623
- - Broad recovery (session start, after compaction): call \`mem_context(recall_query=...)\` for fused recent context when useful.
1624
- - Targeted retrieval:
1625
- 1. Call \`mem_recall(mode="compact")\` to scan candidate IDs and titles.
1626
- 2. Call \`mem_recall(mode="context")\` to expand the strongest hits into retrieved context.
1627
- 3. Call \`mem_get(...)\` only for records you need in full.
1628
- - Use HyDE/fused recall for semantic or ambiguous searches.
1629
- - Set \`mem_recall\` \`limit\` from 1 to 20 for candidate/result count.
1630
- - Narrow with \`topic_key\`, \`type\`, \`time_from\`, \`time_to\`, \`scope\`, \`project\`, and \`session_id\` filters.
1631
- - Use \`mem_get\` with \`kind="observation"|"prompt"\`; use \`mem_get(include_timeline=true)\` with \`before\`/\`after\`, and \`offset\`/\`max_length\` for large content.
1632
- - Use bounded \`mem_project(action="graph"|"topics"|"topic")\` for relationship and topic navigation; \`mem_project(action="graph")\` relations are \`HAS_TYPE\`, \`IN_PROJECT\`, \`HAS_TOPIC_KEY\`, \`HAS_WHAT\`, \`HAS_WHY\`, \`HAS_WHERE\`, and \`HAS_LEARNED\`; these calls supplement, not replace, the recall funnel.
1633
- - Search proactively on the first message about a project, feature, or problem when prior context may matter.
1634
- - Search before starting work that may have been done before.
1635
- - Search when the user mentions a topic that lacks enough local context.
1636
-
1637
- ### SESSION CLOSE PROTOCOL
1638
- - Before ending the session, call \`mem_session(action="summary")\` with this exact template.
1639
- - This is NOT optional. If you skip this, the next session starts blind.
1640
- - Do not claim memory was saved unless the tool call succeeded.
1641
- - If your response includes \`## Key Learnings:\`, preserve them with \`mem_save(kind="passive_learnings")\`.
1642
-
1643
- ${SESSION_SUMMARY_TEMPLATE}
1644
-
1645
- ### AFTER COMPACTION
1646
- - IMMEDIATELY call \`mem_session(action="summary")\` with the compacted summary content.
1647
- - Then call \`mem_context(recall_query=...)\` for fused recent context.
1648
- - Use the recall funnel (\`mem_recall(mode="compact")\` -> \`mem_recall(mode="context")\` -> \`mem_get(...)\`) for precise artifact/prior-observation retrieval.
1649
- - Only then continue working.
1650
-
1651
- ### SDD TOPIC KEY CONVENTION
1652
- - use ${SDD_TOPIC_KEY_FORMAT}
1653
- - examples: sdd/add-user-auth/spec, sdd/add-user-auth/design, sdd/add-user-auth/tasks
1654
- </memory_protocol>
1655
- `.trim();
1656
- }
1657
- function buildSaveNudge() {
1658
- 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.";
1659
- }
1660
-
1661
- // src/hooks/thoth-mem/index.ts
1662
- function isRootSession(session) {
1663
- return !session.parentID;
1664
- }
1665
- function getSessionInfo(event) {
1666
- const info = event.properties?.info;
1667
- return info?.id ? info : null;
1668
- }
1669
- function isTextPart(part) {
1670
- return part.type === "text";
1671
- }
1672
- function extractPromptText(parts) {
1673
- const text = parts.filter(isTextPart).map((part) => part.text.trim()).filter((part) => part.length > 0).join("\n\n");
1674
- return text.length > 0 ? text : null;
1675
- }
1676
- function appendInstruction(target, instruction) {
1677
- if (target.includes(instruction)) {
1678
- return target;
1679
- }
1680
- return `${target}
1681
-
1682
- ${instruction}`;
1683
- }
1684
- function stripPrivateTags(str) {
1685
- if (!str) {
1686
- return "";
1687
- }
1688
- return str.replace(/<private>[\s\S]*?<\/private>/gi, "[REDACTED]").trim();
1689
- }
1690
- function truncate(str, max) {
1691
- if (!str) {
1692
- return "";
1188
+ // src/utils/zip-extractor.ts
1189
+ import { release } from "os";
1190
+ var WINDOWS_BUILD_WITH_TAR = 17134;
1191
+ function getWindowsBuildNumber() {
1192
+ if (process.platform !== "win32") return null;
1193
+ const parts = release().split(".");
1194
+ if (parts.length >= 3) {
1195
+ const build = parseInt(parts[2], 10);
1196
+ if (!Number.isNaN(build)) return build;
1693
1197
  }
1694
- return str.length > max ? `${str.slice(0, max)}...` : str;
1695
- }
1696
- function sanitizePromptText(text) {
1697
- return truncate(stripPrivateTags(stripPhaseReminder(text)), 2e3);
1198
+ return null;
1698
1199
  }
1699
- function isSessionSummaryTool(toolName, args) {
1700
- if (!args || typeof args !== "object" || Array.isArray(args)) {
1701
- return false;
1702
- }
1703
- const normalized = toolName.toLowerCase();
1704
- const isMemSessionTool = normalized === "mem_session" || normalized.endsWith(".mem_session") || normalized.endsWith("_mem_session");
1705
- return isMemSessionTool && args.action === "summary";
1200
+ function isPwshAvailable() {
1201
+ if (process.platform !== "win32") return false;
1202
+ const result = spawnSync(["where", "pwsh"], {
1203
+ stdout: "pipe",
1204
+ stderr: "pipe"
1205
+ });
1206
+ return result.exitCode === 0;
1706
1207
  }
1707
- function isMemSaveTool(toolName) {
1708
- const normalized = toolName.toLowerCase();
1709
- return normalized === "mem_save" || normalized.endsWith(".mem_save") || normalized.endsWith("_mem_save");
1208
+ function escapePowerShellPath(path4) {
1209
+ return path4.replace(/'/g, "''");
1710
1210
  }
1711
- function createThothMemHook(options) {
1712
- const enabled = options.enabled !== false;
1713
- const thoth = createThothClient({
1714
- project: options.project,
1715
- directory: options.directory,
1716
- httpPort: options.thoth?.http_port,
1717
- timeoutMs: options.thoth?.timeout,
1718
- enabled
1719
- });
1720
- const trackedRootSessions = /* @__PURE__ */ new Set();
1721
- const needsCompactionFollowUp = /* @__PURE__ */ new Set();
1722
- const sessionCreatedAt = /* @__PURE__ */ new Map();
1723
- const lastMemSaveAt = /* @__PURE__ */ new Map();
1724
- const nudgePending = /* @__PURE__ */ new Set();
1725
- function isTrackedRootSession(sessionId) {
1726
- return typeof sessionId === "string" && trackedRootSessions.has(sessionId);
1727
- }
1728
- function shouldInjectSaveNudge(sessionId) {
1729
- if (nudgePending.has(sessionId)) {
1730
- return false;
1731
- }
1732
- if (needsCompactionFollowUp.has(sessionId)) {
1733
- return false;
1734
- }
1735
- const createdAt = sessionCreatedAt.get(sessionId);
1736
- if (!createdAt || Date.now() - createdAt < 5 * 60 * 1e3) {
1737
- return false;
1738
- }
1739
- const lastSave = lastMemSaveAt.get(sessionId);
1740
- if (lastSave && Date.now() - lastSave < 15 * 60 * 1e3) {
1741
- return false;
1742
- }
1743
- nudgePending.add(sessionId);
1744
- return true;
1745
- }
1746
- async function handleSessionCreated(session) {
1747
- if (!enabled || !isRootSession(session)) {
1748
- return;
1749
- }
1750
- const started = await thoth.memSessionStart(session.id);
1751
- if (started) {
1752
- trackedRootSessions.add(session.id);
1753
- sessionCreatedAt.set(session.id, Date.now());
1754
- } else {
1755
- log("[thoth] session start unavailable, skipping tracking:", session.id);
1756
- }
1757
- }
1758
- function handleSessionCompacted(session) {
1759
- if (!enabled || !isRootSession(session) || !session.id) {
1760
- return;
1761
- }
1762
- needsCompactionFollowUp.add(session.id);
1211
+ function getWindowsZipExtractor() {
1212
+ const buildNumber = getWindowsBuildNumber();
1213
+ if (buildNumber !== null && buildNumber >= WINDOWS_BUILD_WITH_TAR) {
1214
+ return "tar";
1763
1215
  }
1764
- function handleSessionDeleted(session) {
1765
- trackedRootSessions.delete(session.id);
1766
- needsCompactionFollowUp.delete(session.id);
1767
- sessionCreatedAt.delete(session.id);
1768
- lastMemSaveAt.delete(session.id);
1769
- nudgePending.delete(session.id);
1216
+ if (isPwshAvailable()) {
1217
+ return "pwsh";
1770
1218
  }
1771
- return {
1772
- event: async ({ event }) => {
1773
- switch (event.type) {
1774
- case "session.created": {
1775
- const session = getSessionInfo(event);
1776
- if (!session) {
1777
- break;
1778
- }
1779
- await handleSessionCreated(session);
1780
- break;
1781
- }
1782
- case "session.compacted": {
1783
- const session = getSessionInfo(event);
1784
- if (!session) {
1785
- break;
1219
+ return "powershell";
1220
+ }
1221
+ async function extractZip(archivePath, destDir) {
1222
+ let proc;
1223
+ if (process.platform === "win32") {
1224
+ const extractor = getWindowsZipExtractor();
1225
+ switch (extractor) {
1226
+ case "tar":
1227
+ proc = spawn(["tar", "-xf", archivePath, "-C", destDir], {
1228
+ stdout: "ignore",
1229
+ stderr: "pipe"
1230
+ });
1231
+ break;
1232
+ case "pwsh":
1233
+ proc = spawn(
1234
+ [
1235
+ "pwsh",
1236
+ "-Command",
1237
+ `Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
1238
+ ],
1239
+ {
1240
+ stdout: "ignore",
1241
+ stderr: "pipe"
1786
1242
  }
1787
- handleSessionCompacted(session);
1788
- break;
1789
- }
1790
- case "session.deleted": {
1791
- const session = getSessionInfo(event);
1792
- if (!session) {
1793
- break;
1243
+ );
1244
+ break;
1245
+ default:
1246
+ proc = spawn(
1247
+ [
1248
+ "powershell",
1249
+ "-Command",
1250
+ `Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`
1251
+ ],
1252
+ {
1253
+ stdout: "ignore",
1254
+ stderr: "pipe"
1794
1255
  }
1795
- handleSessionDeleted(session);
1796
- break;
1797
- }
1798
- }
1799
- },
1800
- "chat.message": async (input, output) => {
1801
- void output.message;
1802
- if (!enabled) {
1803
- return;
1804
- }
1805
- if (!isTrackedRootSession(input.sessionID)) {
1806
- return;
1807
- }
1808
- const promptText = extractPromptText(output.parts);
1809
- if (!promptText) {
1810
- return;
1811
- }
1812
- const sanitizedPromptText = sanitizePromptText(promptText);
1813
- if (!sanitizedPromptText || sanitizedPromptText.length <= 10) {
1814
- return;
1815
- }
1816
- await thoth.memSavePrompt(input.sessionID, sanitizedPromptText);
1817
- },
1818
- "experimental.chat.system.transform": async (input, output) => {
1819
- void input.model;
1820
- if (!enabled || !input.sessionID) {
1821
- return;
1822
- }
1823
- if (!isTrackedRootSession(input.sessionID)) {
1824
- return;
1825
- }
1826
- const memoryInstructions = buildMemoryInstructions(
1827
- input.sessionID,
1828
- options.project
1829
- );
1830
- const compactionReminder = needsCompactionFollowUp.has(input.sessionID) ? buildCompactionReminder(input.sessionID) : null;
1831
- const saveNudge = shouldInjectSaveNudge(input.sessionID) ? buildSaveNudge() : null;
1832
- if (output.system.length === 0) {
1833
- let systemPrompt = memoryInstructions;
1834
- if (compactionReminder) {
1835
- systemPrompt = appendInstruction(systemPrompt, compactionReminder);
1836
- }
1837
- if (saveNudge) {
1838
- systemPrompt = appendInstruction(systemPrompt, saveNudge);
1839
- }
1840
- output.system.push(systemPrompt);
1841
- return;
1842
- }
1843
- const lastIndex = output.system.length - 1;
1844
- let updatedSystemPrompt = appendInstruction(
1845
- output.system[lastIndex],
1846
- memoryInstructions
1847
- );
1848
- if (compactionReminder) {
1849
- updatedSystemPrompt = appendInstruction(
1850
- updatedSystemPrompt,
1851
- compactionReminder
1852
1256
  );
1853
- }
1854
- if (saveNudge) {
1855
- updatedSystemPrompt = appendInstruction(updatedSystemPrompt, saveNudge);
1856
- }
1857
- output.system[lastIndex] = updatedSystemPrompt;
1858
- },
1859
- "experimental.session.compacting": async (input, output) => {
1860
- void output.prompt;
1861
- if (!enabled) {
1862
- return;
1863
- }
1864
- if (!isTrackedRootSession(input.sessionID)) {
1865
- return;
1866
- }
1867
- needsCompactionFollowUp.add(input.sessionID);
1868
- output.context.push(buildCompactorInstruction(options.project));
1869
- output.context.push(FIRST_ACTION_INSTRUCTION);
1870
- const memoryContext = await thoth.memContext(input.sessionID);
1871
- if (memoryContext) {
1872
- output.context.push(memoryContext);
1873
- }
1874
- },
1875
- "tool.execute.after": async (input, output) => {
1876
- void input.callID;
1877
- void output.title;
1878
- void output.output;
1879
- void output.metadata;
1880
- if (!enabled) {
1881
- return;
1882
- }
1883
- if (isMemSaveTool(input.tool)) {
1884
- lastMemSaveAt.set(input.sessionID, Date.now());
1885
- nudgePending.delete(input.sessionID);
1886
- }
1887
- if (isSessionSummaryTool(input.tool, input.args)) {
1888
- needsCompactionFollowUp.delete(input.sessionID);
1889
- }
1257
+ break;
1890
1258
  }
1891
- };
1892
- }
1893
-
1894
- // src/mcp/thoth.ts
1895
- function createThothMcp(config) {
1896
- const environment = {
1897
- ...config?.data_dir ? { THOTH_DATA_DIR: config.data_dir } : {},
1898
- ...typeof config?.http_port === "number" ? { THOTH_HTTP_PORT: String(config.http_port) } : {},
1899
- ...config?.environment
1900
- };
1901
- const mcpConfig = {
1902
- type: "local",
1903
- command: [...config?.command ?? DEFAULT_THOTH_COMMAND],
1904
- ...Object.keys(environment).length > 0 ? { environment } : {},
1905
- ...typeof config?.timeout === "number" ? { timeout: config.timeout } : {}
1906
- };
1907
- return mcpConfig;
1908
- }
1909
-
1910
- // src/mcp/index.ts
1911
- function createAllBuiltinMcps(thothConfig) {
1912
- return {
1913
- exa,
1914
- context7,
1915
- grep_app,
1916
- thoth_mem: createThothMcp(thothConfig)
1917
- };
1918
- }
1919
- function createBuiltinMcps(disabledMcps = [], thothConfig) {
1920
- const allBuiltinMcps = createAllBuiltinMcps(thothConfig);
1921
- return Object.fromEntries(
1922
- Object.entries(allBuiltinMcps).filter(
1923
- ([name]) => !disabledMcps.includes(name)
1924
- )
1925
- );
1259
+ } else {
1260
+ proc = spawn(["unzip", "-o", archivePath, "-d", destDir], {
1261
+ stdout: "ignore",
1262
+ stderr: "pipe"
1263
+ });
1264
+ }
1265
+ const exitCode = await proc.exited;
1266
+ if (exitCode !== 0) {
1267
+ const stderr = await new Response(proc.stderr).text();
1268
+ throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
1269
+ }
1926
1270
  }
1927
1271
 
1928
- // src/tools/ast-grep/tools.ts
1929
- import { tool } from "@opencode-ai/plugin/tool";
1930
-
1931
- // src/tools/ast-grep/cli.ts
1932
- import { existsSync as existsSync5 } from "fs";
1933
-
1934
- // src/tools/ast-grep/constants.ts
1935
- import { spawnSync as spawnSync2 } from "child_process";
1936
- import { existsSync as existsSync4, statSync as statSync2 } from "fs";
1937
- import { createRequire as createRequire3 } from "module";
1938
- import { dirname as dirname2, join as join5 } from "path";
1939
-
1940
- // src/tools/ast-grep/downloader.ts
1941
- import { chmodSync, existsSync as existsSync3, mkdirSync, unlinkSync } from "fs";
1942
- import { createRequire as createRequire2 } from "module";
1943
- import { homedir as homedir2 } from "os";
1944
- import { join as join4 } from "path";
1945
-
1946
1272
  // src/utils/file-io.ts
1947
1273
  import { readFile, writeFile } from "fs/promises";
1948
1274
  async function writeTextFile(filePath, contents) {
@@ -1976,19 +1302,19 @@ var PLATFORM_MAP = {
1976
1302
  function getCacheDir2() {
1977
1303
  if (process.platform === "win32") {
1978
1304
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
1979
- const base2 = localAppData || join4(homedir2(), "AppData", "Local");
1980
- return join4(base2, "thoth-agents", "bin");
1305
+ const base2 = localAppData || join5(homedir2(), "AppData", "Local");
1306
+ return join5(base2, "thoth-agents", "bin");
1981
1307
  }
1982
1308
  const xdgCache = process.env.XDG_CACHE_HOME;
1983
- const base = xdgCache || join4(homedir2(), ".cache");
1984
- return join4(base, "thoth-agents", "bin");
1309
+ const base = xdgCache || join5(homedir2(), ".cache");
1310
+ return join5(base, "thoth-agents", "bin");
1985
1311
  }
1986
1312
  function getBinaryName() {
1987
1313
  return process.platform === "win32" ? "sg.exe" : "sg";
1988
1314
  }
1989
1315
  function getCachedBinaryPath() {
1990
- const binaryPath = join4(getCacheDir2(), getBinaryName());
1991
- return existsSync3(binaryPath) ? binaryPath : null;
1316
+ const binaryPath = join5(getCacheDir2(), getBinaryName());
1317
+ return existsSync4(binaryPath) ? binaryPath : null;
1992
1318
  }
1993
1319
  async function downloadAstGrep(version = DEFAULT_VERSION) {
1994
1320
  const platformKey = `${process.platform}-${process.arch}`;
@@ -2001,8 +1327,8 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
2001
1327
  }
2002
1328
  const cacheDir = getCacheDir2();
2003
1329
  const binaryName = getBinaryName();
2004
- const binaryPath = join4(cacheDir, binaryName);
2005
- if (existsSync3(binaryPath)) {
1330
+ const binaryPath = join5(cacheDir, binaryName);
1331
+ if (existsSync4(binaryPath)) {
2006
1332
  return binaryPath;
2007
1333
  }
2008
1334
  const { arch, os: os2 } = platformInfo;
@@ -2010,21 +1336,21 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
2010
1336
  const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
2011
1337
  console.log(`[thoth-agents] Downloading ast-grep binary...`);
2012
1338
  try {
2013
- if (!existsSync3(cacheDir)) {
1339
+ if (!existsSync4(cacheDir)) {
2014
1340
  mkdirSync(cacheDir, { recursive: true });
2015
1341
  }
2016
1342
  const response = await fetch(downloadUrl, { redirect: "follow" });
2017
1343
  if (!response.ok) {
2018
1344
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2019
1345
  }
2020
- const archivePath = join4(cacheDir, assetName);
1346
+ const archivePath = join5(cacheDir, assetName);
2021
1347
  const arrayBuffer = await response.arrayBuffer();
2022
1348
  await writeTextFile(archivePath, arrayBuffer);
2023
1349
  await extractZip(archivePath, cacheDir);
2024
- if (existsSync3(archivePath)) {
1350
+ if (existsSync4(archivePath)) {
2025
1351
  unlinkSync(archivePath);
2026
1352
  }
2027
- if (process.platform !== "win32" && existsSync3(binaryPath)) {
1353
+ if (process.platform !== "win32" && existsSync4(binaryPath)) {
2028
1354
  chmodSync(binaryPath, 493);
2029
1355
  }
2030
1356
  console.log(`[thoth-agents] ast-grep binary ready.`);
@@ -2108,8 +1434,8 @@ function findSgCliPathSync() {
2108
1434
  const require3 = createRequire3(import.meta.url);
2109
1435
  const cliPkgPath = require3.resolve("@ast-grep/cli/package.json");
2110
1436
  const cliDir = dirname2(cliPkgPath);
2111
- const sgPath = join5(cliDir, binaryName);
2112
- if (existsSync4(sgPath) && isValidBinary(sgPath)) {
1437
+ const sgPath = join6(cliDir, binaryName);
1438
+ if (existsSync5(sgPath) && isValidBinary(sgPath)) {
2113
1439
  return sgPath;
2114
1440
  }
2115
1441
  } catch {
@@ -2121,8 +1447,8 @@ function findSgCliPathSync() {
2121
1447
  const pkgPath = require3.resolve(`${platformPkg}/package.json`);
2122
1448
  const pkgDir = dirname2(pkgPath);
2123
1449
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
2124
- const binaryPath = join5(pkgDir, astGrepName);
2125
- if (existsSync4(binaryPath) && isValidBinary(binaryPath)) {
1450
+ const binaryPath = join6(pkgDir, astGrepName);
1451
+ if (existsSync5(binaryPath) && isValidBinary(binaryPath)) {
2126
1452
  return binaryPath;
2127
1453
  }
2128
1454
  } catch {
@@ -2130,9 +1456,9 @@ function findSgCliPathSync() {
2130
1456
  }
2131
1457
  if (process.platform === "darwin") {
2132
1458
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
2133
- for (const path5 of homebrewPaths) {
2134
- if (existsSync4(path5) && isValidBinary(path5)) {
2135
- return path5;
1459
+ for (const path4 of homebrewPaths) {
1460
+ if (existsSync5(path4) && isValidBinary(path4)) {
1461
+ return path4;
2136
1462
  }
2137
1463
  }
2138
1464
  }
@@ -2149,8 +1475,8 @@ function getSgCliPath() {
2149
1475
  }
2150
1476
  return "sg";
2151
1477
  }
2152
- function setSgCliPath(path5) {
2153
- resolvedCliPath = path5;
1478
+ function setSgCliPath(path4) {
1479
+ resolvedCliPath = path4;
2154
1480
  }
2155
1481
  var DEFAULT_TIMEOUT_MS = 3e5;
2156
1482
  var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
@@ -2160,7 +1486,7 @@ var DEFAULT_MAX_MATCHES = 500;
2160
1486
  var initPromise = null;
2161
1487
  async function getAstGrepPath() {
2162
1488
  const currentPath = getSgCliPath();
2163
- if (currentPath !== "sg" && existsSync5(currentPath)) {
1489
+ if (currentPath !== "sg" && existsSync6(currentPath)) {
2164
1490
  return currentPath;
2165
1491
  }
2166
1492
  if (initPromise) {
@@ -2168,7 +1494,7 @@ async function getAstGrepPath() {
2168
1494
  }
2169
1495
  initPromise = (async () => {
2170
1496
  const syncPath = findSgCliPathSync();
2171
- if (syncPath && existsSync5(syncPath)) {
1497
+ if (syncPath && existsSync6(syncPath)) {
2172
1498
  setSgCliPath(syncPath);
2173
1499
  return syncPath;
2174
1500
  }
@@ -2207,7 +1533,7 @@ async function runSg(options) {
2207
1533
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
2208
1534
  args.push(...paths);
2209
1535
  let cliPath = getSgCliPath();
2210
- if (!existsSync5(cliPath) && cliPath !== "sg") {
1536
+ if (!existsSync6(cliPath) && cliPath !== "sg") {
2211
1537
  const downloadedPath = await getAstGrepPath();
2212
1538
  if (downloadedPath) {
2213
1539
  cliPath = downloadedPath;
@@ -2500,7 +1826,7 @@ var ast_grep_replace = tool({
2500
1826
  });
2501
1827
 
2502
1828
  // src/tools/lsp/client.ts
2503
- import { readFileSync as readFileSync3 } from "fs";
1829
+ import { readFileSync as readFileSync4 } from "fs";
2504
1830
  import { extname, resolve as resolve2 } from "path";
2505
1831
  import { Readable, Writable } from "stream";
2506
1832
  import { pathToFileURL } from "url";
@@ -2511,9 +1837,9 @@ import {
2511
1837
  } from "vscode-jsonrpc/node.js";
2512
1838
 
2513
1839
  // src/tools/lsp/config.ts
2514
- import { existsSync as existsSync7 } from "fs";
1840
+ import { existsSync as existsSync8 } from "fs";
2515
1841
  import { homedir as homedir3 } from "os";
2516
- import { join as join6 } from "path";
1842
+ import { join as join7 } from "path";
2517
1843
  import whichSync from "which";
2518
1844
 
2519
1845
  // src/tools/lsp/config-store.ts
@@ -2544,7 +1870,7 @@ function hasUserLspConfig() {
2544
1870
  }
2545
1871
 
2546
1872
  // src/tools/lsp/constants.ts
2547
- import { existsSync as existsSync6, readdirSync, statSync as statSync3 } from "fs";
1873
+ import { existsSync as existsSync7, readdirSync, statSync as statSync3 } from "fs";
2548
1874
  import { dirname as dirname3, resolve } from "path";
2549
1875
  var SEVERITY_MAP = {
2550
1876
  1: "error",
@@ -2584,7 +1910,7 @@ function NearestRoot(includePatterns, excludePatterns) {
2584
1910
  if (excludePatterns) {
2585
1911
  for (const dir of walkUpDirectories(file, cwd)) {
2586
1912
  for (const pattern of excludePatterns) {
2587
- if (existsSync6(`${dir}/${pattern}`)) {
1913
+ if (existsSync7(`${dir}/${pattern}`)) {
2588
1914
  return void 0;
2589
1915
  }
2590
1916
  }
@@ -2603,7 +1929,7 @@ function NearestRoot(includePatterns, excludePatterns) {
2603
1929
  }
2604
1930
  } catch {
2605
1931
  }
2606
- } else if (existsSync6(`${dir}/${pattern}`)) {
1932
+ } else if (existsSync7(`${dir}/${pattern}`)) {
2607
1933
  return dir;
2608
1934
  }
2609
1935
  }
@@ -3225,11 +2551,11 @@ function isServerInstalled(command) {
3225
2551
  if (command.length === 0) return false;
3226
2552
  const cmd = command[0];
3227
2553
  if (cmd.includes("/") || cmd.includes("\\")) {
3228
- return existsSync7(cmd);
2554
+ return existsSync8(cmd);
3229
2555
  }
3230
2556
  const isWindows = process.platform === "win32";
3231
2557
  const ext = isWindows ? ".exe" : "";
3232
- const opencodeBin = join6(homedir3(), ".config", "opencode", "bin");
2558
+ const opencodeBin = join7(homedir3(), ".config", "opencode", "bin");
3233
2559
  const searchPath = (process.env.PATH ?? "") + (isWindows ? ";" : ":") + opencodeBin;
3234
2560
  const result = whichSync.sync(cmd, {
3235
2561
  path: searchPath,
@@ -3240,8 +2566,8 @@ function isServerInstalled(command) {
3240
2566
  return true;
3241
2567
  }
3242
2568
  const cwd = process.cwd();
3243
- const localBin = join6(cwd, "node_modules", ".bin", cmd);
3244
- if (existsSync7(localBin) || existsSync7(localBin + ext)) {
2569
+ const localBin = join7(cwd, "node_modules", ".bin", cmd);
2570
+ if (existsSync8(localBin) || existsSync8(localBin + ext)) {
3245
2571
  return true;
3246
2572
  }
3247
2573
  return false;
@@ -3581,7 +2907,7 @@ stderr: ${stderr}` : "")
3581
2907
  log("[lsp] openFile: already open, skipping", { filePath: absPath });
3582
2908
  return;
3583
2909
  }
3584
- const text = readFileSync3(absPath, "utf-8");
2910
+ const text = readFileSync4(absPath, "utf-8");
3585
2911
  const ext = extname(absPath);
3586
2912
  const languageId = getLanguageId(ext);
3587
2913
  log("[lsp] openFile: opening document", {
@@ -3672,13 +2998,13 @@ import { tool as tool2 } from "@opencode-ai/plugin/tool";
3672
2998
 
3673
2999
  // src/tools/lsp/utils.ts
3674
3000
  import {
3675
- existsSync as existsSync8,
3676
- readFileSync as readFileSync4,
3001
+ existsSync as existsSync9,
3002
+ readFileSync as readFileSync5,
3677
3003
  statSync as statSync4,
3678
3004
  unlinkSync as unlinkSync2,
3679
3005
  writeFileSync as writeFileSync3
3680
3006
  } from "fs";
3681
- import { dirname as dirname4, extname as extname2, join as join7, resolve as resolve3 } from "path";
3007
+ import { dirname as dirname4, extname as extname2, join as join8, resolve as resolve3 } from "path";
3682
3008
  import { fileURLToPath as fileURLToPath2 } from "url";
3683
3009
  function findServerProjectRoot(filePath, server) {
3684
3010
  if (server.root) {
@@ -3784,7 +3110,7 @@ function filterDiagnosticsBySeverity(diagnostics, severityFilter) {
3784
3110
  }
3785
3111
  function applyTextEditsToFile(filePath, edits) {
3786
3112
  try {
3787
- const content = readFileSync4(filePath, "utf-8");
3113
+ const content = readFileSync5(filePath, "utf-8");
3788
3114
  const lines = content.split("\n");
3789
3115
  const sortedEdits = [...edits].sort((a, b) => {
3790
3116
  if (b.range.start.line !== a.range.start.line) {
@@ -3868,7 +3194,7 @@ function applyWorkspaceEdit(edit) {
3868
3194
  try {
3869
3195
  const oldPath = uriToPath(change.oldUri);
3870
3196
  const newPath = uriToPath(change.newUri);
3871
- const content = readFileSync4(oldPath, "utf-8");
3197
+ const content = readFileSync5(oldPath, "utf-8");
3872
3198
  writeFileSync3(newPath, content, "utf-8");
3873
3199
  unlinkSync2(oldPath);
3874
3200
  result.filesModified.push(newPath);
@@ -4250,17 +3576,16 @@ var TmuxSessionManager = class {
4250
3576
  };
4251
3577
 
4252
3578
  // src/index.ts
4253
- function resolveProjectName(project, directory) {
4254
- const runtimeProjectName = "name" in project && typeof project.name === "string" ? project.name : void 0;
4255
- return runtimeProjectName || path4.basename(directory) || "thoth-agents";
4256
- }
4257
3579
  var ThothAgents = async (ctx, _options) => {
4258
- const { client, directory, project, $: shell } = ctx;
4259
- const projectName = resolveProjectName(project, directory);
4260
- const sessionContext = /* @__PURE__ */ new Map();
3580
+ const { client, directory, $: shell } = ctx;
4261
3581
  const config = loadPluginConfig(directory);
4262
3582
  const agentDefs = createAgents(config);
4263
3583
  const agents = renderOpenCodeAgentConfigs(config);
3584
+ const packageRoot = fileURLToPath3(new URL("..", import.meta.url));
3585
+ const thothInitCommand = createOpenCodeInitCommand({
3586
+ projectRoot: directory,
3587
+ packageRoot
3588
+ });
4264
3589
  const modelArrayMap = {};
4265
3590
  for (const agentDef of agentDefs) {
4266
3591
  if (agentDef._modelArray && agentDef._modelArray.length > 0) {
@@ -4298,16 +3623,10 @@ var ThothAgents = async (ctx, _options) => {
4298
3623
  rawTmuxConfig: config.tmux,
4299
3624
  directory
4300
3625
  });
4301
- try {
4302
- syncSkillsOnStartup();
4303
- } catch (error) {
4304
- console.error("[plugin] Failed to sync bundled skills on startup");
4305
- log("[plugin] Skill sync failed during initialization", error);
4306
- }
4307
3626
  if (tmuxConfig.enabled) {
4308
3627
  startTmuxCheck();
4309
3628
  }
4310
- const mcps = createBuiltinMcps(config.disabled_mcps, config.thoth);
3629
+ const mcps = createBuiltinMcps(config.disabled_mcps);
4311
3630
  const tmuxSessionManager = new TmuxSessionManager(ctx, tmuxConfig);
4312
3631
  const autoUpdateChecker = createAutoUpdateCheckerHook(
4313
3632
  ctx,
@@ -4317,15 +3636,6 @@ var ThothAgents = async (ctx, _options) => {
4317
3636
  },
4318
3637
  shell
4319
3638
  );
4320
- const phaseReminderHook = createPhaseReminderHook();
4321
- const postReadNudgeHook = createPostReadNudgeHook();
4322
- const thothMemHook = createThothMemHook({
4323
- project: projectName,
4324
- directory,
4325
- thoth: config.thoth,
4326
- enabled: true
4327
- });
4328
- const chatHeadersHook = createChatHeadersHook(ctx);
4329
3639
  const delegateTaskRetryHook = createDelegateTaskRetryHook(ctx);
4330
3640
  const jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
4331
3641
  const foregroundFallback = new ForegroundFallbackManager(
@@ -4348,6 +3658,11 @@ var ThothAgents = async (ctx, _options) => {
4348
3658
  config: async (opencodeConfig) => {
4349
3659
  const lspConfig = opencodeConfig.lsp;
4350
3660
  setUserLspConfig(lspConfig);
3661
+ const commands = opencodeConfig.command ?? {};
3662
+ if (!commands["thoth-init"]) {
3663
+ commands["thoth-init"] = thothInitCommand;
3664
+ }
3665
+ opencodeConfig.command = commands;
4351
3666
  if (config.setDefaultAgent !== false && !opencodeConfig.default_agent) {
4352
3667
  opencodeConfig.default_agent = "orchestrator";
4353
3668
  }
@@ -4416,16 +3731,8 @@ var ThothAgents = async (ctx, _options) => {
4416
3731
  }
4417
3732
  },
4418
3733
  event: async (input) => {
4419
- if (input.event.type === "session.deleted") {
4420
- const deletedEvent = input.event;
4421
- const deletedSessionId = deletedEvent.properties?.info?.id ?? deletedEvent.properties?.sessionID;
4422
- if (deletedSessionId) {
4423
- sessionContext.delete(deletedSessionId);
4424
- }
4425
- }
4426
3734
  await foregroundFallback.handleEvent(input.event);
4427
3735
  await autoUpdateChecker.event(input);
4428
- await thothMemHook.event(input);
4429
3736
  await tmuxSessionManager.onSessionCreated(
4430
3737
  input.event
4431
3738
  );
@@ -4436,56 +3743,7 @@ var ThothAgents = async (ctx, _options) => {
4436
3743
  input.event
4437
3744
  );
4438
3745
  },
4439
- "chat.headers": chatHeadersHook["chat.headers"],
4440
- "chat.message": async (input, output) => {
4441
- const chatInput = input;
4442
- if (chatInput.sessionID) {
4443
- const previous = sessionContext.get(chatInput.sessionID);
4444
- const context = {
4445
- model: previous?.model,
4446
- variant: previous?.variant
4447
- };
4448
- if (typeof chatInput.model?.providerID === "string" && typeof chatInput.model?.modelID === "string") {
4449
- context.model = {
4450
- providerID: chatInput.model.providerID,
4451
- modelID: chatInput.model.modelID
4452
- };
4453
- }
4454
- if (typeof chatInput.variant === "string") {
4455
- context.variant = chatInput.variant;
4456
- }
4457
- sessionContext.set(chatInput.sessionID, context);
4458
- }
4459
- if (thothMemHook["chat.message"]) {
4460
- await thothMemHook["chat.message"](
4461
- input,
4462
- output
4463
- );
4464
- }
4465
- },
4466
- "experimental.chat.system.transform": async (input, output) => {
4467
- if (thothMemHook["experimental.chat.system.transform"]) {
4468
- await thothMemHook["experimental.chat.system.transform"](
4469
- input,
4470
- output
4471
- );
4472
- }
4473
- },
4474
- "experimental.chat.messages.transform": async (input, output) => {
4475
- await phaseReminderHook["experimental.chat.messages.transform"](
4476
- input,
4477
- output
4478
- );
4479
- },
4480
- "experimental.session.compacting": async (input, output) => {
4481
- if (thothMemHook["experimental.session.compacting"]) {
4482
- await thothMemHook["experimental.session.compacting"](
4483
- input,
4484
- output
4485
- );
4486
- }
4487
- },
4488
- // Post-tool hooks: retry guidance for delegation errors + post-read nudge
3746
+ // Post-tool hooks: retry guidance for delegation and JSON errors.
4489
3747
  "tool.execute.after": async (input, output) => {
4490
3748
  await delegateTaskRetryHook["tool.execute.after"](
4491
3749
  input,
@@ -4495,16 +3753,6 @@ var ThothAgents = async (ctx, _options) => {
4495
3753
  input,
4496
3754
  output
4497
3755
  );
4498
- if (thothMemHook["tool.execute.after"]) {
4499
- await thothMemHook["tool.execute.after"](
4500
- input,
4501
- output
4502
- );
4503
- }
4504
- await postReadNudgeHook["tool.execute.after"](
4505
- input,
4506
- output
4507
- );
4508
3756
  }
4509
3757
  };
4510
3758
  };