cawdex 1.35.75 → 1.35.77

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.
Files changed (89) hide show
  1. package/README.md +5 -5
  2. package/bin/anycode.js +2 -2
  3. package/bin/cawdex.js +408 -408
  4. package/bin/ecc-hooks.cjs +11 -11
  5. package/dist/agents-md.d.ts +31 -0
  6. package/dist/agents-md.js +340 -0
  7. package/dist/agents-md.js.map +1 -0
  8. package/dist/agents.js +1424 -1424
  9. package/dist/api.d.ts +1 -0
  10. package/dist/api.js +19 -14
  11. package/dist/api.js.map +1 -1
  12. package/dist/autonomous-loops.js +287 -287
  13. package/dist/benchmark-repos.d.ts +31 -0
  14. package/dist/benchmark-repos.js +234 -8
  15. package/dist/benchmark-repos.js.map +1 -1
  16. package/dist/command-palette.js +5 -2
  17. package/dist/command-palette.js.map +1 -1
  18. package/dist/compaction.js +8 -8
  19. package/dist/config.js +57 -36
  20. package/dist/config.js.map +1 -1
  21. package/dist/content-engine.js +543 -543
  22. package/dist/context-brief.d.ts +4 -0
  23. package/dist/context-brief.js +230 -0
  24. package/dist/context-brief.js.map +1 -0
  25. package/dist/cost-tracker.d.ts +33 -14
  26. package/dist/cost-tracker.js +81 -19
  27. package/dist/cost-tracker.js.map +1 -1
  28. package/dist/coverage.js +39 -39
  29. package/dist/docs-sync.js +98 -98
  30. package/dist/evaluation.js +452 -452
  31. package/dist/fixed-footer.d.ts +11 -2
  32. package/dist/fixed-footer.js +115 -26
  33. package/dist/fixed-footer.js.map +1 -1
  34. package/dist/git-workflow.js +49 -49
  35. package/dist/imports.d.ts +126 -0
  36. package/dist/imports.js +611 -0
  37. package/dist/imports.js.map +1 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.js +367 -66
  40. package/dist/index.js.map +1 -1
  41. package/dist/inline-suggest.js +136 -12
  42. package/dist/inline-suggest.js.map +1 -1
  43. package/dist/live-queue.js +1 -1
  44. package/dist/live-queue.js.map +1 -1
  45. package/dist/model-aliases.d.ts +37 -0
  46. package/dist/model-aliases.js +203 -0
  47. package/dist/model-aliases.js.map +1 -0
  48. package/dist/orchestration.js +15 -15
  49. package/dist/permissions.d.ts +6 -0
  50. package/dist/permissions.js +53 -0
  51. package/dist/permissions.js.map +1 -1
  52. package/dist/pm2-manager.js +26 -26
  53. package/dist/query.d.ts +0 -1
  54. package/dist/query.js +105 -41
  55. package/dist/query.js.map +1 -1
  56. package/dist/refactor.js +87 -87
  57. package/dist/repo-command.js +7 -1
  58. package/dist/repo-command.js.map +1 -1
  59. package/dist/search-first.js +92 -92
  60. package/dist/skill-create.js +100 -100
  61. package/dist/stitch.js +1 -1
  62. package/dist/system-prompt.d.ts +2 -1
  63. package/dist/system-prompt.js +10 -5
  64. package/dist/system-prompt.js.map +1 -1
  65. package/dist/tools/github-repo-digest.d.ts +1 -1
  66. package/dist/tools/github-repo-digest.js +38 -6
  67. package/dist/tools/github-repo-digest.js.map +1 -1
  68. package/dist/types.d.ts +9 -0
  69. package/dist/types.js.map +1 -1
  70. package/dist/verification.js +55 -55
  71. package/package.json +1 -1
  72. package/resources/__init__.py +1 -1
  73. package/resources/exgentic/cawdex_agent/README.md +114 -114
  74. package/resources/exgentic/cawdex_agent/__init__.py +5 -5
  75. package/resources/exgentic/cawdex_agent/agent.py +605 -605
  76. package/resources/exgentic/cawdex_agent/requirements.txt +2 -2
  77. package/resources/exgentic/cawdex_agent/setup.sh +21 -21
  78. package/resources/exgentic/cawdex_agent/utils.py +1061 -1061
  79. package/resources/hal/cawdex_agent/README.md +24 -24
  80. package/resources/hal/cawdex_agent/__init__.py +1 -1
  81. package/resources/hal/cawdex_agent/main.py +550 -550
  82. package/resources/hal/cawdex_agent/requirements.txt +2 -2
  83. package/resources/kbench/cawdex_agent/README.md +107 -107
  84. package/resources/kbench/cawdex_agent/adapter.manifest.json +19 -19
  85. package/resources/kbench/cawdex_agent/runner.mjs +753 -753
  86. package/resources/open_agent_leaderboard/cawdex-agent-card.md +119 -119
  87. package/resources/terminal_bench/__init__.py +1 -1
  88. package/resources/terminal_bench/cawdex_agent.py +174 -174
  89. package/resources/terminal_bench/setup.sh +121 -121
