mixdog 0.9.130 → 0.9.132

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 (161) hide show
  1. package/package.json +2 -2
  2. package/scripts/fixtures/patch-replay-corpus.json +9 -9
  3. package/scripts/reduction-trace-report.mjs +29 -0
  4. package/scripts/release-timing-report.mjs +112 -0
  5. package/scripts/tool-failures.mjs +36 -0
  6. package/scripts/tool-stress.mjs +2 -2
  7. package/src/lib/rules-builder.cjs +0 -3
  8. package/src/rules/lead/01-general.md +1 -9
  9. package/src/rules/shared/01-tool.md +76 -45
  10. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +3 -0
  11. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -10
  12. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +74 -11
  13. package/src/runtime/agent/orchestrator/agent-trace.mjs +2 -0
  14. package/src/runtime/agent/orchestrator/config.mjs +9 -0
  15. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +212 -0
  16. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +2100 -0
  17. package/src/runtime/agent/orchestrator/providers/cursor.mjs +615 -0
  18. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +65 -35
  19. package/src/runtime/agent/orchestrator/providers/gemini.mjs +1 -1
  20. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +10 -0
  21. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +35 -6
  22. package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +142 -0
  23. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +14 -0
  24. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +4 -1
  25. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -2
  26. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -0
  27. package/src/runtime/agent/orchestrator/providers/registry.mjs +16 -5
  28. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +64 -52
  29. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +12 -22
  30. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +15 -1
  31. package/src/runtime/agent/orchestrator/session/evidence-union.mjs +411 -0
  32. package/src/runtime/agent/orchestrator/session/evidence-union.test.mjs +157 -0
  33. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +68 -11
  34. package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +122 -0
  35. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  36. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +8 -9
  37. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +24 -2
  38. package/src/runtime/agent/orchestrator/session/lossless-tool-output.test.mjs +124 -0
  39. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +1 -0
  40. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +17 -14
  41. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +2 -1
  42. package/src/runtime/agent/orchestrator/session/manager/provider-cache-key.mjs +2 -0
  43. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +3 -6
  44. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +47 -2
  45. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  46. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +18 -6
  47. package/src/runtime/agent/orchestrator/session/reduction-metrics.mjs +120 -0
  48. package/src/runtime/agent/orchestrator/session/reduction-metrics.test.mjs +87 -0
  49. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +13 -4
  50. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +55 -37
  51. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +93 -2
  52. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +141 -22
  53. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +111 -71
  54. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +31 -17
  55. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +76 -210
  56. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +81 -44
  57. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +57 -23
  58. package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +56 -0
  59. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +134 -30
  60. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +595 -0
  61. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +132 -0
  62. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +74 -0
  63. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.test.mjs +64 -0
  64. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +8 -5
  65. package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +132 -0
  66. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +90 -10
  67. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +167 -25
  68. package/src/runtime/agent/orchestrator/tools/builtin/native-search-health.test.mjs +21 -0
  69. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +3 -2
  70. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +24 -0
  71. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +33 -8
  72. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +17 -4
  73. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +9 -15
  74. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +40 -51
  75. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +115 -56
  76. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +0 -106
  77. package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +18 -63
  78. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +12 -57
  79. package/src/runtime/agent/orchestrator/tools/builtin.mjs +6 -47
  80. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +5 -85
  81. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +8 -15
  82. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.test.mjs +20 -0
  83. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -4
  84. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +1 -2
  85. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  86. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +23 -18
  87. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +10 -0
  88. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +92 -116
  89. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +4 -0
  90. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +5 -45
  91. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +0 -17
  92. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  93. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +15 -16
  94. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +4 -0
  95. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  96. package/src/runtime/agent/orchestrator/tools/tool-batch-trace.mjs +7 -0
  97. package/src/runtime/channels/lib/session-discovery.mjs +66 -1
  98. package/src/runtime/channels/lib/session-discovery.test.mjs +32 -0
  99. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -1
  100. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -1
  101. package/src/runtime/shared/agent-route-config.mjs +3 -0
  102. package/src/runtime/shared/background-tasks.mjs +0 -4
  103. package/src/runtime/shared/child-guardian.mjs +14 -0
  104. package/src/runtime/shared/edit-tool-dialect.mjs +30 -0
  105. package/src/runtime/shared/pristine-execution-contract.json +1 -0
  106. package/src/runtime/shared/schedule-model-ref.mjs +9 -5
  107. package/src/runtime/shared/schedule-session-run.mjs +2 -0
  108. package/src/runtime/shared/session-shard-health.mjs +89 -0
  109. package/src/runtime/shared/session-shard-health.test.mjs +30 -0
  110. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  111. package/src/runtime/shared/tool-surface.mjs +5 -0
  112. package/src/runtime/shared/webhook-session-run.mjs +8 -1
  113. package/src/session-runtime/config-helpers.mjs +18 -1
  114. package/src/session-runtime/lifecycle-api.mjs +13 -2
  115. package/src/session-runtime/model-capabilities.mjs +24 -2
  116. package/src/session-runtime/model-recency.mjs +11 -0
  117. package/src/session-runtime/model-route-api.mjs +37 -6
  118. package/src/session-runtime/model-settings-persist.test.mjs +6 -1
  119. package/src/session-runtime/native-search.mjs +1 -0
  120. package/src/session-runtime/provider-auth-api.mjs +13 -11
  121. package/src/session-runtime/provider-models.mjs +8 -4
  122. package/src/session-runtime/runtime-core.mjs +6 -31
  123. package/src/session-runtime/session-lifecycle.mjs +13 -1
  124. package/src/session-runtime/session-turn-api.mjs +1 -13
  125. package/src/session-runtime/tool-catalog-data.mjs +8 -5
  126. package/src/session-runtime/tool-catalog-schema.mjs +2 -3
  127. package/src/session-runtime/tool-catalog.mjs +6 -1
  128. package/src/session-runtime/tool-defs.mjs +0 -28
  129. package/src/session-runtime/tool-policy-surface.test.mjs +32 -0
  130. package/src/session-runtime/tool-surface.mjs +5 -1
  131. package/src/session-runtime/workflow-agents-api.mjs +9 -1
  132. package/src/session-runtime/workflow.mjs +5 -0
  133. package/src/standalone/agent-tool/lead-worker-index.mjs +144 -0
  134. package/src/standalone/agent-tool/notify.mjs +6 -2
  135. package/src/standalone/agent-tool/tag-registry.mjs +9 -1
  136. package/src/standalone/agent-tool/tool-def.mjs +1 -0
  137. package/src/standalone/agent-tool/worker-index.mjs +13 -12
  138. package/src/standalone/agent-tool.mjs +3 -0
  139. package/src/standalone/channel-client.mjs +18 -6
  140. package/src/standalone/channel-restart.test.mjs +163 -0
  141. package/src/standalone/channel-transport.mjs +54 -15
  142. package/src/standalone/channel-worker.mjs +6 -2
  143. package/src/standalone/provider-admin.mjs +14 -4
  144. package/src/standalone/session-runtime-pool-health.test.mjs +85 -0
  145. package/src/standalone/session-runtime-pool.mjs +22 -3
  146. package/src/standalone/session-runtime-worker.mjs +35 -0
  147. package/src/standalone/usage-dashboard.mjs +11 -1
  148. package/src/tui/app/model-options.mjs +2 -0
  149. package/src/tui/app/model-picker.mjs +64 -11
  150. package/src/tui/app/provider-setup-picker.mjs +32 -28
  151. package/src/tui/dist/index.mjs +111 -40
  152. package/src/tui/session/agent-envelope.mjs +1 -1
  153. package/src/tui/session/context-state.mjs +1 -0
  154. package/src/tui/session/session-api-ext.mjs +2 -0
  155. package/src/tui/session/session-api.mjs +1 -1
  156. package/src/tui/session/session-flow.mjs +32 -19
  157. package/src/workflows/default/WORKFLOW.md +3 -0
  158. package/src/workflows/solo/WORKFLOW.md +3 -1
  159. package/src/rules/lead/lead-tool.md +0 -3
  160. package/src/runtime/agent/orchestrator/session/loop/stop-hooks.mjs +0 -98
  161. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +0 -279
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixdog",
3
- "version": "0.9.130",
3
+ "version": "0.9.132",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone mixdog coding-agent CLI/TUI workspace.",
@@ -59,7 +59,7 @@
59
59
  "test:tool-contracts": "npm run build:spawn:test && node scripts/tool-smoke.mjs",
