@ashx-j/lunr 0.1.6 → 0.2.0

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/dist/builtin-extensions/index.d.ts.map +1 -1
  2. package/dist/builtin-extensions/index.js +3 -6
  3. package/dist/builtin-extensions/index.js.map +1 -1
  4. package/dist/builtin-extensions/lunr-todos.d.ts +7 -6
  5. package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
  6. package/dist/builtin-extensions/lunr-todos.js +24 -11
  7. package/dist/builtin-extensions/lunr-todos.js.map +1 -1
  8. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.d.ts.map +1 -1
  9. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js +9 -5
  10. package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js.map +1 -1
  11. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.d.ts.map +1 -1
  12. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js +31 -3
  13. package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js.map +1 -1
  14. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.d.ts +1 -0
  15. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.d.ts.map +1 -1
  16. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js +14 -4
  17. package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js.map +1 -1
  18. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +4 -3
  20. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +12 -1
  23. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +3 -0
  25. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  27. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +8 -0
  28. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  29. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +22 -2
  30. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  31. package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
  32. package/dist/builtin-extensions/pi-web-access/index.js +38 -230
  33. package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
  34. package/dist/builtin-extensions/pi-web-access/render-search-chrome.d.ts +35 -0
  35. package/dist/builtin-extensions/pi-web-access/render-search-chrome.d.ts.map +1 -0
  36. package/dist/builtin-extensions/pi-web-access/render-search-chrome.js +93 -0
  37. package/dist/builtin-extensions/pi-web-access/render-search-chrome.js.map +1 -0
  38. package/dist/bun/cli.d.ts.map +1 -1
  39. package/dist/bun/cli.js +1 -0
  40. package/dist/bun/cli.js.map +1 -1
  41. package/dist/catalog/models.json +1 -1
  42. package/dist/catalog/providers/cerebras.json +1 -1
  43. package/dist/catalog/providers/cloudflare-ai-gateway.json +1 -1
  44. package/dist/catalog/providers/cloudflare-workers-ai.json +1 -1
  45. package/dist/catalog/providers/huggingface.json +1 -1
  46. package/dist/catalog/providers/opencode-go.json +1 -1
  47. package/dist/catalog/providers/openrouter.json +1 -1
  48. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  49. package/dist/catalog/publication.json +3 -3
  50. package/dist/cli.d.ts.map +1 -1
  51. package/dist/cli.js +4 -1
  52. package/dist/cli.js.map +1 -1
  53. package/dist/core/context-breakdown.d.ts +21 -1
  54. package/dist/core/context-breakdown.d.ts.map +1 -1
  55. package/dist/core/context-breakdown.js +58 -2
  56. package/dist/core/context-breakdown.js.map +1 -1
  57. package/dist/core/extensions/host-static.d.ts +2 -0
  58. package/dist/core/extensions/host-static.d.ts.map +1 -0
  59. package/dist/core/extensions/host-static.js +15 -0
  60. package/dist/core/extensions/host-static.js.map +1 -0
  61. package/dist/core/extensions/loader.d.ts +10 -0
  62. package/dist/core/extensions/loader.d.ts.map +1 -1
  63. package/dist/core/extensions/loader.js +75 -34
  64. package/dist/core/extensions/loader.js.map +1 -1
  65. package/dist/core/official-catalog.d.ts.map +1 -1
  66. package/dist/core/official-catalog.js +11 -4
  67. package/dist/core/official-catalog.js.map +1 -1
  68. package/dist/core/package-manager.d.ts +3 -0
  69. package/dist/core/package-manager.d.ts.map +1 -1
  70. package/dist/core/package-manager.js +11 -5
  71. package/dist/core/package-manager.js.map +1 -1
  72. package/dist/core/permission-mode-control.d.ts +27 -0
  73. package/dist/core/permission-mode-control.d.ts.map +1 -0
  74. package/dist/core/permission-mode-control.js +39 -0
  75. package/dist/core/permission-mode-control.js.map +1 -0
  76. package/dist/core/resource-loader.d.ts +4 -1
  77. package/dist/core/resource-loader.d.ts.map +1 -1
  78. package/dist/core/resource-loader.js +13 -5
  79. package/dist/core/resource-loader.js.map +1 -1
  80. package/dist/core/settings-manager.d.ts.map +1 -1
  81. package/dist/core/settings-manager.js.map +1 -1
  82. package/dist/core/slash-commands.d.ts.map +1 -1
  83. package/dist/core/slash-commands.js +0 -1
  84. package/dist/core/slash-commands.js.map +1 -1
  85. package/dist/core/timings.d.ts +1 -0
  86. package/dist/core/timings.d.ts.map +1 -1
  87. package/dist/core/timings.js +6 -1
  88. package/dist/core/timings.js.map +1 -1
  89. package/dist/core/tools/read.d.ts.map +1 -1
  90. package/dist/core/tools/read.js +14 -20
  91. package/dist/core/tools/read.js.map +1 -1
  92. package/dist/core/tools/render-utils.d.ts +2 -0
  93. package/dist/core/tools/render-utils.d.ts.map +1 -1
  94. package/dist/core/tools/render-utils.js +10 -0
  95. package/dist/core/tools/render-utils.js.map +1 -1
  96. package/dist/main.d.ts +8 -0
  97. package/dist/main.d.ts.map +1 -1
  98. package/dist/main.js +63 -16
  99. package/dist/main.js.map +1 -1
  100. package/dist/migrations.d.ts.map +1 -1
  101. package/dist/migrations.js +3 -0
  102. package/dist/migrations.js.map +1 -1
  103. package/dist/modes/interactive/components/assistant-message.d.ts +1 -0
  104. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  105. package/dist/modes/interactive/components/assistant-message.js +1 -0
  106. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  107. package/dist/modes/interactive/components/context-view.d.ts.map +1 -1
  108. package/dist/modes/interactive/components/context-view.js +3 -1
  109. package/dist/modes/interactive/components/context-view.js.map +1 -1
  110. package/dist/modes/interactive/components/plan-message.d.ts +17 -0
  111. package/dist/modes/interactive/components/plan-message.d.ts.map +1 -0
  112. package/dist/modes/interactive/components/plan-message.js +43 -0
  113. package/dist/modes/interactive/components/plan-message.js.map +1 -0
  114. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  115. package/dist/modes/interactive/components/settings-selector.js +1 -1
  116. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  117. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  118. package/dist/modes/interactive/components/tool-execution.js +3 -1
  119. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  120. package/dist/modes/interactive/components/usage-view.d.ts +4 -25
  121. package/dist/modes/interactive/components/usage-view.d.ts.map +1 -1
  122. package/dist/modes/interactive/components/usage-view.js +17 -69
  123. package/dist/modes/interactive/components/usage-view.js.map +1 -1
  124. package/dist/modes/interactive/components/user-message.d.ts +1 -0
  125. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/user-message.js +1 -0
  127. package/dist/modes/interactive/components/user-message.js.map +1 -1
  128. package/dist/modes/interactive/interactive-mode.d.ts +23 -6
  129. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  130. package/dist/modes/interactive/interactive-mode.js +267 -148
  131. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  132. package/dist/modes/interactive/smooth-streaming.d.ts +39 -6
  133. package/dist/modes/interactive/smooth-streaming.d.ts.map +1 -1
  134. package/dist/modes/interactive/smooth-streaming.js +160 -73
  135. package/dist/modes/interactive/smooth-streaming.js.map +1 -1
  136. package/dist/utils/clipboard-image.d.ts.map +1 -1
  137. package/dist/utils/clipboard-image.js +7 -3
  138. package/dist/utils/clipboard-image.js.map +1 -1
  139. package/docs/terminal-setup.md +13 -0
  140. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  141. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  142. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  143. package/examples/extensions/gondolin/package-lock.json +2 -2
  144. package/examples/extensions/gondolin/package.json +1 -1
  145. package/examples/extensions/sandbox/package-lock.json +2 -2
  146. package/examples/extensions/sandbox/package.json +1 -1
  147. package/examples/extensions/with-deps/package-lock.json +2 -2
  148. package/examples/extensions/with-deps/package.json +1 -1
  149. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA0BrF,yFAAyF;AACzF,eAAO,MAAM,sBAAsB,EAAE,eAAe,EAcnD,CAAC;AAcF,eAAO,MAAM,gCAAgC,UAAsD,CAAC;AAEpG,6FAA6F;AAC7F,eAAO,MAAM,sBAAsB,yBAA0B,CAAC;AAE9D,6EAA6E;AAC7E,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAQhF;AAED,yFAAyF;AACzF,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAE3E;AAED,qFAAqF;AACrF,eAAO,MAAM,iBAAiB,EAAE,eAAe,EAA2B,CAAC","sourcesContent":["/**\n * Built-in extensions for lunR.\n *\n * Light factories stay on the first-paint graph. Heavy factories are imported\n * only when attachDeferredBuiltinExtensions() runs after the TUI is up, or\n * immediately for print/RPC/gateway where there is no first-paint window.\n */\n\nimport type { ExtensionFactory, InlineExtension } from \"../core/extensions/types.ts\";\n\nimport simplePiMemory from \"./simple-pi-memory.ts\";\nimport piTps from \"./pi-tps.ts\";\nimport ashxjTui from \"./ashxj-tui.ts\";\nimport piOllamaCloud from \"./pi-ollama-cloud/index.ts\";\nimport ashxjSpinners from \"./ashxj-spinners.ts\";\nimport ashxjThinking from \"./ashxj-thinking.ts\";\nimport lunrLocalProviders from \"./lunr-local-providers/index.ts\";\nimport lunrCron from \"./lunr-cron.ts\";\nimport lunrTodos from \"./lunr-todos.ts\";\nimport lunrPlanTools from \"./lunr-plan-tools.ts\";\nimport lunrBehavior from \"./lunr-behavior.ts\";\nimport lunrSkillCreator from \"./lunr-skill-creator/index.ts\";\nimport narumirunaGoal from \"./narumiruna-pi-goal/src/goal.ts\";\n\n/**\n * Wrap a raw factory function as a named InlineExtension.\n * The cast bridges the gap between the source-level ExtensionFactory type\n * (used here) and the dist-level type that extensions import via the package\n * name. The underlying function signatures are structurally identical.\n */\nfunction ext(name: string, factory: unknown): InlineExtension {\n\treturn { name, factory: factory as ExtensionFactory };\n}\n\n/** UI, local providers, and small lunR-native factories. Safe to import at CLI start. */\nexport const lightBuiltinExtensions: InlineExtension[] = [\n\text(\"simple-pi-memory\", simplePiMemory),\n\text(\"pi-tps\", piTps),\n\text(\"ashxj-tui\", ashxjTui),\n\text(\"pi-ollama-cloud\", piOllamaCloud),\n\text(\"ashxj-spinners\", ashxjSpinners),\n\text(\"ashxj-thinking\", ashxjThinking),\n\text(\"narumiruna-pi-goal\", narumirunaGoal),\n\text(\"lunr-local-providers\", lunrLocalProviders),\n\text(\"lunr-cron\", lunrCron),\n\text(\"lunr-todos\", lunrTodos),\n\text(\"lunr-plan-tools\", lunrPlanTools),\n\text(\"lunr-behavior\", lunrBehavior),\n\text(\"lunr-skill-creator\", lunrSkillCreator),\n];\n\nconst DEFERRED_BUILTIN_LOADERS: Array<{\n\tname: string;\n\tload: () => Promise<{ default: unknown }>;\n}> = [\n\t{ name: \"pi-intercom\", load: () => import(\"./pi-intercom/index.ts\") },\n\t{ name: \"pi-prompt-template-model\", load: () => import(\"./pi-prompt-template-model/index.ts\") },\n\t{ name: \"pi-subagents\", load: () => import(\"./pi-subagents/index.ts\") },\n\t{ name: \"pi-web-access\", load: () => import(\"./pi-web-access/index.ts\") },\n\t{ name: \"pi-lsp-extension\", load: () => import(\"./pi-lsp-extension/src/index.ts\") },\n\t{ name: \"pi-mcp-adapter\", load: () => import(\"./pi-mcp-adapter/index.ts\") },\n];\n\nexport const DEFERRED_BUILTIN_EXTENSION_NAMES = DEFERRED_BUILTIN_LOADERS.map((entry) => entry.name);\n\n/** Flags registered only after deferred builtins attach. Do not fail CLI parse for these. */\nexport const DEFERRED_BUILTIN_FLAGS = [\"mcp-config\"] as const;\n\n/** Import MCP / LSP / web-access / intercom / subagents only when needed. */\nexport async function loadDeferredBuiltinExtensions(): Promise<InlineExtension[]> {\n\tconst loaded = await Promise.all(\n\t\tDEFERRED_BUILTIN_LOADERS.map(async ({ name, load }) => {\n\t\t\tconst module = await load();\n\t\t\treturn ext(name, module.default);\n\t\t}),\n\t);\n\treturn loaded;\n}\n\n/** Full roster. Print / RPC / gateway still need every factory before the first turn. */\nexport async function loadAllBuiltinExtensions(): Promise<InlineExtension[]> {\n\treturn [...lightBuiltinExtensions, ...(await loadDeferredBuiltinExtensions())];\n}\n\n/** Eager full roster. Prefer loadAllBuiltinExtensions() so CLI start stays light. */\nexport const builtinExtensions: InlineExtension[] = lightBuiltinExtensions;\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAuBrF,yFAAyF;AACzF,eAAO,MAAM,sBAAsB,EAAE,eAAe,EAWnD,CAAC;AAiBF,eAAO,MAAM,gCAAgC,UAAsD,CAAC;AAEpG,6FAA6F;AAC7F,eAAO,MAAM,sBAAsB,yBAA0B,CAAC;AAE9D,6EAA6E;AAC7E,wBAAsB,6BAA6B,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAQhF;AAED,yFAAyF;AACzF,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAE3E;AAED,qFAAqF;AACrF,eAAO,MAAM,iBAAiB,EAAE,eAAe,EAA2B,CAAC","sourcesContent":["/**\n * Built-in extensions for lunR.\n *\n * Light factories stay on the first-paint graph. Heavy factories are imported\n * only when attachDeferredBuiltinExtensions() runs after the TUI is up, or\n * immediately for print/RPC/gateway where there is no first-paint window.\n */\n\nimport type { ExtensionFactory, InlineExtension } from \"../core/extensions/types.ts\";\n\nimport simplePiMemory from \"./simple-pi-memory.ts\";\nimport piTps from \"./pi-tps.ts\";\nimport ashxjTui from \"./ashxj-tui.ts\";\nimport ashxjSpinners from \"./ashxj-spinners.ts\";\nimport ashxjThinking from \"./ashxj-thinking.ts\";\nimport lunrLocalProviders from \"./lunr-local-providers/index.ts\";\nimport lunrTodos from \"./lunr-todos.ts\";\nimport lunrPlanTools from \"./lunr-plan-tools.ts\";\nimport lunrBehavior from \"./lunr-behavior.ts\";\nimport lunrSkillCreator from \"./lunr-skill-creator/index.ts\";\n\n/**\n * Wrap a raw factory function as a named InlineExtension.\n * The cast bridges the gap between the source-level ExtensionFactory type\n * (used here) and the dist-level type that extensions import via the package\n * name. The underlying function signatures are structurally identical.\n */\nfunction ext(name: string, factory: unknown): InlineExtension {\n\treturn { name, factory: factory as ExtensionFactory };\n}\n\n/** UI, local providers, and small lunR-native factories. Safe to import at CLI start. */\nexport const lightBuiltinExtensions: InlineExtension[] = [\n\text(\"simple-pi-memory\", simplePiMemory),\n\text(\"pi-tps\", piTps),\n\text(\"ashxj-tui\", ashxjTui),\n\text(\"ashxj-spinners\", ashxjSpinners),\n\text(\"ashxj-thinking\", ashxjThinking),\n\text(\"lunr-local-providers\", lunrLocalProviders),\n\text(\"lunr-todos\", lunrTodos),\n\text(\"lunr-plan-tools\", lunrPlanTools),\n\text(\"lunr-behavior\", lunrBehavior),\n\text(\"lunr-skill-creator\", lunrSkillCreator),\n];\n\nconst DEFERRED_BUILTIN_LOADERS: Array<{\n\tname: string;\n\tload: () => Promise<{ default: unknown }>;\n}> = [\n\t{ name: \"pi-ollama-cloud\", load: () => import(\"./pi-ollama-cloud/index.ts\") },\n\t{ name: \"narumiruna-pi-goal\", load: () => import(\"./narumiruna-pi-goal/src/goal.ts\") },\n\t{ name: \"lunr-cron\", load: () => import(\"./lunr-cron.ts\") },\n\t{ name: \"pi-intercom\", load: () => import(\"./pi-intercom/index.ts\") },\n\t{ name: \"pi-prompt-template-model\", load: () => import(\"./pi-prompt-template-model/index.ts\") },\n\t{ name: \"pi-subagents\", load: () => import(\"./pi-subagents/index.ts\") },\n\t{ name: \"pi-web-access\", load: () => import(\"./pi-web-access/index.ts\") },\n\t{ name: \"pi-lsp-extension\", load: () => import(\"./pi-lsp-extension/src/index.ts\") },\n\t{ name: \"pi-mcp-adapter\", load: () => import(\"./pi-mcp-adapter/index.ts\") },\n];\n\nexport const DEFERRED_BUILTIN_EXTENSION_NAMES = DEFERRED_BUILTIN_LOADERS.map((entry) => entry.name);\n\n/** Flags registered only after deferred builtins attach. Do not fail CLI parse for these. */\nexport const DEFERRED_BUILTIN_FLAGS = [\"mcp-config\"] as const;\n\n/** Import MCP / LSP / web-access / intercom / subagents only when needed. */\nexport async function loadDeferredBuiltinExtensions(): Promise<InlineExtension[]> {\n\tconst loaded = await Promise.all(\n\t\tDEFERRED_BUILTIN_LOADERS.map(async ({ name, load }) => {\n\t\t\tconst module = await load();\n\t\t\treturn ext(name, module.default);\n\t\t}),\n\t);\n\treturn loaded;\n}\n\n/** Full roster. Print / RPC / gateway still need every factory before the first turn. */\nexport async function loadAllBuiltinExtensions(): Promise<InlineExtension[]> {\n\treturn [...lightBuiltinExtensions, ...(await loadDeferredBuiltinExtensions())];\n}\n\n/** Eager full roster. Prefer loadAllBuiltinExtensions() so CLI start stays light. */\nexport const builtinExtensions: InlineExtension[] = lightBuiltinExtensions;\n"]}
