mixdog 0.9.166 → 0.9.167

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 (221) hide show
  1. package/package.json +1 -1
  2. package/scripts/audit-usage-ledger.mjs +86 -0
  3. package/scripts/generate-voice-runtime-manifest.mjs +22 -17
  4. package/scripts/memory-chunk-quality.mjs +128 -0
  5. package/scripts/memory-layer2-check.mjs +133 -0
  6. package/scripts/model-catalog-audit.mjs +6 -0
  7. package/scripts/native-tool-download.mjs +14 -0
  8. package/scripts/prepare-native-assets.mjs +2 -7
  9. package/scripts/tool-failures.mjs +7 -2
  10. package/scripts/verify-release-assets.mjs +48 -90
  11. package/src/defaults/skills/browser-use/SKILL.md +33 -18
  12. package/src/defaults/skills/computer-use/SKILL.md +16 -8
  13. package/src/lib/keychain-cjs.cjs +0 -1
  14. package/src/lib/rules-builder.cjs +4 -1
  15. package/src/rules/agent/40-cycle1-agent.md +21 -13
  16. package/src/rules/lead/01-general.md +8 -3
  17. package/src/rules/shared/10-tool-workflow.md +20 -11
  18. package/src/rules/shared/30-exploration.md +11 -4
  19. package/src/rules/shared/40-editing.md +2 -3
  20. package/src/rules/shared/50-execution.md +6 -3
  21. package/src/rules/shared/60-verification.md +3 -2
  22. package/src/rules/shared/70-delivery.md +0 -4
  23. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +51 -45
  24. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +3 -4
  25. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +44 -7
  26. package/src/runtime/agent/orchestrator/agent-trace.mjs +9 -0
  27. package/src/runtime/agent/orchestrator/config.mjs +9 -4
  28. package/src/runtime/agent/orchestrator/context/collect.mjs +25 -1055
  29. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +272 -0
  30. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +296 -0
  31. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +523 -0
  32. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +7 -2
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +13 -14
  34. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +214 -0
  35. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +220 -116
  36. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +74 -44
  37. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +191 -29
  38. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +13 -12
  39. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +1 -9
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +5 -2
  41. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +48 -25
  42. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +28 -2
  43. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -1
  44. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +1 -8
  45. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +32 -0
  46. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +21 -6
  47. package/src/runtime/agent/orchestrator/providers/lib/grok-tool-schema.mjs +61 -13
  48. package/src/runtime/agent/orchestrator/providers/model-catalog-projection.mjs +6 -1
  49. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +123 -118
  50. package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +54 -0
  51. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +5 -2
  52. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +34 -3
  53. package/src/runtime/agent/orchestrator/providers/openai-codex-endpoints.mjs +21 -0
  54. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +154 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +2 -1
  56. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +322 -0
  57. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +243 -702
  58. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +2 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-startup-prewarm.mjs +221 -0
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -2
  61. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +19 -0
  63. package/src/runtime/agent/orchestrator/providers/provider-model-identities.mjs +40 -0
  64. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +120 -710
  65. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +36 -15
  66. package/src/runtime/agent/orchestrator/session/cache/util.mjs +9 -0
  67. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +17 -4
  68. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +36 -31
  69. package/src/runtime/agent/orchestrator/session/compact.mjs +4 -5
  70. package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +6 -5
  71. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -6
  72. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +121 -0
  73. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -3
  74. package/src/runtime/agent/orchestrator/session/loop/diagnostics.mjs +74 -0
  75. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +44 -17
  76. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +266 -0
  77. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +111 -0
  78. package/src/runtime/agent/orchestrator/session/loop/request-projection.mjs +99 -0
  79. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +91 -0
  80. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +36 -0
  81. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +3 -3
  82. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  83. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +20 -11
  84. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +3 -5
  85. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -3
  86. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +39 -0
  87. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +3 -2
  88. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +21 -3
  89. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +2 -3
  91. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +25 -25
  92. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +62 -10
  93. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +17 -0
  94. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +61 -4
  95. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +13 -9
  96. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +12 -2
  97. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +3 -2
  98. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +8 -3
  99. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +69 -6
  100. package/src/runtime/agent/orchestrator/tools/builtin.mjs +14 -17
  101. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  102. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  104. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
  105. package/src/runtime/bridge-discovery.mjs +8 -0
  106. package/src/runtime/browser-bridge/action-schema.mjs +37 -43
  107. package/src/runtime/browser-bridge/client.mjs +111 -99
  108. package/src/runtime/browser-bridge/input-fields.mjs +187 -0
  109. package/src/runtime/browser-bridge/tool-defs.mjs +12 -227
  110. package/src/runtime/computer-bridge/action-schema.mjs +2 -46
  111. package/src/runtime/computer-bridge/client.mjs +69 -77
  112. package/src/runtime/computer-bridge/core-actions.mjs +7 -5
  113. package/src/runtime/computer-bridge/error-recovery.mjs +41 -37
  114. package/src/runtime/computer-bridge/tool-defs.mjs +1 -1
  115. package/src/runtime/github/commands.mjs +4 -1
  116. package/src/runtime/github/contract.mjs +1 -1
  117. package/src/runtime/github/tool.mjs +2 -2
  118. package/src/runtime/media/adapters/antigravity-image.mjs +72 -0
  119. package/src/runtime/media/adapters/gemini-image.mjs +20 -11
  120. package/src/runtime/media/auth.mjs +12 -0
  121. package/src/runtime/media/catalog.mjs +30 -10
  122. package/src/runtime/media/jobs.mjs +4 -0
  123. package/src/runtime/media/lanes.mjs +40 -31
  124. package/src/runtime/memory/index.mjs +2 -9
  125. package/src/runtime/memory/lib/compact-handoff.mjs +26 -18
  126. package/src/runtime/memory/lib/memory-chunk-audit.mjs +41 -0
  127. package/src/runtime/memory/lib/memory-chunk-quality.mjs +388 -0
  128. package/src/runtime/memory/lib/memory-cycle1.mjs +95 -394
  129. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +29 -4
  130. package/src/runtime/memory/lib/memory-recall-store.mjs +8 -12
  131. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -11
  132. package/src/runtime/memory/lib/memory.mjs +2 -0
  133. package/src/runtime/memory/lib/query-handlers.mjs +98 -144
  134. package/src/runtime/memory/lib/query-ranking.mjs +23 -9
  135. package/src/runtime/memory/lib/recall-format.mjs +37 -27
  136. package/src/runtime/memory/lib/session-ingest-runtime.mjs +14 -8
  137. package/src/runtime/memory/lib/session-ingest.mjs +45 -84
  138. package/src/runtime/memory/lib/transcript-ingest.mjs +19 -21
  139. package/src/runtime/memory/tool-defs.mjs +2 -2
  140. package/src/runtime/office/authoring/pptx-author-session.mjs +3 -7
  141. package/src/runtime/office/core/office-actions-batch.mjs +2 -8
  142. package/src/runtime/office/core/office-actions-open.mjs +124 -0
  143. package/src/runtime/office/core/office-actions-read.mjs +127 -0
  144. package/src/runtime/office/core/office-core.mjs +80 -1
  145. package/src/runtime/office/core/office-sessions.mjs +116 -138
  146. package/src/runtime/office/index.mjs +18 -271
  147. package/src/runtime/office/pdf/pdf-analysis.mjs +2 -2
  148. package/src/runtime/office/pdf/pdf-limits.mjs +2 -0
  149. package/src/runtime/office/tool-defs.mjs +7 -6
  150. package/src/runtime/shared/agent-route-config.mjs +8 -8
  151. package/src/runtime/shared/child-spawn-gate.mjs +10 -13
  152. package/src/runtime/shared/config.mjs +2 -7
  153. package/src/runtime/shared/env.mjs +3 -3
  154. package/src/runtime/shared/err-text.mjs +1 -1
  155. package/src/runtime/shared/extension-scopes.mjs +3 -2
  156. package/src/runtime/shared/heap-cap.mjs +10 -6
  157. package/src/runtime/shared/llm/cost.mjs +35 -10
  158. package/src/runtime/shared/llm/usage-accounting.mjs +11 -1
  159. package/src/runtime/shared/llm/usage-context.mjs +6 -0
  160. package/src/runtime/shared/llm/usage-ledger-import.mjs +5 -0
  161. package/src/runtime/shared/llm/usage-ledger-repair.mjs +109 -0
  162. package/src/runtime/shared/llm/usage-ledger.mjs +59 -20
  163. package/src/runtime/shared/llm/usage-pricing-refresh.mjs +26 -0
  164. package/src/runtime/shared/llm/usage-rollup.mjs +17 -15
  165. package/src/runtime/shared/native-asset.mjs +3 -4
  166. package/src/runtime/shared/object.mjs +4 -0
  167. package/src/runtime/shared/pg-schema-db.mjs +21 -0
  168. package/src/runtime/shared/plugin-metadata.mjs +3 -1
  169. package/src/runtime/shared/resource-admission.mjs +1 -6
  170. package/src/runtime/shared/schedules-db.mjs +8 -24
  171. package/src/runtime/shared/schema-value-error.d.mts +2 -0
  172. package/src/runtime/shared/schema-value-error.mjs +68 -0
  173. package/src/runtime/shared/singleton-owner.mjs +11 -24
  174. package/src/runtime/shared/staged-update.mjs +1 -10
  175. package/src/runtime/shared/tool-card-model.mjs +8 -5
  176. package/src/runtime/shared/tool-execution-contract.mjs +1 -1
  177. package/src/runtime/shared/tool-surface.mjs +1 -5
  178. package/src/runtime/shared/user-data-guard.mjs +1 -4
  179. package/src/runtime/shared/webhooks-db.mjs +8 -24
  180. package/src/runtime/web-search/index.mjs +32 -8
  181. package/src/runtime/web-search/tool-defs.mjs +4 -4
  182. package/src/session-runtime/config-lifecycle.mjs +34 -0
  183. package/src/session-runtime/context-status.mjs +19 -0
  184. package/src/session-runtime/cwd-plugins.mjs +12 -29
  185. package/src/session-runtime/goal-tasks.mjs +2 -2
  186. package/src/session-runtime/goal-tool-defs.mjs +3 -2
  187. package/src/session-runtime/internal-tool-executor.mjs +7 -0
  188. package/src/session-runtime/lifecycle-api.mjs +5 -11
  189. package/src/session-runtime/notification-bus.mjs +41 -0
  190. package/src/session-runtime/prewarm.mjs +22 -0
  191. package/src/session-runtime/runtime-bootstrap.mjs +90 -0
  192. package/src/session-runtime/runtime-core.mjs +88 -202
  193. package/src/session-runtime/runtime-feature-gates.mjs +37 -0
  194. package/src/session-runtime/session-lifecycle.mjs +5 -5
  195. package/src/session-runtime/session-turn-api.mjs +1 -12
  196. package/src/session-runtime/setup-tool/executor.mjs +13 -5
  197. package/src/session-runtime/setup-tool/tool-defs.mjs +52 -46
  198. package/src/session-runtime/tool-defs.mjs +73 -4
  199. package/src/session-runtime/usage-stats-api.mjs +4 -1
  200. package/src/session-runtime/warmup-schedulers.mjs +8 -2
  201. package/src/session-runtime/workflow.mjs +14 -0
  202. package/src/standalone/channel-session-router.mjs +3 -1
  203. package/src/standalone/channel-worker.mjs +18 -9
  204. package/src/standalone/daemon-crash-capture.mjs +534 -0
  205. package/src/standalone/daemon-telemetry.mjs +175 -0
  206. package/src/standalone/daemon.mjs +31 -21
  207. package/src/standalone/session-client.mjs +31 -23
  208. package/src/standalone/usage-dashboard-model.mjs +2 -0
  209. package/src/standalone/usage-stats-hours.mjs +18 -11
  210. package/src/standalone/usage-stats-model.mjs +39 -28
  211. package/src/standalone/usage-stats-period.mjs +43 -45
  212. package/src/tui/app/use-transcript-window.mjs +1 -1
  213. package/src/tui/components/StatusLine.jsx +13 -42
  214. package/src/tui/dist/index.mjs +256 -239
  215. package/src/tui/session/session-api-ext.mjs +44 -41
  216. package/src/tui/session/session-api.mjs +4 -0
  217. package/src/tui/session/tool-card-results.mjs +25 -78
  218. package/src/tui/session/tool-result-status.mjs +94 -1
  219. package/src/tui/session/turn-aggregate-cards.mjs +17 -54
  220. package/src/ui/statusline-format.mjs +6 -26
  221. package/src/workflows/default/WORKFLOW.md +5 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixdog",