60
60
  "smoke:patch": "node scripts/apply-patch-edit-smoke.mjs",
61
61
  "test:shellhardening": "npm run build:spawn:test && node --test scripts/suite-shellhardening-test.mjs scripts/suite-shell-direct-exe-test.mjs",
62
- "test:providers": "node --test scripts/provider-toolcall-test.mjs scripts/provider-contract-test.mjs scripts/provider-stream-outcome-test.mjs",
62
+ "test:providers": "node --test scripts/provider-toolcall-test.mjs scripts/provider-contract-test.mjs scripts/provider-stream-outcome-test.mjs scripts/cursor-provider-test.mjs",
63
63
  "test:embedding-runtime:warmup": "node scripts/verify-embedding-runtime.mjs --warmup",
64
64
  "test:release-assets": "node --check scripts/verify-release-assets.mjs && node --check scripts/release-gate-test.mjs && node --check scripts/prepare-native-assets.mjs && node --test scripts/release-gate-test.mjs scripts/prepare-native-assets-test.mjs",
65
65
  "test:native-edit-wire": "node scripts/native-edit-wire-test.mjs",
@@ -169,9 +169,9 @@
169
169
  },
170
170
  {
171
171
  "id": "add-overwrite-crlf",
172
- "note": "Add File overwrite of an existing CRLF file must keep CRLF",
173
- "expect": "applied",
174
- "expect_content": { "a.txt": "NEW\r\n" },
172
+ "note": "Add File must reject an existing file without changing its CRLF content",
173
+ "expect": "rejected",
174
+ "expect_error": "Add File target already exists",
175
175
  "file_snapshots": { "a.txt": "old\r\n" },
176
176
  "args": { "patch": "*** Begin Patch\n*** Add File: a.txt\n+NEW\n*** End Patch\n" }
177
177
  },