@@ -8,16 +8,13 @@
8
8
  import simplePiMemory from "./simple-pi-memory.js";
9
9
  import piTps from "./pi-tps.js";
10
10
  import ashxjTui from "./ashxj-tui.js";
11
- import piOllamaCloud from "./pi-ollama-cloud/index.js";
12
11
  import ashxjSpinners from "./ashxj-spinners.js";
13
12
  import ashxjThinking from "./ashxj-thinking.js";
14
13
  import lunrLocalProviders from "./lunr-local-providers/index.js";
15
- import lunrCron from "./lunr-cron.js";
16
14
  import lunrTodos from "./lunr-todos.js";
17
15
  import lunrPlanTools from "./lunr-plan-tools.js";
18
16
  import lunrBehavior from "./lunr-behavior.js";
19
17
  import lunrSkillCreator from "./lunr-skill-creator/index.js";
20
- import narumirunaGoal from "./narumiruna-pi-goal/src/goal.js";
21
18
  /**
22
19
  * Wrap a raw factory function as a named InlineExtension.
23
20
  * The cast bridges the gap between the source-level ExtensionFactory type
@@ -32,18 +29,18 @@ export const lightBuiltinExtensions = [
32
29
  ext("simple-pi-memory", simplePiMemory),
33
30
  ext("pi-tps", piTps),
34
31
  ext("ashxj-tui", ashxjTui),
35
- ext("pi-ollama-cloud", piOllamaCloud),
36
32
  ext("ashxj-spinners", ashxjSpinners),
37
33
  ext("ashxj-thinking", ashxjThinking),
38
- ext("narumiruna-pi-goal", narumirunaGoal),
39
34
  ext("lunr-local-providers", lunrLocalProviders),
40
- ext("lunr-cron", lunrCron),
41
35
  ext("lunr-todos", lunrTodos),
42
36
  ext("lunr-plan-tools", lunrPlanTools),
43
37
  ext("lunr-behavior", lunrBehavior),
44
38
  ext("lunr-skill-creator", lunrSkillCreator),
45
39
  ];
46
40
  const DEFERRED_BUILTIN_LOADERS = [
41
+ { name: "pi-ollama-cloud", load: () => import("./pi-ollama-cloud/index.js") },
42
+ { name: "narumiruna-pi-goal", load: () => import("./narumiruna-pi-goal/src/goal.js") },
43
+ { name: "lunr-cron", load: () => import("./lunr-cron.js") },
47
44
  { name: "pi-intercom", load: () => import("./pi-intercom/index.js") },
48
45
  { name: "pi-prompt-template-model", load: () => import("./pi-prompt-template-model/index.js") },
49
46
  { name: "pi-subagents", load: () => import("./pi-subagents/index.js") },
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/builtin-extensions/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Built-in extensions for lunR.\n *\n * Light factories stay on the first-paint graph. Heavy factories are imported\n * only when attachDeferredBuiltinExtensions() runs after the TUI is up, or\n * immediately for print/RPC/gateway where there is no first-paint window.\n */\n\nimport type { ExtensionFactory, InlineExtension } from \"../core/extensions/types.ts\";\n\nimport simplePiMemory from \"./simple-pi-memory.ts\";\nimport piTps from \"./pi-tps.ts\";\nimport ashxjTui from \"./ashxj-tui.ts\";\nimport piOllamaCloud from \"./pi-ollama-cloud/index.ts\";\nimport ashxjSpinners from \"./ashxj-spinners.ts\";\nimport ashxjThinking from \"./ashxj-thinking.ts\";\nimport lunrLocalProviders from \"./lunr-local-providers/index.ts\";\nimport lunrCron from \"./lunr-cron.ts\";\nimport lunrTodos from \"./lunr-todos.ts\";\nimport lunrPlanTools from \"./lunr-plan-tools.ts\";\nimport lunrBehavior from \"./lunr-behavior.ts\";\nimport lunrSkillCreator from \"./lunr-skill-creator/index.ts\";\nimport narumirunaGoal from \"./narumiruna-pi-goal/src/goal.ts\";\n\n/**\n * Wrap a raw factory function as a named InlineExtension.\n * The cast bridges the gap between the source-level ExtensionFactory type\n * (used here) and the dist-level type that extensions import via the package\n * name. The underlying function signatures are structurally identical.\n */\nfunction ext(name: string, factory: unknown): InlineExtension {\n\treturn { name, factory: factory as ExtensionFactory };\n}\n\n/** UI, local providers, and small lunR-native factories. Safe to import at CLI start. */\nexport const lightBuiltinExtensions: InlineExtension[] = [\n\text(\"simple-pi-memory\", simplePiMemory),\n\text(\"pi-tps\", piTps),\n\text(\"ashxj-tui\", ashxjTui),\n\text(\"pi-ollama-cloud\", piOllamaCloud),\n\text(\"ashxj-spinners\", ashxjSpinners),\n\text(\"ashxj-thinking\", ashxjThinking),\n\text(\"narumiruna-pi-goal\", narumirunaGoal),\n\text(\"lunr-local-providers\", lunrLocalProviders),\n\text(\"lunr-cron\", lunrCron),\n\text(\"lunr-todos\", lunrTodos),\n\text(\"lunr-plan-tools\", lunrPlanTools),\n\text(\"lunr-behavior\", lunrBehavior),\n\text(\"lunr-skill-creator\", lunrSkillCreator),\n];\n\nconst DEFERRED_BUILTIN_LOADERS: Array<{\n\tname: string;\n\tload: () => Promise<{ default: unknown }>;\n}> = [\n\t{ name: \"pi-intercom\", load: () => import(\"./pi-intercom/index.ts\") },\n\t{ name: \"pi-prompt-template-model\", load: () => import(\"./pi-prompt-template-model/index.ts\") },\n\t{ name: \"pi-subagents\", load: () => import(\"./pi-subagents/index.ts\") },\n\t{ name: \"pi-web-access\", load: () => import(\"./pi-web-access/index.ts\") },\n\t{ name: \"pi-lsp-extension\", load: () => import(\"./pi-lsp-extension/src/index.ts\") },\n\t{ name: \"pi-mcp-adapter\", load: () => import(\"./pi-mcp-adapter/index.ts\") },\n];\n\nexport const DEFERRED_BUILTIN_EXTENSION_NAMES = DEFERRED_BUILTIN_LOADERS.map((entry) => entry.name);\n\n/** Flags registered only after deferred builtins attach. Do not fail CLI parse for these. */\nexport const DEFERRED_BUILTIN_FLAGS = [\"mcp-config\"] as const;\n\n/** Import MCP / LSP / web-access / intercom / subagents only when needed. */\nexport async function loadDeferredBuiltinExtensions(): Promise<InlineExtension[]> {\n\tconst loaded = await Promise.all(\n\t\tDEFERRED_BUILTIN_LOADERS.map(async ({ name, load }) => {\n\t\t\tconst module = await load();\n\t\t\treturn ext(name, module.default);\n\t\t}),\n\t);\n\treturn loaded;\n}\n\n/** Full roster. Print / RPC / gateway still need every factory before the first turn. */\nexport async function loadAllBuiltinExtensions(): Promise<InlineExtension[]> {\n\treturn [...lightBuiltinExtensions, ...(await loadDeferredBuiltinExtensions())];\n}\n\n/** Eager full roster. Prefer loadAllBuiltinExtensions() so CLI start stays light. */\nexport const builtinExtensions: InlineExtension[] = lightBuiltinExtensions;\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/builtin-extensions/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Built-in extensions for lunR.\n *\n * Light factories stay on the first-paint graph. Heavy factories are imported\n * only when attachDeferredBuiltinExtensions() runs after the TUI is up, or\n * immediately for print/RPC/gateway where there is no first-paint window.\n */\n\nimport type { ExtensionFactory, InlineExtension } from \"../core/extensions/types.ts\";\n\nimport simplePiMemory from \"./simple-pi-memory.ts\";\nimport piTps from \"./pi-tps.ts\";\nimport ashxjTui from \"./ashxj-tui.ts\";\nimport ashxjSpinners from \"./ashxj-spinners.ts\";\nimport ashxjThinking from \"./ashxj-thinking.ts\";\nimport lunrLocalProviders from \"./lunr-local-providers/index.ts\";\nimport lunrTodos from \"./lunr-todos.ts\";\nimport lunrPlanTools from \"./lunr-plan-tools.ts\";\nimport lunrBehavior from \"./lunr-behavior.ts\";\nimport lunrSkillCreator from \"./lunr-skill-creator/index.ts\";\n\n/**\n * Wrap a raw factory function as a named InlineExtension.\n * The cast bridges the gap between the source-level ExtensionFactory type\n * (used here) and the dist-level type that extensions import via the package\n * name. The underlying function signatures are structurally identical.\n */\nfunction ext(name: string, factory: unknown): InlineExtension {\n\treturn { name, factory: factory as ExtensionFactory };\n}\n\n/** UI, local providers, and small lunR-native factories. Safe to import at CLI start. */\nexport const lightBuiltinExtensions: InlineExtension[] = [\n\text(\"simple-pi-memory\", simplePiMemory),\n\text(\"pi-tps\", piTps),\n\text(\"ashxj-tui\", ashxjTui),\n\text(\"ashxj-spinners\", ashxjSpinners),\n\text(\"ashxj-thinking\", ashxjThinking),\n\text(\"lunr-local-providers\", lunrLocalProviders),\n\text(\"lunr-todos\", lunrTodos),\n\text(\"lunr-plan-tools\", lunrPlanTools),\n\text(\"lunr-behavior\", lunrBehavior),\n\text(\"lunr-skill-creator\", lunrSkillCreator),\n];\n\nconst DEFERRED_BUILTIN_LOADERS: Array<{\n\tname: string;\n\tload: () => Promise<{ default: unknown }>;\n}> = [\n\t{ name: \"pi-ollama-cloud\", load: () => import(\"./pi-ollama-cloud/index.ts\") },\n\t{ name: \"narumiruna-pi-goal\", load: () => import(\"./narumiruna-pi-goal/src/goal.ts\") },\n\t{ name: \"lunr-cron\", load: () => import(\"./lunr-cron.ts\") },\n\t{ name: \"pi-intercom\", load: () => import(\"./pi-intercom/index.ts\") },\n\t{ name: \"pi-prompt-template-model\", load: () => import(\"./pi-prompt-template-model/index.ts\") },\n\t{ name: \"pi-subagents\", load: () => import(\"./pi-subagents/index.ts\") },\n\t{ name: \"pi-web-access\", load: () => import(\"./pi-web-access/index.ts\") },\n\t{ name: \"pi-lsp-extension\", load: () => import(\"./pi-lsp-extension/src/index.ts\") },\n\t{ name: \"pi-mcp-adapter\", load: () => import(\"./pi-mcp-adapter/index.ts\") },\n];\n\nexport const DEFERRED_BUILTIN_EXTENSION_NAMES = DEFERRED_BUILTIN_LOADERS.map((entry) => entry.name);\n\n/** Flags registered only after deferred builtins attach. Do not fail CLI parse for these. */\nexport const DEFERRED_BUILTIN_FLAGS = [\"mcp-config\"] as const;\n\n/** Import MCP / LSP / web-access / intercom / subagents only when needed. */\nexport async function loadDeferredBuiltinExtensions(): Promise<InlineExtension[]> {\n\tconst loaded = await Promise.all(\n\t\tDEFERRED_BUILTIN_LOADERS.map(async ({ name, load }) => {\n\t\t\tconst module = await load();\n\t\t\treturn ext(name, module.default);\n\t\t}),\n\t);\n\treturn loaded;\n}\n\n/** Full roster. Print / RPC / gateway still need every factory before the first turn. */\nexport async function loadAllBuiltinExtensions(): Promise<InlineExtension[]> {\n\treturn [...lightBuiltinExtensions, ...(await loadDeferredBuiltinExtensions())];\n}\n\n/** Eager full roster. Prefer loadAllBuiltinExtensions() so CLI start stays light. */\nexport const builtinExtensions: InlineExtension[] = lightBuiltinExtensions;\n"]}
@@ -11,8 +11,9 @@
11
11
  * - After every state change the list is mirrored into a `todos` widget above
12
12
  * the editor (component factory, so MAX_WIDGET_LINES string truncation does
13
13
  * not apply). Collapsed: up to 3 active rows (in-progress first, then
14
- * pending) + a `✓ N done` summary + a `+N more (<key> to expand)` hint.
15
- * Expanded: every row, completed included.
14
+ * pending). Completed items never appear collapsed. `+N more (<key> to
15
+ * expand)` still applies to hidden active rows. Expanded: every row,
16
+ * completed included until the next user turn prunes them.
16
17
  * - Expansion follows the global ctrl+o toggle via the
17
18
  * `@lunr/tools-expanded-changed` bridge, invoked from