3
- "version": "0.9.166",
3
+ "version": "0.9.167",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone mixdog coding-agent CLI/TUI workspace.",
@@ -0,0 +1,86 @@
1
+ import assert from 'node:assert/strict';
2
+ import { copyFileSync, mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
3
+ import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
4
+ import { DatabaseSync, backup } from 'node:sqlite';
5
+ import { UsageLedger } from '../src/runtime/shared/llm/usage-ledger.mjs';
6
+ import { repairUsageLedger, usageLedgerIntegrity } from '../src/runtime/shared/llm/usage-ledger-repair.mjs';
7
+ import { usageStatsSnapshot } from '../src/standalone/usage-stats-model.mjs';
8
+
9
+ // Usage: node scripts/audit-usage-ledger.mjs --ledger=<absolute file>
10
+ // [--reattribute-xai-to-grok] [--apply]
11
+ // Raw files and a consistent SQLite snapshot survive both dry runs and apply.
12
+ const options = process.argv.slice(2);
13
+ const ledgerArg = options.find((arg) => arg.startsWith('--ledger='))?.slice(9);
14
+ if (!ledgerArg || !isAbsolute(ledgerArg) || !/\.sqlite$/i.test(ledgerArg)) {
15
+ throw new Error('Supply an exact absolute --ledger=<file.sqlite> path');
16
+ }
17
+ for (const arg of options) {
18
+ if (!arg.startsWith('--ledger=') && !['--reattribute-xai-to-grok', '--apply'].includes(arg)) {
19
+ throw new Error(`Unknown argument: ${arg}`);
20
+ }
21
+ }
22
+ const source = resolve(ledgerArg);
23
+ const auditRoot = join(dirname(source), 'audits');
24
+ mkdirSync(auditRoot, { recursive: true });
25
+ const workspace = mkdtempSync(join(auditRoot, 'usage-'));
26
+ const raw = join(workspace, 'original-files');
27
+ mkdirSync(raw);
28
+ for (const suffix of ['', '-wal', '-shm']) {
29
+ try { copyFileSync(source + suffix, join(raw, basename(source) + suffix)); }
30
+ catch (error) { if (!suffix || error.code !== 'ENOENT') throw error; }
31
+ }
32
+ const snapshotPath = join(workspace, 'backup.sqlite');
33
+ const original = new DatabaseSync(source, { readOnly: true });
34
+ try { await backup(original, snapshotPath); } finally { original.close(); }
35
+ const workingPath = join(workspace, 'working.sqlite');
36
+ copyFileSync(snapshotPath, workingPath);
37
+ const throughTs = Date.now();
38
+ const providerOverrides = options.includes('--reattribute-xai-to-grok') ? { xai: 'grok-oauth' } : {};
39
+ const summarize = (ledger) => {
40
+ const stats = usageStatsSnapshot({ rollup: ledger.rollup(), now: throughTs, source: 'all' });
41
+ const sum = (rows, key) => rows.reduce((n, row) => n + Number(row[key] || 0), 0);
42
+ for (const key of ['turns', 'input', 'output', 'cacheRead', 'cacheWrite', 'costKnownTurns']) {
43
+ assert.equal(sum(stats.providers, key), stats.totals[key], `provider sum: ${key}`);
44
+ }
45
+ for (const rows of [stats.providers, stats.daily]) {
46
+ assert.ok(Math.abs(sum(rows, 'costUsd') - stats.totals.costUsd) <= rows.length * 0.000001 + 0.000001, 'cost sum');
47
+ assert.equal(sum(rows, 'tokens'), stats.totals.tokens, 'token sum');
48
+ assert.equal(sum(rows, 'turns'), stats.totals.turns, 'record sum');
49
+ }
50
+ assert.ok(Math.abs(stats.totals.costBilled + stats.totals.costEstimated - stats.totals.costUsd) < 0.000002, 'cost categories');
51
+ assert.ok(stats.totals.costKnownTurns <= stats.totals.turns, 'price coverage');
52
+ for (const p of stats.providers) {
53
+ assert.equal(sum(p.models, 'turns'), p.turns, `model sum: ${p.provider}`);
54
+ }
55
+ return {
56
+ integrity: usageLedgerIntegrity(ledger.db), totals: stats.totals,
57
+ providers: stats.providers.map(({ provider, providerKind, turns, tokens, costUsd, costUnpricedTurns }) =>
58
+ ({ provider, providerKind, turns, tokens, costUsd, costUnpricedTurns })),
59
+ };
60
+ };
61
+ const working = new UsageLedger(workingPath);
62
+ let before, preview, after;
63
+ try {
64
+ assert.deepEqual(working.db.prepare('PRAGMA quick_check').all().map((r) => r.quick_check), ['ok']);
65
+ before = summarize(working);
66
+ preview = repairUsageLedger(working, { throughTs, providerOverrides });
67
+ after = summarize(working);
68
+ assert.deepEqual(before.integrity, after.integrity);
69
+ } finally { working.close(); }
70
+ const report = { source, workspace, backup: snapshotPath, throughTs, providerOverrides, before, preview, after, applied: false };
71
+ writeFileSync(join(workspace, 'report.json'), JSON.stringify(report, null, 2));
72
+ if (options.includes('--apply')) {
73
+ const live = new UsageLedger(source);
74
+ try {
75
+ // The transaction repairs only the bounded history. Concurrent new
76
+ // requests are retained and indexed too; no database file is replaced.
77
+ report.appliedRepair = repairUsageLedger(live, { throughTs, providerOverrides });
78
+ report.live = summarize(live);
79
+ assert.deepEqual(live.db.prepare('PRAGMA quick_check').all().map((r) => r.quick_check), ['ok']);
80
+ report.applied = true;
81
+ } finally { live.close(); }
82
+ writeFileSync(join(workspace, 'report.json'), JSON.stringify(report, null, 2));
83
+ }
84
+ console.log(JSON.stringify({ workspace, backup: snapshotPath, applied: report.applied,
85
+ preview, ...(report.appliedRepair ? { appliedRepair: report.appliedRepair } : {}),
86
+ totals: (report.live || after).totals, providers: (report.live || after).providers }, null, 2));
@@ -82,6 +82,24 @@ export function compareSemver(left, right) {
82
82
  return 0
83
83
  }
84
84
 
85
+ export function isHiddenDraftReleaseUrl(sourceUrl, { repository, asset }) {
86
+ try {
87
+ const parsed = new URL(sourceUrl)
88
+ const prefix = `/${repository}/releases/download/`
89
+ const parts = parsed.pathname.startsWith(prefix)
90
+ ? parsed.pathname.slice(prefix.length).split('/')
91
+ : []
92
+ return parsed.origin === 'https://github.com'
93
+ && !parsed.search
94
+ && !parsed.hash
95
+ && parts.length === 2
96
+ && parts[0].startsWith('untagged-')
97
+ && parts[1] === asset
98
+ } catch {
99
+ return false
100
+ }
101
+ }
102
+
85
103
  export function buildVoiceRuntimePlatforms(
86
104
  releaseAssets,
87
105
  { repository = 'tribgames/mixdog', tag = VOICE_RUNTIME_TAG } = {},
@@ -103,23 +121,10 @@ export function buildVoiceRuntimePlatforms(
103
121
  }
104
122
  const sourceUrl = String(asset.browser_download_url || '')
105
123
  const url = `https://github.com/${repository}/releases/download/${tag}/${spec.asset}`
106
- let validDraftUrl = false
107
- try {
108
- const parsed = new URL(sourceUrl)
109
- const prefix = `/${repository}/releases/download/`
110
- const parts = parsed.pathname.startsWith(prefix)
111
- ? parsed.pathname.slice(prefix.length).split('/')
112
- : []
113
- validDraftUrl = parsed.origin === 'https://github.com'
114
- && !parsed.search
115
- && !parsed.hash
116
- && parts.length === 2
117
- && parts[0].startsWith('untagged-')
118
- && parts[1] === spec.asset
119
- } catch {
120
- validDraftUrl = false
121
- }
122
- if (sourceUrl !== url && !validDraftUrl) {
124
+ if (sourceUrl !== url && !isHiddenDraftReleaseUrl(sourceUrl, {
125
+ repository,
126
+ asset: spec.asset,
127
+ })) {
123
128
  throw new Error(`release asset ${spec.asset} has invalid download URL`)
124
129
  }
125
130
  platforms[spec.key] = {
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ // Audit/simulate on a separate snapshot. This command NEVER updates the
3
+ // live DB, starts PG, migrates a schema or replaces the supplied input file.
4
+ import assert from 'node:assert/strict'
5
+ import { createHash } from 'node:crypto'
6
+ import { mkdtemp, readFile, writeFile } from 'node:fs/promises'
7
+ import { tmpdir } from 'node:os'
8
+ import { join } from 'node:path'
9
+ import { parseArgs } from 'node:util'
10
+ import { auditChunkEntries, collectChunkRoots } from '../src/runtime/memory/lib/memory-chunk-audit.mjs'
11
+ import {
12
+ generateCycle1Chunks,
13
+ } from '../src/runtime/memory/lib/memory-chunk-quality.mjs'
14
+ import { compactHandoffRows } from '../src/runtime/memory/lib/compact-handoff.mjs'
15
+ import { renderEntryLines } from '../src/runtime/memory/lib/recall-format.mjs'
16
+ import { readServiceAdvert } from '../src/runtime/shared/service-discovery.mjs'
17
+
18
+ const { values } = parseArgs({
19
+ options: {
20
+ input: { type: 'string' },
21
+ 'simulate-limit': { type: 'string', default: '0' },
22
+ },
23
+ })
24
+ const simulationLimit = Number(values['simulate-limit'])
25
+ if (!Number.isSafeInteger(simulationLimit) || simulationLimit < 0) throw new Error('simulate-limit must be a nonnegative integer')
26
+ const directory = await mkdtemp(join(tmpdir(), 'mixdog-chunk-quality-'))
27
+ console.log(`Artifacts: ${directory}`)
28
+ let snapshot
29
+ if (values.input) {
30
+ snapshot = JSON.parse(await readFile(values.input, 'utf8'))
31
+ } else {
32
+ const advert = readServiceAdvert('pg')
33
+ if (!Number.isInteger(Number(advert?.pg_port)) || Number(advert.pg_port) < 1) throw new Error('No advertised PG port; live audit blocked')
34
+ const { default: pg } = await import('pg')
35
+ const client = new pg.Client({
36
+ host: '127.0.0.1', port: Number(advert.pg_port), user: 'postgres',
37
+ database: 'mixdog', password: '', connectionTimeoutMillis: 10000,
38
+ })
39
+ await client.connect()
40
+ try {
41
+ await client.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY')
42
+ const { rows } = await client.query(`
43
+ SELECT id, ts, role, content, source_ref, session_id, source_turn, time_source,
44
+ project_id, chunk_root, is_root, element, category, summary, status, error_count,
45
+ to_jsonb(e)->'chunk_quality' AS chunk_quality
46
+ FROM memory.entries e ORDER BY id`)
47
+ snapshot = { capturedAt: new Date().toISOString(), entries: rows }
48
+ await client.query('COMMIT')
49
+ } catch (error) {
50
+ await client.query('ROLLBACK')
51
+ throw error
52
+ } finally {
53
+ await client.end()
54
+ }
55
+ }
56
+ assert.ok(Array.isArray(snapshot.entries), 'snapshot.entries must be an array')
57
+ const sourceBytes = JSON.stringify(snapshot)
58
+ const sourceHash = createHash('sha256').update(sourceBytes).digest('hex')
59
+ await writeFile(join(directory, 'snapshot.json'), sourceBytes, { flag: 'wx' })
60
+ const working = structuredClone(snapshot)
61
+ const before = auditChunkEntries(snapshot.entries)
62
+ const outcomes = []
63
+ let blocked = false
64
+ const renderOptions = { chronologicalOrder: true, compactTimestamps: true, pendingMarks: false, maxBodyChars: null }
65
+ function assertCoverage(projection, expectedRows) {
66
+ const represented = new Set()
67
+ for (const row of projection) {
68
+ const ids = row._compactBody ? row._compactMemberIds : [String(row.id)]
69
+ for (const id of ids) {
70
+ assert.equal(represented.has(String(id)), false, `duplicate projected source ${id}`)
71
+ represented.add(String(id))
72
+ }
73
+ }
74
+ assert.deepEqual([...represented].sort(), expectedRows.map(entry => String(entry.id)).sort())
75
+ return represented.size
76
+ }
77
+ if (simulationLimit) {
78
+ const { callAgentDispatch } = await import('../src/runtime/memory/lib/agent-ipc.mjs')
79
+ const { resolveMaintenancePreset } = await import('../src/runtime/shared/llm/index.mjs')
80
+ const options = {
81
+ callLlm: callAgentDispatch,
82
+ request: { agent: 'cycle1-agent', taskType: 'maintenance', preset: resolveMaintenancePreset('memory'), timeout: 180000, cwd: null },
83
+ }
84
+ for (const root of collectChunkRoots(working.entries).slice(0, simulationLimit)) {
85
+ const generated = await generateCycle1Chunks(root.members, options)
86
+ const rawIds = new Set(generated.rawRowIds.map(String))
87
+ const projected = compactHandoffRows([
88
+ ...generated.chunks.map(chunk => ({
89
+ ...chunk.members[0], is_root: 1, members: chunk.members,
90
+ summary: chunk.summary, element: chunk.element, chunk_quality: chunk.quality,
91
+ })),
92
+ ...root.members.filter(member => rawIds.has(String(member.id))).map(member => ({ ...member, is_root: 0 })),
93
+ ])
94
+ const outcome = {
95
+ id: root.id, inputRows: root.members.length,
96
+ stats: generated.stats, chunks: generated.chunks.length, rawRowIds: generated.rawRowIds,
97
+ errors: generated.invalidChunks, projectionCoverage: assertCoverage(projected, root.members),
98
+ text: renderEntryLines(projected, renderOptions),
99
+ }
100
+ if (generated.invalidChunks.some(invalid => invalid.reason === 'llm_error')) blocked = true
101
+ outcomes.push(outcome)
102
+ if (blocked) break
103
+ }
104
+ }
105
+ // Independent integrity checks: the source snapshot is unchanged; no source
106
+ // identity, content, chronology, membership or status is rewritten in the copy.
107
+ assert.equal(createHash('sha256').update(JSON.stringify(snapshot)).digest('hex'), sourceHash)
108
+ assert.deepEqual(working.entries.map(({ id, content, ts, role, chunk_root, status }) => [id, content, ts, role, chunk_root, status]),
109
+ snapshot.entries.map(({ id, content, ts, role, chunk_root, status }) => [id, content, ts, role, chunk_root, status]))
110
+ const after = auditChunkEntries(working.entries)
111
+ const roots = collectChunkRoots(working.entries)
112
+ const rootIds = new Set(roots.map(root => String(root.id)))
113
+ const standalone = working.entries.filter(entry => !rootIds.has(String(entry.chunk_root)))
114
+ const projection = compactHandoffRows([...roots, ...standalone])
115
+ const projectionCoverage = assertCoverage(projection, working.entries)
116
+ const report = {
117
+ capturedAt: snapshot.capturedAt, sourceHash, before, after, outcomes, blocked,
118
+ integrity: { sourceUnchanged: true, sourceRowsUnchanged: true, projectionCoverage },
119
+ }
120
+ working.simulations = outcomes
121
+ await writeFile(join(directory, 'working.json'), JSON.stringify(working), { flag: 'wx' })
122
+ await writeFile(join(directory, 'report.json'), JSON.stringify(report, null, 2), { flag: 'wx' })
123
+ console.log(JSON.stringify({
124
+ directory, audit: { ...before, chunks: undefined },
125
+ outcomes: outcomes.map(outcome => ({ ...outcome, text: undefined })),
126
+ integrity: report.integrity, blocked,
127
+ }, null, 2))
128
+ if (blocked) process.exitCode = 1
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ // Real-AI second-layer simulation on immutable, prepared first-layer cases.
3
+ // Input: { cases: [{ name, rows: [{id,ts,role,session_id,content,sourceIds}],
4
+ // preservedRawIds?, reviewCriteria? }] }.
5
+ import assert from 'node:assert/strict'
6
+ import { createHash } from 'node:crypto'
7
+ import { mkdtemp, readFile, writeFile } from 'node:fs/promises'
8
+ import { tmpdir } from 'node:os'
9
+ import { join } from 'node:path'
10
+ import { parseArgs } from 'node:util'
11
+ import { generateCycle1Chunks } from '../src/runtime/memory/lib/memory-chunk-quality.mjs'
12
+ import { estimateTokens } from '../src/runtime/agent/orchestrator/session/token-estimate.mjs'
13
+ import { makeAgentDispatch } from '../src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs'
14
+ import { loadConfig } from '../src/runtime/agent/orchestrator/config.mjs'
15
+ import { resolveMaintenanceRoute } from '../src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs'
16
+ import { initProviders } from '../src/runtime/agent/orchestrator/providers/registry.mjs'
17
+ import { loadScopedRoleInstructions } from '../src/runtime/agent/orchestrator/context/collect.mjs'
18
+ import { resolveMaintenancePreset } from '../src/runtime/shared/llm/index.mjs'
19
+
20
+ const { values } = parseArgs({ options: {
21
+ input: { type: 'string' }, case: { type: 'string', multiple: true },
22
+ } })
23
+ if (!values.input) throw new Error('--input must name a prepared first-layer case file')
24
+ const bytes = await readFile(values.input, 'utf8')
25
+ const sourceHash = createHash('sha256').update(bytes).digest('hex')
26
+ const source = JSON.parse(bytes)
27
+ assert.ok(Array.isArray(source.cases) && source.cases.length, 'cases must be nonempty')
28
+ const selectedCases = values.case ? new Set(values.case) : null
29
+ if (selectedCases) {
30
+ for (const name of selectedCases) assert.ok(source.cases.some(item => item.name === name), `unknown case: ${name}`)
31
+ }
32
+ const directory = await mkdtemp(join(tmpdir(), 'mixdog-layer2-once-'))
33
+ await writeFile(join(directory, 'input.json'), bytes, { flag: 'wx' })
34
+ await writeFile(join(directory, 'working.json'), bytes, { flag: 'wx' })
35
+ console.log(`Artifacts: ${directory}`)
36
+ const working = structuredClone(source)
37
+ const config = loadConfig()
38
+ const configuredRoute = resolveMaintenancePreset('memory')
39
+ || resolveMaintenanceRoute({ agent: 'cycle1-agent', config })
40
+ const preset = typeof configuredRoute === 'object' ? configuredRoute
41
+ : config.presets.find(item => item.id === configuredRoute || item.name === configuredRoute)
42
+ assert.ok(preset?.provider && preset?.model, 'second-layer model route is unresolved')
43
+ const request = {
44
+ agent: 'cycle1-agent', taskType: 'maintenance', preset: { ...preset, effort: 'low' },
45
+ timeout: 180000, cwd: null,
46
+ }
47
+ // The installed broker can carry an older, first-layer-only system policy.
48
+ // A source-code benchmark must use the worktree's actual scoped role rules,
49
+ // not a new user prompt under that incompatible old system instruction.
50
+ const roleInstructions = loadScopedRoleInstructions(request.agent, request.preset?.provider)
51
+ assert.ok(roleInstructions.includes('SECOND_LAYER'), 'source runtime did not load the second-layer role policy')
52
+ const rolePolicyHash = createHash('sha256').update(roleInstructions).digest('hex')
53
+ await initProviders(config.providers)
54
+ const dispatch = makeAgentDispatch({
55
+ agent: request.agent, taskType: request.taskType, preset: request.preset,
56
+ cwd: null, brief: false, config,
57
+ })
58
+ const outcomes = []
59
+ let blocked = false
60
+ for (const item of working.cases) {
61
+ if (selectedCases && !selectedCases.has(item.name)) continue
62
+ if (blocked) {
63
+ outcomes.push({ name: item.name, status: 'not_run_after_provider_failure' })
64
+ continue
65
+ }
66
+ const original = JSON.stringify(item)
67
+ const inputText = item.rows.map(row => row.content).join('\n')
68
+ const inputTokens = estimateTokens(inputText)
69
+ assert.ok(inputTokens >= 2)
70
+ assert.equal(new Set(item.rows.map(row => String(row.id))).size, item.rows.length)
71
+ assert.equal(new Set(item.rows.map(row => row.session_id)).size, 1)
72
+ const calls = []
73
+ const result = await generateCycle1Chunks(item.rows, {
74
+ // Prepared cases have no measured live-context budget. Report the writing
75
+ // target separately instead of inventing a hard half-size context limit.
76
+ layer: 2,
77
+ request,
78
+ callLlm: async (options, prompt) => {
79
+ const response = await dispatch({
80
+ prompt, preset: options.preset, cwd: null, parentSignal: options.signal,
81
+ idleTimeoutMs: options.timeout,
82
+ })
83
+ calls.push({ mode: options.mode, response })
84
+ return response
85
+ },
86
+ })
87
+ assert.equal(JSON.stringify(item), original, 'source input mutated')
88
+ assert.ok(result)
89
+ assert.ok(result.stats.groupingCalls <= 1, 'second layer must not make multiple AI calls')
90
+ const fallback = new Set(result.rawRowIds.map(String))
91
+ const units = [
92
+ ...result.chunks.map(chunk => ({
93
+ position: Math.min(...chunk._idxList) - 1, text: chunk.summary,
94
+ parentIds: chunk.members.map(row => String(row.id)),
95
+ })),
96
+ ...item.rows.flatMap((row, position) => fallback.has(String(row.id))
97
+ ? [{ position, text: row.content, parentIds: [String(row.id)] }] : []),
98
+ ].sort((a, b) => a.position - b.position)
99
+ const parentIds = units.flatMap(unit => unit.parentIds)
100
+ assert.equal(new Set(parentIds).size, parentIds.length)
101
+ assert.deepEqual(parentIds.slice().sort(), item.rows.map(row => String(row.id)).sort())
102
+ const text = units.map(unit => unit.text).join('\n')
103
+ const outputTokens = estimateTokens(text)
104
+ if (result.compression.used) assert.ok(outputTokens < inputTokens)
105
+ const sourceIds = item.rows.flatMap(row => row.sourceIds || [String(row.id)])
106
+ assert.equal(new Set(sourceIds).size, sourceIds.length)
107
+ assert.equal(result.stats.verificationCalls, 0)
108
+ assert.equal(result.stats.retries, 0)
109
+ outcomes.push({
110
+ name: item.name, applied: result.compression.used, targetMet: result.compression.targetMet,
111
+ inputChunks: item.rows.length, outputChunks: result.chunks.length, fallbackChunks: fallback.size,
112
+ inputTokens, outputTokens, reductionPercent: 100 * (inputTokens - outputTokens) / inputTokens,
113
+ stats: result.stats, compression: result.compression, errors: result.invalidChunks,
114
+ parentCoverage: parentIds.length, sourceRowCoverage: sourceIds.length,
115
+ preservedRawIds: item.preservedRawIds || [], reviewCriteria: item.reviewCriteria || [],
116
+ text, calls,
117
+ })
118
+ if (result.invalidChunks.some(error => error.reason === 'llm_error')) blocked = true
119
+ }
120
+ assert.equal(createHash('sha256').update(await readFile(values.input, 'utf8')).digest('hex'), sourceHash)
121
+ assert.deepEqual(working, source)
122
+ const report = {
123
+ sourceHash, sourceUnchanged: true, runtime: 'source', route: request.preset, rolePolicyHash, outcomes, blocked,
124
+ allSizeTargetsMet: outcomes.length > 0 && outcomes.every(outcome => outcome.targetMet === true),
125
+ scope: 'Single-call lossy second-layer compression with a soft half-size writing target. Reports time and approximate savings, not semantic equivalence. No AI verification, quality rejection, retry, live DB update or automatic compaction deployment.',
126
+ }
127
+ await writeFile(join(directory, 'report.json'), JSON.stringify(report, null, 2), { flag: 'wx' })
128
+ console.log(JSON.stringify({
129
+ directory, sourceUnchanged: report.sourceUnchanged, route: report.route,
130
+ allSizeTargetsMet: report.allSizeTargetsMet, blocked,
131
+ outcomes: outcomes.map(({ calls, reviewCriteria, ...outcome }) => outcome),
132
+ }, null, 2))
133
+ process.exitCode = blocked ? 1 : 0
@@ -4,6 +4,7 @@ import { initProviders, getAllProviders } from '../src/runtime/agent/orchestrato
4
4
  import {
5
5
  loadModelsDevCatalog,
6
6
  warmModelMetadataCatalogs,
7
+ auditModelPricing,
7
8
  } from '../src/runtime/agent/orchestrator/providers/model-catalog.mjs';
8
9
  import { QUICK_WEB_SEARCH_MODELS } from '../src/session-runtime/quick-web-search-models.mjs';
9
10
 
@@ -151,6 +152,7 @@ for (const [name, provider] of getAllProviders()) {
151
152
  error: listed.error,
152
153
  count: listed.models.length,
153
154
  rows,
155
+ pricing: auditModelPricing(listed.models, name),
154
156
  });
155
157
  }
156
158
 
@@ -178,6 +180,7 @@ const report = {
178
180
  generatedAt: new Date().toISOString(),
179
181
  enabledProviders: Object.entries(cfg.providers || {}).filter(([, v]) => v?.enabled).map(([k]) => k),
180
182
  providerResults,
183
+ unpricedRoutes: providerResults.flatMap((p) => p.pricing.filter((row) => !row.priced)),
181
184
  mismatches,
182
185
  exactOneM,
183
186
  nearOneM,
@@ -198,6 +201,9 @@ if (json) {
198
201
  ].filter(Boolean).join('; ');
199
202
  console.log(` ${r.id}: ${r.liveContextLabel} ctx, out=${r.liveOutput ?? '-'}${flags ? ` [${flags}]` : ''}`);
200
203
  }
204
+ for (const row of p.pricing.filter((r) => !r.priced)) {
205
+ console.log(` UNPRICED ${row.model} -> ${row.pricingProvider}/${row.pricingModel}: ${row.missingRates.join(', ')}`);
206
+ }
201
207
  }
202
208
  console.log(`\nsummary: mismatches=${mismatches.length}, exact1M=${exactOneM.length}, near1MNotExact=${nearOneM.length}, staticContextRows=${quickRows.length}`);
203
209
  if (mismatches.length) {
@@ -27,6 +27,13 @@ const RELEASE_ROOT = 'https://github.com/tribgames/mixdog/releases/download'
27
27
  const TOOLS_DIR = new URL('../src/runtime/agent/orchestrator/tools/', import.meta.url)
28
28
 
29
29
  export const NATIVE_TOOL_KINDS = Object.freeze(['graph', 'patch', 'spawn'])
30
+ export const NATIVE_TOOL_PLATFORM_KEYS = Object.freeze([
31
+ 'darwin-arm64',
32
+ 'darwin-x64',
33
+ 'linux-arm64',
34
+ 'linux-x64',
35
+ 'win32-x64',
36
+ ])
30
37
 
31
38
  /** Released asset name. */
32
39
  export function nativeToolAssetName(kind, target) {
@@ -43,6 +50,13 @@ export function nativeToolInstalledName(kind, target) {
43
50
  return target.platform === 'win32' ? `mixdog-${kind}.exe` : `mixdog-${kind}`
44
51
  }
45
52
 
53
+ export function nativeToolPlatformAssets(kind) {
54
+ return Object.fromEntries(NATIVE_TOOL_PLATFORM_KEYS.map((key) => {
55
+ const [platform, arch] = key.split('-')
56
+ return [key, nativeToolAssetName(kind, { platform, arch })]
57
+ }))
58
+ }
59
+
46
60
  async function sha256File(path) {
47
61
  return createHash('sha256').update(await readFile(path)).digest('hex')
48
62
  }
@@ -18,16 +18,11 @@ import {
18
18
  import { ensureGraphBinary } from '../src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs';
19
19
  import { ensurePatchBinary } from '../src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs';
20
20
  import { ensureSpawnBinary } from '../src/runtime/agent/orchestrator/tools/spawn-binary-fetcher.mjs';
21
+ import { NATIVE_TOOL_PLATFORM_KEYS } from './native-tool-download.mjs';
21
22
 
22
23
  const DEFAULT_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
23
24
 
24
- export const NATIVE_ASSET_PLATFORMS = Object.freeze([
25
- 'darwin-arm64',
26
- 'darwin-x64',
27
- 'linux-arm64',
28
- 'linux-x64',
29
- 'win32-x64',
30
- ]);
25
+ export const NATIVE_ASSET_PLATFORMS = NATIVE_TOOL_PLATFORM_KEYS;
31
26
 
32
27
  export const REQUIRED_NATIVE_INSTALLERS = Object.freeze({
33
28
  graph: ensureGraphBinary,
@@ -104,8 +104,13 @@ function normalizeRowCategory(row) {
104
104
  let category = storedCategory;
105
105
  if (isKnownTestFixture(row)) {
106
106
  category = 'expected-test';
107
- } else if (rowTool(row) === 'apply_patch') {
108
- const derived = classifyToolFailure(rowErrorText(row), 'apply_patch');
107
+ } else {
108
+ const derived = classifyToolFailure(rowErrorText(row), rowTool(row));
109
+ // The shell classifier's generic command-exit fallback cannot recover
110
+ // execution metadata from a short historical preview such as "SIGKILL".
111
+ // Keep recorded signals, timeouts and capture failures instead of hiding
112
+ // them among ordinary command exits.
113
+ if (derived === 'command-exit' && storedCategory !== 'command-exit') return row;
109
114
  // Failure previews are bounded and may end before the nested cause. Never
110
115
  // downgrade a stored specific category to the generic fallback merely
111
116
  // because the historical preview lacks that tail.