@bastani/atomic 0.8.13 → 0.8.14
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/CHANGELOG.md +23 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/host-html-template.ts +1 -1
- package/dist/builtin/mcp/init.ts +15 -2
- package/dist/builtin/mcp/mcp-callback-server.ts +10 -9
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/mcp/ui-session.ts +9 -6
- package/dist/builtin/subagents/CHANGELOG.md +8 -1
- package/dist/builtin/subagents/README.md +39 -32
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +11 -11
- package/dist/builtin/subagents/src/agents/agent-management.ts +6 -1
- package/dist/builtin/subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/builtin/subagents/src/agents/agents.ts +44 -19
- package/dist/builtin/subagents/src/extension/config.ts +16 -0
- package/dist/builtin/subagents/src/extension/fanout-child.ts +246 -0
- package/dist/builtin/subagents/src/extension/index.ts +466 -603
- package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +6 -4
- package/dist/builtin/subagents/src/intercom/result-intercom.ts +109 -1
- package/dist/builtin/subagents/src/runs/background/async-execution.ts +124 -19
- package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +41 -6
- package/dist/builtin/subagents/src/runs/background/async-resume.ts +28 -15
- package/dist/builtin/subagents/src/runs/background/async-status.ts +60 -30
- package/dist/builtin/subagents/src/runs/background/result-watcher.ts +111 -54
- package/dist/builtin/subagents/src/runs/background/run-id-resolver.ts +83 -0
- package/dist/builtin/subagents/src/runs/background/run-status.ts +79 -3
- package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +46 -1
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +66 -14
- package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +10 -3
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +14 -2
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +320 -23
- package/dist/builtin/subagents/src/runs/shared/completion-guard.ts +23 -1
- package/dist/builtin/subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +369 -0
- package/dist/builtin/subagents/src/runs/shared/nested-events.ts +935 -0
- package/dist/builtin/subagents/src/runs/shared/nested-path.ts +52 -0
- package/dist/builtin/subagents/src/runs/shared/nested-render.ts +115 -0
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +1 -0
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +82 -9
- package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +1 -1
- package/dist/builtin/subagents/src/runs/shared/single-output.ts +12 -2
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +32 -10
- package/dist/builtin/subagents/src/runs/shared/worktree.ts +3 -2
- package/dist/builtin/subagents/src/shared/artifacts.ts +0 -1
- package/dist/builtin/subagents/src/shared/types.ts +96 -1
- package/dist/builtin/subagents/src/shared/utils.ts +10 -2
- package/dist/builtin/subagents/src/slash/slash-commands.ts +468 -625
- package/dist/builtin/subagents/src/tui/render.ts +1227 -2093
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +24 -0
- package/dist/builtin/workflows/README.md +28 -11
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +323 -40
- package/dist/builtin/workflows/builtin/ralph.ts +362 -176
- package/dist/builtin/workflows/package.json +2 -5
- package/dist/builtin/workflows/skills/research-codebase/SKILL.md +1 -1
- package/dist/builtin/workflows/skills/skill-creator/LICENSE.txt +202 -0
- package/dist/builtin/workflows/skills/skill-creator/SKILL.md +489 -0
- package/dist/builtin/workflows/skills/skill-creator/agents/analyzer.md +274 -0
- package/dist/builtin/workflows/skills/skill-creator/agents/comparator.md +202 -0
- package/dist/builtin/workflows/skills/skill-creator/agents/grader.md +223 -0
- package/dist/builtin/workflows/skills/skill-creator/assets/eval_review.html +146 -0
- package/dist/builtin/workflows/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/dist/builtin/workflows/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/dist/builtin/workflows/skills/skill-creator/references/schemas.md +430 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/__init__.py +0 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/generate_report.py +326 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/improve_description.py +247 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/package_skill.py +136 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/run_eval.py +310 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/run_loop.py +328 -0
- package/dist/builtin/workflows/skills/skill-creator/scripts/utils.py +47 -0
- package/dist/builtin/workflows/src/extension/index.ts +869 -93
- package/dist/builtin/workflows/src/extension/render-call.ts +34 -1
- package/dist/builtin/workflows/src/extension/render-result.ts +126 -21
- package/dist/builtin/workflows/src/extension/runtime.ts +91 -3
- package/dist/builtin/workflows/src/extension/wiring.ts +38 -12
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +62 -5
- package/dist/builtin/workflows/src/runs/background/runner.ts +3 -3
- package/dist/builtin/workflows/src/runs/background/status.ts +42 -8
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +410 -95
- package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +5 -2
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +8 -0
- package/dist/builtin/workflows/src/runs/shared/model-fallback.ts +6 -4
- package/dist/builtin/workflows/src/runs/shared/worktree.ts +3 -2
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +138 -5
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +30 -0
- package/dist/builtin/workflows/src/shared/render-inputs-schema.ts +78 -120
- package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +193 -0
- package/dist/builtin/workflows/src/shared/store-types.ts +26 -1
- package/dist/builtin/workflows/src/shared/store.ts +145 -17
- package/dist/builtin/workflows/src/shared/timing.ts +6 -2
- package/dist/builtin/workflows/src/shared/workflow-failures.ts +375 -0
- package/dist/builtin/workflows/src/tui/chat-surface.ts +68 -17
- package/dist/builtin/workflows/src/tui/connectors.ts +2 -2
- package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +24 -26
- package/dist/builtin/workflows/src/tui/graph-canvas.ts +4 -8
- package/dist/builtin/workflows/src/tui/graph-view.ts +17 -14
- package/dist/builtin/workflows/src/tui/header.ts +38 -0
- package/dist/builtin/workflows/src/tui/inline-form-card.ts +161 -238
- package/dist/builtin/workflows/src/tui/inline-form-editor.ts +68 -73
- package/dist/builtin/workflows/src/tui/inline-form-overlay.ts +2 -3
- package/dist/builtin/workflows/src/tui/inline-form-store.ts +2 -1
- package/dist/builtin/workflows/src/tui/inputs-overlay.ts +1 -3
- package/dist/builtin/workflows/src/tui/inputs-picker.ts +286 -399
- package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +11 -0
- package/dist/builtin/workflows/src/tui/node-card.ts +2 -1
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +9 -1
- package/dist/builtin/workflows/src/tui/prompt-card.ts +46 -19
- package/dist/builtin/workflows/src/tui/run-detail.ts +63 -80
- package/dist/builtin/workflows/src/tui/session-confirm.ts +9 -3
- package/dist/builtin/workflows/src/tui/session-picker.ts +19 -16
- package/dist/builtin/workflows/src/tui/stage-chat-layout.ts +88 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +368 -879
- package/dist/builtin/workflows/src/tui/status-helpers.ts +4 -0
- package/dist/builtin/workflows/src/tui/status-list.ts +67 -75
- package/dist/builtin/workflows/src/tui/store-widget-installer.ts +50 -12
- package/dist/builtin/workflows/src/tui/submit-pane.ts +164 -0
- package/dist/builtin/workflows/src/tui/switcher.ts +27 -4
- package/dist/builtin/workflows/src/tui/text-helpers.ts +98 -4
- package/dist/builtin/workflows/src/tui/widget.ts +90 -68
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +23 -2
- package/dist/builtin/workflows/src/tui/workflow-list.ts +44 -68
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +2 -3
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -10
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +2 -1
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -2
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +16 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +8 -9
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +3 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +2 -1
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +8 -6
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/export-html/template.js +6 -3
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +12 -29
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +5 -1
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/package-manager.d.ts +8 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +145 -58
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +6 -20
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +38 -31
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +9 -4
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +32 -24
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -15
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +8 -22
- package/dist/core/skills.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts +5 -4
- package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/questionnaire-session.js +34 -11
- package/dist/core/tools/ask-user-question/state/questionnaire-session.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts +1 -0
- package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/contract.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/projections.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/projections.js +1 -0
- package/dist/core/tools/ask-user-question/state/selectors/projections.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/state-reducer.d.ts +1 -2
- package/dist/core/tools/ask-user-question/state/state-reducer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/state-reducer.js +26 -9
- package/dist/core/tools/ask-user-question/state/state-reducer.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/state.d.ts +4 -0
- package/dist/core/tools/ask-user-question/state/state.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/state.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts +1 -0
- package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/option-list-view.js +1 -0
- package/dist/core/tools/ask-user-question/view/components/option-list-view.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts +9 -6
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.js +28 -7
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.js +4 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +56 -53
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +3 -1
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +8 -1
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +3 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +44 -81
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +27 -12
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +2 -3
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +3 -3
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +5 -5
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +2 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +11 -4
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/path-utils.d.ts +2 -0
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +39 -21
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +9 -8
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/truncate.d.ts.map +1 -1
- package/dist/core/tools/truncate.js +12 -2
- package/dist/core/tools/truncate.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +20 -35
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -6
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/chat-input-actions.d.ts +24 -0
- package/dist/modes/interactive/chat-input-actions.d.ts.map +1 -0
- package/dist/modes/interactive/chat-input-actions.js +179 -0
- package/dist/modes/interactive/chat-input-actions.js.map +1 -0
- package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -0
- package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.js +14 -3
- package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.d.ts +157 -0
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -0
- package/dist/modes/interactive/components/chat-session-host.js +1007 -0
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -0
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +1 -1
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +1 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +14 -5
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +9 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +29 -4
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +18 -67
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/child-process.d.ts +1 -0
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js +8 -0
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +3 -1
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js +14 -8
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/image-resize-core.d.ts +30 -0
- package/dist/utils/image-resize-core.d.ts.map +1 -0
- package/dist/utils/image-resize-core.js +124 -0
- package/dist/utils/image-resize-core.js.map +1 -0
- package/dist/utils/image-resize-worker.d.ts +2 -0
- package/dist/utils/image-resize-worker.d.ts.map +1 -0
- package/dist/utils/image-resize-worker.js +31 -0
- package/dist/utils/image-resize-worker.js.map +1 -0
- package/dist/utils/image-resize.d.ts +7 -27
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +75 -115
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/paths.d.ts +16 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +49 -7
- package/dist/utils/paths.js.map +1 -1
- package/docs/changelog.mdx +29 -0
- package/docs/compaction.md +1 -1
- package/docs/custom-provider.md +2 -2
- package/docs/development.md +1 -1
- package/docs/docs.json +98 -143
- package/docs/extensions.md +29 -16
- package/docs/favicon.svg +29 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/images/workflow-command.png +0 -0
- package/docs/images/workflow-graph.png +0 -0
- package/docs/images/workflow-input-picker.png +0 -0
- package/docs/images/workflow-list.png +0 -0
- package/docs/index.md +10 -1
- package/docs/logo.svg +59 -0
- package/docs/packages.md +3 -3
- package/docs/providers.md +1 -1
- package/docs/quickstart.md +98 -2
- package/docs/rpc.md +8 -8
- package/docs/sdk.md +23 -12
- package/docs/sessions.md +1 -1
- package/docs/skills.md +15 -1
- package/docs/termux.md +11 -1
- package/docs/themes.md +6 -6
- package/docs/tui.md +18 -18
- package/docs/usage.md +1 -1
- package/docs/workflows.md +172 -2
- package/examples/extensions/subagent/index.ts +2 -1
- package/package.json +6 -6
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/SKILL.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/element-attributes.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/playwright-tests.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/request-mocking.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/running-code.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/session-management.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/spec-driven-testing.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/storage-state.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/test-generation.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/tracing.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/playwright-cli/references/video-recording.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/SKILL.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/deep-modules.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/interface-design.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/mocking.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/refactoring.md +0 -0
- /package/dist/builtin/{workflows → subagents}/skills/tdd/tests.md +0 -0
|
@@ -40,23 +40,14 @@
|
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
42
|
import {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
FooterComponent,
|
|
46
|
-
ScrollableComponentViewport,
|
|
47
|
-
SessionManager,
|
|
48
|
-
LiveChatEntriesController,
|
|
49
|
-
UsageMeterComponent,
|
|
50
|
-
WorkingStatusComponent,
|
|
51
|
-
pickWhimsicalWorkingMessage,
|
|
52
|
-
renderChatMessageEntry,
|
|
43
|
+
ChatSessionHost,
|
|
44
|
+
type ChatSessionHostStyle,
|
|
53
45
|
type AgentSession,
|
|
54
|
-
type AgentSessionEvent,
|
|
55
46
|
type ChatMessageEntry,
|
|
56
47
|
type ChatMessageRenderOptions,
|
|
57
48
|
type ReadonlyFooterDataProvider,
|
|
58
49
|
} from "@bastani/atomic";
|
|
59
|
-
import { Box,
|
|
50
|
+
import { Box, Text } from "@earendil-works/pi-tui";
|
|
60
51
|
import type {
|
|
61
52
|
Component,
|
|
62
53
|
EditorComponent,
|
|
@@ -65,12 +56,23 @@ import type {
|
|
|
65
56
|
TUI,
|
|
66
57
|
} from "@earendil-works/pi-tui";
|
|
67
58
|
import type { Store } from "../shared/store.js";
|
|
59
|
+
import {
|
|
60
|
+
mountStageCustomUi,
|
|
61
|
+
stageUiBroker,
|
|
62
|
+
type MountedStageCustomUi,
|
|
63
|
+
type StageCustomUiRequest,
|
|
64
|
+
type StageUiBroker,
|
|
65
|
+
} from "../shared/stage-ui-broker.js";
|
|
68
66
|
import type { StageNotice, StageSnapshot } from "../shared/store-types.js";
|
|
69
|
-
import { elapsedStageMs } from "../shared/timing.js";
|
|
70
67
|
import type { GraphTheme } from "./graph-theme.js";
|
|
71
68
|
import type { StageControlHandle } from "../runs/foreground/stage-control-registry.js";
|
|
72
69
|
import { BOLD, RESET, hexBg, hexToAnsi, lerpColor } from "./color-utils.js";
|
|
73
|
-
import {
|
|
70
|
+
import { Key, matchesKey, visibleWidth } from "./text-helpers.js";
|
|
71
|
+
import {
|
|
72
|
+
fitStageChatFrame,
|
|
73
|
+
planStageChatFrame,
|
|
74
|
+
resolveStageChatViewportRows,
|
|
75
|
+
} from "./stage-chat-layout.js";
|
|
74
76
|
|
|
75
77
|
// ---------------------------------------------------------------------------
|
|
76
78
|
// Options & types
|
|
@@ -96,6 +98,7 @@ export interface StageChatViewOpts {
|
|
|
96
98
|
requestRender?: () => void;
|
|
97
99
|
/** Live pi-tui host objects. When present, stage input uses pi's editor UI. */
|
|
98
100
|
piTui?: TUI;
|
|
101
|
+
piTheme?: unknown;
|
|
99
102
|
piKeybindings?: unknown;
|
|
100
103
|
/** Currently installed host editor factory, inherited from extension `ctx.ui.setEditorComponent()`. */
|
|
101
104
|
piEditorFactory?: (
|
|
@@ -105,7 +108,7 @@ export interface StageChatViewOpts {
|
|
|
105
108
|
) => EditorComponent;
|
|
106
109
|
/** Parent chat rendering settings and extension renderers inherited from the host UI. */
|
|
107
110
|
getChatRenderSettings?: () =>
|
|
108
|
-
| Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd"
|
|
111
|
+
| Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd">>
|
|
109
112
|
| undefined;
|
|
110
113
|
/** Parent footer data provider inherited from the host UI for core footer/usage rendering. */
|
|
111
114
|
footerData?: ReadonlyFooterDataProvider;
|
|
@@ -117,6 +120,8 @@ export interface StageChatViewOpts {
|
|
|
117
120
|
* Returning `undefined` falls back to the constant 32-row frame.
|
|
118
121
|
*/
|
|
119
122
|
getViewportRows?: () => number | undefined;
|
|
123
|
+
/** Broker that routes stage-local custom UI, such as ask_user_question, into this node. */
|
|
124
|
+
stageUiBroker?: StageUiBroker;
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
/**
|
|
@@ -147,18 +152,10 @@ type AgentSnapshotMessage = AgentSession["messages"][number];
|
|
|
147
152
|
*/
|
|
148
153
|
const VIEW_LINE_COUNT = 32;
|
|
149
154
|
|
|
150
|
-
/** Header strip —
|
|
155
|
+
/** Header strip — ` STAGE wf / stage <meta> ● status` without a leading marker glyph. */
|
|
151
156
|
const HEADER_ROWS = 1;
|
|
152
157
|
/** Single dim rule between header and body. */
|
|
153
158
|
const SEP_ROWS = 1;
|
|
154
|
-
/** Spinner glyphs — Braille spinner at 80ms per frame. */
|
|
155
|
-
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
156
|
-
/** Pi's Loader advances at 80ms; use the same cadence for embedded stage chats. */
|
|
157
|
-
const ANIMATION_FRAME_MS = 80;
|
|
158
|
-
const STREAMING_RENDER_THROTTLE_MS = 80;
|
|
159
|
-
const STREAMING_TEXT_TAIL_LINES = 240;
|
|
160
|
-
const STREAMING_TEXT_TAIL_CHARS = 16_000;
|
|
161
|
-
|
|
162
159
|
const ITALIC = "\x1b[3m";
|
|
163
160
|
const FG_RESET = "\x1b[39m";
|
|
164
161
|
const WEIGHT_RESET = "\x1b[22m";
|
|
@@ -180,41 +177,25 @@ export class StageChatView implements Component, Focusable {
|
|
|
180
177
|
private onClose: () => void;
|
|
181
178
|
private requestRender: (() => void) | undefined;
|
|
182
179
|
private getViewportRows?: () => number | undefined;
|
|
183
|
-
private
|
|
180
|
+
private piTui?: TUI;
|
|
181
|
+
private piTheme?: unknown;
|
|
182
|
+
private piKeybindings?: unknown;
|
|
183
|
+
private chatHost: ChatSessionHost<NoticeEntry>;
|
|
184
|
+
private stageUiBroker: StageUiBroker;
|
|
185
|
+
private mountedCustomUi: MountedStageCustomUi | null = null;
|
|
184
186
|
private getChatRenderSettings?: () =>
|
|
185
|
-
| Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd"
|
|
187
|
+
| Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd">>
|
|
186
188
|
| undefined;
|
|
187
189
|
private footerData?: ReadonlyFooterDataProvider;
|
|
188
190
|
|
|
189
|
-
private inputBuffer = "";
|
|
190
|
-
private transcript: TranscriptEntry[] = [];
|
|
191
|
-
private statusMessage = "";
|
|
192
191
|
/** True while a pending pause request is in flight (between ctrl+p and resolve). */
|
|
193
192
|
private localPaused = false;
|
|
194
193
|
/** De-dup set so the store subscription doesn't re-append known notices. */
|
|
195
194
|
private seenNoticeIds = new Set<string>();
|
|
196
|
-
/** Wall-clock at construction, used to colour the spinner frame stably. */
|
|
197
|
-
private attachedAt = Date.now();
|
|
198
|
-
/** True after SDK `agent_start` until `agent_end`; mirrors Pi's working-loader lifecycle. */
|
|
199
|
-
private sdkBusy = false;
|
|
200
|
-
/** Pi-style per-turn working message, populated from coding-agent's message picker. */
|
|
201
|
-
private workingMessage: string | undefined;
|
|
202
|
-
/** User rows optimistically appended by this embedded editor, de-duped on SDK echo. */
|
|
203
|
-
private optimisticUserSignatures = new Set<string>();
|
|
204
|
-
/** Pending steering messages emitted by AgentSession queue updates. */
|
|
205
|
-
private pendingSteeringMessages: readonly string[] = [];
|
|
206
|
-
/** Pending follow-up messages emitted by AgentSession queue updates. */
|
|
207
|
-
private pendingFollowUpMessages: readonly string[] = [];
|
|
208
|
-
/** Chat-mode repaint driver for Pi-style loaders/spinners. */
|
|
209
|
-
private animationTimer: ReturnType<typeof setInterval> | undefined;
|
|
210
|
-
/** Coalesces high-frequency SDK deltas while the fixed overlay is streaming. */
|
|
211
|
-
private renderThrottleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
212
|
-
/** Scrollable fixed-height body viewport for attached chat history. */
|
|
213
|
-
private bodyViewport = new ScrollableComponentViewport();
|
|
214
|
-
private liveChat: LiveChatEntriesController;
|
|
215
195
|
|
|
216
196
|
private _unsubscribeStore: (() => void) | null = null;
|
|
217
197
|
private _unsubscribeHandle: (() => void) | null = null;
|
|
198
|
+
private _unregisterStageUiHost: (() => void) | null = null;
|
|
218
199
|
|
|
219
200
|
constructor(opts: StageChatViewOpts) {
|
|
220
201
|
this.store = opts.store;
|
|
@@ -227,14 +208,90 @@ export class StageChatView implements Component, Focusable {
|
|
|
227
208
|
this.onClose = opts.onClose;
|
|
228
209
|
this.requestRender = opts.requestRender;
|
|
229
210
|
this.getViewportRows = opts.getViewportRows;
|
|
211
|
+
this.piTui = opts.piTui;
|
|
212
|
+
this.piTheme = opts.piTheme;
|
|
213
|
+
this.piKeybindings = opts.piKeybindings;
|
|
230
214
|
this.getChatRenderSettings = opts.getChatRenderSettings;
|
|
231
215
|
this.footerData = opts.footerData;
|
|
232
|
-
this.
|
|
233
|
-
this.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
216
|
+
this.stageUiBroker = opts.stageUiBroker ?? stageUiBroker;
|
|
217
|
+
this.chatHost = new ChatSessionHost<NoticeEntry>({
|
|
218
|
+
style: this._chatHostStyle(),
|
|
219
|
+
commands: {
|
|
220
|
+
ensureAttached: async () => {
|
|
221
|
+
await this._liveHandle()?.ensureAttached();
|
|
222
|
+
},
|
|
223
|
+
prompt: async (text) => {
|
|
224
|
+
const handle = this._liveHandle();
|
|
225
|
+
if (!handle) throw new Error("no live handle on this stage");
|
|
226
|
+
await handle.prompt(text);
|
|
227
|
+
},
|
|
228
|
+
steer: async (text) => {
|
|
229
|
+
const handle = this._liveHandle();
|
|
230
|
+
if (!handle) throw new Error("no live handle on this stage");
|
|
231
|
+
await handle.steer(text);
|
|
232
|
+
},
|
|
233
|
+
followUp: async (text) => {
|
|
234
|
+
const handle = this._liveHandle();
|
|
235
|
+
if (!handle) throw new Error("no live handle on this stage");
|
|
236
|
+
await handle.followUp(text);
|
|
237
|
+
},
|
|
238
|
+
interrupt: async () => {
|
|
239
|
+
const handle = this._liveHandle();
|
|
240
|
+
if (!handle) return;
|
|
241
|
+
const status = this._currentStage()?.status ?? handle.status;
|
|
242
|
+
if (status === "pending" || status === "running" || status === "awaiting_input") {
|
|
243
|
+
await handle.pause();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
await handle.agentSession?.abort();
|
|
247
|
+
},
|
|
248
|
+
resume: async (message) => {
|
|
249
|
+
const handle = this._liveHandle();
|
|
250
|
+
if (!handle) throw new Error("no live handle on this stage");
|
|
251
|
+
this.localPaused = true;
|
|
252
|
+
await handle.resume(message);
|
|
253
|
+
this.localPaused = false;
|
|
254
|
+
},
|
|
255
|
+
runBash: async (request) => {
|
|
256
|
+
const handle = this._liveHandle();
|
|
257
|
+
if (!handle) throw new Error("no live handle on this stage");
|
|
258
|
+
await handle.ensureAttached();
|
|
259
|
+
const agentSession = handle.agentSession;
|
|
260
|
+
if (!agentSession) throw new Error("no live agent session on this stage");
|
|
261
|
+
return agentSession.executeBash(request.command, request.onChunk, {
|
|
262
|
+
excludeFromContext: request.excludeFromContext,
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
abortBash: async () => {
|
|
266
|
+
this._liveHandle()?.agentSession?.abortBash();
|
|
267
|
+
},
|
|
268
|
+
abortCompaction: async () => {
|
|
269
|
+
this._liveHandle()?.agentSession?.abortCompaction();
|
|
270
|
+
},
|
|
271
|
+
handleSlashCommand: async (text) => this._handleSlashCommand(text),
|
|
272
|
+
},
|
|
273
|
+
isBashRunning: () => this._liveHandle()?.agentSession?.isBashRunning === true,
|
|
274
|
+
requestRender: opts.requestRender,
|
|
275
|
+
getAgentSession: () => this._liveHandle()?.agentSession,
|
|
276
|
+
isStreaming: () => this._liveHandle()?.isStreaming === true,
|
|
277
|
+
isPaused: () => this._isPaused(),
|
|
278
|
+
isDisabled: () => this._isBlocked() || !this._liveHandle(),
|
|
279
|
+
tui: opts.piTui,
|
|
280
|
+
keybindings: opts.piKeybindings,
|
|
281
|
+
editorFactory: opts.piEditorFactory,
|
|
282
|
+
editorTheme: editorThemeFromGraphTheme(this.theme),
|
|
283
|
+
getChatRenderSettings: opts.getChatRenderSettings,
|
|
284
|
+
footerData: opts.footerData,
|
|
285
|
+
renderExtraEntry: (entry) => this._noticeRow(entry),
|
|
286
|
+
});
|
|
287
|
+
this._unregisterStageUiHost = this.stageUiBroker.registerHost(this.runId, this.stageId, {
|
|
288
|
+
showCustomUi: (request) => {
|
|
289
|
+
void this._showCustomUi(request);
|
|
290
|
+
},
|
|
291
|
+
hideCustomUi: (request) => {
|
|
292
|
+
this._hideMountedCustomUi(request);
|
|
293
|
+
},
|
|
294
|
+
});
|
|
238
295
|
|
|
239
296
|
// Seed transcript from the live SDK session at attach time, plus any
|
|
240
297
|
// stage notices the workflow body has already recorded.
|
|
@@ -257,72 +314,62 @@ export class StageChatView implements Component, Focusable {
|
|
|
257
314
|
// `stage.setModel`, `stage.compact`, …) so they thread through the
|
|
258
315
|
// transcript without a special render path.
|
|
259
316
|
changed = this._absorbStageNotices(stage) || changed;
|
|
260
|
-
this.
|
|
317
|
+
this.chatHost.syncAnimationTick();
|
|
261
318
|
if (changed) this.requestRender?.();
|
|
262
319
|
});
|
|
263
320
|
|
|
264
321
|
if (this.handle) {
|
|
265
322
|
this._unsubscribeHandle = this.handle.subscribe((event) => {
|
|
266
|
-
|
|
267
|
-
this._syncAnimationTick();
|
|
268
|
-
if (changed) this._requestEventRender();
|
|
323
|
+
this.chatHost.applyAgentEvent(event);
|
|
269
324
|
});
|
|
270
325
|
}
|
|
271
|
-
this.
|
|
326
|
+
this.chatHost.syncAnimationTick();
|
|
272
327
|
}
|
|
273
328
|
|
|
274
|
-
private
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const editorTheme = editorThemeFromGraphTheme(this.theme);
|
|
287
|
-
const editor =
|
|
288
|
-
this._createInheritedEditor(
|
|
289
|
-
tui,
|
|
290
|
-
editorTheme,
|
|
291
|
-
keybindings,
|
|
292
|
-
editorFactory,
|
|
293
|
-
) ??
|
|
294
|
-
new CustomEditor(
|
|
295
|
-
tui,
|
|
296
|
-
editorTheme,
|
|
297
|
-
keybindings as ConstructorParameters<typeof CustomEditor>[2],
|
|
298
|
-
{ paddingX: 0, autocompleteMaxVisible: 5 },
|
|
299
|
-
);
|
|
300
|
-
editor.onChange = (text) => {
|
|
301
|
-
this.inputBuffer = text;
|
|
302
|
-
};
|
|
303
|
-
editor.onSubmit = (text) => {
|
|
304
|
-
void this._submit("auto", text);
|
|
329
|
+
private _chatHostStyle(): ChatSessionHostStyle {
|
|
330
|
+
return {
|
|
331
|
+
dim: (text) => paint(text, this.theme.dim),
|
|
332
|
+
text: (text) => paint(text, this.theme.text),
|
|
333
|
+
textMuted: (text) => paint(text, this.theme.textMuted),
|
|
334
|
+
accent: (text) => paint(text, this.theme.accent),
|
|
335
|
+
accentBold: (text) => paint(text, this.theme.accent, { bold: true }),
|
|
336
|
+
rule: (hex, text) => hexToAnsi(hex) + text + RESET,
|
|
337
|
+
cursor: () => cursorBlock(),
|
|
338
|
+
blank: (width) => this._blank(width),
|
|
339
|
+
editorRuleColor: (disabled, agentSession, state) =>
|
|
340
|
+
this._editorRuleColor(disabled, agentSession, state),
|
|
305
341
|
};
|
|
306
|
-
return editor;
|
|
307
342
|
}
|
|
308
343
|
|
|
309
|
-
private
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
| undefined,
|
|
320
|
-
): EditorComponent | undefined {
|
|
321
|
-
if (!editorFactory) return undefined;
|
|
344
|
+
private async _showCustomUi(request: StageCustomUiRequest): Promise<void> {
|
|
345
|
+
this.mountedCustomUi?.component.dispose?.();
|
|
346
|
+
this.mountedCustomUi = null;
|
|
347
|
+
if (!this.piTui || this.piTheme === undefined || this.piKeybindings === undefined) {
|
|
348
|
+
this.stageUiBroker.reject(
|
|
349
|
+
request,
|
|
350
|
+
new Error("pi-workflows: stage custom UI cannot mount without attached TUI host"),
|
|
351
|
+
);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
322
354
|
try {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
355
|
+
this.mountedCustomUi = await mountStageCustomUi(
|
|
356
|
+
request,
|
|
357
|
+
this.piTui,
|
|
358
|
+
this.piTheme,
|
|
359
|
+
this.piKeybindings,
|
|
360
|
+
this.stageUiBroker,
|
|
361
|
+
() => {
|
|
362
|
+
if (this.mountedCustomUi?.request.id !== request.id) return;
|
|
363
|
+
this.mountedCustomUi.component.dispose?.();
|
|
364
|
+
this.mountedCustomUi = null;
|
|
365
|
+
this.chatHost.focused = this.focused;
|
|
366
|
+
this.chatHost.scrollToBottom();
|
|
367
|
+
this.requestRender?.();
|
|
368
|
+
},
|
|
369
|
+
);
|
|
370
|
+
this.requestRender?.();
|
|
371
|
+
} catch (error) {
|
|
372
|
+
this.stageUiBroker.reject(request, error);
|
|
326
373
|
}
|
|
327
374
|
}
|
|
328
375
|
|
|
@@ -331,115 +378,15 @@ export class StageChatView implements Component, Focusable {
|
|
|
331
378
|
// -------------------------------------------------------------------------
|
|
332
379
|
|
|
333
380
|
private _snapshotMessagesFromHandle(): void {
|
|
334
|
-
|
|
335
|
-
|
|
381
|
+
const handle = this._liveHandle();
|
|
382
|
+
if (!handle) return;
|
|
383
|
+
this.chatHost.appendMessages(handle.messages);
|
|
336
384
|
}
|
|
337
385
|
|
|
338
386
|
private _snapshotMessagesFromSessionFile(
|
|
339
387
|
stage: StageSnapshot | undefined,
|
|
340
388
|
): void {
|
|
341
|
-
|
|
342
|
-
const sessionFile = this.handle?.sessionFile ?? stage?.sessionFile;
|
|
343
|
-
if (sessionFile === undefined) return;
|
|
344
|
-
|
|
345
|
-
let messages: readonly AgentSnapshotMessage[];
|
|
346
|
-
try {
|
|
347
|
-
messages = SessionManager.open(sessionFile).buildSessionContext()
|
|
348
|
-
.messages as readonly AgentSnapshotMessage[];
|
|
349
|
-
} catch {
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
this.liveChat.appendMessages(messages);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
private _appendEvent(event: AgentSessionEvent): boolean {
|
|
357
|
-
// Shared live transcript ingestion covers assistant/user/custom messages
|
|
358
|
-
// and tool start/update/end rows. StageChatView keeps workflow-only status
|
|
359
|
-
// events (pause, compaction captions, animation state) locally.
|
|
360
|
-
const type = String((event as { type?: unknown }).type ?? "");
|
|
361
|
-
if (type === "message_start") {
|
|
362
|
-
const message = (event as { message?: unknown }).message;
|
|
363
|
-
if (isUserMessageLike(message)) {
|
|
364
|
-
const signature = userMessageSignature(
|
|
365
|
-
extractMessageText(message.content),
|
|
366
|
-
);
|
|
367
|
-
if (this.optimisticUserSignatures.delete(signature)) return false;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
if (isSharedLiveChatEvent(type)) {
|
|
371
|
-
const changed = this.liveChat.applyEvent(event);
|
|
372
|
-
const toolCallEvent = assistantToolCallEvent(event);
|
|
373
|
-
const changedByToolCall = toolCallEvent !== undefined
|
|
374
|
-
? this.liveChat.applyEvent(toolCallEvent)
|
|
375
|
-
: false;
|
|
376
|
-
return changed || changedByToolCall;
|
|
377
|
-
}
|
|
378
|
-
switch (type) {
|
|
379
|
-
case "agent_start":
|
|
380
|
-
this.sdkBusy = true;
|
|
381
|
-
this.liveChat.clearPendingTools();
|
|
382
|
-
this.statusMessage = "";
|
|
383
|
-
return true;
|
|
384
|
-
|
|
385
|
-
case "agent_end":
|
|
386
|
-
this.sdkBusy = false;
|
|
387
|
-
this.workingMessage = undefined;
|
|
388
|
-
this.liveChat.clearPendingTools();
|
|
389
|
-
this.statusMessage = "";
|
|
390
|
-
return true;
|
|
391
|
-
|
|
392
|
-
case "turn_start":
|
|
393
|
-
this.workingMessage = pickWhimsicalWorkingMessage();
|
|
394
|
-
return true;
|
|
395
|
-
|
|
396
|
-
case "turn_end":
|
|
397
|
-
this.workingMessage = undefined;
|
|
398
|
-
return true;
|
|
399
|
-
|
|
400
|
-
case "queue_update": {
|
|
401
|
-
const queue = event as Extract<AgentSessionEvent, { type: "queue_update" }>;
|
|
402
|
-
this.pendingSteeringMessages = queue.steering;
|
|
403
|
-
this.pendingFollowUpMessages = queue.followUp;
|
|
404
|
-
return true;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Compatibility with older/headless shims that predate the SDK's
|
|
408
|
-
// tool_execution_* events. Project these shims into coding-agent's live
|
|
409
|
-
// controller rather than maintaining a second workflow tool renderer.
|
|
410
|
-
case "tool_call":
|
|
411
|
-
case "tool_use":
|
|
412
|
-
return this.liveChat.applyEvent(legacyToolStartEvent(event));
|
|
413
|
-
|
|
414
|
-
case "tool_result":
|
|
415
|
-
return this.liveChat.applyEvent(legacyToolResultEvent(event));
|
|
416
|
-
|
|
417
|
-
case "thinking_delta":
|
|
418
|
-
case "thinking":
|
|
419
|
-
return this.liveChat.applyEvent(legacyThinkingEvent(event));
|
|
420
|
-
|
|
421
|
-
case "compaction_start":
|
|
422
|
-
this.sdkBusy = true;
|
|
423
|
-
this.statusMessage = "compacting context…";
|
|
424
|
-
return true;
|
|
425
|
-
|
|
426
|
-
case "compaction_end":
|
|
427
|
-
this.sdkBusy = false;
|
|
428
|
-
this.statusMessage = "";
|
|
429
|
-
return true;
|
|
430
|
-
|
|
431
|
-
case "auto_retry_start":
|
|
432
|
-
this.sdkBusy = true;
|
|
433
|
-
this.statusMessage = "retrying…";
|
|
434
|
-
return true;
|
|
435
|
-
|
|
436
|
-
case "auto_retry_end":
|
|
437
|
-
this.statusMessage = "";
|
|
438
|
-
return true;
|
|
439
|
-
|
|
440
|
-
default:
|
|
441
|
-
return false;
|
|
442
|
-
}
|
|
389
|
+
this.chatHost.loadSessionFile(this._liveHandle()?.sessionFile ?? stage?.sessionFile);
|
|
443
390
|
}
|
|
444
391
|
|
|
445
392
|
private _absorbStageNotices(stage: StageSnapshot | undefined): boolean {
|
|
@@ -450,7 +397,7 @@ export class StageChatView implements Component, Focusable {
|
|
|
450
397
|
if (this.seenNoticeIds.has(n.id)) continue;
|
|
451
398
|
this.seenNoticeIds.add(n.id);
|
|
452
399
|
changed = true;
|
|
453
|
-
this.
|
|
400
|
+
this.chatHost.appendExtraEntry({
|
|
454
401
|
role: "notice",
|
|
455
402
|
noticeId: n.id,
|
|
456
403
|
kind: n.kind,
|
|
@@ -484,44 +431,15 @@ export class StageChatView implements Component, Focusable {
|
|
|
484
431
|
if (typeof reported !== "number" || !Number.isFinite(reported)) {
|
|
485
432
|
return VIEW_LINE_COUNT;
|
|
486
433
|
}
|
|
487
|
-
return
|
|
434
|
+
return resolveStageChatViewportRows(reported, VIEW_LINE_COUNT);
|
|
488
435
|
}
|
|
489
436
|
|
|
490
|
-
private
|
|
491
|
-
return this.
|
|
437
|
+
private _liveHandle(): StageControlHandle | undefined {
|
|
438
|
+
return this.handle?.isDisposed === true ? undefined : this.handle;
|
|
492
439
|
}
|
|
493
440
|
|
|
494
|
-
private
|
|
495
|
-
return this.
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
private _syncAnimationTick(): void {
|
|
499
|
-
const shouldAnimate =
|
|
500
|
-
this._isStreaming() || (this.sdkBusy && this._hasPendingToolEntries());
|
|
501
|
-
if (shouldAnimate && !this.animationTimer) {
|
|
502
|
-
this.animationTimer = setInterval(() => {
|
|
503
|
-
this.requestRender?.();
|
|
504
|
-
}, ANIMATION_FRAME_MS);
|
|
505
|
-
this.animationTimer.unref?.();
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
if (!shouldAnimate && this.animationTimer) {
|
|
509
|
-
clearInterval(this.animationTimer);
|
|
510
|
-
this.animationTimer = undefined;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
private _requestEventRender(): void {
|
|
515
|
-
if (!this._isStreaming()) {
|
|
516
|
-
this.requestRender?.();
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
if (this.renderThrottleTimer) return;
|
|
520
|
-
this.renderThrottleTimer = setTimeout(() => {
|
|
521
|
-
this.renderThrottleTimer = undefined;
|
|
522
|
-
this.requestRender?.();
|
|
523
|
-
}, STREAMING_RENDER_THROTTLE_MS);
|
|
524
|
-
this.renderThrottleTimer.unref?.();
|
|
441
|
+
private _isStreaming(): boolean {
|
|
442
|
+
return this.chatHost.isStreaming();
|
|
525
443
|
}
|
|
526
444
|
|
|
527
445
|
private _isBlocked(): boolean {
|
|
@@ -534,6 +452,32 @@ export class StageChatView implements Component, Focusable {
|
|
|
534
452
|
return this.localPaused || stage?.status === "paused";
|
|
535
453
|
}
|
|
536
454
|
|
|
455
|
+
private _isReadOnlyArchive(stage: StageSnapshot | undefined = this._currentStage()): boolean {
|
|
456
|
+
if (this._liveHandle()) return false;
|
|
457
|
+
if (!stage) return true;
|
|
458
|
+
return stage.status === "completed" || stage.status === "failed" || Boolean(stage.sessionFile);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private async _handleSlashCommand(text: string): Promise<boolean> {
|
|
462
|
+
const [command, ...rest] = text.trim().split(/\s+/);
|
|
463
|
+
switch (command) {
|
|
464
|
+
case "/compact": {
|
|
465
|
+
const handle = this._liveHandle();
|
|
466
|
+
if (!handle) return false;
|
|
467
|
+
await handle.ensureAttached();
|
|
468
|
+
if (!handle.agentSession) return false;
|
|
469
|
+
await handle.agentSession.compact(rest.join(" ") || undefined);
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
case "/quit":
|
|
473
|
+
case "/exit":
|
|
474
|
+
this.onClose();
|
|
475
|
+
return true;
|
|
476
|
+
default:
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
537
481
|
// -------------------------------------------------------------------------
|
|
538
482
|
// Top-level render — composes header / body / usage / editor / footer
|
|
539
483
|
// -------------------------------------------------------------------------
|
|
@@ -542,44 +486,54 @@ export class StageChatView implements Component, Focusable {
|
|
|
542
486
|
const w = Math.max(40, width);
|
|
543
487
|
const stage = this._currentStage();
|
|
544
488
|
const blocked = this._isBlocked();
|
|
545
|
-
const streaming = this._isStreaming() && !blocked;
|
|
546
489
|
|
|
490
|
+
this.chatHost.focused = this.focused;
|
|
547
491
|
const headerLines = this._renderHeader(w, stage);
|
|
548
492
|
const sepLines = [this._sepRule(w)];
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
const
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
SEP_ROWS +
|
|
558
|
-
pendingLines.length +
|
|
559
|
-
workingLines.length +
|
|
560
|
-
usageLines.length +
|
|
561
|
-
editorLines.length +
|
|
562
|
-
footerLines.length;
|
|
493
|
+
const customUiActive = this.mountedCustomUi !== null;
|
|
494
|
+
const readOnlyArchive = this._isReadOnlyArchive(stage);
|
|
495
|
+
const pendingLines = customUiActive || readOnlyArchive ? [] : this.chatHost.renderPendingMessages(w);
|
|
496
|
+
const workingLines = customUiActive || readOnlyArchive ? [] : this.chatHost.renderWorkingStatus(w);
|
|
497
|
+
const usageLines = customUiActive || readOnlyArchive ? [] : this.chatHost.renderUsage(w);
|
|
498
|
+
const editorLines = customUiActive || readOnlyArchive ? [] : this.chatHost.renderEditor(w);
|
|
499
|
+
const footerLines = customUiActive || readOnlyArchive ? [] : this.chatHost.renderFooter(w);
|
|
500
|
+
|
|
563
501
|
const totalRows = this._viewLineCount();
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
:
|
|
502
|
+
const plan = planStageChatFrame({
|
|
503
|
+
viewportRows: totalRows,
|
|
504
|
+
headerRows: HEADER_ROWS,
|
|
505
|
+
separatorRows: SEP_ROWS,
|
|
506
|
+
pendingRows: pendingLines.length,
|
|
507
|
+
workingRows: workingLines.length,
|
|
508
|
+
usageRows: usageLines.length,
|
|
509
|
+
editorRows: editorLines.length,
|
|
510
|
+
footerRows: footerLines.length,
|
|
511
|
+
});
|
|
512
|
+
const visiblePendingLines = pendingLines.slice(0, plan.pendingRows);
|
|
513
|
+
const visibleWorkingLines = workingLines.slice(0, plan.workingRows);
|
|
514
|
+
const visibleUsageLines = usageLines.slice(0, plan.usageRows);
|
|
515
|
+
const visibleEditorLines = editorLines.slice(0, plan.editorRows);
|
|
516
|
+
const visibleFooterLines = footerLines.slice(0, plan.footerRows);
|
|
517
|
+
const bodyBudget = plan.bodyRows;
|
|
518
|
+
if (blocked) this.chatHost.scrollToBottom();
|
|
519
|
+
const bodyLines = customUiActive
|
|
520
|
+
? this._renderCustomUiBody(w, bodyBudget)
|
|
521
|
+
: blocked
|
|
522
|
+
? this._renderBlockedBody(w, bodyBudget, stage)
|
|
523
|
+
: readOnlyArchive
|
|
524
|
+
? this._renderReadOnlyArchiveBody(w, bodyBudget, stage)
|
|
525
|
+
: this.chatHost.renderBody(w, bodyBudget);
|
|
570
526
|
const lines = [
|
|
571
527
|
...headerLines,
|
|
572
528
|
...sepLines,
|
|
573
529
|
...bodyLines,
|
|
574
|
-
...
|
|
575
|
-
...
|
|
576
|
-
...
|
|
577
|
-
...
|
|
578
|
-
...
|
|
530
|
+
...visiblePendingLines,
|
|
531
|
+
...visibleWorkingLines,
|
|
532
|
+
...visibleUsageLines,
|
|
533
|
+
...visibleEditorLines,
|
|
534
|
+
...visibleFooterLines,
|
|
579
535
|
];
|
|
580
|
-
|
|
581
|
-
if (lines.length > totalRows) lines.length = totalRows;
|
|
582
|
-
return lines;
|
|
536
|
+
return fitStageChatFrame(lines, totalRows, this._blank(w));
|
|
583
537
|
}
|
|
584
538
|
|
|
585
539
|
// -------------------------------------------------------------------------
|
|
@@ -592,85 +546,39 @@ export class StageChatView implements Component, Focusable {
|
|
|
592
546
|
): string[] {
|
|
593
547
|
const t = this.theme;
|
|
594
548
|
const stageName = stage?.name ?? "stage";
|
|
595
|
-
const status = stage?.status ?? (this.handle ? "pending" : "completed");
|
|
596
549
|
|
|
597
|
-
// Left side:
|
|
550
|
+
// Left side: ` STAGE <wf> / <stage>`
|
|
598
551
|
const left =
|
|
599
|
-
paint("
|
|
552
|
+
paint(" ", t.mauve, { bold: true }) +
|
|
600
553
|
paint("STAGE", t.textMuted, { bold: true }) +
|
|
601
554
|
" " +
|
|
602
555
|
paint(this.workflowName, t.textMuted) +
|
|
603
556
|
paint(" / ", t.dim) +
|
|
604
557
|
paint(stageName, t.text, { bold: true });
|
|
605
558
|
|
|
606
|
-
// Right side:
|
|
559
|
+
// Right side: stable session metadata only. Avoid workflow-status chrome
|
|
560
|
+
// in the embedded chat so the surface does not change colour when the
|
|
561
|
+
// workflow stage settles.
|
|
607
562
|
const meta = this._headerMeta(stage);
|
|
608
|
-
const
|
|
609
|
-
const right = (meta ? paint(meta, t.dim) + " " : "") + pill.styled + " ";
|
|
563
|
+
const right = meta ? paint(meta, t.dim) + " " : "";
|
|
610
564
|
|
|
611
565
|
const leftW =
|
|
612
566
|
visibleWidth(this.workflowName) +
|
|
613
567
|
visibleWidth(stageName) +
|
|
614
568
|
visibleWidth(" STAGE / ") +
|
|
615
569
|
1;
|
|
616
|
-
const rightW = visibleWidth(meta) + (meta ?
|
|
570
|
+
const rightW = visibleWidth(meta) + (meta ? 1 : 0);
|
|
617
571
|
const gap = Math.max(1, width - leftW - rightW);
|
|
618
572
|
return [left + " ".repeat(gap) + right];
|
|
619
573
|
}
|
|
620
574
|
|
|
621
575
|
private _headerMeta(stage: StageSnapshot | undefined): string {
|
|
622
576
|
const parts: string[] = [];
|
|
623
|
-
if (stage) {
|
|
624
|
-
const dur = stageDurationText(stage);
|
|
625
|
-
if (dur) parts.push(dur);
|
|
626
|
-
}
|
|
627
577
|
const sid = this.handle?.sessionId ?? stage?.sessionId;
|
|
628
578
|
if (sid) parts.push(`session ${shortenId(sid)}`);
|
|
629
579
|
return parts.join(" · ");
|
|
630
580
|
}
|
|
631
581
|
|
|
632
|
-
/**
|
|
633
|
-
* Render an inline ` ● status ` pill with the status colour applied to a
|
|
634
|
-
* tinted background. Matches the mockup's `.status-pill` vocabulary.
|
|
635
|
-
*/
|
|
636
|
-
private _statusPill(status: string): { styled: string; width: number } {
|
|
637
|
-
const t = this.theme;
|
|
638
|
-
const map: Record<string, { fg: string; bg: string; label: string }> = {
|
|
639
|
-
pending: { fg: t.dim, bg: blendBg(t.bg, t.dim, 0.18), label: "pending" },
|
|
640
|
-
running: {
|
|
641
|
-
fg: t.accent,
|
|
642
|
-
bg: blendBg(t.bg, t.accent, 0.18),
|
|
643
|
-
label: "running",
|
|
644
|
-
},
|
|
645
|
-
paused: {
|
|
646
|
-
fg: t.warning,
|
|
647
|
-
bg: blendBg(t.bg, t.warning, 0.18),
|
|
648
|
-
label: "paused",
|
|
649
|
-
},
|
|
650
|
-
blocked: {
|
|
651
|
-
fg: t.warning,
|
|
652
|
-
bg: blendBg(t.bg, t.warning, 0.18),
|
|
653
|
-
label: "blocked",
|
|
654
|
-
},
|
|
655
|
-
completed: {
|
|
656
|
-
fg: t.success,
|
|
657
|
-
bg: blendBg(t.bg, t.success, 0.18),
|
|
658
|
-
label: "completed",
|
|
659
|
-
},
|
|
660
|
-
failed: {
|
|
661
|
-
fg: t.error,
|
|
662
|
-
bg: blendBg(t.bg, t.error, 0.18),
|
|
663
|
-
label: "failed",
|
|
664
|
-
},
|
|
665
|
-
};
|
|
666
|
-
const cfg = map[status] ?? map.pending!;
|
|
667
|
-
const body = ` ● ${cfg.label} `;
|
|
668
|
-
return {
|
|
669
|
-
styled: hexBg(cfg.bg) + hexToAnsi(cfg.fg) + BOLD + body + RESET,
|
|
670
|
-
width: visibleWidth(body),
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
|
|
674
582
|
private _sepRule(width: number): string {
|
|
675
583
|
return hexToAnsi(this.theme.borderDim) + "─".repeat(width) + RESET;
|
|
676
584
|
}
|
|
@@ -679,6 +587,50 @@ export class StageChatView implements Component, Focusable {
|
|
|
679
587
|
// Body — welcome panel / banner + transcript / blocked
|
|
680
588
|
// -------------------------------------------------------------------------
|
|
681
589
|
|
|
590
|
+
private _renderReadOnlyArchiveBody(
|
|
591
|
+
width: number,
|
|
592
|
+
budget: number,
|
|
593
|
+
stage: StageSnapshot | undefined,
|
|
594
|
+
): string[] {
|
|
595
|
+
const t = this.theme;
|
|
596
|
+
const calloutRows = 6;
|
|
597
|
+
const transcriptBudget = Math.max(1, budget - calloutRows);
|
|
598
|
+
const lines = this.chatHost.renderBody(width, transcriptBudget);
|
|
599
|
+
const callout: string[] = [];
|
|
600
|
+
callout.push(this._blank(width));
|
|
601
|
+
callout.push(
|
|
602
|
+
...this._bannerLines(
|
|
603
|
+
width,
|
|
604
|
+
"info",
|
|
605
|
+
"◌",
|
|
606
|
+
"READ-ONLY SESSION",
|
|
607
|
+
stage?.sessionFile ? "archived transcript" : "no live chat session",
|
|
608
|
+
),
|
|
609
|
+
);
|
|
610
|
+
callout.push(
|
|
611
|
+
...new Text(
|
|
612
|
+
paint("This node is no longer attached to a live chat session.", t.textMuted),
|
|
613
|
+
2,
|
|
614
|
+
0,
|
|
615
|
+
).render(width),
|
|
616
|
+
);
|
|
617
|
+
callout.push(
|
|
618
|
+
...new Text(
|
|
619
|
+
paint("esc", t.accent, { bold: true }) +
|
|
620
|
+
paint(" close", t.textMuted) +
|
|
621
|
+
paint(" · ", t.dim) +
|
|
622
|
+
paint("ctrl+d", t.accent, { bold: true }) +
|
|
623
|
+
paint(" return to graph", t.textMuted),
|
|
624
|
+
2,
|
|
625
|
+
0,
|
|
626
|
+
).render(width),
|
|
627
|
+
);
|
|
628
|
+
lines.push(...callout);
|
|
629
|
+
while (lines.length < budget) lines.push(this._blank(width));
|
|
630
|
+
if (lines.length > budget) lines.length = budget;
|
|
631
|
+
return lines;
|
|
632
|
+
}
|
|
633
|
+
|
|
682
634
|
private _renderBlockedBody(
|
|
683
635
|
width: number,
|
|
684
636
|
budget: number,
|
|
@@ -721,87 +673,13 @@ export class StageChatView implements Component, Focusable {
|
|
|
721
673
|
return lines;
|
|
722
674
|
}
|
|
723
675
|
|
|
724
|
-
private
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
// spacing and coding-agent message widgets as the main interactive chat.
|
|
732
|
-
if (this.transcript.length > 0) {
|
|
733
|
-
components.push(
|
|
734
|
-
new ChatTranscriptComponent(this.transcript, (entry) =>
|
|
735
|
-
this._renderEntry(entry),
|
|
736
|
-
),
|
|
737
|
-
);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// Stream a static status message (e.g. "pausing…") as a dim trailing row.
|
|
741
|
-
if (this.statusMessage) {
|
|
742
|
-
components.push(new Spacer(1));
|
|
743
|
-
components.push(
|
|
744
|
-
new Text(paint(this.statusMessage, this.theme.dim), 2, 0),
|
|
745
|
-
);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
this.bodyViewport.setComponents(components);
|
|
749
|
-
return this.bodyViewport.render(width);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
// -------------------------------------------------------------------------
|
|
753
|
-
// Transcript entry → pi/coding-agent Component. Stage chat deliberately uses
|
|
754
|
-
// the same exported message/tool components as the main interactive chat
|
|
755
|
-
// instead of maintaining parallel workflow-specific bubbles.
|
|
756
|
-
// -------------------------------------------------------------------------
|
|
757
|
-
|
|
758
|
-
private _renderEntry(entry: TranscriptEntry): Component {
|
|
759
|
-
if (isChatMessageEntry(entry)) {
|
|
760
|
-
return renderChatMessageEntry(
|
|
761
|
-
this._streamingWindowedEntry(entry),
|
|
762
|
-
this._chatMessageRenderOptions(),
|
|
763
|
-
);
|
|
764
|
-
}
|
|
765
|
-
return this._noticeRow(entry);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
private _streamingWindowedEntry(entry: ChatMessageEntry): ChatMessageEntry {
|
|
769
|
-
if (!this._isStreaming() || this.bodyViewport.getScrollFromBottom() !== 0) {
|
|
770
|
-
return entry;
|
|
771
|
-
}
|
|
772
|
-
if (entry.kind !== "assistant") return entry;
|
|
773
|
-
const content = entry.message.content.map((item) => {
|
|
774
|
-
if (item.type === "text") {
|
|
775
|
-
return { ...item, text: tailStreamingText(item.text) };
|
|
776
|
-
}
|
|
777
|
-
if (item.type === "thinking") {
|
|
778
|
-
return { ...item, thinking: tailStreamingText(item.thinking) };
|
|
779
|
-
}
|
|
780
|
-
return item;
|
|
781
|
-
});
|
|
782
|
-
return {
|
|
783
|
-
...entry,
|
|
784
|
-
message: {
|
|
785
|
-
...entry.message,
|
|
786
|
-
content,
|
|
787
|
-
},
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
private _chatMessageRenderOptions(): ChatMessageRenderOptions {
|
|
792
|
-
const inherited = this.getChatRenderSettings?.();
|
|
793
|
-
return {
|
|
794
|
-
...inherited,
|
|
795
|
-
ui: this._toolTui(),
|
|
796
|
-
cwd: this.handle?.agentSession?.sessionManager.getCwd() ?? process.cwd(),
|
|
797
|
-
showImages: inherited?.showImages ?? true,
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
private _toolTui(): TUI {
|
|
802
|
-
return {
|
|
803
|
-
requestRender: () => this.requestRender?.(),
|
|
804
|
-
} as TUI;
|
|
676
|
+
private _renderCustomUiBody(width: number, budget: number): string[] {
|
|
677
|
+
const component = this.mountedCustomUi?.component;
|
|
678
|
+
if (component) setComponentFocused(component, this.focused);
|
|
679
|
+
const lines = component ? component.render(width) : [];
|
|
680
|
+
const framed = lines.slice(0, budget);
|
|
681
|
+
while (framed.length < budget) framed.push(this._blank(width));
|
|
682
|
+
return framed;
|
|
805
683
|
}
|
|
806
684
|
|
|
807
685
|
private _noticeRow(entry: NoticeEntry): Component {
|
|
@@ -823,14 +701,20 @@ export class StageChatView implements Component, Focusable {
|
|
|
823
701
|
// -------------------------------------------------------------------------
|
|
824
702
|
|
|
825
703
|
private _banner(
|
|
826
|
-
kind: "warning" | "success" | "error",
|
|
704
|
+
kind: "warning" | "success" | "error" | "info",
|
|
827
705
|
glyph: string,
|
|
828
706
|
label: string,
|
|
829
707
|
meta: string,
|
|
830
708
|
): Component {
|
|
831
709
|
const t = this.theme;
|
|
832
710
|
const fg =
|
|
833
|
-
kind === "warning"
|
|
711
|
+
kind === "warning"
|
|
712
|
+
? t.warning
|
|
713
|
+
: kind === "success"
|
|
714
|
+
? t.success
|
|
715
|
+
: kind === "info"
|
|
716
|
+
? t.info
|
|
717
|
+
: t.error;
|
|
834
718
|
const bg = blendBg(t.bg, fg, 0.1);
|
|
835
719
|
const head =
|
|
836
720
|
paintOnFill(glyph, fg, { bold: true }) +
|
|
@@ -849,7 +733,7 @@ export class StageChatView implements Component, Focusable {
|
|
|
849
733
|
*/
|
|
850
734
|
private _bannerLines(
|
|
851
735
|
width: number,
|
|
852
|
-
kind: "warning" | "success" | "error",
|
|
736
|
+
kind: "warning" | "success" | "error" | "info",
|
|
853
737
|
glyph: string,
|
|
854
738
|
label: string,
|
|
855
739
|
meta: string,
|
|
@@ -857,42 +741,14 @@ export class StageChatView implements Component, Focusable {
|
|
|
857
741
|
return this._banner(kind, glyph, label, meta).render(width);
|
|
858
742
|
}
|
|
859
743
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const t = this.theme;
|
|
866
|
-
// Disabled only when no live chat handle exists or workflow dependencies
|
|
867
|
-
// are blocked. A settled attached stage remains a regular chat session.
|
|
868
|
-
const disabled = blocked || !this.handle;
|
|
869
|
-
const ruleHex = this._editorRuleColor(disabled);
|
|
870
|
-
if (!disabled && this.editor) {
|
|
871
|
-
setEditorFocused(this.editor, this.focused);
|
|
872
|
-
setEditorPlaceholder(this.editor, undefined);
|
|
873
|
-
setEditorBorderColor(this.editor, ruleHex);
|
|
874
|
-
return this.editor.render(width);
|
|
875
|
-
}
|
|
876
|
-
if (this.editor) setEditorFocused(this.editor, false);
|
|
877
|
-
const rule = hexToAnsi(ruleHex) + "─".repeat(width) + RESET;
|
|
878
|
-
|
|
879
|
-
const glyphHex = disabled ? t.dim : t.accent;
|
|
880
|
-
const available = Math.max(1, width - 3);
|
|
881
|
-
const value = this.inputBuffer
|
|
882
|
-
? paint(truncateToWidth(this.inputBuffer, available), t.text) + cursorBlock()
|
|
883
|
-
: disabled
|
|
884
|
-
? ""
|
|
885
|
-
: cursorBlock();
|
|
886
|
-
|
|
887
|
-
const left = paint("❯", glyphHex, { bold: true }) + " " + value;
|
|
888
|
-
const gap = Math.max(0, width - visibleWidth(stripAnsi(left)));
|
|
889
|
-
const body = left + " ".repeat(gap);
|
|
890
|
-
return [rule, body, rule];
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
private _editorRuleColor(disabled: boolean): string {
|
|
744
|
+
private _editorRuleColor(
|
|
745
|
+
disabled: boolean,
|
|
746
|
+
agentSession: AgentSession | undefined,
|
|
747
|
+
state?: { isBashMode: boolean },
|
|
748
|
+
): string {
|
|
894
749
|
if (disabled) return this.theme.borderDim;
|
|
895
|
-
|
|
750
|
+
if (state?.isBashMode) return this.theme.warning;
|
|
751
|
+
const level = agentSession?.state.thinkingLevel ?? "off";
|
|
896
752
|
switch (level) {
|
|
897
753
|
case "minimal":
|
|
898
754
|
return this.theme.borderDim;
|
|
@@ -910,71 +766,6 @@ export class StageChatView implements Component, Focusable {
|
|
|
910
766
|
}
|
|
911
767
|
}
|
|
912
768
|
|
|
913
|
-
// -------------------------------------------------------------------------
|
|
914
|
-
// Working, usage + footer — mirrors the main chat composer stack
|
|
915
|
-
// -------------------------------------------------------------------------
|
|
916
|
-
|
|
917
|
-
private _renderWorkingStatus(
|
|
918
|
-
width: number,
|
|
919
|
-
stage: StageSnapshot | undefined,
|
|
920
|
-
flags: { streaming: boolean },
|
|
921
|
-
): string[] {
|
|
922
|
-
if (!flags.streaming) return [];
|
|
923
|
-
const t = this.theme;
|
|
924
|
-
const dur = stageDurationText(stage);
|
|
925
|
-
const message = this.workingMessage ?? `Working${dur ? " · " + dur : ""}`;
|
|
926
|
-
return new WorkingStatusComponent({
|
|
927
|
-
spinner: spinnerFrame(),
|
|
928
|
-
message,
|
|
929
|
-
spinnerColor: (text) => paint(text, t.accent, { bold: true }),
|
|
930
|
-
messageColor: (text) => paint(text, t.textMuted),
|
|
931
|
-
}).render(width);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
private _renderPendingMessages(width: number): string[] {
|
|
935
|
-
if (
|
|
936
|
-
this.pendingSteeringMessages.length === 0 &&
|
|
937
|
-
this.pendingFollowUpMessages.length === 0
|
|
938
|
-
) {
|
|
939
|
-
return [];
|
|
940
|
-
}
|
|
941
|
-
const lines = [this._blank(width)];
|
|
942
|
-
for (const message of this.pendingSteeringMessages) {
|
|
943
|
-
lines.push(...this._pendingMessageLine(width, "Steering", message));
|
|
944
|
-
}
|
|
945
|
-
for (const message of this.pendingFollowUpMessages) {
|
|
946
|
-
lines.push(...this._pendingMessageLine(width, "Follow-up", message));
|
|
947
|
-
}
|
|
948
|
-
return lines;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
private _pendingMessageLine(
|
|
952
|
-
width: number,
|
|
953
|
-
label: "Steering" | "Follow-up",
|
|
954
|
-
message: string,
|
|
955
|
-
): string[] {
|
|
956
|
-
const text = `${label}: ${message}`;
|
|
957
|
-
return new Text(
|
|
958
|
-
paint(truncateToWidth(text, Math.max(1, width - 2)), this.theme.dim),
|
|
959
|
-
1,
|
|
960
|
-
0,
|
|
961
|
-
).render(width);
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
private _renderUsage(width: number): string[] {
|
|
965
|
-
const agentSession = this.handle?.agentSession;
|
|
966
|
-
if (!agentSession) return [];
|
|
967
|
-
return new UsageMeterComponent(agentSession).render(width);
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
private _renderFooter(width: number): string[] {
|
|
971
|
-
const agentSession = this.handle?.agentSession;
|
|
972
|
-
if (agentSession && this.footerData) {
|
|
973
|
-
return new FooterComponent(agentSession, this.footerData).render(width);
|
|
974
|
-
}
|
|
975
|
-
return [];
|
|
976
|
-
}
|
|
977
|
-
|
|
978
769
|
// -------------------------------------------------------------------------
|
|
979
770
|
// Small helpers
|
|
980
771
|
// -------------------------------------------------------------------------
|
|
@@ -992,179 +783,57 @@ export class StageChatView implements Component, Focusable {
|
|
|
992
783
|
// -------------------------------------------------------------------------
|
|
993
784
|
|
|
994
785
|
handleInput(data: string): boolean {
|
|
995
|
-
if (this.
|
|
786
|
+
if (this.mountedCustomUi) {
|
|
787
|
+
if (matchesKey(data, Key.ctrl("d"))) {
|
|
788
|
+
this._rejectMountedCustomUi("stage custom UI detached");
|
|
789
|
+
if (this._isPaused()) this.onClose();
|
|
790
|
+
else this.onDetach();
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
if (matchesKey(data, Key.ctrl("c"))) {
|
|
794
|
+
this._rejectMountedCustomUi("stage custom UI closed");
|
|
795
|
+
this.onClose();
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
798
|
+
setComponentFocused(this.mountedCustomUi.component, this.focused);
|
|
799
|
+
this.mountedCustomUi.component.handleInput?.(data);
|
|
800
|
+
this.requestRender?.();
|
|
801
|
+
return true;
|
|
802
|
+
}
|
|
803
|
+
if (this.chatHost.handleScrollInput(data)) {
|
|
996
804
|
return true;
|
|
997
805
|
}
|
|
998
|
-
if (matchesKey(data, "
|
|
806
|
+
if (matchesKey(data, Key.ctrl("d"))) {
|
|
807
|
+
if (this.chatHost.hasInputText()) return this.chatHost.handleInput(data);
|
|
999
808
|
if (this._isPaused()) this.onClose();
|
|
1000
809
|
else this.onDetach();
|
|
1001
810
|
return true;
|
|
1002
811
|
}
|
|
1003
|
-
if (matchesKey(data,
|
|
1004
|
-
if (
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
this.
|
|
812
|
+
if (matchesKey(data, Key.escape)) {
|
|
813
|
+
if (
|
|
814
|
+
this._isStreaming() ||
|
|
815
|
+
this.chatHost.isBashRunning() ||
|
|
816
|
+
this.chatHost.isEditingBashCommand()
|
|
817
|
+
) {
|
|
818
|
+
return this.chatHost.handleInput(data);
|
|
1008
819
|
}
|
|
820
|
+
this.onClose();
|
|
1009
821
|
return true;
|
|
1010
822
|
}
|
|
1011
|
-
if (matchesKey(data, "
|
|
823
|
+
if (matchesKey(data, Key.ctrl("c"))) {
|
|
1012
824
|
this.onClose();
|
|
1013
825
|
return true;
|
|
1014
826
|
}
|
|
827
|
+
const readOnlyArchive = this._isReadOnlyArchive();
|
|
828
|
+
if (readOnlyArchive) return true;
|
|
1015
829
|
const blocked = this._isBlocked();
|
|
1016
|
-
if (matchesKey(data, "
|
|
1017
|
-
if (blocked) return true;
|
|
1018
|
-
void this._submit("followUp");
|
|
1019
|
-
return true;
|
|
1020
|
-
}
|
|
1021
|
-
if (this.editor) {
|
|
830
|
+
if (matchesKey(data, Key.ctrl("f"))) {
|
|
1022
831
|
if (blocked) return true;
|
|
1023
|
-
this.
|
|
832
|
+
void this.chatHost.submit("followUp");
|
|
1024
833
|
return true;
|
|
1025
834
|
}
|
|
1026
|
-
if (
|
|
1027
|
-
|
|
1028
|
-
void this._submit("auto");
|
|
1029
|
-
return true;
|
|
1030
|
-
}
|
|
1031
|
-
if (matchesKey(data, "backspace")) {
|
|
1032
|
-
if (blocked) return true;
|
|
1033
|
-
this.inputBuffer = this.inputBuffer.slice(0, -1);
|
|
1034
|
-
return true;
|
|
1035
|
-
}
|
|
1036
|
-
if (data.length === 1 && data >= " " && data <= "~") {
|
|
1037
|
-
if (blocked) return true;
|
|
1038
|
-
this.inputBuffer += data;
|
|
1039
|
-
return true;
|
|
1040
|
-
}
|
|
1041
|
-
return false;
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
private _canPause(): boolean {
|
|
1045
|
-
if (!this.handle || this.localPaused || this._isBlocked()) return false;
|
|
1046
|
-
const stage = this._currentStage();
|
|
1047
|
-
if (stage?.status === "paused") return false;
|
|
1048
|
-
return this._isStreaming();
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
private async _pause(): Promise<void> {
|
|
1052
|
-
if (!this.handle) {
|
|
1053
|
-
this.statusMessage = "no live handle on this stage";
|
|
1054
|
-
this.requestRender?.();
|
|
1055
|
-
return;
|
|
1056
|
-
}
|
|
1057
|
-
this.localPaused = true;
|
|
1058
|
-
this.statusMessage = "pausing…";
|
|
1059
|
-
this.requestRender?.();
|
|
1060
|
-
try {
|
|
1061
|
-
await this.handle.pause();
|
|
1062
|
-
this.sdkBusy = false;
|
|
1063
|
-
this.statusMessage = "";
|
|
1064
|
-
} catch (err) {
|
|
1065
|
-
this.statusMessage = `pause failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
1066
|
-
this.localPaused = false;
|
|
1067
|
-
} finally {
|
|
1068
|
-
this._syncAnimationTick();
|
|
1069
|
-
this.requestRender?.();
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
private async _resume(message?: string): Promise<void> {
|
|
1074
|
-
if (!this.handle) {
|
|
1075
|
-
this.statusMessage = "no live handle on this stage";
|
|
1076
|
-
this.requestRender?.();
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
this.localPaused = true;
|
|
1080
|
-
this.sdkBusy = true;
|
|
1081
|
-
this.statusMessage = "resuming…";
|
|
1082
|
-
this._syncAnimationTick();
|
|
1083
|
-
this.requestRender?.();
|
|
1084
|
-
try {
|
|
1085
|
-
await this.handle.resume(message);
|
|
1086
|
-
this.localPaused = false;
|
|
1087
|
-
this.sdkBusy = false;
|
|
1088
|
-
this.statusMessage = "";
|
|
1089
|
-
} catch (err) {
|
|
1090
|
-
this.sdkBusy = false;
|
|
1091
|
-
this.statusMessage = `resume failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
1092
|
-
} finally {
|
|
1093
|
-
this._syncAnimationTick();
|
|
1094
|
-
this.requestRender?.();
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
private async _submit(
|
|
1099
|
-
mode: "auto" | "followUp",
|
|
1100
|
-
submittedText?: string,
|
|
1101
|
-
): Promise<void> {
|
|
1102
|
-
const text = (submittedText ?? this.inputBuffer).trim();
|
|
1103
|
-
if (!text) return;
|
|
1104
|
-
this.inputBuffer = "";
|
|
1105
|
-
this.editor?.setText("");
|
|
1106
|
-
if (!this.handle) {
|
|
1107
|
-
this.statusMessage = "no live handle on this stage";
|
|
1108
|
-
this.transcript.push({
|
|
1109
|
-
role: "system",
|
|
1110
|
-
kind: "system",
|
|
1111
|
-
text: "(no live handle — message dropped)",
|
|
1112
|
-
});
|
|
1113
|
-
this.requestRender?.();
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
const isPaused = this._isPaused();
|
|
1117
|
-
const isStreaming = this._isStreaming();
|
|
1118
|
-
const shouldAppendOptimisticUser = mode === "auto" && !isStreaming;
|
|
1119
|
-
if (shouldAppendOptimisticUser) {
|
|
1120
|
-
this.liveChat.appendUserText(text);
|
|
1121
|
-
this.bodyViewport.scrollToBottom();
|
|
1122
|
-
this.optimisticUserSignatures.add(userMessageSignature(text));
|
|
1123
|
-
}
|
|
1124
|
-
this.requestRender?.();
|
|
1125
|
-
try {
|
|
1126
|
-
if (isPaused) {
|
|
1127
|
-
await this._resume(text);
|
|
1128
|
-
return;
|
|
1129
|
-
}
|
|
1130
|
-
if (mode === "followUp") {
|
|
1131
|
-
await this._queueFollowUp(text);
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
if (isStreaming) {
|
|
1135
|
-
await this._queueSteer(text);
|
|
1136
|
-
} else {
|
|
1137
|
-
this.sdkBusy = true;
|
|
1138
|
-
this._syncAnimationTick();
|
|
1139
|
-
await this.handle.ensureAttached();
|
|
1140
|
-
await this.handle.prompt(text);
|
|
1141
|
-
this.sdkBusy = false;
|
|
1142
|
-
this._syncAnimationTick();
|
|
1143
|
-
}
|
|
1144
|
-
} catch (err) {
|
|
1145
|
-
this.sdkBusy = false;
|
|
1146
|
-
this.statusMessage = err instanceof Error ? err.message : String(err);
|
|
1147
|
-
this._syncAnimationTick();
|
|
1148
|
-
this.requestRender?.();
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
private async _queueSteer(text: string): Promise<void> {
|
|
1153
|
-
const agentSession = this.handle?.agentSession;
|
|
1154
|
-
if (agentSession?.isStreaming) {
|
|
1155
|
-
await agentSession.prompt(text, { streamingBehavior: "steer" });
|
|
1156
|
-
return;
|
|
1157
|
-
}
|
|
1158
|
-
await this.handle?.steer(text);
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
private async _queueFollowUp(text: string): Promise<void> {
|
|
1162
|
-
const agentSession = this.handle?.agentSession;
|
|
1163
|
-
if (agentSession?.isStreaming) {
|
|
1164
|
-
await agentSession.prompt(text, { streamingBehavior: "followUp" });
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
await this.handle?.followUp(text);
|
|
835
|
+
if (blocked) return true;
|
|
836
|
+
return this.chatHost.handleInput(data);
|
|
1168
837
|
}
|
|
1169
838
|
|
|
1170
839
|
invalidate(): void {
|
|
@@ -1176,38 +845,51 @@ export class StageChatView implements Component, Focusable {
|
|
|
1176
845
|
this._unsubscribeStore = null;
|
|
1177
846
|
this._unsubscribeHandle?.();
|
|
1178
847
|
this._unsubscribeHandle = null;
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
848
|
+
this._rejectMountedCustomUi("stage chat view disposed");
|
|
849
|
+
this._unregisterStageUiHost?.();
|
|
850
|
+
this._unregisterStageUiHost = null;
|
|
851
|
+
this.chatHost.dispose();
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
private _hideMountedCustomUi(request: StageCustomUiRequest): void {
|
|
855
|
+
const mounted = this.mountedCustomUi;
|
|
856
|
+
if (!mounted || mounted.request.id !== request.id) return;
|
|
857
|
+
this.mountedCustomUi = null;
|
|
858
|
+
mounted.component.dispose?.();
|
|
859
|
+
this.chatHost.focused = this.focused;
|
|
860
|
+
this.chatHost.scrollToBottom();
|
|
861
|
+
this.requestRender?.();
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
private _rejectMountedCustomUi(message: string): void {
|
|
865
|
+
const mounted = this.mountedCustomUi;
|
|
866
|
+
if (!mounted) return;
|
|
867
|
+
this.mountedCustomUi = null;
|
|
868
|
+
this.stageUiBroker.reject(mounted.request, new Error(`pi-workflows: ${message}`));
|
|
869
|
+
mounted.component.dispose?.();
|
|
1188
870
|
}
|
|
1189
871
|
|
|
1190
872
|
// ---- Test seams ----
|
|
1191
873
|
get _inputBuffer(): string {
|
|
1192
|
-
return this.
|
|
874
|
+
return this.chatHost.inputText();
|
|
1193
875
|
}
|
|
1194
876
|
get _transcript(): ReadonlyArray<TranscriptDebugEntry> {
|
|
1195
|
-
return this.
|
|
877
|
+
return this.chatHost.entries().flatMap((entry) => transcriptDebugEntries(entry));
|
|
1196
878
|
}
|
|
1197
879
|
get _statusMessage(): string {
|
|
1198
|
-
return this.
|
|
880
|
+
return this.chatHost.statusText();
|
|
1199
881
|
}
|
|
1200
882
|
get _isLocalPaused(): boolean {
|
|
1201
883
|
return this.localPaused;
|
|
1202
884
|
}
|
|
1203
885
|
get _hasAnimationTick(): boolean {
|
|
1204
|
-
return this.
|
|
886
|
+
return this.chatHost.hasAnimationTick();
|
|
1205
887
|
}
|
|
1206
888
|
get _bodyScrollFromBottom(): number {
|
|
1207
|
-
return this.
|
|
889
|
+
return this.chatHost.bodyScrollFromBottom();
|
|
1208
890
|
}
|
|
1209
891
|
get _lastBodyMaxScroll(): number {
|
|
1210
|
-
return this.
|
|
892
|
+
return this.chatHost.bodyMaxScroll();
|
|
1211
893
|
}
|
|
1212
894
|
}
|
|
1213
895
|
|
|
@@ -1302,174 +984,19 @@ function transcriptDebugToolOutput(entry: TranscriptEntry): string {
|
|
|
1302
984
|
return "";
|
|
1303
985
|
}
|
|
1304
986
|
|
|
1305
|
-
function setEditorPlaceholder(
|
|
1306
|
-
editor: EditorComponent,
|
|
1307
|
-
placeholder: string | undefined,
|
|
1308
|
-
): void {
|
|
1309
|
-
const candidate = editor as EditorComponent & {
|
|
1310
|
-
setPlaceholder?: (value: string | undefined) => void;
|
|
1311
|
-
};
|
|
1312
|
-
candidate.setPlaceholder?.(placeholder);
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
987
|
function cursorBlock(): string {
|
|
1316
988
|
return "\x1b[7m \x1b[0m";
|
|
1317
989
|
}
|
|
1318
990
|
|
|
1319
|
-
function
|
|
1320
|
-
const candidate =
|
|
1321
|
-
borderColor?: (text: string) => string;
|
|
1322
|
-
};
|
|
1323
|
-
if (candidate.borderColor !== undefined) {
|
|
1324
|
-
candidate.borderColor = (text: string) => hexToAnsi(hex) + text + RESET;
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
function setEditorFocused(editor: EditorComponent, focused: boolean): void {
|
|
1329
|
-
const candidate = editor as EditorComponent & Partial<Focusable>;
|
|
991
|
+
function setComponentFocused(component: Component, focused: boolean): void {
|
|
992
|
+
const candidate = component as Component & Partial<Focusable>;
|
|
1330
993
|
if ("focused" in candidate) candidate.focused = focused;
|
|
1331
994
|
}
|
|
1332
995
|
|
|
1333
|
-
function isSharedLiveChatEvent(type: string): boolean {
|
|
1334
|
-
return (
|
|
1335
|
-
type === "message_start" ||
|
|
1336
|
-
type === "message_update" ||
|
|
1337
|
-
type === "message_end" ||
|
|
1338
|
-
type === "tool_execution_start" ||
|
|
1339
|
-
type === "tool_execution_update" ||
|
|
1340
|
-
type === "tool_execution_end"
|
|
1341
|
-
);
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
996
|
function isChatMessageEntry(entry: TranscriptEntry): entry is ChatMessageEntry {
|
|
1345
997
|
return "kind" in entry && entry.role !== "notice";
|
|
1346
998
|
}
|
|
1347
999
|
|
|
1348
|
-
function isMessageLike(message: unknown): message is { role?: unknown; content?: unknown } {
|
|
1349
|
-
return message !== null && typeof message === "object" && "role" in message;
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
function isUserMessageLike(
|
|
1353
|
-
message: unknown,
|
|
1354
|
-
): message is { role: "user"; content?: unknown } {
|
|
1355
|
-
return isMessageLike(message) && message.role === "user";
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
function userMessageSignature(text: string): string {
|
|
1359
|
-
return text.trim();
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
function assistantToolCallEvent(event: AgentSessionEvent): {
|
|
1363
|
-
type: "tool_execution_start";
|
|
1364
|
-
toolCallId: string;
|
|
1365
|
-
toolName: string;
|
|
1366
|
-
args: unknown;
|
|
1367
|
-
} | undefined {
|
|
1368
|
-
const assistantEvent = (event as {
|
|
1369
|
-
assistantMessageEvent?: {
|
|
1370
|
-
type?: unknown;
|
|
1371
|
-
contentIndex?: unknown;
|
|
1372
|
-
partial?: unknown;
|
|
1373
|
-
toolCall?: unknown;
|
|
1374
|
-
};
|
|
1375
|
-
}).assistantMessageEvent;
|
|
1376
|
-
const streamType = String(assistantEvent?.type ?? "");
|
|
1377
|
-
if (!streamType.startsWith("toolcall_")) return undefined;
|
|
1378
|
-
|
|
1379
|
-
const explicit = toolCallPayload(assistantEvent?.toolCall);
|
|
1380
|
-
if (explicit) return explicit;
|
|
1381
|
-
|
|
1382
|
-
const contentIndex = typeof assistantEvent?.contentIndex === "number" ? assistantEvent.contentIndex : undefined;
|
|
1383
|
-
if (contentIndex === undefined) return undefined;
|
|
1384
|
-
const partial = assistantEvent?.partial;
|
|
1385
|
-
if (!isMessageLike(partial) || partial.role !== "assistant") return undefined;
|
|
1386
|
-
const content = partial.content;
|
|
1387
|
-
if (!Array.isArray(content)) return undefined;
|
|
1388
|
-
return toolCallPayload(content[contentIndex]);
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
function toolCallPayload(value: unknown): {
|
|
1392
|
-
type: "tool_execution_start";
|
|
1393
|
-
toolCallId: string;
|
|
1394
|
-
toolName: string;
|
|
1395
|
-
args: unknown;
|
|
1396
|
-
} | undefined {
|
|
1397
|
-
if (value === null || typeof value !== "object") return undefined;
|
|
1398
|
-
const candidate = value as { type?: unknown; id?: unknown; name?: unknown; arguments?: unknown };
|
|
1399
|
-
if (candidate.type !== "toolCall") return undefined;
|
|
1400
|
-
if (typeof candidate.id !== "string" || typeof candidate.name !== "string") return undefined;
|
|
1401
|
-
return {
|
|
1402
|
-
type: "tool_execution_start",
|
|
1403
|
-
toolCallId: candidate.id,
|
|
1404
|
-
toolName: candidate.name,
|
|
1405
|
-
args: candidate.arguments ?? {},
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
function legacyToolStartEvent(event: AgentSessionEvent): {
|
|
1410
|
-
type: "tool_execution_start";
|
|
1411
|
-
toolCallId: string;
|
|
1412
|
-
toolName: string;
|
|
1413
|
-
args: unknown;
|
|
1414
|
-
} {
|
|
1415
|
-
const payload = event as { toolCallId?: unknown; name?: unknown; input?: unknown; args?: unknown };
|
|
1416
|
-
const toolName = typeof payload.name === "string" ? payload.name : "tool";
|
|
1417
|
-
const toolCallId =
|
|
1418
|
-
typeof payload.toolCallId === "string" ? payload.toolCallId : `live-${toolName}`;
|
|
1419
|
-
return {
|
|
1420
|
-
type: "tool_execution_start",
|
|
1421
|
-
toolCallId,
|
|
1422
|
-
toolName,
|
|
1423
|
-
args: payload.input ?? payload.args ?? {},
|
|
1424
|
-
};
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
function legacyToolResultEvent(event: AgentSessionEvent): {
|
|
1428
|
-
type: "tool_execution_end";
|
|
1429
|
-
toolCallId: string;
|
|
1430
|
-
toolName: string;
|
|
1431
|
-
result: unknown;
|
|
1432
|
-
isError: boolean;
|
|
1433
|
-
} {
|
|
1434
|
-
const payload = event as {
|
|
1435
|
-
toolCallId?: unknown;
|
|
1436
|
-
name?: unknown;
|
|
1437
|
-
output?: unknown;
|
|
1438
|
-
isError?: unknown;
|
|
1439
|
-
};
|
|
1440
|
-
const toolName = typeof payload.name === "string" ? payload.name : "tool";
|
|
1441
|
-
const toolCallId =
|
|
1442
|
-
typeof payload.toolCallId === "string" ? payload.toolCallId : `live-${toolName}`;
|
|
1443
|
-
const output = payload.output;
|
|
1444
|
-
return {
|
|
1445
|
-
type: "tool_execution_end",
|
|
1446
|
-
toolCallId,
|
|
1447
|
-
toolName,
|
|
1448
|
-
result:
|
|
1449
|
-
output !== null && typeof output === "object" && "content" in output
|
|
1450
|
-
? output
|
|
1451
|
-
: { content: typeof output === "string" ? [{ type: "text", text: output }] : [] },
|
|
1452
|
-
isError: payload.isError === true,
|
|
1453
|
-
};
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
function legacyThinkingEvent(event: AgentSessionEvent): {
|
|
1457
|
-
type: "message_update";
|
|
1458
|
-
assistantMessageEvent: { type: "thinking_delta"; delta: string };
|
|
1459
|
-
message: { role: "assistant"; content: [] };
|
|
1460
|
-
} {
|
|
1461
|
-
const delta = String(
|
|
1462
|
-
(event as { delta?: unknown }).delta ??
|
|
1463
|
-
(event as { text?: unknown }).text ??
|
|
1464
|
-
"",
|
|
1465
|
-
);
|
|
1466
|
-
return {
|
|
1467
|
-
type: "message_update",
|
|
1468
|
-
assistantMessageEvent: { type: "thinking_delta", delta },
|
|
1469
|
-
message: { role: "assistant", content: [] },
|
|
1470
|
-
};
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
1000
|
function extractThinkingText(content: unknown): string {
|
|
1474
1001
|
if (!Array.isArray(content)) return "";
|
|
1475
1002
|
const parts: string[] = [];
|
|
@@ -1515,48 +1042,10 @@ function noticeSummary(n: StageNotice): string {
|
|
|
1515
1042
|
return n.from ? `${base} (was ${n.from})` : base;
|
|
1516
1043
|
}
|
|
1517
1044
|
|
|
1518
|
-
function tailStreamingText(text: string): string {
|
|
1519
|
-
if (
|
|
1520
|
-
text.length <= STREAMING_TEXT_TAIL_CHARS &&
|
|
1521
|
-
text.split("\n").length <= STREAMING_TEXT_TAIL_LINES
|
|
1522
|
-
) {
|
|
1523
|
-
return text;
|
|
1524
|
-
}
|
|
1525
|
-
const byChars = text.slice(-STREAMING_TEXT_TAIL_CHARS);
|
|
1526
|
-
const lines = byChars.split("\n");
|
|
1527
|
-
const tail =
|
|
1528
|
-
lines.length > STREAMING_TEXT_TAIL_LINES
|
|
1529
|
-
? lines.slice(-STREAMING_TEXT_TAIL_LINES).join("\n")
|
|
1530
|
-
: byChars;
|
|
1531
|
-
return `[earlier streaming output hidden while attached]\n\n${tail.trimStart()}`;
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
function stageDurationText(stage: StageSnapshot | undefined): string {
|
|
1535
|
-
if (!stage) return "";
|
|
1536
|
-
const elapsed = elapsedStageMs(stage);
|
|
1537
|
-
return elapsed === undefined ? "" : formatDuration(elapsed);
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
function formatDuration(ms: number): string {
|
|
1541
|
-
const s = Math.floor(Math.max(0, ms) / 1000);
|
|
1542
|
-
if (s < 60) return `${s}s`;
|
|
1543
|
-
const m = Math.floor(s / 60);
|
|
1544
|
-
const rs = s % 60;
|
|
1545
|
-
if (m < 60) return rs ? `${m}m ${rs}s` : `${m}m`;
|
|
1546
|
-
const h = Math.floor(m / 60);
|
|
1547
|
-
const rm = m % 60;
|
|
1548
|
-
return rm ? `${h}h ${rm}m` : `${h}h`;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
1045
|
function shortenId(id: string): string {
|
|
1552
1046
|
return id.length > 10 ? id.slice(0, 8) : id;
|
|
1553
1047
|
}
|
|
1554
1048
|
|
|
1555
|
-
function spinnerFrame(): string {
|
|
1556
|
-
const idx = Math.floor(Date.now() / 80) % SPINNER_FRAMES.length;
|
|
1557
|
-
return SPINNER_FRAMES[idx]!;
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
1049
|
function bgFn(hex: string): (text: string) => string {
|
|
1561
1050
|
const open = hexBg(hex);
|
|
1562
1051
|
return (text: string) => open + text + RESET;
|