mixdog 0.9.3 → 0.9.4
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/package.json +7 -3
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +23 -63
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +6 -5
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/worker/AGENT.md +8 -4
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +632 -2042
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +0 -11
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +229 -663
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +201 -1948
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +5 -13
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +84 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +11 -9
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +110 -671
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +30 -9
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +945 -8094
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +23 -101
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +52 -594
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +19652 -18630
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine.mjs +122 -562
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +67 -501
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +1 -1
- package/src/workflows/sequential/WORKFLOW.md +1 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/input-editing.selection.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/paste-fix.test.mjs +0 -119
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
package/src/tui/engine.mjs
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
} from '../runtime/shared/tool-surface.mjs';
|
|
20
20
|
import {
|
|
21
21
|
isModelVisibleToolCompletionWrapper,
|
|
22
|
-
modelVisibleToolCompletionMessage,
|
|
23
22
|
} from '../runtime/shared/tool-execution-contract.mjs';
|
|
24
23
|
import { presentErrorText } from '../runtime/shared/err-text.mjs';
|
|
25
24
|
import { listThemes, getThemeSetting, setThemeSetting } from './theme.mjs';
|
|
@@ -48,13 +47,8 @@ import {
|
|
|
48
47
|
toolCallArgs,
|
|
49
48
|
} from './engine/tool-call-fields.mjs';
|
|
50
49
|
import {
|
|
51
|
-
parseAgentJob,
|
|
52
|
-
parseAgentResultEnvelope,
|
|
53
50
|
parseBackgroundTaskEnvelope,
|
|
54
51
|
parseSyntheticAgentMessage,
|
|
55
|
-
isStatusOnlyAgentCompletionNotification,
|
|
56
|
-
agentJobResultText,
|
|
57
|
-
agentArgsWithResultMetadata,
|
|
58
52
|
toolResultStatus,
|
|
59
53
|
isErrorToolStatus,
|
|
60
54
|
} from './engine/agent-envelope.mjs';
|
|
@@ -64,7 +58,6 @@ import {
|
|
|
64
58
|
isQueuedEntryEditable,
|
|
65
59
|
isQueuedEntryVisible,
|
|
66
60
|
isSlashQueuedEntry,
|
|
67
|
-
shortTextFingerprint,
|
|
68
61
|
notificationDisplayText,
|
|
69
62
|
sessionActivityTimestamp,
|
|
70
63
|
promptDisplayText,
|
|
@@ -73,6 +66,19 @@ import {
|
|
|
73
66
|
mergePastedTexts,
|
|
74
67
|
callCommitCallbacks,
|
|
75
68
|
} from './engine/queue-helpers.mjs';
|
|
69
|
+
import {
|
|
70
|
+
resolveTuiRuntimeNotificationDelivery,
|
|
71
|
+
} from './engine/notification-plan.mjs';
|
|
72
|
+
import { yieldToRenderer } from './engine/render-timing.mjs';
|
|
73
|
+
import {
|
|
74
|
+
aggregateRawResult,
|
|
75
|
+
aggregateBucketForCategory,
|
|
76
|
+
aggregateSummaries,
|
|
77
|
+
assignAggregateSummaryOrder,
|
|
78
|
+
} from './engine/tool-result-status.mjs';
|
|
79
|
+
import { createToolApproval } from './engine/tool-approval.mjs';
|
|
80
|
+
import { createToolCardResults } from './engine/tool-card-results.mjs';
|
|
81
|
+
import { createAgentJobFeed } from './engine/agent-job-feed.mjs';
|
|
76
82
|
|
|
77
83
|
// Source tests resolve from src/tui/engine.mjs; the built bundle resolves from
|
|
78
84
|
// src/tui/dist/index.mjs.
|
|
@@ -92,77 +98,9 @@ const nextId = () => `it_${++_idSeq}`;
|
|
|
92
98
|
// keep resolving them from engine.mjs unchanged.
|
|
93
99
|
export { toolResultText, toolAggregateDetailFallback, toolGroupedDisplayFallback };
|
|
94
100
|
export { parseBackgroundTaskEnvelope };
|
|
95
|
-
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
// current throttle window, the next paint may still be queued and our following
|
|
99
|
-
// transcript mutation can coalesce into the same visible frame. Wait just past
|
|
100
|
-
// one render window when we intentionally split transcript commits for visual
|
|
101
|
-
// stability (preamble frame → tool-card frame).
|
|
102
|
-
const RENDER_THROTTLE_FLUSH_MS = 12;
|
|
103
|
-
const yieldToRenderer = () => new Promise((resolve) => {
|
|
104
|
-
setTimeout(resolve, RENDER_THROTTLE_FLUSH_MS);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
function notificationQueueKey(event, text, parsed) {
|
|
108
|
-
const meta = event?.meta && typeof event.meta === 'object' ? event.meta : {};
|
|
109
|
-
const synthetic = parseSyntheticAgentMessage(text);
|
|
110
|
-
if (synthetic?.name === 'agent' && String(synthetic.args?.type || '').toLowerCase() === 'result') {
|
|
111
|
-
const taskId = String(synthetic.args?.task_id || '').trim();
|
|
112
|
-
const executionId = String(meta.execution_id || '').trim();
|
|
113
|
-
const tag = String(synthetic.args?.tag || '').trim();
|
|
114
|
-
const resultId = taskId
|
|
115
|
-
? `task:${taskId}`
|
|
116
|
-
: executionId
|
|
117
|
-
? `exec:${executionId}`
|
|
118
|
-
: tag
|
|
119
|
-
? `tag:${tag}:${shortTextFingerprint(synthetic.result || text)}`
|
|
120
|
-
: '';
|
|
121
|
-
const agent = String(synthetic.args?.agent || '').trim();
|
|
122
|
-
if (resultId || agent) return ['agent-result', resultId, agent].filter(Boolean).join(':');
|
|
123
|
-
}
|
|
124
|
-
const id = String(meta.execution_id || parsed?.taskId || '').trim();
|
|
125
|
-
if (!id) return '';
|
|
126
|
-
const type = String(meta.type || '').trim();
|
|
127
|
-
const status = String(meta.status || parsed?.status || '').trim();
|
|
128
|
-
const fallbackKind = String(text || '').split('\n', 1)[0]?.trim() || 'notification';
|
|
129
|
-
// Distinguish a body-carrying completion from a header-only preview that
|
|
130
|
-
// shares the same id/type/status. An early agent preview can arrive before
|
|
131
|
-
// the session is persisted (no result body); the canonical notification that
|
|
132
|
-
// follows DOES carry the body. Without this dimension the bodyless preview
|
|
133
|
-
// would claim the dedupe key and suppress the real result. A blank-line gap
|
|
134
|
-
// separates the task header block from the result body in the envelope.
|
|
135
|
-
const hasBody = /\n\s*\n[\s\S]*\S/.test(String(text || '')) ? 'b1' : 'b0';
|
|
136
|
-
return [id, type || fallbackKind, status, hasBody].filter(Boolean).join(':');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** Pure delivery plan for runtime.onNotification execution envelopes (tests + handler). */
|
|
140
|
-
export function resolveTuiRuntimeNotificationDelivery(event, text) {
|
|
141
|
-
const trimmed = String(text ?? '').trim();
|
|
142
|
-
if (!trimmed) return { action: 'ignore' };
|
|
143
|
-
const parsed = parseAgentJob(trimmed);
|
|
144
|
-
const meta = event?.meta && typeof event.meta === 'object' ? event.meta : {};
|
|
145
|
-
if (!isExecutionNotification(event, trimmed, parsed)) {
|
|
146
|
-
return { action: 'enqueue', displayText: trimmed, modelContent: trimmed };
|
|
147
|
-
}
|
|
148
|
-
if (isStatusOnlyAgentCompletionNotification(trimmed)) {
|
|
149
|
-
return { action: 'status-only', displayText: trimmed, modelContent: '' };
|
|
150
|
-
}
|
|
151
|
-
const modelContent = modelVisibleToolCompletionMessage(trimmed, meta);
|
|
152
|
-
return {
|
|
153
|
-
action: 'execution-ui',
|
|
154
|
-
displayText: trimmed,
|
|
155
|
-
modelContent,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function isExecutionNotification(event, text, parsed) {
|
|
160
|
-
const meta = event?.meta && typeof event.meta === 'object' ? event.meta : {};
|
|
161
|
-
if (meta.execution_id || meta.execution_surface) return true;
|
|
162
|
-
if (parseAgentResultEnvelope(text)) return true;
|
|
163
|
-
if (parseBackgroundTaskEnvelope(text)) return true;
|
|
164
|
-
return Boolean(parsed?.taskId && /^(?:agent task:|task_id:)/mi.test(String(text || '')));
|
|
165
|
-
}
|
|
101
|
+
// Re-export the pure notification delivery plan (moved to ./engine/notification-plan.mjs)
|
|
102
|
+
// so importers/tests keep resolving resolveTuiRuntimeNotificationDelivery from engine.mjs.
|
|
103
|
+
export { resolveTuiRuntimeNotificationDelivery };
|
|
166
104
|
|
|
167
105
|
export async function createEngineSession({
|
|
168
106
|
provider: providerName,
|
|
@@ -187,7 +125,7 @@ export async function createEngineSession({
|
|
|
187
125
|
bootProfile('engine:create:runtime-ready', { ms: (performance.now() - startedAt).toFixed(1) });
|
|
188
126
|
const cwd = runtime.cwd || process.cwd();
|
|
189
127
|
const stateStartedAt = performance.now();
|
|
190
|
-
const autoClearState = () => runtime.getAutoClear?.() || runtime.autoClear || { enabled: true, idleMs: 60 * 60 * 1000 };
|
|
128
|
+
const autoClearState = () => runtime.getAutoClear?.() || runtime.autoClear || { enabled: true, idleMs: 60 * 60 * 1000, custom: false, providerDefault: 60 * 60 * 1000, provider: null };
|
|
191
129
|
const AGENT_STATUS_CACHE_MS = 250;
|
|
192
130
|
let agentStatusCache = null;
|
|
193
131
|
let agentStatusCacheAt = 0;
|
|
@@ -535,6 +473,16 @@ export async function createEngineSession({
|
|
|
535
473
|
pushItem({ kind: 'notice', id, text: value, tone });
|
|
536
474
|
return id;
|
|
537
475
|
};
|
|
476
|
+
// Remove a transcript notice previously created via pushNotice(...,
|
|
477
|
+
// {transcript:true}). Used for transient-but-persistent notices (e.g. the
|
|
478
|
+
// manual OAuth URL) that must disappear once their flow concludes.
|
|
479
|
+
const removeNotice = (id) => {
|
|
480
|
+
if (id == null) return false;
|
|
481
|
+
const items = state.items.filter((it) => !(it?.kind === 'notice' && it?.id === id));
|
|
482
|
+
if (items.length === state.items.length) return false;
|
|
483
|
+
set({ items: replaceItems(items) });
|
|
484
|
+
return true;
|
|
485
|
+
};
|
|
538
486
|
// Sticky (non-TTL) input-hint-line progress state, for long-running
|
|
539
487
|
// installs (e.g. voice runtime download) that would otherwise spam the
|
|
540
488
|
// 3s toast queue. Distinct from pushToast/pushNotice: it persists across
|
|
@@ -545,80 +493,18 @@ export async function createEngineSession({
|
|
|
545
493
|
const value = String(text ?? '').trim();
|
|
546
494
|
set({ progressHint: value ? { text: value, tone } : null });
|
|
547
495
|
};
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
reason: String(input?.reason || input?.message || 'approval requested by hook').trim(),
|
|
561
|
-
requestedAt: now,
|
|
562
|
-
timeoutMs,
|
|
563
|
-
expiresAt: now + timeoutMs,
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
function presentNextToolApproval() {
|
|
567
|
-
if (activeToolApproval || disposed) return;
|
|
568
|
-
const entry = toolApprovalQueue.shift();
|
|
569
|
-
if (!entry) {
|
|
570
|
-
if (state.toolApproval) set({ toolApproval: null });
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
activeToolApproval = entry;
|
|
574
|
-
entry.timer = setTimeout(() => {
|
|
575
|
-
finishToolApproval(entry.id, false, 'approval timed out');
|
|
576
|
-
}, entry.request.timeoutMs);
|
|
577
|
-
entry.timer.unref?.();
|
|
578
|
-
set({ toolApproval: entry.request });
|
|
579
|
-
}
|
|
580
|
-
function finishToolApproval(id, approved, reason = '') {
|
|
581
|
-
const targetId = String(id || '');
|
|
582
|
-
if (activeToolApproval && activeToolApproval.id === targetId) {
|
|
583
|
-
const entry = activeToolApproval;
|
|
584
|
-
activeToolApproval = null;
|
|
585
|
-
if (entry.timer) clearTimeout(entry.timer);
|
|
586
|
-
set({ toolApproval: null });
|
|
587
|
-
try { entry.resolve({ approved: approved === true, reason: String(reason || '') }); } catch {}
|
|
588
|
-
presentNextToolApproval();
|
|
589
|
-
return true;
|
|
590
|
-
}
|
|
591
|
-
const index = toolApprovalQueue.findIndex((entry) => entry.id === targetId);
|
|
592
|
-
if (index >= 0) {
|
|
593
|
-
const [entry] = toolApprovalQueue.splice(index, 1);
|
|
594
|
-
if (entry?.timer) clearTimeout(entry.timer);
|
|
595
|
-
try { entry.resolve({ approved: approved === true, reason: String(reason || '') }); } catch {}
|
|
596
|
-
return true;
|
|
597
|
-
}
|
|
598
|
-
return false;
|
|
599
|
-
}
|
|
600
|
-
function denyAllToolApprovals(reason = 'approval cancelled') {
|
|
601
|
-
if (activeToolApproval) {
|
|
602
|
-
const entry = activeToolApproval;
|
|
603
|
-
activeToolApproval = null;
|
|
604
|
-
if (entry.timer) clearTimeout(entry.timer);
|
|
605
|
-
try { entry.resolve({ approved: false, reason }); } catch {}
|
|
606
|
-
}
|
|
607
|
-
while (toolApprovalQueue.length > 0) {
|
|
608
|
-
const entry = toolApprovalQueue.shift();
|
|
609
|
-
if (entry?.timer) clearTimeout(entry.timer);
|
|
610
|
-
try { entry.resolve({ approved: false, reason }); } catch {}
|
|
611
|
-
}
|
|
612
|
-
if (state.toolApproval) set({ toolApproval: null });
|
|
613
|
-
}
|
|
614
|
-
function requestToolApproval(input = {}) {
|
|
615
|
-
if (disposed) return Promise.resolve({ approved: false, reason: 'runtime disposed' });
|
|
616
|
-
return new Promise((resolve) => {
|
|
617
|
-
const id = nextId();
|
|
618
|
-
toolApprovalQueue.push({ id, request: normalizeToolApprovalRequest(input, id), resolve, timer: null });
|
|
619
|
-
presentNextToolApproval();
|
|
620
|
-
});
|
|
621
|
-
}
|
|
496
|
+
const {
|
|
497
|
+
presentNextToolApproval,
|
|
498
|
+
finishToolApproval,
|
|
499
|
+
denyAllToolApprovals,
|
|
500
|
+
requestToolApproval,
|
|
501
|
+
} = createToolApproval({
|
|
502
|
+
getState: () => state,
|
|
503
|
+
set,
|
|
504
|
+
nextId,
|
|
505
|
+
getDisposed: () => disposed,
|
|
506
|
+
timeoutMs: TOOL_APPROVAL_TIMEOUT_MS,
|
|
507
|
+
});
|
|
622
508
|
const patchItem = (id, patch) => {
|
|
623
509
|
let index = itemIndexById.get(id);
|
|
624
510
|
if (!Number.isInteger(index) || state.items[index]?.id !== id) {
|
|
@@ -666,404 +552,51 @@ export async function createEngineSession({
|
|
|
666
552
|
let autoClearRunning = false;
|
|
667
553
|
const pendingNotificationKeys = new Set();
|
|
668
554
|
const displayedExecutionNotificationKeys = new Set();
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
const parsed = parseAgentJob(text);
|
|
699
|
-
const current = state.items.find((it) => it.id === itemId);
|
|
700
|
-
const rawDisplayText = agentJobResultText(text, parsed) || String(text ?? '').trim();
|
|
701
|
-
const displayText = isError ? toolErrorDisplay(rawDisplayText, 'agent') : rawDisplayText;
|
|
702
|
-
patchItem(itemId, {
|
|
703
|
-
result: displayText,
|
|
704
|
-
text: displayText,
|
|
705
|
-
isError,
|
|
706
|
-
errorCount: isError ? 1 : 0,
|
|
707
|
-
...(parsed ? { args: agentArgsWithResultMetadata(current?.args, parsed) } : {}),
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
if (typeof runtime.onNotification === 'function') {
|
|
712
|
-
unsubscribeRuntimeNotifications = runtime.onNotification((event) => {
|
|
555
|
+
const {
|
|
556
|
+
kickExecutionPendingResume,
|
|
557
|
+
flushDeferredExecutionPendingResumeKick,
|
|
558
|
+
scheduleExecutionPendingResumeKick,
|
|
559
|
+
updateAgentJobCard,
|
|
560
|
+
subscribeRuntimeNotifications,
|
|
561
|
+
} = createAgentJobFeed({
|
|
562
|
+
runtime,
|
|
563
|
+
getState: () => state,
|
|
564
|
+
set,
|
|
565
|
+
nextId,
|
|
566
|
+
getDisposed: () => disposed,
|
|
567
|
+
patchItem,
|
|
568
|
+
enqueue: (...args) => enqueue(...args),
|
|
569
|
+
drain: (...args) => drain(...args),
|
|
570
|
+
pushUserOrSyntheticItem,
|
|
571
|
+
makeQueueEntry: (...args) => makeQueueEntry(...args),
|
|
572
|
+
getPending: () => pending,
|
|
573
|
+
agentStatusState,
|
|
574
|
+
displayedExecutionNotificationKeys,
|
|
575
|
+
});
|
|
576
|
+
unsubscribeRuntimeNotifications = subscribeRuntimeNotifications();
|
|
577
|
+
|
|
578
|
+
// Remote seat superseded by another session: runtime already stopped its
|
|
579
|
+
// worker; sync the indicator and tell the user. Non-user-initiated, so a
|
|
580
|
+
// toast (not transcript) is right.
|
|
581
|
+
let unsubscribeRemoteState = null;
|
|
582
|
+
if (typeof runtime.onRemoteStateChange === 'function') {
|
|
583
|
+
unsubscribeRemoteState = runtime.onRemoteStateChange(({ enabled, reason }) => {
|
|
713
584
|
if (disposed) return;
|
|
714
|
-
|
|
715
|
-
if (
|
|
716
|
-
|
|
717
|
-
const notificationKey = notificationQueueKey(event, text, parsed);
|
|
718
|
-
const delivery = resolveTuiRuntimeNotificationDelivery(event, text);
|
|
719
|
-
if (delivery.action === 'ignore') return;
|
|
720
|
-
if (delivery.action === 'status-only') {
|
|
721
|
-
if (parsed?.taskId) set(agentStatusState({ force: true }));
|
|
722
|
-
return true;
|
|
723
|
-
}
|
|
724
|
-
if (delivery.action === 'execution-ui') {
|
|
725
|
-
const firstDelivery = !notificationKey || !displayedExecutionNotificationKeys.has(notificationKey);
|
|
726
|
-
if (firstDelivery) {
|
|
727
|
-
if (notificationKey) displayedExecutionNotificationKeys.add(notificationKey);
|
|
728
|
-
pushUserOrSyntheticItem(delivery.displayText, nextId());
|
|
729
|
-
}
|
|
730
|
-
if (parsed?.taskId) set(agentStatusState({ force: true }));
|
|
731
|
-
if (String(delivery.modelContent || '').trim()) {
|
|
732
|
-
scheduleExecutionPendingResumeKick();
|
|
733
|
-
}
|
|
734
|
-
return true;
|
|
735
|
-
}
|
|
736
|
-
if (parsed?.taskId) {
|
|
737
|
-
set(agentStatusState({ force: true }));
|
|
585
|
+
set({ remoteEnabled: enabled === true });
|
|
586
|
+
if (reason === 'superseded') {
|
|
587
|
+
pushNotice('Remote mode OFF — another session took over remote.', 'warn');
|
|
738
588
|
}
|
|
739
|
-
const modelContent = String(delivery.modelContent ?? delivery.displayText ?? text).trim();
|
|
740
|
-
if (!modelContent) return true;
|
|
741
|
-
enqueue(modelContent, {
|
|
742
|
-
mode: 'task-notification',
|
|
743
|
-
priority: 'next',
|
|
744
|
-
key: notificationKey || undefined,
|
|
745
|
-
displayText: delivery.displayText || text,
|
|
746
|
-
});
|
|
747
|
-
return true;
|
|
748
589
|
});
|
|
749
590
|
}
|
|
750
591
|
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
if (/^(cancelled|canceled|cancel)$/.test(raw)) return 'cancelled';
|
|
760
|
-
return '';
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
function resultTextTerminalStatus(text) {
|
|
764
|
-
const body = String(text || '');
|
|
765
|
-
const tagged = body.match(/<status[^>]*>([\s\S]*?)<\/status>/i)?.[1]?.trim();
|
|
766
|
-
if (tagged) return normalizedResultStatusToken(tagged);
|
|
767
|
-
const bracketed = body.match(/^\[status:\s*([^\]]*)\]/mi)?.[1]?.trim();
|
|
768
|
-
if (bracketed) return normalizedResultStatusToken(bracketed);
|
|
769
|
-
const inline = body.match(/^(?:status|state):\s*([^\s·,;]+)/mi)?.[1]?.trim();
|
|
770
|
-
return normalizedResultStatusToken(inline);
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
function itemHasKnownTerminalStatus(item, texts = []) {
|
|
774
|
-
const settled = (token) => token === 'completed' || token === 'failed' || token === 'cancelled';
|
|
775
|
-
if (settled(normalizedResultStatusToken(item?.args?.status))) return true;
|
|
776
|
-
for (const text of texts) {
|
|
777
|
-
if (settled(resultTextTerminalStatus(text))) return true;
|
|
778
|
-
}
|
|
779
|
-
return false;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
function withCancelledResultMarker(text, item) {
|
|
783
|
-
const body = String(text || '');
|
|
784
|
-
// Do NOT inspect item.rawResult here: aggregate rawResult is child tool
|
|
785
|
-
// output (`1. grep\n<result>…`) that can incidentally contain a `status:`
|
|
786
|
-
// line, which would false-positive as an already-terminal status and skip
|
|
787
|
-
// the cancelled marker. Only result/text/body are engine-controlled
|
|
788
|
-
// collapsed detail (empty / status word / an existing marker), so they are
|
|
789
|
-
// the trustworthy terminal-status sources.
|
|
790
|
-
const sources = [item?.result, item?.text, body];
|
|
791
|
-
if (itemHasKnownTerminalStatus(item, sources)) return body;
|
|
792
|
-
if (!body.trim()) return `${CANCELLED_RESULT_STATUS_LINE}\n`;
|
|
793
|
-
return `${CANCELLED_RESULT_STATUS_LINE}\n${body}`;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
function groupedToolResultText(group) {
|
|
797
|
-
const completed = Math.min(group.count, group.completed);
|
|
798
|
-
if (group.count <= 1) return group.results.at(-1)?.text ?? '';
|
|
799
|
-
if (group.errors > 0) {
|
|
800
|
-
const succeeded = Math.max(0, completed - group.errors);
|
|
801
|
-
const reasons = group.results
|
|
802
|
-
.filter((result) => result?.isError)
|
|
803
|
-
.map((result) => firstErrorLine(result?.text))
|
|
804
|
-
.filter(Boolean);
|
|
805
|
-
const uniqueReasons = [...new Set(reasons)].slice(0, 2);
|
|
806
|
-
const base = succeeded > 0
|
|
807
|
-
? `${succeeded} Ok · ${group.errors} Failed`
|
|
808
|
-
: `${group.errors} Failed`;
|
|
809
|
-
return [
|
|
810
|
-
`${base}${uniqueReasons[0] ? ` · ${uniqueReasons[0]}` : ''}`,
|
|
811
|
-
...uniqueReasons.slice(1),
|
|
812
|
-
].join('\n');
|
|
813
|
-
}
|
|
814
|
-
for (const result of group.results || []) {
|
|
815
|
-
const line = String(result?.text || '').trim();
|
|
816
|
-
if (line) return result.text;
|
|
817
|
-
}
|
|
818
|
-
return '';
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
function firstErrorLine(text) {
|
|
822
|
-
const clean = toolErrorDisplay(text, 'tool');
|
|
823
|
-
if (clean) return clean;
|
|
824
|
-
for (const line of String(text || '').split('\n')) {
|
|
825
|
-
const trimmed = line.trim();
|
|
826
|
-
if (!trimmed) continue;
|
|
827
|
-
if (/^(Error|\[?error|FAIL\b)/i.test(trimmed)) return trimmed;
|
|
828
|
-
}
|
|
829
|
-
return String(text || '').split('\n').map((line) => line.trim()).find(Boolean) || '';
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
function aggregateRawResult(calls) {
|
|
833
|
-
const chunks = [];
|
|
834
|
-
for (const rec of calls || []) {
|
|
835
|
-
if (rec?.resolved !== true) continue;
|
|
836
|
-
let text = String(rec?.resultText || '').replace(/\s+$/, '');
|
|
837
|
-
if (!text.trim()) continue;
|
|
838
|
-
const label = String(rec?.name || rec?.category || 'tool').trim() || 'tool';
|
|
839
|
-
chunks.push(`${chunks.length + 1}. ${label}\n${text}`);
|
|
840
|
-
}
|
|
841
|
-
return chunks.join('\n\n');
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
function aggregateBucketForCategory(category) {
|
|
845
|
-
// Merge consecutive tool calls of the SAME category into one aggregate card;
|
|
846
|
-
// a different category opens a fresh card (no cross-category merge). The
|
|
847
|
-
// bucket key is the category itself, so a run of Search calls collapses into
|
|
848
|
-
// one Search card while an adjacent Read/Patch stays separate. Falls back to
|
|
849
|
-
// 'default' when a call has no resolved category. Hook/approval denials keep
|
|
850
|
-
// their dedicated ToolHookDenialCard path in App.jsx.
|
|
851
|
-
const key = String(category || '').trim();
|
|
852
|
-
return key ? `category:${key}` : 'default';
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
function aggregateSummaries(aggregate) {
|
|
856
|
-
return [...(aggregate?.calls?.values?.() || [])]
|
|
857
|
-
.filter((r) => r.summary)
|
|
858
|
-
.sort((a, b) => Number(a.summarySeq ?? 0) - Number(b.summarySeq ?? 0))
|
|
859
|
-
.map((r) => r.summary);
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
function assignAggregateSummaryOrder(aggregate, callRec) {
|
|
863
|
-
if (!aggregate || !callRec?.summary || callRec.summarySeq != null) return;
|
|
864
|
-
const next = Math.max(0, Number(aggregate.nextSummarySeq || 0));
|
|
865
|
-
callRec.summarySeq = next;
|
|
866
|
-
aggregate.nextSummarySeq = next + 1;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
function patchToolCardResult(card, message, toolGroups, done) {
|
|
870
|
-
if (!card || card.done) return false;
|
|
871
|
-
const callId = toolResultCallId(message) || card.callId;
|
|
872
|
-
if (callId && done.has(callId)) return false;
|
|
873
|
-
// Any resolving call clears its active-summary entry (keyed by the same
|
|
874
|
-
// callKey used at markToolCallActive; card.callId holds it for both branches).
|
|
875
|
-
markToolCallDone(card.callId);
|
|
876
|
-
// A result for this card arrived (possibly before its deferred push delay
|
|
877
|
-
// elapsed) — surface the card now so the patch below has a live item and the
|
|
878
|
-
// fast tool paints a completed card directly, no pending placeholder stage.
|
|
879
|
-
// ensureVisible flushes this card AND every earlier-created still-deferred
|
|
880
|
-
// card in order, so transcript order always matches call order.
|
|
881
|
-
(card.aggregate?.ensureVisible || card.ensureVisible)?.();
|
|
882
|
-
const rawText = toolResultText(message?.content);
|
|
883
|
-
const isError = message?.isError === true || message?.toolKind === 'error' || /^\s*\[?error/i.test(rawText) || isErrorToolStatus(toolResultStatus(rawText));
|
|
884
|
-
const text = isError ? toolErrorDisplay(rawText, card?.name || 'tool') : rawText;
|
|
885
|
-
|
|
886
|
-
// Aggregate card handling — collect semantic summaries per call
|
|
887
|
-
const aggregate = card.aggregate;
|
|
888
|
-
if (aggregate && card.itemId === aggregate.itemId) {
|
|
889
|
-
const callRec = callId ? aggregate.calls.get(callId) : null;
|
|
890
|
-
if (!callRec) return false;
|
|
891
|
-
if (callRec.resolved) {
|
|
892
|
-
card.done = true;
|
|
893
|
-
if (callId) done.add(callId);
|
|
894
|
-
return false;
|
|
895
|
-
}
|
|
896
|
-
callRec.summary = !isError ? summarizeToolResult(callRec.name, callRec.args, rawText, isError) : null;
|
|
897
|
-
assignAggregateSummaryOrder(aggregate, callRec);
|
|
898
|
-
callRec.isError = isError;
|
|
899
|
-
callRec.resultText = text;
|
|
900
|
-
callRec.resolved = true;
|
|
901
|
-
const allCalls = [...aggregate.calls.values()];
|
|
902
|
-
const completed = allCalls.filter((r) => r.resolved).length;
|
|
903
|
-
const errors = allCalls.filter((r) => r.isError).length;
|
|
904
|
-
// Collapsed detail carries the merged per-call count summary
|
|
905
|
-
// ("512 lines, 6 matches, 3 files") so the finished card answers "how
|
|
906
|
-
// much" without ctrl+o. Failures keep a bare 'N Ok · N Failed' status so
|
|
907
|
-
// an error stays visible while collapsed.
|
|
908
|
-
const succeeded = completed - errors;
|
|
909
|
-
const detailText = errors > 0
|
|
910
|
-
? (succeeded > 0 ? `${succeeded} Ok · ${errors} Failed` : `${errors} Failed`)
|
|
911
|
-
: formatAggregateDetail(aggregateSummaries(aggregate));
|
|
912
|
-
const currentItem = state.items.find((it) => it.id === card.itemId);
|
|
913
|
-
const earlyCompleted = allCalls.filter((r) => r.resolved || r.completedEarly).length;
|
|
914
|
-
const visualCompleted = Math.max(completed, earlyCompleted, Math.min(allCalls.length, Number(currentItem?.completedCount || 0)));
|
|
915
|
-
const rawResult = aggregateRawResult(allCalls);
|
|
916
|
-
// The numbered+labelled raw (rawResult) is preserved for ctrl+o expansion.
|
|
917
|
-
const displayDetail = detailText;
|
|
918
|
-
patchItem(card.itemId, {
|
|
919
|
-
result: displayDetail,
|
|
920
|
-
text: displayDetail,
|
|
921
|
-
rawResult: rawResult || null,
|
|
922
|
-
isError: errors > 0,
|
|
923
|
-
errorCount: errors,
|
|
924
|
-
count: allCalls.length,
|
|
925
|
-
completedCount: visualCompleted,
|
|
926
|
-
completedAt: Number(currentItem?.completedAt) || Date.now(),
|
|
927
|
-
});
|
|
928
|
-
card.done = true;
|
|
929
|
-
if (callId) done.add(callId);
|
|
930
|
-
return true;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
// Non-aggregate (legacy agent-job cards, etc.)
|
|
934
|
-
const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, results: [] };
|
|
935
|
-
group.completed = Math.min(group.count, group.completed + 1);
|
|
936
|
-
group.errors += isError ? 1 : 0;
|
|
937
|
-
group.results.push({ text, isError });
|
|
938
|
-
toolGroups.set(card.itemId, group);
|
|
939
|
-
const resultText = groupedToolResultText(group);
|
|
940
|
-
const displayResult = toolGroupedDisplayFallback(resultText, text, rawText);
|
|
941
|
-
const patch = {
|
|
942
|
-
result: displayResult,
|
|
943
|
-
text: displayResult,
|
|
944
|
-
isError: group.errors > 0,
|
|
945
|
-
errorCount: group.errors,
|
|
946
|
-
count: group.count,
|
|
947
|
-
completedCount: group.completed,
|
|
948
|
-
completedAt: Date.now(),
|
|
949
|
-
};
|
|
950
|
-
if (group.count <= 1) {
|
|
951
|
-
const body = String(text || rawText || '').trim();
|
|
952
|
-
if (body) patch.rawResult = text || rawText;
|
|
953
|
-
const parsedAgent = parseAgentJob(rawText);
|
|
954
|
-
if (parsedAgent) {
|
|
955
|
-
patch.args = agentArgsWithResultMetadata(state.items.find((it) => it.id === card.itemId)?.args, parsedAgent);
|
|
956
|
-
set(agentStatusState({ force: true }));
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
patchItem(card.itemId, patch);
|
|
960
|
-
if (group.count <= 1) updateAgentJobCard(card.itemId, rawText, isError);
|
|
961
|
-
card.done = true;
|
|
962
|
-
if (callId) done.add(callId);
|
|
963
|
-
return true;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
const flushToolResults = (messages, toolCards, cardByCallId, toolGroups, done, { finalize = false, cancelled = false } = {}) => {
|
|
967
|
-
const results = [];
|
|
968
|
-
for (const m of messages || []) {
|
|
969
|
-
if (!m || m.role !== 'tool') continue;
|
|
970
|
-
const callId = toolResultCallId(m);
|
|
971
|
-
results.push({ message: m, callId, used: false });
|
|
972
|
-
if (!callId || done.has(callId)) continue;
|
|
973
|
-
const card = cardByCallId.get(callId);
|
|
974
|
-
if (patchToolCardResult(card, m, toolGroups, done)) {
|
|
975
|
-
results[results.length - 1].used = true;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
const openCards = (toolCards || []).filter((card) => !card.done);
|
|
980
|
-
if (openCards.length === 0) return;
|
|
981
|
-
|
|
982
|
-
const unusedResults = results.filter((result) => !result.used);
|
|
983
|
-
const fallbackResults = unusedResults.slice(-openCards.length);
|
|
984
|
-
for (let i = 0; i < fallbackResults.length; i++) {
|
|
985
|
-
const card = openCards[i];
|
|
986
|
-
const result = fallbackResults[i];
|
|
987
|
-
if (!card || !result || card.done) continue;
|
|
988
|
-
if (patchToolCardResult(card, result.message, toolGroups, done)) {
|
|
989
|
-
if (result.callId) done.add(result.callId);
|
|
990
|
-
result.used = true;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
if (!finalize) return;
|
|
995
|
-
for (const card of toolCards || []) {
|
|
996
|
-
if (card.done) continue;
|
|
997
|
-
// Finalize must surface any still-deferred card before patching its result
|
|
998
|
-
// so the completed/cancelled card is never silently dropped.
|
|
999
|
-
(card.aggregate?.ensureVisible || card.ensureVisible)?.();
|
|
1000
|
-
// Aggregate finalize — mark any remaining calls as done
|
|
1001
|
-
const aggregate = card.aggregate;
|
|
1002
|
-
if (aggregate && card.itemId === aggregate.itemId) {
|
|
1003
|
-
const allCalls = [...aggregate.calls.values()];
|
|
1004
|
-
// Never let a call that truly never resolved be presented as a real
|
|
1005
|
-
// completion. Stamp it resolved so completedCount reflects an honest
|
|
1006
|
-
// (if degenerate) accounting instead of manufacturing success out of
|
|
1007
|
-
// a call that never came back. A record already marked completedEarly
|
|
1008
|
-
// (via __earlyNotify) already carries a real isError/resultText/summary
|
|
1009
|
-
// from its actual result — preserve those; only blank-fill for calls
|
|
1010
|
-
// truly never heard from (no completedEarly, no resolved).
|
|
1011
|
-
for (const rec of allCalls) {
|
|
1012
|
-
if (rec.resolved) continue;
|
|
1013
|
-
rec.resolved = true;
|
|
1014
|
-
if (!rec.completedEarly) {
|
|
1015
|
-
rec.isError = false;
|
|
1016
|
-
rec.resultText = rec.resultText || '';
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
const completed = allCalls.filter((r) => r.resolved).length;
|
|
1020
|
-
const totalCompleted = completed;
|
|
1021
|
-
const errors = allCalls.filter((r) => r.isError).length;
|
|
1022
|
-
const succeeded = completed - errors;
|
|
1023
|
-
const rawResult = aggregateRawResult(allCalls);
|
|
1024
|
-
// Collapsed detail carries the merged per-call count summary; failures
|
|
1025
|
-
// keep a bare 'N Ok · N Failed' status. Raw is kept for ctrl+o.
|
|
1026
|
-
let displayDetail = errors > 0
|
|
1027
|
-
? (succeeded > 0 ? `${succeeded} Ok · ${errors} Failed` : `${errors} Failed`)
|
|
1028
|
-
: formatAggregateDetail(aggregateSummaries(aggregate));
|
|
1029
|
-
if (cancelled) {
|
|
1030
|
-
// Cancelled aggregates MUST keep the [status: cancelled] marker on the
|
|
1031
|
-
// result so terminalStatus parsing resolves to 'cancelled'. Only normal
|
|
1032
|
-
// completions drop the summary; cancelled ones prepend the marker.
|
|
1033
|
-
const currentItem = state.items.find((it) => it.id === card.itemId);
|
|
1034
|
-
displayDetail = withCancelledResultMarker(displayDetail, currentItem);
|
|
1035
|
-
}
|
|
1036
|
-
patchItem(card.itemId, {
|
|
1037
|
-
result: displayDetail,
|
|
1038
|
-
text: displayDetail,
|
|
1039
|
-
rawResult: rawResult || null,
|
|
1040
|
-
isError: errors > 0,
|
|
1041
|
-
errorCount: errors,
|
|
1042
|
-
count: allCalls.length,
|
|
1043
|
-
completedCount: totalCompleted,
|
|
1044
|
-
completedAt: Date.now(),
|
|
1045
|
-
});
|
|
1046
|
-
for (const sibling of toolCards || []) {
|
|
1047
|
-
if (sibling.itemId !== card.itemId) continue;
|
|
1048
|
-
sibling.done = true;
|
|
1049
|
-
if (sibling.callId) done.add(sibling.callId);
|
|
1050
|
-
}
|
|
1051
|
-
continue;
|
|
1052
|
-
}
|
|
1053
|
-
// Non-aggregate finalize
|
|
1054
|
-
const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, results: [] };
|
|
1055
|
-
group.completed = Math.min(group.count, group.completed + 1);
|
|
1056
|
-
toolGroups.set(card.itemId, group);
|
|
1057
|
-
let resultText = groupedToolResultText(group);
|
|
1058
|
-
if (cancelled) {
|
|
1059
|
-
const currentItem = state.items.find((it) => it.id === card.itemId);
|
|
1060
|
-
resultText = withCancelledResultMarker(resultText, currentItem);
|
|
1061
|
-
}
|
|
1062
|
-
patchItem(card.itemId, { result: resultText, text: resultText, isError: group.errors > 0, errorCount: group.errors, count: group.count, completedCount: group.completed, completedAt: Date.now() });
|
|
1063
|
-
card.done = true;
|
|
1064
|
-
if (card.callId) done.add(card.callId);
|
|
1065
|
-
}
|
|
1066
|
-
};
|
|
592
|
+
const { patchToolCardResult, flushToolResults } = createToolCardResults({
|
|
593
|
+
getState: () => state,
|
|
594
|
+
set,
|
|
595
|
+
patchItem,
|
|
596
|
+
markToolCallDone,
|
|
597
|
+
updateAgentJobCard,
|
|
598
|
+
agentStatusState,
|
|
599
|
+
});
|
|
1067
600
|
|
|
1068
601
|
async function runTurn(userText, options = {}) {
|
|
1069
602
|
const turnIndex = state.stats.turns || 0;
|
|
@@ -1105,7 +638,7 @@ export async function createEngineSession({
|
|
|
1105
638
|
// cards (send() still in flight). Hold those by callId until the batch lands.
|
|
1106
639
|
const earlyResultBuffer = new Map();
|
|
1107
640
|
const aggregateCards = []; // active aggregate cards in the current consecutive tool block
|
|
1108
|
-
|
|
641
|
+
let tailAggregate = null; // most recently touched aggregate card; only the tail may absorb the next same-bucket call
|
|
1109
642
|
|
|
1110
643
|
// ── Deferred tool-card push (scroll/text sync) ────────────────────────────
|
|
1111
644
|
// A tool card used to enter the transcript the instant onToolCall fired,
|
|
@@ -1264,28 +797,25 @@ export async function createEngineSession({
|
|
|
1264
797
|
aggregateCards.length = 0;
|
|
1265
798
|
// Seal the block: same-bucket calls after this point must open a fresh
|
|
1266
799
|
// card, never continue one from before the seal (assistant text/turn
|
|
1267
|
-
// end boundary).
|
|
1268
|
-
|
|
1269
|
-
aggregateByBucket.clear();
|
|
800
|
+
// end boundary).
|
|
801
|
+
tailAggregate = null;
|
|
1270
802
|
};
|
|
1271
803
|
|
|
1272
804
|
const rememberActiveAggregate = (aggregate) => {
|
|
1273
805
|
if (!aggregate) return;
|
|
1274
806
|
if (!aggregateCards.includes(aggregate)) aggregateCards.push(aggregate);
|
|
1275
|
-
|
|
807
|
+
tailAggregate = aggregate;
|
|
1276
808
|
};
|
|
1277
809
|
|
|
1278
810
|
const ensureAggregateCard = (bucket) => {
|
|
1279
|
-
//
|
|
1280
|
-
//
|
|
1281
|
-
//
|
|
1282
|
-
//
|
|
1283
|
-
//
|
|
1284
|
-
//
|
|
1285
|
-
//
|
|
1286
|
-
|
|
1287
|
-
// cards that were pushed after it.
|
|
1288
|
-
const cached = aggregateByBucket.get(bucket);
|
|
811
|
+
// Only the TAIL aggregate (most recent card) may absorb the next call,
|
|
812
|
+
// and only when the bucket matches. Any different-bucket aggregate or
|
|
813
|
+
// standalone card in between breaks the run, so Search, Memory, Search
|
|
814
|
+
// renders as three cards in call order — a new call never merges into
|
|
815
|
+
// an earlier card above the current tail (which read as out-of-order
|
|
816
|
+
// count changes in the transcript). clearAggregateContinuation seals
|
|
817
|
+
// the block at assistant-text/turn boundaries.
|
|
818
|
+
const cached = tailAggregate && tailAggregate.bucket === bucket ? tailAggregate : null;
|
|
1289
819
|
if (cached) {
|
|
1290
820
|
rememberActiveAggregate(cached);
|
|
1291
821
|
return cached;
|
|
@@ -1632,7 +1162,11 @@ export async function createEngineSession({
|
|
|
1632
1162
|
// Category drives the aggregate bucket so only same-category calls
|
|
1633
1163
|
// merge into one card; classify first, then bucket by it.
|
|
1634
1164
|
const category = classifyToolCategory(name, args);
|
|
1635
|
-
|
|
1165
|
+
// Agent/bridge calls always get their own standalone card so
|
|
1166
|
+
// ToolExecution's isAgentTool/agentActionTitle path renders a
|
|
1167
|
+
// "Spawn <Agent> (model, tag)" header instead of being folded
|
|
1168
|
+
// into the "Called N agent(s)" category-aggregate card.
|
|
1169
|
+
const bucket = category === 'Agent' ? null : aggregateBucketForCategory(category);
|
|
1636
1170
|
const callId = toolCallId(c);
|
|
1637
1171
|
const callKey = callId || `__tool_${toolCards.length}_${i}`;
|
|
1638
1172
|
// The old App scan counted multi-pattern calls via
|
|
@@ -1673,6 +1207,10 @@ export async function createEngineSession({
|
|
|
1673
1207
|
cardByCallId.set(callId, card);
|
|
1674
1208
|
}
|
|
1675
1209
|
toolCards.push(card);
|
|
1210
|
+
// A standalone card (Agent) breaks the consecutive run too: a
|
|
1211
|
+
// later same-bucket call must open a fresh card BELOW it, not
|
|
1212
|
+
// merge into an aggregate above it.
|
|
1213
|
+
tailAggregate = null;
|
|
1676
1214
|
continue;
|
|
1677
1215
|
}
|
|
1678
1216
|
|
|
@@ -2856,6 +2394,16 @@ export async function createEngineSession({
|
|
|
2856
2394
|
set({ remoteEnabled: next });
|
|
2857
2395
|
return next;
|
|
2858
2396
|
},
|
|
2397
|
+
// Force-claim remote for this session (single-holder, last-wins). Always
|
|
2398
|
+
// turns remote ON here and steals the bridge seat; the previous holder is
|
|
2399
|
+
// superseded and flips itself OFF via onRemoteStateChange. Used by the
|
|
2400
|
+
// `/remote` slash command — repeated /remote just re-claims (idempotent).
|
|
2401
|
+
claimRemote: () => {
|
|
2402
|
+
runtime.startRemote?.();
|
|
2403
|
+
const next = runtime.isRemoteEnabled?.() === true;
|
|
2404
|
+
set({ remoteEnabled: next });
|
|
2405
|
+
return next;
|
|
2406
|
+
},
|
|
2859
2407
|
isRemoteEnabled: () => runtime.isRemoteEnabled?.() === true,
|
|
2860
2408
|
// Theme is a TUI-local concern (no runtime round-trip). listThemes returns
|
|
2861
2409
|
// picker metadata; getTheme reports the active id; setTheme applies the
|
|
@@ -2938,6 +2486,15 @@ export async function createEngineSession({
|
|
|
2938
2486
|
'info');
|
|
2939
2487
|
return true;
|
|
2940
2488
|
},
|
|
2489
|
+
loginOpenCodeGoUsage: async () => {
|
|
2490
|
+
if (state.commandBusy) throw new Error('command busy');
|
|
2491
|
+
set({ commandBusy: true });
|
|
2492
|
+
try {
|
|
2493
|
+
return await runtime.loginOpenCodeGoUsage();
|
|
2494
|
+
} finally {
|
|
2495
|
+
set({ commandBusy: false });
|
|
2496
|
+
}
|
|
2497
|
+
},
|
|
2941
2498
|
saveOpenAIUsageSessionKey: (secret) => {
|
|
2942
2499
|
runtime.saveOpenAIUsageSessionKey(secret);
|
|
2943
2500
|
pushNotice('OpenAI usage auth saved', 'info');
|
|
@@ -3062,6 +2619,7 @@ export async function createEngineSession({
|
|
|
3062
2619
|
}
|
|
3063
2620
|
},
|
|
3064
2621
|
pushNotice,
|
|
2622
|
+
removeNotice,
|
|
3065
2623
|
setProgressHint,
|
|
3066
2624
|
clear: async () => {
|
|
3067
2625
|
if (state.commandBusy) return false;
|
|
@@ -3199,6 +2757,8 @@ export async function createEngineSession({
|
|
|
3199
2757
|
try { clearInterval(runtimePulseTimer); } catch {}
|
|
3200
2758
|
try { unsubscribeRuntimeNotifications?.(); } catch {}
|
|
3201
2759
|
unsubscribeRuntimeNotifications = null;
|
|
2760
|
+
try { unsubscribeRemoteState?.(); } catch {}
|
|
2761
|
+
unsubscribeRemoteState = null;
|
|
3202
2762
|
denyAllToolApprovals('runtime closing');
|
|
3203
2763
|
await runtime.close(reason, options);
|
|
3204
2764
|
listeners.clear();
|