@ashx-j/lunr 0.1.7 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builtin-extensions/lunr-todos.d.ts +7 -6
- package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-todos.js +24 -11
- package/dist/builtin-extensions/lunr-todos.js.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.d.ts.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js +9 -5
- package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.d.ts.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js +31 -3
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.d.ts +1 -0
- package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js +14 -4
- package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js +4 -3
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +12 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +3 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +8 -0
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +22 -2
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.js +38 -230
- package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/render-search-chrome.d.ts +35 -0
- package/dist/builtin-extensions/pi-web-access/render-search-chrome.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-web-access/render-search-chrome.js +93 -0
- package/dist/builtin-extensions/pi-web-access/render-search-chrome.js.map +1 -0
- package/dist/catalog/models.json +1 -1
- package/dist/catalog/providers/amazon-bedrock.json +1 -1
- package/dist/catalog/providers/cloudflare-workers-ai.json +1 -1
- package/dist/catalog/providers/huggingface.json +1 -1
- package/dist/catalog/providers/opencode-go.json +1 -1
- package/dist/catalog/providers/opencode.json +1 -1
- package/dist/catalog/providers/openrouter.json +1 -1
- package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
- package/dist/catalog/publication.json +3 -3
- package/dist/core/context-breakdown.d.ts +21 -1
- package/dist/core/context-breakdown.d.ts.map +1 -1
- package/dist/core/context-breakdown.js +58 -2
- package/dist/core/context-breakdown.js.map +1 -1
- package/dist/core/permission-mode-control.d.ts +27 -0
- package/dist/core/permission-mode-control.d.ts.map +1 -0
- package/dist/core/permission-mode-control.js +39 -0
- package/dist/core/permission-mode-control.js.map +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +0 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +14 -20
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +2 -0
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +10 -0
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +1 -0
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/context-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/context-view.js +3 -1
- package/dist/modes/interactive/components/context-view.js.map +1 -1
- package/dist/modes/interactive/components/plan-message.d.ts +17 -0
- package/dist/modes/interactive/components/plan-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/plan-message.js +43 -0
- package/dist/modes/interactive/components/plan-message.js.map +1 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +1 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +3 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +18 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/usage-view.d.ts +4 -25
- package/dist/modes/interactive/components/usage-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/usage-view.js +17 -69
- package/dist/modes/interactive/components/usage-view.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +1 -0
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +1 -0
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +17 -6
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +200 -120
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/smooth-streaming.d.ts +39 -6
- package/dist/modes/interactive/smooth-streaming.d.ts.map +1 -1
- package/dist/modes/interactive/smooth-streaming.js +160 -73
- package/dist/modes/interactive/smooth-streaming.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +7 -3
- package/dist/utils/clipboard-image.js.map +1 -1
- package/docs/terminal-setup.md +13 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -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)
|
|
15
|
-
* Expanded: every row,
|
|
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
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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)
|
|
16
|
-
* Expanded: every row,
|
|
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
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
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
|
-
|
|
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":"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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). 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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|