@pentoshi/clai 3.5.1 → 3.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agent/compaction-summary.d.ts +17 -0
- package/dist/agent/compaction-summary.js +64 -0
- package/dist/agent/compaction-summary.js.map +1 -0
- package/dist/agent/confirm-port.d.ts +3 -1
- package/dist/agent/confirm-port.js +5 -1
- package/dist/agent/confirm-port.js.map +1 -1
- package/dist/agent/context-manager.js +53 -24
- package/dist/agent/context-manager.js.map +1 -1
- package/dist/agent/events.d.ts +6 -0
- package/dist/agent/evidence-governor.d.ts +37 -0
- package/dist/agent/evidence-governor.js +70 -0
- package/dist/agent/evidence-governor.js.map +1 -0
- package/dist/agent/loop-guard.d.ts +29 -9
- package/dist/agent/loop-guard.js +117 -26
- package/dist/agent/loop-guard.js.map +1 -1
- package/dist/agent/must-continue.d.ts +56 -0
- package/dist/agent/must-continue.js +240 -0
- package/dist/agent/must-continue.js.map +1 -0
- package/dist/agent/outcomes.d.ts +101 -0
- package/dist/agent/outcomes.js +359 -0
- package/dist/agent/outcomes.js.map +1 -0
- package/dist/agent/plan-decision.d.ts +33 -0
- package/dist/agent/plan-decision.js +104 -0
- package/dist/agent/plan-decision.js.map +1 -0
- package/dist/agent/plan-tool.d.ts +34 -0
- package/dist/agent/plan-tool.js +594 -76
- package/dist/agent/plan-tool.js.map +1 -1
- package/dist/agent/project-root.d.ts +33 -0
- package/dist/agent/project-root.js +161 -0
- package/dist/agent/project-root.js.map +1 -0
- package/dist/agent/prompt-composer.d.ts +21 -0
- package/dist/agent/prompt-composer.js +46 -0
- package/dist/agent/prompt-composer.js.map +1 -0
- package/dist/agent/runner.d.ts +11 -1
- package/dist/agent/runner.js +1915 -886
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/scope-context.d.ts +3 -0
- package/dist/agent/scope-context.js +24 -0
- package/dist/agent/scope-context.js.map +1 -0
- package/dist/agent/scratch-write.d.ts +4 -0
- package/dist/agent/scratch-write.js +54 -0
- package/dist/agent/scratch-write.js.map +1 -0
- package/dist/agent/session-policy.d.ts +7 -0
- package/dist/agent/session-policy.js +61 -0
- package/dist/agent/session-policy.js.map +1 -1
- package/dist/agent/session-state.d.ts +41 -0
- package/dist/agent/session-state.js +107 -0
- package/dist/agent/session-state.js.map +1 -0
- package/dist/agent/step-budget.d.ts +14 -0
- package/dist/agent/step-budget.js +26 -0
- package/dist/agent/step-budget.js.map +1 -0
- package/dist/agent/task-analyzer.d.ts +12 -3
- package/dist/agent/task-analyzer.js +263 -23
- package/dist/agent/task-analyzer.js.map +1 -1
- package/dist/agent/task-evidence.d.ts +173 -0
- package/dist/agent/task-evidence.js +802 -0
- package/dist/agent/task-evidence.js.map +1 -0
- package/dist/agent/task-plan.d.ts +57 -0
- package/dist/agent/task-plan.js +147 -1
- package/dist/agent/task-plan.js.map +1 -1
- package/dist/agent/tool-call-parser.d.ts +19 -11
- package/dist/agent/tool-call-parser.js +96 -71
- package/dist/agent/tool-call-parser.js.map +1 -1
- package/dist/agent/tool-history.d.ts +34 -0
- package/dist/agent/tool-history.js +200 -0
- package/dist/agent/tool-history.js.map +1 -0
- package/dist/agent/tool-output-formatting.d.ts +13 -1
- package/dist/agent/tool-output-formatting.js +75 -22
- package/dist/agent/tool-output-formatting.js.map +1 -1
- package/dist/agent/turn-outcome.d.ts +11 -0
- package/dist/agent/turn-outcome.js +17 -0
- package/dist/agent/turn-outcome.js.map +1 -0
- package/dist/agent/turn-state.d.ts +11 -0
- package/dist/agent/turn-state.js +28 -0
- package/dist/agent/turn-state.js.map +1 -0
- package/dist/agent/workspace-orient.d.ts +75 -0
- package/dist/agent/workspace-orient.js +509 -0
- package/dist/agent/workspace-orient.js.map +1 -0
- package/dist/app/adapters/agent-event-adapter.d.ts +2 -13
- package/dist/app/adapters/agent-event-adapter.js +63 -25
- package/dist/app/adapters/agent-event-adapter.js.map +1 -1
- package/dist/app/adapters/current-agent-adapter.d.ts +0 -5
- package/dist/app/adapters/current-agent-adapter.js +4 -7
- package/dist/app/adapters/current-agent-adapter.js.map +1 -1
- package/dist/app/adapters/current-jobs-adapter.js +1 -0
- package/dist/app/adapters/current-jobs-adapter.js.map +1 -1
- package/dist/app/adapters/current-terminal-adapter.d.ts +0 -7
- package/dist/app/adapters/current-terminal-adapter.js +0 -7
- package/dist/app/adapters/current-terminal-adapter.js.map +1 -1
- package/dist/app/adapters/current-updates-adapter.d.ts +0 -5
- package/dist/app/adapters/current-updates-adapter.js +0 -5
- package/dist/app/adapters/current-updates-adapter.js.map +1 -1
- package/dist/app/adapters/in-memory-clipboard-adapter.d.ts +0 -5
- package/dist/app/adapters/in-memory-clipboard-adapter.js +0 -5
- package/dist/app/adapters/in-memory-clipboard-adapter.js.map +1 -1
- package/dist/app/commands/command.d.ts +0 -5
- package/dist/app/commands/command.js.map +1 -1
- package/dist/app/commands/registry.d.ts +0 -14
- package/dist/app/commands/registry.js +0 -20
- package/dist/app/commands/registry.js.map +1 -1
- package/dist/app/controllers/disposable.d.ts +0 -5
- package/dist/app/controllers/disposable.js +0 -5
- package/dist/app/controllers/disposable.js.map +1 -1
- package/dist/app/controllers/job-controller.d.ts +1 -5
- package/dist/app/controllers/job-controller.js +0 -4
- package/dist/app/controllers/job-controller.js.map +1 -1
- package/dist/app/controllers/plan-controller.d.ts +5 -8
- package/dist/app/controllers/plan-controller.js +30 -12
- package/dist/app/controllers/plan-controller.js.map +1 -1
- package/dist/app/controllers/session-controller.d.ts +17 -32
- package/dist/app/controllers/session-controller.js +60 -35
- package/dist/app/controllers/session-controller.js.map +1 -1
- package/dist/app/controllers/turn-controller.d.ts +2 -6
- package/dist/app/controllers/turn-controller.js +8 -8
- package/dist/app/controllers/turn-controller.js.map +1 -1
- package/dist/app/events/app-event.d.ts +4 -7
- package/dist/app/events/app-event.js +0 -6
- package/dist/app/events/app-event.js.map +1 -1
- package/dist/app/events/event-buffer.d.ts +6 -17
- package/dist/app/events/event-buffer.js +27 -24
- package/dist/app/events/event-buffer.js.map +1 -1
- package/dist/app/events/sequencer.d.ts +0 -9
- package/dist/app/events/sequencer.js +0 -4
- package/dist/app/events/sequencer.js.map +1 -1
- package/dist/app/ports/agent-port.d.ts +13 -3
- package/dist/app/ports/clipboard-port.d.ts +0 -5
- package/dist/app/ports/confirm-port.d.ts +0 -7
- package/dist/app/ports/jobs-port.d.ts +2 -5
- package/dist/app/ports/persistence-port.d.ts +0 -4
- package/dist/app/ports/secret-port.d.ts +0 -5
- package/dist/app/ports/terminal-port.d.ts +0 -6
- package/dist/app/ports/updates-port.d.ts +0 -4
- package/dist/attachments/service.d.ts +29 -0
- package/dist/attachments/service.js +47 -0
- package/dist/attachments/service.js.map +1 -0
- package/dist/commands/doctor.js +3 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/providers.js +0 -3
- package/dist/commands/providers.js.map +1 -1
- package/dist/commands/search-providers.d.ts +0 -33
- package/dist/commands/search-providers.js +0 -33
- package/dist/commands/search-providers.js.map +1 -1
- package/dist/commands/update.js +1 -9
- package/dist/commands/update.js.map +1 -1
- package/dist/index.js +22 -42
- package/dist/index.js.map +1 -1
- package/dist/llm/adapters/anthropic-tools.d.ts +99 -0
- package/dist/llm/adapters/anthropic-tools.js +225 -0
- package/dist/llm/adapters/anthropic-tools.js.map +1 -0
- package/dist/llm/adapters/gemini-tools.d.ts +54 -0
- package/dist/llm/adapters/gemini-tools.js +139 -0
- package/dist/llm/adapters/gemini-tools.js.map +1 -0
- package/dist/llm/adapters/ollama-tools.d.ts +18 -0
- package/dist/llm/adapters/ollama-tools.js +58 -0
- package/dist/llm/adapters/ollama-tools.js.map +1 -0
- package/dist/llm/adapters/openai-tools.d.ts +39 -0
- package/dist/llm/adapters/openai-tools.js +71 -0
- package/dist/llm/adapters/openai-tools.js.map +1 -0
- package/dist/llm/agentrouter.js +23 -4
- package/dist/llm/agentrouter.js.map +1 -1
- package/dist/llm/anthropic.js +93 -92
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/aws-mantle.js +93 -56
- package/dist/llm/aws-mantle.js.map +1 -1
- package/dist/llm/bynara.js +23 -4
- package/dist/llm/bynara.js.map +1 -1
- package/dist/llm/capabilities.d.ts +13 -6
- package/dist/llm/capabilities.js +113 -13
- package/dist/llm/capabilities.js.map +1 -1
- package/dist/llm/gemini.js +63 -48
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/groq.js +23 -10
- package/dist/llm/groq.js.map +1 -1
- package/dist/llm/http.d.ts +22 -34
- package/dist/llm/http.js +97 -69
- package/dist/llm/http.js.map +1 -1
- package/dist/llm/kimchi.js +23 -4
- package/dist/llm/kimchi.js.map +1 -1
- package/dist/llm/nvidia.js +23 -4
- package/dist/llm/nvidia.js.map +1 -1
- package/dist/llm/ollama.js +49 -31
- package/dist/llm/ollama.js.map +1 -1
- package/dist/llm/openai.js +23 -4
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/openrouter.js +23 -4
- package/dist/llm/openrouter.js.map +1 -1
- package/dist/llm/qwen-cloud.js +23 -4
- package/dist/llm/qwen-cloud.js.map +1 -1
- package/dist/llm/router.d.ts +0 -6
- package/dist/llm/router.js +48 -21
- package/dist/llm/router.js.map +1 -1
- package/dist/llm/tool-protocol.d.ts +67 -0
- package/dist/llm/tool-protocol.js +278 -0
- package/dist/llm/tool-protocol.js.map +1 -0
- package/dist/modes/agent.d.ts +2 -1
- package/dist/modes/agent.js +4 -2
- package/dist/modes/agent.js.map +1 -1
- package/dist/modes/ask.d.ts +0 -18
- package/dist/modes/ask.js +71 -66
- package/dist/modes/ask.js.map +1 -1
- package/dist/os/cwd.d.ts +0 -21
- package/dist/os/cwd.js +0 -24
- package/dist/os/cwd.js.map +1 -1
- package/dist/os/pkgmgr.js +0 -6
- package/dist/os/pkgmgr.js.map +1 -1
- package/dist/prompts/index.d.ts +17 -21
- package/dist/prompts/index.js +131 -259
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/system.agent.md +242 -0
- package/dist/prompts/system.ask.md +56 -0
- package/dist/repl/prompt-line.js +0 -36
- package/dist/repl/prompt-line.js.map +1 -1
- package/dist/repl/slash-commands.d.ts +0 -9
- package/dist/repl/slash-commands.js +2 -23
- package/dist/repl/slash-commands.js.map +1 -1
- package/dist/repl.d.ts +5 -0
- package/dist/repl.js +176 -55
- package/dist/repl.js.map +1 -1
- package/dist/safety/classifier.js +17 -9
- package/dist/safety/classifier.js.map +1 -1
- package/dist/safety/engagement-policy.d.ts +46 -0
- package/dist/safety/engagement-policy.js +198 -0
- package/dist/safety/engagement-policy.js.map +1 -0
- package/dist/store/config.d.ts +7 -0
- package/dist/store/config.js +1 -0
- package/dist/store/config.js.map +1 -1
- package/dist/store/engagement.d.ts +115 -0
- package/dist/store/engagement.js +278 -0
- package/dist/store/engagement.js.map +1 -0
- package/dist/store/logs.d.ts +5 -0
- package/dist/store/logs.js +52 -2
- package/dist/store/logs.js.map +1 -1
- package/dist/store/plan.d.ts +59 -5
- package/dist/store/plan.js +182 -8
- package/dist/store/plan.js.map +1 -1
- package/dist/store/scope.d.ts +4 -1
- package/dist/store/scope.js +2 -2
- package/dist/store/scope.js.map +1 -1
- package/dist/tools/capabilities.d.ts +2 -0
- package/dist/tools/capabilities.js +101 -6
- package/dist/tools/capabilities.js.map +1 -1
- package/dist/tools/command-intent.d.ts +1 -0
- package/dist/tools/command-intent.js +31 -0
- package/dist/tools/command-intent.js.map +1 -1
- package/dist/tools/definitions.d.ts +20 -0
- package/dist/tools/definitions.js +578 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/elevated-shell.d.ts +60 -0
- package/dist/tools/elevated-shell.js +233 -0
- package/dist/tools/elevated-shell.js.map +1 -0
- package/dist/tools/file-diff.d.ts +125 -0
- package/dist/tools/file-diff.js +495 -0
- package/dist/tools/file-diff.js.map +1 -0
- package/dist/tools/fs.d.ts +11 -13
- package/dist/tools/fs.js +280 -80
- package/dist/tools/fs.js.map +1 -1
- package/dist/tools/http.d.ts +15 -0
- package/dist/tools/http.js +202 -61
- package/dist/tools/http.js.map +1 -1
- package/dist/tools/jobs.d.ts +71 -10
- package/dist/tools/jobs.js +403 -124
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/net-ping-sweep.js +69 -16
- package/dist/tools/net-ping-sweep.js.map +1 -1
- package/dist/tools/nmap-runner.d.ts +19 -7
- package/dist/tools/nmap-runner.js +182 -80
- package/dist/tools/nmap-runner.js.map +1 -1
- package/dist/tools/pentest-workflows.d.ts +8 -0
- package/dist/tools/pentest-workflows.js +71 -0
- package/dist/tools/pentest-workflows.js.map +1 -0
- package/dist/tools/registry.d.ts +9 -0
- package/dist/tools/registry.js +272 -63
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/shell.d.ts +3 -0
- package/dist/tools/shell.js +35 -13
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/tool-types.d.ts +11 -0
- package/dist/tools/validate.d.ts +11 -0
- package/dist/tools/validate.js +53 -0
- package/dist/tools/validate.js.map +1 -1
- package/dist/tools/web/search.js +48 -4
- package/dist/tools/web/search.js.map +1 -1
- package/dist/tui-v2/app/App.js +25 -39
- package/dist/tui-v2/app/App.js.map +1 -1
- package/dist/tui-v2/app/command-handlers.js +20 -1
- package/dist/tui-v2/app/command-handlers.js.map +1 -1
- package/dist/tui-v2/app/commands/picker-commands.js +3 -0
- package/dist/tui-v2/app/commands/picker-commands.js.map +1 -1
- package/dist/tui-v2/app/commands/session-commands.js +5 -0
- package/dist/tui-v2/app/commands/session-commands.js.map +1 -1
- package/dist/tui-v2/app/plan-lifecycle.d.ts +25 -3
- package/dist/tui-v2/app/plan-lifecycle.js +87 -24
- package/dist/tui-v2/app/plan-lifecycle.js.map +1 -1
- package/dist/tui-v2/bootstrap/patch-opentui-text.d.ts +6 -0
- package/dist/tui-v2/bootstrap/patch-opentui-text.js +53 -0
- package/dist/tui-v2/bootstrap/patch-opentui-text.js.map +1 -0
- package/dist/tui-v2/bootstrap/start-tui-v2.js +7 -0
- package/dist/tui-v2/bootstrap/start-tui-v2.js.map +1 -1
- package/dist/tui-v2/components/jobs/jobs-panel.js +23 -14
- package/dist/tui-v2/components/jobs/jobs-panel.js.map +1 -1
- package/dist/tui-v2/components/modal/confirm-modal.d.ts +1 -1
- package/dist/tui-v2/components/modal/confirm-modal.js +18 -7
- package/dist/tui-v2/components/modal/confirm-modal.js.map +1 -1
- package/dist/tui-v2/components/overlay/overlay-host.js +1 -1
- package/dist/tui-v2/components/overlay/overlay-host.js.map +1 -1
- package/dist/tui-v2/components/pager/pager.d.ts +6 -0
- package/dist/tui-v2/components/pager/pager.js +313 -67
- package/dist/tui-v2/components/pager/pager.js.map +1 -1
- package/dist/tui-v2/components/status/status-line.d.ts +9 -0
- package/dist/tui-v2/components/status/status-line.js +17 -3
- package/dist/tui-v2/components/status/status-line.js.map +1 -1
- package/dist/tui-v2/components/transcript/assistant-message.js +1 -1
- package/dist/tui-v2/components/transcript/assistant-message.js.map +1 -1
- package/dist/tui-v2/components/transcript/compacted-row.js +1 -1
- package/dist/tui-v2/components/transcript/compacted-row.js.map +1 -1
- package/dist/tui-v2/components/transcript/tool-card.d.ts +9 -1
- package/dist/tui-v2/components/transcript/tool-card.js +115 -10
- package/dist/tui-v2/components/transcript/tool-card.js.map +1 -1
- package/dist/tui-v2/components/transcript/transcript-row.js +2 -2
- package/dist/tui-v2/components/transcript/transcript-row.js.map +1 -1
- package/dist/tui-v2/composer/composer-editor.js +16 -0
- package/dist/tui-v2/composer/composer-editor.js.map +1 -1
- package/dist/tui-v2/controllers/overlay-controller.d.ts +16 -2
- package/dist/tui-v2/controllers/overlay-controller.js +92 -12
- package/dist/tui-v2/controllers/overlay-controller.js.map +1 -1
- package/dist/tui-v2/rendering/artifact-pager-source.d.ts +19 -0
- package/dist/tui-v2/rendering/artifact-pager-source.js +103 -0
- package/dist/tui-v2/rendering/artifact-pager-source.js.map +1 -0
- package/dist/tui-v2/rendering/file-diff-view.d.ts +63 -0
- package/dist/tui-v2/rendering/file-diff-view.js +263 -0
- package/dist/tui-v2/rendering/file-diff-view.js.map +1 -0
- package/dist/tui-v2/rendering/open-tool-output.d.ts +9 -1
- package/dist/tui-v2/rendering/open-tool-output.js +65 -12
- package/dist/tui-v2/rendering/open-tool-output.js.map +1 -1
- package/dist/tui-v2/rendering/pager-chrome.d.ts +12 -0
- package/dist/tui-v2/rendering/pager-chrome.js +56 -0
- package/dist/tui-v2/rendering/pager-chrome.js.map +1 -0
- package/dist/tui-v2/rendering/plan-view.js +1 -1
- package/dist/tui-v2/rendering/plan-view.js.map +1 -1
- package/dist/tui-v2/rendering/syntax-highlight.d.ts +37 -0
- package/dist/tui-v2/rendering/syntax-highlight.js +1303 -0
- package/dist/tui-v2/rendering/syntax-highlight.js.map +1 -0
- package/dist/tui-v2/rendering/theme.d.ts +28 -0
- package/dist/tui-v2/rendering/theme.js +28 -0
- package/dist/tui-v2/rendering/theme.js.map +1 -1
- package/dist/tui-v2/rendering/tool-presenter.d.ts +11 -1
- package/dist/tui-v2/rendering/tool-presenter.js +101 -5
- package/dist/tui-v2/rendering/tool-presenter.js.map +1 -1
- package/dist/tui-v2/state/transcript-hydrate.js +5 -0
- package/dist/tui-v2/state/transcript-hydrate.js.map +1 -1
- package/dist/tui-v2/state/transcript-reducer.js +12 -2
- package/dist/tui-v2/state/transcript-reducer.js.map +1 -1
- package/dist/tui-v2/state/transcript-store.d.ts +7 -0
- package/dist/tui-v2/state/transcript-store.js +46 -0
- package/dist/tui-v2/state/transcript-store.js.map +1 -1
- package/dist/tui-v2/state/transcript-types.d.ts +12 -0
- package/dist/tui-v2/state/transcript-types.js +9 -0
- package/dist/tui-v2/state/transcript-types.js.map +1 -1
- package/dist/types.d.ts +75 -1
- package/dist/ui/ansi-box.d.ts +2 -0
- package/dist/ui/ansi-box.js +8 -1
- package/dist/ui/ansi-box.js.map +1 -1
- package/dist/ui/mentions.d.ts +6 -0
- package/dist/ui/mentions.js +50 -12
- package/dist/ui/mentions.js.map +1 -1
- package/dist/ui/plan-pane.js +1 -1
- package/dist/ui/plan-pane.js.map +1 -1
- package/package.json +16 -11
package/dist/agent/plan-tool.js
CHANGED
|
@@ -1,7 +1,161 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { createPlan, loadPlan, savePlan, markTask, } from "../store/plan.js";
|
|
2
|
+
import { createPlan, loadPlan, savePlan, markTask, isBareTaskIdTitle, isPlanTerminal, isPlanSuccessful, readyPlanTasks, validateSessionPlan, } from "../store/plan.js";
|
|
3
3
|
import { renderPlanChecklist } from "../ui/plan-pane.js";
|
|
4
4
|
import { isLumpedSingleTask } from "./tool-call-parser.js";
|
|
5
|
+
import { getActiveProjectRoot } from "./project-root.js";
|
|
6
|
+
import { detectPackageManager } from "./workspace-orient.js";
|
|
7
|
+
/** Titles match for plan merge (exact or mutual long substring). */
|
|
8
|
+
export function titlesMatchForPlan(a, b) {
|
|
9
|
+
const t1 = a.trim().toLowerCase();
|
|
10
|
+
const t2 = b.trim().toLowerCase();
|
|
11
|
+
return (t1 === t2 ||
|
|
12
|
+
(t1.length > 8 && t2.length > 8 && (t1.includes(t2) || t2.includes(t1))));
|
|
13
|
+
}
|
|
14
|
+
/** Final coding-plan task: start server / probe localhost / leave running. */
|
|
15
|
+
export function looksLikeRunServerTask(title) {
|
|
16
|
+
const t = title.trim().toLowerCase();
|
|
17
|
+
if (/\b(dev\s*server|npm\s+run\s+dev|pnpm\s+(run\s+)?dev|yarn\s+dev|bun\s+run\s+dev|shell\.start|localhost|leave\s+(it\s+)?running|verify\s+in\s+browser|open\s+in\s+browser|probe\s+(localhost|http)|http:\/\/localhost)\b/.test(t)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (/\b(start|run)\b.+\b(server|dev|vite|next|preview)\b/.test(t))
|
|
21
|
+
return true;
|
|
22
|
+
if (/\b(server|dev|vite|next|preview)\b.+\b(start|run|verify|tail|probe)\b/.test(t)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
// "run and verify" / "start app" style finals
|
|
26
|
+
if (/\b(run|start)\b.+\b(app|verify)\b/.test(t) && /\b(server|dev|browser|localhost|url|port|job)\b/.test(t)) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
export function looksLikeLocalAppScaffold(kind, goal, detail, tasks) {
|
|
32
|
+
if (kind === "pentest")
|
|
33
|
+
return false;
|
|
34
|
+
const blob = `${kind} ${goal} ${detail} ${tasks.join(" ")}`.toLowerCase();
|
|
35
|
+
// Pure libraries / CLIs with no server surface
|
|
36
|
+
if (/\b(library|cli\b|command[- ]line|npm package|rust crate|go module)\b/.test(blob) &&
|
|
37
|
+
!/\b(react|vite|next|vue|svelte|web\s*app|frontend|dev\s*server|localhost|todo\s*app|dashboard|spa)\b/.test(blob)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (kind === "coding") {
|
|
41
|
+
// coding + no library-only signal → require run/verify when app-like
|
|
42
|
+
return /\b(app|react|vite|next|vue|svelte|nuxt|angular|frontend|web|ui|todo|blog|dashboard|spa|server|localhost|scaffold)\b/.test(blob);
|
|
43
|
+
}
|
|
44
|
+
return /\b(react|vite|next\.?js?|vue|svelte|nuxt|angular|web\s*app|frontend|spa|todo\s*app|blog\s*app|dashboard)\b/.test(blob);
|
|
45
|
+
}
|
|
46
|
+
export function codingPlanNeedsRunVerifyTask(kind, goal, detail, tasks) {
|
|
47
|
+
if (!looksLikeLocalAppScaffold(kind, goal, detail, tasks))
|
|
48
|
+
return false;
|
|
49
|
+
return !tasks.some(looksLikeRunServerTask);
|
|
50
|
+
}
|
|
51
|
+
function looksLikeInstallTask(title) {
|
|
52
|
+
const t = title.toLowerCase();
|
|
53
|
+
return (/\b(install|dependencies|deps|packages)\b/.test(t) &&
|
|
54
|
+
!/\b(dev\s*server|localhost|probe|run\s+dev)\b/.test(t));
|
|
55
|
+
}
|
|
56
|
+
function looksLikeScaffoldishTask(title) {
|
|
57
|
+
return /\b(scaffold|create|init|vite|next|bootstrap|cargo\s+new)\b/i.test(title);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Ensure coding app plans have an explicit install step after scaffold.
|
|
61
|
+
* Avoids "implement blocked from npm install" + install attributed to wrong task.
|
|
62
|
+
*/
|
|
63
|
+
export function ensureCodingPlanInstallTask(kind, goal, detail, tasks) {
|
|
64
|
+
if (!looksLikeLocalAppScaffold(kind, goal, detail, tasks))
|
|
65
|
+
return tasks;
|
|
66
|
+
if (tasks.some(looksLikeInstallTask))
|
|
67
|
+
return tasks;
|
|
68
|
+
if (!tasks.some(looksLikeScaffoldishTask))
|
|
69
|
+
return tasks;
|
|
70
|
+
const out = [...tasks];
|
|
71
|
+
const scaffoldIdx = out.findIndex(looksLikeScaffoldishTask);
|
|
72
|
+
const insertAt = scaffoldIdx >= 0 ? scaffoldIdx + 1 : 0;
|
|
73
|
+
out.splice(insertAt, 0, "Install project dependencies (npm/yarn/pnpm/bun install in project root)");
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
function looksLikeFeatureImplementTask(title) {
|
|
77
|
+
const t = title.toLowerCase();
|
|
78
|
+
if (looksLikeInstallTask(title) || looksLikeRunServerTask(title))
|
|
79
|
+
return false;
|
|
80
|
+
if (looksLikeScaffoldishTask(title) && !/\b(feature|todo|component|ui)\b/.test(t)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return (/\b(implement|feature|todo|component|ui|crud|auth|localstorage|persist|integrate)\b/.test(t) ||
|
|
84
|
+
(/\b(add|build|write|create)\b/.test(t) &&
|
|
85
|
+
/\b(todo|feature|component|page|ui|list|form)\b/.test(t)));
|
|
86
|
+
}
|
|
87
|
+
function looksLikeFeatureAppPlan(goal, detail, tasks) {
|
|
88
|
+
const blob = `${goal}\n${detail}\n${tasks.join("\n")}`.toLowerCase();
|
|
89
|
+
if (/\b(just|only)\s+(scaffold|init|boilerplate|starter)\b/.test(blob)) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return (/\b(todo|to-?do|blog|dashboard|chat|kanban|notes?|crm|shop|auth|login|crud|portfolio)\b/.test(blob) &&
|
|
93
|
+
/\b(app|application|project|site|ui|feature)\b/.test(blob));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Ensure feature-app plans include an implement-feature task (not scaffold-only).
|
|
97
|
+
*/
|
|
98
|
+
export function ensureCodingPlanFeatureTask(kind, goal, detail, tasks) {
|
|
99
|
+
if (!looksLikeLocalAppScaffold(kind, goal, detail, tasks))
|
|
100
|
+
return tasks;
|
|
101
|
+
if (!looksLikeFeatureAppPlan(goal, detail, tasks))
|
|
102
|
+
return tasks;
|
|
103
|
+
if (tasks.some(looksLikeFeatureImplementTask))
|
|
104
|
+
return tasks;
|
|
105
|
+
const out = [...tasks];
|
|
106
|
+
// Insert after install if present, else after scaffold, else before last (verify)
|
|
107
|
+
const installIdx = out.findIndex(looksLikeInstallTask);
|
|
108
|
+
const scaffoldIdx = out.findIndex(looksLikeScaffoldishTask);
|
|
109
|
+
const runIdx = out.findIndex(looksLikeRunServerTask);
|
|
110
|
+
let insertAt;
|
|
111
|
+
if (installIdx >= 0)
|
|
112
|
+
insertAt = installIdx + 1;
|
|
113
|
+
else if (scaffoldIdx >= 0)
|
|
114
|
+
insertAt = scaffoldIdx + 1;
|
|
115
|
+
else if (runIdx >= 0)
|
|
116
|
+
insertAt = runIdx;
|
|
117
|
+
else
|
|
118
|
+
insertAt = out.length;
|
|
119
|
+
out.splice(insertAt, 0, "Implement the requested product feature (replace starter boilerplate with real UI/state)");
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/** Ensure local app plans end with shell.start + probe + leave running. */
|
|
123
|
+
export function ensureCodingPlanRunVerifyTask(kind, goal, detail, tasks) {
|
|
124
|
+
if (!looksLikeLocalAppScaffold(kind, goal, detail, tasks))
|
|
125
|
+
return tasks;
|
|
126
|
+
if (tasks.some(looksLikeRunServerTask))
|
|
127
|
+
return tasks;
|
|
128
|
+
return [
|
|
129
|
+
...tasks,
|
|
130
|
+
"Start dev server with shell.start, probe localhost, leave running, report URL/port/job id",
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
/** Preserve the model/user-authored checklist exactly for fresh coding plans. */
|
|
134
|
+
export function normalizeCodingPlanTasks(_kind, _goal, _detail, tasks) {
|
|
135
|
+
return [...tasks];
|
|
136
|
+
}
|
|
137
|
+
/** Goal is only "run/start the existing app" — do not open a new plan. */
|
|
138
|
+
export function looksLikeRunOnlyGoal(goal, detail) {
|
|
139
|
+
const blob = `${goal} ${detail}`.toLowerCase();
|
|
140
|
+
if (!/\b(run|start|launch|serve|dev\s*server|npm\s+run\s+dev)\b/.test(blob)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
// Exclude fresh scaffold goals
|
|
144
|
+
if (/\b(scaffold|create|build|implement|add feature|from scratch|new app)\b/.test(blob)) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return /\b(existing|already|the app|dev server|server|verify|test it|open it)\b/.test(blob)
|
|
148
|
+
|| /^(run|start)\b/.test(blob.trim());
|
|
149
|
+
}
|
|
150
|
+
function nextTaskId(existingIds) {
|
|
151
|
+
let max = 0;
|
|
152
|
+
for (const id of existingIds) {
|
|
153
|
+
const m = /^t(\d+)$/i.exec(id);
|
|
154
|
+
if (m)
|
|
155
|
+
max = Math.max(max, Number(m[1]));
|
|
156
|
+
}
|
|
157
|
+
return `t${max + 1}`;
|
|
158
|
+
}
|
|
5
159
|
/** Coerce model-sloppy plan.create fields into clean titles. */
|
|
6
160
|
function normalizePlanGoal(args) {
|
|
7
161
|
const raw = args.goal ?? args.objective ?? args.title ?? args.name;
|
|
@@ -59,21 +213,92 @@ function normalizeTaskTitle(t) {
|
|
|
59
213
|
}
|
|
60
214
|
return "";
|
|
61
215
|
}
|
|
216
|
+
export function slugifyTaskId(text) {
|
|
217
|
+
return text
|
|
218
|
+
.trim()
|
|
219
|
+
.toLowerCase()
|
|
220
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
221
|
+
.replace(/^_+|_+$/g, "")
|
|
222
|
+
.slice(0, 64);
|
|
223
|
+
}
|
|
62
224
|
/**
|
|
63
225
|
* Accept tasks as string[], object[], or a single newline/comma-separated string.
|
|
64
226
|
* Models frequently emit `[{id,title}]` instead of plain strings.
|
|
65
227
|
*/
|
|
66
228
|
function normalizePlanTasks(args) {
|
|
229
|
+
return normalizePlanTaskEntries(args).map((t) => t.title);
|
|
230
|
+
}
|
|
231
|
+
/** Full entries including model-supplied id/name aliases (X3). */
|
|
232
|
+
export function normalizePlanTaskEntries(args) {
|
|
67
233
|
const raw = args.tasks ?? args.steps ?? args.checklist ?? args.items ?? args.todos;
|
|
68
234
|
if (typeof raw === "string") {
|
|
69
235
|
return raw
|
|
70
236
|
.split(/\n|;|(?:,\s*(?=[A-Z0-9\-]))/)
|
|
71
237
|
.map((s) => s.replace(/^\s*[-*\d.)]+\s*/, "").trim())
|
|
72
|
-
.filter(Boolean)
|
|
238
|
+
.filter(Boolean)
|
|
239
|
+
.map((title) => ({
|
|
240
|
+
title,
|
|
241
|
+
aliases: [],
|
|
242
|
+
dependencies: [],
|
|
243
|
+
dependenciesSpecified: false,
|
|
244
|
+
resourceLocks: [],
|
|
245
|
+
}));
|
|
73
246
|
}
|
|
74
247
|
if (!Array.isArray(raw))
|
|
75
248
|
return [];
|
|
76
|
-
return raw
|
|
249
|
+
return raw
|
|
250
|
+
.map((t) => {
|
|
251
|
+
const title = normalizeTaskTitle(t);
|
|
252
|
+
if (!title || isBareTaskIdTitle(title))
|
|
253
|
+
return null;
|
|
254
|
+
const aliases = [];
|
|
255
|
+
if (t && typeof t === "object") {
|
|
256
|
+
const o = t;
|
|
257
|
+
for (const k of ["id", "taskId", "key", "slug"]) {
|
|
258
|
+
if (typeof o[k] === "string" && o[k].toString().trim()) {
|
|
259
|
+
aliases.push(String(o[k]).trim());
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// `name` is often the title; only alias if it differs.
|
|
263
|
+
if (typeof o.name === "string" &&
|
|
264
|
+
o.name.trim() &&
|
|
265
|
+
o.name.trim().toLowerCase() !== title.toLowerCase()) {
|
|
266
|
+
aliases.push(o.name.trim());
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const slug = slugifyTaskId(title);
|
|
270
|
+
if (slug && !aliases.includes(slug))
|
|
271
|
+
aliases.push(slug);
|
|
272
|
+
const object = t && typeof t === "object" ? t : {};
|
|
273
|
+
const stringArray = (value) => Array.isArray(value)
|
|
274
|
+
? [...new Set(value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean))]
|
|
275
|
+
: [];
|
|
276
|
+
return {
|
|
277
|
+
title,
|
|
278
|
+
aliases: [...new Set(aliases)],
|
|
279
|
+
dependencies: stringArray(object.dependencies ?? object.dependsOn),
|
|
280
|
+
dependenciesSpecified: Object.prototype.hasOwnProperty.call(object, "dependencies") ||
|
|
281
|
+
Object.prototype.hasOwnProperty.call(object, "dependsOn"),
|
|
282
|
+
resourceLocks: stringArray(object.resourceLocks ?? object.resources),
|
|
283
|
+
};
|
|
284
|
+
})
|
|
285
|
+
.filter((x) => Boolean(x));
|
|
286
|
+
}
|
|
287
|
+
/** Resolve model taskId (t1 or slug) to the canonical plan task id. */
|
|
288
|
+
export function resolvePlanTaskId(plan, taskId) {
|
|
289
|
+
const raw = taskId.trim();
|
|
290
|
+
if (!raw)
|
|
291
|
+
return undefined;
|
|
292
|
+
if (plan.tasks.some((t) => t.id === raw))
|
|
293
|
+
return raw;
|
|
294
|
+
const lower = raw.toLowerCase();
|
|
295
|
+
const slug = slugifyTaskId(raw);
|
|
296
|
+
for (const t of plan.tasks) {
|
|
297
|
+
if (t.aliases?.some((a) => a === raw || a.toLowerCase() === lower)) {
|
|
298
|
+
return t.id;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return undefined;
|
|
77
302
|
}
|
|
78
303
|
/** Render the portable inline form; the Ink TUI owns its responsive sidebar. */
|
|
79
304
|
export function renderPlanForTerminal(plan) {
|
|
@@ -82,40 +307,57 @@ export function renderPlanForTerminal(plan) {
|
|
|
82
307
|
/** Build the system-context block describing the session's active plan. */
|
|
83
308
|
export function planContextMessage(plan, approved) {
|
|
84
309
|
const lines = [];
|
|
85
|
-
lines.push(`ACTIVE PLAN for this session (goal: ${plan.goal}, status: ${plan.status}):`);
|
|
310
|
+
lines.push(`ACTIVE PLAN v${plan.version ?? 1} for this session (goal: ${plan.goal}, status: ${plan.status}):`);
|
|
86
311
|
if (plan.detail.trim())
|
|
87
312
|
lines.push(plan.detail.trim());
|
|
88
313
|
lines.push("Tasks:");
|
|
89
314
|
plan.tasks.forEach((t, i) => {
|
|
90
|
-
|
|
315
|
+
const aliasHint = t.aliases?.length && t.aliases[0] !== t.id
|
|
316
|
+
? ` [aliases: ${t.aliases.slice(0, 3).join(", ")}]`
|
|
317
|
+
: "";
|
|
318
|
+
const dependencyHint = t.dependencies?.length
|
|
319
|
+
? ` [depends: ${t.dependencies.join(", ")}]`
|
|
320
|
+
: "";
|
|
321
|
+
const resourceHint = t.resourceLocks?.length
|
|
322
|
+
? ` [locks: ${t.resourceLocks.join(", ")}]`
|
|
323
|
+
: "";
|
|
324
|
+
const evidenceHint = t.evidence?.successWorkCount
|
|
325
|
+
? ` [evidence: ${t.evidence.successWorkCount} successful tool${t.evidence.successWorkCount === 1 ? "" : "s"}${t.evidence.lastOkTool ? `; last ${t.evidence.lastOkTool}` : ""}]`
|
|
326
|
+
: "";
|
|
327
|
+
lines.push(` ${i + 1}. [${t.id}] (${t.state}) ${t.title}${aliasHint}${dependencyHint}${resourceHint}${evidenceHint}`);
|
|
91
328
|
});
|
|
329
|
+
lines.push("task.update taskId MUST be t1, t2, … from this list (or a listed alias) — never invent free-form slugs alone.");
|
|
330
|
+
if (plan.meta?.projectRoot) {
|
|
331
|
+
lines.push(`project_root: ${plan.meta.projectRoot}`);
|
|
332
|
+
}
|
|
333
|
+
if (plan.meta?.packageManager) {
|
|
334
|
+
lines.push(`package_manager: ${plan.meta.packageManager}`);
|
|
335
|
+
}
|
|
92
336
|
if (approved) {
|
|
93
337
|
const inProgress = plan.tasks.find((t) => t.state === "in_progress");
|
|
94
|
-
const firstPending = plan
|
|
95
|
-
lines.push("The user APPROVED this plan. Execute it
|
|
338
|
+
const firstPending = readyPlanTasks(plan)[0];
|
|
339
|
+
lines.push("The user APPROVED this plan. Execute it NOW. Tasks are checkpoints — you still own the whole goal.");
|
|
96
340
|
if (inProgress) {
|
|
97
341
|
lines.push(`RESUME TASK ${inProgress.id} (${inProgress.title}) — it was started but interrupted. ` +
|
|
98
342
|
"Retry what was in progress; do NOT restart completed work from scratch. " +
|
|
99
|
-
"Do NOT re-do tasks already marked done
|
|
343
|
+
"Do NOT re-do tasks already marked done.");
|
|
100
344
|
}
|
|
101
345
|
else if (firstPending) {
|
|
102
346
|
lines.push(`START WITH TASK ${firstPending.id} (${firstPending.title}). ` +
|
|
103
|
-
"Do NOT re-do tasks already marked done
|
|
347
|
+
"Do NOT re-do tasks already marked done.");
|
|
104
348
|
}
|
|
105
|
-
lines.push("
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
349
|
+
lines.push("Flow: task.update in_progress → real work → WAIT for and READ the tool result → " +
|
|
350
|
+
"mark done only when verified → next task. Durable evidence shown beside a task survives resume; use it to close that task rather than repeating already-confirmed work. " +
|
|
351
|
+
"Independent read-only lookups may parallelize within a task. " +
|
|
352
|
+
"Never mark done before successful relevant evidence. If a tool fails: mark failed, fix, retry. " +
|
|
353
|
+
"For run/verify: prove runtime (shell.start, ready tail, LISTEN, or localhost GET), leave server running, final message includes URL, port, and job id. " +
|
|
354
|
+
"Do not re-open done tasks. If mid-task evidence shows the plan is wrong, adapt (fix root cause; for pentest, revise plan with completed tasks preserved).");
|
|
110
355
|
}
|
|
111
356
|
else {
|
|
112
|
-
lines.push("This plan is NOT yet approved
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"plan, then STOP and wait. Do NOT call shell.exec, pkg.install, net.scan, tool.check, fs.write, or any " +
|
|
117
|
-
"other execution tool. The user will APPROVE with /implement, or CANCEL with /discard. Only after " +
|
|
118
|
-
"/implement may you begin executing.");
|
|
357
|
+
lines.push("This plan is NOT yet approved — do not execute tasks (no scaffold/write/install/exploit). " +
|
|
358
|
+
"User free-text is PLAN REVISION feedback, not approval — even if it sounds like an instruction. " +
|
|
359
|
+
"Refine with plan.create and/or read-only explore/research, then STOP. " +
|
|
360
|
+
"User may Accept, Discard, View, or Suggest changes (or /implement / /discard).");
|
|
119
361
|
}
|
|
120
362
|
return lines.join("\n");
|
|
121
363
|
}
|
|
@@ -125,12 +367,15 @@ export function planContextMessage(plan, approved) {
|
|
|
125
367
|
* the agent keeps it in context across the whole session.
|
|
126
368
|
*/
|
|
127
369
|
export async function handlePlanTool(call, session, ctx) {
|
|
128
|
-
|
|
370
|
+
const autoApprove = Boolean(ctx.autoApprove);
|
|
371
|
+
void ctx.loopGuard;
|
|
372
|
+
void ctx.step;
|
|
129
373
|
if (call.name === "plan.create") {
|
|
130
374
|
const goal = normalizePlanGoal(call.args);
|
|
131
375
|
const detail = normalizePlanDetail(call.args);
|
|
132
376
|
const kind = normalizePlanKind(call.args);
|
|
133
|
-
const
|
|
377
|
+
const taskEntries = normalizePlanTaskEntries(call.args);
|
|
378
|
+
const taskTitles = taskEntries.map((t) => t.title);
|
|
134
379
|
if (!goal || taskTitles.length === 0) {
|
|
135
380
|
return {
|
|
136
381
|
handled: true,
|
|
@@ -150,67 +395,219 @@ export async function handlePlanTool(call, session, ctx) {
|
|
|
150
395
|
handled: true,
|
|
151
396
|
ok: false,
|
|
152
397
|
display: chalk.red(" ✗ plan.create: that single task lumps the whole build into one step\n"),
|
|
153
|
-
modelNote: "plan.create rejected: you put everything into ONE task. Break it into
|
|
154
|
-
"ordered tasks — each a distinct action
|
|
155
|
-
"
|
|
156
|
-
"'install deps and run dev server to verify'. Call plan.create again with that tasks array.",
|
|
398
|
+
modelNote: "plan.create rejected: you put everything into ONE task. Break it into separate " +
|
|
399
|
+
"ordered tasks — each a distinct action (scaffold, implement feature, install, run/verify). " +
|
|
400
|
+
"Call plan.create again with a proper tasks array.",
|
|
157
401
|
};
|
|
158
402
|
}
|
|
159
403
|
const existingPlan = await loadPlan(session.sessionId).catch(() => undefined);
|
|
404
|
+
// X12: do not open a second full plan just to run an existing app.
|
|
405
|
+
if (existingPlan &&
|
|
406
|
+
(existingPlan.status === "completed" ||
|
|
407
|
+
existingPlan.tasks.every((t) => t.state === "done" || t.state === "skipped" || t.state === "failed")) &&
|
|
408
|
+
looksLikeRunOnlyGoal(goal, detail)) {
|
|
409
|
+
return {
|
|
410
|
+
handled: true,
|
|
411
|
+
ok: false,
|
|
412
|
+
display: chalk.yellow(" ⚠ plan.create skipped — use tools to run the existing app\n"),
|
|
413
|
+
modelNote: "plan.create rejected: the previous plan is already finished and this goal is only run/verify. " +
|
|
414
|
+
"Do NOT create a new plan or re-list old scaffold tasks. " +
|
|
415
|
+
"Just shell.start the dev server, shell.tail until ready, probe localhost " +
|
|
416
|
+
"(curl or http.fetch with iOwnThis:true), LEAVE the server running, and tell the user " +
|
|
417
|
+
"the URL (http://localhost:<port>), port, and job id.",
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
// Preserve authored task count/order. Build, install, and verification
|
|
421
|
+
// evidence may span tasks; the runtime must not inject regex-derived
|
|
422
|
+
// checkpoints that change IDs or make progress diverge from the plan.
|
|
423
|
+
const normalizedTitles = existingPlan
|
|
424
|
+
? taskTitles
|
|
425
|
+
: normalizeCodingPlanTasks(kind, goal, detail, taskTitles);
|
|
426
|
+
const root = getActiveProjectRoot();
|
|
427
|
+
const pm = root ? detectPackageManager(root) : undefined;
|
|
428
|
+
const meta = root || existingPlan?.meta
|
|
429
|
+
? {
|
|
430
|
+
...(existingPlan?.meta ?? {}),
|
|
431
|
+
...(root ? { projectRoot: root } : {}),
|
|
432
|
+
...(pm ? { packageManager: pm } : {}),
|
|
433
|
+
}
|
|
434
|
+
: undefined;
|
|
160
435
|
const plan = createPlan({
|
|
161
436
|
sessionId: session.sessionId,
|
|
162
437
|
goal,
|
|
163
438
|
detail,
|
|
164
|
-
taskTitles,
|
|
439
|
+
taskTitles: normalizedTitles,
|
|
165
440
|
kind,
|
|
441
|
+
meta,
|
|
166
442
|
});
|
|
443
|
+
// Attach model-supplied slug aliases so task.update can resolve them (X3).
|
|
444
|
+
for (let i = 0; i < plan.tasks.length; i++) {
|
|
445
|
+
const aliases = taskEntries[i]?.aliases ?? [];
|
|
446
|
+
if (aliases.length)
|
|
447
|
+
plan.tasks[i].aliases = aliases;
|
|
448
|
+
const locks = taskEntries[i]?.resourceLocks ?? [];
|
|
449
|
+
if (locks.length)
|
|
450
|
+
plan.tasks[i].resourceLocks = locks;
|
|
451
|
+
}
|
|
452
|
+
for (let i = 0; i < plan.tasks.length; i++) {
|
|
453
|
+
const rawDependencies = taskEntries[i]?.dependencies ?? [];
|
|
454
|
+
const dependencies = rawDependencies.map((dependency) => resolvePlanTaskId(plan, dependency) ?? dependency);
|
|
455
|
+
if (taskEntries[i]?.dependenciesSpecified) {
|
|
456
|
+
plan.tasks[i].dependencies = [...new Set(dependencies)];
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
let additiveOnly = false;
|
|
167
460
|
if (existingPlan) {
|
|
461
|
+
plan.version = (existingPlan.version ?? 1) + 1;
|
|
462
|
+
const usedOldIds = new Set();
|
|
168
463
|
const mappedNewTasks = plan.tasks.map((task) => {
|
|
169
|
-
const match = existingPlan.tasks.find((t) =>
|
|
170
|
-
const t1 = t.title.trim().toLowerCase();
|
|
171
|
-
const t2 = task.title.trim().toLowerCase();
|
|
172
|
-
return (t1 === t2 ||
|
|
173
|
-
(t1.length > 8 && t2.length > 8 && (t1.includes(t2) || t2.includes(t1))));
|
|
174
|
-
});
|
|
464
|
+
const match = existingPlan.tasks.find((t) => !usedOldIds.has(t.id) && titlesMatchForPlan(t.title, task.title));
|
|
175
465
|
if (match) {
|
|
466
|
+
usedOldIds.add(match.id);
|
|
176
467
|
return {
|
|
177
468
|
...task,
|
|
469
|
+
id: match.id,
|
|
178
470
|
state: match.state,
|
|
179
471
|
note: match.note,
|
|
472
|
+
evidence: match.evidence,
|
|
180
473
|
};
|
|
181
474
|
}
|
|
182
475
|
return task;
|
|
183
476
|
});
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
477
|
+
// Free ids for brand-new tasks (avoid clobbering preserved ids).
|
|
478
|
+
const taken = new Set(mappedNewTasks.map((t) => t.id));
|
|
479
|
+
for (const task of mappedNewTasks) {
|
|
480
|
+
if (usedOldIds.has(task.id))
|
|
481
|
+
continue;
|
|
482
|
+
if (existingPlan.tasks.some((t) => t.id === task.id) ||
|
|
483
|
+
mappedNewTasks.filter((t) => t.id === task.id).length > 1) {
|
|
484
|
+
let id = nextTaskId([...taken]);
|
|
485
|
+
while (taken.has(id))
|
|
486
|
+
id = nextTaskId([...taken, id]);
|
|
487
|
+
task.id = id;
|
|
488
|
+
taken.add(id);
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
taken.add(task.id);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const oldTasksToKeep = existingPlan.tasks.filter((oldTask) => !isBareTaskIdTitle(oldTask.title) &&
|
|
495
|
+
!mappedNewTasks.some((newTask) => titlesMatchForPlan(oldTask.title, newTask.title)));
|
|
496
|
+
plan.tasks = [...oldTasksToKeep, ...mappedNewTasks].filter((t) => !isBareTaskIdTitle(t.title));
|
|
497
|
+
const finalIdByInputReference = new Map();
|
|
498
|
+
for (let index = 0; index < mappedNewTasks.length; index += 1) {
|
|
499
|
+
const task = mappedNewTasks[index];
|
|
500
|
+
const entry = taskEntries[index];
|
|
501
|
+
finalIdByInputReference.set(`t${index + 1}`, task.id);
|
|
502
|
+
finalIdByInputReference.set(task.id, task.id);
|
|
503
|
+
finalIdByInputReference.set(slugifyTaskId(task.title), task.id);
|
|
504
|
+
for (const alias of entry?.aliases ?? []) {
|
|
505
|
+
finalIdByInputReference.set(alias, task.id);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
for (let index = 0; index < mappedNewTasks.length; index += 1) {
|
|
509
|
+
const task = mappedNewTasks[index];
|
|
510
|
+
const entry = taskEntries[index];
|
|
511
|
+
const references = entry?.dependenciesSpecified
|
|
512
|
+
? entry.dependencies
|
|
513
|
+
: index > 0
|
|
514
|
+
? [`t${index}`]
|
|
515
|
+
: [];
|
|
516
|
+
task.dependencies = [...new Set(references.map((reference) => finalIdByInputReference.get(reference) ??
|
|
517
|
+
resolvePlanTaskId(plan, reference) ??
|
|
518
|
+
reference))];
|
|
519
|
+
}
|
|
520
|
+
// X7: keep approval when only additive pending work remains.
|
|
521
|
+
const priorFinished = existingPlan.tasks.filter((t) => t.state === "done" || t.state === "skipped" || t.state === "failed");
|
|
522
|
+
const finishedStillFinished = priorFinished.length > 0 &&
|
|
523
|
+
priorFinished.every((old) => plan.tasks.some((n) => titlesMatchForPlan(old.title, n.title) &&
|
|
524
|
+
(n.state === "done" ||
|
|
525
|
+
n.state === "skipped" ||
|
|
526
|
+
n.state === "failed")));
|
|
527
|
+
const hasNewPending = plan.tasks.some((t) => t.state === "pending");
|
|
528
|
+
const noDoneReopened = !plan.tasks.some((n) => {
|
|
529
|
+
const old = existingPlan.tasks.find((t) => titlesMatchForPlan(t.title, n.title));
|
|
530
|
+
return (old &&
|
|
531
|
+
(old.state === "done" || old.state === "skipped") &&
|
|
532
|
+
n.state === "pending");
|
|
191
533
|
});
|
|
192
|
-
|
|
534
|
+
additiveOnly =
|
|
535
|
+
finishedStillFinished &&
|
|
536
|
+
hasNewPending &&
|
|
537
|
+
noDoneReopened &&
|
|
538
|
+
(session.planApproved.value ||
|
|
539
|
+
existingPlan.status === "approved" ||
|
|
540
|
+
existingPlan.status === "in_progress" ||
|
|
541
|
+
existingPlan.status === "completed");
|
|
542
|
+
if (additiveOnly) {
|
|
543
|
+
plan.status = "in_progress";
|
|
544
|
+
plan.createdAt = existingPlan.createdAt;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const dag = validateSessionPlan(plan);
|
|
548
|
+
if (!dag.ok) {
|
|
549
|
+
return {
|
|
550
|
+
handled: true,
|
|
551
|
+
ok: false,
|
|
552
|
+
display: chalk.red(` ✗ plan.create: invalid dependency graph — ${dag.reason}\n`),
|
|
553
|
+
modelNote: `plan.create failed: ${dag.reason}. Use only declared task ids/aliases and remove dependency cycles.`,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
if (additiveOnly) {
|
|
557
|
+
session.planApproved.value = true;
|
|
558
|
+
plan.status = "in_progress";
|
|
559
|
+
}
|
|
560
|
+
else if (autoApprove) {
|
|
561
|
+
session.planApproved.value = true;
|
|
562
|
+
plan.status = "approved";
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
session.planApproved.value = false;
|
|
193
566
|
}
|
|
194
567
|
await savePlan(plan).catch(() => undefined);
|
|
195
|
-
// A freshly (re)created plan resets approval — the user must /implement.
|
|
196
|
-
session.planApproved.value = false;
|
|
197
568
|
const checklist = renderPlanForTerminal(plan);
|
|
198
|
-
const display =
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
"
|
|
569
|
+
const display = additiveOnly
|
|
570
|
+
? chalk.cyan(" ● plan updated (additive)\n") +
|
|
571
|
+
checklist +
|
|
572
|
+
"\n" +
|
|
573
|
+
chalk.dim(" ✦ done tasks preserved — continue from the first pending task only.\n" +
|
|
574
|
+
" Do NOT re-run completed scaffold/build work.\n")
|
|
575
|
+
: autoApprove
|
|
576
|
+
? chalk.cyan(" ● plan created (auto-approved)\n") +
|
|
577
|
+
checklist +
|
|
578
|
+
"\n" +
|
|
579
|
+
chalk.dim(" ✦ plan approved in agent mode — continue executing task by task.\n")
|
|
580
|
+
: chalk.cyan(" ● planning\n") +
|
|
581
|
+
checklist +
|
|
582
|
+
"\n" +
|
|
583
|
+
chalk.dim(" ✦ plan created — Accept to run, Discard, View, or Suggest changes\n" +
|
|
584
|
+
" (/implement, /discard, Ctrl+P also work).\n");
|
|
585
|
+
const firstPending = readyPlanTasks(plan)[0];
|
|
203
586
|
return {
|
|
204
587
|
handled: true,
|
|
205
588
|
ok: true,
|
|
206
589
|
plan,
|
|
207
590
|
display,
|
|
208
|
-
modelNote:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
591
|
+
modelNote: additiveOnly
|
|
592
|
+
? `Plan updated with additive task(s); ${plan.tasks.filter((t) => t.state === "done" || t.state === "skipped").length} prior task(s) stay done. ` +
|
|
593
|
+
"Do NOT re-execute completed tasks (no re-scaffold). " +
|
|
594
|
+
(firstPending
|
|
595
|
+
? `Continue from [${firstPending.id}] "${firstPending.title}" only. `
|
|
596
|
+
: "") +
|
|
597
|
+
"If the only new work is run/verify on an existing app, prefer shell.start + probe + report URL — " +
|
|
598
|
+
"do not reboot the world. When the run/verify task finishes, final message MUST include " +
|
|
599
|
+
"http://localhost:<port>, port, job id, and that the server is still running."
|
|
600
|
+
: autoApprove
|
|
601
|
+
? `Plan saved and approved with ${plan.tasks.length} task(s). Execute now. ` +
|
|
602
|
+
(firstPending
|
|
603
|
+
? `Start with [${firstPending.id}] "${firstPending.title}". `
|
|
604
|
+
: "") +
|
|
605
|
+
"task.update in_progress → work → verify → done. When run/verify completes: report URL, port, job id, server still running."
|
|
606
|
+
: `Plan saved with ${plan.tasks.length} task(s). STOP here and wait — produce NO other tool calls now. ` +
|
|
607
|
+
"Do NOT start executing until the user accepts the plan. " +
|
|
608
|
+
"If the user's next message gives feedback, that is a REVISION: call plan.create again with the updated plan and STOP again. " +
|
|
609
|
+
"The user may discard the plan. After acceptance: task.update in_progress → work → verify → done. " +
|
|
610
|
+
"When run/verify completes: report URL, port, job id, and that the server is still running.",
|
|
214
611
|
};
|
|
215
612
|
}
|
|
216
613
|
// task.update
|
|
@@ -223,7 +620,11 @@ export async function handlePlanTool(call, session, ctx) {
|
|
|
223
620
|
modelNote: "task.update failed: there is no active plan. Call plan.create first.",
|
|
224
621
|
};
|
|
225
622
|
}
|
|
226
|
-
const
|
|
623
|
+
const taskIdRaw = typeof call.args.taskId === "string"
|
|
624
|
+
? call.args.taskId
|
|
625
|
+
: typeof call.args.id === "string"
|
|
626
|
+
? call.args.id
|
|
627
|
+
: "";
|
|
227
628
|
const stateRaw = typeof call.args.state === "string" ? call.args.state : "";
|
|
228
629
|
const note = typeof call.args.note === "string" ? call.args.note : undefined;
|
|
229
630
|
const validStates = [
|
|
@@ -241,50 +642,167 @@ export async function handlePlanTool(call, session, ctx) {
|
|
|
241
642
|
modelNote: `task.update failed: state must be one of ${validStates.join(", ")}.`,
|
|
242
643
|
};
|
|
243
644
|
}
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
// current task before opening the next one, instead of leaving a task
|
|
247
|
-
// "in_progress" as an umbrella while it quietly works through the rest
|
|
248
|
-
// of the plan underneath it.
|
|
645
|
+
// X3: accept t1..tn or model slug aliases / title slugs.
|
|
646
|
+
const taskId = resolvePlanTaskId(plan, taskIdRaw) ?? taskIdRaw;
|
|
249
647
|
if (stateRaw === "in_progress") {
|
|
250
|
-
const
|
|
251
|
-
|
|
648
|
+
const target = plan.tasks.find((task) => task.id === taskId);
|
|
649
|
+
const ready = readyPlanTasks(plan).some((task) => task.id === taskId);
|
|
650
|
+
const retryingFailedTask = target?.state === "failed";
|
|
651
|
+
if (target?.state === "done" || target?.state === "skipped") {
|
|
652
|
+
const nextPending = readyPlanTasks(plan)[0];
|
|
653
|
+
const nextHint = nextPending
|
|
654
|
+
? ` Continue with task.update {taskId:"${nextPending.id}", state:"in_progress"} ("${nextPending.title}").`
|
|
655
|
+
: " All ready work is finished — write the final summary if needed.";
|
|
656
|
+
return {
|
|
657
|
+
handled: true,
|
|
658
|
+
ok: false,
|
|
659
|
+
display: chalk.red(` ✗ task.update: [${taskId}] is already ${target.state} — do not re-open\n`),
|
|
660
|
+
modelNote: `task.update failed: [${taskId}] is already ${target.state}. ` +
|
|
661
|
+
`Do not re-run completed tasks.${nextHint}`,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
if (target?.state !== "in_progress") {
|
|
665
|
+
const incompleteDependencies = (target?.dependencies ?? []).filter((dependency) => {
|
|
666
|
+
const dependencyTask = plan.tasks.find((task) => task.id === dependency);
|
|
667
|
+
return !dependencyTask || (dependencyTask.state !== "done" && dependencyTask.state !== "skipped");
|
|
668
|
+
});
|
|
669
|
+
const heldLocks = new Set(plan.tasks
|
|
670
|
+
.filter((task) => task.state === "in_progress" && task.id !== taskId)
|
|
671
|
+
.flatMap((task) => task.resourceLocks ?? []));
|
|
672
|
+
const conflictingLocks = (target?.resourceLocks ?? []).filter((lock) => heldLocks.has(lock));
|
|
673
|
+
if ((!ready && !retryingFailedTask) ||
|
|
674
|
+
incompleteDependencies.length > 0 ||
|
|
675
|
+
conflictingLocks.length > 0) {
|
|
676
|
+
const nextReady = readyPlanTasks(plan)[0];
|
|
677
|
+
return {
|
|
678
|
+
handled: true,
|
|
679
|
+
ok: false,
|
|
680
|
+
display: chalk.red(` ✗ task.update: [${taskId}] is not dependency/resource ready\n`),
|
|
681
|
+
modelNote: `task.update failed: [${taskId}] is not ready. ` +
|
|
682
|
+
(incompleteDependencies.length ? `Incomplete dependencies: ${incompleteDependencies.join(", ")}. ` : "") +
|
|
683
|
+
(conflictingLocks.length ? `Resources currently locked: ${conflictingLocks.join(", ")}. ` : "") +
|
|
684
|
+
(nextReady
|
|
685
|
+
? `Open the next ready task first: ${nextReady.id} ("${nextReady.title}").`
|
|
686
|
+
: ""),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
// A retry is a new execution attempt. Previous receipts proved only the
|
|
691
|
+
// failed attempt and must not close the task before recovery work succeeds.
|
|
692
|
+
if (retryingFailedTask && target)
|
|
693
|
+
target.evidence = undefined;
|
|
694
|
+
}
|
|
695
|
+
if (stateRaw === "done") {
|
|
696
|
+
const target = plan.tasks.find((task) => task.id === taskId);
|
|
697
|
+
const incompleteDependencies = (target?.dependencies ?? []).filter((dependency) => {
|
|
698
|
+
const dependencyTask = plan.tasks.find((task) => task.id === dependency);
|
|
699
|
+
return !dependencyTask || (dependencyTask.state !== "done" && dependencyTask.state !== "skipped");
|
|
700
|
+
});
|
|
701
|
+
if (incompleteDependencies.length > 0) {
|
|
252
702
|
return {
|
|
253
703
|
handled: true,
|
|
254
704
|
ok: false,
|
|
255
|
-
display: chalk.red(`
|
|
256
|
-
modelNote: `task.update failed: task
|
|
257
|
-
"Finish it first \u2014 call task.update with state 'done' (or 'failed'/'skipped' with a note) " +
|
|
258
|
-
`for [${otherInProgress.id}] before starting [${taskId}].`,
|
|
705
|
+
display: chalk.red(` ✗ task.update: cannot complete [${taskId}] before dependencies\n`),
|
|
706
|
+
modelNote: `task.update failed: earlier task/dependency ${incompleteDependencies.join(", ")} is not complete for [${taskId}].`,
|
|
259
707
|
};
|
|
260
708
|
}
|
|
709
|
+
if (target && target.state !== "in_progress") {
|
|
710
|
+
// Soft-auto: pending + dependency-ready → open then complete in one call.
|
|
711
|
+
// Models often skip the ritual in_progress step after work already landed.
|
|
712
|
+
if (target.state === "pending" && incompleteDependencies.length === 0) {
|
|
713
|
+
const opened = markTask(plan, taskId, "in_progress", note);
|
|
714
|
+
if (!opened) {
|
|
715
|
+
return {
|
|
716
|
+
handled: true,
|
|
717
|
+
ok: false,
|
|
718
|
+
display: chalk.red(` ✗ task.update: could not auto-open [${taskId}] before completion\n`),
|
|
719
|
+
modelNote: `task.update failed: could not auto-open pending [${taskId}].`,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
// Fall through to mark done below (state is now in_progress).
|
|
723
|
+
}
|
|
724
|
+
else if (target.state === "failed") {
|
|
725
|
+
return {
|
|
726
|
+
handled: true,
|
|
727
|
+
ok: false,
|
|
728
|
+
display: chalk.red(` ✗ task.update: [${taskId}] is failed — retry with in_progress first\n`),
|
|
729
|
+
modelNote: `task.update failed: [${taskId}] is failed. ` +
|
|
730
|
+
`Call task.update {taskId:"${taskId}", state:"in_progress"} to retry, then mark done after recovery work.`,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
const nextReady = readyPlanTasks(plan)[0];
|
|
735
|
+
return {
|
|
736
|
+
handled: true,
|
|
737
|
+
ok: false,
|
|
738
|
+
display: chalk.red(` ✗ task.update: [${taskId}] must be in_progress before completion\n`),
|
|
739
|
+
modelNote: `task.update failed: [${taskId}] is ${target.state}. ` +
|
|
740
|
+
"Start or retry it, perform fresh work, then mark it done." +
|
|
741
|
+
(nextReady
|
|
742
|
+
? ` Open next ready: ${nextReady.id} ("${nextReady.title}").`
|
|
743
|
+
: ""),
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
}
|
|
261
747
|
}
|
|
262
748
|
const ok = markTask(plan, taskId, stateRaw, note);
|
|
263
749
|
if (!ok) {
|
|
264
|
-
const
|
|
750
|
+
const idMap = plan.tasks
|
|
751
|
+
.map((t) => `${t.id}="${t.title}"` +
|
|
752
|
+
(t.aliases?.length ? ` (also: ${t.aliases.join(", ")})` : ""))
|
|
753
|
+
.join("; ");
|
|
265
754
|
return {
|
|
266
755
|
handled: true,
|
|
267
756
|
ok: false,
|
|
268
|
-
display: chalk.red(` ✗ task.update: unknown taskId "${
|
|
269
|
-
modelNote: `task.update failed: unknown taskId
|
|
757
|
+
display: chalk.red(` ✗ task.update: unknown taskId "${taskIdRaw}" (have: ${plan.tasks.map((t) => t.id).join(", ")})\n`),
|
|
758
|
+
modelNote: `task.update failed: unknown taskId "${taskIdRaw}". ` +
|
|
759
|
+
`Valid ids (use these, not title text): ${idMap}.`,
|
|
270
760
|
};
|
|
271
761
|
}
|
|
272
762
|
if (plan.status === "draft" || plan.status === "approved") {
|
|
273
763
|
plan.status = "in_progress";
|
|
274
764
|
}
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
765
|
+
const terminal = isPlanTerminal(plan);
|
|
766
|
+
const successful = isPlanSuccessful(plan);
|
|
767
|
+
if (terminal)
|
|
768
|
+
plan.status = successful ? "completed" : "abandoned";
|
|
278
769
|
await savePlan(plan).catch(() => undefined);
|
|
279
770
|
const checklist = renderPlanForTerminal(plan);
|
|
771
|
+
const nextPending = readyPlanTasks(plan)[0];
|
|
772
|
+
let modelNote;
|
|
773
|
+
if (successful) {
|
|
774
|
+
modelNote =
|
|
775
|
+
"Task updated. All required tasks succeeded or were explicitly skipped. Verify the result and give your final summary. " +
|
|
776
|
+
"If a dev server was started: report URL, port, job id, and that it is still running.";
|
|
777
|
+
}
|
|
778
|
+
else if (terminal) {
|
|
779
|
+
const failed = plan.tasks.filter((task) => task.state === "failed");
|
|
780
|
+
modelNote =
|
|
781
|
+
`Task updated. The plan is terminal but NOT successful: ${failed.length} task(s) failed. ` +
|
|
782
|
+
"Do not claim completion. Report a failed/partial outcome with the failed tasks and remaining user outcome.";
|
|
783
|
+
}
|
|
784
|
+
else if (stateRaw === "done" && nextPending) {
|
|
785
|
+
modelNote =
|
|
786
|
+
`Task [${taskId}] marked done after verified work. ` +
|
|
787
|
+
`NEXT: open only the next task with task.update {taskId:"${nextPending.id}", state:"in_progress"}, ` +
|
|
788
|
+
`then do work for "${nextPending.title}" only. Wait for tool results before marking that one done. ` +
|
|
789
|
+
"Do not batch later tasks. Do not start a later task's tools early.";
|
|
790
|
+
}
|
|
791
|
+
else if (stateRaw === "in_progress") {
|
|
792
|
+
modelNote =
|
|
793
|
+
`Task [${taskId}] is now in_progress. Do ONLY this task's work, wait for tool results, ` +
|
|
794
|
+
`and mark done only when you are satisfied the results prove success. ` +
|
|
795
|
+
`Then task.update {taskId:"${taskId}", state:"done"}. Do not open or work on later tasks yet.`;
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
modelNote = "Task updated. Continue with the next pending task.";
|
|
799
|
+
}
|
|
280
800
|
return {
|
|
281
801
|
handled: true,
|
|
282
802
|
ok: true,
|
|
283
803
|
plan,
|
|
284
804
|
display: checklist + "\n",
|
|
285
|
-
modelNote
|
|
286
|
-
? "Task updated. ALL tasks are now finished. Verify the result and give your final summary."
|
|
287
|
-
: "Task updated. Continue with the next pending task.",
|
|
805
|
+
modelNote,
|
|
288
806
|
};
|
|
289
807
|
}
|
|
290
808
|
//# sourceMappingURL=plan-tool.js.map
|