@@ -194,24 +194,24 @@
194
194
  {
195
195
  "id": "already-applied-insert",
196
196
  "note": "observed: re-sent insertion whose unique new-side is already on disk",
197
- "expect": "applied",
198
- "expect_content": { "a.js": "const STATUS = {\n pending: 1,\n archived: 2,\n}\n" },
197
+ "expect": "rejected",
198
+ "expect_error": "context not found",
199
199
  "file_snapshots": { "a.js": "const STATUS = {\n pending: 1,\n archived: 2,\n}\n" },
200
200
  "args": { "patch": "*** Begin Patch\n*** Update File: a.js\n@@\n const STATUS = {\n pending: 1,\n+ archived: 2,\n }\n*** End Patch\n" }
201
201
  },
202
202
  {
203
203
  "id": "already-applied-replace",
204
204
  "note": "observed: re-sent replacement whose unique new-side is already on disk",
205
- "expect": "applied",
206
- "expect_content": { "a.js": "head\nreturn ALL.has(v) || v === 'core' || v === 'lineage'\ntail\n" },
205
+ "expect": "rejected",
206
+ "expect_error": "context not found",
207
207
  "file_snapshots": { "a.js": "head\nreturn ALL.has(v) || v === 'core' || v === 'lineage'\ntail\n" },
208
208
  "args": { "patch": "*** Begin Patch\n*** Update File: a.js\n@@\n head\n-return ALL.has(v) || v === 'core'\n+return ALL.has(v) || v === 'core' || v === 'lineage'\n tail\n*** End Patch\n" }
209
209
  },
