mixdog 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -23
- package/package.json +34 -28
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +205 -33
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1222 -90
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -29
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +242 -98
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2211 -446
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +108 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +380 -268
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +131 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +203 -71
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +512 -312
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -518
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +289 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +840 -612
- package/src/runtime/agent/orchestrator/session/manager.mjs +1574 -459
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +67 -29
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +35 -8
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -53
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +149 -51
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +0 -1
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -8
- package/src/runtime/channels/lib/runtime-paths.mjs +0 -6
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -6
- package/src/runtime/channels/lib/tool-format.mjs +0 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -12
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +519 -118
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +28 -7
- package/src/runtime/memory/lib/memory-cycle2.mjs +4 -5
- package/src/runtime/memory/lib/memory-cycle3.mjs +2 -2
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -2
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +1 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +80 -21
- package/src/runtime/memory/lib/session-ingest.mjs +196 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +104 -473
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +44 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +139 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -95
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +1 -78
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4800 -2140
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +14 -4
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +428 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +597 -362
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +499 -0
- package/src/tui/components/tool-output-format.test.mjs +312 -0
- package/src/tui/dist/index.mjs +9192 -2399
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +671 -69
- package/src/tui/markdown/format-token.test.mjs +312 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +756 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +2 -13
- package/src/vendor/statusline/bin/statusline-lib.mjs +1 -620
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/ink.js +54 -8
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
package/src/ui/statusline.mjs
CHANGED
|
@@ -11,15 +11,18 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
13
13
|
import { homedir } from 'node:os';
|
|
14
|
-
import {
|
|
15
|
-
import { bold, colorEnabled,
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { bold, colorEnabled, rgb } from './ansi.mjs';
|
|
16
|
+
import { displayModelName, shortenModelName } from './model-display.mjs';
|
|
17
|
+
import { createSessionStats } from './session-stats.mjs';
|
|
16
18
|
import { forEachSessionRuntime } from '../runtime/agent/orchestrator/session/manager.mjs';
|
|
17
19
|
import { listHiddenRoleNames } from '../runtime/agent/orchestrator/internal-roles.mjs';
|
|
18
20
|
import { getModelMetadataSync } from '../runtime/agent/orchestrator/providers/model-catalog.mjs';
|
|
19
21
|
import { readCachedOAuthUsageSnapshot } from '../runtime/agent/orchestrator/providers/oauth-usage.mjs';
|
|
20
22
|
import { readCachedOpenCodeGoUsageSnapshot } from '../runtime/agent/orchestrator/providers/opencode-go-usage.mjs';
|
|
21
23
|
import { buildGatewayLimits } from '../runtime/agent/orchestrator/providers/statusline-route-meta.mjs';
|
|
22
|
-
import { formatGatewayLimitSegments, loadGatewayStatus } from '../vendor/statusline/bin/statusline-route.mjs';
|
|
24
|
+
import { compactBoundaryForStatus, formatGatewayLimitSegments, loadGatewayStatus } from '../vendor/statusline/bin/statusline-route.mjs';
|
|
25
|
+
export { createSessionStats, applyUsageDelta } from './session-stats.mjs';
|
|
23
26
|
|
|
24
27
|
// Token window used to compute a fallback context% from our own session usage.
|
|
25
28
|
// The live gateway (when up) overrides this with the real route's window. This
|
|
@@ -45,62 +48,63 @@ const CYN = sgr('38;2;136;136;136');
|
|
|
45
48
|
const GREY = sgr('38;2;136;136;136');
|
|
46
49
|
const SHELL_JOBS_SEGMENT_CACHE_MS = 1000;
|
|
47
50
|
const GATEWAY_QUOTA_STATUS_CACHE_MS = 500;
|
|
51
|
+
const WORKER_SPINNER_FRAMES = Object.freeze(['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']);
|
|
52
|
+
const WORKER_SPINNER_FRAME_MS = 160;
|
|
53
|
+
// L2 segment spinner: reuses the original WORKER_SPINNER_FRAMES dot glyphs (no
|
|
54
|
+
// separate glyph list) but spins them at a faster 120ms step than the worker
|
|
55
|
+
// spinner's 160ms. l2SpinnerFrame() indexes straight into WORKER_SPINNER_FRAMES.
|
|
56
|
+
const L2_SPINNER_FRAME_MS = 120;
|
|
57
|
+
// Keep the last known usage snapshot visible while idle. The runtime still
|
|
58
|
+
// refreshes OAuth usage in the background, but if that refresh is delayed or
|
|
59
|
+
// fails, the statusline should not blink/drop the usage segment; it should hold
|
|
60
|
+
// the last captured 5H/7D values from THIS process until a newer snapshot
|
|
61
|
+
// replaces them. Snapshots from a previous launch stay hidden during boot so the
|
|
62
|
+
// statusline starts empty until the current session captures usage once.
|
|
63
|
+
const STATUSLINE_PROCESS_STARTED_AT_MS = Date.now() - Math.floor((Number(process.uptime?.()) || 0) * 1000);
|
|
48
64
|
const DEFAULT_HIDDEN_STATUSLINE_ROLES = Object.freeze(['explorer', 'cycle1-agent', 'cycle2-agent', 'cycle3-agent', 'scheduler-task', 'webhook-handler']);
|
|
49
|
-
let _shellJobsSegmentCache = { ownerPid: 0, at: 0, value: '' };
|
|
65
|
+
let _shellJobsSegmentCache = { ownerPid: 0, at: 0, value: { count: 0, elapsedLabel: '' } };
|
|
50
66
|
let _gatewayQuotaStatusCache = { key: '', at: 0, value: null };
|
|
51
67
|
let _fallbackQuotaStatusCache = { key: '', at: 0, value: null };
|
|
52
68
|
let _hiddenStatuslineRoles = null;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
// Option A boot gate: the L1 usage/quota segment stays fully empty until THIS
|
|
70
|
+
// process has captured its FIRST confirmed (current-process) OAuth usage
|
|
71
|
+
// snapshot for THAT provider. The latch is monotonic PER PROVIDER — once a
|
|
72
|
+
// provider arms it stays on for the process lifetime, so its segment turns on
|
|
73
|
+
// exactly once (single clean transition) and then holds. This suppresses the
|
|
74
|
+
// early gateway active-instance quota/balance (from another owning process,
|
|
75
|
+
// which is NOT process-start guarded) and any stale/in-progress reads before
|
|
76
|
+
// the first confirmed snapshot lands. Keyed per provider (not per global) so an
|
|
77
|
+
// in-process route switch (openai-oauth → anthropic-oauth / grok-oauth) re-gates
|
|
78
|
+
// the new provider until ITS own confirmed snapshot exists — otherwise the new
|
|
79
|
+
// provider's stale fallback balance (Credit $…) could leak prematurely.
|
|
80
|
+
const _oauthUsageArmedProviders = new Set();
|
|
81
|
+
|
|
82
|
+
function isConfirmedCurrentProcessSnapshot(snapshot) {
|
|
83
|
+
if (!snapshot || typeof snapshot !== 'object') return false;
|
|
84
|
+
const cachedAt = num(snapshot.cachedAt);
|
|
85
|
+
return cachedAt > 0 && cachedAt >= STATUSLINE_PROCESS_STARTED_AT_MS;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function summarizeWorkerTags(workers, limit = 3) {
|
|
89
|
+
const cleanLabels = [...new Set((Array.isArray(workers) ? workers : [])
|
|
90
|
+
.map((worker) => String(worker?.tag || '').trim())
|
|
57
91
|
.filter(Boolean))];
|
|
58
|
-
if (
|
|
59
|
-
return `${
|
|
92
|
+
if (cleanLabels.length <= limit) return cleanLabels.join(', ');
|
|
93
|
+
return `${cleanLabels.slice(0, limit).join(', ')}, +${cleanLabels.length - limit}`;
|
|
60
94
|
}
|
|
61
95
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return
|
|
65
|
-
inputTokens: 0,
|
|
66
|
-
outputTokens: 0,
|
|
67
|
-
cachedTokens: 0,
|
|
68
|
-
cacheWriteTokens: 0,
|
|
69
|
-
promptTokens: 0,
|
|
70
|
-
latestInputTokens: 0,
|
|
71
|
-
latestOutputTokens: 0,
|
|
72
|
-
latestCachedTokens: 0,
|
|
73
|
-
latestCacheWriteTokens: 0,
|
|
74
|
-
latestPromptTokens: 0,
|
|
75
|
-
costUsd: 0,
|
|
76
|
-
turns: 0,
|
|
77
|
-
};
|
|
96
|
+
function workerSpinnerFrame(now = Date.now()) {
|
|
97
|
+
const index = Math.floor(now / WORKER_SPINNER_FRAME_MS) % WORKER_SPINNER_FRAMES.length;
|
|
98
|
+
return WORKER_SPINNER_FRAMES[index] || WORKER_SPINNER_FRAMES[0];
|
|
78
99
|
}
|
|
79
100
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
* @param {object} delta — { deltaInput, deltaOutput, deltaCachedRead, deltaCacheWrite, costUsd }
|
|
84
|
-
*/
|
|
85
|
-
export function applyUsageDelta(stats, delta = {}) {
|
|
86
|
-
if (!stats || !delta) return stats;
|
|
87
|
-
stats.inputTokens += num(delta.deltaInput);
|
|
88
|
-
stats.outputTokens += num(delta.deltaOutput);
|
|
89
|
-
stats.cachedTokens += num(delta.deltaCachedRead);
|
|
90
|
-
stats.cacheWriteTokens += num(delta.deltaCacheWrite);
|
|
91
|
-
stats.promptTokens += num(delta.deltaPrompt);
|
|
92
|
-
stats.latestInputTokens = num(delta.deltaInput);
|
|
93
|
-
stats.latestOutputTokens = num(delta.deltaOutput);
|
|
94
|
-
stats.latestCachedTokens = num(delta.deltaCachedRead);
|
|
95
|
-
stats.latestCacheWriteTokens = num(delta.deltaCacheWrite);
|
|
96
|
-
stats.latestPromptTokens = num(delta.deltaPrompt);
|
|
97
|
-
// costUsd from the engine is cumulative-per-call; we sum the per-turn deltas.
|
|
98
|
-
stats.costUsd += num(delta.costUsd);
|
|
99
|
-
return stats;
|
|
101
|
+
function l2SpinnerFrame(now = Date.now()) {
|
|
102
|
+
const index = Math.floor(now / L2_SPINNER_FRAME_MS) % WORKER_SPINNER_FRAMES.length;
|
|
103
|
+
return WORKER_SPINNER_FRAMES[index] || WORKER_SPINNER_FRAMES[0];
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
/**
|
|
103
|
-
* Build the
|
|
107
|
+
* Build the status-line JSON the vendored renderer reads, from our REPL
|
|
104
108
|
* state. Only the fields `renderStatusLine()` actually consumes are emitted:
|
|
105
109
|
* - display_name → model name (L1)
|
|
106
110
|
* - effort.level → effort string
|
|
@@ -125,22 +129,23 @@ function promptFootprintTokens(provider, stats) {
|
|
|
125
129
|
return input;
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
function
|
|
132
|
+
function activeContextNumerator(provider, stats) {
|
|
129
133
|
const s = stats || createSessionStats();
|
|
130
134
|
const source = String(s.currentContextSource || '').toLowerCase();
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const latestCacheWrite = num(s.latestCacheWriteTokens);
|
|
138
|
-
if (latestInput || latestCacheRead || latestCacheWrite) {
|
|
139
|
-
return providerInputExcludesCache(provider)
|
|
140
|
-
? latestInput + latestCacheRead + latestCacheWrite
|
|
141
|
-
: latestInput;
|
|
135
|
+
const estimated = num(s.currentEstimatedContextTokens);
|
|
136
|
+
if (estimated > 0) return estimated;
|
|
137
|
+
if (source === 'estimated') return 0;
|
|
138
|
+
if (source === 'last_api_request') {
|
|
139
|
+
const apiUsed = num(s.currentContextTokens);
|
|
140
|
+
if (apiUsed > 0) return apiUsed;
|
|
142
141
|
}
|
|
143
|
-
|
|
142
|
+
const explicit = num(s.currentContextTokens ?? s.contextTokens);
|
|
143
|
+
if (explicit > 0) return explicit;
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function currentContextTokens(provider, stats) {
|
|
148
|
+
return activeContextNumerator(provider, stats);
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
function modelContextWindow(provider, model, explicitContextWindow = 0) {
|
|
@@ -151,7 +156,61 @@ function modelContextWindow(provider, model, explicitContextWindow = 0) {
|
|
|
151
156
|
return FALLBACK_CONTEXT_WINDOW;
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
function
|
|
159
|
+
function displayContextBoundary({
|
|
160
|
+
contextWindow = 0,
|
|
161
|
+
displayContextWindow = 0,
|
|
162
|
+
rawContextWindow = 0,
|
|
163
|
+
compactBoundaryTokens = 0,
|
|
164
|
+
autoCompactTokenLimit = 0,
|
|
165
|
+
compact = null,
|
|
166
|
+
} = {}) {
|
|
167
|
+
const boundarySeed = num(compactBoundaryTokens) > 0
|
|
168
|
+
? num(compactBoundaryTokens)
|
|
169
|
+
: (num(displayContextWindow) > 0 ? num(displayContextWindow) : num(contextWindow));
|
|
170
|
+
const boundary = compactBoundaryForStatus({
|
|
171
|
+
contextWindow: boundarySeed,
|
|
172
|
+
rawContextWindow: num(rawContextWindow),
|
|
173
|
+
autoCompactTokenLimit: num(autoCompactTokenLimit),
|
|
174
|
+
}, compact);
|
|
175
|
+
if (Number.isFinite(boundary) && boundary > 0) return boundary;
|
|
176
|
+
return modelContextWindow('', '', boundarySeed);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function resolveContextUsedPct({
|
|
180
|
+
provider = '',
|
|
181
|
+
model = '',
|
|
182
|
+
stats = null,
|
|
183
|
+
contextWindow = 0,
|
|
184
|
+
displayContextWindow = 0,
|
|
185
|
+
rawContextWindow = 0,
|
|
186
|
+
compactBoundaryTokens = 0,
|
|
187
|
+
autoCompactTokenLimit = 0,
|
|
188
|
+
gatewayStatus = null,
|
|
189
|
+
} = {}) {
|
|
190
|
+
const numerator = activeContextNumerator(provider, stats);
|
|
191
|
+
const compact = gatewayStatus?.lastUsage?.compact || null;
|
|
192
|
+
const boundary = displayContextBoundary({
|
|
193
|
+
contextWindow,
|
|
194
|
+
displayContextWindow,
|
|
195
|
+
rawContextWindow,
|
|
196
|
+
compactBoundaryTokens,
|
|
197
|
+
autoCompactTokenLimit,
|
|
198
|
+
compact,
|
|
199
|
+
});
|
|
200
|
+
const gatewayRawPct = gatewayStatus?.contextUsedPct;
|
|
201
|
+
if (
|
|
202
|
+
gatewayStatus
|
|
203
|
+
&& gatewayRawPct !== null
|
|
204
|
+
&& gatewayRawPct !== undefined
|
|
205
|
+
) {
|
|
206
|
+
const gatewayPct = Number(gatewayRawPct);
|
|
207
|
+
if (Number.isFinite(gatewayPct)) return gatewayPct;
|
|
208
|
+
}
|
|
209
|
+
if (boundary > 0) return (numerator / boundary) * 100;
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function normalizeAgentWorkerForStatusline(worker = {}) {
|
|
155
214
|
const tag = String(worker.tag || worker.role || worker.name || '').trim();
|
|
156
215
|
if (!tag) return null;
|
|
157
216
|
const statusText = String(worker.stage || worker.status || '').toLowerCase();
|
|
@@ -159,6 +218,7 @@ function normalizeBridgeWorkerForStatusline(worker = {}) {
|
|
|
159
218
|
return {
|
|
160
219
|
tag,
|
|
161
220
|
status,
|
|
221
|
+
startedAtMs: timeMs(worker.startedAt || worker.startTime || worker.createdAt),
|
|
162
222
|
role: worker.role || null,
|
|
163
223
|
stage: worker.stage || worker.status || null,
|
|
164
224
|
sessionId: worker.sessionId || null,
|
|
@@ -167,7 +227,7 @@ function normalizeBridgeWorkerForStatusline(worker = {}) {
|
|
|
167
227
|
};
|
|
168
228
|
}
|
|
169
229
|
|
|
170
|
-
function
|
|
230
|
+
function normalizeAgentJobForStatusline(job = {}) {
|
|
171
231
|
const statusText = String(job.status || job.stage || '').toLowerCase();
|
|
172
232
|
if (!statusText) return null;
|
|
173
233
|
const taskId = String(job.task_id || job.taskId || '').trim();
|
|
@@ -195,6 +255,7 @@ function normalizeBridgeJobForStatusline(job = {}) {
|
|
|
195
255
|
tag,
|
|
196
256
|
taskId,
|
|
197
257
|
status: 'running',
|
|
258
|
+
startedAtMs,
|
|
198
259
|
role: job.role || null,
|
|
199
260
|
stage: job.stage || job.workerStatus || job.status || null,
|
|
200
261
|
sessionId: job.sessionId || null,
|
|
@@ -203,15 +264,15 @@ function normalizeBridgeJobForStatusline(job = {}) {
|
|
|
203
264
|
};
|
|
204
265
|
}
|
|
205
266
|
|
|
206
|
-
function
|
|
267
|
+
function agentStatuslinePayload(agentWorkers = [], agentJobs = []) {
|
|
207
268
|
const byTag = new Map();
|
|
208
269
|
const finishedJobs = [];
|
|
209
|
-
for (const worker of Array.isArray(
|
|
210
|
-
const row =
|
|
270
|
+
for (const worker of Array.isArray(agentWorkers) ? agentWorkers : []) {
|
|
271
|
+
const row = normalizeAgentWorkerForStatusline(worker);
|
|
211
272
|
if (row) byTag.set(row.tag, row);
|
|
212
273
|
}
|
|
213
|
-
for (const job of Array.isArray(
|
|
214
|
-
const row =
|
|
274
|
+
for (const job of Array.isArray(agentJobs) ? agentJobs : []) {
|
|
275
|
+
const row = normalizeAgentJobForStatusline(job);
|
|
215
276
|
if (!row) continue;
|
|
216
277
|
if (row.status === 'finished') {
|
|
217
278
|
finishedJobs.push(row);
|
|
@@ -246,23 +307,57 @@ function bridgeStatuslinePayload(bridgeWorkers = [], bridgeJobs = []) {
|
|
|
246
307
|
* @param {string} [opts.sessionId]
|
|
247
308
|
* @returns {Promise<string>}
|
|
248
309
|
*/
|
|
249
|
-
export async function renderStatusline({
|
|
310
|
+
export async function renderStatusline({
|
|
311
|
+
provider = '', model = '', effort = '', fast = false, cwd = '', stats, sessionId,
|
|
312
|
+
contextWindow = 0, displayContextWindow = 0, rawContextWindow = 0,
|
|
313
|
+
compactBoundaryTokens = 0, autoCompactTokenLimit = 0,
|
|
314
|
+
agentWorkers = [], agentJobs = [], activeTools = null, clientHostPid = process.pid,
|
|
315
|
+
} = {}) {
|
|
316
|
+
const displayArgs = {
|
|
317
|
+
contextWindow, displayContextWindow, rawContextWindow, compactBoundaryTokens, autoCompactTokenLimit,
|
|
318
|
+
};
|
|
250
319
|
try {
|
|
251
|
-
return renderNativeStatusline({
|
|
320
|
+
return renderNativeStatusline({
|
|
321
|
+
provider, model, effort, fast, cwd, stats, sessionId, agentWorkers, agentJobs, activeTools, clientHostPid,
|
|
322
|
+
...displayArgs,
|
|
323
|
+
});
|
|
252
324
|
} catch {
|
|
253
|
-
return fallbackLine({ provider, model, effort, fast, cwd, stats });
|
|
325
|
+
return fallbackLine({ provider, model, effort, fast, cwd, stats, ...displayArgs });
|
|
254
326
|
}
|
|
255
327
|
}
|
|
256
328
|
|
|
257
329
|
// --- helpers -----------------------------------------------------------------
|
|
258
330
|
|
|
259
|
-
function renderNativeStatusline({
|
|
331
|
+
function renderNativeStatusline({
|
|
332
|
+
provider = '', model = '', effort = '', fast = false, stats, sessionId,
|
|
333
|
+
contextWindow = 0, displayContextWindow = 0, rawContextWindow = 0,
|
|
334
|
+
compactBoundaryTokens = 0, autoCompactTokenLimit = 0,
|
|
335
|
+
agentWorkers = [], agentJobs = [], activeTools = null, clientHostPid = process.pid,
|
|
336
|
+
} = {}) {
|
|
260
337
|
const cols = terminalColumns();
|
|
261
338
|
const s = stats || createSessionStats();
|
|
262
|
-
const contextTokens =
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
|
|
339
|
+
const contextTokens = activeContextNumerator(provider, s);
|
|
340
|
+
const routeContextWindow = num(displayContextWindow) > 0 ? num(displayContextWindow) : num(contextWindow);
|
|
341
|
+
const gatewayStatus = loadGatewayQuotaStatus({
|
|
342
|
+
provider, model, effort, fast,
|
|
343
|
+
contextWindow: routeContextWindow,
|
|
344
|
+
rawContextWindow,
|
|
345
|
+
autoCompactTokenLimit,
|
|
346
|
+
sessionId,
|
|
347
|
+
activeContextTokens: contextTokens,
|
|
348
|
+
clientHostPid,
|
|
349
|
+
});
|
|
350
|
+
const ctxPct = resolveContextUsedPct({
|
|
351
|
+
provider,
|
|
352
|
+
model,
|
|
353
|
+
stats: s,
|
|
354
|
+
contextWindow,
|
|
355
|
+
displayContextWindow,
|
|
356
|
+
rawContextWindow,
|
|
357
|
+
compactBoundaryTokens,
|
|
358
|
+
autoCompactTokenLimit,
|
|
359
|
+
gatewayStatus,
|
|
360
|
+
});
|
|
266
361
|
|
|
267
362
|
const sep = ` ${D}│${R} `;
|
|
268
363
|
const l1Parts = [];
|
|
@@ -273,22 +368,63 @@ function renderNativeStatusline({ provider = '', model = '', effort = '', fast =
|
|
|
273
368
|
addL1(formatModelSegment({ provider, model, effort, fast, cols }));
|
|
274
369
|
addL1(formatContextSegment(ctxPct, cols));
|
|
275
370
|
|
|
276
|
-
|
|
371
|
+
// Option A boot gate: for OAuth routes, render NOTHING for the usage/quota
|
|
372
|
+
// segment until this process has captured its first confirmed (current-
|
|
373
|
+
// process) OAuth usage snapshot. This suppresses the startup jitter where the
|
|
374
|
+
// gateway active-instance quota windows (not process-start guarded) and the
|
|
375
|
+
// boot-guarded OAuth cache windows would otherwise pop in/merge at different
|
|
376
|
+
// ticks. Model + context% always render (built above). Non-OAuth routes are
|
|
377
|
+
// unaffected. Once armed, the latch holds for the process lifetime so the
|
|
378
|
+
// segment turns on exactly once and then holds the last known value as today.
|
|
379
|
+
const usageReady = oauthUsageSegmentReady({ provider, model });
|
|
380
|
+
const quotaStatus = usageReady
|
|
381
|
+
? mergeQuotaStatus(gatewayStatus, fallbackQuotaStatus({ provider, model }))
|
|
382
|
+
: null;
|
|
277
383
|
const quotaSegments = quotaStatus
|
|
278
384
|
? formatGatewayLimitSegments(quotaStatus, { COLS: cols, D, R, GRN, YLW, RED, colourPct, epochMsToHHMM })
|
|
279
385
|
: [];
|
|
280
386
|
for (const seg of quotaSegments) addL1(seg);
|
|
281
387
|
|
|
282
|
-
const
|
|
283
|
-
...(Array.isArray(
|
|
388
|
+
const agentPayload = agentStatuslinePayload([
|
|
389
|
+
...(Array.isArray(agentWorkers) ? agentWorkers : []),
|
|
284
390
|
...activeHiddenRoleWorkers({ sessionId, clientHostPid }),
|
|
285
|
-
],
|
|
286
|
-
const { maintenance, runningWorkers } =
|
|
391
|
+
], agentJobs);
|
|
392
|
+
const { maintenance, runningWorkers } = classifyAgentWorkers(agentPayload.workers);
|
|
287
393
|
if (maintenance.length) addL1(maintenance.join(' '));
|
|
288
|
-
|
|
394
|
+
const shellStatus = shellJobsStatus({ clientHostPid });
|
|
289
395
|
|
|
396
|
+
const spinnerNow = Date.now();
|
|
397
|
+
const sp = l2SpinnerFrame(spinnerNow);
|
|
398
|
+
const spin = `${GRN}${sp}${R}`;
|
|
399
|
+
const elapsedSuffix = (label) => (label ? ` ${D}·${R} ${label}` : '');
|
|
400
|
+
// Segment order: Running Agents → Exploring → Searching → Running Shells.
|
|
290
401
|
if (runningWorkers.length) {
|
|
291
|
-
|
|
402
|
+
const n = runningWorkers.length;
|
|
403
|
+
const label = `Running ${n} Agent${n === 1 ? '' : 's'}`;
|
|
404
|
+
const tagSummary = summarizeWorkerTags(runningWorkers);
|
|
405
|
+
const tags = tagSummary ? ` ${D}(${R}${B}${tagSummary}${R}${D})${R}` : '';
|
|
406
|
+
const oldestStart = runningWorkers.reduce((min, w) => {
|
|
407
|
+
const t = num(w?.startedAtMs);
|
|
408
|
+
return t > 0 && t < min ? t : min;
|
|
409
|
+
}, Infinity);
|
|
410
|
+
const elapsed = Number.isFinite(oldestStart) ? formatElapsed(Date.now() - oldestStart) : '';
|
|
411
|
+
addL2(`${spin} ${B}${label}${R}${tags}${elapsedSuffix(elapsed)}`);
|
|
412
|
+
}
|
|
413
|
+
const tools = activeTools && typeof activeTools === 'object' ? activeTools : {};
|
|
414
|
+
const exploreInfo = tools.explore || null;
|
|
415
|
+
const searchInfo = tools.search || null;
|
|
416
|
+
if (exploreInfo && num(exploreInfo.count) > 0) {
|
|
417
|
+
const elapsed = num(exploreInfo.startedAt) > 0 ? formatElapsed(Date.now() - num(exploreInfo.startedAt)) : '';
|
|
418
|
+
addL2(`${spin} ${B}Exploring${R}${elapsedSuffix(elapsed)}`);
|
|
419
|
+
}
|
|
420
|
+
if (searchInfo && num(searchInfo.count) > 0) {
|
|
421
|
+
const elapsed = num(searchInfo.startedAt) > 0 ? formatElapsed(Date.now() - num(searchInfo.startedAt)) : '';
|
|
422
|
+
addL2(`${spin} ${B}Searching${R}${elapsedSuffix(elapsed)}`);
|
|
423
|
+
}
|
|
424
|
+
if (shellStatus.count > 0) {
|
|
425
|
+
const n = shellStatus.count;
|
|
426
|
+
const label = `Running ${n} Shell${n === 1 ? '' : 's'}`;
|
|
427
|
+
addL2(`${spin} ${B}${label}${R}${elapsedSuffix(shellStatus.elapsedLabel)}`);
|
|
292
428
|
}
|
|
293
429
|
const l1 = l1Parts.join(sep) || 'mixdog';
|
|
294
430
|
const l2 = l2Parts.join(sep);
|
|
@@ -304,9 +440,15 @@ function dataDir() {
|
|
|
304
440
|
return process.env.MIXDOG_DATA_DIR || DEFAULT_STANDALONE_DATA_DIR;
|
|
305
441
|
}
|
|
306
442
|
|
|
307
|
-
function loadGatewayQuotaStatus({
|
|
443
|
+
function loadGatewayQuotaStatus({
|
|
444
|
+
provider, model, effort, fast, contextWindow, rawContextWindow, autoCompactTokenLimit = 0,
|
|
445
|
+
sessionId, activeContextTokens, clientHostPid,
|
|
446
|
+
} = {}) {
|
|
308
447
|
const key = [
|
|
309
448
|
String(provider || ''),
|
|
449
|
+
String(model || ''),
|
|
450
|
+
String(effort || ''),
|
|
451
|
+
fast === true ? 'fast' : '',
|
|
310
452
|
String(sessionId || ''),
|
|
311
453
|
String(clientHostPid || ''),
|
|
312
454
|
Math.floor((Number(activeContextTokens) || 0) / 1024),
|
|
@@ -317,7 +459,20 @@ function loadGatewayQuotaStatus({ provider, sessionId, activeContextTokens, clie
|
|
|
317
459
|
}
|
|
318
460
|
let value = null;
|
|
319
461
|
try {
|
|
320
|
-
const status = loadGatewayStatus({
|
|
462
|
+
const status = loadGatewayStatus({
|
|
463
|
+
sessionId,
|
|
464
|
+
activeContextTokens,
|
|
465
|
+
clientHostPid,
|
|
466
|
+
currentRoute: {
|
|
467
|
+
provider,
|
|
468
|
+
model,
|
|
469
|
+
effort,
|
|
470
|
+
fast,
|
|
471
|
+
contextWindow,
|
|
472
|
+
rawContextWindow,
|
|
473
|
+
autoCompactTokenLimit,
|
|
474
|
+
},
|
|
475
|
+
});
|
|
321
476
|
if (!status) {
|
|
322
477
|
_gatewayQuotaStatusCache = { key, at: now, value };
|
|
323
478
|
return value;
|
|
@@ -328,6 +483,12 @@ function loadGatewayQuotaStatus({ provider, sessionId, activeContextTokens, clie
|
|
|
328
483
|
_gatewayQuotaStatusCache = { key, at: now, value };
|
|
329
484
|
return value;
|
|
330
485
|
}
|
|
486
|
+
const statusModel = String(status.model || '').trim();
|
|
487
|
+
const cliModel = String(model || '').trim();
|
|
488
|
+
if (cliModel && statusModel && statusModel !== cliModel) {
|
|
489
|
+
_gatewayQuotaStatusCache = { key, at: now, value };
|
|
490
|
+
return value;
|
|
491
|
+
}
|
|
331
492
|
value = status;
|
|
332
493
|
} catch {
|
|
333
494
|
value = null;
|
|
@@ -336,6 +497,38 @@ function loadGatewayQuotaStatus({ provider, sessionId, activeContextTokens, clie
|
|
|
336
497
|
return value;
|
|
337
498
|
}
|
|
338
499
|
|
|
500
|
+
// Option A boot gate. Returns true once THIS process has captured its first
|
|
501
|
+
// confirmed (current-process) OAuth usage snapshot for THIS provider, and stays
|
|
502
|
+
// true afterwards (monotonic, per-provider latch). Non-OAuth providers are never
|
|
503
|
+
// gated (they have no async usage fetch on this path). Keyed on provider only:
|
|
504
|
+
// the OAuth cache lookup is provider-keyed and snapshots are stored provider-
|
|
505
|
+
// wide (oauth-usage.mjs writes a provider-only key + uses newestProviderSnapshot
|
|
506
|
+
// fallback), so per-provider arming matches the data granularity — a model
|
|
507
|
+
// switch within one provider shares the same provider-wide snapshot. The latch
|
|
508
|
+
// reads the same cache `fallbackQuotaStatus()` consumes so it flips in lock-step
|
|
509
|
+
// with the data actually becoming renderable — no extra delay, single clean
|
|
510
|
+
// transition.
|
|
511
|
+
function oauthUsageSegmentReady({ provider, model } = {}) {
|
|
512
|
+
const normalizedProvider = String(provider || '').trim().toLowerCase();
|
|
513
|
+
if (!normalizedProvider.includes('oauth')) return true;
|
|
514
|
+
if (_oauthUsageArmedProviders.has(normalizedProvider)) return true;
|
|
515
|
+
let snapshot = null;
|
|
516
|
+
try {
|
|
517
|
+
snapshot = readCachedOAuthUsageSnapshot({
|
|
518
|
+
provider: normalizedProvider,
|
|
519
|
+
model: String(model || '').trim(),
|
|
520
|
+
providerKind: providerKindForQuota(normalizedProvider),
|
|
521
|
+
}, { allowStale: true });
|
|
522
|
+
} catch {
|
|
523
|
+
snapshot = null;
|
|
524
|
+
}
|
|
525
|
+
if (isConfirmedCurrentProcessSnapshot(snapshot)) {
|
|
526
|
+
_oauthUsageArmedProviders.add(normalizedProvider);
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
|
|
339
532
|
function fallbackQuotaStatus({ provider, model } = {}) {
|
|
340
533
|
const normalizedProvider = String(provider || '').trim().toLowerCase();
|
|
341
534
|
if (!normalizedProvider) return null;
|
|
@@ -358,9 +551,18 @@ function fallbackQuotaStatus({ provider, model } = {}) {
|
|
|
358
551
|
}
|
|
359
552
|
} else if (normalizedProvider.includes('oauth')) {
|
|
360
553
|
try {
|
|
361
|
-
usageSnapshot = readCachedOAuthUsageSnapshot(routeInfo);
|
|
554
|
+
usageSnapshot = readCachedOAuthUsageSnapshot(routeInfo, { allowStale: true });
|
|
362
555
|
} catch {}
|
|
363
556
|
}
|
|
557
|
+
// Boot guard: do not render previous-launch usage before the current runtime
|
|
558
|
+
// has captured at least one snapshot. Once a snapshot is captured in this
|
|
559
|
+
// process, keep it visible while idle even if refreshes are delayed.
|
|
560
|
+
if (usageSnapshot) {
|
|
561
|
+
const cachedAt = num(usageSnapshot.cachedAt, 0);
|
|
562
|
+
if (!cachedAt || cachedAt < STATUSLINE_PROCESS_STARTED_AT_MS) {
|
|
563
|
+
usageSnapshot = { ...usageSnapshot, quotaWindows: [] };
|
|
564
|
+
}
|
|
565
|
+
}
|
|
364
566
|
try {
|
|
365
567
|
const limits = buildGatewayLimits(routeInfo, null, usageSnapshot);
|
|
366
568
|
if (!limits?.quotaWindows?.length && !limits?.balance && !limits?.routeSpend) {
|
|
@@ -389,82 +591,53 @@ function providerKindForQuota(provider) {
|
|
|
389
591
|
return 'api';
|
|
390
592
|
}
|
|
391
593
|
|
|
392
|
-
function
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
594
|
+
function mergeQuotaStatus(primary, fallback) {
|
|
595
|
+
if (!primary) return fallback || null;
|
|
596
|
+
if (!fallback) return primary;
|
|
597
|
+
return {
|
|
598
|
+
...fallback,
|
|
599
|
+
...primary,
|
|
600
|
+
quotaWindows: Array.isArray(primary.quotaWindows) && primary.quotaWindows.length
|
|
601
|
+
? primary.quotaWindows
|
|
602
|
+
: (fallback.quotaWindows || []),
|
|
603
|
+
balance: primary.balance || fallback.balance || null,
|
|
604
|
+
routeSpend: primary.routeSpend || fallback.routeSpend || null,
|
|
605
|
+
providerKind: primary.providerKind || fallback.providerKind || providerKindForQuota(primary.provider || fallback.provider),
|
|
606
|
+
};
|
|
398
607
|
}
|
|
399
608
|
|
|
400
|
-
function
|
|
609
|
+
function formatModelSegment({ provider, model, effort, fast, cols }) {
|
|
401
610
|
const raw = String(model || '').trim();
|
|
402
611
|
const meta = getModelMetadataSync(raw, provider) || {};
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (!text) return '';
|
|
410
|
-
const lower = text.toLowerCase();
|
|
411
|
-
if (lower === 'gpt') return 'GPT';
|
|
412
|
-
if (lower === 'api') return 'API';
|
|
413
|
-
if (lower === 'v4') return 'V4';
|
|
414
|
-
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
function canonicalModelDisplay(model, provider) {
|
|
418
|
-
const raw = String(model || '').trim().replace(/-\d{4}-\d{2}-\d{2}$/, '');
|
|
419
|
-
if (!raw) return '';
|
|
420
|
-
|
|
421
|
-
const gpt = raw.match(/^gpt-(\d+(?:\.\d+)?)(?:-(.+))?$/i);
|
|
422
|
-
if (gpt) {
|
|
423
|
-
const suffix = gpt[2]
|
|
424
|
-
? '-' + gpt[2].split('-').map(titleModelPart).filter(Boolean).join('-')
|
|
425
|
-
: '';
|
|
426
|
-
return `GPT-${gpt[1]}${suffix}`;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const codex = raw.match(/^codex-(.+)$/i);
|
|
430
|
-
if (codex) {
|
|
431
|
-
return `Codex ${codex[1].split('-').map(titleModelPart).filter(Boolean).join(' ')}`;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const deepseek = raw.match(/^deepseek-(.+)$/i);
|
|
435
|
-
if (deepseek) {
|
|
436
|
-
return `DeepSeek ${deepseek[1].split('-').map(titleModelPart).filter(Boolean).join(' ')}`;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const grok = raw.match(/^grok-(.+)$/i);
|
|
440
|
-
if (grok) {
|
|
441
|
-
return `Grok ${grok[1].split('-').map(titleModelPart).filter(Boolean).join(' ')}`;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const claude = raw.match(/^claude-(opus|sonnet|haiku)-(.+)$/i);
|
|
445
|
-
if (claude) {
|
|
446
|
-
return `Claude ${titleModelPart(claude[1])} ${claude[2].replace(/-/g, '.')}`;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
return raw;
|
|
612
|
+
const displayHint = String(meta.displayName || meta.display || meta.name || '').trim();
|
|
613
|
+
const modelName = shortenModelName(displayModelName(raw, provider, displayHint), cols);
|
|
614
|
+
const bits = [`${B}${modelName}${R}`];
|
|
615
|
+
if (effort) bits.push(`${B}${String(effort).toUpperCase()}${R}`);
|
|
616
|
+
if (fast === true) bits.push(`${B}FAST${R}`);
|
|
617
|
+
return bits.join(` ${D}·${R} `);
|
|
450
618
|
}
|
|
451
619
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if (
|
|
457
|
-
|
|
458
|
-
return out;
|
|
620
|
+
/** Display label for context % (clamped to 100); raw pct still drives bar/color thresholds. */
|
|
621
|
+
export function contextPctDisplayLabel(ctxPct) {
|
|
622
|
+
const pct = Number(ctxPct);
|
|
623
|
+
if (!Number.isFinite(pct) || pct <= 0) return '0';
|
|
624
|
+
if (pct > 0 && pct < 1) return String(Math.round(pct * 10) / 10);
|
|
625
|
+
return String(Math.floor(Math.min(100, pct)));
|
|
459
626
|
}
|
|
460
627
|
|
|
461
628
|
function formatContextSegment(ctxPct, cols) {
|
|
462
|
-
const
|
|
629
|
+
const raw = Number(ctxPct);
|
|
630
|
+
const pct = Number.isFinite(raw) ? Math.max(0, raw) : 0;
|
|
631
|
+
const barPct = clampPct(pct);
|
|
463
632
|
const fill = pct >= 90 ? RED : pct >= 70 ? YLW : GRN;
|
|
464
|
-
const label =
|
|
465
|
-
|
|
633
|
+
const label = contextPctDisplayLabel(pct);
|
|
634
|
+
// Keep a full-width bar wherever there is room for one. Below 80 cols the bar
|
|
635
|
+
// is dropped (label-only) so the footer never overflows a narrow terminal;
|
|
636
|
+
// at 80+ it stays a fixed 14 cells instead of shrinking to 8, which read as
|
|
637
|
+
// too small/cramped on mid-width terminals.
|
|
638
|
+
const cells = cols >= 80 ? 14 : 0;
|
|
466
639
|
if (!cells) return `${fill}${label}%${R}`;
|
|
467
|
-
const bar = makeBar(
|
|
640
|
+
const bar = makeBar(barPct, cells);
|
|
468
641
|
const filled = bar.replace(/░/g, '');
|
|
469
642
|
const empty = bar.replace(/▓/g, '');
|
|
470
643
|
return `${fill}${filled}${R}${D}${empty}${R} ${label}%`;
|
|
@@ -490,7 +663,7 @@ function epochMsToHHMM(ms) {
|
|
|
490
663
|
return d.toLocaleTimeString('sv-SE', { hour: '2-digit', minute: '2-digit', hour12: false });
|
|
491
664
|
}
|
|
492
665
|
|
|
493
|
-
function
|
|
666
|
+
function classifyAgentWorkers(workers = []) {
|
|
494
667
|
const maintenance = [];
|
|
495
668
|
const runningWorkers = [];
|
|
496
669
|
const seenMaintenance = new Set();
|
|
@@ -508,7 +681,7 @@ function classifyBridgeWorkers(workers = []) {
|
|
|
508
681
|
}
|
|
509
682
|
if (w.status !== 'idle' && !seenRunning.has(tag)) {
|
|
510
683
|
seenRunning.add(tag);
|
|
511
|
-
runningWorkers.push(
|
|
684
|
+
runningWorkers.push(w);
|
|
512
685
|
}
|
|
513
686
|
}
|
|
514
687
|
return { maintenance, runningWorkers };
|
|
@@ -559,7 +732,7 @@ function activeHiddenRoleWorkers({ sessionId = '', clientHostPid = 0 } = {}) {
|
|
|
559
732
|
const status = String(session?.status || stage || '').trim().toLowerCase();
|
|
560
733
|
if (!isActiveHiddenStatus(stage || status)) continue;
|
|
561
734
|
rows.push({
|
|
562
|
-
tag: String(session?.
|
|
735
|
+
tag: String(session?.agentTag || `${role}:${id || rows.length}`).trim(),
|
|
563
736
|
role,
|
|
564
737
|
status: 'running',
|
|
565
738
|
stage: stage || status || 'running',
|
|
@@ -594,14 +767,15 @@ function maintenanceLabel(tag) {
|
|
|
594
767
|
}
|
|
595
768
|
}
|
|
596
769
|
|
|
597
|
-
function
|
|
770
|
+
function shellJobsStatus({ clientHostPid } = {}) {
|
|
598
771
|
const ownerPid = positiveInt(clientHostPid);
|
|
599
|
-
|
|
772
|
+
const empty = { count: 0, elapsedLabel: '' };
|
|
773
|
+
if (!ownerPid) return empty;
|
|
600
774
|
const now = Date.now();
|
|
601
775
|
if (_shellJobsSegmentCache.ownerPid === ownerPid && now - _shellJobsSegmentCache.at < SHELL_JOBS_SEGMENT_CACHE_MS) {
|
|
602
|
-
return _shellJobsSegmentCache.value;
|
|
776
|
+
return _shellJobsSegmentCache.value || empty;
|
|
603
777
|
}
|
|
604
|
-
let value =
|
|
778
|
+
let value = empty;
|
|
605
779
|
try {
|
|
606
780
|
const dir = join(dataDir(), 'shell-jobs');
|
|
607
781
|
if (!existsSync(dir)) {
|
|
@@ -642,10 +816,9 @@ function formatShellJobsSegment({ clientHostPid } = {}) {
|
|
|
642
816
|
return value;
|
|
643
817
|
}
|
|
644
818
|
const elapsedLabel = Number.isFinite(oldestMs) ? formatElapsed(Date.now() - oldestMs) : '';
|
|
645
|
-
|
|
646
|
-
value = `${GREY}⚙ shell:${count}${elapsed}${R}`;
|
|
819
|
+
value = { count, elapsedLabel };
|
|
647
820
|
} catch {
|
|
648
|
-
value =
|
|
821
|
+
value = empty;
|
|
649
822
|
}
|
|
650
823
|
_shellJobsSegmentCache = { ownerPid, at: now, value };
|
|
651
824
|
return value;
|
|
@@ -682,29 +855,64 @@ function positiveInt(value) {
|
|
|
682
855
|
return Number.isInteger(n) && n > 0 ? n : 0;
|
|
683
856
|
}
|
|
684
857
|
|
|
858
|
+
// Byte-identical replica of src/tui/time-format.mjs formatDuration() with
|
|
859
|
+
// DEFAULT options (no mostSignificantOnly / hideTrailingZeros), wrapped to drop
|
|
860
|
+
// sub-1s like formatElapsed there. Output shape: '' (<1s), `Xs`, `Xm Ys`,
|
|
861
|
+
// `Xh Ym Zs`, `Xd Yh Zm`. statusline.mjs is a standalone UI module that should
|
|
862
|
+
// not depend on the React/ink TUI tree, so the algorithm is replicated rather
|
|
863
|
+
// than imported. Used for ALL L2 elapsed (Agents/Explore/Search/Shell).
|
|
685
864
|
function formatElapsed(ms) {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if (
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
865
|
+
if (!Number.isFinite(Number(ms))) return '';
|
|
866
|
+
const value = Math.max(0, Number(ms) || 0);
|
|
867
|
+
if (value < 60_000) {
|
|
868
|
+
if (value < 1_000) return '';
|
|
869
|
+
return `${Math.floor(value / 1000)}s`;
|
|
870
|
+
}
|
|
871
|
+
const days = Math.floor(value / 86_400_000);
|
|
872
|
+
const hours = Math.floor((value % 86_400_000) / 3_600_000);
|
|
873
|
+
const minutes = Math.floor((value % 3_600_000) / 60_000);
|
|
874
|
+
const seconds = Math.floor((value % 60_000) / 1000);
|
|
875
|
+
if (days > 0) return `${days}d ${hours}h ${minutes}m`;
|
|
876
|
+
if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
|
|
877
|
+
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
878
|
+
return `${seconds}s`;
|
|
692
879
|
}
|
|
693
880
|
|
|
694
881
|
/** Minimal one-line footer used when the vendored renderer is unavailable. */
|
|
695
|
-
function
|
|
882
|
+
export function fallbackStatusline({
|
|
883
|
+
provider = '', model = '', effort = '', fast = false, cwd = '', stats, contextWindow = 0,
|
|
884
|
+
displayContextWindow = 0, rawContextWindow = 0, compactBoundaryTokens = 0, autoCompactTokenLimit = 0,
|
|
885
|
+
} = {}) {
|
|
886
|
+
return fallbackLine({
|
|
887
|
+
provider, model, effort, fast, cwd, stats, contextWindow, displayContextWindow,
|
|
888
|
+
rawContextWindow, compactBoundaryTokens, autoCompactTokenLimit,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function fallbackLine({
|
|
893
|
+
provider = '', model = '', effort = '', fast = false, cwd = '', stats, contextWindow = 0,
|
|
894
|
+
displayContextWindow = 0, rawContextWindow = 0, compactBoundaryTokens = 0, autoCompactTokenLimit = 0,
|
|
895
|
+
} = {}) {
|
|
696
896
|
const s = stats || createSessionStats();
|
|
697
|
-
const
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
897
|
+
const cols = terminalColumns();
|
|
898
|
+
const ctxPct = resolveContextUsedPct({
|
|
899
|
+
provider,
|
|
900
|
+
model,
|
|
901
|
+
stats: s,
|
|
902
|
+
contextWindow,
|
|
903
|
+
displayContextWindow,
|
|
904
|
+
rawContextWindow,
|
|
905
|
+
compactBoundaryTokens,
|
|
906
|
+
autoCompactTokenLimit,
|
|
907
|
+
gatewayStatus: null,
|
|
908
|
+
});
|
|
909
|
+
const sep = ` ${D}│${R} `;
|
|
910
|
+
const parts = [
|
|
911
|
+
formatModelSegment({ provider, model, effort, fast, cols }),
|
|
912
|
+
formatContextSegment(ctxPct, cols),
|
|
913
|
+
].filter(Boolean);
|
|
914
|
+
if (!parts.length) return statusSubtle('> mixdog');
|
|
915
|
+
return parts.join(sep);
|
|
708
916
|
}
|
|
709
917
|
|
|
710
918
|
function num(v) {
|