@@ -1,6 +1,59 @@
1
1
  import chalk from 'chalk';
2
2
  import { saveConfig } from './config.js';
3
3
  import { evaluateCommand } from './execpolicy.js';
4
+ export function explainPermission(tool, input, config) {
5
+ const lines = [
6
+ `tool: ${tool.name}`,
7
+ `permission mode: ${config.permissionMode}`,
8
+ ];
9
+ if (tool.name === 'bash') {
10
+ const command = String(input.command || '').trim();
11
+ const policy = evaluateCommand(command);
12
+ lines.push(`execpolicy: ${policy.decision}${policy.ruleId ? ` (${policy.ruleId})` : ''}`);
13
+ if (policy.reason)
14
+ lines.push(`execpolicy reason: ${policy.reason}`);
15
+ if (policy.decision === 'forbidden') {
16
+ lines.push('result: blocked before the normal permission prompt');
17
+ return {
18
+ decision: 'deny',
19
+ reason: policy.reason || 'blocked by execpolicy',
20
+ lines,
21
+ };
22
+ }
23
+ if (policy.decision === 'allow') {
24
+ lines.push('result: allowed by execpolicy before mode checks');
25
+ return {
26
+ decision: 'allow',
27
+ reason: `execpolicy${policy.ruleId ? ` ${policy.ruleId}` : ''}`,
28
+ lines,
29
+ };
30
+ }
31
+ }
32
+ if (config.permissionMode === 'yolo') {
33
+ lines.push('result: allowed because yolo permits all non-forbidden tools');
34
+ return { decision: 'allow', reason: 'permission mode yolo', lines };
35
+ }
36
+ if (tool.isReadOnly) {
37
+ lines.push('result: allowed because read-only tools do not prompt');
38
+ return { decision: 'allow', reason: 'read-only tool', lines };
39
+ }
40
+ if (config.alwaysAllowedTools?.includes(tool.name)) {
41
+ lines.push('result: allowed by the per-tool always-allow list');
42
+ return { decision: 'allow', reason: 'always-allow list', lines };
43
+ }
44
+ if (config.permissionMode === 'auto' && !tool.isDestructive) {
45
+ lines.push('result: allowed because auto permits non-destructive tools');
46
+ return { decision: 'allow', reason: 'auto mode non-destructive tool', lines };
47
+ }
48
+ lines.push('result: prompt required before execution');
49
+ return {
50
+ decision: 'prompt',
51
+ reason: config.permissionMode === 'auto'
52
+ ? 'destructive tool in auto mode'
53
+ : 'ask mode requires confirmation',
54
+ lines,
55
+ };
56
+ }
4
57
  /**
5
58
  * Check if a tool call is allowed under the current permission mode.
6
59
  * Returns true if allowed, false if denied.
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
@@ -63,32 +63,32 @@ export function listPM2Services(cwd) {
63
63
  * @returns A prompt with guidance on creating ecosystem.config.js
64
64
  */