210
210
  {
211
211
  "id": "already-applied-mixed-remaining",
212
212
  "note": "already-applied hunks skip; a later unique old-side hunk still applies",
213
- "expect": "applied",
214
- "expect_content": { "a.js": "const STATUS = {\n pending: 1,\n archived: 2,\n}\n// lineage backfill\n" },
213
+ "expect": "rejected",
214
+ "expect_error": "context not found",
215
215
  "file_snapshots": { "a.js": "const STATUS = {\n pending: 1,\n archived: 2,\n}\n// pending/active only\n" },
216
216
  "args": { "patch": "*** Begin Patch\n*** Update File: a.js\n@@\n const STATUS = {\n pending: 1,\n+ archived: 2,\n }\n@@\n-// pending/active only\n+// lineage backfill\n*** End Patch\n" }
217
217
  },
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { summarizeReductionTraceRows } from '../src/runtime/agent/orchestrator/session/reduction-metrics.mjs';
6
+
7
+ export function parseTraceJsonl(text) {
8
+ const rows = [];
9
+ for (const line of String(text || '').split(/\r?\n/)) {
10
+ if (!line.trim()) continue;
11
+ try { rows.push(JSON.parse(line)); } catch { /* retain valid rows */ }
12
+ }
13
+ return rows;
14
+ }
15
+
16
+ function main(argv) {
17
+ const tracePath = argv[2];
18
+ if (!tracePath) {
19
+ process.stderr.write('Usage: node scripts/reduction-trace-report.mjs <agent-trace.jsonl>\n');
20
+ return 2;
21
+ }
22
+ const rows = parseTraceJsonl(readFileSync(resolve(tracePath), 'utf8'));
23
+ process.stdout.write(`${JSON.stringify(summarizeReductionTraceRows(rows), null, 2)}\n`);
24
+ return 0;
25
+ }
26
+
27
+ if (resolve(process.argv[1] || '') === fileURLToPath(import.meta.url)) {
28
+ process.exitCode = main(process.argv);
29
+ }
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { appendFile, readFile } from 'node:fs/promises';
4
+ import { resolve } from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
6
+
7
+ function payloadJobs(payload) {
8
+ const pages = Array.isArray(payload) ? payload : [payload];
9
+ return pages.flatMap((page) => Array.isArray(page?.jobs) ? page.jobs : []);
10
+ }
11
+
12
+ function elapsedSeconds(startedAt, completedAt) {
13
+ const start = Date.parse(startedAt || '');
14
+ const end = Date.parse(completedAt || '');
15
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) return null;
16
+ return Math.round((end - start) / 1000);
17
+ }
18
+
19
+ function timingRows(payload) {
20
+ return payloadJobs(payload).flatMap((job) => (job.steps || []).flatMap((step) => {
21
+ const seconds = elapsedSeconds(step.started_at, step.completed_at);
22
+ if (seconds === null) return [];
23
+ return [{
24
+ key: `${job.name} / ${step.name}`,
25
+ job: job.name,
26
+ step: step.name,
27
+ seconds,
28
+ }];
29
+ }));
30
+ }
31
+
32
+ function workflowSeconds(payload) {
33
+ const jobs = payloadJobs(payload);
34
+ const starts = jobs.map((job) => Date.parse(job.started_at || '')).filter(Number.isFinite);
35
+ const ends = jobs.map((job) => Date.parse(job.completed_at || '')).filter(Number.isFinite);
36
+ if (starts.length === 0 || ends.length === 0) return null;
37
+ return Math.round((Math.max(...ends) - Math.min(...starts)) / 1000);
38
+ }
39
+
40
+ function percentChange(current, baseline) {
41
+ if (!(baseline > 0)) return null;
42
+ return Math.round(((current - baseline) / baseline) * 100);
43
+ }
44
+
45
+ export function buildReleaseTimingReport(currentPayload, baselinePayload = []) {
46
+ const currentRows = timingRows(currentPayload);
47
+ const baselineRows = timingRows(baselinePayload);
48
+ const baselineByKey = new Map(baselineRows.map((row) => [row.key, row]));
49
+ const regressions = currentRows.flatMap((row) => {
50
+ const baseline = baselineByKey.get(row.key);
51
+ if (!baseline) return [];
52
+ const percent = percentChange(row.seconds, baseline.seconds);
53
+ if (percent === null || percent <= 10 || row.seconds - baseline.seconds < 15) return [];
54
+ return [{ ...row, baselineSeconds: baseline.seconds, percent }];
55
+ }).sort((a, b) => b.percent - a.percent || b.seconds - a.seconds);
56
+
57
+ const currentWorkflowSeconds = workflowSeconds(currentPayload);
58
+ const baselineWorkflowSeconds = workflowSeconds(baselinePayload);
59
+ const workflowPercent = currentWorkflowSeconds !== null && baselineWorkflowSeconds !== null
60
+ ? percentChange(currentWorkflowSeconds, baselineWorkflowSeconds)
61
+ : null;
62
+ const slowest = [...currentRows].sort((a, b) => b.seconds - a.seconds).slice(0, 15);
63
+ const markdown = [
64
+ '## Release timing',
65
+ '',
66
+ `- Current workflow span: ${currentWorkflowSeconds ?? 'n/a'}s`,
67
+ `- Previous successful span: ${baselineWorkflowSeconds ?? 'n/a'}s`,
68
+ `- Material step regressions (>10% and ≥15s): ${regressions.length}`,
69
+ '',
70
+ '| Slowest step | Current | Previous | Change |',
71
+ '| --- | ---: | ---: | ---: |',
72
+ ...slowest.map((row) => {
73
+ const baseline = baselineByKey.get(row.key);
74
+ const change = baseline ? percentChange(row.seconds, baseline.seconds) : null;
75
+ return `| ${row.key.replaceAll('|', '\\|')} | ${row.seconds}s | ${baseline ? `${baseline.seconds}s` : 'n/a'} | ${change === null ? 'n/a' : `${change > 0 ? '+' : ''}${change}%`} |`;
76
+ }),
77
+ '',
78
+ ].join('\n');
79
+
80
+ return {
81
+ currentWorkflowSeconds,
82
+ baselineWorkflowSeconds,
83
+ workflowPercent,
84
+ regressions,
85
+ markdown,
86
+ };
87
+ }
88
+
89
+ function commandValue(value) {
90
+ return String(value).replaceAll('%', '%25').replaceAll('\r', '%0D').replaceAll('\n', '%0A');
91
+ }
92
+
93
+ const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : '';
94
+ if (invokedPath === import.meta.url) {
95
+ const currentPath = process.argv[2];
96
+ const baselinePath = process.argv[3];
97
+ if (!currentPath) throw new Error('Usage: release-timing-report.mjs <current-jobs.json> [baseline-jobs.json]');
98
+ const current = JSON.parse(await readFile(currentPath, 'utf8'));
99
+ const baseline = baselinePath ? JSON.parse(await readFile(baselinePath, 'utf8')) : [];
100
+ const report = buildReleaseTimingReport(current, baseline);
101
+ if (process.env.GITHUB_STEP_SUMMARY) {
102
+ await appendFile(process.env.GITHUB_STEP_SUMMARY, `${report.markdown}\n`);
103
+ }
104
+ if (report.workflowPercent > 10
105
+ && report.currentWorkflowSeconds - report.baselineWorkflowSeconds >= 30) {
106
+ console.log(`::warning title=Release duration regression::Workflow span increased ${report.workflowPercent}% to ${report.currentWorkflowSeconds}s`);
107
+ }
108
+ for (const row of report.regressions) {
109
+ console.log(`::warning title=Release step regression::${commandValue(row.key)} increased ${row.percent}% (${row.baselineSeconds}s to ${row.seconds}s)`);
110
+ }
111
+ console.log(`Release timing recorded: ${report.currentWorkflowSeconds ?? 'n/a'}s, ${report.regressions.length} material step regressions.`);
112
+ }
@@ -2,6 +2,7 @@
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { resolve } from 'node:path';
5
+ import { classifyToolFailure } from '../src/runtime/agent/orchestrator/agent-trace-format.mjs';
5
6
 
