mixdog 0.8.0 → 0.9.0
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 +33 -27
- 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 +344 -0
- 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 +1223 -95
- 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 -7
- package/src/lib/rules-builder.cjs +240 -96
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2325 -450
- 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 +118 -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 +377 -243
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -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 +221 -70
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
- 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 +31 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
- 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 +851 -674
- package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
- 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 +34 -7
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
- 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/snapshot-store.mjs +1 -1
- 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 +152 -24
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -2
- package/src/runtime/channels/lib/tool-format.mjs +0 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/memory/index.mjs +635 -107
- 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/memory-cycle1.mjs +38 -17
- package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
- package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
- 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 +15 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
- package/src/runtime/memory/lib/session-ingest.mjs +194 -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 +102 -466
- package/src/runtime/search/lib/web-tools.mjs +45 -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 +145 -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 -98
- 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 +2 -69
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4798 -2153
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +87 -29
- 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 +367 -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 +617 -368
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +365 -0
- package/src/tui/components/tool-output-format.test.mjs +220 -0
- package/src/tui/dist/index.mjs +8915 -2418
- 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 +511 -68
- package/src/tui/markdown/format-token.test.mjs +216 -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 +746 -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 +3 -16
- package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
- 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/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/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/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/tool-card.mjs
CHANGED
|
@@ -3,22 +3,21 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Replaces the raw `[tool: name]` line with a styled card that shows the tool
|
|
5
5
|
* name plus a short, human-readable summary of its most relevant argument
|
|
6
|
-
* (path for read/
|
|
6
|
+
* (path for read/apply_patch, command for shell, pattern for grep, etc.).
|
|
7
7
|
*
|
|
8
8
|
* Pure formatting: returns a string, never touches stdout. Robust to missing or
|
|
9
9
|
* malformed argument objects (the engine hands us `{ name, arguments, id }`).
|
|
10
10
|
*/
|
|
11
|
-
import { bold,
|
|
11
|
+
import { bold, cyan, gray } from './ansi.mjs';
|
|
12
12
|
|
|
13
13
|
const MAX_SUMMARY = 72;
|
|
14
14
|
|
|
15
15
|
/** Map of tool name -> function deriving its one-line summary from args. */
|
|
16
16
|
const SUMMARIZERS = {
|
|
17
17
|
read: (a) => a.path ?? a.file,
|
|
18
|
-
write: (a) => a.path,
|
|
19
18
|
apply_patch: (a) => a.path ?? a.base_path ?? firstPatchPath(a.patch),
|
|
20
|
-
edit: (a) => a.path ?? (Array.isArray(a.edits) ? a.edits[0]?.path : undefined),
|
|
21
19
|
list: (a) => a.path ?? a.pattern,
|
|
20
|
+
find: (a) => a.query ?? a.fuzzy ?? a.path,
|
|
22
21
|
glob: (a) => joinMaybe(a.pattern) ?? a.path,
|
|
23
22
|
grep: (a) => joinMaybe(a.pattern),
|
|
24
23
|
shell: (a) => a.command,
|
|
@@ -45,18 +44,6 @@ export function renderToolCard(call) {
|
|
|
45
44
|
return ` ${bullet} ${label} ${gray(truncate(summary, MAX_SUMMARY))}`;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
/** Render several calls (from one iteration) as a joined block. */
|
|
49
|
-
export function renderToolCards(calls) {
|
|
50
|
-
return (calls || []).map(renderToolCard).join('\n');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** A small "done" marker line for a completed tool (optional nicety). */
|
|
54
|
-
export function renderToolResult(name, { ok = true, note = '' } = {}) {
|
|
55
|
-
const mark = ok ? green('ok') : yellow('x');
|
|
56
|
-
const tail = note ? ' ' + dim(truncate(String(note), 48)) : '';
|
|
57
|
-
return ` ${mark} ${dim(String(name ?? 'tool'))}${tail}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
47
|
// --- helpers -----------------------------------------------------------------
|
|
61
48
|
|
|
62
49
|
function safeSummary(name, args) {
|
|
@@ -151,7 +151,7 @@ function compactWindowForRouteLike(route) {
|
|
|
151
151
|
const explicit = num(route.autoCompactTokenLimit ?? route.auto_compact_token_limit);
|
|
152
152
|
const raw = num(route.rawContextWindow ?? route.raw_context_window ?? route.contextWindow ?? route.context_window);
|
|
153
153
|
const context = num(route.contextWindow ?? route.context_window);
|
|
154
|
-
const pct = num(route.effectiveContextWindowPercent ?? route.effective_context_window_percent) ||
|
|
154
|
+
const pct = num(route.effectiveContextWindowPercent ?? route.effective_context_window_percent) || 90;
|
|
155
155
|
const effective = context || (raw > 0 ? Math.max(1, Math.floor(raw * Math.min(100, pct) / 100)) : 0);
|
|
156
156
|
if (explicit > 0 && effective > 0) return Math.min(explicit, effective);
|
|
157
157
|
if (explicit > 0) return explicit;
|
|
@@ -573,6 +573,10 @@ export async function renderStatusLine(ccJsonInput) {
|
|
|
573
573
|
if (cleanTags.length <= limit) return cleanTags.join(', ');
|
|
574
574
|
return `${cleanTags.slice(0, limit).join(', ')}, +${cleanTags.length - limit}`;
|
|
575
575
|
}
|
|
576
|
+
const workerSpinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
577
|
+
function workerSpinnerFrame(now = Date.now()) {
|
|
578
|
+
return workerSpinnerFrames[Math.floor(now / 120) % workerSpinnerFrames.length] || workerSpinnerFrames[0];
|
|
579
|
+
}
|
|
576
580
|
|
|
577
581
|
const gatewayLimitSegments = formatGatewayLimitSegments(GATEWAY_STATUS, {
|
|
578
582
|
COLS, D, R, RED, GRN, YLW, colourPct, epochMsToHHMM,
|
|
@@ -595,9 +599,9 @@ export async function renderStatusLine(ccJsonInput) {
|
|
|
595
599
|
if (modelStr) {
|
|
596
600
|
const m = COLS >= 120 ? modelStr : modelShort;
|
|
597
601
|
if (effortStr) {
|
|
598
|
-
addL1(`${
|
|
602
|
+
addL1(`${B}${m}${R} ${D}·${R} ${B}${effortStr}${R}`);
|
|
599
603
|
} else {
|
|
600
|
-
addL1(`${
|
|
604
|
+
addL1(`${B}${m}${R}`);
|
|
601
605
|
}
|
|
602
606
|
}
|
|
603
607
|
|
|
@@ -781,7 +785,7 @@ export async function renderStatusLine(ccJsonInput) {
|
|
|
781
785
|
function addL2(seg) { if (seg) l2Parts.push(seg); }
|
|
782
786
|
|
|
783
787
|
if (workCount > 0 && workOrder) {
|
|
784
|
-
addL2(`${GRN}
|
|
788
|
+
addL2(`${GRN}${workerSpinnerFrame()}${R} ${B}${workCount} Running${R} ${D}(${R}${B}${summarizeWorkerTags(workOrder.split(','))}${R}${D})${R}`);
|
|
785
789
|
}
|
|
786
790
|
|
|
787
791
|
if (idleWorkers.length) {
|
|
@@ -17,6 +17,11 @@ function positiveInt(value) {
|
|
|
17
17
|
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// Keep the last known OAuth usage snapshot visible while idle. Live refreshes
|
|
21
|
+
// replace it when available, but a delayed/failed refresh must not make the
|
|
22
|
+
// statusline usage segment disappear. Previous-launch snapshots stay hidden
|
|
23
|
+
// during boot until the current process captures usage once.
|
|
24
|
+
const STATUSLINE_PROCESS_STARTED_AT_MS = Date.now() - Math.floor((Number(process.uptime?.()) || 0) * 1000);
|
|
20
25
|
function isPidAlive(pid) {
|
|
21
26
|
const n = positiveInt(pid);
|
|
22
27
|
if (!n) return false;
|
|
@@ -56,6 +61,14 @@ function num(value, fallback = null) {
|
|
|
56
61
|
return Number.isFinite(n) ? n : fallback;
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
function firstPositiveWindow(...values) {
|
|
65
|
+
for (const value of values) {
|
|
66
|
+
const n = num(value, null);
|
|
67
|
+
if (n !== null && n > 0) return n;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
59
72
|
function money(value) {
|
|
60
73
|
const n = Number(value);
|
|
61
74
|
if (!Number.isFinite(n)) return '';
|
|
@@ -88,6 +101,19 @@ function cleanString(value) {
|
|
|
88
101
|
return s || '';
|
|
89
102
|
}
|
|
90
103
|
|
|
104
|
+
// Mirror of route-meta.mjs providerKind(): classify a provider id into a kind
|
|
105
|
+
// bucket. statusline-route.mjs derives its own status snapshot and calls this
|
|
106
|
+
// the same way route-meta does, but never imported/defined it, so any route
|
|
107
|
+
// status build threw `providerKind is not defined`.
|
|
108
|
+
function providerKind(provider) {
|
|
109
|
+
const p = String(provider || '').toLowerCase();
|
|
110
|
+
if (!p) return 'unknown';
|
|
111
|
+
if (p === 'opencode-go') return 'quota-api';
|
|
112
|
+
if (p.includes('oauth')) return 'oauth';
|
|
113
|
+
if (p === 'ollama' || p === 'lmstudio') return 'local';
|
|
114
|
+
return 'api';
|
|
115
|
+
}
|
|
116
|
+
|
|
91
117
|
function slugSegment(value) {
|
|
92
118
|
const s = cleanString(value);
|
|
93
119
|
if (!s) return '';
|
|
@@ -163,18 +189,16 @@ function boundedPercent(value, fallback = null) {
|
|
|
163
189
|
}
|
|
164
190
|
|
|
165
191
|
function defaultEffectiveContextWindowPercent(_provider) {
|
|
166
|
-
return
|
|
192
|
+
return 90;
|
|
167
193
|
}
|
|
168
194
|
|
|
169
195
|
function routeContextMeta(provider, info = {}, inherited = {}) {
|
|
170
|
-
const rawContextWindow =
|
|
171
|
-
inherited.rawContextWindow
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
?? inherited.contextWindow,
|
|
177
|
-
null,
|
|
196
|
+
const rawContextWindow = firstPositiveWindow(
|
|
197
|
+
inherited.rawContextWindow,
|
|
198
|
+
inherited.raw_context_window,
|
|
199
|
+
info.contextWindow,
|
|
200
|
+
info.maxContextWindow,
|
|
201
|
+
info.max_input_tokens,
|
|
178
202
|
);
|
|
179
203
|
const effectiveContextWindowPercent = boundedPercent(
|
|
180
204
|
inherited.effectiveContextWindowPercent
|
|
@@ -183,9 +207,16 @@ function routeContextMeta(provider, info = {}, inherited = {}) {
|
|
|
183
207
|
?? info.effective_context_window_percent,
|
|
184
208
|
defaultEffectiveContextWindowPercent(provider),
|
|
185
209
|
);
|
|
186
|
-
const
|
|
210
|
+
const derivedContextWindow = rawContextWindow
|
|
187
211
|
? Math.max(1, Math.floor(rawContextWindow * boundedPercent(effectiveContextWindowPercent, 100) / 100))
|
|
188
212
|
: null;
|
|
213
|
+
const explicitEffectiveContextWindow = firstPositiveWindow(
|
|
214
|
+
inherited.contextWindow,
|
|
215
|
+
inherited.displayContextWindow,
|
|
216
|
+
inherited.compactBoundaryTokens,
|
|
217
|
+
inherited.compact_boundary_tokens,
|
|
218
|
+
);
|
|
219
|
+
const contextWindow = explicitEffectiveContextWindow ?? derivedContextWindow;
|
|
189
220
|
const explicitCompactLimit = num(
|
|
190
221
|
inherited.autoCompactTokenLimit
|
|
191
222
|
?? inherited.auto_compact_token_limit
|
|
@@ -193,10 +224,18 @@ function routeContextMeta(provider, info = {}, inherited = {}) {
|
|
|
193
224
|
?? info.auto_compact_token_limit,
|
|
194
225
|
null,
|
|
195
226
|
);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
227
|
+
// autoCompactTokenLimit must mean an EXPLICIT auto-compaction limit only.
|
|
228
|
+
// Do NOT derive it from the context/raw window: the runtime treats a present
|
|
229
|
+
// autoCompactTokenLimit as the compaction trigger, and a full-window value
|
|
230
|
+
// collapses the compaction buffer to 0. Keep boundary/window display via the
|
|
231
|
+
// separate contextWindow/rawContextWindow fields. An explicit limit is only a
|
|
232
|
+
// real trigger when STRICTLY BELOW the boundary; a legacy seed/info value
|
|
233
|
+
// equal to or above it is a derived full-window artifact and is dropped to
|
|
234
|
+
// null (Math.min would still surface the boundary). Never derive a limit.
|
|
235
|
+
const _boundary = contextWindow || rawContextWindow || null;
|
|
236
|
+
const autoCompactTokenLimit = explicitCompactLimit && explicitCompactLimit > 0
|
|
237
|
+
? (_boundary ? (explicitCompactLimit < _boundary ? explicitCompactLimit : null) : explicitCompactLimit)
|
|
238
|
+
: null;
|
|
200
239
|
return {
|
|
201
240
|
contextWindow,
|
|
202
241
|
rawContextWindow,
|
|
@@ -205,14 +244,49 @@ function routeContextMeta(provider, info = {}, inherited = {}) {
|
|
|
205
244
|
};
|
|
206
245
|
}
|
|
207
246
|
|
|
208
|
-
|
|
247
|
+
// Test-only export of the route context-meta resolver for the auto-compact
|
|
248
|
+
// limit migration smoke (scripts/compact-trigger-migration-smoke.mjs).
|
|
249
|
+
export const _routeContextMeta = routeContextMeta;
|
|
250
|
+
|
|
251
|
+
// Resolve the auto-compact limit displayed by loadGatewayStatus(). The active
|
|
252
|
+
// value MUST be validated against the ACTIVE route's own boundary/window, never
|
|
253
|
+
// configured?.contextWindow: the configured window can be larger than the
|
|
254
|
+
// active window, so validating a stale active full-window value (== active
|
|
255
|
+
// window) against the configured window would let it slip through as an
|
|
256
|
+
// explicit limit. The configured route's limit (already sanitized by
|
|
257
|
+
// routeContextMeta) is preferred when a configured route is present, but it
|
|
258
|
+
// never validates a stale active value.
|
|
259
|
+
function resolveStatusAutoCompactTokenLimit(configured, active = {}, lastCompact = null) {
|
|
260
|
+
if (configured) {
|
|
261
|
+
const c = num(configured.autoCompactTokenLimit, 0);
|
|
262
|
+
return c > 0 ? c : null;
|
|
263
|
+
}
|
|
264
|
+
const activeBoundary = num(
|
|
265
|
+
active?.gateway_context_window
|
|
266
|
+
?? active?.gateway_raw_context_window
|
|
267
|
+
?? lastCompact?.budgetWindow
|
|
268
|
+
?? lastCompact?.contextWindow,
|
|
269
|
+
0,
|
|
270
|
+
);
|
|
271
|
+
const raw = num(active?.gateway_auto_compact_token_limit, 0);
|
|
272
|
+
if (!(raw > 0)) return null;
|
|
273
|
+
if (activeBoundary > 0) return raw < activeBoundary ? raw : null;
|
|
274
|
+
return raw;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Test-only export of the status auto-compact-limit resolver.
|
|
278
|
+
export const _resolveStatusAutoCompactTokenLimit = resolveStatusAutoCompactTokenLimit;
|
|
279
|
+
|
|
280
|
+
export function compactBoundaryForStatus(routeInfo = {}, compact = null) {
|
|
209
281
|
const compactLimit = num(routeInfo?.autoCompactTokenLimit ?? compact?.compactLimitTokens, 0);
|
|
210
282
|
const contextWindow = num(routeInfo?.contextWindow ?? compact?.contextWindow, 0);
|
|
211
283
|
const budgetWindow = num(compact?.budgetWindow, 0);
|
|
212
284
|
const rawContextWindow = num(routeInfo?.rawContextWindow ?? compact?.rawContextWindow, 0);
|
|
213
|
-
if (contextWindow > 0) return contextWindow;
|
|
214
|
-
if (budgetWindow > 0) return budgetWindow;
|
|
285
|
+
if (compactLimit > 0 && contextWindow > 0) return Math.min(compactLimit, contextWindow);
|
|
215
286
|
if (compactLimit > 0) return compactLimit;
|
|
287
|
+
if (budgetWindow > 0 && contextWindow > 0) return Math.min(budgetWindow, contextWindow);
|
|
288
|
+
if (budgetWindow > 0) return budgetWindow;
|
|
289
|
+
if (contextWindow > 0) return contextWindow;
|
|
216
290
|
return rawContextWindow > 0 ? rawContextWindow : null;
|
|
217
291
|
}
|
|
218
292
|
|
|
@@ -237,7 +311,7 @@ function gatewaySessionStatus(sessionId, clientHostPid = null) {
|
|
|
237
311
|
function sessionRouteFor(gateway, sessionId, clientHostPid = null) {
|
|
238
312
|
const sid = cleanString(sessionId);
|
|
239
313
|
if (!sid) return { route: gateway, sessionScoped: false };
|
|
240
|
-
const route = readGatewaySessionRoute(sid, { clientHostPid })
|
|
314
|
+
const route = readGatewaySessionRoute(sid, { clientHostPid, fallbackLegacy: true })
|
|
241
315
|
|| readLatestGatewayHostRoute(clientHostPid, { excludeSessionId: sid });
|
|
242
316
|
if (!route || typeof route !== 'object') return { route: gateway, sessionScoped: false };
|
|
243
317
|
const provider = cleanString(route.defaultProvider || route.provider);
|
|
@@ -273,10 +347,9 @@ function loadCachedModel(provider, model) {
|
|
|
273
347
|
}
|
|
274
348
|
|
|
275
349
|
function cachedQuotaWindowsFallback(provider, model) {
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
// on a different route would clobber this session's 5H/7D/reset display.
|
|
350
|
+
// OAuth quota is provider/account-scoped, not model-scoped. Prefer the
|
|
351
|
+
// current route key, then provider-wide cache, then the freshest same-provider
|
|
352
|
+
// route entry so a model switch keeps showing quota before the next live fetch.
|
|
280
353
|
if (!provider || !model) return [];
|
|
281
354
|
try {
|
|
282
355
|
const cachePath = path.join(pluginDataDir(), 'gateway-oauth-usage-cache.json');
|
|
@@ -285,8 +358,17 @@ function cachedQuotaWindowsFallback(provider, model) {
|
|
|
285
358
|
if (!routes) return [];
|
|
286
359
|
const routeKey = `${String(provider).toLowerCase()}${String(model)}`;
|
|
287
360
|
const providerOnlyKey = String(provider).toLowerCase();
|
|
288
|
-
const
|
|
289
|
-
|
|
361
|
+
const routePrefix = `${providerOnlyKey}`;
|
|
362
|
+
const entry = routes[routeKey] || routes[providerOnlyKey] || Object.entries(routes)
|
|
363
|
+
.filter(([key, value]) => key.startsWith(routePrefix) && Array.isArray(value?.quotaWindows))
|
|
364
|
+
.sort((a, b) => (Number(b[1]?.cachedAt) || 0) - (Number(a[1]?.cachedAt) || 0))[0]?.[1];
|
|
365
|
+
if (!Array.isArray(entry?.quotaWindows)) return [];
|
|
366
|
+
// Boot guard: do not render previous-launch usage before the current runtime
|
|
367
|
+
// has captured at least one snapshot. Once captured in this process, hold it
|
|
368
|
+
// instead of blanking it during idle/network gaps.
|
|
369
|
+
const cachedAt = Number(entry.cachedAt);
|
|
370
|
+
if (!Number.isFinite(cachedAt) || cachedAt < STATUSLINE_PROCESS_STARTED_AT_MS) return [];
|
|
371
|
+
return entry.quotaWindows;
|
|
290
372
|
} catch {
|
|
291
373
|
return [];
|
|
292
374
|
}
|
|
@@ -336,7 +418,22 @@ function configuredGatewayStatus(options = {}) {
|
|
|
336
418
|
const cfgPath = path.join(pluginDataDir(), 'mixdog-config.json');
|
|
337
419
|
const cfg = readJson(cfgPath);
|
|
338
420
|
const gatewayBase = cfg?.gateway && typeof cfg.gateway === 'object' ? cfg.gateway : {};
|
|
339
|
-
|
|
421
|
+
let { route: gateway, sessionScoped } = sessionRouteFor(gatewayBase, options.sessionId, options.clientHostPid);
|
|
422
|
+
const currentRoute = options?.currentRoute && typeof options.currentRoute === 'object' ? options.currentRoute : null;
|
|
423
|
+
const currentProvider = cleanString(currentRoute?.provider || currentRoute?.defaultProvider);
|
|
424
|
+
const currentModel = cleanString(currentRoute?.model || currentRoute?.defaultModel);
|
|
425
|
+
if ((!cleanString(gateway?.defaultProvider) || !cleanString(gateway?.defaultModel)) && currentProvider && currentModel) {
|
|
426
|
+
gateway = {
|
|
427
|
+
...(gateway || {}),
|
|
428
|
+
mode: cleanString(gateway?.mode) || 'fixed',
|
|
429
|
+
defaultProvider: currentProvider,
|
|
430
|
+
defaultModel: currentModel,
|
|
431
|
+
...(cleanString(currentRoute?.effort ?? currentRoute?.displayEffort) ? { effort: cleanString(currentRoute?.effort ?? currentRoute?.displayEffort) } : {}),
|
|
432
|
+
...(cleanBool(currentRoute?.fast) !== null ? { fast: cleanBool(currentRoute.fast) } : {}),
|
|
433
|
+
...(num(currentRoute?.contextWindow, null) ? { contextWindow: num(currentRoute.contextWindow, null) } : {}),
|
|
434
|
+
...(num(currentRoute?.rawContextWindow, null) ? { rawContextWindow: num(currentRoute.rawContextWindow, null) } : {}),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
340
437
|
const modules = cfg?.modules && typeof cfg.modules === 'object' ? cfg.modules : {};
|
|
341
438
|
if (modules.gateway && modules.gateway.enabled === false) return null;
|
|
342
439
|
const inherit = cleanString(gateway.mode) === CLAUDE_CURRENT_MODE;
|
|
@@ -381,11 +478,17 @@ function configuredGatewayStatus(options = {}) {
|
|
|
381
478
|
: inherit
|
|
382
479
|
? cleanBool(aliasTarget?.fast) ?? cleanBool(inherited?.fast) ?? cleanBool(gateway.fast) ?? cleanBool(preset?.fast) ?? false
|
|
383
480
|
: cleanBool(gateway.fast) ?? cleanBool(preset?.fast) ?? false;
|
|
384
|
-
const
|
|
481
|
+
const routeMatchesCurrent = currentProvider === provider && currentModel === model;
|
|
482
|
+
const contextSeed = {
|
|
483
|
+
...(inherited || {}),
|
|
484
|
+
...(routeMatchesCurrent ? currentRoute || {} : {}),
|
|
485
|
+
};
|
|
486
|
+
const contextMeta = routeContextMeta(provider, info, aliasTarget ? {} : contextSeed);
|
|
385
487
|
return {
|
|
386
488
|
mode: inherit ? CLAUDE_CURRENT_MODE : '',
|
|
387
489
|
provider,
|
|
388
490
|
model,
|
|
491
|
+
providerKind: providerKind(provider),
|
|
389
492
|
modelDisplay: statuslineDisplayForModel(
|
|
390
493
|
provider,
|
|
391
494
|
model,
|
|
@@ -464,12 +567,29 @@ export function loadGatewayStatus(options = {}) {
|
|
|
464
567
|
num(contextBoundary, 0),
|
|
465
568
|
)
|
|
466
569
|
: null;
|
|
467
|
-
const
|
|
570
|
+
const contextNumerator = liveContextTokens !== null && Number.isFinite(liveContextTokens)
|
|
571
|
+
? liveContextTokens
|
|
572
|
+
: (overflowContextTokens ?? null);
|
|
573
|
+
const recomputedContextUsedPct = pctOf(contextNumerator, contextBoundary);
|
|
468
574
|
const activeQuotaWindows = metricsMatch && Array.isArray(active.gateway_quota_windows)
|
|
469
575
|
? active.gateway_quota_windows
|
|
470
576
|
: [];
|
|
577
|
+
const statusProvider = configured?.provider || active.gateway_provider;
|
|
578
|
+
const statusProviderKind = configured?.providerKind
|
|
579
|
+
|| (routeMatchesConfigured ? cleanString(active.gateway_provider_kind) : '')
|
|
580
|
+
|| providerKind(statusProvider);
|
|
581
|
+
// Sanitize the auto-compact limit before display: a stale boundary/window-
|
|
582
|
+
// sized active.gateway_auto_compact_token_limit must not surface as an
|
|
583
|
+
// explicit autoCompactTokenLimit (the runtime would read it as the trigger
|
|
584
|
+
// and collapse the buffer). The active value must be validated against the
|
|
585
|
+
// ACTIVE route's own boundary/window — NOT configured?.contextWindow. The
|
|
586
|
+
// configured window can be larger than the active window, so using it would
|
|
587
|
+
// let a stale active full-window value (== active window) slip through. Use
|
|
588
|
+
// the active boundary (active context/raw window, or the lastCompact budget
|
|
589
|
+
// when present) and null the active value when it is >= that boundary.
|
|
590
|
+
const statusAutoCompactTokenLimit = resolveStatusAutoCompactTokenLimit(configured, active, lastCompact);
|
|
471
591
|
const activeStatus = {
|
|
472
|
-
provider:
|
|
592
|
+
provider: statusProvider,
|
|
473
593
|
model: configured?.model || active.gateway_model,
|
|
474
594
|
modelDisplay: configured?.modelDisplay
|
|
475
595
|
|| statuslineDisplayForModel(active.gateway_provider, active.gateway_model, {}, active.gateway_model_display)
|
|
@@ -479,13 +599,13 @@ export function loadGatewayStatus(options = {}) {
|
|
|
479
599
|
contextWindow: configured?.contextWindow ?? num(active.gateway_context_window, null),
|
|
480
600
|
rawContextWindow: configured?.rawContextWindow ?? num(active.gateway_raw_context_window, null),
|
|
481
601
|
effectiveContextWindowPercent: configured?.effectiveContextWindowPercent ?? num(active.gateway_effective_context_window_percent, null),
|
|
482
|
-
autoCompactTokenLimit:
|
|
602
|
+
autoCompactTokenLimit: statusAutoCompactTokenLimit,
|
|
483
603
|
contextUsedPct: metricsMatch ? recomputedContextUsedPct ?? num(active.gateway_context_used_pct, null) : null,
|
|
484
604
|
lastUsage,
|
|
485
605
|
quotaWindows: activeQuotaWindows.length ? activeQuotaWindows : (configuredStatus?.quotaWindows || []),
|
|
486
606
|
balance: metricsMatch && active.gateway_balance && typeof active.gateway_balance === 'object' ? active.gateway_balance : (configuredStatus?.balance || null),
|
|
487
607
|
routeSpend: metricsMatch && active.gateway_route_spend && typeof active.gateway_route_spend === 'object' ? active.gateway_route_spend : (configuredStatus?.routeSpend || null),
|
|
488
|
-
providerKind:
|
|
608
|
+
providerKind: statusProviderKind,
|
|
489
609
|
};
|
|
490
610
|
if (!configured) return activeStatus;
|
|
491
611
|
return activeStatus;
|
|
@@ -510,6 +630,17 @@ export function formatGatewayLimitSegments(status, fmt) {
|
|
|
510
630
|
} = fmt || {};
|
|
511
631
|
const segments = [];
|
|
512
632
|
const kind = cleanString(status.providerKind || '').toLowerCase();
|
|
633
|
+
const providerId = cleanString(status.provider || '').toLowerCase();
|
|
634
|
+
const statusSource = cleanString(status.source || '').toLowerCase();
|
|
635
|
+
const balanceSource = cleanString(status.balance?.source || '').toLowerCase();
|
|
636
|
+
const isOpenAiOAuth = (kind === 'oauth' || providerId.includes('oauth'))
|
|
637
|
+
&& (
|
|
638
|
+
providerId === 'openai-oauth'
|
|
639
|
+
|| statusSource.includes('openai')
|
|
640
|
+
|| statusSource.includes('codex')
|
|
641
|
+
|| balanceSource.includes('openai')
|
|
642
|
+
|| balanceSource.includes('codex')
|
|
643
|
+
);
|
|
513
644
|
const isPlainApi = kind === 'api';
|
|
514
645
|
const windows = !isPlainApi && Array.isArray(status.quotaWindows) ? status.quotaWindows : [];
|
|
515
646
|
const maxWindows = COLS >= 120 ? 3 : COLS >= 80 ? 2 : 1;
|
|
@@ -537,6 +668,7 @@ export function formatGatewayLimitSegments(status, fmt) {
|
|
|
537
668
|
return false;
|
|
538
669
|
};
|
|
539
670
|
const addApiBalance = () => {
|
|
671
|
+
if (isOpenAiOAuth) return false;
|
|
540
672
|
const remaining = num(status.balance?.remainingUsd, null);
|
|
541
673
|
if (remaining !== null) {
|
|
542
674
|
segments.push(`${D}Credit${R} ${money(remaining)}`);
|
|
@@ -551,12 +683,12 @@ export function formatGatewayLimitSegments(status, fmt) {
|
|
|
551
683
|
for (const w of windows.slice(0, maxWindows)) {
|
|
552
684
|
const label = String(w?.label || 'USE').toUpperCase();
|
|
553
685
|
const pct = num(w?.usedPct, null);
|
|
554
|
-
const remaining = num(w?.remainingUsd, null);
|
|
555
|
-
const limit = num(w?.limitUsd, null);
|
|
556
|
-
const used = num(w?.usedUsd, null);
|
|
557
|
-
const remainingCredits = num(w?.remainingCredits, null);
|
|
558
|
-
const limitCredits = num(w?.limitCredits, null);
|
|
559
|
-
const usedCredits = num(w?.usedCredits, null);
|
|
686
|
+
const remaining = isOpenAiOAuth ? null : num(w?.remainingUsd, null);
|
|
687
|
+
const limit = isOpenAiOAuth ? null : num(w?.limitUsd, null);
|
|
688
|
+
const used = isOpenAiOAuth ? null : num(w?.usedUsd, null);
|
|
689
|
+
const remainingCredits = isOpenAiOAuth ? null : num(w?.remainingCredits, null);
|
|
690
|
+
const limitCredits = isOpenAiOAuth ? null : num(w?.limitCredits, null);
|
|
691
|
+
const usedCredits = isOpenAiOAuth ? null : num(w?.usedCredits, null);
|
|
560
692
|
const estimated = isLocalEstimateWindow(w);
|
|
561
693
|
if (remaining !== null) {
|
|
562
694
|
const color = estimated ? YLW : remaining <= 1 ? RED : remaining <= 5 ? YLW : GRN;
|
|
@@ -581,7 +713,7 @@ export function formatGatewayLimitSegments(status, fmt) {
|
|
|
581
713
|
}
|
|
582
714
|
if (segments.length) return segments;
|
|
583
715
|
|
|
584
|
-
const remaining = num(status.balance?.remainingUsd, null);
|
|
716
|
+
const remaining = isOpenAiOAuth ? null : num(status.balance?.remainingUsd, null);
|
|
585
717
|
if (remaining !== null) {
|
|
586
718
|
segments.push(`${D}Credit${R} ${money(remaining)}`);
|
|
587
719
|
addRouteSpend();
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { formatGatewayLimitSegments } from './statusline-route.mjs';
|
|
4
|
+
|
|
5
|
+
test('formatGatewayLimitSegments hides OpenAI OAuth credit balance on L1', () => {
|
|
6
|
+
const segments = formatGatewayLimitSegments({
|
|
7
|
+
provider: 'openai-oauth',
|
|
8
|
+
providerKind: 'oauth',
|
|
9
|
+
source: 'openai-codex-wham',
|
|
10
|
+
quotaWindows: [
|
|
11
|
+
{ label: '5H', source: 'openai-codex-oauth', usedPct: 42 },
|
|
12
|
+
{ label: '7D', source: 'openai-codex-oauth', usedPct: 5 },
|
|
13
|
+
],
|
|
14
|
+
balance: {
|
|
15
|
+
source: 'openai-codex-credits',
|
|
16
|
+
remainingUsd: 12.34,
|
|
17
|
+
},
|
|
18
|
+
}, { COLS: 120 });
|
|
19
|
+
|
|
20
|
+
assert.deepEqual(segments, ['5H 42%', '7D 5%']);
|
|
21
|
+
assert.equal(segments.some((segment) => /Credit|\$12\.34/.test(segment)), false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('formatGatewayLimitSegments falls back to percent for OpenAI OAuth credit windows', () => {
|
|
25
|
+
const segments = formatGatewayLimitSegments({
|
|
26
|
+
provider: 'openai-oauth',
|
|
27
|
+
providerKind: 'oauth',
|
|
28
|
+
quotaWindows: [
|
|
29
|
+
{
|
|
30
|
+
label: '5H',
|
|
31
|
+
source: 'openai-codex-oauth',
|
|
32
|
+
usedPct: 55,
|
|
33
|
+
remainingUsd: 4,
|
|
34
|
+
usedUsd: 6,
|
|
35
|
+
limitUsd: 10,
|
|
36
|
+
remainingCredits: 40,
|
|
37
|
+
usedCredits: 60,
|
|
38
|
+
limitCredits: 100,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
}, { COLS: 120 });
|
|
42
|
+
|
|
43
|
+
assert.deepEqual(segments, ['5H 55%']);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('formatGatewayLimitSegments keeps non-OpenAI OAuth credit balance on L1', () => {
|
|
47
|
+
const segments = formatGatewayLimitSegments({
|
|
48
|
+
provider: 'anthropic-oauth',
|
|
49
|
+
providerKind: 'oauth',
|
|
50
|
+
source: 'anthropic-oauth',
|
|
51
|
+
quotaWindows: [
|
|
52
|
+
{ label: '5H', source: 'anthropic-oauth', usedPct: 20 },
|
|
53
|
+
],
|
|
54
|
+
balance: {
|
|
55
|
+
source: 'anthropic-oauth-extra',
|
|
56
|
+
remainingUsd: 7.5,
|
|
57
|
+
},
|
|
58
|
+
}, { COLS: 120 });
|
|
59
|
+
|
|
60
|
+
assert.ok(segments.includes('Credit $7.50'));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('formatGatewayLimitSegments suppresses OpenAI OAuth credit via providerId even without a source hint', () => {
|
|
64
|
+
// Mirrors the merged quotaStatus shape (mergeQuotaStatus) where `source` may
|
|
65
|
+
// be absent and `balance.source` is generic: the providerId === openai-oauth
|
|
66
|
+
// branch alone must still suppress any Credit/$ leak on L1.
|
|
67
|
+
const segments = formatGatewayLimitSegments({
|
|
68
|
+
provider: 'openai-oauth',
|
|
69
|
+
providerKind: 'oauth',
|
|
70
|
+
quotaWindows: [
|
|
71
|
+
{ label: '5H', source: 'openai-codex-oauth', usedPct: 30 },
|
|
72
|
+
],
|
|
73
|
+
balance: {
|
|
74
|
+
remainingUsd: 99.99,
|
|
75
|
+
},
|
|
76
|
+
}, { COLS: 120 });
|
|
77
|
+
|
|
78
|
+
assert.deepEqual(segments, ['5H 30%']);
|
|
79
|
+
assert.equal(segments.some((segment) => /Credit|\$/.test(segment)), false);
|
|
80
|
+
});
|
|
@@ -139,7 +139,7 @@ export function setAnthropicBaseUrl(url, { dryRun = false, autoCompactWindow = n
|
|
|
139
139
|
|
|
140
140
|
// RUNTIME base-url sync for live /model changes.
|
|
141
141
|
//
|
|
142
|
-
//
|
|
142
|
+
// The host client re-applies settings.env into process.env while the session is
|
|
143
143
|
// alive, but deleting ANTHROPIC_BASE_URL does not unset the already-present
|
|
144
144
|
// process env key. For native Claude choices, write the official Anthropic base
|
|
145
145
|
// URL instead. For mixdog choices, write the local gateway URL. This helper is
|
|
@@ -190,8 +190,8 @@ export function syncAnthropicBaseUrl(url, { dryRun = false, requireExisting = tr
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
// RUNTIME compact-window sync (Stage 4) — used by the gateway request path when
|
|
193
|
-
// the routed model's context window changes at runtime (/model switch).
|
|
194
|
-
//
|
|
193
|
+
// the routed model's context window changes at runtime (/model switch). The host
|
|
194
|
+
// client watches settings.json (chokidar) and re-applies env to process.env
|
|
195
195
|
// without restart, and getEffectiveContextWindowSize reads process.env live, so
|
|
196
196
|
// rewriting CLAUDE_CODE_AUTO_COMPACT_WINDOW here makes the auto-compact
|
|
197
197
|
// threshold track the live model next turn.
|
|
@@ -313,7 +313,7 @@ export function writeClaudeCodeCurrentSnapshot(ccJsonInput) {
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
export function formatClaudeCurrentChoiceLabel(route = readClaudeCodeCurrentRoute()) {
|
|
316
|
-
const parts = [`
|
|
316
|
+
const parts = [`Current selection: ${route.modelDisplay || route.model || 'current model'}`];
|
|
317
317
|
if (route.effort) parts.push(String(route.effort).toUpperCase());
|
|
318
318
|
if (route.fast === true) parts.push('fast');
|
|
319
319
|
return parts.join(' · ');
|
|
@@ -103,7 +103,7 @@ function defaultEffectiveContextWindowPercent(_provider) {
|
|
|
103
103
|
// models.dev, or native provider catalogs). Reserve a small universal
|
|
104
104
|
// headroom for output/tool/system tokens while keeping the raw model window
|
|
105
105
|
// visible separately.
|
|
106
|
-
return
|
|
106
|
+
return 90;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
function effectiveContextWindowPercent(provider, info = {}, seed = {}) {
|
|
@@ -124,6 +124,16 @@ function effectiveContextWindow(rawContextWindow, percent) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
function autoCompactTokenLimit(provider, rawContextWindow, contextWindow, info = {}, seed = {}) {
|
|
127
|
+
// routeInfo.autoCompactTokenLimit is an EXPLICIT auto-compaction limit only.
|
|
128
|
+
// It must NOT be derived from the effective/raw context window: the runtime
|
|
129
|
+
// (manager.mjs resolveSessionContextMeta / loop.mjs resolveWorkerCompactPolicy)
|
|
130
|
+
// treats autoCompactTokenLimit as the compaction trigger. Deriving it from the
|
|
131
|
+
// full boundary makes autoTriggerTokens == boundary, collapses the compaction
|
|
132
|
+
// buffer to 0, and auto-compaction only fires once the window is already full
|
|
133
|
+
// (where semantic compact overflows and the turn is lost). The boundary/window
|
|
134
|
+
// fallback that status + gateway env still need lives in compactBoundaryForRoute
|
|
135
|
+
// / autoCompactWindowForRoute, which read contextWindow/rawContextWindow
|
|
136
|
+
// directly. Return null when there is no explicit provider/catalog/seed limit.
|
|
127
137
|
const explicit = num(
|
|
128
138
|
seed.autoCompactTokenLimit
|
|
129
139
|
?? seed.auto_compact_token_limit
|
|
@@ -131,12 +141,25 @@ function autoCompactTokenLimit(provider, rawContextWindow, contextWindow, info =
|
|
|
131
141
|
?? info.auto_compact_token_limit,
|
|
132
142
|
0,
|
|
133
143
|
);
|
|
144
|
+
if (!(explicit > 0)) return null;
|
|
145
|
+
// An explicit limit is only a real auto-compaction trigger when it sits
|
|
146
|
+
// STRICTLY BELOW the boundary/window. A legacy seed/info value equal to or
|
|
147
|
+
// above the boundary is a derived full-window artifact — returning
|
|
148
|
+
// Math.min(explicit, derived) would still surface the boundary, which the
|
|
149
|
+
// runtime then treats as the trigger and collapses the compaction buffer.
|
|
150
|
+
// Drop those to null so display + downstream fall back to the default
|
|
151
|
+
// boundary trigger.
|
|
134
152
|
const derived = num(contextWindow, 0) || num(rawContextWindow, 0);
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
if (derived > 0) return explicit < derived ? explicit : null;
|
|
154
|
+
// No boundary known: keep the positive explicit value (cannot be proven to
|
|
155
|
+
// be a full-window artifact), but never derive one.
|
|
156
|
+
return explicit;
|
|
138
157
|
}
|
|
139
158
|
|
|
159
|
+
// Test-only export of the explicit-vs-derived auto-compact-limit resolver
|
|
160
|
+
// (see scripts/compact-trigger-migration-smoke.mjs).
|
|
161
|
+
export const _autoCompactTokenLimit = autoCompactTokenLimit;
|
|
162
|
+
|
|
140
163
|
function routeIdentityKey(routeInfo = {}) {
|
|
141
164
|
return [
|
|
142
165
|
routeInfo.provider || '',
|
|
@@ -333,7 +356,7 @@ function compactBoundaryForRoute(routeInfo = {}, compact = null) {
|
|
|
333
356
|
return rawContextWindow > 0 ? rawContextWindow : 0;
|
|
334
357
|
}
|
|
335
358
|
|
|
336
|
-
// Single source of truth for the value
|
|
359
|
+
// Single source of truth for the value synced to host env
|
|
337
360
|
// CLAUDE_CODE_AUTO_COMPACT_WINDOW. BOTH the --enable-time write
|
|
338
361
|
// (scripts/gateway-model.mjs) and the runtime sync (src/gateway/server.mjs)
|
|
339
362
|
// must derive the window the SAME way, or they disagree on the value written to
|
|
@@ -418,7 +441,22 @@ export function summarizeGatewayUsage(routeInfo, providerOut, compact = null, du
|
|
|
418
441
|
const routeSectionStartedAt = ensureRouteSection(routeInfo);
|
|
419
442
|
const promptTokens = promptFootprintTokens(routeInfo?.provider, u);
|
|
420
443
|
const boundaryTokens = contextUsageBoundary(routeInfo, compact);
|
|
421
|
-
|
|
444
|
+
// Prefer the transcript-estimate footprint for the displayed pct: it is
|
|
445
|
+
// monotonic and window-bounded, unlike provider input_tokens which can swing
|
|
446
|
+
// wildly (and produce >1000% pct) on some providers (e.g. OpenAI gpt-5.5).
|
|
447
|
+
// compact.afterTokens / beforeTokens = estimateMessagesTokens(working)+reserve.
|
|
448
|
+
const estimateFootprintTokens = num(compact?.afterTokens, 0) || num(compact?.beforeTokens, 0);
|
|
449
|
+
let contextUsedPct = null;
|
|
450
|
+
if (boundaryTokens > 0) {
|
|
451
|
+
if (estimateFootprintTokens > 0) {
|
|
452
|
+
// Estimate-based numerator may legitimately exceed boundary -> real >100%.
|
|
453
|
+
contextUsedPct = round(estimateFootprintTokens * 100 / boundaryTokens, 2);
|
|
454
|
+
} else {
|
|
455
|
+
// Fallback to provider tokens; clamp to a sane ceiling since these are
|
|
456
|
+
// not reliably window-bounded.
|
|
457
|
+
contextUsedPct = Math.min(100, round(promptTokens * 100 / boundaryTokens, 2));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
422
460
|
const costUsd = usageCostUsd(routeInfo, u);
|
|
423
461
|
const usageCompact = compact && typeof compact === 'object' ? { ...compact } : compact;
|
|
424
462
|
return {
|
|
@@ -226,7 +226,7 @@ export function gatewayConfigForSessionBoot(globalGateway = {}, sessionId = null
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
|
-
// 3. If
|
|
229
|
+
// 3. If the host session did not provide a reliable prior transcript on /new,
|
|
230
230
|
// carry the last explicit route for this terminal only. This keeps multiple
|
|
231
231
|
// terminals isolated without falling back to the process-global default.
|
|
232
232
|
if (pid) {
|