65
65
  export function buildEcosystemPrompt(cwd) {
66
- return `To manage multiple services with PM2, create an ecosystem.config.js file in ${cwd}.
67
-
68
- Example structure:
69
- module.exports = {
70
- apps: [
71
- {
72
- name: 'api-server',
73
- script: './dist/server.js',
74
- instances: 2,
75
- exec_mode: 'cluster',
76
- max_memory_restart: '500M',
77
- env: {
78
- NODE_ENV: 'production'
79
- }
80
- },
81
- {
82
- name: 'worker',
83
- script: './dist/worker.js',
84
- instances: 1,
85
- max_memory_restart: '300M'
86
- }
87
- ]
88
- };
89
-
90
- Then start all services with: pm2 start ecosystem.config.js
91
- Monitor logs with: pm2 logs
66
+ return `To manage multiple services with PM2, create an ecosystem.config.js file in ${cwd}.
67
+
68
+ Example structure:
69
+ module.exports = {
70
+ apps: [
71
+ {
72
+ name: 'api-server',
73
+ script: './dist/server.js',
74
+ instances: 2,
75
+ exec_mode: 'cluster',
76
+ max_memory_restart: '500M',
77
+ env: {
78
+ NODE_ENV: 'production'
79
+ }
80
+ },
81
+ {
82
+ name: 'worker',
83
+ script: './dist/worker.js',
84
+ instances: 1,
85
+ max_memory_restart: '300M'
86
+ }
87
+ ]
88
+ };
89
+
90
+ Then start all services with: pm2 start ecosystem.config.js
91
+ Monitor logs with: pm2 logs
92
92
  View status with: pm2 status`;
93
93
  }
94
94
  /**
package/dist/query.d.ts CHANGED
@@ -4,7 +4,6 @@ import type { Mode } from './modes.js';
4
4
  export declare function resolveFirstTokenTimeoutMs(config: Pick<CawdexConfig, 'model' | 'provider'>): number;
5
5
  export declare function resolveStreamIdleTimeoutMs(fastDirect?: boolean): number;
6
6
  export declare function isKnownFlakyOpenRouterModel(config: Pick<CawdexConfig, 'model' | 'provider'>): boolean;
7
- export declare function fallbackModelForKnownFlakyTurn(config: CawdexConfig, usedFallbackModel?: boolean): string | null;
8
7
  export declare function isTurnCancelKeySequence(chunk: Buffer): boolean;
9
8
  export declare function shouldUseFastDirectReply(userQuery: string | undefined, mode: Mode, env?: NodeJS.ProcessEnv): boolean;
10
9
  export interface WorkingIndicator {
package/dist/query.js CHANGED
@@ -9,7 +9,7 @@ import { buildSystemPrompt } from './system-prompt.js';
9
9
  import { runHooks } from './hooks.js';
10
10
  import { scanToolCall, printSecurityWarning } from './security.js';
11
11
  import { trackUsage } from './cost-tracker.js';
12
- import { shouldCompact, compactMessages, quickCompact, buildCompactionConfig, contextCapTokens, enforceContextCap, inferContextWindowTokens, } from './compaction.js';
12
+ import { shouldCompact, compactMessages, quickCompact, estimateTokens, buildCompactionConfig, contextCapTokens, enforceContextCap, inferContextWindowTokens, } from './compaction.js';
13
13
  import { assistantTranscriptPrefix, theme, sym, printToolRun, printToolResult, printThinkingOpen, printThinkingText, printThinkingClose, printCost, printApiError, formatDuration, categorizeApiError } from './theme.js';
14
14
  import { isVoiceEnabled, getTtsConfig, getAccessibilityConfig, speakAssistantResponse, speak, speakUserEcho, } from './voice.js';
15
15
  import { isLikelyDestructive, describeDestructive, countWords, summarize } from './accessibility.js';
@@ -20,6 +20,7 @@ import * as liveQueue from './live-queue.js';
20
20
  import { isFooterActive, setFooterActivity, setFooterCost, writeScrollableLine } from './fixed-footer.js';
21
21
  import { applyQueuedInputChunk, drainQueuedInputBytes, queuedInputBytesToText } from './prompt-buffer.js';
22
22
  import { emit as dbgEmit } from './debug.js';
23
+ import { applyAgentToolInstructions } from './agents-md.js';
23
24
  import { buildBenchmarkCompletionReminder, buildBenchmarkTrajectorySystemBlock, makeBenchmarkInvalidToolActionEvent, makeBenchmarkTraceEvent, writeBenchmarkTrace, } from './benchmark-trace.js';
24
25
  import { buildTodoStateBlock } from './tools/todo.js';
25
26
  import { buildRuntimeInfoBlock } from './runtime-info.js';
@@ -30,11 +31,11 @@ import { archiveLargeToolOutput } from './tool-output-archive.js';
30
31
  // not persisted — restart, see hint again. Keyed by sessionId so different
31
32
  // sessions get fresh hints.
32
33
  const _thinkingHintShownForSession = new Set();
33
- const INTERACTIVE_FIRST_TOKEN_TIMEOUT_MS = 12_000;
34
+ const INTERACTIVE_FIRST_TOKEN_TIMEOUT_MS = 8_000;
34
35
  const INTERACTIVE_FLAKY_FIRST_TOKEN_TIMEOUT_MS = 6_000;
35
36
  const NON_INTERACTIVE_FIRST_TOKEN_TIMEOUT_MS = 60_000;
36
37
  const NON_INTERACTIVE_FLAKY_FIRST_TOKEN_TIMEOUT_MS = 20_000;
37
- const FAST_DIRECT_FIRST_TOKEN_TIMEOUT_MS = 8_000;
38
+ const FAST_DIRECT_FIRST_TOKEN_TIMEOUT_MS = 5_000;
38
39
  const INTERACTIVE_STREAM_IDLE_TIMEOUT_MS = 45_000;
39
40
  const NON_INTERACTIVE_STREAM_IDLE_TIMEOUT_MS = 120_000;
40
41
  const FAST_DIRECT_STREAM_IDLE_TIMEOUT_MS = 20_000;
@@ -84,13 +85,6 @@ function fallbackModelForTurn(config, usedFallbackModel) {
84
85
  return null;
85
86
  return fallback;
86
87
  }
87
- export function fallbackModelForKnownFlakyTurn(config, usedFallbackModel = false) {
88
- if (process.env.CAWDEX_ALLOW_FLAKY_MODELS === '1')
89
- return null;
90
- if (!isKnownFlakyOpenRouterModel(config))
91
- return null;
92
- return fallbackModelForTurn(config, usedFallbackModel);
93
- }
94
88
  export function isTurnCancelKeySequence(chunk) {
95
89
  const seq = chunk.toString('utf8');
96
90
  return (seq === '\x1b' ||
@@ -122,6 +116,7 @@ const FAST_DIRECT_POSITIVE = [
122
116
  ];
123
117
  const FAST_DIRECT_REPO_OR_TOOL = /\b(repo|repository|codebase|workspace|file|folder|directory|path|terminal|powershell|shell|command|npm|node|python|typescript|javascript|git|commit|diff|pr|pull request|branch|test|build|lint|install|package|debug|error|stack trace|log|fix|implement|refactor|review|audit|security|benchmark|run|execute|read|search|grep|edit|patch|write[- ]file|website|web\s*site|site|portfolio|landing\s+page|web\s+page|html|css|react|vue|svelte|vite|next\.?js|single[- ]file|dashboard|component|form|desktop|save|hireable|resume)\b/i;
124
118
  const FAST_DIRECT_CONTEXTUAL = /^(?:(?:please\s+)?(?:can|could|would|will)\s+you\s+(?:please\s+)?|please\s+)?(continue|carry on|resume|do it|same|again|that|this|those|these|it)\b/i;
119
+ const FAST_DIRECT_REVISION_CONTEXT = /^(?:(?:please\s+)?(?:can|could|would|will)\s+you\s+(?:please\s+)?)?(?:make|change|update|revise|rewrite|redo|adjust|convert|turn)\s+(?:it|that|this|him|her|them|he|she)\b/i;
125
120
  export function shouldUseFastDirectReply(userQuery, mode, env = process.env) {
126
121
  if (env.CAWDEX_FAST_DIRECT === '0')
127
122
  return false;
@@ -136,6 +131,8 @@ export function shouldUseFastDirectReply(userQuery, mode, env = process.env) {
136
131
  return false;
137
132
  if (FAST_DIRECT_CONTEXTUAL.test(text))
138
133
  return false;
134
+ if (FAST_DIRECT_REVISION_CONTEXT.test(text))
135
+ return false;
139
136
  if (FAST_DIRECT_REPO_OR_TOOL.test(text))
140
137
  return false;
141
138
  return FAST_DIRECT_POSITIVE.some((pattern) => pattern.test(text));
@@ -154,9 +151,9 @@ function printInteractiveTurnAccepted(config) {
154
151
  }
155
152
  }
156
153
  export function formatWorkingIndicatorFrame(elapsedMs, frameIndex = 0, message = 'Working') {
157
- const frames = ['\u25e6', '\u25c7', '\u25c6', '\u25c7'];
154
+ const frames = ['\u25dc', '\u25dd', '\u25de', '\u25df'];
158
155
  const frame = frames[Math.abs(frameIndex) % frames.length];
159
- return ` ${frame} ${message} (${formatDuration(elapsedMs)} \u2022 esc to interrupt)`;
156
+ return ` ${frame} ${message} (${formatDuration(elapsedMs)} \u2022 Esc/F5 to interrupt)`;
160
157
  }
161
158
  function startWorkingIndicator(startedAtMs, screenReader, turn = 0) {
162
159
  if (screenReader)
@@ -166,11 +163,11 @@ function startWorkingIndicator(startedAtMs, screenReader, turn = 0) {
166
163
  if (!process.stdout.isTTY)
167
164
  return null;
168
165
  const messages = [
169
- 'Working hard on your request',
170
- 'Sumi ink settling',
171
- 'Edo lanterns lit',
172
- 'Kamon crest aligned',
173
- 'Neon alley quiet',
166
+ 'Sumi ink moving',
167
+ 'Edo lanterns cycling',
168
+ 'Kamon crest pulsing',
169
+ 'Neon shoji breathing',
170
+ 'Signal blade drawn',
174
171
  ];
175
172
  let frame = 0;
176
173
  let stopped = false;
@@ -1129,18 +1126,21 @@ export async function runQuery(ctx) {
1129
1126
  // user's configured fallbackModel. After we use it, this latches so we
1130
1127
  // don't bounce back and forth between failing models in a single chain.
1131
1128
  let usedFallbackModel = false;
1132
- const immediateFallback = fallbackModelForKnownFlakyTurn(ctx.config, usedFallbackModel);
1133
- if (immediateFallback) {
1134
- usedFallbackModel = true;
1135
- const failedModel = ctx.config.model;
1136
- ctx.config.model = immediateFallback;
1129
+ // Reliability path when fallback is intentionally disabled:
1130
+ // retry the SAME configured model once on transient empty/timeout/unknown
1131
+ // provider failures before surfacing a hard error.
1132
+ let usedPrimaryRetry = false;
1133
+ const retryPrimaryModelOnce = (reason) => {
1134
+ if (usedPrimaryRetry)
1135
+ return false;
1136
+ usedPrimaryRetry = true;
1137
+ const model = ctx.config.model;
1137
1138
  resetClient();
1138
- console.log(theme.warning(` ${sym.warn} ${failedModel} is known to stall in interactive OpenRouter sessions; switching this turn to ${immediateFallback}.`));
1139
- console.log(theme.dim(' Override only if you really want it: CAWDEX_ALLOW_FLAKY_MODELS=1'));
1140
- }
1141
- if (!chainFastDirect) {
1142
- printInteractiveTurnAccepted(ctx.config);
1143
- }
1139
+ console.log(theme.warning(` ${sym.warn} ${reason} retrying once on the same model ${model}.`));
1140
+ console.log(theme.dim(' Fallback is disabled; this is a transient-recovery retry only.'));
1141
+ turns--;
1142
+ return true;
1143
+ };
1144
1144
  // Tracks whether ANY reasoning tokens arrived across the entire chain.
1145
1145
  // Used at chain-end to print a one-time "/thinking is ON but this model
1146
1146
  // doesn't emit reasoning" hint. Hoisted to chain scope (not per-turn)
@@ -1210,8 +1210,11 @@ export async function runQuery(ctx) {
1210
1210
  // as new text, drowning the actual response).
1211
1211
  const isScreenReader = ctx.config.voice?.accessibility?.screenReader === true;
1212
1212
  const inputGuard = startInputSuppression(isScreenReader);
1213
+ let earlyWorkingIndicator = null;
1213
1214
  try {
1214
1215
  if (!chainFastDirect) {
1216
+ printInteractiveTurnAccepted(ctx.config);
1217
+ earlyWorkingIndicator = startWorkingIndicator(chainStart, isScreenReader, 0);
1215
1218
  // Turn-boundary collapse runs BEFORE compaction. Every completed prior
1216
1219
  // turn becomes [user, "<final text>\n[Completed: used X, Y]"] — the
1217
1220
  // model no longer sees stale tool_calls that it might mistake for
@@ -1263,9 +1266,12 @@ export async function runQuery(ctx) {
1263
1266
  if (!fastDirect) {
1264
1267
  replaceMessagesInPlace(ctx.messages, quickCompact(ctx.messages));
1265
1268
  }
1269
+ const requestTools = fastDirect
1270
+ ? []
1271
+ : applyAgentToolInstructions(ALL_TOOLS, ctx.cwd, ctx.config.model);
1266
1272
  const systemPrompt = fastDirect
1267
1273
  ? FAST_DIRECT_SYSTEM_PROMPT
1268
- : buildSystemPrompt(ctx.config, ctx.cwd, ctx.mode, userQuery);
1274
+ : buildSystemPrompt(ctx.config, ctx.cwd, ctx.mode, userQuery, requestTools);
1269
1275
  let visibleMessages = fastDirect
1270
1276
  ? (userQuery ? [{ role: 'user', content: userQuery }] : [])
1271
1277
  : maskOldToolResults(ctx.messages);
@@ -1292,7 +1298,9 @@ export async function runQuery(ctx) {
1292
1298
  // override.
1293
1299
  const stateBlock = fastDirect ? null : buildStateBlock(visibleMessages);
1294
1300
  const runtimeInfoBlock = fastDirect ? null : buildRuntimeInfoBlock(ctx.cwd);
1295
- const repoMapBlock = fastDirect ? null : buildAutoRepoMapBlock(ctx.cwd, userQuery);
1301
+ const repoMapBlock = fastDirect || ctx.mode === 'design'
1302
+ ? null
1303
+ : buildAutoRepoMapBlock(ctx.cwd, userQuery);
1296
1304
  const globalPlanBlock = fastDirect ? null : buildGlobalPlanBlock(visibleMessages);
1297
1305
  const todoStateBlock = fastDirect ? null : buildTodoStateBlock(ctx.cwd);
1298
1306
  const benchmarkTrajectoryBlock = !fastDirect && ctx.mode === 'benchmark'
@@ -1326,6 +1334,7 @@ export async function runQuery(ctx) {
1326
1334
  let lastCharWasNewline = false; // collapse 3+ consecutive newlines down to 2
1327
1335
  let consecutiveNewlines = 0;
1328
1336
  const turnStart = Date.now();
1337
+ let usageRecorded = false;
1329
1338
  // Loop detection state: a stuck model can stream the SAME N-char
1330
1339
  // window of text 50+ times in a single API call (observed in the
1331
1340
  // wild with openrouter/owl-alpha emitting tool-call JSON as text).
@@ -1446,16 +1455,22 @@ export async function runQuery(ctx) {
1446
1455
  // line and then announce every subsequent token as "after the
1447
1456
  // waiting line", which is noisier than helpful).
1448
1457
  let firstTokenSeen = false;
1458
+ let firstTokenLatencyMs = null;
1449
1459
  // Note: the outer `isScreenReader` declared at the top of runQuery
1450
1460
  // (line ~340) is in scope here via closure — no need for a second
1451
1461
  // declaration. Previously this re-declared inside the while loop
1452
1462
  // and TypeScript tolerated it as a different block scope, but it
1453
1463
  // was confusing and the audit flagged it as bug-bait.
1454
1464
  //
1455
- // Live waiting indicator on the response line. It keeps elapsed time
1456
- // and the interrupt hint visible while still clearing itself before
1457
- // the first model event writes real output.
1458
- let workingIndicator = startWorkingIndicator(turnStart, isScreenReader, turns);
1465
+ // Live waiting indicator on the response line. It keeps motion and the
1466
+ // interrupt hint visible while still clearing itself before the first
1467
+ // model event writes real output.
1468
+ let workingIndicator = turns === 1 ? earlyWorkingIndicator : null;
1469
+ if (turns === 1)
1470
+ earlyWorkingIndicator = null;
1471
+ if (!workingIndicator) {
1472
+ workingIndicator = startWorkingIndicator(turnStart, isScreenReader, turns);
1473
+ }
1459
1474
  // Slow-model warning and first-token watchdog. The warning is a
1460
1475
  // UX hint; the watchdog is the hard recovery path for providers
1461
1476
  // that accept a request but then never produce a stream event.
@@ -1482,7 +1497,6 @@ export async function runQuery(ctx) {
1482
1497
  const requestConfig = fastDirect
1483
1498
  ? { ...ctx.config, maxTokens: Math.min(ctx.config.maxTokens ?? 700, 700) }
1484
1499
  : ctx.config;
1485
- const requestTools = fastDirect ? [] : ALL_TOOLS;
1486
1500
  const stream = streamChat(requestConfig, apiMessages, requestTools, streamAbort.signal);
1487
1501
  const iterator = stream[Symbol.asyncIterator]();
1488
1502
  while (true) {
@@ -1529,6 +1543,7 @@ export async function runQuery(ctx) {
1529
1543
  // model warning timer; subsequent events are normal streaming.
1530
1544
  if (!firstTokenSeen) {
1531
1545
  firstTokenSeen = true;
1546
+ firstTokenLatencyMs = Date.now() - turnStart;
1532
1547
  clearTimeout(slowTimer);
1533
1548
  if (streamWaitTimer)
1534
1549
  clearTimeout(streamWaitTimer);
@@ -1573,7 +1588,12 @@ export async function runQuery(ctx) {
1573
1588
  else if (event.type === 'done') {
1574
1589
  if (event.usage) {
1575
1590
  const u = event.usage;
1576
- const { cost, warning } = trackUsage(ctx.sessionId, ctx.config.model, u.prompt, u.completion);
1591
+ const turnDurationMs = Date.now() - turnStart;
1592
+ const { cost, warning } = trackUsage(ctx.sessionId, ctx.config.model, u.prompt, u.completion, {
1593
+ provider: ctx.config.provider,
1594
+ firstTokenMs: firstTokenLatencyMs,
1595
+ durationMs: turnDurationMs,
1596
+ });
1577
1597
  chainStats.benchmarkUsageEvents.push({
1578
1598
  model: ctx.config.model,
1579
1599
  promptTokens: u.prompt,
@@ -1581,12 +1601,16 @@ export async function runQuery(ctx) {
1581
1601
  totalTokens: u.total || u.prompt + u.completion,
1582
1602
  estimatedCostUsd: cost,
1583
1603
  });
1584
- setFooterCost(cost, u.prompt, u.completion);
1604
+ usageRecorded = true;
1605
+ setFooterCost(cost, u.prompt, u.completion, {
1606
+ firstTokenMs: firstTokenLatencyMs,
1607
+ durationMs: turnDurationMs,
1608
+ });
1585
1609
  // Single newline separator if we just streamed text, then the
1586
1610
  // compact telemetry line.
1587
1611
  if (hasOutput && !lastCharWasNewline)
1588
1612
  process.stdout.write('\n');
1589
- printCost(u.prompt, u.completion, cost, warning, Date.now() - turnStart);
1613
+ printCost(u.prompt, u.completion, cost, warning, turnDurationMs);
1590
1614
  }
1591
1615
  try {
1592
1616
  streamAbort.abort();
@@ -1596,6 +1620,28 @@ export async function runQuery(ctx) {
1596
1620
  break;
1597
1621
  }
1598
1622
  }
1623
+ if (!usageRecorded && (hasOutput || (toolCalls && toolCalls.length > 0))) {
1624
+ const promptEstimate = Math.max(1, estimateTokens(apiMessages));
1625
+ const completionEstimate = Math.max(1, Math.ceil(((fullText || '') + (toolCalls ? JSON.stringify(toolCalls) : '')).length / 3.5));
1626
+ const turnDurationMs = Date.now() - turnStart;
1627
+ const { cost } = trackUsage(ctx.sessionId, ctx.config.model, promptEstimate, completionEstimate, {
1628
+ provider: ctx.config.provider,
1629
+ firstTokenMs: firstTokenLatencyMs,
1630
+ durationMs: turnDurationMs,
1631
+ });
1632
+ chainStats.benchmarkUsageEvents.push({
1633
+ model: ctx.config.model,
1634
+ promptTokens: promptEstimate,
1635
+ completionTokens: completionEstimate,
1636
+ totalTokens: promptEstimate + completionEstimate,
1637
+ estimatedCostUsd: cost,
1638
+ });
1639
+ setFooterCost(cost, promptEstimate, completionEstimate, {
1640
+ firstTokenMs: firstTokenLatencyMs,
1641
+ durationMs: turnDurationMs,
1642
+ });
1643
+ usageRecorded = true;
1644
+ }
1599
1645
  clearTimeout(slowTimer);
1600
1646
  if (streamWaitTimer)
1601
1647
  clearTimeout(streamWaitTimer);
@@ -1629,9 +1675,12 @@ export async function runQuery(ctx) {
1629
1675
  turns--;
1630
1676
  continue;
1631
1677
  }
1678
+ if (retryPrimaryModelOnce(`${failedModel} produced no stream events for ${formatDuration(firstTokenTimeoutMs)}`)) {
1679
+ continue;
1680
+ }
1632
1681
  const timeoutMsg = `${failedModel} produced no stream events for ${formatDuration(firstTokenTimeoutMs)}`;
1633
1682
  console.log(theme.error(` ${sym.warn} ${timeoutMsg}.`));
1634
- console.log(theme.dim(' No fallback is configured, so the request was cancelled and the prompt can recover.'));
1683
+ console.log(theme.dim(' Fallback is disabled and the same-model retry already ran; cancelling so the prompt can recover.'));
1635
1684
  console.log(theme.dim(' Use /fallback <model-id> to enable automatic retry, or /model <known-good-model> to switch primary models.'));
1636
1685
  ctx.messages.push({ role: 'assistant', content: `[Provider timeout: ${timeoutMsg}]` });
1637
1686
  break;
@@ -1651,6 +1700,9 @@ export async function runQuery(ctx) {
1651
1700
  turns--;
1652
1701
  continue;
1653
1702
  }
1703
+ if (!hasOutput && !toolCalls && retryPrimaryModelOnce(timeoutMsg)) {
1704
+ continue;
1705
+ }
1654
1706
  if (fullText.trim()) {
1655
1707
  const partial = `${fullText.trimEnd()}\n[Provider timeout: stream stalled before completion]`;
1656
1708
  console.log(theme.warning(` ${sym.warn} stream stalled before completion; returning the partial response.`));
@@ -1659,7 +1711,7 @@ export async function runQuery(ctx) {
1659
1711
  }
1660
1712
  else {
1661
1713
  console.log(theme.error(` ${sym.warn} ${timeoutMsg}.`));
1662
- console.log(theme.dim(' The request was cancelled so the prompt can recover instead of hanging indefinitely.'));
1714
+ console.log(theme.dim(' Same-model retry already ran; cancelling so the prompt can recover instead of hanging indefinitely.'));
1663
1715
  console.log(theme.dim(' Use /fallback <model-id> to enable automatic retry, or /model <known-good-model> to switch primary models.'));
1664
1716
  ctx.messages.push({ role: 'assistant', content: `[Provider timeout: ${timeoutMsg}]` });
1665
1717
  }
@@ -1732,6 +1784,9 @@ export async function runQuery(ctx) {
1732
1784
  turns--; // this retry doesn't burn a turn slot from the max-turns budget
1733
1785
  continue;
1734
1786
  }
1787
+ if (cat.category === 'unknown' && retryPrimaryModelOnce(`${ctx.config.model} returned a cryptic provider error`)) {
1788
+ continue;
1789
+ }
1735
1790
  printApiError(msg, {
1736
1791
  baseURL: ctx.config.baseURL,
1737
1792
  provider: ctx.config.provider,
@@ -1765,9 +1820,12 @@ export async function runQuery(ctx) {
1765
1820
  turns--;
1766
1821
  continue;
1767
1822
  }
1823
+ if (retryPrimaryModelOnce(`${failedModel} returned an empty response`)) {
1824
+ continue;
1825
+ }
1768
1826
  const emptyMsg = `${failedModel} returned an empty response`;
1769
1827
  console.log(theme.error(` ${sym.warn} ${emptyMsg}.`));
1770
- console.log(theme.dim(' No fallback is configured, so this empty response was not retried.'));
1828
+ console.log(theme.dim(' Fallback is disabled and the same-model retry already ran.'));
1771
1829
  console.log(theme.dim(' Use /fallback <model-id> to enable automatic retry, or /model <known-good-model> to switch primary models.'));
1772
1830
  ctx.messages.push({ role: 'assistant', content: `[Provider empty response: ${emptyMsg}]` });
1773
1831
  break;
@@ -1873,6 +1931,8 @@ export async function runQuery(ctx) {
1873
1931
  }
1874
1932
  // Chain ended; back to idle so F1 reports the correct state.
1875
1933
  setStatus({ state: 'idle' });
1934
+ if (isFooterActive())
1935
+ setFooterActivity('Ready', 0, null);
1876
1936
  // ── Voice: read the assistant's final response ────────────
1877
1937
  // Off the hot path — fire-and-forget so the next prompt appears
1878
1938
  // immediately. The playback runs in background; F2 pauses, F4 skips.
@@ -1972,6 +2032,8 @@ export async function runQuery(ctx) {
1972
2032
  }
1973
2033
  }
1974
2034
  finally {
2035
+ earlyWorkingIndicator?.stop();
2036
+ earlyWorkingIndicator = null;
1975
2037
  // Drain any queued user input typed during streaming. Stash on
1976
2038
  // globalThis for the REPL loop in index.ts to restore into the
1977
2039
  // next editable prompt. Enter typed mid-stream is preserved as
@@ -1985,6 +2047,8 @@ export async function runQuery(ctx) {
1985
2047
  // can't be aborted between turns by Shift+F5 (soft-cancel).
1986
2048
  globalThis.__turnAbortCtl = null;
1987
2049
  globalThis.__turnCancelCurrent = null;
2050
+ if (isFooterActive())
2051
+ setFooterActivity('Ready', 0, null);
1988
2052
  }
1989
2053
  }
1990
2054
  const TOOL_CALL_LOOP_THRESHOLD = 3;