6
7
  function argValue(name, fallback = null) {
7
8
  const idx = process.argv.indexOf(name);
@@ -87,9 +88,40 @@ function rowCategory(row) {
87
88
  return row.category || row.result_kind || row.resultKind || '(uncategorized)';
88
89
  }
89
90
 
91
+ function rowErrorText(row) {
92
+ return row.error_preview || row.result || row.error || row.message || row.error_first_line || '';
93
+ }
94
+
95
+ function isKnownTestFixture(row) {
96
+ if (row.session_id !== 'no-session' || row.agent != null || row.model != null) return false;
97
+ const tool = rowTool(row);
98
+ if (tool === 'unknown_test_tool') return true;
99
+ return tool === 'apply_patch' && /^Error:\s*patch failed\s*$/i.test(String(rowErrorText(row)).trim());
100
+ }
101
+
102
+ function normalizeRowCategory(row) {
103
+ const storedCategory = rowCategory(row);
104
+ let category = storedCategory;
105
+ if (isKnownTestFixture(row)) {
106
+ category = 'expected-test';
107
+ } else if (rowTool(row) === 'apply_patch') {
108
+ const derived = classifyToolFailure(rowErrorText(row), 'apply_patch');
109
+ // Failure previews are bounded and may end before the nested cause. Never
110
+ // downgrade a stored specific category to the generic fallback merely
111
+ // because the historical preview lacks that tail.
112
+ if (derived !== 'runtime/failure' || storedCategory === 'runtime/failure') {
113
+ category = derived;
114
+ }
115
+ }
116
+ return category === storedCategory
117
+ ? row
118
+ : { ...row, stored_category: storedCategory, category };
119
+ }
120
+
90
121
  const sinceTs = parseSince(sinceArg);
91
122
  const onlyArg = String(argValue('--only', 'all') || 'all').toLowerCase();
92
123
  const rows = files.flatMap(readRows)
124
+ .map(normalizeRowCategory)
93
125
  .filter((row) => sinceTs == null || Number(row.ts || 0) >= sinceTs)
94
126
  .filter((row) => !toolFilter || rowTool(row) === toolFilter)
95
127
  .filter((row) => !agentFilter || String(row.agent || '-') === agentFilter)
@@ -150,6 +182,8 @@ const actionableByFamily = tally(actionableRows, categoryFamily);
150
182
  const commandExitByTool = tally(commandExitRows, rowTool);
151
183
  const expectedByCategory = tally(expectedRows, rowCategory);
152
184
  const patchByCategory = tally(patchRows, rowCategory);
185
+ const reclassifiedRows = rows.filter((row) => row.stored_category && row.stored_category !== rowCategory(row));
186
+ const reclassifiedByCategory = tally(reclassifiedRows, (row) => `${row.stored_category} -> ${rowCategory(row)}`);
153
187
 
154
188
  if (jsonMode) {
155
189
  console.log(JSON.stringify({
@@ -160,6 +194,7 @@ if (jsonMode) {
160
194
  expected_absorbed: { shown: expectedRecent.length, matched: expectedRows.length },
161
195
  session_cancellations: { shown: 0, matched: cancellationRows.length },
162
196
  patch_failures: { matched: patchRows.length, categories: asObject(patchByCategory) },
197
+ reclassified: { matched: reclassifiedRows.length, categories: asObject(reclassifiedByCategory) },
163
198
  since: sinceTs ? new Date(sinceTs).toISOString() : null,
164
199
  filters: {
165
200
  tool: toolFilter,
@@ -200,6 +235,7 @@ console.log(`actionable families (matched): ${asText(actionableByFamily)}`);
200
235
  console.log(`patch failures (matched): ${patchRows.length} — ${asText(patchByCategory)}`);
201
236
  console.log(`command-exit tools (matched): ${asText(commandExitByTool)}`);
202
237
  console.log(`expected/absorbed categories (matched): ${asText(expectedByCategory)}`);
238
+ console.log(`reclassified rows (matched): ${reclassifiedRows.length} — ${asText(reclassifiedByCategory)}`);
203
239
  console.log(`shown categories: ${asText(byCategory)}`);
204
240
  for (const row of recent) {
205
241
  const tool = rowTool(row);
@@ -129,13 +129,13 @@ try {
129
129
 
130
130
  // ── Phase D: cancellation under load ─────────────────────────────────────
131
131
  const bg = await timed('shell-async', /task_id/, () => executeBuiltinTool('shell', {
132
- command: 'node -e "setTimeout(()=>{}, 30000)"', run_in_background: true, timeout_ms: 60_000,
132
+ command: 'node -e "setTimeout(()=>{}, 30000)"',
133
133
  }, root, { sessionId: 'stress-cancel' }));
134
134
  const bgId = (/task_id:\s*(\S+)/.exec(String(bg)) || [])[1];
135
135
  if (!bgId) failures.push('async shell did not return task_id');
136
136
  else {
137
137
  await timed('task-cancel', /cancelled/, () => executeBuiltinTool('task', { action: 'cancel', task_id: bgId }, root, { sessionId: 'stress-cancel' }));
138
- const st = await timed('task-status', /cancelled|failed/, () => executeBuiltinTool('task', { action: 'status', task_id: bgId }, root, { sessionId: 'stress-cancel' }));
138
+ const st = await timed('task-status', /cancelled|failed/, () => executeBuiltinTool('task', { action: 'read', task_id: bgId }, root, { sessionId: 'stress-cancel' }));
139
139
  if (!/cancelled/.test(String(st))) failures.push(`cancelled task not reported cancelled: ${String(st).slice(0, 120)}`);
140
140
  }
141
141
  } finally {
@@ -211,9 +211,6 @@ function buildLeadRoleContent({ PLUGIN_ROOT, DATA_DIR, includeLeadBrief = true }
211
211
  const general = readOptional(path.join(LEAD_DIR, '01-general.md'));
212
212
  const parts = [];
213
213
 
214
- const toolLead = readOptional(path.join(LEAD_DIR, 'lead-tool.md'));
215
- if (toolLead) parts.push(toolLead);
216
-
217
214
  if (includeLeadBrief) {
218
215
  const briefLead = readOptional(path.join(LEAD_DIR, 'lead-brief.md'));
219
216
  if (briefLead) parts.push(briefLead);
@@ -7,17 +7,9 @@
7
7
  - Confirm destructive/hard-to-reverse actions against explicit validated paths;
8
8
  never `~`, a root, or unresolved variables/globs; report material deletion
9
9
  recoverability.
10
- - Ask only for decisions.
11
- - Investigate, build, and verify only what the requested outcome requires;
12
- trust internal and framework guarantees.
13
- - Blocking tests cover only essential integrity, security, compatibility, and
14
- buildability invariants. Treat mutable behavior, UX, exact text, snapshots,
15
- and implementation shape as advisory specifications; update them when the
16
- requested behavior changes instead of preserving obsolete behavior.
17
10
  - After required work, run final verification only when the outcome needs
18
11
  evidence the successful tool result does not already give, and run only
19
- affected blocking invariants. Verification is that extra check, not
20
- reopening already obtained content. Combine commands when dependency or
12
+ affected blocking invariants. Combine commands when dependency or
21
13
  atomicity requires it.
22
14
  - Mid-task: replacement supersedes; addition folds in; status gets a brief
23
15
  answer while work continues. After compaction, resume the summary.
@@ -5,15 +5,18 @@
5
5
  path/name only→`find`; wildcard/recursive paths→`glob` (including known-root
6
6
  unknown descendants); exact directory entries→`list`;
7
7
  file-content search→`grep`; known-file content→`read`;
8
- exact symbol, body, or relation→`code_graph`;
8
+ exact symbol, body, or relation→`code_graph`
9
+ (identifier declarations/usages→`code_graph`; literal values/strings→`grep`);
10
+ local Git repository inspection and mutation→`git`;
9
11
  program execution, calculations, data transformation, file generation, or
10
12
  unsupported formats→`shell`;
11
13
  web/current→`search`; returned URL body→`web_fetch`;
12
14
  prior work→`recall` (history only, never current local state);
13
15
  durable compact English memory→`memory`;
14
16
  explicit Project change→`cwd`
15
- (a shell-local `cd` never changes the Project);
16
- explicit user-requested conversation reset→`session_manage`.
17
+ (a shell-local `cd` never changes the Project).
18
+ Paths reachable by expanding an environment variable or the home directory
19
+ are resolved locations, not unknowns.
17
20
  Use only named tools present in the current tool surface.
18
21
  - Requirements define what must be true; evidence establishes what is true.
19
22
  Never use one as the other. Treat supplied target locations as resolved;
@@ -22,30 +25,56 @@
22
25
  inspect the original content itself. Within the current project, pass project-relative
23
26
  paths and omit optional scopes equal to its root; explicit paths may be
24
27
  outside cwd only for targets outside the project.
25
- - After identifying all result-critical evidence needs, plan the fewest
26
- evidence-complete dependent rounds, then the fewest calls. Known state
27
- system/framework guarantees, supplied facts, exact lines or values already
28
- visible here, tool returns, applied patches, and proved checks — is never
29
- re-found, re-derived, or re-verified.
28
+ For a required new file, Add File itself is the atomic absence check: call
29
+ it directly, and inspect only if it reports that the target already exists.
30
+ - Evidence economy: investigate, build, and verify only what the requested
31
+ outcome requires; trust internal and framework guarantees. After
32
+ identifying all result-critical evidence needs, plan the fewest
33
+ evidence-complete dependent rounds, then the fewest calls.
34
+ Known state — system/framework guarantees, supplied facts, exact lines or
35
+ values already visible here, tool returns, applied patches, and proved
36
+ checks — is never re-found, re-derived, or re-verified at any granularity:
37
+ no re-query call, no confirmation subcommand inside a shell command, no
38
+ availability probe for what the operation itself would report, no reopening
39
+ a file to rebuild context or confirm an edit, no rerun of a passed check.
30
40
  A hole (needed content absent and not reconstructable) is fetched once;
31
- a change re-opens only that hole. Batch only calls whose need and inputs
32
- cannot be changed or eliminated by another result; otherwise run the cheapest
33
- decisive call that satisfies the remaining evidence needs. Before each batch, deduplicate
34
- the remaining necessary facets, route each once to the cheapest sufficient
35
- tool, and launch independent facets together never split or duplicate a
36
- facet across tools, mutate merely to widen retrieval, reserve known work, or
37
- cap fanout.
38
- Cover every independent facet of the round in one batch — one best-routed
39
- call per facet; extra tools on the same facet add cost, not progress.
40
- Returned output is fully mined
41
- before the next round. `code_graph references` supplies the declaration and
42
- scoped usages and ends that facet; values/locations end at the context grep returns; `read`
43
- covers only what returned spans cannot, as an anchored offset/limit
44
- window. Already obtained hunk text is any visible span `grep`,
45
- `code_graph`, `shell`, system/reminder text, or `read`not only `read`.
46
- Each follow-up may address only facts left unresolved or changed by
47
- prior results; never re-query or re-verify established facts. Evidence that
48
- determines the answer, edit, or deliverable ends retrieval — patch if needed.
41
+ a change re-opens only that hole. Returned output is fully mined before the
42
+ next round. `code_graph references` supplies the declaration and scoped
43
+ usages and ends that facet; values/locations end at the context grep
44
+ returns; `read` covers only what returned spans cannot, as an anchored
45
+ offset/limit window. Already obtained hunk text is any visible span
46
+ `grep`, `code_graph`, `shell`, system/reminder text, or `read` not only
47
+ `read`. Each follow-up may address only facts left unresolved or changed by
48
+ prior results. Evidence that determines the answer, edit, or deliverable
49
+ ends retrieval patch if needed.
50
+ - Parallel batching: independent calls share one batch by default — one
51
+ best-routed call per facet. Cost is counted in rounds, not calls: a batch
52
+ of N calls in one message is one round, so a call-count saving never
53
+ justifies folding work into a single worse-routed call.
54
+ The opening round is a one-shot sweep: every facet knowable from the
55
+ request alone enumeration, content probes, file samples launches in
56
+ that single first batch. Each later round exists only for facets the
57
+ previous round's results created; a facet no result produced belonged in
58
+ the round before it.
59
+ Opening-round batching never licenses a guessed path. `glob.path` must be
60
+ an established existing directory; omit it for the current Project. When
61
+ the location itself is unknown, use `find` first and call `glob` only if
62
+ wildcard descendants are still needed.
63
+ Serialize two calls only when the later call's
64
+ inputs are actually produced by the earlier result; the mere possibility
65
+ that a result could reshape later work never defers an independent call.
66
+ Before each batch, deduplicate the remaining necessary facets and route
67
+ each once to the cheapest sufficient tool — never split or duplicate a
68
+ facet across tools, mutate merely to widen retrieval, reserve known work,
69
+ or cap fanout. Applying one analysis to many targets is a single
70
+ parameterized call over all targets, not one call per target.
71
+ Enumerating sibling directories or same-kind files is one wildcard call
72
+ (`glob`, or `read` with a glob for content sampling), never a
73
+ directory-by-directory `list` walk or one `read` per file.
74
+ - Blocking checks cover only essential integrity, security, compatibility,
75
+ and buildability invariants. Treat mutable behavior, UX, exact text,
76
+ snapshots, and implementation shape as advisory specifications; update them
77
+ when the requested behavior changes instead of preserving obsolete behavior.
49
78
  - A successful verification closes the task unless later changes affect it.
50
79
  Rerun a failed action only after its inputs or subject changes; otherwise
51
80
  report it unresolved.
@@ -53,26 +82,28 @@
53
82
  mutate only when the deliverable requires it, first preserving evidence
54
83
  at risk. Never mutate merely to clear an obstacle or unexpected state;
55
84
  unrecoverably lost evidence ends its search — report best effort.
56
- - Before `apply_patch`, use only already obtained hunk text. Never infer
57
- patch context from another file, a sample, or expected text. Never
58
- reopen a path to refresh patch context or to confirm a successful
59
- apply_patch. Apply all determined edits in one cohesive `apply_patch`
60
- call.
61
- Hand-authored text is edited only with `apply_patch`. Use `shell` for program
62
- execution, runtime/state operations, calculations, data transformation, file
63
- generation, or formats unsupported by file tools. Do not use `shell` instead
64
- of an available file tool for ordinary file-content inspection.
65
- - Start shell commands in the foreground. Set `run_in_background:true` only when their result is not needed
66
- immediately and completion notification is sufficient. Tracked sync/async
67
- commands belong to the current run. Only when the request explicitly requires
85
+ - Before the exposed file-editing tool, use only already obtained exact source
86
+ text. Never infer edit context from another file, a sample, or expected text.
87
+ For context patches, include exact unchanged lines around each change and use
88
+ a class/function locator when that context is not unique.
89
+ - Apply all determined changes in the fewest safe calls the active tool
90
+ supports. Hand-authored text is edited only with the exposed file-editing
91
+ tool.
92
+ - Avoid Shell for file operations covered by dedicated tools unless explicitly
93
+ instructed or after verifying that a dedicated tool cannot do the job.
94
+ Shell otherwise joins investigation only for facts requiring execution or
95
+ unsupported decoding; an already-open shell is never a routing reason.
96
+ - Shell commands start in the foreground. If still running after 15 seconds,
97
+ the command continues as a tracked `task_id`; completion arrives by notification.
98
+ Only when the request explicitly requires
68
99
  a service to survive after the run exits, detach it at shell level (for
69
100
  example, `nohup ... &`); never detach ordinary jobs merely to avoid tracking.
70
101
  A sync call may return a `task_id` and partial output after its blocking
71
102
  budget. Do not poll: completion resumes automatically. When intermediate output must drive
72
- decisions or the user requests monitoring, use `task check_after` with an
73
- explicit `after_ms` to schedule one non-blocking progress snapshot: normally
74
- 10–30s for active progress and 30–60s for long downloads/builds. Schedule
75
- another only after inspecting that snapshot; completion cancels a pending
76
- check.
77
- Omit timeout by default, including for long jobs; set it only for a real total
78
- deadline, since it kills even async jobs.
103
+ decisions or the user explicitly requests monitoring, call `task read` once
104
+ to return the current status and output snapshot. If it is still running,
105
+ await the completion notification; do not
106
+ call `task` again unless the user explicitly asks for another snapshot.
107
+ Omit `timeout_ms` by default, including for long jobs. A positive value is a
108
+ hard total deadline that kills the command even after task promotion; `0`
109
+ means no deadline.
@@ -173,6 +173,9 @@ function maintenanceRouteToPreset(routeOrName, agent) {
173
173
  const effort = String(routeOrName.effort || '').trim();
174
174
  if (effort) out.effort = effort;
175
175
  if (routeOrName.fast === true) out.fast = true;
176
+ if (routeOrName.modelParameters && typeof routeOrName.modelParameters === 'object') {
177
+ out.modelParameters = { ...routeOrName.modelParameters };
178
+ }
176
179
  return out;
177
180
  }
178
181
 
@@ -49,8 +49,9 @@ function isAgentProgressWatchdogAbortError(err) {
49
49
  return typeof msg === 'string' && WATCHDOG_ABORT_RE.test(msg);
50
50
  }
51
51
 
52
- // Tools that enforce their own execution deadline: 'shell' kills the process
53
- // at its configured timeout. These are NOT blanket-exempted from the
52
+ // Tools that enforce their own execution deadline: shell kills the process
53
+ // only when the caller supplied a positive timeout_ms. These are NOT
54
+ // blanket-exempted from the
54
55
  // tool-running watchdog — if their own
55
56
  // deadline timer dies the session would otherwise hang forever. Instead the
56
57
  // watchdog raises the tool-running ceiling to their self-deadline + a grace
@@ -60,10 +61,6 @@ const SELF_DEADLINE_TOOLS = new Set(['shell']);
60
61
  // Grace added on top of a tool's own deadline before the watchdog steps in, so
61
62
  // the tool's in-process kill always fires first under normal operation.
62
63
  const TOOL_SELF_DEADLINE_GRACE_MS = 60_000;
63
- // Fallback deadline matching the shell implementation's 120s foreground
64
- // timeout.
65
- const SHELL_DEFAULT_TIMEOUT_MS = 120_000;
66
-
67
64
  function bareToolName(toolName) {
68
65
  if (typeof toolName !== 'string' || !toolName) return '';
69
66
  // Strip any MCP/server prefix (e.g. 'server__shell' or 'server.shell').
@@ -80,17 +77,16 @@ function isSelfDeadlineTool(toolName) {
80
77
  * recorded into the progress snapshot at dispatch time. Returns a positive
81
78
  * number when the tool enforces its own deadline, or null when unknown/missing
82
79
  * (caller then falls back to the plain toolRunningMs ceiling).
83
- * - shell: explicit `timeout` (ms) if positive, else the 120s default.
80
+ * - shell: explicit positive `timeout_ms`; omitted/0 has no self-deadline.
84
81
  */
85
82
  export function resolveToolSelfDeadlineMs(toolName, args) {
86
83
  if (!isSelfDeadlineTool(toolName)) return null;
87
84
  const bare = bareToolName(toolName);
88
85
  const a = (args && typeof args === 'object') ? args : {};
89
86
  if (bare === 'shell') {
90
- const t = Number(a.timeout);
87
+ const t = Number(a.timeout_ms);
91
88
  if (Number.isFinite(t) && t > 0) return t;
92
- const envDefault = parseInt(process.env.BASH_DEFAULT_TIMEOUT_MS ?? '', 10);
93
- return envDefault > 0 ? envDefault : SHELL_DEFAULT_TIMEOUT_MS;
89
+ return null;
94
90
  }
95
91
  return null;
96
92
  }