mixdog 0.9.1 → 0.9.3
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 +9 -1
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/anthropic-maxtokens-test.mjs +119 -0
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/build-tui.mjs +13 -1
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/explore-bench.mjs +124 -0
- package/scripts/hook-bus-test.mjs +191 -0
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/path-suffix-test.mjs +57 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/recall-bench.mjs +207 -0
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +310 -67
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +20 -9
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +17 -9
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/rules-builder.cjs +32 -54
- package/src/mixdog-session-runtime.mjs +1040 -2036
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +17 -7
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +8 -12
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +13 -2
- package/src/rules/shared/01-tool.md +23 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
- package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
- package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
- package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +437 -1439
- package/src/runtime/channels/lib/boot-profile.mjs +23 -0
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/crash-log.mjs +106 -0
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +280 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/lib/whisper-language.mjs +42 -0
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +465 -345
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
- package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
- package/src/runtime/memory/lib/http-wire.mjs +57 -0
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
- package/src/runtime/memory/lib/memory.mjs +121 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
- package/src/runtime/memory/lib/recall-format.mjs +183 -0
- package/src/runtime/memory/lib/session-ingest.mjs +107 -0
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +10 -7
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/background-tasks.mjs +2 -3
- package/src/runtime/shared/buffered-appender.mjs +149 -0
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/task-notification-envelope.mjs +98 -0
- package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -2
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +156 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/session-runtime/config-helpers.mjs +209 -0
- package/src/session-runtime/effort.mjs +128 -0
- package/src/session-runtime/fs-utils.mjs +10 -0
- package/src/session-runtime/model-capabilities.mjs +130 -0
- package/src/session-runtime/output-styles.mjs +124 -0
- package/src/session-runtime/plugin-mcp.mjs +114 -0
- package/src/session-runtime/session-text.mjs +100 -0
- package/src/session-runtime/statusline-route.mjs +35 -0
- package/src/session-runtime/tool-catalog.mjs +720 -0
- package/src/session-runtime/workflow.mjs +358 -0
- package/src/standalone/agent-tool.mjs +302 -117
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +10 -292
- package/src/standalone/explore-tool.mjs +12 -5
- package/src/standalone/hook-bus.mjs +165 -8
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/opencode-go-login.mjs +121 -0
- package/src/standalone/provider-admin.mjs +25 -3
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2883 -778
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +355 -22
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +183 -101
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +161 -23
- package/src/tui/components/tool-output-format.test.mjs +87 -0
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +6731 -2333
- package/src/tui/engine/agent-envelope.mjs +296 -0
- package/src/tui/engine/boot-profile.mjs +21 -0
- package/src/tui/engine/labels.mjs +67 -0
- package/src/tui/engine/notice-text.mjs +112 -0
- package/src/tui/engine/queue-helpers.mjs +161 -0
- package/src/tui/engine/session-stats.mjs +46 -0
- package/src/tui/engine/tool-call-fields.mjs +23 -0
- package/src/tui/engine/tool-result-text.mjs +126 -0
- package/src/tui/engine.mjs +569 -948
- package/src/tui/index.jsx +117 -7
- package/src/tui/input-editing.mjs +58 -8
- package/src/tui/input-editing.selection.test.mjs +75 -0
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +469 -0
- package/src/tui/markdown/format-token.mjs +128 -76
- package/src/tui/markdown/format-token.test.mjs +61 -19
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +36 -9
- package/src/tui/paste-fix.test.mjs +119 -0
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -657
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +80 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +75 -27
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +46 -12
- package/src/workflows/sequential/WORKFLOW.md +51 -0
- package/src/workflows/solo/WORKFLOW.md +12 -1
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +100 -12
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/agent-envelope.mjs - parse agent/background-task/shell
|
|
3
|
+
* notification envelopes into synthetic tool-item shapes, and derive
|
|
4
|
+
* status/result text. Extracted from engine.mjs; parseBackgroundTaskEnvelope
|
|
5
|
+
* remains part of engine.mjs's public surface.
|
|
6
|
+
*/
|
|
7
|
+
import { isBackgroundErrorOnlyBody } from '../../runtime/shared/err-text.mjs';
|
|
8
|
+
|
|
9
|
+
export function textBetweenTag(text, tag) {
|
|
10
|
+
const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'i');
|
|
11
|
+
const match = re.exec(String(text ?? ''));
|
|
12
|
+
return match ? match[1].trim() : '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function stripSyntheticAgentTags(text) {
|
|
16
|
+
const value = String(text ?? '').trim();
|
|
17
|
+
const finalAnswer = textBetweenTag(value, 'final-answer');
|
|
18
|
+
if (finalAnswer) return finalAnswer;
|
|
19
|
+
const taskResult = textBetweenTag(value, 'result');
|
|
20
|
+
if (taskResult) return taskResult;
|
|
21
|
+
return value
|
|
22
|
+
.replace(/^agent result[^\n]*(?:\n|$)/i, '')
|
|
23
|
+
.replace(/<\/?(?:final-answer|task-notification|task-id|tool-use-id|output-file|result|status|summary|usage|total_tokens|tool_uses|duration_ms|worktree|worktreePath|worktreeBranch)[^>]*>/gi, '')
|
|
24
|
+
.trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function splitBridgeEnvelope(text) {
|
|
28
|
+
const value = String(text ?? '').trim();
|
|
29
|
+
if (!value) return { head: '', body: '' };
|
|
30
|
+
const match = /\n\s*\n/.exec(value);
|
|
31
|
+
if (!match) return { head: value, body: '' };
|
|
32
|
+
return {
|
|
33
|
+
head: value.slice(0, match.index).trim(),
|
|
34
|
+
body: value.slice(match.index + match[0].length).trim(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function agentJobStatusText(parsed) {
|
|
39
|
+
if (!parsed) return '';
|
|
40
|
+
const parts = [];
|
|
41
|
+
if (parsed.status) parts.push(`status: ${parsed.status}`);
|
|
42
|
+
if (parsed.taskId) parts.push(`task_id: ${parsed.taskId}`);
|
|
43
|
+
return parts.join(' · ');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function agentJobResultText(text, parsed = parseAgentJob(text)) {
|
|
47
|
+
const value = String(text ?? '').trim();
|
|
48
|
+
if (!value) return '';
|
|
49
|
+
if (parsed?.taskId) {
|
|
50
|
+
const { body } = splitBridgeEnvelope(value);
|
|
51
|
+
const cleanBody = stripSyntheticAgentTags(body);
|
|
52
|
+
if (cleanBody) return cleanBody;
|
|
53
|
+
return agentJobStatusText(parsed);
|
|
54
|
+
}
|
|
55
|
+
return stripSyntheticAgentTags(value) || value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function parseAgentResultEnvelope(text, fallback = {}) {
|
|
59
|
+
const value = String(text ?? '').trim();
|
|
60
|
+
if (!/^agent result\b/i.test(value)) return null;
|
|
61
|
+
const [head = '', ...restLines] = value.split('\n');
|
|
62
|
+
const body = stripSyntheticAgentTags(restLines.join('\n'));
|
|
63
|
+
const attrs = {};
|
|
64
|
+
const attrRe = /([a-zA-Z][\w-]*)=("[^"]*"|'[^']*'|\S+)/g;
|
|
65
|
+
let match;
|
|
66
|
+
while ((match = attrRe.exec(head))) {
|
|
67
|
+
attrs[match[1].toLowerCase()] = String(match[2] || '').replace(/^["']|["']$/g, '');
|
|
68
|
+
}
|
|
69
|
+
const providerModel = /\s([a-zA-Z0-9_.-]+)\/([^\s]+)\s*$/i.exec(head);
|
|
70
|
+
const agent = attrs.agent || fallback.agent || '';
|
|
71
|
+
return {
|
|
72
|
+
name: 'agent',
|
|
73
|
+
label: String(fallback.status || attrs.status || 'completed').toLowerCase(),
|
|
74
|
+
args: {
|
|
75
|
+
type: 'result',
|
|
76
|
+
status: fallback.status || attrs.status || 'completed',
|
|
77
|
+
task_id: fallback.taskId || attrs.task_id || attrs.taskid || undefined,
|
|
78
|
+
tag: fallback.tag || attrs.tag || undefined,
|
|
79
|
+
agent: agent || undefined,
|
|
80
|
+
provider: fallback.provider || attrs.provider || providerModel?.[1] || undefined,
|
|
81
|
+
model: fallback.model || attrs.model || providerModel?.[2] || undefined,
|
|
82
|
+
preset: fallback.preset || attrs.preset || undefined,
|
|
83
|
+
effort: fallback.effort || attrs.effort || undefined,
|
|
84
|
+
fast: fallback.fast ?? attrs.fast,
|
|
85
|
+
},
|
|
86
|
+
result: body || agentJobStatusText({ status: fallback.status || attrs.status || 'completed', taskId: fallback.taskId || attrs.task_id || attrs.taskid || '' }),
|
|
87
|
+
isError: /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(fallback.status || attrs.status || ''),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function parseBackgroundTaskEnvelope(text) {
|
|
92
|
+
const value = String(text ?? '').trim();
|
|
93
|
+
if (!/^background task\b/i.test(value)) return null;
|
|
94
|
+
const allLines = value.split('\n');
|
|
95
|
+
const rest = allLines.slice(1);
|
|
96
|
+
const blank = rest.findIndex((line) => !line.trim());
|
|
97
|
+
const headLines = blank >= 0 ? rest.slice(0, blank) : rest;
|
|
98
|
+
const body = blank >= 0 ? rest.slice(blank + 1).join('\n').trim() : '';
|
|
99
|
+
const fields = {};
|
|
100
|
+
for (const line of headLines) {
|
|
101
|
+
const match = /^([a-zA-Z][\w-]*):\s*(.*)$/.exec(line.trim());
|
|
102
|
+
if (match) fields[match[1].toLowerCase()] = match[2].trim();
|
|
103
|
+
}
|
|
104
|
+
const surface = String(fields.surface || fields.operation || 'task').toLowerCase();
|
|
105
|
+
const name = surface === 'explore' || surface === 'search' || surface === 'shell' || surface === 'agent' ? surface : 'task';
|
|
106
|
+
const status = String(fields.status || '').toLowerCase();
|
|
107
|
+
const taskId = fields.task_id || fields.taskid || '';
|
|
108
|
+
const errorText = fields.error || '';
|
|
109
|
+
const agentResult = parseAgentResultEnvelope(body, {
|
|
110
|
+
status,
|
|
111
|
+
taskId,
|
|
112
|
+
tag: fields.tag || fields.label || '',
|
|
113
|
+
agent: fields.agent || '',
|
|
114
|
+
provider: fields.provider || '',
|
|
115
|
+
model: fields.model || '',
|
|
116
|
+
preset: fields.preset || '',
|
|
117
|
+
effort: fields.effort || '',
|
|
118
|
+
fast: fields.fast,
|
|
119
|
+
});
|
|
120
|
+
if (agentResult) return { ...agentResult, rawResult: value };
|
|
121
|
+
const errorOnlyBody = isBackgroundErrorOnlyBody(body, errorText);
|
|
122
|
+
const resultBody = body && !errorOnlyBody ? body : '';
|
|
123
|
+
return {
|
|
124
|
+
name,
|
|
125
|
+
label: status || 'notification',
|
|
126
|
+
args: {
|
|
127
|
+
type: body ? 'result' : (fields.operation || 'status'),
|
|
128
|
+
status,
|
|
129
|
+
task_id: taskId || undefined,
|
|
130
|
+
surface,
|
|
131
|
+
operation: fields.operation || undefined,
|
|
132
|
+
label: fields.label || undefined,
|
|
133
|
+
tag: fields.tag || undefined,
|
|
134
|
+
agent: fields.agent || undefined,
|
|
135
|
+
provider: fields.provider || undefined,
|
|
136
|
+
model: fields.model || undefined,
|
|
137
|
+
preset: fields.preset || undefined,
|
|
138
|
+
effort: fields.effort || undefined,
|
|
139
|
+
fast: fields.fast || undefined,
|
|
140
|
+
error: errorText || undefined,
|
|
141
|
+
startedAt: fields.started || fields.startedat || undefined,
|
|
142
|
+
finishedAt: fields.finished || fields.finishedat || undefined,
|
|
143
|
+
},
|
|
144
|
+
result: resultBody || (!errorText ? [status ? `status: ${status}` : '', taskId ? `task_id: ${taskId}` : ''].filter(Boolean).join(' · ') : ''),
|
|
145
|
+
rawResult: value,
|
|
146
|
+
isError: /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(status) || /^error:/i.test(body) || Boolean(errorText),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function isStatusOnlyAgentCompletionNotification(text) {
|
|
151
|
+
const background = parseBackgroundTaskEnvelope(text);
|
|
152
|
+
if (background?.name === 'agent' && /^(completed|cancelled|canceled)$/i.test(background.label || '')) {
|
|
153
|
+
return !(hasAgentResponseResultText(background.result) || hasAgentResponseResultText(text));
|
|
154
|
+
}
|
|
155
|
+
const parsed = parseAgentJob(text);
|
|
156
|
+
const result = agentJobResultText(text, parsed);
|
|
157
|
+
if (!parsed?.taskId || !/^(completed|cancelled|canceled)$/i.test(parsed.status || '')) return false;
|
|
158
|
+
return !(hasAgentResponseResultText(result) || hasAgentResponseResultText(text));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function hasAgentResponseResultText(text) {
|
|
162
|
+
const value = String(text || '').trim();
|
|
163
|
+
if (!value) return false;
|
|
164
|
+
if (/^status:\s*(?:running|pending|queued|completed|failed|cancelled|canceled)(?:\s*·\s*task_id:\s*\S+)?$/i.test(value)) return false;
|
|
165
|
+
if (/^(?:background task\b|agent task:|task_id:)/i.test(value) && !/\n\s*\n[\s\S]*\S/.test(value)) return false;
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function bracketField(text, name) {
|
|
170
|
+
const re = new RegExp(`^\\[${name}:\\s*([^\\]]*)\\]`, 'mi');
|
|
171
|
+
return re.exec(String(text ?? ''))?.[1]?.trim() || '';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function toolResultStatus(text) {
|
|
175
|
+
const value = String(text ?? '');
|
|
176
|
+
const tagged = textBetweenTag(value, 'status');
|
|
177
|
+
if (tagged) return tagged.trim();
|
|
178
|
+
const bracketed = bracketField(value, 'status');
|
|
179
|
+
if (bracketed) return bracketed.trim();
|
|
180
|
+
const inline = /^(?:status|state):\s*([^\s·,;]+)/mi.exec(value);
|
|
181
|
+
return inline ? inline[1].trim() : '';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function isErrorToolStatus(status) {
|
|
185
|
+
return /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(String(status || '').trim());
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function parseSyntheticAgentMessage(text) {
|
|
189
|
+
const value = String(text ?? '').trim();
|
|
190
|
+
if (!value) return null;
|
|
191
|
+
const finalAnswer = textBetweenTag(value, 'final-answer');
|
|
192
|
+
if (finalAnswer) {
|
|
193
|
+
return {
|
|
194
|
+
name: 'agent',
|
|
195
|
+
label: 'final',
|
|
196
|
+
args: { type: 'read', description: 'agent result' },
|
|
197
|
+
result: finalAnswer,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const agentResult = parseAgentResultEnvelope(value);
|
|
201
|
+
if (agentResult) return agentResult;
|
|
202
|
+
const backgroundTask = parseBackgroundTaskEnvelope(value);
|
|
203
|
+
if (backgroundTask) return backgroundTask;
|
|
204
|
+
const shellTaskId = bracketField(value, 'task_id');
|
|
205
|
+
if (shellTaskId) {
|
|
206
|
+
const status = bracketField(value, 'status') || 'done';
|
|
207
|
+
const exit = bracketField(value, 'exit');
|
|
208
|
+
const command = bracketField(value, 'command');
|
|
209
|
+
return {
|
|
210
|
+
name: 'shell',
|
|
211
|
+
label: status,
|
|
212
|
+
args: { type: 'result', task_id: shellTaskId, command },
|
|
213
|
+
result: value,
|
|
214
|
+
isError: /^(failed|error|timeout|cancelled|killed)$/i.test(status) || (exit && exit !== '0' && exit !== 'n/a'),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const agentJob = parseAgentJob(value);
|
|
218
|
+
if (agentJob?.taskId) {
|
|
219
|
+
const label = agentJob.status || 'notification';
|
|
220
|
+
const result = agentJobResultText(value, agentJob);
|
|
221
|
+
return {
|
|
222
|
+
name: 'agent',
|
|
223
|
+
label,
|
|
224
|
+
args: agentArgsWithResultMetadata({ type: agentJob.type || 'notification', description: 'agent notification' }, agentJob),
|
|
225
|
+
result: result || agentJobStatusText(agentJob) || 'agent notification',
|
|
226
|
+
isError: /^(failed|error|timeout|cancelled|killed)$/i.test(label),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (/<task-notification\b/i.test(value)) {
|
|
230
|
+
const status = textBetweenTag(value, 'status') || 'completed';
|
|
231
|
+
const summary = textBetweenTag(value, 'summary') || `Agent ${status}`;
|
|
232
|
+
const taskId = textBetweenTag(value, 'task-id');
|
|
233
|
+
const result = stripSyntheticAgentTags(value);
|
|
234
|
+
return {
|
|
235
|
+
name: 'agent',
|
|
236
|
+
label: status,
|
|
237
|
+
taskId,
|
|
238
|
+
summary,
|
|
239
|
+
result: result || summary,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function parseAgentJob(text) {
|
|
246
|
+
const value = String(text || '');
|
|
247
|
+
const idMatch = /^agent task:\s*([^\s]+)/m.exec(value) || /^task_id:\s*([^\s]+)/m.exec(value);
|
|
248
|
+
if (!idMatch) return null;
|
|
249
|
+
const statusMatch = /^status:\s*([^\s(]+)/m.exec(value);
|
|
250
|
+
const typeMatch = /^type:\s*(.+)$/m.exec(value);
|
|
251
|
+
const targetMatch = /^target:\s*(.+)$/m.exec(value);
|
|
252
|
+
const agentMatch = /^agent:\s*(.+)$/m.exec(value);
|
|
253
|
+
const presetMatch = /^preset:\s*(.+)$/m.exec(value);
|
|
254
|
+
const modelMatch = /^model:\s*([^/\s]+)\/(.+)$/m.exec(value);
|
|
255
|
+
const effortMatch = /^effort:\s*(.+)$/m.exec(value);
|
|
256
|
+
const fastMatch = /^fast:\s*(on|off|true|false)$/m.exec(value);
|
|
257
|
+
return {
|
|
258
|
+
taskId: idMatch[1],
|
|
259
|
+
status: (statusMatch?.[1] || '').toLowerCase(),
|
|
260
|
+
type: (typeMatch?.[1] || '').trim(),
|
|
261
|
+
target: (targetMatch?.[1] || '').trim(),
|
|
262
|
+
agent: (agentMatch?.[1] || '').trim(),
|
|
263
|
+
preset: (presetMatch?.[1] || '').trim(),
|
|
264
|
+
provider: (modelMatch?.[1] || '').trim(),
|
|
265
|
+
model: (modelMatch?.[2] || '').trim(),
|
|
266
|
+
effort: (effortMatch?.[1] || '').trim(),
|
|
267
|
+
fast: fastMatch ? /^(on|true)$/i.test(fastMatch[1]) : undefined,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function agentArgsWithResultMetadata(args, parsed) {
|
|
272
|
+
if (!parsed) return args;
|
|
273
|
+
const next = { ...(args && typeof args === 'object' ? args : {}) };
|
|
274
|
+
const requestedAction = String(next.type || next.action || next.mode || '').trim().toLowerCase();
|
|
275
|
+
if (parsed.type) {
|
|
276
|
+
// Job status envelopes report the original job type (usually "spawn").
|
|
277
|
+
// Preserve the user's current agent tool action ("status", "read", …) so
|
|
278
|
+
// manual checks render as "Reviewer status" instead of another
|
|
279
|
+
// "Spawning Reviewer" card. Keep the job type as metadata for detail.
|
|
280
|
+
if (!requestedAction || /^(notification|result|completion)$/i.test(requestedAction)) next.type = parsed.type;
|
|
281
|
+
else next.jobType = parsed.type;
|
|
282
|
+
}
|
|
283
|
+
if (parsed.status) next.status = parsed.status;
|
|
284
|
+
if (parsed.taskId) next.task_id = parsed.taskId;
|
|
285
|
+
if (parsed.agent) next.agent = parsed.agent;
|
|
286
|
+
if (parsed.preset) next.preset = parsed.preset;
|
|
287
|
+
if (parsed.provider) next.provider = parsed.provider;
|
|
288
|
+
if (parsed.model) next.model = parsed.model;
|
|
289
|
+
if (parsed.effort) next.effort = parsed.effort;
|
|
290
|
+
if (parsed.fast !== undefined) next.fast = parsed.fast;
|
|
291
|
+
if (!next.tag && parsed.target) {
|
|
292
|
+
const target = parsed.target.split(/\s+/)[0];
|
|
293
|
+
if (target && !target.startsWith('sess_')) next.tag = target;
|
|
294
|
+
}
|
|
295
|
+
return next;
|
|
296
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/boot-profile.mjs - opt-in boot timing trace.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from engine.mjs. Emits `[mixdog-boot]` stderr lines when
|
|
5
|
+
* MIXDOG_BOOT_PROFILE is truthy; a no-op otherwise.
|
|
6
|
+
*/
|
|
7
|
+
import { performance } from 'node:perf_hooks';
|
|
8
|
+
|
|
9
|
+
const BOOT_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ''));
|
|
10
|
+
const BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance.now());
|
|
11
|
+
|
|
12
|
+
export function bootProfile(event, fields = {}) {
|
|
13
|
+
if (!BOOT_PROFILE_ENABLED) return;
|
|
14
|
+
const elapsedMs = performance.now() - BOOT_PROFILE_START;
|
|
15
|
+
const parts = [`[mixdog-boot] +${elapsedMs.toFixed(1)}ms`, `tui:${event}`];
|
|
16
|
+
for (const [key, value] of Object.entries(fields || {})) {
|
|
17
|
+
if (value === undefined || value === null || value === '') continue;
|
|
18
|
+
parts.push(`${key}=${String(value).replace(/\s+/g, '_')}`);
|
|
19
|
+
}
|
|
20
|
+
try { process.stderr.write(`${parts.join(' ')}\n`); } catch {}
|
|
21
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/labels.mjs - spinner verbs, elapsed formatting, and compact
|
|
3
|
+
* event label/detail helpers. Extracted from engine.mjs.
|
|
4
|
+
*/
|
|
5
|
+
import { SPINNER_VERBS } from '../spinner-verbs.mjs';
|
|
6
|
+
|
|
7
|
+
export function pickVerb(turn) {
|
|
8
|
+
return SPINNER_VERBS[(turn * 7 + 3) % SPINNER_VERBS.length];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const TURN_DONE_VERBS = [
|
|
12
|
+
'Thought',
|
|
13
|
+
'Reasoned',
|
|
14
|
+
'Mapped',
|
|
15
|
+
'Checked',
|
|
16
|
+
'Solved',
|
|
17
|
+
'Composed',
|
|
18
|
+
'Synthesized',
|
|
19
|
+
'Wrapped',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export function pickDoneVerb(turn) {
|
|
23
|
+
return TURN_DONE_VERBS[(turn * 5 + 2) % TURN_DONE_VERBS.length];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function formatElapsedSeconds(ms) {
|
|
27
|
+
const value = Math.max(0, Number(ms) || 0);
|
|
28
|
+
if (value <= 0) return '0s';
|
|
29
|
+
return `${Math.max(1, Math.ceil(value / 1000))}s`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function compactEventLabel(event = {}) {
|
|
33
|
+
const status = String(event.status || '').toLowerCase();
|
|
34
|
+
const reactive = String(event.trigger || '').toLowerCase() === 'reactive';
|
|
35
|
+
if (status === 'failed') return reactive ? 'Compact failed (overflow retry)' : 'Compact failed';
|
|
36
|
+
if (status === 'skipped') return 'Compact skipped';
|
|
37
|
+
if (status === 'no_change') return 'Compact checked';
|
|
38
|
+
return reactive ? 'Compact complete (overflow recovery)' : 'Compact complete';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function compactEventDetail(event = {}) {
|
|
42
|
+
// Keep the elapsed time as the lead detail, but no longer discard the rest of
|
|
43
|
+
// the compact metadata. Surface type/trigger and the boundary/pressure so the
|
|
44
|
+
// statusdone marker reflects what actually fired.
|
|
45
|
+
const parts = [];
|
|
46
|
+
const elapsed = formatElapsedSeconds(Number(event.durationMs ?? event.elapsedMs ?? 0));
|
|
47
|
+
if (elapsed) parts.push(elapsed);
|
|
48
|
+
const type = String(event.compactType || event.type || '').trim();
|
|
49
|
+
if (type && type !== 'semantic') parts.push(type);
|
|
50
|
+
const trigger = String(event.trigger || '').toLowerCase();
|
|
51
|
+
if (trigger === 'reactive') parts.push('reactive');
|
|
52
|
+
else if (trigger === 'manual') parts.push('manual');
|
|
53
|
+
const before = Number(event.beforeTokens ?? event.pressureTokens ?? 0);
|
|
54
|
+
const after = Number(event.afterTokens ?? 0);
|
|
55
|
+
const fmtTok = (n) => {
|
|
56
|
+
const v = Number(n) || 0;
|
|
57
|
+
if (v >= 1000) return `${(v / 1000).toFixed(v >= 10_000 ? 0 : 1)}k`;
|
|
58
|
+
return `${Math.round(v)}`;
|
|
59
|
+
};
|
|
60
|
+
if (before > 0 && after > 0 && after !== before) parts.push(`${fmtTok(before)}→${fmtTok(after)}`);
|
|
61
|
+
return parts.join(' · ');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function projectNameFromPath(value) {
|
|
65
|
+
const text = String(value || '').replace(/[\\/]+$/, '');
|
|
66
|
+
return text.split(/[\\/]/).pop() || text || '(current)';
|
|
67
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/notice-text.mjs - polish user-facing failure/notice text.
|
|
3
|
+
* Extracted from engine.mjs.
|
|
4
|
+
*/
|
|
5
|
+
const FAILED_NOTICE_ACTIONS = new Map([
|
|
6
|
+
['api key save', 'save API key'],
|
|
7
|
+
['auth-forget', 'forget auth'],
|
|
8
|
+
['auto-clear', 'update auto-clear'],
|
|
9
|
+
['autoclear', 'update auto-clear'],
|
|
10
|
+
['agent', 'run agent command'],
|
|
11
|
+
['channels', 'load channels'],
|
|
12
|
+
['channels update', 'update channels'],
|
|
13
|
+
['clear', 'clear chat'],
|
|
14
|
+
['compact', 'compact context'],
|
|
15
|
+
['copy', 'copy'],
|
|
16
|
+
['core memory', 'load core memory'],
|
|
17
|
+
['cwd', 'update working directory'],
|
|
18
|
+
['effort switch', 'switch effort'],
|
|
19
|
+
['fast', 'update fast mode'],
|
|
20
|
+
['hook rule update', 'update hook rule'],
|
|
21
|
+
['hook toggle', 'toggle hook'],
|
|
22
|
+
['hook update', 'update hook'],
|
|
23
|
+
['hooks status', 'load hooks'],
|
|
24
|
+
['local provider update', 'update local provider'],
|
|
25
|
+
['mcp add', 'add MCP server'],
|
|
26
|
+
['mcp reconnect', 'reconnect MCP server'],
|
|
27
|
+
['mcp status', 'load MCP status'],
|
|
28
|
+
['mcp toggle', 'toggle MCP server'],
|
|
29
|
+
['memory', 'run memory command'],
|
|
30
|
+
['memory status', 'load memory status'],
|
|
31
|
+
['model save', 'save model'],
|
|
32
|
+
['model switch', 'switch model'],
|
|
33
|
+
['oauth code', 'finish OAuth login'],
|
|
34
|
+
['oauth login', 'start OAuth login'],
|
|
35
|
+
['output style switch', 'switch output style'],
|
|
36
|
+
['OpenAI usage auth save', 'save OpenAI usage auth'],
|
|
37
|
+
['OpenCode Go usage auth save', 'save OpenCode Go usage auth'],
|
|
38
|
+
['plugin add', 'add plugin'],
|
|
39
|
+
['plugin MCP enable', 'enable plugin MCP'],
|
|
40
|
+
['plugin uninstall', 'uninstall plugin'],
|
|
41
|
+
['plugin update', 'update plugin'],
|
|
42
|
+
['plugins status', 'load plugins'],
|
|
43
|
+
['providers', 'load providers'],
|
|
44
|
+
['recall', 'run recall'],
|
|
45
|
+
['resume', 'resume chat'],
|
|
46
|
+
['schedule toggle', 'toggle schedule'],
|
|
47
|
+
['setup save', 'save setup'],
|
|
48
|
+
['settings update', 'update settings'],
|
|
49
|
+
['skill add', 'add skill'],
|
|
50
|
+
['skills status', 'load skills'],
|
|
51
|
+
['tools status', 'load tool status'],
|
|
52
|
+
['usage', 'load usage'],
|
|
53
|
+
['webhook toggle', 'toggle webhook'],
|
|
54
|
+
['workflow switch', 'switch workflow'],
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
function polishNoticeAction(action) {
|
|
58
|
+
const value = String(action || '').trim();
|
|
59
|
+
if (!value) return 'finish';
|
|
60
|
+
const key = value.toLowerCase();
|
|
61
|
+
for (const [candidate, replacement] of FAILED_NOTICE_ACTIONS.entries()) {
|
|
62
|
+
if (candidate.toLowerCase() === key) return replacement;
|
|
63
|
+
}
|
|
64
|
+
const suffixes = [
|
|
65
|
+
[' save', 'save'],
|
|
66
|
+
[' switch', 'switch'],
|
|
67
|
+
[' update', 'update'],
|
|
68
|
+
[' toggle', 'toggle'],
|
|
69
|
+
[' reconnect', 'reconnect'],
|
|
70
|
+
[' enable', 'enable'],
|
|
71
|
+
[' uninstall', 'uninstall'],
|
|
72
|
+
[' add', 'add'],
|
|
73
|
+
];
|
|
74
|
+
for (const [suffix, verb] of suffixes) {
|
|
75
|
+
if (!key.endsWith(suffix)) continue;
|
|
76
|
+
const subject = value.slice(0, -suffix.length).trim();
|
|
77
|
+
return subject ? `${verb} ${subject}` : verb;
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function sentenceStart(text) {
|
|
83
|
+
const value = String(text || '').trim();
|
|
84
|
+
return value ? `${value[0].toUpperCase()}${value.slice(1)}` : value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function polishNoticeText(text) {
|
|
88
|
+
let value = String(text ?? '').trim().replace(/^✓\s*/, '');
|
|
89
|
+
if (!value) return '';
|
|
90
|
+
const error = /^error\s*:\s*(.+)$/i.exec(value);
|
|
91
|
+
if (error?.[1]) value = error[1].trim();
|
|
92
|
+
const couldNot = /^could not\s+(.+?)(?::\s*(.+))?$/i.exec(value);
|
|
93
|
+
if (couldNot) {
|
|
94
|
+
return couldNot[2]
|
|
95
|
+
? `Couldn’t ${couldNot[1]}: ${couldNot[2]}`
|
|
96
|
+
: `Couldn’t ${couldNot[1]}.`;
|
|
97
|
+
}
|
|
98
|
+
const failed = /^(.+?)\s+failed(?::\s*(.+))?$/i.exec(value);
|
|
99
|
+
if (failed) {
|
|
100
|
+
const action = polishNoticeAction(failed[1]);
|
|
101
|
+
return failed[2] ? `Couldn’t ${action}: ${failed[2]}` : `Couldn’t ${action}.`;
|
|
102
|
+
}
|
|
103
|
+
const busy = /^(.+?)\s+already in progress\.?$/i.exec(value);
|
|
104
|
+
if (busy) return `${sentenceStart(polishNoticeAction(busy[1]))} is already running.`;
|
|
105
|
+
const required = /^(.+?)\s+is required(?:\s+for\s+(.+))?\.?$/i.exec(value);
|
|
106
|
+
if (required) {
|
|
107
|
+
const subject = required[1].trim();
|
|
108
|
+
const target = required[2]?.trim();
|
|
109
|
+
return `${subject}${target ? ` required for ${target}` : ' required'}.`;
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/queue-helpers.mjs - pure helpers for the prompt/notification
|
|
3
|
+
* queue: priority ordering, entry visibility/editability, prompt content
|
|
4
|
+
* flattening, session activity timestamps, and batch merging. Extracted from
|
|
5
|
+
* engine.mjs.
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
agentJobResultText,
|
|
9
|
+
parseAgentJob,
|
|
10
|
+
parseSyntheticAgentMessage,
|
|
11
|
+
} from './agent-envelope.mjs';
|
|
12
|
+
|
|
13
|
+
const QUEUE_PRIORITY = { now: 0, next: 1, later: 2 };
|
|
14
|
+
|
|
15
|
+
export function queuePriorityValue(value) {
|
|
16
|
+
return QUEUE_PRIORITY[String(value || 'next')] ?? QUEUE_PRIORITY.next;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function defaultQueuePriority(mode) {
|
|
20
|
+
// Queue priority defaults:
|
|
21
|
+
// - user/bashed prompt input defaults to `next`, so it can be attached at the
|
|
22
|
+
// next model-send boundary while a turn is active.
|
|
23
|
+
// - task notifications default to `later`, unless the caller explicitly marks
|
|
24
|
+
// them urgent (e.g. interactive shell stall/completion).
|
|
25
|
+
return mode === 'task-notification' ? 'later' : 'next';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isQueuedEntryEditable(entry) {
|
|
29
|
+
const mode = entry?.mode || 'prompt';
|
|
30
|
+
return mode !== 'task-notification' && mode !== 'pending-resume';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isQueuedEntryVisible(entry) {
|
|
34
|
+
// state.queued drives the user-command wait list above the prompt. Background
|
|
35
|
+
// task completions stay in the internal pending queue, but should never look
|
|
36
|
+
// like commands typed by the user while they wait to be drained.
|
|
37
|
+
const mode = entry?.mode || 'prompt';
|
|
38
|
+
if (mode === 'pending-resume') return false;
|
|
39
|
+
return isQueuedEntryEditable(entry);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isSlashQueuedEntry(entry) {
|
|
43
|
+
if (entry?.skipSlashCommands) return false;
|
|
44
|
+
const text = promptContentText(entry?.content ?? entry?.text ?? '');
|
|
45
|
+
return text.trim().startsWith('/');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function firstQueueLine(text) {
|
|
49
|
+
return String(text || '').split('\n').map((line) => line.trim()).find(Boolean) || '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function shortTextFingerprint(text) {
|
|
53
|
+
const value = String(text || '').trim();
|
|
54
|
+
let hash = 2166136261;
|
|
55
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
56
|
+
hash ^= value.charCodeAt(i);
|
|
57
|
+
hash = Math.imul(hash, 16777619);
|
|
58
|
+
}
|
|
59
|
+
return (hash >>> 0).toString(36);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function notificationDisplayText(text) {
|
|
63
|
+
const parsed = parseAgentJob(text);
|
|
64
|
+
const result = agentJobResultText(text, parsed);
|
|
65
|
+
const synthetic = parseSyntheticAgentMessage(text);
|
|
66
|
+
return firstQueueLine(synthetic?.result || result || text) || 'agent notification';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function promptContentText(content) {
|
|
70
|
+
if (typeof content === 'string') return content;
|
|
71
|
+
if (Array.isArray(content)) {
|
|
72
|
+
return content.map((part) => {
|
|
73
|
+
if (typeof part === 'string') return part;
|
|
74
|
+
if (part?.type === 'text') return part.text || '';
|
|
75
|
+
if (part?.type === 'image') return '[Image]';
|
|
76
|
+
return part?.text || '';
|
|
77
|
+
}).filter(Boolean).join('\n');
|
|
78
|
+
}
|
|
79
|
+
return String(content ?? '');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function timestampMs(value) {
|
|
83
|
+
if (value == null || value === '') return 0;
|
|
84
|
+
const n = Number(value);
|
|
85
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
86
|
+
const parsed = Date.parse(String(value));
|
|
87
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function hasModelVisibleConversation(session) {
|
|
91
|
+
const messages = Array.isArray(session?.messages) ? session.messages : [];
|
|
92
|
+
return messages.some((message) => {
|
|
93
|
+
const role = message?.role;
|
|
94
|
+
if (role !== 'user' && role !== 'assistant' && role !== 'tool') return false;
|
|
95
|
+
const text = promptContentText(message.content).trim();
|
|
96
|
+
if (role === 'user' && text.startsWith('<system-reminder>')) return false;
|
|
97
|
+
if (role === 'assistant' && text === '.' && !Array.isArray(message.toolCalls)) return false;
|
|
98
|
+
return !!text || role === 'assistant' || role === 'tool';
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function sessionActivityTimestamp(session, fallback = 0) {
|
|
103
|
+
if (!hasModelVisibleConversation(session)) return 0;
|
|
104
|
+
return timestampMs(session?.lastUsedAt)
|
|
105
|
+
|| timestampMs(session?.updatedAt)
|
|
106
|
+
|| timestampMs(fallback);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function promptDisplayText(content, options = {}) {
|
|
110
|
+
if (typeof options.displayText === 'string') return options.displayText;
|
|
111
|
+
return promptContentText(content);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function mergePromptContents(entries) {
|
|
115
|
+
const batch = Array.isArray(entries) ? entries : [];
|
|
116
|
+
if (batch.every((entry) => typeof entry?.content === 'string')) {
|
|
117
|
+
return batch.map((entry) => entry.content).filter((text) => String(text || '').trim()).join('\n');
|
|
118
|
+
}
|
|
119
|
+
const parts = [];
|
|
120
|
+
for (const entry of batch) {
|
|
121
|
+
const content = entry?.content;
|
|
122
|
+
if (typeof content === 'string') {
|
|
123
|
+
if (content.trim()) parts.push({ type: 'text', text: content });
|
|
124
|
+
} else if (Array.isArray(content)) {
|
|
125
|
+
parts.push(...content);
|
|
126
|
+
}
|
|
127
|
+
parts.push({ type: 'text', text: '\n' });
|
|
128
|
+
}
|
|
129
|
+
while (parts.length && parts[parts.length - 1]?.type === 'text' && parts[parts.length - 1]?.text === '\n') parts.pop();
|
|
130
|
+
return parts.length === 1 && parts[0]?.type === 'text' ? parts[0].text : parts;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function mergePastedImages(entries) {
|
|
134
|
+
const out = {};
|
|
135
|
+
for (const entry of entries || []) {
|
|
136
|
+
const images = entry?.pastedImages;
|
|
137
|
+
if (!images || typeof images !== 'object') continue;
|
|
138
|
+
for (const [id, image] of Object.entries(images)) {
|
|
139
|
+
if (image) out[id] = image;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function mergePastedTexts(entries) {
|
|
146
|
+
const out = {};
|
|
147
|
+
for (const entry of entries || []) {
|
|
148
|
+
const texts = entry?.pastedTexts;
|
|
149
|
+
if (!texts || typeof texts !== 'object') continue;
|
|
150
|
+
for (const [id, text] of Object.entries(texts)) {
|
|
151
|
+
if (text) out[id] = text;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function callCommitCallbacks(entries) {
|
|
158
|
+
for (const entry of entries || []) {
|
|
159
|
+
try { entry?.onCommitted?.(); } catch {}
|
|
160
|
+
}
|
|
161
|
+
}
|