18
19
  * interactive-mode.ts `setToolsExpanded` (initial state is read from
@@ -42,10 +43,10 @@ export declare const TODO_WIDGET_COLLAPSED_ROWS = 3;
42
43
  export declare function summarizeTodos(todos: TodoItem[]): string;
43
44
  /**
44
45
  * Widget lines for the current list. Collapsed mode shows up to
45
- * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending),
46
- * collapses completed items into a `✓ N done` summary, and appends a
47
- * `+N more (<expandKey> to expand)` hint when active rows are hidden.
48
- * Expanded mode shows every row. Empty list → no lines (widget removed).
46
+ * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending)
47
+ * and never emits a `✓ N done` summary. A `+N more (<expandKey> to expand)`
48
+ * hint is appended when active rows are hidden. Expanded mode shows every
49
+ * row, completed included. Empty list / all-completed collapsed → no lines.
49
50
  */
50
51
  export declare function buildTodoWidgetLines(todos: TodoItem[], expanded: boolean, expandKey?: string): TodoWidgetLine[];
51
52
  export default function (pi: ExtensionAPI): void;
@@ -1 +1 @@
1
- {"version":3,"file":"lunr-todos.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-todos.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AAStF,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAEjE,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IAClC,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,4EAA4E;AAC5E,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAQ5C,yFAAyF;AACzF,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAWxD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,SAAW,GAAG,cAAc,EAAE,CAwBjH;AASD,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,GAAG,IAAI,CAoG/C","sourcesContent":["// @ts-nocheck\n/**\n * lunr-todos — lunR-native agent todo list with an above-editor widget.\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension).\n *\n * - One `todo` tool (TypeBox), full-replace semantics: every call sends the\n * ENTIRE list; an empty array clears it. State is per-session, in-memory\n * only (extension closure) — nothing is persisted.\n * - Chat render is a quiet one-liner (renderCall/renderResult), matching the\n * cron tool's minimal footprint.\n * - After every state change the list is mirrored into a `todos` widget above\n * the editor (component factory, so MAX_WIDGET_LINES string truncation does\n * not apply). Collapsed: up to 3 active rows (in-progress first, then\n * pending) + a `✓ N done` summary + a `+N more (<key> to expand)` hint.\n * Expanded: every row, completed included.\n * - Expansion follows the global ctrl+o toggle via the\n * `@lunr/tools-expanded-changed` bridge, invoked from\n * interactive-mode.ts `setToolsExpanded` (initial state is read from\n * `ctx.ui.getToolsExpanded()`).\n *\n * The pure logic (summarizeTodos / buildTodoWidgetLines) is exported for\n * vitest — see test/lunr-todos.test.ts.\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-cron).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { Type } from \"@sinclair/typebox\";\nimport { keyText } from \"../modes/interactive/components/keybinding-hints.ts\";\n\n// ---------------------------------------------------------------------------\n// Pure logic (exported for tests)\n// ---------------------------------------------------------------------------\n\nexport type TodoStatus = \"pending\" | \"in_progress\" | \"completed\";\n\nexport interface TodoItem {\n\tid: string;\n\tcontent: string;\n\tstatus: TodoStatus;\n}\n\nexport interface TodoWidgetLine {\n\tkind: \"todo\" | \"summary\" | \"hint\";\n\tstatus?: TodoStatus;\n\ttext: string;\n}\n\n/** Collapsed widget shows at most this many active (non-completed) rows. */\nexport const TODO_WIDGET_COLLAPSED_ROWS = 3;\n\nconst STATUS_GLYPHS: Record<TodoStatus, string> = {\n\tin_progress: \"●\",\n\tpending: \"○\",\n\tcompleted: \"✓\",\n};\n\n/** Short model-facing summary, e.g. \"4 todos: 1 in progress, 2 pending, 1 completed\". */\nexport function summarizeTodos(todos: TodoItem[]): string {\n\tif (todos.length === 0) return \"No todos.\";\n\tconst count = (s: TodoStatus) => todos.filter((t) => t.status === s).length;\n\tconst parts: string[] = [];\n\tconst inProgress = count(\"in_progress\");\n\tconst pending = count(\"pending\");\n\tconst completed = count(\"completed\");\n\tif (inProgress > 0) parts.push(`${inProgress} in progress`);\n\tif (pending > 0) parts.push(`${pending} pending`);\n\tif (completed > 0) parts.push(`${completed} completed`);\n\treturn `${todos.length} todo${todos.length === 1 ? \"\" : \"s\"}: ${parts.join(\", \")}`;\n}\n\n/**\n * Widget lines for the current list. Collapsed mode shows up to\n * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending),\n * collapses completed items into a `✓ N done` summary, and appends a\n * `+N more (<expandKey> to expand)` hint when active rows are hidden.\n * Expanded mode shows every row. Empty list → no lines (widget removed).\n */\nexport function buildTodoWidgetLines(todos: TodoItem[], expanded: boolean, expandKey = \"ctrl+o\"): TodoWidgetLine[] {\n\tif (todos.length === 0) return [];\n\tconst row = (t: TodoItem): TodoWidgetLine => ({\n\t\tkind: \"todo\",\n\t\tstatus: t.status,\n\t\ttext: `${STATUS_GLYPHS[t.status]} ${t.content}`,\n\t});\n\tconst active = [\n\t\t...todos.filter((t) => t.status === \"in_progress\"),\n\t\t...todos.filter((t) => t.status === \"pending\"),\n\t];\n\tconst done = todos.filter((t) => t.status === \"completed\");\n\tif (expanded) {\n\t\treturn [...active.map(row), ...done.map(row)];\n\t}\n\tconst lines: TodoWidgetLine[] = active.slice(0, TODO_WIDGET_COLLAPSED_ROWS).map(row);\n\tif (done.length > 0) {\n\t\tlines.push({ kind: \"summary\", text: `✓ ${done.length} done` });\n\t}\n\tconst hidden = active.length - TODO_WIDGET_COLLAPSED_ROWS;\n\tif (hidden > 0) {\n\t\tlines.push({ kind: \"hint\", text: `+${hidden} more (${expandKey} to expand)` });\n\t}\n\treturn lines;\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nconst WIDGET_KEY = \"todos\";\nconst EXPANDED_BRIDGE_SYMBOL = Symbol.for(\"@lunr/tools-expanded-changed\");\n\nexport default function (pi: ExtensionAPI): void {\n\tlet todos: TodoItem[] = [];\n\tlet expanded = false;\n\tlet lastCtx: ExtensionContext | null = null;\n\n\tfunction colorize(line: TodoWidgetLine, theme: any): string {\n\t\tif (line.kind === \"summary\" || line.kind === \"hint\") return theme.fg(\"muted\", line.text);\n\t\tif (line.status === \"in_progress\") return theme.fg(\"accent\", line.text);\n\t\tif (line.status === \"completed\") return theme.fg(\"dim\", line.text);\n\t\treturn line.text;\n\t}\n\n\tfunction refreshWidget(): void {\n\t\tconst ctx = lastCtx;\n\t\tif (!ctx?.hasUI) return;\n\t\tif (todos.length === 0) {\n\t\t\tctx.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\t\tconst lines = buildTodoWidgetLines(todos, expanded, keyText(\"app.tools.expand\"));\n\t\tctx.ui.setWidget(\n\t\t\tWIDGET_KEY,\n\t\t\t(_tui: unknown, theme: any) => new Text(lines.map((l) => colorize(l, theme)).join(\"\\n\"), 1, 0),\n\t\t\t{ placement: \"aboveEditor\" },\n\t\t);\n\t}\n\n\t// --- Expansion bridge (interactive-mode setToolsExpanded invokes this) ---\n\tfunction registerExpandedBridge(): void {\n\t\t(globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL] = (value: boolean) => {\n\t\t\texpanded = Boolean(value);\n\t\t\trefreshWidget();\n\t\t};\n\t}\n\tregisterExpandedBridge();\n\n\t// --- Session lifecycle: per-session in-memory state ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\ttodos = [];\n\t\texpanded = ctx.hasUI ? (ctx.ui.getToolsExpanded?.() ?? false) : false;\n\t\tregisterExpandedBridge();\n\t\tif (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tif (lastCtx?.hasUI) lastCtx.ui.setWidget(WIDGET_KEY, undefined);\n\t\tlastCtx = null;\n\t\tdelete (globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL];\n\t});\n\n\t// --- todo tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"todo\",\n\t\tlabel: \"Todo\",\n\t\tdescription: [\n\t\t\t\"Manage the session todo list shown above the user's editor. Full-replace semantics:\",\n\t\t\t\"every call must send the ENTIRE list; send an empty array to clear it.\",\n\t\t\t\"Each item: { id, content, status: 'pending' | 'in_progress' | 'completed' }.\",\n\t\t\t\"Use it to track multi-step work; keep exactly one item in_progress at a time.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\ttodos: Type.Array(\n\t\t\t\tType.Object({\n\t\t\t\t\tid: Type.String({ description: \"Stable id for the item.\" }),\n\t\t\t\t\tcontent: Type.String({ description: \"Short description of the task.\" }),\n\t\t\t\t\tstatus: Type.Union([\n\t\t\t\t\t\tType.Literal(\"pending\"),\n\t\t\t\t\t\tType.Literal(\"in_progress\"),\n\t\t\t\t\t\tType.Literal(\"completed\"),\n\t\t\t\t\t]),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tconst incoming = Array.isArray(params?.todos) ? params.todos : [];\n\t\t\ttodos = incoming\n\t\t\t\t.filter((t: any) => t && typeof t.content === \"string\" && t.content.trim().length > 0)\n\t\t\t\t.map((t: any, i: number) => ({\n\t\t\t\t\tid: typeof t.id === \"string\" && t.id ? t.id : String(i + 1),\n\t\t\t\t\tcontent: t.content.trim(),\n\t\t\t\t\tstatus: t.status === \"in_progress\" || t.status === \"completed\" ? t.status : \"pending\",\n\t\t\t\t}));\n\t\t\tlastCtx = ctx;\n\t\t\trefreshWidget();\n\t\t\treturn text(summarizeTodos(todos));\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tconst n = Array.isArray(args?.todos) ? args.todos.length : 0;\n\t\t\tconst detail = n === 0 ? \"clear\" : `${n} item${n === 1 ? \"\" : \"s\"}`;\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todo \")) + theme.fg(\"dim\", detail), 0, 0);\n\t\t},\n\t\trenderResult(result, { isPartial }, theme) {\n\t\t\tif (isPartial) return new Text(theme.fg(\"warning\", \"Updating todos...\"), 0, 0);\n\t\t\tconst first = result?.content?.[0];\n\t\t\tconst summary = first?.type === \"text\" ? first.text : \"\";\n\t\t\treturn new Text(theme.fg(\"dim\", summary), 0, 0);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"lunr-todos.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-todos.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AAStF,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAEjE,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;IAClC,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,4EAA4E;AAC5E,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAQ5C,yFAAyF;AACzF,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAWxD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,SAAW,GAAG,cAAc,EAAE,CAqBjH;AASD,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,GAAG,IAAI,CAmH/C","sourcesContent":["// @ts-nocheck\n/**\n * lunr-todos — lunR-native agent todo list with an above-editor widget.\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension).\n *\n * - One `todo` tool (TypeBox), full-replace semantics: every call sends the\n * ENTIRE list; an empty array clears it. State is per-session, in-memory\n * only (extension closure) — nothing is persisted.\n * - Chat render is a quiet one-liner (renderCall/renderResult), matching the\n * cron tool's minimal footprint.\n * - After every state change the list is mirrored into a `todos` widget above\n * the editor (component factory, so MAX_WIDGET_LINES string truncation does\n * not apply). Collapsed: up to 3 active rows (in-progress first, then\n * pending). Completed items never appear collapsed. `+N more (<key> to\n * expand)` still applies to hidden active rows. Expanded: every row,\n * completed included until the next user turn prunes them.\n * - Expansion follows the global ctrl+o toggle via the\n * `@lunr/tools-expanded-changed` bridge, invoked from\n * interactive-mode.ts `setToolsExpanded` (initial state is read from\n * `ctx.ui.getToolsExpanded()`).\n *\n * The pure logic (summarizeTodos / buildTodoWidgetLines) is exported for\n * vitest — see test/lunr-todos.test.ts.\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-cron).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { Type } from \"@sinclair/typebox\";\nimport { keyText } from \"../modes/interactive/components/keybinding-hints.ts\";\n\n// ---------------------------------------------------------------------------\n// Pure logic (exported for tests)\n// ---------------------------------------------------------------------------\n\nexport type TodoStatus = \"pending\" | \"in_progress\" | \"completed\";\n\nexport interface TodoItem {\n\tid: string;\n\tcontent: string;\n\tstatus: TodoStatus;\n}\n\nexport interface TodoWidgetLine {\n\tkind: \"todo\" | \"summary\" | \"hint\";\n\tstatus?: TodoStatus;\n\ttext: string;\n}\n\n/** Collapsed widget shows at most this many active (non-completed) rows. */\nexport const TODO_WIDGET_COLLAPSED_ROWS = 3;\n\nconst STATUS_GLYPHS: Record<TodoStatus, string> = {\n\tin_progress: \"●\",\n\tpending: \"○\",\n\tcompleted: \"✓\",\n};\n\n/** Short model-facing summary, e.g. \"4 todos: 1 in progress, 2 pending, 1 completed\". */\nexport function summarizeTodos(todos: TodoItem[]): string {\n\tif (todos.length === 0) return \"No todos.\";\n\tconst count = (s: TodoStatus) => todos.filter((t) => t.status === s).length;\n\tconst parts: string[] = [];\n\tconst inProgress = count(\"in_progress\");\n\tconst pending = count(\"pending\");\n\tconst completed = count(\"completed\");\n\tif (inProgress > 0) parts.push(`${inProgress} in progress`);\n\tif (pending > 0) parts.push(`${pending} pending`);\n\tif (completed > 0) parts.push(`${completed} completed`);\n\treturn `${todos.length} todo${todos.length === 1 ? \"\" : \"s\"}: ${parts.join(\", \")}`;\n}\n\n/**\n * Widget lines for the current list. Collapsed mode shows up to\n * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending)\n * and never emits a `✓ N done` summary. A `+N more (<expandKey> to expand)`\n * hint is appended when active rows are hidden. Expanded mode shows every\n * row, completed included. Empty list / all-completed collapsed → no lines.\n */\nexport function buildTodoWidgetLines(todos: TodoItem[], expanded: boolean, expandKey = \"ctrl+o\"): TodoWidgetLine[] {\n\tif (todos.length === 0) return [];\n\tconst row = (t: TodoItem): TodoWidgetLine => ({\n\t\tkind: \"todo\",\n\t\tstatus: t.status,\n\t\ttext: `${STATUS_GLYPHS[t.status]} ${t.content}`,\n\t});\n\tconst active = [\n\t\t...todos.filter((t) => t.status === \"in_progress\"),\n\t\t...todos.filter((t) => t.status === \"pending\"),\n\t];\n\tconst done = todos.filter((t) => t.status === \"completed\");\n\tif (expanded) {\n\t\treturn [...active.map(row), ...done.map(row)];\n\t}\n\tconst lines: TodoWidgetLine[] = active.slice(0, TODO_WIDGET_COLLAPSED_ROWS).map(row);\n\tconst hidden = active.length - TODO_WIDGET_COLLAPSED_ROWS;\n\tif (hidden > 0) {\n\t\tlines.push({ kind: \"hint\", text: `+${hidden} more (${expandKey} to expand)` });\n\t}\n\treturn lines;\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nconst WIDGET_KEY = \"todos\";\nconst EXPANDED_BRIDGE_SYMBOL = Symbol.for(\"@lunr/tools-expanded-changed\");\n\nexport default function (pi: ExtensionAPI): void {\n\tlet todos: TodoItem[] = [];\n\tlet expanded = false;\n\tlet lastCtx: ExtensionContext | null = null;\n\n\tfunction colorize(line: TodoWidgetLine, theme: any): string {\n\t\tif (line.kind === \"summary\" || line.kind === \"hint\") return theme.fg(\"muted\", line.text);\n\t\tif (line.status === \"in_progress\") return theme.fg(\"accent\", line.text);\n\t\tif (line.status === \"completed\") return theme.fg(\"dim\", line.text);\n\t\treturn line.text;\n\t}\n\n\tfunction refreshWidget(): void {\n\t\tconst ctx = lastCtx;\n\t\tif (!ctx?.hasUI) return;\n\t\tconst lines = buildTodoWidgetLines(todos, expanded, keyText(\"app.tools.expand\"));\n\t\tif (todos.length === 0 || lines.length === 0) {\n\t\t\tctx.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\t\tctx.ui.setWidget(\n\t\t\tWIDGET_KEY,\n\t\t\t(_tui: unknown, theme: any) => new Text(lines.map((l) => colorize(l, theme)).join(\"\\n\"), 1, 0),\n\t\t\t{ placement: \"aboveEditor\" },\n\t\t);\n\t}\n\n\tfunction pruneCompleted(): void {\n\t\tconst next = todos.filter((t) => t.status !== \"completed\");\n\t\tif (next.length === todos.length) return;\n\t\ttodos = next;\n\t\trefreshWidget();\n\t}\n\n\t// --- Expansion bridge (interactive-mode setToolsExpanded invokes this) ---\n\tfunction registerExpandedBridge(): void {\n\t\t(globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL] = (value: boolean) => {\n\t\t\texpanded = Boolean(value);\n\t\t\trefreshWidget();\n\t\t};\n\t}\n\tregisterExpandedBridge();\n\n\t// --- Session lifecycle: per-session in-memory state ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\ttodos = [];\n\t\texpanded = ctx.hasUI ? (ctx.ui.getToolsExpanded?.() ?? false) : false;\n\t\tregisterExpandedBridge();\n\t\tif (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);\n\t});\n\n\t// lunr: prune completed items on the next real user message, not slash\n\t// commands or agent follow-up turns.\n\tpi.on(\"message_start\", (event, ctx) => {\n\t\tif (event.message?.role !== \"user\") return;\n\t\tlastCtx = ctx;\n\t\tpruneCompleted();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tif (lastCtx?.hasUI) lastCtx.ui.setWidget(WIDGET_KEY, undefined);\n\t\tlastCtx = null;\n\t\tdelete (globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL];\n\t});\n\n\t// --- todo tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"todo\",\n\t\tlabel: \"Todo\",\n\t\tdescription: [\n\t\t\t\"Manage the session todo list shown above the user's editor. Full-replace semantics:\",\n\t\t\t\"every call must send the ENTIRE list; send an empty array to clear it.\",\n\t\t\t\"Each item: { id, content, status: 'pending' | 'in_progress' | 'completed' }.\",\n\t\t\t\"Use it to track multi-step work; keep exactly one item in_progress at a time.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\ttodos: Type.Array(\n\t\t\t\tType.Object({\n\t\t\t\t\tid: Type.String({ description: \"Stable id for the item.\" }),\n\t\t\t\t\tcontent: Type.String({ description: \"Short description of the task.\" }),\n\t\t\t\t\tstatus: Type.Union([\n\t\t\t\t\t\tType.Literal(\"pending\"),\n\t\t\t\t\t\tType.Literal(\"in_progress\"),\n\t\t\t\t\t\tType.Literal(\"completed\"),\n\t\t\t\t\t]),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tconst incoming = Array.isArray(params?.todos) ? params.todos : [];\n\t\t\ttodos = incoming\n\t\t\t\t.filter((t: any) => t && typeof t.content === \"string\" && t.content.trim().length > 0)\n\t\t\t\t.map((t: any, i: number) => ({\n\t\t\t\t\tid: typeof t.id === \"string\" && t.id ? t.id : String(i + 1),\n\t\t\t\t\tcontent: t.content.trim(),\n\t\t\t\t\tstatus: t.status === \"in_progress\" || t.status === \"completed\" ? t.status : \"pending\",\n\t\t\t\t}));\n\t\t\tlastCtx = ctx;\n\t\t\trefreshWidget();\n\t\t\treturn text(summarizeTodos(todos));\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tconst n = Array.isArray(args?.todos) ? args.todos.length : 0;\n\t\t\tconst detail = n === 0 ? \"clear\" : `${n} item${n === 1 ? \"\" : \"s\"}`;\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todo \")) + theme.fg(\"dim\", detail), 0, 0);\n\t\t},\n\t\trenderResult(result, { isPartial }, theme) {\n\t\t\tif (isPartial) return new Text(theme.fg(\"warning\", \"Updating todos...\"), 0, 0);\n\t\t\tconst first = result?.content?.[0];\n\t\t\tconst summary = first?.type === \"text\" ? first.text : \"\";\n\t\t\treturn new Text(theme.fg(\"dim\", summary), 0, 0);\n\t\t},\n\t});\n}\n"]}
@@ -12,8 +12,9 @@
12
12
  * - After every state change the list is mirrored into a `todos` widget above
13
13
  * the editor (component factory, so MAX_WIDGET_LINES string truncation does
14
14
  * not apply). Collapsed: up to 3 active rows (in-progress first, then
15
- * pending) + a `✓ N done` summary + a `+N more (<key> to expand)` hint.
16
- * Expanded: every row, completed included.
15
+ * pending). Completed items never appear collapsed. `+N more (<key> to
16
+ * expand)` still applies to hidden active rows. Expanded: every row,
17
+ * completed included until the next user turn prunes them.
17
18
  * - Expansion follows the global ctrl+o toggle via the
18
19
  * `@lunr/tools-expanded-changed` bridge, invoked from
19
20
  * interactive-mode.ts `setToolsExpanded` (initial state is read from
@@ -54,10 +55,10 @@ export function summarizeTodos(todos) {
54
55
  }
55
56
  /**
56
57
  * Widget lines for the current list. Collapsed mode shows up to
57
- * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending),
58
- * collapses completed items into a `✓ N done` summary, and appends a
59
- * `+N more (<expandKey> to expand)` hint when active rows are hidden.
60
- * Expanded mode shows every row. Empty list → no lines (widget removed).
58
+ * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending)
59
+ * and never emits a `✓ N done` summary. A `+N more (<expandKey> to expand)`
60
+ * hint is appended when active rows are hidden. Expanded mode shows every
61
+ * row, completed included. Empty list / all-completed collapsed → no lines.
61
62
  */
62
63
  export function buildTodoWidgetLines(todos, expanded, expandKey = "ctrl+o") {
63
64
  if (todos.length === 0)
@@ -76,9 +77,6 @@ export function buildTodoWidgetLines(todos, expanded, expandKey = "ctrl+o") {
76
77
  return [...active.map(row), ...done.map(row)];
77
78
  }
78
79
  const lines = active.slice(0, TODO_WIDGET_COLLAPSED_ROWS).map(row);
79
- if (done.length > 0) {
80
- lines.push({ kind: "summary", text: `✓ ${done.length} done` });
81
- }
82
80
  const hidden = active.length - TODO_WIDGET_COLLAPSED_ROWS;
83
81
  if (hidden > 0) {
84
82
  lines.push({ kind: "hint", text: `+${hidden} more (${expandKey} to expand)` });
@@ -107,13 +105,20 @@ export default function (pi) {
107
105
  const ctx = lastCtx;
108
106
  if (!ctx?.hasUI)
109
107
  return;
110
- if (todos.length === 0) {
108
+ const lines = buildTodoWidgetLines(todos, expanded, keyText("app.tools.expand"));
109
+ if (todos.length === 0 || lines.length === 0) {
111
110
  ctx.ui.setWidget(WIDGET_KEY, undefined);
112
111
  return;
113
112
  }
114
- const lines = buildTodoWidgetLines(todos, expanded, keyText("app.tools.expand"));
115
113
  ctx.ui.setWidget(WIDGET_KEY, (_tui, theme) => new Text(lines.map((l) => colorize(l, theme)).join("\n"), 1, 0), { placement: "aboveEditor" });
116
114
  }
115
+ function pruneCompleted() {
116
+ const next = todos.filter((t) => t.status !== "completed");
117
+ if (next.length === todos.length)
118
+ return;
119
+ todos = next;
120
+ refreshWidget();
121
+ }
117
122
  // --- Expansion bridge (interactive-mode setToolsExpanded invokes this) ---
118
123
  function registerExpandedBridge() {
119
124
  globalThis[EXPANDED_BRIDGE_SYMBOL] = (value) => {
@@ -131,6 +136,14 @@ export default function (pi) {
131
136
  if (ctx.hasUI)
132
137
  ctx.ui.setWidget(WIDGET_KEY, undefined);
133
138
  });
139
+ // lunr: prune completed items on the next real user message, not slash
140
+ // commands or agent follow-up turns.
141
+ pi.on("message_start", (event, ctx) => {
142
+ if (event.message?.role !== "user")
143
+ return;
144
+ lastCtx = ctx;
145
+ pruneCompleted();
146
+ });
134
147
  pi.on("session_shutdown", () => {
135
148
  if (lastCtx?.hasUI)
136
149
  lastCtx.ui.setWidget(WIDGET_KEY, undefined);
@@ -1 +1 @@
1
- {"version":3,"file":"lunr-todos.js","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-todos.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\n/**\n * lunr-todos — lunR-native agent todo list with an above-editor widget.\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension).\n *\n * - One `todo` tool (TypeBox), full-replace semantics: every call sends the\n * ENTIRE list; an empty array clears it. State is per-session, in-memory\n * only (extension closure) — nothing is persisted.\n * - Chat render is a quiet one-liner (renderCall/renderResult), matching the\n * cron tool's minimal footprint.\n * - After every state change the list is mirrored into a `todos` widget above\n * the editor (component factory, so MAX_WIDGET_LINES string truncation does\n * not apply). Collapsed: up to 3 active rows (in-progress first, then\n * pending) + a `✓ N done` summary + a `+N more (<key> to expand)` hint.\n * Expanded: every row, completed included.\n * - Expansion follows the global ctrl+o toggle via the\n * `@lunr/tools-expanded-changed` bridge, invoked from\n * interactive-mode.ts `setToolsExpanded` (initial state is read from\n * `ctx.ui.getToolsExpanded()`).\n *\n * The pure logic (summarizeTodos / buildTodoWidgetLines) is exported for\n * vitest — see test/lunr-todos.test.ts.\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-cron).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { Type } from \"@sinclair/typebox\";\nimport { keyText } from \"../modes/interactive/components/keybinding-hints.ts\";\n\n// ---------------------------------------------------------------------------\n// Pure logic (exported for tests)\n// ---------------------------------------------------------------------------\n\nexport type TodoStatus = \"pending\" | \"in_progress\" | \"completed\";\n\nexport interface TodoItem {\n\tid: string;\n\tcontent: string;\n\tstatus: TodoStatus;\n}\n\nexport interface TodoWidgetLine {\n\tkind: \"todo\" | \"summary\" | \"hint\";\n\tstatus?: TodoStatus;\n\ttext: string;\n}\n\n/** Collapsed widget shows at most this many active (non-completed) rows. */\nexport const TODO_WIDGET_COLLAPSED_ROWS = 3;\n\nconst STATUS_GLYPHS: Record<TodoStatus, string> = {\n\tin_progress: \"●\",\n\tpending: \"○\",\n\tcompleted: \"✓\",\n};\n\n/** Short model-facing summary, e.g. \"4 todos: 1 in progress, 2 pending, 1 completed\". */\nexport function summarizeTodos(todos: TodoItem[]): string {\n\tif (todos.length === 0) return \"No todos.\";\n\tconst count = (s: TodoStatus) => todos.filter((t) => t.status === s).length;\n\tconst parts: string[] = [];\n\tconst inProgress = count(\"in_progress\");\n\tconst pending = count(\"pending\");\n\tconst completed = count(\"completed\");\n\tif (inProgress > 0) parts.push(`${inProgress} in progress`);\n\tif (pending > 0) parts.push(`${pending} pending`);\n\tif (completed > 0) parts.push(`${completed} completed`);\n\treturn `${todos.length} todo${todos.length === 1 ? \"\" : \"s\"}: ${parts.join(\", \")}`;\n}\n\n/**\n * Widget lines for the current list. Collapsed mode shows up to\n * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending),\n * collapses completed items into a `✓ N done` summary, and appends a\n * `+N more (<expandKey> to expand)` hint when active rows are hidden.\n * Expanded mode shows every row. Empty list → no lines (widget removed).\n */\nexport function buildTodoWidgetLines(todos: TodoItem[], expanded: boolean, expandKey = \"ctrl+o\"): TodoWidgetLine[] {\n\tif (todos.length === 0) return [];\n\tconst row = (t: TodoItem): TodoWidgetLine => ({\n\t\tkind: \"todo\",\n\t\tstatus: t.status,\n\t\ttext: `${STATUS_GLYPHS[t.status]} ${t.content}`,\n\t});\n\tconst active = [\n\t\t...todos.filter((t) => t.status === \"in_progress\"),\n\t\t...todos.filter((t) => t.status === \"pending\"),\n\t];\n\tconst done = todos.filter((t) => t.status === \"completed\");\n\tif (expanded) {\n\t\treturn [...active.map(row), ...done.map(row)];\n\t}\n\tconst lines: TodoWidgetLine[] = active.slice(0, TODO_WIDGET_COLLAPSED_ROWS).map(row);\n\tif (done.length > 0) {\n\t\tlines.push({ kind: \"summary\", text: `✓ ${done.length} done` });\n\t}\n\tconst hidden = active.length - TODO_WIDGET_COLLAPSED_ROWS;\n\tif (hidden > 0) {\n\t\tlines.push({ kind: \"hint\", text: `+${hidden} more (${expandKey} to expand)` });\n\t}\n\treturn lines;\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nconst WIDGET_KEY = \"todos\";\nconst EXPANDED_BRIDGE_SYMBOL = Symbol.for(\"@lunr/tools-expanded-changed\");\n\nexport default function (pi: ExtensionAPI): void {\n\tlet todos: TodoItem[] = [];\n\tlet expanded = false;\n\tlet lastCtx: ExtensionContext | null = null;\n\n\tfunction colorize(line: TodoWidgetLine, theme: any): string {\n\t\tif (line.kind === \"summary\" || line.kind === \"hint\") return theme.fg(\"muted\", line.text);\n\t\tif (line.status === \"in_progress\") return theme.fg(\"accent\", line.text);\n\t\tif (line.status === \"completed\") return theme.fg(\"dim\", line.text);\n\t\treturn line.text;\n\t}\n\n\tfunction refreshWidget(): void {\n\t\tconst ctx = lastCtx;\n\t\tif (!ctx?.hasUI) return;\n\t\tif (todos.length === 0) {\n\t\t\tctx.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\t\tconst lines = buildTodoWidgetLines(todos, expanded, keyText(\"app.tools.expand\"));\n\t\tctx.ui.setWidget(\n\t\t\tWIDGET_KEY,\n\t\t\t(_tui: unknown, theme: any) => new Text(lines.map((l) => colorize(l, theme)).join(\"\\n\"), 1, 0),\n\t\t\t{ placement: \"aboveEditor\" },\n\t\t);\n\t}\n\n\t// --- Expansion bridge (interactive-mode setToolsExpanded invokes this) ---\n\tfunction registerExpandedBridge(): void {\n\t\t(globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL] = (value: boolean) => {\n\t\t\texpanded = Boolean(value);\n\t\t\trefreshWidget();\n\t\t};\n\t}\n\tregisterExpandedBridge();\n\n\t// --- Session lifecycle: per-session in-memory state ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\ttodos = [];\n\t\texpanded = ctx.hasUI ? (ctx.ui.getToolsExpanded?.() ?? false) : false;\n\t\tregisterExpandedBridge();\n\t\tif (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tif (lastCtx?.hasUI) lastCtx.ui.setWidget(WIDGET_KEY, undefined);\n\t\tlastCtx = null;\n\t\tdelete (globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL];\n\t});\n\n\t// --- todo tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"todo\",\n\t\tlabel: \"Todo\",\n\t\tdescription: [\n\t\t\t\"Manage the session todo list shown above the user's editor. Full-replace semantics:\",\n\t\t\t\"every call must send the ENTIRE list; send an empty array to clear it.\",\n\t\t\t\"Each item: { id, content, status: 'pending' | 'in_progress' | 'completed' }.\",\n\t\t\t\"Use it to track multi-step work; keep exactly one item in_progress at a time.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\ttodos: Type.Array(\n\t\t\t\tType.Object({\n\t\t\t\t\tid: Type.String({ description: \"Stable id for the item.\" }),\n\t\t\t\t\tcontent: Type.String({ description: \"Short description of the task.\" }),\n\t\t\t\t\tstatus: Type.Union([\n\t\t\t\t\t\tType.Literal(\"pending\"),\n\t\t\t\t\t\tType.Literal(\"in_progress\"),\n\t\t\t\t\t\tType.Literal(\"completed\"),\n\t\t\t\t\t]),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tconst incoming = Array.isArray(params?.todos) ? params.todos : [];\n\t\t\ttodos = incoming\n\t\t\t\t.filter((t: any) => t && typeof t.content === \"string\" && t.content.trim().length > 0)\n\t\t\t\t.map((t: any, i: number) => ({\n\t\t\t\t\tid: typeof t.id === \"string\" && t.id ? t.id : String(i + 1),\n\t\t\t\t\tcontent: t.content.trim(),\n\t\t\t\t\tstatus: t.status === \"in_progress\" || t.status === \"completed\" ? t.status : \"pending\",\n\t\t\t\t}));\n\t\t\tlastCtx = ctx;\n\t\t\trefreshWidget();\n\t\t\treturn text(summarizeTodos(todos));\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tconst n = Array.isArray(args?.todos) ? args.todos.length : 0;\n\t\t\tconst detail = n === 0 ? \"clear\" : `${n} item${n === 1 ? \"\" : \"s\"}`;\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todo \")) + theme.fg(\"dim\", detail), 0, 0);\n\t\t},\n\t\trenderResult(result, { isPartial }, theme) {\n\t\t\tif (isPartial) return new Text(theme.fg(\"warning\", \"Updating todos...\"), 0, 0);\n\t\t\tconst first = result?.content?.[0];\n\t\t\tconst summary = first?.type === \"text\" ? first.text : \"\";\n\t\t\treturn new Text(theme.fg(\"dim\", summary), 0, 0);\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"lunr-todos.js","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-todos.ts"],"names":[],"mappings":"AAAA,cAAc;AACd;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,qDAAqD,CAAC;AAoB9E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC;AAE5C,MAAM,aAAa,GAA+B;IACjD,WAAW,EAAE,KAAG;IAChB,OAAO,EAAE,KAAG;IACZ,SAAS,EAAE,KAAG;CACd,CAAC;AAEF,yFAAyF;AACzF,MAAM,UAAU,cAAc,CAAC,KAAiB,EAAU;IACzD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,WAAW,CAAC;IAC3C,MAAM,KAAK,GAAG,CAAC,CAAa,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5E,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IACrC,IAAI,UAAU,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,cAAc,CAAC,CAAC;IAC5D,IAAI,OAAO,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,UAAU,CAAC,CAAC;IAClD,IAAI,SAAS,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,SAAS,YAAY,CAAC,CAAC;IACxD,OAAO,GAAG,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAAA,CACnF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAiB,EAAE,QAAiB,EAAE,SAAS,GAAG,QAAQ,EAAoB;IAClH,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,CAAC,CAAW,EAAkB,EAAE,CAAC,CAAC;QAC7C,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,IAAI,EAAE,GAAG,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE;KAC/C,CAAC,CAAC;IACH,MAAM,MAAM,GAAG;QACd,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,aAAa,CAAC;QAClD,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC;KAC9C,CAAC;IACF,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;IAC3D,IAAI,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,MAAM,KAAK,GAAqB,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,0BAA0B,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACrF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,GAAG,0BAA0B,CAAC;IAC1D,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,MAAM,UAAU,SAAS,aAAa,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb;AAED,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,MAAM,UAAU,GAAG,OAAO,CAAC;AAC3B,MAAM,sBAAsB,GAAG,MAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;AAE1E,MAAM,CAAC,OAAO,WAAW,EAAgB,EAAQ;IAChD,IAAI,KAAK,GAAe,EAAE,CAAC;IAC3B,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,OAAO,GAA4B,IAAI,CAAC;IAE5C,SAAS,QAAQ,CAAC,IAAoB,EAAE,KAAU,EAAU;QAC3D,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACzF,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa;YAAE,OAAO,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACxE,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW;YAAE,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,OAAO,IAAI,CAAC,IAAI,CAAC;IAAA,CACjB;IAED,SAAS,aAAa,GAAS;QAC9B,MAAM,GAAG,GAAG,OAAO,CAAC;QACpB,IAAI,CAAC,GAAG,EAAE,KAAK;YAAE,OAAO;QACxB,MAAM,KAAK,GAAG,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC;QACjF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9C,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;YACxC,OAAO;QACR,CAAC;QACD,GAAG,CAAC,EAAE,CAAC,SAAS,CACf,UAAU,EACV,CAAC,IAAa,EAAE,KAAU,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC9F,EAAE,SAAS,EAAE,aAAa,EAAE,CAC5B,CAAC;IAAA,CACF;IAED,SAAS,cAAc,GAAS;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;QAC3D,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;YAAE,OAAO;QACzC,KAAK,GAAG,IAAI,CAAC;QACb,aAAa,EAAE,CAAC;IAAA,CAChB;IAED,4EAA4E;IAC5E,SAAS,sBAAsB,GAAS;QACtC,UAAsC,CAAC,sBAAsB,CAAC,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC;YACrF,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1B,aAAa,EAAE,CAAC;QAAA,CAChB,CAAC;IAAA,CACF;IACD,sBAAsB,EAAE,CAAC;IAEzB,yDAAyD;IACzD,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QACvC,OAAO,GAAG,GAAG,CAAC;QACd,KAAK,GAAG,EAAE,CAAC;QACX,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,gBAAgB,EAAE,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACtE,sBAAsB,EAAE,CAAC;QACzB,IAAI,GAAG,CAAC,KAAK;YAAE,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAAA,CACvD,CAAC,CAAC;IAEH,uEAAuE;IACvE,qCAAqC;IACrC,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC;QACtC,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM;YAAE,OAAO;QAC3C,OAAO,GAAG,GAAG,CAAC;QACd,cAAc,EAAE,CAAC;IAAA,CACjB,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC;QAC/B,IAAI,OAAO,EAAE,KAAK;YAAE,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAChE,OAAO,GAAG,IAAI,CAAC;QACf,OAAQ,UAAsC,CAAC,sBAAsB,CAAC,CAAC;IAAA,CACvE,CAAC,CAAC;IAEH,mCAAmC;IACnC,EAAE,CAAC,YAAY,CAAC;QACf,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,MAAM;QACb,WAAW,EAAE;YACZ,qFAAqF;YACrF,wEAAwE;YACxE,8EAA8E;YAC9E,+EAA+E;SAC/E,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC;YACvB,KAAK,EAAE,IAAI,CAAC,KAAK,CAChB,IAAI,CAAC,MAAM,CAAC;gBACX,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,yBAAyB,EAAE,CAAC;gBAC3D,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,gCAAgC,EAAE,CAAC;gBACvE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC;oBAClB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;oBACvB,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;oBAC3B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;iBACzB,CAAC;aACF,CAAC,CACF;SACD,CAAC;QACF,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE;YAC3D,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YAChF,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAClE,KAAK,GAAG,QAAQ;iBACd,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;iBACrF,GAAG,CAAC,CAAC,CAAM,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC;gBAC5B,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;gBAC3D,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE;gBACzB,MAAM,EAAE,CAAC,CAAC,MAAM,KAAK,aAAa,IAAI,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;aACrF,CAAC,CAAC,CAAC;YACL,OAAO,GAAG,GAAG,CAAC;YACd,aAAa,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAAA,CACnC;QACD,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE;YACvB,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7D,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YACpE,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA,CAC5F;QACD,YAAY,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE;YAC1C,IAAI,SAAS;gBAAE,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,mBAAmB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/E,MAAM,KAAK,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAAA,CAChD;KACD,CAAC,CAAC;AAAA,CACH","sourcesContent":["// @ts-nocheck\n/**\n * lunr-todos — lunR-native agent todo list with an above-editor widget.\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension).\n *\n * - One `todo` tool (TypeBox), full-replace semantics: every call sends the\n * ENTIRE list; an empty array clears it. State is per-session, in-memory\n * only (extension closure) — nothing is persisted.\n * - Chat render is a quiet one-liner (renderCall/renderResult), matching the\n * cron tool's minimal footprint.\n * - After every state change the list is mirrored into a `todos` widget above\n * the editor (component factory, so MAX_WIDGET_LINES string truncation does\n * not apply). Collapsed: up to 3 active rows (in-progress first, then\n * pending). Completed items never appear collapsed. `+N more (<key> to\n * expand)` still applies to hidden active rows. Expanded: every row,\n * completed included until the next user turn prunes them.\n * - Expansion follows the global ctrl+o toggle via the\n * `@lunr/tools-expanded-changed` bridge, invoked from\n * interactive-mode.ts `setToolsExpanded` (initial state is read from\n * `ctx.ui.getToolsExpanded()`).\n *\n * The pure logic (summarizeTodos / buildTodoWidgetLines) is exported for\n * vitest — see test/lunr-todos.test.ts.\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-cron).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { Type } from \"@sinclair/typebox\";\nimport { keyText } from \"../modes/interactive/components/keybinding-hints.ts\";\n\n// ---------------------------------------------------------------------------\n// Pure logic (exported for tests)\n// ---------------------------------------------------------------------------\n\nexport type TodoStatus = \"pending\" | \"in_progress\" | \"completed\";\n\nexport interface TodoItem {\n\tid: string;\n\tcontent: string;\n\tstatus: TodoStatus;\n}\n\nexport interface TodoWidgetLine {\n\tkind: \"todo\" | \"summary\" | \"hint\";\n\tstatus?: TodoStatus;\n\ttext: string;\n}\n\n/** Collapsed widget shows at most this many active (non-completed) rows. */\nexport const TODO_WIDGET_COLLAPSED_ROWS = 3;\n\nconst STATUS_GLYPHS: Record<TodoStatus, string> = {\n\tin_progress: \"●\",\n\tpending: \"○\",\n\tcompleted: \"✓\",\n};\n\n/** Short model-facing summary, e.g. \"4 todos: 1 in progress, 2 pending, 1 completed\". */\nexport function summarizeTodos(todos: TodoItem[]): string {\n\tif (todos.length === 0) return \"No todos.\";\n\tconst count = (s: TodoStatus) => todos.filter((t) => t.status === s).length;\n\tconst parts: string[] = [];\n\tconst inProgress = count(\"in_progress\");\n\tconst pending = count(\"pending\");\n\tconst completed = count(\"completed\");\n\tif (inProgress > 0) parts.push(`${inProgress} in progress`);\n\tif (pending > 0) parts.push(`${pending} pending`);\n\tif (completed > 0) parts.push(`${completed} completed`);\n\treturn `${todos.length} todo${todos.length === 1 ? \"\" : \"s\"}: ${parts.join(\", \")}`;\n}\n\n/**\n * Widget lines for the current list. Collapsed mode shows up to\n * TODO_WIDGET_COLLAPSED_ROWS active rows (in-progress first, then pending)\n * and never emits a `✓ N done` summary. A `+N more (<expandKey> to expand)`\n * hint is appended when active rows are hidden. Expanded mode shows every\n * row, completed included. Empty list / all-completed collapsed → no lines.\n */\nexport function buildTodoWidgetLines(todos: TodoItem[], expanded: boolean, expandKey = \"ctrl+o\"): TodoWidgetLine[] {\n\tif (todos.length === 0) return [];\n\tconst row = (t: TodoItem): TodoWidgetLine => ({\n\t\tkind: \"todo\",\n\t\tstatus: t.status,\n\t\ttext: `${STATUS_GLYPHS[t.status]} ${t.content}`,\n\t});\n\tconst active = [\n\t\t...todos.filter((t) => t.status === \"in_progress\"),\n\t\t...todos.filter((t) => t.status === \"pending\"),\n\t];\n\tconst done = todos.filter((t) => t.status === \"completed\");\n\tif (expanded) {\n\t\treturn [...active.map(row), ...done.map(row)];\n\t}\n\tconst lines: TodoWidgetLine[] = active.slice(0, TODO_WIDGET_COLLAPSED_ROWS).map(row);\n\tconst hidden = active.length - TODO_WIDGET_COLLAPSED_ROWS;\n\tif (hidden > 0) {\n\t\tlines.push({ kind: \"hint\", text: `+${hidden} more (${expandKey} to expand)` });\n\t}\n\treturn lines;\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nconst WIDGET_KEY = \"todos\";\nconst EXPANDED_BRIDGE_SYMBOL = Symbol.for(\"@lunr/tools-expanded-changed\");\n\nexport default function (pi: ExtensionAPI): void {\n\tlet todos: TodoItem[] = [];\n\tlet expanded = false;\n\tlet lastCtx: ExtensionContext | null = null;\n\n\tfunction colorize(line: TodoWidgetLine, theme: any): string {\n\t\tif (line.kind === \"summary\" || line.kind === \"hint\") return theme.fg(\"muted\", line.text);\n\t\tif (line.status === \"in_progress\") return theme.fg(\"accent\", line.text);\n\t\tif (line.status === \"completed\") return theme.fg(\"dim\", line.text);\n\t\treturn line.text;\n\t}\n\n\tfunction refreshWidget(): void {\n\t\tconst ctx = lastCtx;\n\t\tif (!ctx?.hasUI) return;\n\t\tconst lines = buildTodoWidgetLines(todos, expanded, keyText(\"app.tools.expand\"));\n\t\tif (todos.length === 0 || lines.length === 0) {\n\t\t\tctx.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\t\tctx.ui.setWidget(\n\t\t\tWIDGET_KEY,\n\t\t\t(_tui: unknown, theme: any) => new Text(lines.map((l) => colorize(l, theme)).join(\"\\n\"), 1, 0),\n\t\t\t{ placement: \"aboveEditor\" },\n\t\t);\n\t}\n\n\tfunction pruneCompleted(): void {\n\t\tconst next = todos.filter((t) => t.status !== \"completed\");\n\t\tif (next.length === todos.length) return;\n\t\ttodos = next;\n\t\trefreshWidget();\n\t}\n\n\t// --- Expansion bridge (interactive-mode setToolsExpanded invokes this) ---\n\tfunction registerExpandedBridge(): void {\n\t\t(globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL] = (value: boolean) => {\n\t\t\texpanded = Boolean(value);\n\t\t\trefreshWidget();\n\t\t};\n\t}\n\tregisterExpandedBridge();\n\n\t// --- Session lifecycle: per-session in-memory state ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\ttodos = [];\n\t\texpanded = ctx.hasUI ? (ctx.ui.getToolsExpanded?.() ?? false) : false;\n\t\tregisterExpandedBridge();\n\t\tif (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, undefined);\n\t});\n\n\t// lunr: prune completed items on the next real user message, not slash\n\t// commands or agent follow-up turns.\n\tpi.on(\"message_start\", (event, ctx) => {\n\t\tif (event.message?.role !== \"user\") return;\n\t\tlastCtx = ctx;\n\t\tpruneCompleted();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tif (lastCtx?.hasUI) lastCtx.ui.setWidget(WIDGET_KEY, undefined);\n\t\tlastCtx = null;\n\t\tdelete (globalThis as Record<symbol, unknown>)[EXPANDED_BRIDGE_SYMBOL];\n\t});\n\n\t// --- todo tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"todo\",\n\t\tlabel: \"Todo\",\n\t\tdescription: [\n\t\t\t\"Manage the session todo list shown above the user's editor. Full-replace semantics:\",\n\t\t\t\"every call must send the ENTIRE list; send an empty array to clear it.\",\n\t\t\t\"Each item: { id, content, status: 'pending' | 'in_progress' | 'completed' }.\",\n\t\t\t\"Use it to track multi-step work; keep exactly one item in_progress at a time.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\ttodos: Type.Array(\n\t\t\t\tType.Object({\n\t\t\t\t\tid: Type.String({ description: \"Stable id for the item.\" }),\n\t\t\t\t\tcontent: Type.String({ description: \"Short description of the task.\" }),\n\t\t\t\t\tstatus: Type.Union([\n\t\t\t\t\t\tType.Literal(\"pending\"),\n\t\t\t\t\t\tType.Literal(\"in_progress\"),\n\t\t\t\t\t\tType.Literal(\"completed\"),\n\t\t\t\t\t]),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tconst incoming = Array.isArray(params?.todos) ? params.todos : [];\n\t\t\ttodos = incoming\n\t\t\t\t.filter((t: any) => t && typeof t.content === \"string\" && t.content.trim().length > 0)\n\t\t\t\t.map((t: any, i: number) => ({\n\t\t\t\t\tid: typeof t.id === \"string\" && t.id ? t.id : String(i + 1),\n\t\t\t\t\tcontent: t.content.trim(),\n\t\t\t\t\tstatus: t.status === \"in_progress\" || t.status === \"completed\" ? t.status : \"pending\",\n\t\t\t\t}));\n\t\t\tlastCtx = ctx;\n\t\t\trefreshWidget();\n\t\t\treturn text(summarizeTodos(todos));\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tconst n = Array.isArray(args?.todos) ? args.todos.length : 0;\n\t\t\tconst detail = n === 0 ? \"clear\" : `${n} item${n === 1 ? \"\" : \"s\"}`;\n\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"todo \")) + theme.fg(\"dim\", detail), 0, 0);\n\t\t},\n\t\trenderResult(result, { isPartial }, theme) {\n\t\t\tif (isPartial) return new Text(theme.fg(\"warning\", \"Updating todos...\"), 0, 0);\n\t\t\tconst first = result?.content?.[0];\n\t\t\tconst summary = first?.type === \"text\" ? first.text : \"\";\n\t\t\treturn new Text(theme.fg(\"dim\", summary), 0, 0);\n\t\t},\n\t});\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/builtin-extensions/narumiruna-pi-goal/src/commands.ts"],"names":[],"mappings":"AAYA,OAAO,EAON,KAAK,WAAW,EAMhB,KAAK,aAAa,EAGlB,MAAM,cAAc,CAAC;AAItB,qBAAa,qBAAqB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IAEtC,YAAY,OAAO,EAAE,WAAW,EAE/B;IAEK,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,aAAa,iBAiFrF;IAEK,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,aAAa,iBAmBnF;IAEK,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,aAAa,iBAkB1F;IAED,YAAY,CAAC,GAAG,EAAE,aAAa,QAe9B;IAEK,QAAQ,CAAC,GAAG,EAAE,aAAa,iBA2BhC;IAEK,mCAAmC,CAAC,GAAG,EAAE,aAAa,oBA2F3D;IAED,iBAAiB,CAAC,GAAG,EAAE,aAAa,QAKnC;IAED,SAAS,CAAC,GAAG,EAAE,aAAa,QAqB3B;IAEK,UAAU,CAAC,GAAG,EAAE,aAAa,iBAsElC;IAED,SAAS,CAAC,GAAG,EAAE,aAAa,QAe3B;IAEK,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,aAAa,iBA8EpF;IAED,QAAQ,CAAC,GAAG,EAAE,aAAa,QAoB1B;YAEa,uBAAuB;CAsFrC","sourcesContent":["// @ts-nocheck\nimport { currentTokenTotal } from \"./accounting.js\";\nimport { validateObjective } from \"./command.js\";\nimport { buildGoalPrompt, buildObjectiveUpdatedPrompt, buildResumePrompt } from \"./prompts.js\";\nimport {\n\tactivateQueuedGoal,\n\tappendGoal,\n\tcreateQueuedGoal,\n\tdropLastGoal as dropLastQueuedGoal,\n\tprioritizeGoal as prioritizeQueuedGoal,\n\tskipGoal as skipQueuedGoal,\n} from \"./queue.js\";\nimport {\n\tabortCurrentTurn,\n\tblocksStaleGoalToolCalls,\n\tcreateGoal,\n\teditedGoalStatus,\n\tformatBudget,\n\tformatError,\n\ttype GoalRuntime,\n\tgoalSummary,\n\thasPendingMessages,\n\tisResumableGoalStatus,\n\tnextGoalInstance,\n\tSTATUS_KEY,\n\ttype StatusContext,\n\tstoppedStatusLabel,\n\ttransitionGoal,\n} from \"./runtime.js\";\n\n// User-command mutations are kept separate from Pi event wiring. Every controller\n// receives exactly one per-factory GoalRuntime, preserving session isolation.\nexport class GoalCommandController {\n\tprivate readonly runtime: GoalRuntime;\n\n\tconstructor(runtime: GoalRuntime) {\n\t\tthis.runtime = runtime;\n\t}\n\n\tasync startGoal(objective: string, tokenBudget: number | undefined, ctx: StatusContext) {\n\t\tconst validationError = validateObjective(objective);\n\t\tif (validationError) {\n\t\t\tctx.ui.notify(validationError, \"warning\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst existingGoal =\n\t\t\tthis.runtime.activeGoal?.status !== \"complete\" ? this.runtime.activeGoal : undefined;\n\t\tconst existingQueuedGoals = [...this.runtime.queuedGoals];\n\t\tif (existingGoal) {\n\t\t\tconst shouldReplace = await ctx.ui.confirm(\n\t\t\t\t\"Replace goal?\",\n\t\t\t\t`Current goal: ${existingGoal.text}\\n\\nNew goal: ${objective}`,\n\t\t\t);\n\t\t\tif (!shouldReplace) {\n\t\t\t\tctx.ui.notify(`Goal kept: ${existingGoal.text}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Unlock lazy visibility only for a real activation. In always mode, a\n\t\t// missing tool means another policy or allowlist intentionally removed it.\n\t\tconst goalToolVisibilityBeforeActivation = this.runtime.snapshotGoalToolVisibility();\n\t\ttry {\n\t\t\tthis.runtime.prepareGoalToolsForActivation(ctx);\n\t\t} catch (error) {\n\t\t\tctx.ui.notify(`Cannot start /goal: ${formatError(error)}`, \"error\");\n\t\t\tif (existingGoal?.status === \"active\") this.runtime.pauseGoalForUnavailableTools(ctx);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\tthis.runtime.queuedGoals = [];\n\t\tthis.runtime.pendingQueueAction = undefined;\n\t\tthis.runtime.activeGoal = createGoal(objective, tokenBudget, currentTokenTotal(ctx));\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tconst startedGoal = this.runtime.activeGoal;\n\t\tconst sent = await this.runtime.sendOwnedGoalPrompt(\n\t\t\tctx,\n\t\t\tstartedGoal.id,\n\t\t\tbuildGoalPrompt(startedGoal),\n\t\t);\n\t\tif (!sent) {\n\t\t\tlet rolledBackStartedGoal = false;\n\t\t\tif (this.runtime.activeGoal?.id === startedGoal.id) {\n\t\t\t\trolledBackStartedGoal = true;\n\t\t\t\tif (existingGoal) {\n\t\t\t\t\tthis.runtime.queuedGoals = existingQueuedGoals;\n\t\t\t\t\tthis.runtime.recordGoalUsage(existingGoal, ctx);\n\t\t\t\t\tif (existingGoal.status === \"active\") {\n\t\t\t\t\t\tabortCurrentTurn(ctx);\n\t\t\t\t\t\tthis.runtime.activeGoal = transitionGoal(existingGoal, \"paused\");\n\t\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.runtime.activeGoal = existingGoal;\n\t\t\t\t\t\tif (blocksStaleGoalToolCalls(this.runtime.activeGoal.status)) {\n\t\t\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\t\t} else {\n\t\t\t\t\tthis.runtime.clearActiveGoal(ctx);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (rolledBackStartedGoal) {\n\t\t\t\tthis.runtime.restoreGoalToolVisibility(goalToolVisibilityBeforeActivation);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tctx.ui.notify(\n\t\t\texistingGoal ? `Goal replaced: ${objective}` : `Goal started: ${objective}`,\n\t\t\t\"info\",\n\t\t);\n\t}\n\n\tasync addGoal(objective: string, tokenBudget: number | undefined, ctx: StatusContext) {\n\t\tconst validationError = validateObjective(objective);\n\t\tif (validationError) {\n\t\t\tctx.ui.notify(validationError, \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tawait this.startGoal(objective, tokenBudget, ctx);\n\t\t\treturn;\n\t\t}\n\t\tthis.runtime.queuedGoals = appendGoal(\n\t\t\tthis.runtime.queuedGoals,\n\t\t\tcreateQueuedGoal(objective, tokenBudget),\n\t\t);\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tctx.ui.notify(\n\t\t\t`Goal added at position ${this.runtime.queuedGoals.length + 1}: ${objective}`,\n\t\t\t\"info\",\n\t\t);\n\t}\n\n\tasync prioritizeGoal(objective: string, tokenBudget: number | undefined, ctx: StatusContext) {\n\t\tconst validationError = validateObjective(objective);\n\t\tif (validationError) {\n\t\t\tctx.ui.notify(validationError, \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tawait this.startGoal(objective, tokenBudget, ctx);\n\t\t\treturn;\n\t\t}\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.pendingQueueAction = { kind: \"prioritize\", objective, tokenBudget };\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tif (ctx.isIdle?.() !== true || hasPendingMessages(ctx)) {\n\t\t\tctx.ui.notify(`Priority goal queued until Pi settles: ${objective}`, \"info\");\n\t\t\treturn;\n\t\t}\n\t\tawait this.dispatchPendingQueueActionIfSettled(ctx);\n\t}\n\n\tdropLastGoal(ctx: StatusContext) {\n\t\tconst currentGoal = this.runtime.activeGoal;\n\t\tif (!currentGoal) {\n\t\t\tctx.ui.notify(\"No goals to drop.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t\tconst result = dropLastQueuedGoal(currentGoal, this.runtime.queuedGoals);\n\t\tif (!result.goal) {\n\t\t\tthis.runtime.clearActiveGoal(ctx);\n\t\t\tctx.ui.notify(`Goal dropped: ${result.removed?.text ?? currentGoal.text}`, \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tthis.runtime.queuedGoals = result.queue;\n\t\tthis.runtime.persistGoal(result.goal);\n\t\tctx.ui.notify(`Goal dropped: ${result.removed?.text ?? \"unknown goal\"}`, \"warning\");\n\t}\n\n\tasync skipGoal(ctx: StatusContext) {\n\t\tconst currentGoal = this.runtime.activeGoal;\n\t\tif (!currentGoal) {\n\t\t\tctx.ui.notify(\"No goals to skip.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t\tif (this.runtime.queuedGoals.length === 0) {\n\t\t\tthis.runtime.clearActiveGoal(ctx);\n\t\t\tctx.ui.notify(`Goal skipped: ${currentGoal.text}. No goals remain.`, \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tif (currentGoal.status === \"active\") this.runtime.recordGoalUsage(currentGoal, ctx);\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\tthis.runtime.pendingQueueAction = {\n\t\t\tkind: \"advance\",\n\t\t\tgoalId: currentGoal.id,\n\t\t\treason: \"skip\",\n\t\t\tcompletedText: currentGoal.text,\n\t\t};\n\t\tthis.runtime.persistGoal(currentGoal);\n\t\tctx.ui.notify(`Goal skip queued until Pi settles: ${currentGoal.text}`, \"info\");\n\t\tif (ctx.isIdle?.() === true && !hasPendingMessages(ctx)) {\n\t\t\tawait this.dispatchPendingQueueActionIfSettled(ctx);\n\t\t}\n\t}\n\n\tasync dispatchPendingQueueActionIfSettled(ctx: StatusContext) {\n\t\tconst pending = this.runtime.pendingQueueAction;\n\t\tif (!pending || this.runtime.queueFrozen) return false;\n\t\tif (ctx.isIdle?.() !== true || hasPendingMessages(ctx)) return false;\n\t\tif (pending.kind === \"prioritize\") {\n\t\t\tthis.runtime.pendingQueueAction = undefined;\n\t\t\treturn this.activatePrioritizedGoal(\n\t\t\t\tpending.objective,\n\t\t\t\tpending.tokenBudget,\n\t\t\t\tctx,\n\t\t\t\tpending.displacedUsageFinalized === true,\n\t\t\t);\n\t\t}\n\t\tif (\n\t\t\t!this.runtime.activeGoal ||\n\t\t\tthis.runtime.activeGoal.id !== pending.goalId ||\n\t\t\t(this.runtime.activeGoal.status !== \"complete\" && pending.reason === \"complete\")\n\t\t) {\n\t\t\tthis.runtime.pendingQueueAction = undefined;\n\t\t\tif (this.runtime.activeGoal) this.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\treturn false;\n\t\t}\n\n\t\tconst previousText = pending.completedText;\n\t\tconst reason = pending.reason;\n\t\tthis.runtime.pendingQueueAction = undefined;\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\tconst next = skipQueuedGoal(this.runtime.queuedGoals);\n\t\tthis.runtime.queuedGoals = next.queue;\n\t\tthis.runtime.activeGoal = next.goal\n\t\t\t? activateQueuedGoal(next.goal, currentTokenTotal(ctx))\n\t\t\t: undefined;\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tthis.runtime.clearActiveGoal(ctx);\n\t\t\tctx.ui.notify(\n\t\t\t\treason === \"complete\"\n\t\t\t\t\t? `Goal complete: ${previousText}. No goals remain.`\n\t\t\t\t\t: `Goal skipped: ${previousText}. No goals remain.`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t\treturn true;\n\t\t}\n\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tif (this.runtime.activeGoal.status !== \"active\") {\n\t\t\tif (blocksStaleGoalToolCalls(this.runtime.activeGoal.status)) {\n\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t}\n\t\t\tctx.ui.notify(\n\t\t\t\t`${reason === \"complete\" ? \"Goal complete\" : \"Goal skipped\"}: ${previousText}. Next goal remains ${this.runtime.activeGoal.status}: ${this.runtime.activeGoal.text}`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t\treturn true;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.runtime.prepareGoalToolsForActivation(ctx);\n\t\t} catch (error) {\n\t\t\tthis.runtime.activeGoal = transitionGoal(this.runtime.activeGoal, \"paused\");\n\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\tctx.ui.notify(`Cannot start the next /goal: ${formatError(error)}`, \"error\");\n\t\t\treturn false;\n\t\t}\n\t\tconst activatedGoal = this.runtime.activeGoal;\n\t\tconst sent = await this.runtime.sendOwnedGoalPrompt(\n\t\t\tctx,\n\t\t\tactivatedGoal.id,\n\t\t\tbuildGoalPrompt(activatedGoal),\n\t\t);\n\t\tif (!sent && this.runtime.activeGoal?.id === activatedGoal.id) {\n\t\t\tthis.runtime.activeGoal = transitionGoal(activatedGoal, \"paused\");\n\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\tctx.ui.notify(\n\t\t\t\t`Next goal paused after prompt delivery failed: ${activatedGoal.text}`,\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t\tctx.ui.notify(\n\t\t\t`${reason === \"complete\" ? \"Goal complete\" : \"Goal skipped\"}: ${previousText}. Started next goal: ${activatedGoal.text}`,\n\t\t\t\"info\",\n\t\t);\n\t\treturn true;\n\t}\n\n\tnotifyFrozenQueue(ctx: StatusContext) {\n\t\tctx.ui.notify(\n\t\t\t\"The experimental goal queue is frozen. Re-enable experimental.goals in pi-goal.json and run /reload, or use /goal clear.\",\n\t\t\t\"warning\",\n\t\t);\n\t}\n\n\tpauseGoal(ctx: StatusContext) {\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tctx.ui.notify(\"No active goal.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t\tif (this.runtime.activeGoal.status !== \"active\") {\n\t\t\tctx.ui.notify(\n\t\t\t\t`Goal is ${this.runtime.activeGoal.status}; only active goals can be paused.`,\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tthis.runtime.recordGoalUsage(this.runtime.activeGoal, ctx);\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\tabortCurrentTurn(ctx);\n\t\tthis.runtime.activeGoal = transitionGoal(this.runtime.activeGoal, \"paused\");\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tctx.ui.notify(`Goal paused: ${this.runtime.activeGoal.text}`, \"info\");\n\t}\n\n\tasync resumeGoal(ctx: StatusContext) {\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tctx.ui.notify(\"No active goal.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t\tif (!isResumableGoalStatus(this.runtime.activeGoal.status)) {\n\t\t\tctx.ui.notify(\n\t\t\t\t`Goal is ${this.runtime.activeGoal.status}; only paused, blocked, usage-limited, or budget-limited goals can be resumed.`,\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tif (\n\t\t\tthis.runtime.activeGoal.tokenBudget !== undefined &&\n\t\t\tthis.runtime.activeGoal.tokensUsed >= this.runtime.activeGoal.tokenBudget\n\t\t) {\n\t\t\tctx.ui.notify(\n\t\t\t\t`Goal token budget is still reached: ${formatBudget(this.runtime.activeGoal)}`,\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst goalToolVisibilityBeforeActivation = this.runtime.snapshotGoalToolVisibility();\n\t\ttry {\n\t\t\tthis.runtime.prepareGoalToolsForActivation(ctx);\n\t\t} catch (error) {\n\t\t\tctx.ui.notify(`Cannot resume /goal: ${formatError(error)}`, \"error\");\n\t\t\treturn;\n\t\t}\n\t\tconst stoppedGoal = this.runtime.activeGoal;\n\t\tconst stoppedStatus = stoppedGoal.status;\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\tthis.runtime.activeGoal = transitionGoal(nextGoalInstance(this.runtime.activeGoal), \"active\");\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tif (this.runtime.activeGoal.status !== \"active\") {\n\t\t\tctx.ui.notify(\n\t\t\t\t`Goal token budget is still reached: ${formatBudget(this.runtime.activeGoal)}`,\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst resumedGoal = this.runtime.activeGoal;\n\t\tconst sent = await this.runtime.sendOwnedGoalPrompt(\n\t\t\tctx,\n\t\t\tresumedGoal.id,\n\t\t\tbuildResumePrompt(resumedGoal, stoppedStatus),\n\t\t);\n\t\tif (!sent) {\n\t\t\tif (\n\t\t\t\tthis.runtime.activeGoal?.id === resumedGoal.id &&\n\t\t\t\tthis.runtime.activeGoal.status === \"active\"\n\t\t\t) {\n\t\t\t\tthis.runtime.activeGoal = stoppedGoal;\n\t\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\t\tif (blocksStaleGoalToolCalls(this.runtime.activeGoal.status)) {\n\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t}\n\t\t\t\tthis.runtime.restoreGoalToolVisibility(goalToolVisibilityBeforeActivation);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tctx.ui.notify(\n\t\t\t`Goal resumed from ${stoppedStatusLabel(stoppedStatus)}: ${resumedGoal.text}`,\n\t\t\t\"info\",\n\t\t);\n\t}\n\n\tclearGoal(ctx: StatusContext) {\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tctx.ui.notify(\"No active goal.\", \"info\");\n\t\t\tthis.runtime.cancelContinuationWork();\n\t\t\tthis.runtime.clearGoalRecovery();\n\t\t\tthis.runtime.clearBudgetWrapUp();\n\t\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\t\tthis.runtime.clearPersistedGoal(ctx.cwd);\n\t\t\tctx.ui.setStatus(STATUS_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\n\t\tconst stoppedGoal = this.runtime.activeGoal.text;\n\t\tthis.runtime.clearActiveGoal(ctx);\n\t\tctx.ui.notify(`Goal cleared: ${stoppedGoal}`, \"warning\");\n\t}\n\n\tasync editGoal(objective: string, tokenBudget: number | undefined, ctx: StatusContext) {\n\t\tconst validationError = validateObjective(objective);\n\t\tif (validationError) {\n\t\t\tctx.ui.notify(validationError, \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tctx.ui.notify(\"No active goal. Use /goal <objective> to start one.\", \"warning\");\n\t\t\treturn;\n\t\t}\n\n\t\tthis.runtime.recordGoalUsage(this.runtime.activeGoal, ctx);\n\t\tconst previousGoal = { ...this.runtime.activeGoal };\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tconst previousStatus = this.runtime.activeGoal.status;\n\t\tconst nextGoal = transitionGoal(\n\t\t\t{\n\t\t\t\t...nextGoalInstance(this.runtime.activeGoal),\n\t\t\t\ttext: objective,\n\t\t\t\ttokenBudget: tokenBudget ?? this.runtime.activeGoal.tokenBudget,\n\t\t\t},\n\t\t\teditedGoalStatus(previousStatus),\n\t\t);\n\t\tconst goalToolVisibilityBeforeActivation =\n\t\t\tnextGoal.status === \"active\" ? this.runtime.snapshotGoalToolVisibility() : undefined;\n\t\tif (nextGoal.status === \"active\") {\n\t\t\ttry {\n\t\t\t\tthis.runtime.prepareGoalToolsForActivation(ctx);\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(`Cannot reactivate /goal: ${formatError(error)}`, \"error\");\n\t\t\t\tif (this.runtime.activeGoal?.status === \"active\") {\n\t\t\t\t\tthis.runtime.pauseGoalForUnavailableTools(ctx);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tthis.runtime.activeGoal = nextGoal;\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tconst editedGoal = this.runtime.activeGoal;\n\t\tif (!editedGoal) return;\n\t\tif (editedGoal.status === \"active\") {\n\t\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\t\tconst sent = await this.runtime.sendOwnedGoalPrompt(\n\t\t\t\tctx,\n\t\t\t\teditedGoal.id,\n\t\t\t\tbuildObjectiveUpdatedPrompt(editedGoal),\n\t\t\t);\n\t\t\tif (!sent) {\n\t\t\t\tif (this.runtime.activeGoal?.id === editedGoal.id) {\n\t\t\t\t\tif (previousStatus === \"active\") {\n\t\t\t\t\t\tabortCurrentTurn(ctx);\n\t\t\t\t\t\tthis.runtime.activeGoal = transitionGoal(previousGoal, \"paused\");\n\t\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.runtime.activeGoal = previousGoal;\n\t\t\t\t\t\tif (blocksStaleGoalToolCalls(this.runtime.activeGoal.status)) {\n\t\t\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\t\t\tif (goalToolVisibilityBeforeActivation) {\n\t\t\t\t\t\tthis.runtime.restoreGoalToolVisibility(goalToolVisibilityBeforeActivation);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t} else if (blocksStaleGoalToolCalls(editedGoal.status)) {\n\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t} else {\n\t\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\t}\n\t\tctx.ui.notify(`Goal updated: ${objective}`, \"info\");\n\t}\n\n\tshowGoal(ctx: StatusContext) {\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tctx.ui.notify(\"Usage: /goal <objective>\\nNo goal is currently set.\", \"info\");\n\t\t\tctx.ui.setStatus(STATUS_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.runtime.queueFrozen) {\n\t\t\tthis.runtime.recordGoalUsage(this.runtime.activeGoal, ctx);\n\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t}\n\t\tctx.ui.notify(\n\t\t\tgoalSummary(\n\t\t\t\tthis.runtime.activeGoal,\n\t\t\t\tthis.runtime.queuedGoals,\n\t\t\t\tthis.runtime.settings.experimental.goals,\n\t\t\t\tthis.runtime.queueFrozen,\n\t\t\t),\n\t\t\t\"info\",\n\t\t);\n\t}\n\n\tprivate async activatePrioritizedGoal(\n\t\tobjective: string,\n\t\ttokenBudget: number | undefined,\n\t\tctx: StatusContext,\n\t\tdisplacedUsageFinalized = false,\n\t) {\n\t\tconst currentGoal = this.runtime.activeGoal;\n\t\tif (!currentGoal) {\n\t\t\tawait this.startGoal(objective, tokenBudget, ctx);\n\t\t\treturn true;\n\t\t}\n\t\tif (currentGoal.status === \"active\" && !displacedUsageFinalized) {\n\t\t\tthis.runtime.recordGoalUsage(currentGoal, ctx);\n\t\t}\n\t\tconst previousGoal = { ...currentGoal };\n\t\tconst previousQueue = [...this.runtime.queuedGoals];\n\t\tconst visibilityBeforeActivation = this.runtime.snapshotGoalToolVisibility();\n\t\ttry {\n\t\t\tthis.runtime.prepareGoalToolsForActivation(ctx);\n\t\t} catch (error) {\n\t\t\tctx.ui.notify(`Cannot prioritize /goal: ${formatError(error)}`, \"error\");\n\t\t\tif (currentGoal.status === \"complete\") {\n\t\t\t\t// Completion already committed, so retain the priority intent for a\n\t\t\t\t// later /reload after the tool policy is restored.\n\t\t\t\tthis.runtime.pendingQueueAction = {\n\t\t\t\t\tkind: \"prioritize\",\n\t\t\t\t\tobjective,\n\t\t\t\t\ttokenBudget,\n\t\t\t\t\t...(displacedUsageFinalized ? { displacedUsageFinalized: true } : {}),\n\t\t\t\t};\n\t\t\t\tthis.runtime.persistGoal(currentGoal);\n\t\t\t} else {\n\t\t\t\t// Roll back an activation that never started. An active displaced goal\n\t\t\t\t// cannot continue safely without its terminal tools, so make it resumable.\n\t\t\t\tthis.runtime.pendingQueueAction = undefined;\n\t\t\t\tif (currentGoal.status === \"active\") {\n\t\t\t\t\tthis.runtime.pauseGoalForUnavailableTools(ctx, true, !displacedUsageFinalized);\n\t\t\t\t} else {\n\t\t\t\t\tthis.runtime.persistGoal(currentGoal);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\tconst prioritized = createGoal(objective, tokenBudget, currentTokenTotal(ctx));\n\t\tconst next =\n\t\t\tcurrentGoal.status === \"complete\"\n\t\t\t\t? { goal: prioritized, queue: [...this.runtime.queuedGoals] }\n\t\t\t\t: prioritizeQueuedGoal(currentGoal, this.runtime.queuedGoals, prioritized);\n\t\tthis.runtime.activeGoal = next.goal;\n\t\tthis.runtime.queuedGoals = next.queue;\n\t\tthis.runtime.pendingQueueAction = undefined;\n\t\tif (!this.runtime.activeGoal) return false;\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tconst sent = await this.runtime.sendOwnedGoalPrompt(\n\t\t\tctx,\n\t\t\tthis.runtime.activeGoal.id,\n\t\t\tbuildGoalPrompt(this.runtime.activeGoal),\n\t\t);\n\t\tif (!sent && this.runtime.activeGoal.id === prioritized.id) {\n\t\t\tthis.runtime.queuedGoals = previousQueue;\n\t\t\tif (previousGoal.status === \"active\") {\n\t\t\t\tabortCurrentTurn(ctx);\n\t\t\t\tthis.runtime.activeGoal = transitionGoal(previousGoal, \"paused\");\n\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t} else {\n\t\t\t\tthis.runtime.activeGoal = previousGoal;\n\t\t\t\tif (previousGoal.status === \"complete\") {\n\t\t\t\t\tthis.runtime.pendingQueueAction = { kind: \"prioritize\", objective, tokenBudget };\n\t\t\t\t} else if (blocksStaleGoalToolCalls(previousGoal.status)) {\n\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\tthis.runtime.restoreGoalToolVisibility(visibilityBeforeActivation);\n\t\t\treturn false;\n\t\t}\n\t\tctx.ui.notify(`Goal prioritized: ${objective}`, \"info\");\n\t\treturn true;\n\t}\n}\n"]}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/builtin-extensions/narumiruna-pi-goal/src/commands.ts"],"names":[],"mappings":"AAYA,OAAO,EAON,KAAK,WAAW,EAMhB,KAAK,aAAa,EAGlB,MAAM,cAAc,CAAC;AAItB,qBAAa,qBAAqB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IAEtC,YAAY,OAAO,EAAE,WAAW,EAE/B;IAEK,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,aAAa,iBAiFrF;IAEK,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,aAAa,iBAmBnF;IAEK,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,aAAa,iBAkB1F;IAED,YAAY,CAAC,GAAG,EAAE,aAAa,QAe9B;IAEK,QAAQ,CAAC,GAAG,EAAE,aAAa,iBA2BhC;IAEK,mCAAmC,CAAC,GAAG,EAAE,aAAa,oBAyF3D;IAED,iBAAiB,CAAC,GAAG,EAAE,aAAa,QAKnC;IAED,SAAS,CAAC,GAAG,EAAE,aAAa,QAqB3B;IAEK,UAAU,CAAC,GAAG,EAAE,aAAa,iBAsElC;IAED,SAAS,CAAC,GAAG,EAAE,aAAa,QAe3B;IAEK,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,aAAa,iBA8EpF;IAED,QAAQ,CAAC,GAAG,EAAE,aAAa,QAoB1B;YAEa,uBAAuB;CAsFrC","sourcesContent":["// @ts-nocheck\nimport { currentTokenTotal } from \"./accounting.js\";\nimport { validateObjective } from \"./command.js\";\nimport { buildGoalPrompt, buildObjectiveUpdatedPrompt, buildResumePrompt } from \"./prompts.js\";\nimport {\n\tactivateQueuedGoal,\n\tappendGoal,\n\tcreateQueuedGoal,\n\tdropLastGoal as dropLastQueuedGoal,\n\tprioritizeGoal as prioritizeQueuedGoal,\n\tskipGoal as skipQueuedGoal,\n} from \"./queue.js\";\nimport {\n\tabortCurrentTurn,\n\tblocksStaleGoalToolCalls,\n\tcreateGoal,\n\teditedGoalStatus,\n\tformatBudget,\n\tformatError,\n\ttype GoalRuntime,\n\tgoalSummary,\n\thasPendingMessages,\n\tisResumableGoalStatus,\n\tnextGoalInstance,\n\tSTATUS_KEY,\n\ttype StatusContext,\n\tstoppedStatusLabel,\n\ttransitionGoal,\n} from \"./runtime.js\";\n\n// User-command mutations are kept separate from Pi event wiring. Every controller\n// receives exactly one per-factory GoalRuntime, preserving session isolation.\nexport class GoalCommandController {\n\tprivate readonly runtime: GoalRuntime;\n\n\tconstructor(runtime: GoalRuntime) {\n\t\tthis.runtime = runtime;\n\t}\n\n\tasync startGoal(objective: string, tokenBudget: number | undefined, ctx: StatusContext) {\n\t\tconst validationError = validateObjective(objective);\n\t\tif (validationError) {\n\t\t\tctx.ui.notify(validationError, \"warning\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst existingGoal =\n\t\t\tthis.runtime.activeGoal?.status !== \"complete\" ? this.runtime.activeGoal : undefined;\n\t\tconst existingQueuedGoals = [...this.runtime.queuedGoals];\n\t\tif (existingGoal) {\n\t\t\tconst shouldReplace = await ctx.ui.confirm(\n\t\t\t\t\"Replace goal?\",\n\t\t\t\t`Current goal: ${existingGoal.text}\\n\\nNew goal: ${objective}`,\n\t\t\t);\n\t\t\tif (!shouldReplace) {\n\t\t\t\tctx.ui.notify(`Goal kept: ${existingGoal.text}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Unlock lazy visibility only for a real activation. In always mode, a\n\t\t// missing tool means another policy or allowlist intentionally removed it.\n\t\tconst goalToolVisibilityBeforeActivation = this.runtime.snapshotGoalToolVisibility();\n\t\ttry {\n\t\t\tthis.runtime.prepareGoalToolsForActivation(ctx);\n\t\t} catch (error) {\n\t\t\tctx.ui.notify(`Cannot start /goal: ${formatError(error)}`, \"error\");\n\t\t\tif (existingGoal?.status === \"active\") this.runtime.pauseGoalForUnavailableTools(ctx);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\tthis.runtime.queuedGoals = [];\n\t\tthis.runtime.pendingQueueAction = undefined;\n\t\tthis.runtime.activeGoal = createGoal(objective, tokenBudget, currentTokenTotal(ctx));\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tconst startedGoal = this.runtime.activeGoal;\n\t\tconst sent = await this.runtime.sendOwnedGoalPrompt(\n\t\t\tctx,\n\t\t\tstartedGoal.id,\n\t\t\tbuildGoalPrompt(startedGoal),\n\t\t);\n\t\tif (!sent) {\n\t\t\tlet rolledBackStartedGoal = false;\n\t\t\tif (this.runtime.activeGoal?.id === startedGoal.id) {\n\t\t\t\trolledBackStartedGoal = true;\n\t\t\t\tif (existingGoal) {\n\t\t\t\t\tthis.runtime.queuedGoals = existingQueuedGoals;\n\t\t\t\t\tthis.runtime.recordGoalUsage(existingGoal, ctx);\n\t\t\t\t\tif (existingGoal.status === \"active\") {\n\t\t\t\t\t\tabortCurrentTurn(ctx);\n\t\t\t\t\t\tthis.runtime.activeGoal = transitionGoal(existingGoal, \"paused\");\n\t\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.runtime.activeGoal = existingGoal;\n\t\t\t\t\t\tif (blocksStaleGoalToolCalls(this.runtime.activeGoal.status)) {\n\t\t\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\t\t} else {\n\t\t\t\t\tthis.runtime.clearActiveGoal(ctx);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (rolledBackStartedGoal) {\n\t\t\t\tthis.runtime.restoreGoalToolVisibility(goalToolVisibilityBeforeActivation);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tctx.ui.notify(\n\t\t\texistingGoal ? `Goal replaced: ${objective}` : `Goal started: ${objective}`,\n\t\t\t\"info\",\n\t\t);\n\t}\n\n\tasync addGoal(objective: string, tokenBudget: number | undefined, ctx: StatusContext) {\n\t\tconst validationError = validateObjective(objective);\n\t\tif (validationError) {\n\t\t\tctx.ui.notify(validationError, \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tawait this.startGoal(objective, tokenBudget, ctx);\n\t\t\treturn;\n\t\t}\n\t\tthis.runtime.queuedGoals = appendGoal(\n\t\t\tthis.runtime.queuedGoals,\n\t\t\tcreateQueuedGoal(objective, tokenBudget),\n\t\t);\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tctx.ui.notify(\n\t\t\t`Goal added at position ${this.runtime.queuedGoals.length + 1}: ${objective}`,\n\t\t\t\"info\",\n\t\t);\n\t}\n\n\tasync prioritizeGoal(objective: string, tokenBudget: number | undefined, ctx: StatusContext) {\n\t\tconst validationError = validateObjective(objective);\n\t\tif (validationError) {\n\t\t\tctx.ui.notify(validationError, \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tawait this.startGoal(objective, tokenBudget, ctx);\n\t\t\treturn;\n\t\t}\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.pendingQueueAction = { kind: \"prioritize\", objective, tokenBudget };\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tif (ctx.isIdle?.() !== true || hasPendingMessages(ctx)) {\n\t\t\tctx.ui.notify(`Priority goal queued until Pi settles: ${objective}`, \"info\");\n\t\t\treturn;\n\t\t}\n\t\tawait this.dispatchPendingQueueActionIfSettled(ctx);\n\t}\n\n\tdropLastGoal(ctx: StatusContext) {\n\t\tconst currentGoal = this.runtime.activeGoal;\n\t\tif (!currentGoal) {\n\t\t\tctx.ui.notify(\"No goals to drop.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t\tconst result = dropLastQueuedGoal(currentGoal, this.runtime.queuedGoals);\n\t\tif (!result.goal) {\n\t\t\tthis.runtime.clearActiveGoal(ctx);\n\t\t\tctx.ui.notify(`Goal dropped: ${result.removed?.text ?? currentGoal.text}`, \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tthis.runtime.queuedGoals = result.queue;\n\t\tthis.runtime.persistGoal(result.goal);\n\t\tctx.ui.notify(`Goal dropped: ${result.removed?.text ?? \"unknown goal\"}`, \"warning\");\n\t}\n\n\tasync skipGoal(ctx: StatusContext) {\n\t\tconst currentGoal = this.runtime.activeGoal;\n\t\tif (!currentGoal) {\n\t\t\tctx.ui.notify(\"No goals to skip.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t\tif (this.runtime.queuedGoals.length === 0) {\n\t\t\tthis.runtime.clearActiveGoal(ctx);\n\t\t\tctx.ui.notify(`Goal skipped: ${currentGoal.text}. No goals remain.`, \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tif (currentGoal.status === \"active\") this.runtime.recordGoalUsage(currentGoal, ctx);\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\tthis.runtime.pendingQueueAction = {\n\t\t\tkind: \"advance\",\n\t\t\tgoalId: currentGoal.id,\n\t\t\treason: \"skip\",\n\t\t\tcompletedText: currentGoal.text,\n\t\t};\n\t\tthis.runtime.persistGoal(currentGoal);\n\t\tctx.ui.notify(`Goal skip queued until Pi settles: ${currentGoal.text}`, \"info\");\n\t\tif (ctx.isIdle?.() === true && !hasPendingMessages(ctx)) {\n\t\t\tawait this.dispatchPendingQueueActionIfSettled(ctx);\n\t\t}\n\t}\n\n\tasync dispatchPendingQueueActionIfSettled(ctx: StatusContext) {\n\t\tconst pending = this.runtime.pendingQueueAction;\n\t\tif (!pending || this.runtime.queueFrozen) return false;\n\t\tif (ctx.isIdle?.() !== true || hasPendingMessages(ctx)) return false;\n\t\tif (pending.kind === \"prioritize\") {\n\t\t\tthis.runtime.pendingQueueAction = undefined;\n\t\t\treturn this.activatePrioritizedGoal(\n\t\t\t\tpending.objective,\n\t\t\t\tpending.tokenBudget,\n\t\t\t\tctx,\n\t\t\t\tpending.displacedUsageFinalized === true,\n\t\t\t);\n\t\t}\n\t\tif (\n\t\t\t!this.runtime.activeGoal ||\n\t\t\tthis.runtime.activeGoal.id !== pending.goalId ||\n\t\t\t(this.runtime.activeGoal.status !== \"complete\" && pending.reason === \"complete\")\n\t\t) {\n\t\t\tthis.runtime.pendingQueueAction = undefined;\n\t\t\tif (this.runtime.activeGoal) this.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\treturn false;\n\t\t}\n\n\t\tconst previousText = pending.completedText;\n\t\tconst reason = pending.reason;\n\t\tthis.runtime.pendingQueueAction = undefined;\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\tconst next = skipQueuedGoal(this.runtime.queuedGoals);\n\t\tthis.runtime.queuedGoals = next.queue;\n\t\tthis.runtime.activeGoal = next.goal\n\t\t\t? activateQueuedGoal(next.goal, currentTokenTotal(ctx))\n\t\t\t: undefined;\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tthis.runtime.clearActiveGoal(ctx);\n\t\t\tif (reason !== \"complete\") {\n\t\t\t\tctx.ui.notify(`Goal skipped: ${previousText}. No goals remain.`, \"info\");\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tif (this.runtime.activeGoal.status !== \"active\") {\n\t\t\tif (blocksStaleGoalToolCalls(this.runtime.activeGoal.status)) {\n\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t}\n\t\t\tif (reason !== \"complete\") {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`Goal skipped: ${previousText}. Next goal remains ${this.runtime.activeGoal.status}: ${this.runtime.activeGoal.text}`,\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.runtime.prepareGoalToolsForActivation(ctx);\n\t\t} catch (error) {\n\t\t\tthis.runtime.activeGoal = transitionGoal(this.runtime.activeGoal, \"paused\");\n\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\tctx.ui.notify(`Cannot start the next /goal: ${formatError(error)}`, \"error\");\n\t\t\treturn false;\n\t\t}\n\t\tconst activatedGoal = this.runtime.activeGoal;\n\t\tconst sent = await this.runtime.sendOwnedGoalPrompt(\n\t\t\tctx,\n\t\t\tactivatedGoal.id,\n\t\t\tbuildGoalPrompt(activatedGoal),\n\t\t);\n\t\tif (!sent && this.runtime.activeGoal?.id === activatedGoal.id) {\n\t\t\tthis.runtime.activeGoal = transitionGoal(activatedGoal, \"paused\");\n\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\tctx.ui.notify(\n\t\t\t\t`Next goal paused after prompt delivery failed: ${activatedGoal.text}`,\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t\tif (reason !== \"complete\") {\n\t\t\tctx.ui.notify(`Goal skipped: ${previousText}. Started next goal: ${activatedGoal.text}`, \"info\");\n\t\t}\n\t\treturn true;\n\t}\n\n\tnotifyFrozenQueue(ctx: StatusContext) {\n\t\tctx.ui.notify(\n\t\t\t\"The experimental goal queue is frozen. Re-enable experimental.goals in pi-goal.json and run /reload, or use /goal clear.\",\n\t\t\t\"warning\",\n\t\t);\n\t}\n\n\tpauseGoal(ctx: StatusContext) {\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tctx.ui.notify(\"No active goal.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t\tif (this.runtime.activeGoal.status !== \"active\") {\n\t\t\tctx.ui.notify(\n\t\t\t\t`Goal is ${this.runtime.activeGoal.status}; only active goals can be paused.`,\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tthis.runtime.recordGoalUsage(this.runtime.activeGoal, ctx);\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\tabortCurrentTurn(ctx);\n\t\tthis.runtime.activeGoal = transitionGoal(this.runtime.activeGoal, \"paused\");\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tctx.ui.notify(`Goal paused: ${this.runtime.activeGoal.text}`, \"info\");\n\t}\n\n\tasync resumeGoal(ctx: StatusContext) {\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tctx.ui.notify(\"No active goal.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t\tif (!isResumableGoalStatus(this.runtime.activeGoal.status)) {\n\t\t\tctx.ui.notify(\n\t\t\t\t`Goal is ${this.runtime.activeGoal.status}; only paused, blocked, usage-limited, or budget-limited goals can be resumed.`,\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tif (\n\t\t\tthis.runtime.activeGoal.tokenBudget !== undefined &&\n\t\t\tthis.runtime.activeGoal.tokensUsed >= this.runtime.activeGoal.tokenBudget\n\t\t) {\n\t\t\tctx.ui.notify(\n\t\t\t\t`Goal token budget is still reached: ${formatBudget(this.runtime.activeGoal)}`,\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst goalToolVisibilityBeforeActivation = this.runtime.snapshotGoalToolVisibility();\n\t\ttry {\n\t\t\tthis.runtime.prepareGoalToolsForActivation(ctx);\n\t\t} catch (error) {\n\t\t\tctx.ui.notify(`Cannot resume /goal: ${formatError(error)}`, \"error\");\n\t\t\treturn;\n\t\t}\n\t\tconst stoppedGoal = this.runtime.activeGoal;\n\t\tconst stoppedStatus = stoppedGoal.status;\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\tthis.runtime.activeGoal = transitionGoal(nextGoalInstance(this.runtime.activeGoal), \"active\");\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tif (this.runtime.activeGoal.status !== \"active\") {\n\t\t\tctx.ui.notify(\n\t\t\t\t`Goal token budget is still reached: ${formatBudget(this.runtime.activeGoal)}`,\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tconst resumedGoal = this.runtime.activeGoal;\n\t\tconst sent = await this.runtime.sendOwnedGoalPrompt(\n\t\t\tctx,\n\t\t\tresumedGoal.id,\n\t\t\tbuildResumePrompt(resumedGoal, stoppedStatus),\n\t\t);\n\t\tif (!sent) {\n\t\t\tif (\n\t\t\t\tthis.runtime.activeGoal?.id === resumedGoal.id &&\n\t\t\t\tthis.runtime.activeGoal.status === \"active\"\n\t\t\t) {\n\t\t\t\tthis.runtime.activeGoal = stoppedGoal;\n\t\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\t\tif (blocksStaleGoalToolCalls(this.runtime.activeGoal.status)) {\n\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t}\n\t\t\t\tthis.runtime.restoreGoalToolVisibility(goalToolVisibilityBeforeActivation);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tctx.ui.notify(\n\t\t\t`Goal resumed from ${stoppedStatusLabel(stoppedStatus)}: ${resumedGoal.text}`,\n\t\t\t\"info\",\n\t\t);\n\t}\n\n\tclearGoal(ctx: StatusContext) {\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tctx.ui.notify(\"No active goal.\", \"info\");\n\t\t\tthis.runtime.cancelContinuationWork();\n\t\t\tthis.runtime.clearGoalRecovery();\n\t\t\tthis.runtime.clearBudgetWrapUp();\n\t\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\t\tthis.runtime.clearPersistedGoal(ctx.cwd);\n\t\t\tctx.ui.setStatus(STATUS_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\n\t\tconst stoppedGoal = this.runtime.activeGoal.text;\n\t\tthis.runtime.clearActiveGoal(ctx);\n\t\tctx.ui.notify(`Goal cleared: ${stoppedGoal}`, \"warning\");\n\t}\n\n\tasync editGoal(objective: string, tokenBudget: number | undefined, ctx: StatusContext) {\n\t\tconst validationError = validateObjective(objective);\n\t\tif (validationError) {\n\t\t\tctx.ui.notify(validationError, \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tctx.ui.notify(\"No active goal. Use /goal <objective> to start one.\", \"warning\");\n\t\t\treturn;\n\t\t}\n\n\t\tthis.runtime.recordGoalUsage(this.runtime.activeGoal, ctx);\n\t\tconst previousGoal = { ...this.runtime.activeGoal };\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tconst previousStatus = this.runtime.activeGoal.status;\n\t\tconst nextGoal = transitionGoal(\n\t\t\t{\n\t\t\t\t...nextGoalInstance(this.runtime.activeGoal),\n\t\t\t\ttext: objective,\n\t\t\t\ttokenBudget: tokenBudget ?? this.runtime.activeGoal.tokenBudget,\n\t\t\t},\n\t\t\teditedGoalStatus(previousStatus),\n\t\t);\n\t\tconst goalToolVisibilityBeforeActivation =\n\t\t\tnextGoal.status === \"active\" ? this.runtime.snapshotGoalToolVisibility() : undefined;\n\t\tif (nextGoal.status === \"active\") {\n\t\t\ttry {\n\t\t\t\tthis.runtime.prepareGoalToolsForActivation(ctx);\n\t\t\t} catch (error) {\n\t\t\t\tctx.ui.notify(`Cannot reactivate /goal: ${formatError(error)}`, \"error\");\n\t\t\t\tif (this.runtime.activeGoal?.status === \"active\") {\n\t\t\t\t\tthis.runtime.pauseGoalForUnavailableTools(ctx);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tthis.runtime.activeGoal = nextGoal;\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tconst editedGoal = this.runtime.activeGoal;\n\t\tif (!editedGoal) return;\n\t\tif (editedGoal.status === \"active\") {\n\t\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\t\tconst sent = await this.runtime.sendOwnedGoalPrompt(\n\t\t\t\tctx,\n\t\t\t\teditedGoal.id,\n\t\t\t\tbuildObjectiveUpdatedPrompt(editedGoal),\n\t\t\t);\n\t\t\tif (!sent) {\n\t\t\t\tif (this.runtime.activeGoal?.id === editedGoal.id) {\n\t\t\t\t\tif (previousStatus === \"active\") {\n\t\t\t\t\t\tabortCurrentTurn(ctx);\n\t\t\t\t\t\tthis.runtime.activeGoal = transitionGoal(previousGoal, \"paused\");\n\t\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.runtime.activeGoal = previousGoal;\n\t\t\t\t\t\tif (blocksStaleGoalToolCalls(this.runtime.activeGoal.status)) {\n\t\t\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\t\t\tif (goalToolVisibilityBeforeActivation) {\n\t\t\t\t\t\tthis.runtime.restoreGoalToolVisibility(goalToolVisibilityBeforeActivation);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t} else if (blocksStaleGoalToolCalls(editedGoal.status)) {\n\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t} else {\n\t\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\t}\n\t\tctx.ui.notify(`Goal updated: ${objective}`, \"info\");\n\t}\n\n\tshowGoal(ctx: StatusContext) {\n\t\tif (!this.runtime.activeGoal) {\n\t\t\tctx.ui.notify(\"Usage: /goal <objective>\\nNo goal is currently set.\", \"info\");\n\t\t\tctx.ui.setStatus(STATUS_KEY, undefined);\n\t\t\treturn;\n\t\t}\n\t\tif (!this.runtime.queueFrozen) {\n\t\t\tthis.runtime.recordGoalUsage(this.runtime.activeGoal, ctx);\n\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t}\n\t\tctx.ui.notify(\n\t\t\tgoalSummary(\n\t\t\t\tthis.runtime.activeGoal,\n\t\t\t\tthis.runtime.queuedGoals,\n\t\t\t\tthis.runtime.settings.experimental.goals,\n\t\t\t\tthis.runtime.queueFrozen,\n\t\t\t),\n\t\t\t\"info\",\n\t\t);\n\t}\n\n\tprivate async activatePrioritizedGoal(\n\t\tobjective: string,\n\t\ttokenBudget: number | undefined,\n\t\tctx: StatusContext,\n\t\tdisplacedUsageFinalized = false,\n\t) {\n\t\tconst currentGoal = this.runtime.activeGoal;\n\t\tif (!currentGoal) {\n\t\t\tawait this.startGoal(objective, tokenBudget, ctx);\n\t\t\treturn true;\n\t\t}\n\t\tif (currentGoal.status === \"active\" && !displacedUsageFinalized) {\n\t\t\tthis.runtime.recordGoalUsage(currentGoal, ctx);\n\t\t}\n\t\tconst previousGoal = { ...currentGoal };\n\t\tconst previousQueue = [...this.runtime.queuedGoals];\n\t\tconst visibilityBeforeActivation = this.runtime.snapshotGoalToolVisibility();\n\t\ttry {\n\t\t\tthis.runtime.prepareGoalToolsForActivation(ctx);\n\t\t} catch (error) {\n\t\t\tctx.ui.notify(`Cannot prioritize /goal: ${formatError(error)}`, \"error\");\n\t\t\tif (currentGoal.status === \"complete\") {\n\t\t\t\t// Completion already committed, so retain the priority intent for a\n\t\t\t\t// later /reload after the tool policy is restored.\n\t\t\t\tthis.runtime.pendingQueueAction = {\n\t\t\t\t\tkind: \"prioritize\",\n\t\t\t\t\tobjective,\n\t\t\t\t\ttokenBudget,\n\t\t\t\t\t...(displacedUsageFinalized ? { displacedUsageFinalized: true } : {}),\n\t\t\t\t};\n\t\t\t\tthis.runtime.persistGoal(currentGoal);\n\t\t\t} else {\n\t\t\t\t// Roll back an activation that never started. An active displaced goal\n\t\t\t\t// cannot continue safely without its terminal tools, so make it resumable.\n\t\t\t\tthis.runtime.pendingQueueAction = undefined;\n\t\t\t\tif (currentGoal.status === \"active\") {\n\t\t\t\t\tthis.runtime.pauseGoalForUnavailableTools(ctx, true, !displacedUsageFinalized);\n\t\t\t\t} else {\n\t\t\t\t\tthis.runtime.persistGoal(currentGoal);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.runtime.cancelContinuationWork();\n\t\tthis.runtime.clearGoalRecovery();\n\t\tthis.runtime.clearBudgetWrapUp();\n\t\tthis.runtime.clearStaleGoalToolCallBlock();\n\t\tconst prioritized = createGoal(objective, tokenBudget, currentTokenTotal(ctx));\n\t\tconst next =\n\t\t\tcurrentGoal.status === \"complete\"\n\t\t\t\t? { goal: prioritized, queue: [...this.runtime.queuedGoals] }\n\t\t\t\t: prioritizeQueuedGoal(currentGoal, this.runtime.queuedGoals, prioritized);\n\t\tthis.runtime.activeGoal = next.goal;\n\t\tthis.runtime.queuedGoals = next.queue;\n\t\tthis.runtime.pendingQueueAction = undefined;\n\t\tif (!this.runtime.activeGoal) return false;\n\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\tconst sent = await this.runtime.sendOwnedGoalPrompt(\n\t\t\tctx,\n\t\t\tthis.runtime.activeGoal.id,\n\t\t\tbuildGoalPrompt(this.runtime.activeGoal),\n\t\t);\n\t\tif (!sent && this.runtime.activeGoal.id === prioritized.id) {\n\t\t\tthis.runtime.queuedGoals = previousQueue;\n\t\t\tif (previousGoal.status === \"active\") {\n\t\t\t\tabortCurrentTurn(ctx);\n\t\t\t\tthis.runtime.activeGoal = transitionGoal(previousGoal, \"paused\");\n\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t} else {\n\t\t\t\tthis.runtime.activeGoal = previousGoal;\n\t\t\t\tif (previousGoal.status === \"complete\") {\n\t\t\t\t\tthis.runtime.pendingQueueAction = { kind: \"prioritize\", objective, tokenBudget };\n\t\t\t\t} else if (blocksStaleGoalToolCalls(previousGoal.status)) {\n\t\t\t\t\tthis.runtime.blockStaleGoalToolCalls();\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.runtime.persistGoal(this.runtime.activeGoal);\n\t\t\tthis.runtime.updateStatus(ctx, this.runtime.activeGoal);\n\t\t\tthis.runtime.restoreGoalToolVisibility(visibilityBeforeActivation);\n\t\t\treturn false;\n\t\t}\n\t\tctx.ui.notify(`Goal prioritized: ${objective}`, \"info\");\n\t\treturn true;\n\t}\n}\n"]}
@@ -194,9 +194,9 @@ export class GoalCommandController {
194
194
  : undefined;
195
195
  if (!this.runtime.activeGoal) {
196
196
  this.runtime.clearActiveGoal(ctx);
197
- ctx.ui.notify(reason === "complete"
198
- ? `Goal complete: ${previousText}. No goals remain.`
199
- : `Goal skipped: ${previousText}. No goals remain.`, "info");
197
+ if (reason !== "complete") {
198
+ ctx.ui.notify(`Goal skipped: ${previousText}. No goals remain.`, "info");
199
+ }
200
200
  return true;
201
201
  }
202
202
  this.runtime.persistGoal(this.runtime.activeGoal);
@@ -205,7 +205,9 @@ export class GoalCommandController {
205
205
  if (blocksStaleGoalToolCalls(this.runtime.activeGoal.status)) {
206
206
  this.runtime.blockStaleGoalToolCalls();
207
207
  }
208
- ctx.ui.notify(`${reason === "complete" ? "Goal complete" : "Goal skipped"}: ${previousText}. Next goal remains ${this.runtime.activeGoal.status}: ${this.runtime.activeGoal.text}`, "info");
208
+ if (reason !== "complete") {
209
+ ctx.ui.notify(`Goal skipped: ${previousText}. Next goal remains ${this.runtime.activeGoal.status}: ${this.runtime.activeGoal.text}`, "info");
210
+ }
209
211
  return true;
210
212
  }
211
213
  try {
@@ -229,7 +231,9 @@ export class GoalCommandController {
229
231
  ctx.ui.notify(`Next goal paused after prompt delivery failed: ${activatedGoal.text}`, "warning");
230
232
  return false;
231
233
  }
232
- ctx.ui.notify(`${reason === "complete" ? "Goal complete" : "Goal skipped"}: ${previousText}. Started next goal: ${activatedGoal.text}`, "info");
234
+ if (reason !== "complete") {
235
+ ctx.ui.notify(`Goal skipped: ${previousText}. Started next goal: ${activatedGoal.text}`, "info");
236
+ }
233
237
  return true;
234
238
  }
235
239
  notifyFrozenQueue(ctx) {