mixdog 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/gate-local.mjs +19 -3
- package/scripts/model-catalog-audit.mjs +1 -15
- package/src/agents/reviewer/AGENT.md +3 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +1 -0
- package/src/repl/slash-commands.mjs +16 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
- package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +25 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +73 -35
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +34 -7
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +30 -14
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +102 -23
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +77 -17
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -0
- package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +21 -82
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +56 -23
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +34 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
- package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
- package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +31 -20
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
- package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -219
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
- package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +41 -21
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/memory/lib/pg/process.mjs +34 -24
- package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
- package/src/runtime/office/README.md +1 -1
- package/src/runtime/office/shared/values.mjs +0 -5
- package/src/runtime/shared/background-tasks.mjs +92 -14
- package/src/runtime/shared/err-text.mjs +25 -0
- package/src/runtime/shared/error-presentation.mjs +159 -19
- package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +123 -134
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
- package/src/runtime/shared/llm/model-catalog.mjs +53 -40
- package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
- package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
- package/src/runtime/shared/model-display.mjs +13 -0
- package/src/runtime/shared/quota-resume-store.mjs +11 -0
- package/src/runtime/shared/session-baseline-store.mjs +960 -0
- package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
- package/src/runtime/shared/task-notification-envelope.mjs +2 -2
- package/src/runtime/shared/turn-changes.mjs +447 -219
- package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
- package/src/runtime/shared/turn-snapshot.mjs +170 -48
- package/src/runtime/shared/turn-worktree-snapshot.mjs +135 -46
- package/src/runtime/shared/worktrees/contract.mjs +0 -24
- package/src/runtime/shared/worktrees/test-support.mjs +19 -4
- package/src/session-runtime/boot/apis.mjs +6 -0
- package/src/session-runtime/boot/infrastructure.mjs +6 -0
- package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/connect.mjs +29 -9
- package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
- package/src/session-runtime/mcp-glue.mjs +3 -0
- package/src/session-runtime/mcp-oauth-login.mjs +174 -0
- package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
- package/src/session-runtime/model-route/set-route.mjs +83 -15
- package/src/session-runtime/model-route/web-search-route.mjs +1 -1
- package/src/session-runtime/native-web-search/routes.mjs +22 -3
- package/src/session-runtime/provider-models/model-meta.mjs +12 -3
- package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
- package/src/session-runtime/quick-web-search-models.mjs +13 -38
- package/src/session-runtime/resource-extension-sync.mjs +4 -0
- package/src/session-runtime/resource-mcp-api.mjs +98 -2
- package/src/session-runtime/runtime-review-api.mjs +13 -0
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
- package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
- package/src/session-runtime/settings-compaction-api.mjs +1 -0
- package/src/standalone/daemon-drain-policy.mjs +8 -0
- package/src/standalone/daemon-telemetry.mjs +55 -0
- package/src/standalone/daemon.mjs +3 -7
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
- package/src/standalone/session-service/agent-tree.mjs +12 -0
- package/src/standalone/session-service/quota-resume.mjs +8 -0
- package/src/standalone/session-service/retention/busy.mjs +15 -1
- package/src/standalone/session-service-api.mjs +5 -0
- package/src/standalone/session-service.mjs +1 -0
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/create-app-pickers.mjs +4 -0
- package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
- package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
- package/src/tui/app/extension-pickers.mjs +2 -1
- package/src/tui/app/model-options.mjs +11 -16
- package/src/tui/app/slash-commands.mjs +7 -2
- package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
- package/src/tui/components/StatusLine.jsx +2 -0
- package/src/tui/components/status-line/local-line.mjs +5 -2
- package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
- package/src/tui/dist/index.mjs +816 -43
- package/src/tui/session/quota-resume.mjs +46 -14
- package/src/tui/session/session-api/commands.mjs +33 -24
- package/src/tui/session/session-api/extensions.mjs +22 -0
- package/src/tui/session/session-api/integrations.mjs +7 -0
- package/src/tui/session/session-api/lifecycle.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +71 -7
- package/src/tui/session/session-api/settings.mjs +5 -14
- package/src/tui/session/session-flow/drain.mjs +7 -1
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
- package/src/tui/session/turn.mjs +38 -15
- package/src/tui/statusline-ansi-bridge.mjs +1 -1
- package/src/ui/statusline-agents.mjs +15 -4
- package/src/ui/statusline.mjs +7 -3
- package/src/ui/streaming-markdown-heal.mjs +34 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
- package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
- package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
|
@@ -12,17 +12,16 @@ import {
|
|
|
12
12
|
existsSync,
|
|
13
13
|
mkdirSync,
|
|
14
14
|
mkdtempSync,
|
|
15
|
-
readdirSync,
|
|
16
15
|
readFileSync,
|
|
17
16
|
rmdirSync,
|
|
18
17
|
rmSync,
|
|
19
|
-
statSync,
|
|
20
18
|
symlinkSync,
|
|
21
19
|
unlinkSync,
|
|
22
20
|
writeFileSync,
|
|
23
21
|
} from 'node:fs';
|
|
22
|
+
import { readdir, readFile, rm, stat } from 'node:fs/promises';
|
|
24
23
|
import { join } from 'node:path';
|
|
25
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
24
|
+
import { execFile, spawn, spawnSync } from 'node:child_process';
|
|
26
25
|
import { createConnection, createServer } from 'node:net';
|
|
27
26
|
import { isPidAlive } from '../../../shared/pid-liveness.mjs';
|
|
28
27
|
|
|
@@ -607,22 +606,36 @@ export function classifyOrphanTempPostmaster({
|
|
|
607
606
|
return Number(uptimeSec) >= Math.max(60, Number(minUptimeSec) || ORPHAN_TEMP_PG_MIN_UPTIME_SEC);
|
|
608
607
|
}
|
|
609
608
|
|
|
610
|
-
function tempPostmasterOwnerAlive(args) {
|
|
609
|
+
async function tempPostmasterOwnerAlive(args) {
|
|
611
610
|
const rawDataDir = pgDataDirFromArgs(args);
|
|
612
611
|
if (!rawDataDir) return false;
|
|
613
612
|
const dataRoot = join(rawDataDir, '..');
|
|
614
613
|
for (const name of ['memory-runtime-owner.json', 'daemon-owner.json']) {
|
|
615
614
|
try {
|
|
616
|
-
const owner = JSON.parse(
|
|
615
|
+
const owner = JSON.parse(await readFile(join(dataRoot, name), 'utf8'));
|
|
617
616
|
if (Number(owner?.pid) > 0 && isPidAlive(Number(owner.pid))) return true;
|
|
618
617
|
} catch {}
|
|
619
618
|
}
|
|
620
619
|
return false;
|
|
621
620
|
}
|
|
622
621
|
|
|
623
|
-
|
|
622
|
+
// Async child run: stdout on exit 0, null on any failure/timeout (the old
|
|
623
|
+
// spawnSync `status !== 0` contract). Never blocks the event loop.
|
|
624
|
+
function execFileText(file, args, options) {
|
|
625
|
+
return new Promise((resolve) => {
|
|
626
|
+
try {
|
|
627
|
+
execFile(file, args, { encoding: 'utf8', ...options }, (error, stdout) => {
|
|
628
|
+
resolve(error ? null : String(stdout || ''));
|
|
629
|
+
});
|
|
630
|
+
} catch {
|
|
631
|
+
resolve(null);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async function listPostmasterRows() {
|
|
624
637
|
if (process.platform === 'win32') {
|
|
625
|
-
const
|
|
638
|
+
const stdout = await execFileText(
|
|
626
639
|
'powershell',
|
|
627
640
|
[
|
|
628
641
|
'-NoProfile',
|
|
@@ -632,10 +645,10 @@ function listPostmasterRows() {
|
|
|
632
645
|
'started = [int](New-TimeSpan -Start $_.CreationDate -End (Get-Date)).TotalSeconds; ' +
|
|
633
646
|
'args = $_.CommandLine } }',
|
|
634
647
|
],
|
|
635
|
-
{
|
|
648
|
+
{ timeout: 15_000, windowsHide: true }
|
|
636
649
|
);
|
|
637
|
-
if (
|
|
638
|
-
return
|
|
650
|
+
if (stdout === null) return [];
|
|
651
|
+
return stdout
|
|
639
652
|
.split(/\r?\n/)
|
|
640
653
|
.filter(Boolean)
|
|
641
654
|
.flatMap((line) => {
|
|
@@ -647,12 +660,9 @@ function listPostmasterRows() {
|
|
|
647
660
|
}
|
|
648
661
|
});
|
|
649
662
|
}
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
});
|
|
654
|
-
if (result.status !== 0) return [];
|
|
655
|
-
return String(result.stdout || '')
|
|
663
|
+
const stdout = await execFileText('ps', ['-eo', 'pid=,etimes=,args='], { timeout: 15_000 });
|
|
664
|
+
if (stdout === null) return [];
|
|
665
|
+
return stdout
|
|
656
666
|
.split(/\r?\n/)
|
|
657
667
|
.flatMap((line) => {
|
|
658
668
|
const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.*postgres.*)$/);
|
|
@@ -679,11 +689,11 @@ function pristineRootForArgs(args, tempRoot) {
|
|
|
679
689
|
// (observed: 87 roots, 6.1GB, alongside 57 live postmasters). Roots a live
|
|
680
690
|
// postmaster still owns are kept, and the same uptime margin that protects a
|
|
681
691
|
// running harness from the process sweep protects its directory here.
|
|
682
|
-
function sweepAbandonedPristineRoots(tempRoot, liveRoots, minAgeMs) {
|
|
692
|
+
async function sweepAbandonedPristineRoots(tempRoot, liveRoots, minAgeMs) {
|
|
683
693
|
let removed = 0;
|
|
684
694
|
let entries;
|
|
685
695
|
try {
|
|
686
|
-
entries =
|
|
696
|
+
entries = await readdir(tempRoot, { withFileTypes: true });
|
|
687
697
|
} catch {
|
|
688
698
|
return 0;
|
|
689
699
|
}
|
|
@@ -693,12 +703,12 @@ function sweepAbandonedPristineRoots(tempRoot, liveRoots, minAgeMs) {
|
|
|
693
703
|
const full = join(tempRoot, entry.name);
|
|
694
704
|
if (liveRoots.has(normalizePathForMatch(full))) continue;
|
|
695
705
|
try {
|
|
696
|
-
if (
|
|
706
|
+
if ((await stat(full)).mtimeMs > cutoff) continue;
|
|
697
707
|
} catch {
|
|
698
708
|
continue;
|
|
699
709
|
}
|
|
700
710
|
try {
|
|
701
|
-
|
|
711
|
+
await rm(full, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
702
712
|
removed += 1;
|
|
703
713
|
} catch {
|
|
704
714
|
/* a process this sweep did not reap still holds it */
|
|
@@ -709,18 +719,18 @@ function sweepAbandonedPristineRoots(tempRoot, liveRoots, minAgeMs) {
|
|
|
709
719
|
|
|
710
720
|
/** Best-effort machine hygiene: reap orphan temp postmasters, then drop the
|
|
711
721
|
* throwaway roots no live postmaster owns. Runs periodically, not once. */
|
|
712
|
-
export function sweepOrphanTempPostmasters({ tempRoot, minUptimeSec } = {}) {
|
|
722
|
+
export async function sweepOrphanTempPostmasters({ tempRoot, minUptimeSec } = {}) {
|
|
713
723
|
let reaped = 0;
|
|
714
724
|
try {
|
|
715
725
|
const root = tempRoot || process.env.TMPDIR || process.env.TEMP || process.env.TMP || '/tmp';
|
|
716
726
|
const liveRoots = new Set();
|
|
717
|
-
for (const row of listPostmasterRows()) {
|
|
727
|
+
for (const row of await listPostmasterRows()) {
|
|
718
728
|
if (!row.pid || row.pid === process.pid) continue;
|
|
719
729
|
if (
|
|
720
730
|
!classifyOrphanTempPostmaster({
|
|
721
731
|
...row,
|
|
722
732
|
tempRoot: root,
|
|
723
|
-
ownerAlive: tempPostmasterOwnerAlive(row.args),
|
|
733
|
+
ownerAlive: await tempPostmasterOwnerAlive(row.args),
|
|
724
734
|
minUptimeSec,
|
|
725
735
|
})
|
|
726
736
|
) {
|
|
@@ -737,7 +747,7 @@ export function sweepOrphanTempPostmasters({ tempRoot, minUptimeSec } = {}) {
|
|
|
737
747
|
}
|
|
738
748
|
}
|
|
739
749
|
const minAgeMs = Math.max(60, Number(minUptimeSec) || ORPHAN_TEMP_PG_MIN_UPTIME_SEC) * 1000;
|
|
740
|
-
const removed = sweepAbandonedPristineRoots(root, liveRoots, minAgeMs);
|
|
750
|
+
const removed = await sweepAbandonedPristineRoots(root, liveRoots, minAgeMs);
|
|
741
751
|
if (removed > 0) __mixdogMemoryLog(`[pg-process] removed ${removed} abandoned pristine root(s)\n`);
|
|
742
752
|
} catch {
|
|
743
753
|
/* hygiene is best-effort */
|
|
@@ -372,6 +372,16 @@ function readPostmasterInfo(pgdata) {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
|
|
375
|
+
// Async child run (never blocks the daemon loop): stdout on exit 0, else null.
|
|
376
|
+
async function _execFileText(file, args) {
|
|
377
|
+
const { execFile } = await import('node:child_process');
|
|
378
|
+
return new Promise((resolveText) => {
|
|
379
|
+
execFile(file, args, { encoding: 'utf8', windowsHide: true, timeout: 15_000 }, (error, stdout) => {
|
|
380
|
+
resolveText(error ? null : String(stdout || ''));
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
375
385
|
/**
|
|
376
386
|
* Best-effort process-name check: confirms the pid's comm name contains 'postgres'.
|
|
377
387
|
* Falls back to true (alive) when the name cannot be determined.
|
|
@@ -383,19 +393,12 @@ async function isPostgresPid(pid) {
|
|
|
383
393
|
return comm.includes('postgres');
|
|
384
394
|
}
|
|
385
395
|
if (process.platform === 'darwin') {
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
if (r.status === 0) return (r.stdout || '').trim().includes('postgres');
|
|
389
|
-
return true;
|
|
396
|
+
const out = await _execFileText('ps', ['-o', 'comm=', '-p', String(pid)]);
|
|
397
|
+
return out === null ? true : out.trim().includes('postgres');
|
|
390
398
|
}
|
|
391
399
|
if (process.platform === 'win32') {
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
encoding: 'utf8',
|
|
395
|
-
windowsHide: true,
|
|
396
|
-
});
|
|
397
|
-
if (r.status === 0) return (r.stdout || '').toLowerCase().includes('postgres');
|
|
398
|
-
return true;
|
|
400
|
+
const out = await _execFileText('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH']);
|
|
401
|
+
return out === null ? true : out.toLowerCase().includes('postgres');
|
|
399
402
|
}
|
|
400
403
|
} catch {
|
|
401
404
|
/* cannot read — fall back to alive */
|
|
@@ -463,8 +466,8 @@ function scheduleOrphanTempPostmasterSweep() {
|
|
|
463
466
|
_orphanSweepStarted = true;
|
|
464
467
|
const runSweep = () => {
|
|
465
468
|
void _getPgProc()
|
|
466
|
-
.then(({ sweepOrphanTempPostmasters }) => {
|
|
467
|
-
const reaped = sweepOrphanTempPostmasters?.() || 0;
|
|
469
|
+
.then(async ({ sweepOrphanTempPostmasters }) => {
|
|
470
|
+
const reaped = (await sweepOrphanTempPostmasters?.()) || 0;
|
|
468
471
|
if (reaped > 0) __mixdogMemoryLog(`[supervisor-pg] orphan sweep reaped ${reaped} temp postmaster(s)\n`);
|
|
469
472
|
})
|
|
470
473
|
.catch(() => {
|
|
@@ -17,7 +17,7 @@ Microsoft Office installed.
|
|
|
17
17
|
| `design/` | Design tokens, content model, composition, creative direction for the Word and Excel composers (`compose_document`, `compose_sheet`); `design-discipline.mjs` owns the safe typeface list, palette repair (tinted dark fields, WCAG contrast, paired accent tints), and the brief rules; `docx/`, `xlsx/` format compilers; `library/` template library with the bundled `templates/`. Decks are never composed here — a slide is authored by the model as a script (`authoring/`), and `compose_slide` is refused with that route. |
|
|
18
18
|
| `quality/` | Reviews and gates: `assurance-*.mjs` (trust, structure, rendered pages, checklist), aesthetics, design review, scoring, visual diff. |
|
|
19
19
|
| `bench/` | Benchmarks behind `npm run bench:office*`; `bench-support.mjs` holds the shared tool-call helpers. |
|
|
20
|
-
| `shared/` | Tiny helpers used across folders: `values.mjs` (`plainObject`, `clone`, `stableValue`, `sha256`, `clamp`, `compact
|
|
20
|
+
| `shared/` | Tiny helpers used across folders: `values.mjs` (`plainObject`, `clone`, `stableValue`, `sha256`, `clamp`, `compact`), `page-sizes.mjs` (the paper sizes PDF create and Word `set_page` share), `column-widths.mjs` (the widths a Word or PDF table given none takes from its text), and `asar-path.mjs`. |
|
|
21
21
|
|
|
22
22
|
## Dependency direction
|
|
23
23
|
|
|
@@ -32,8 +32,3 @@ export function compact(value, maximum = 120) {
|
|
|
32
32
|
.replace(/\s+/g, ' ')
|
|
33
33
|
.slice(0, maximum);
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
export function imageBuffer(image) {
|
|
37
|
-
if (Buffer.isBuffer(image?.data)) return image.data;
|
|
38
|
-
return Buffer.from(String(image?.data || ''), 'base64');
|
|
39
|
-
}
|
|
@@ -18,6 +18,10 @@ export { TOOL_ASYNC_EXECUTION_CONTRACT, TOOL_MANUAL_CONTROL_CONTRACT, TOOL_SYNC_
|
|
|
18
18
|
const TASK_TTL_MS = 30 * 60_000;
|
|
19
19
|
const MAX_TASKS = 300;
|
|
20
20
|
const TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
21
|
+
// A `notifyOnCancel` task reports its cancellation to the owner once, after its
|
|
22
|
+
// run settles, so the notification can carry what the run produced before it
|
|
23
|
+
// stopped. A run that never settles still reports, header-only, after this grace.
|
|
24
|
+
const CANCEL_NOTIFY_GRACE_MS = 10_000;
|
|
21
25
|
const tasks = new Map();
|
|
22
26
|
let seq = 0;
|
|
23
27
|
|
|
@@ -151,6 +155,7 @@ export function registerBackgroundTask({
|
|
|
151
155
|
resultType,
|
|
152
156
|
renderResult,
|
|
153
157
|
cancel,
|
|
158
|
+
notifyOnCancel = false,
|
|
154
159
|
} = {}) {
|
|
155
160
|
pruneTasks();
|
|
156
161
|
const explicitId = clean(taskId);
|
|
@@ -189,6 +194,7 @@ export function registerBackgroundTask({
|
|
|
189
194
|
resultType: clean(resultType) || null,
|
|
190
195
|
renderResult: typeof renderResult === 'function' ? renderResult : null,
|
|
191
196
|
cancel: typeof cancel === 'function' ? cancel : null,
|
|
197
|
+
notifyOnCancel: notifyOnCancel === true,
|
|
192
198
|
};
|
|
193
199
|
tasks.set(id, task);
|
|
194
200
|
return task;
|
|
@@ -200,10 +206,14 @@ export function startBackgroundTask(options = {}) {
|
|
|
200
206
|
.then(() => options.run?.())
|
|
201
207
|
.then((result) => {
|
|
202
208
|
completeBackgroundTask(task.taskId, { status: 'completed', result, terminalReason: 'run-resolved' });
|
|
209
|
+
deliverCancelledNotification(task);
|
|
203
210
|
return result;
|
|
204
211
|
})
|
|
205
212
|
.catch((error) => {
|
|
206
|
-
if (task.status === 'cancelled')
|
|
213
|
+
if (task.status === 'cancelled') {
|
|
214
|
+
deliverCancelledNotification(task);
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
207
217
|
completeBackgroundTask(task.taskId, {
|
|
208
218
|
status: 'failed',
|
|
209
219
|
error,
|
|
@@ -258,15 +268,62 @@ export function cleanupBackgroundTasks(options = {}) {
|
|
|
258
268
|
return { removed: before - after, tasks: after };
|
|
259
269
|
}
|
|
260
270
|
|
|
261
|
-
|
|
271
|
+
// `notify`: true notifies immediately, false never notifies (owner teardown).
|
|
272
|
+
// Left unset, a `notifyOnCancel` task defers its single `cancelled`
|
|
273
|
+
// notification until its run settles (see settleCancelledBackgroundTask); any
|
|
274
|
+
// other task stays silent.
|
|
275
|
+
function cancelRunningTask(task, reason, notify) {
|
|
276
|
+
try {
|
|
277
|
+
task.cancel?.();
|
|
278
|
+
} catch {}
|
|
279
|
+
completeBackgroundTask(task.taskId, { status: 'cancelled', error: reason, notify: notify === true });
|
|
280
|
+
if (task.notifyOnCancel !== true || notify === true || task.status !== 'cancelled' || task.cancelNotify) return;
|
|
281
|
+
if (notify === false) {
|
|
282
|
+
task.cancelNotify = 'silent';
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
task.cancelNotify = 'pending';
|
|
286
|
+
task.cancelNotifyTimer = setTimeout(() => deliverCancelledNotification(task), CANCEL_NOTIFY_GRACE_MS);
|
|
287
|
+
task.cancelNotifyTimer.unref?.();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Owner teardown/delete (`notify: false`) also silences a cancellation that is
|
|
291
|
+
// still waiting for its run to settle.
|
|
292
|
+
function resolvePendingCancelNotify(task, next) {
|
|
293
|
+
if (task.cancelNotify !== 'pending') return false;
|
|
294
|
+
clearTimeout(task.cancelNotifyTimer);
|
|
295
|
+
task.cancelNotifyTimer = null;
|
|
296
|
+
task.cancelNotify = next;
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function silenceCancelledNotification(task) {
|
|
301
|
+
resolvePendingCancelNotify(task, 'silent');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function deliverCancelledNotification(task) {
|
|
305
|
+
if (!resolvePendingCancelNotify(task, 'delivered')) return false;
|
|
306
|
+
if (tasks.get(task.taskId) !== task) return false;
|
|
307
|
+
task.meta = sanitizeTaskMeta(task.meta);
|
|
308
|
+
return notifyTaskCompletion(task, task.notificationInstruction);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function cancelBackgroundTask(taskId, reason = 'cancelled', { notify } = {}) {
|
|
262
312
|
const task = getBackgroundTask(taskId);
|
|
263
313
|
if (!task) return null;
|
|
264
|
-
if (!TERMINAL_STATUSES.has(task.status))
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
314
|
+
if (!TERMINAL_STATUSES.has(task.status)) cancelRunningTask(task, reason, notify);
|
|
315
|
+
else if (notify === false) silenceCancelledNotification(task);
|
|
316
|
+
return task;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** The cancelled run has stopped: attach what it produced before stopping (if
|
|
320
|
+
* any) and deliver the task's single deferred `cancelled` notification. A
|
|
321
|
+
* no-op unless that notification is still pending. */
|
|
322
|
+
export function settleCancelledBackgroundTask(taskId, { result } = {}) {
|
|
323
|
+
const task = getBackgroundTask(taskId);
|
|
324
|
+
if (task?.status !== 'cancelled' || task.cancelNotify !== 'pending') return null;
|
|
325
|
+
if (result !== undefined) task.result = result;
|
|
326
|
+
deliverCancelledNotification(task);
|
|
270
327
|
return task;
|
|
271
328
|
}
|
|
272
329
|
|
|
@@ -280,11 +337,11 @@ export function cancelBackgroundTasks(options = {}) {
|
|
|
280
337
|
// visible/recoverable, but may not be claimed by whichever session happens
|
|
281
338
|
// to close first.
|
|
282
339
|
if (!taskMatchesScope(task, options, { includeUnattributed: false })) continue;
|
|
283
|
-
if (TERMINAL_STATUSES.has(task.status))
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
340
|
+
if (TERMINAL_STATUSES.has(task.status)) {
|
|
341
|
+
if (options.notify === false) silenceCancelledNotification(task);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
cancelRunningTask(task, reason, options.notify);
|
|
288
345
|
cancelled += 1;
|
|
289
346
|
}
|
|
290
347
|
return { cancelled };
|
|
@@ -358,10 +415,26 @@ export function completeBackgroundTask(
|
|
|
358
415
|
return task;
|
|
359
416
|
}
|
|
360
417
|
|
|
418
|
+
// Surfaces whose later reads consume the structured result object itself:
|
|
419
|
+
// shell `task read` re-reads output via result paths and renders exit_code /
|
|
420
|
+
// timed_out / signal; agent job views return `job.result`. They keep it for the
|
|
421
|
+
// TTL; every other surface keeps only resultText + metadata once delivered.
|
|
422
|
+
const RESULT_KEPT_SURFACES = new Set(['shell', 'agent']);
|
|
423
|
+
|
|
424
|
+
function releaseDeliveredResult(task, body) {
|
|
425
|
+
if (task.result === undefined || RESULT_KEPT_SURFACES.has(task.surface)) return;
|
|
426
|
+
// Pin the rendered body first so re-renders and retries stay identical.
|
|
427
|
+
if (!task.resultText) task.resultText = body;
|
|
428
|
+
task.result = undefined;
|
|
429
|
+
}
|
|
430
|
+
|
|
361
431
|
export function notifyTaskCompletion(task, instruction) {
|
|
362
432
|
if (!task) return false;
|
|
363
433
|
if (!TERMINAL_STATUSES.has(task.status)) return false;
|
|
364
434
|
if (task.completionAcknowledged === true) return false;
|
|
435
|
+
// A deferred cancellation is delivered only by deliverCancelledNotification;
|
|
436
|
+
// a silent (owner-teardown) cancellation never notifies.
|
|
437
|
+
if (task.cancelNotify === 'pending' || task.cancelNotify === 'silent') return false;
|
|
365
438
|
// `notified` tracks whether *any* completion notification was sent (e.g. an
|
|
366
439
|
// early header-only preview from the agent surface). A later call that can
|
|
367
440
|
// finally include the result body must still fire once, otherwise the owner
|
|
@@ -396,7 +469,10 @@ export function notifyTaskCompletion(task, instruction) {
|
|
|
396
469
|
});
|
|
397
470
|
if (sent) {
|
|
398
471
|
task.notified = true;
|
|
399
|
-
if (hasBody)
|
|
472
|
+
if (hasBody) {
|
|
473
|
+
task.notifiedWithBody = true;
|
|
474
|
+
releaseDeliveredResult(task, body);
|
|
475
|
+
}
|
|
400
476
|
}
|
|
401
477
|
return sent;
|
|
402
478
|
}
|
|
@@ -457,6 +533,8 @@ export function renderBackgroundTaskNotification(task) {
|
|
|
457
533
|
status: task.status,
|
|
458
534
|
result,
|
|
459
535
|
error: task.error,
|
|
536
|
+
// A cancelled Agent's header names the cancel reason.
|
|
537
|
+
detail: task.surface === 'agent' && task.status === 'cancelled' ? task.error : null,
|
|
460
538
|
});
|
|
461
539
|
}
|
|
462
540
|
|
|
@@ -180,9 +180,34 @@ export function isCancelLikeError(error) {
|
|
|
180
180
|
);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
// One-line reason per provider kind. Fixed sentences (optionally with a
|
|
184
|
+
// parenthesised value) are the renderer's translation keys.
|
|
185
|
+
const KIND_LINES = {
|
|
186
|
+
overloaded: 'Provider is busy at capacity.',
|
|
187
|
+
rate_limited: ({ retryAfterMs }) =>
|
|
188
|
+
retryAfterMs ? `Provider is rate-limited (retry in ${formatDurationMs(retryAfterMs)}).` : 'Provider is rate-limited.',
|
|
189
|
+
quota_exhausted: 'Provider usage limit reached.',
|
|
190
|
+
entitlement: 'Not included in your plan.',
|
|
191
|
+
billing: 'Out of credits or at spend limit.',
|
|
192
|
+
permission: 'Access denied by the provider.',
|
|
193
|
+
policy: 'Refused by the provider safety policy.',
|
|
194
|
+
auth: 'Provider authentication failed.',
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Typed evidence: a persisted descriptor or structured status/code fields.
|
|
198
|
+
// A bare message carries none, so its wording keeps the legacy text path.
|
|
199
|
+
function hasTypedEvidence(error) {
|
|
200
|
+
if (!error || typeof error !== 'object') return false;
|
|
201
|
+
return Boolean(error.errorKind?.kind) || errorHttpStatus(error) > 0 || Boolean(error.code) || Boolean(error.retryAfterMs);
|
|
202
|
+
}
|
|
203
|
+
|
|
183
204
|
export function presentErrorText(error, options = {}) {
|
|
184
205
|
const presentation = describeError(error);
|
|
185
206
|
if (['image-size', 'payload-size', 'request'].includes(presentation.kind)) return presentation.summary;
|
|
207
|
+
const kindLine = hasTypedEvidence(error) ? KIND_LINES[presentation.providerKind] : null;
|
|
208
|
+
if (kindLine && presentation.kind !== 'unknown') {
|
|
209
|
+
return typeof kindLine === 'function' ? kindLine(presentation) : kindLine;
|
|
210
|
+
}
|
|
186
211
|
const surface = options.surface || options.tool || '';
|
|
187
212
|
const subject = subjectForSurface(surface);
|
|
188
213
|
const max = options.max ?? 240;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PROVIDER_ERROR_KINDS, providerErrorKind } from '../agent/orchestrator/providers/provider-error-kind.mjs';
|
|
1
2
|
import { transportErrorText } from './transport-error-text.mjs';
|
|
2
3
|
|
|
3
4
|
const DETAILS_LIMIT = 12_000;
|
|
@@ -72,34 +73,167 @@ export function isRejected429(value) {
|
|
|
72
73
|
return /\bAPI 429 request rejected\b/.test(messageOf(value));
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
// Provider kind → presentation. Summaries are also the persisted form of a
|
|
77
|
+
// failure, so each maps back to its kind when only the text survives.
|
|
78
|
+
const KIND_COPY = {
|
|
79
|
+
overloaded: ['overloaded', 'The provider is busy right now.', 'Try again later or choose another model.'],
|
|
80
|
+
rate_limited: ['rate-limit', 'The provider is temporarily rate-limiting requests.', 'Try again shortly.'],
|
|
81
|
+
quota_exhausted: [
|
|
82
|
+
'quota',
|
|
83
|
+
'The provider usage limit was reached.',
|
|
84
|
+
'Wait for the limit to reset or choose another account.',
|
|
85
|
+
],
|
|
86
|
+
entitlement: [
|
|
87
|
+
'entitlement',
|
|
88
|
+
"This model isn't included in your plan.",
|
|
89
|
+
'Upgrade your plan, or choose another model or provider.',
|
|
90
|
+
],
|
|
91
|
+
billing: [
|
|
92
|
+
'billing',
|
|
93
|
+
'The provider account is out of credits or at its spend limit.',
|
|
94
|
+
'Check your billing with the provider, then try again.',
|
|
95
|
+
],
|
|
96
|
+
permission: [
|
|
97
|
+
'permission',
|
|
98
|
+
"You don't have access to this resource.",
|
|
99
|
+
'Check your account permissions, or choose another model.',
|
|
100
|
+
],
|
|
101
|
+
context_overflow: [
|
|
102
|
+
'context-overflow',
|
|
103
|
+
'The conversation is too long for this model.',
|
|
104
|
+
'Compact the conversation or start a new one.',
|
|
105
|
+
],
|
|
106
|
+
request_too_large: [
|
|
107
|
+
'payload-size',
|
|
108
|
+
'The request exceeds the size limit.',
|
|
109
|
+
'Reduce the attachments or request size, then try again.',
|
|
110
|
+
],
|
|
111
|
+
policy: [
|
|
112
|
+
'policy',
|
|
113
|
+
'The provider refused this request under its safety policy.',
|
|
114
|
+
'Rephrase the request or choose another model.',
|
|
115
|
+
],
|
|
116
|
+
service_tier_unavailable: [
|
|
117
|
+
'service-tier',
|
|
118
|
+
'The requested service tier is unavailable right now.',
|
|
119
|
+
'Switch to the standard tier or try again later.',
|
|
120
|
+
],
|
|
121
|
+
};
|
|
122
|
+
const SUMMARY_KINDS = new Map(Object.entries(KIND_COPY).map(([providerKind, [, summary]]) => [summary, providerKind]));
|
|
123
|
+
|
|
124
|
+
function waitLabel(ms) {
|
|
125
|
+
const seconds = Math.max(1, Math.ceil(ms / 1000));
|
|
126
|
+
if (seconds < 60) return `${seconds}s`;
|
|
127
|
+
const minutes = Math.ceil(seconds / 60);
|
|
128
|
+
if (minutes < 60) return `${minutes}m`;
|
|
129
|
+
const hours = Math.floor(minutes / 60);
|
|
130
|
+
return minutes % 60 ? `${hours}h ${minutes % 60}m` : `${hours}h`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Local HH:mm, prefixed with month/day when the reset is not today. */
|
|
134
|
+
function resetLabel(resetAt) {
|
|
135
|
+
const reset = new Date(resetAt);
|
|
136
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
137
|
+
const time = `${pad(reset.getHours())}:${pad(reset.getMinutes())}`;
|
|
138
|
+
return reset.toDateString() === new Date().toDateString() ? time : `${reset.getMonth() + 1}/${reset.getDate()} ${time}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Only legacy items (no persisted descriptor) and untyped strings reach this.
|
|
142
|
+
const LEGACY_TEXT_KINDS = [
|
|
143
|
+
[/The provider usage limit was reached|quota.*(?:hit|exceed)|usage limit/i, 'quota_exhausted'],
|
|
144
|
+
[/rate[_ -]?limit|too many requests/i, 'rate_limited'],
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The provider kind descriptor: the persisted one, else typed evidence, else
|
|
149
|
+
* wording. `typed` is true when the kind came from a persisted descriptor or a
|
|
150
|
+
* typed error: wording heuristics must then never override it.
|
|
151
|
+
*/
|
|
152
|
+
function classify(value, raw, clean, status, code) {
|
|
153
|
+
const persisted = value?.errorKind;
|
|
154
|
+
if (persisted && PROVIDER_ERROR_KINDS.includes(persisted.kind)) {
|
|
155
|
+
return { descriptor: persisted, typed: persisted.kind !== 'unknown' };
|
|
156
|
+
}
|
|
157
|
+
const hasTypedFields =
|
|
158
|
+
value &&
|
|
159
|
+
typeof value === 'object' &&
|
|
160
|
+
Boolean(value.status || value.httpStatus || value.response?.status || value.code || value.retryAfterMs);
|
|
161
|
+
if (!hasTypedFields) {
|
|
162
|
+
// Legacy persisted strings: their wording (and wait) is the only evidence.
|
|
163
|
+
const legacy = SUMMARY_KINDS.get(clean) ?? LEGACY_TEXT_KINDS.find(([re]) => re.test(clean))?.[1];
|
|
164
|
+
if (legacy) {
|
|
165
|
+
const wait = /retryAfter=(\d+)(ms|s|m)\b/i.exec(raw);
|
|
166
|
+
const retryAfterMs = wait ? Number(wait[1]) * { ms: 1, s: 1000, m: 60_000 }[wait[2].toLowerCase()] : undefined;
|
|
167
|
+
return { descriptor: { kind: legacy, ...(retryAfterMs ? { retryAfterMs } : {}) }, typed: false };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
let descriptor = value && typeof value === 'object' ? providerErrorKind(value) : null;
|
|
171
|
+
const typed = Boolean(descriptor) && descriptor.kind !== 'unknown';
|
|
172
|
+
if (!descriptor || descriptor.kind === 'unknown') {
|
|
173
|
+
descriptor = providerErrorKind(
|
|
174
|
+
Object.assign(new Error(raw), { ...(status ? { status } : {}), ...(code ? { code } : {}) })
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (descriptor.kind === 'unknown') {
|
|
178
|
+
const known = SUMMARY_KINDS.get(clean);
|
|
179
|
+
const legacy = known ?? LEGACY_TEXT_KINDS.find(([re]) => re.test(clean))?.[1];
|
|
180
|
+
if (legacy) return { descriptor: { ...descriptor, kind: legacy }, typed: false };
|
|
181
|
+
}
|
|
182
|
+
return { descriptor, typed };
|
|
183
|
+
}
|
|
184
|
+
|
|
75
185
|
/** Presentation only: never changes retry policy or the underlying failure. */
|
|
76
186
|
export function describeError(value) {
|
|
77
|
-
const { raw, message, status, code } = unwrap(value);
|
|
78
|
-
const rejected429 = isRejected429(value);
|
|
187
|
+
const { raw, message, status: textStatus, code: textCode } = unwrap(value);
|
|
79
188
|
const details = safeErrorDetails(raw);
|
|
80
189
|
const clean = safeErrorDetails(message)
|
|
81
190
|
.replace(/^(?:Error invoking remote method ['"][^'"]+['"]:\s*)?(?:Error:\s*)?/i, '')
|
|
82
191
|
.trim();
|
|
192
|
+
const { descriptor, typed } = classify(value, raw, clean, textStatus, textCode);
|
|
193
|
+
// A typed or persisted kind owns the verdict: its own status and code stand
|
|
194
|
+
// in for the text-derived ones, and wording below may only refine it.
|
|
195
|
+
const status = typed ? Number(descriptor.status) || 0 : textStatus;
|
|
196
|
+
const code = typed ? String(descriptor.code || '') : textCode;
|
|
197
|
+
// A 429 refusal without quota headers refines a rate limit, nothing else.
|
|
198
|
+
const rejected429 = isRejected429(value) && (!typed || descriptor.kind === 'rate_limited');
|
|
199
|
+
const copy = KIND_COPY[descriptor.kind];
|
|
200
|
+
const retryAfterMs = Number(descriptor.retryAfterMs) > 0 ? Number(descriptor.retryAfterMs) : null;
|
|
201
|
+
const resetAt = Number(descriptor.resetAt) > 0 ? Number(descriptor.resetAt) : null;
|
|
83
202
|
let kind = 'unknown';
|
|
84
203
|
let summary = '';
|
|
85
204
|
let recovery = '';
|
|
86
|
-
|
|
205
|
+
// Localization hook: a recovery that carries a value is given as a template.
|
|
206
|
+
let recoveryTemplate = '';
|
|
207
|
+
let params = null;
|
|
208
|
+
// A persisted descriptor keeps the status and errno code, not the cause
|
|
209
|
+
// chain: rebuild the transport evidence from them. Wording only reads for
|
|
210
|
+
// untyped failures and transient ones (a bare "fetch failed" is that evidence).
|
|
211
|
+
const transport =
|
|
212
|
+
transportErrorText(value) ||
|
|
213
|
+
(typed && transportErrorText({ code: code.toUpperCase(), status })) ||
|
|
214
|
+
(!typed || descriptor.kind === 'transient' ? transportErrorText(raw) : null);
|
|
87
215
|
if (
|
|
88
216
|
value?.reauthRequired === true ||
|
|
89
|
-
|
|
217
|
+
(!typed &&
|
|
218
|
+
/invalid_grant|refresh token (?:expired|revoked|not available)|sign in again|sign-in expired|Reauth Required/i.test(raw))
|
|
90
219
|
) {
|
|
91
220
|
kind = 'reauth';
|
|
92
221
|
summary = 'Sign-in expired.';
|
|
93
222
|
recovery = 'Sign in again from Providers.';
|
|
94
223
|
} else if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
224
|
+
descriptor.kind === 'auth' ||
|
|
225
|
+
(descriptor.kind === 'unknown' &&
|
|
226
|
+
/\b(?:invalid|expired)\s+(?:api[ _-]?key|access token|credentials?)\b|Provider authentication failed/i.test(clean))
|
|
98
227
|
) {
|
|
99
228
|
kind = 'authentication';
|
|
100
229
|
summary = 'Provider authentication failed';
|
|
101
230
|
recovery = 'Check your sign-in or API key, then try again.';
|
|
231
|
+
} else if (rejected429) {
|
|
232
|
+
kind = 'request';
|
|
233
|
+
summary = 'The provider rejected this request.';
|
|
234
|
+
recovery = 'Review the request details before trying again.';
|
|
102
235
|
} else if (
|
|
236
|
+
['invalid_request', 'unknown', 'request_too_large'].includes(descriptor.kind) &&
|
|
103
237
|
/image.*dimensions.*exceed|image.*(?:too large|maximum.*(?:size|dimension))|many-image requests|An attached image exceeds the provider limit/i.test(
|
|
104
238
|
clean
|
|
105
239
|
)
|
|
@@ -108,20 +242,21 @@ export function describeError(value) {
|
|
|
108
242
|
summary = 'An attached image exceeds the provider limit.';
|
|
109
243
|
recovery = 'Reduce the image dimensions or number of images, then try again.';
|
|
110
244
|
} else if (
|
|
111
|
-
|
|
245
|
+
descriptor.kind === 'unknown' &&
|
|
112
246
|
/payload too large|request (?:body )?too large|The request exceeds the size limit/i.test(clean)
|
|
113
247
|
) {
|
|
114
|
-
kind =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
248
|
+
[kind, summary, recovery] = KIND_COPY.request_too_large;
|
|
249
|
+
} else if (copy) {
|
|
250
|
+
[kind, summary, recovery] = copy;
|
|
251
|
+
if (descriptor.kind === 'rate_limited' && retryAfterMs) {
|
|
252
|
+
recovery = `Try again in ${waitLabel(retryAfterMs)}.`;
|
|
253
|
+
recoveryTemplate = 'Try again in {{wait}}.';
|
|
254
|
+
params = { wait: waitLabel(retryAfterMs) };
|
|
255
|
+
} else if (descriptor.kind === 'quota_exhausted' && resetAt) {
|
|
256
|
+
recovery = `It resets at ${resetLabel(resetAt)}. Switch accounts or providers to continue now.`;
|
|
257
|
+
recoveryTemplate = 'It resets at {{time}}. Switch accounts or providers to continue now.';
|
|
258
|
+
params = { time: resetLabel(resetAt) };
|
|
259
|
+
}
|
|
125
260
|
} else if (transport) {
|
|
126
261
|
kind = 'connection';
|
|
127
262
|
summary = transport.replace(/\.$/, '');
|
|
@@ -150,6 +285,11 @@ export function describeError(value) {
|
|
|
150
285
|
kind,
|
|
151
286
|
summary,
|
|
152
287
|
recovery,
|
|
288
|
+
recoveryTemplate,
|
|
289
|
+
params,
|
|
290
|
+
providerKind: descriptor.kind,
|
|
291
|
+
resetAt,
|
|
292
|
+
retryAfterMs,
|
|
153
293
|
details,
|
|
154
294
|
// Known structured failures ignore volatile indexes; unknown failures
|
|
155
295
|
// retain their diagnostic identity rather than merging unrelated work.
|