titan-agent 6.0.0-beta.3 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/agent/verifier.js +86 -7
  2. package/dist/agent/verifier.js.map +1 -1
  3. package/dist/gateway/server.js +264 -0
  4. package/dist/gateway/server.js.map +1 -1
  5. package/dist/utils/constants.js +1 -1
  6. package/dist/utils/constants.js.map +1 -1
  7. package/package.json +1 -1
  8. package/ui/dist/assets/{AuditPanel-B6ByeY9s.js → AuditPanel-DgKHxTPP.js} +1 -1
  9. package/ui/dist/assets/{AutonomyPanel-DFbEUiXa.js → AutonomyPanel-CGoGTTeQ.js} +1 -1
  10. package/ui/dist/assets/{AutopilotPanel-DfY429Lo.js → AutopilotPanel-BRB2iw62.js} +1 -1
  11. package/ui/dist/assets/{AutoresearchPanel-CuezuEdT.js → AutoresearchPanel-C0CVdlZo.js} +1 -1
  12. package/ui/dist/assets/{BackupPanel-CqbapnjY.js → BackupPanel-DbGK-62d.js} +1 -1
  13. package/ui/dist/assets/{BrowserPanel-Dt5YewRB.js → BrowserPanel-QBeYtxku.js} +1 -1
  14. package/ui/dist/assets/{CPActivity-CSnv3CqO.js → CPActivity-RKfcA14S.js} +1 -1
  15. package/ui/dist/assets/{CPAgentDetail-s4IJvapx.js → CPAgentDetail-DLQ-SLUv.js} +1 -1
  16. package/ui/dist/assets/{CPAgents-VWubCERb.js → CPAgents-BvoWQVsN.js} +1 -1
  17. package/ui/dist/assets/{CPApprovals-rT9Lszle.js → CPApprovals-C4DBwT0d.js} +1 -1
  18. package/ui/dist/assets/{CPCosts-D9Smzdgc.js → CPCosts-DWGuPs18.js} +1 -1
  19. package/ui/dist/assets/{CPDashboard-BHUoLz2x.js → CPDashboard-DLuJ3vMn.js} +1 -1
  20. package/ui/dist/assets/{CPFiles-gGieOziV.js → CPFiles-BeLI7pDx.js} +1 -1
  21. package/ui/dist/assets/{CPGoals-DVnDs_V-.js → CPGoals-9NeuC7Bf.js} +1 -1
  22. package/ui/dist/assets/{CPInbox-D7ogD-yT.js → CPInbox-QvoryAsM.js} +1 -1
  23. package/ui/dist/assets/{CPIssueDetail-BYC5HPiJ.js → CPIssueDetail-e_XoGfNN.js} +1 -1
  24. package/ui/dist/assets/{CPIssues-peJUlaZe.js → CPIssues-CrfHaPp1.js} +1 -1
  25. package/ui/dist/assets/{CPLayout-B6dAtmzO.js → CPLayout-DFtDIu-l.js} +3 -3
  26. package/ui/dist/assets/{CPOrg-Bf0V4v9D.js → CPOrg-Bvgvg6TU.js} +1 -1
  27. package/ui/dist/assets/{CPRuns-CKJVqML1.js → CPRuns-HSig_POk.js} +1 -1
  28. package/ui/dist/assets/{CPSocial-DLqeJMzs.js → CPSocial-DszBYcz3.js} +1 -1
  29. package/ui/dist/assets/{ChannelsPanel-COoqA79b.js → ChannelsPanel-CA_33e6X.js} +1 -1
  30. package/ui/dist/assets/{CheckpointsPanel-DZkPfYie.js → CheckpointsPanel-BWY5_she.js} +1 -1
  31. package/ui/dist/assets/{CommandPostHub-Bztgzaq5.js → CommandPostHub-B9t1MQE-.js} +3 -3
  32. package/ui/dist/assets/{CronPanel-DX0JDeNF.js → CronPanel-0GfnFx-2.js} +1 -1
  33. package/ui/dist/assets/{DataTable-CYoW9s8a.js → DataTable-Xubw7ZZ_.js} +1 -1
  34. package/ui/dist/assets/{DreamPanel-CvLxLUm_.js → DreamPanel-Cr_Qvz-y.js} +1 -1
  35. package/ui/dist/assets/{EmptyState-BVJs3cdU.js → EmptyState-SMpctu2L.js} +1 -1
  36. package/ui/dist/assets/{EvalHarnessPanel-B3HkI4DS.js → EvalHarnessPanel-mt32iK8b.js} +1 -1
  37. package/ui/dist/assets/{EvalPanel-Bi2FLZzb.js → EvalPanel-DxCpBDZ0.js} +1 -1
  38. package/ui/dist/assets/{FilesPanel-SYm03-oB.js → FilesPanel-GiPGeCFc.js} +1 -1
  39. package/ui/dist/assets/{FleetPanel-6JQMVl9S.js → FleetPanel-BrXnJRTE.js} +1 -1
  40. package/ui/dist/assets/{HomelabPanel-D06cZR0f.js → HomelabPanel-Dvu42BHt.js} +1 -1
  41. package/ui/dist/assets/InfraView-UVUPjoCu.js +2 -0
  42. package/ui/dist/assets/{InlineEditableField-jwqOGJoa.js → InlineEditableField-DLOUEEYu.js} +1 -1
  43. package/ui/dist/assets/{Input-KpZ5nc4A.js → Input-B7EQI0I4.js} +1 -1
  44. package/ui/dist/assets/{IntegrationsPanel-BkCbBZHZ.js → IntegrationsPanel-DZfRW7AO.js} +1 -1
  45. package/ui/dist/assets/IntelligenceView-Bk8rxP_6.js +2 -0
  46. package/ui/dist/assets/{LearningPanel-azlN9qlH.js → LearningPanel-BDHo-NOl.js} +1 -1
  47. package/ui/dist/assets/{LogsPanel-BqZlM3Gy.js → LogsPanel-Di2xx_p9.js} +1 -1
  48. package/ui/dist/assets/{McpPanel-sgkhqXjs.js → McpPanel-B4WBhj3t.js} +1 -1
  49. package/ui/dist/assets/{MemoryGraphPanel-BUIazKR7.js → MemoryGraphPanel-CVFpfV7R.js} +1 -1
  50. package/ui/dist/assets/{MemoryWikiPanel-Bupaum4q.js → MemoryWikiPanel-BnzChGK9.js} +1 -1
  51. package/ui/dist/assets/{MeshPanel-B82U1JCy.js → MeshPanel-CWHtS8I_.js} +1 -1
  52. package/ui/dist/assets/{Modal-BkPE2Vkg.js → Modal-DvJUV13H.js} +1 -1
  53. package/ui/dist/assets/{NvidiaPanel-C9_2WQcw.js → NvidiaPanel-95hBnxjC.js} +1 -1
  54. package/ui/dist/assets/{OrganismPanel-CRgtwXCk.js → OrganismPanel-BGjBnOvs.js} +1 -1
  55. package/ui/dist/assets/{OverviewPanel-DoWSJHFa.js → OverviewPanel-Dk1Q70Se.js} +1 -1
  56. package/ui/dist/assets/{PageHeader-Du2itazq.js → PageHeader-DDrb6I8n.js} +1 -1
  57. package/ui/dist/assets/{PersonaProfilesPanel-DmfaD0xB.js → PersonaProfilesPanel-DGejWnRB.js} +1 -1
  58. package/ui/dist/assets/{PersonasPanel-DmTx0Liz.js → PersonasPanel-DxrMDBTh.js} +1 -1
  59. package/ui/dist/assets/{RecipesPanel-CFKTNOVT.js → RecipesPanel-BHRS_0kN.js} +1 -1
  60. package/ui/dist/assets/{SecurityPanel-yO--atII.js → SecurityPanel-DkfHmxvT.js} +1 -1
  61. package/ui/dist/assets/{SelfImprovePanel-D82p2MSW.js → SelfImprovePanel-DrsAjnc4.js} +1 -1
  62. package/ui/dist/assets/{SelfProposalsPanel-BVdwstVR.js → SelfProposalsPanel-BDCwQNxy.js} +1 -1
  63. package/ui/dist/assets/{SessionsPanel-DSUYjyLs.js → SessionsPanel-DZW5lmgf.js} +1 -1
  64. package/ui/dist/assets/{SessionsTab-CLzmoMtf.js → SessionsTab-iqQ8K-uq.js} +1 -1
  65. package/ui/dist/assets/{SettingsPanel-W5fWLJ0g.js → SettingsPanel-FcAq4i6v.js} +1 -1
  66. package/ui/dist/assets/SettingsView-BgpaJGqz.js +2 -0
  67. package/ui/dist/assets/{SkeletonLoader-Cay6smRV.js → SkeletonLoader-CRDu3-6Q.js} +1 -1
  68. package/ui/dist/assets/{SkillsPanel-suV_28aX.js → SkillsPanel-C3ZK2ERk.js} +1 -1
  69. package/ui/dist/assets/{SomaView-CxFduOGQ.js → SomaView-BVQByvrz.js} +1 -1
  70. package/ui/dist/assets/{StatCard-BYg3n_oA.js → StatCard-Bc0P7ZAM.js} +1 -1
  71. package/ui/dist/assets/{StatusBadge-B3u_B28U.js → StatusBadge-CD5xxHjI.js} +1 -1
  72. package/ui/dist/assets/{Tabs-Cj8gIxMR.js → Tabs-CpP274I1.js} +1 -1
  73. package/ui/dist/assets/{TeamsPanel-b4Qx5KTi.js → TeamsPanel-Bko8t-L1.js} +1 -1
  74. package/ui/dist/assets/{TelemetryPanel-CjN5WH2K.js → TelemetryPanel-BZQTTim6.js} +1 -1
  75. package/ui/dist/assets/TitanCanvas-CZ9SbUlP.js +1072 -0
  76. package/ui/dist/assets/{ToolsView-CGRNYkub.js → ToolsView-CFIz5Zz6.js} +2 -2
  77. package/ui/dist/assets/{Tooltip-Ce8POzUQ.js → Tooltip-CVeAK0ZZ.js} +1 -1
  78. package/ui/dist/assets/{TraceViewer-DUv0vZRq.js → TraceViewer-CTI2-r0i.js} +1 -1
  79. package/ui/dist/assets/{TrainingPanel-DAO9LQM-.js → TrainingPanel-BFec-615.js} +1 -1
  80. package/ui/dist/assets/{VoiceOverlay-CF0IwN0R.js → VoiceOverlay-LSUKsCN7.js} +1 -1
  81. package/ui/dist/assets/{VramPanel-Cp4H9Xxl.js → VramPanel-CJneTxxb.js} +1 -1
  82. package/ui/dist/assets/{WatchView-NFUf82q9.js → WatchView-DsFOe9ns.js} +1 -1
  83. package/ui/dist/assets/{WorkTab-B8ww1X4Y.js → WorkTab-BzpzUgiy.js} +1 -1
  84. package/ui/dist/assets/{WorkflowsPanel-BQMwhjCa.js → WorkflowsPanel-CiM_4Cb3.js} +1 -1
  85. package/ui/dist/assets/{arrow-left-DIFIuRsJ.js → arrow-left-B5CXNaIk.js} +1 -1
  86. package/ui/dist/assets/{briefcase-CAxB_U-8.js → briefcase-CAsOYhH7.js} +1 -1
  87. package/ui/dist/assets/{chart-column-C2lpYDcZ.js → chart-column-C5TUkViO.js} +1 -1
  88. package/ui/dist/assets/{check-F7ivHP88.js → check-CgAgq8JZ.js} +1 -1
  89. package/ui/dist/assets/{chevron-down-DKVRFEpi.js → chevron-down-HieTTETf.js} +1 -1
  90. package/ui/dist/assets/{chevron-right-BHfhTPvA.js → chevron-right-DonjB84W.js} +1 -1
  91. package/ui/dist/assets/{chevron-up-BpBI-ayg.js → chevron-up-BSRDbuA0.js} +1 -1
  92. package/ui/dist/assets/{circle-check-big-OIVZenlI.js → circle-check-big-DzuchC-4.js} +1 -1
  93. package/ui/dist/assets/{clock-Dy-1wXpd.js → clock-DaAt7jPK.js} +1 -1
  94. package/ui/dist/assets/{dollar-sign-BOplOhSO.js → dollar-sign-CHo-CSz7.js} +1 -1
  95. package/ui/dist/assets/{download-DEW3EXPL.js → download-CLzgNsVN.js} +1 -1
  96. package/ui/dist/assets/{external-link-B5GBq_-Q.js → external-link-Dq-gXAWi.js} +1 -1
  97. package/ui/dist/assets/{eye-off-r0IR_a6i.js → eye-off-CEY2IGhA.js} +1 -1
  98. package/ui/dist/assets/{folder-DMH6qMKv.js → folder-BenNQiQE.js} +1 -1
  99. package/ui/dist/assets/{funnel-s8SIZOyD.js → funnel-Dxmik6Ls.js} +1 -1
  100. package/ui/dist/assets/{git-branch-CF21vxD4.js → git-branch-C7Hc1_hh.js} +1 -1
  101. package/ui/dist/assets/{index-C0phyJAs.js → index-BNaZESGb.js} +2 -2
  102. package/ui/dist/assets/{layers-CEQphI5n.js → layers-DvR5qUxC.js} +1 -1
  103. package/ui/dist/assets/{legacy-ByhfvWA8.js → legacy-Qcc8euuf.js} +1 -1
  104. package/ui/dist/assets/{lightbulb-BdEQy6pp.js → lightbulb-ZbEGfVpn.js} +1 -1
  105. package/ui/dist/assets/{list-todo-CCn69fwy.js → list-todo-BFKOVzxo.js} +1 -1
  106. package/ui/dist/assets/{loader-circle-BKWnHOOK.js → loader-circle-CpcxHf52.js} +1 -1
  107. package/ui/dist/assets/{network-Comy2P5e.js → network-xmc8fyEg.js} +1 -1
  108. package/ui/dist/assets/{pause-B7GkpUTy.js → pause-DzuexF_t.js} +1 -1
  109. package/ui/dist/assets/{play-D1ZUvkUu.js → play-BMx5X2Kb.js} +1 -1
  110. package/ui/dist/assets/{plug-CjDI6g3D.js → plug-BII-8v4F.js} +1 -1
  111. package/ui/dist/assets/{plus-Dzqxvvmw.js → plus-Bc7CG41K.js} +1 -1
  112. package/ui/dist/assets/{proxy-C_6aztI8.js → proxy-DkTAczox.js} +1 -1
  113. package/ui/dist/assets/{rotate-ccw-BYSbVQMg.js → rotate-ccw-Bdg5wieT.js} +1 -1
  114. package/ui/dist/assets/{save-SKty-KeV.js → save-DPl93H0B.js} +1 -1
  115. package/ui/dist/assets/{search-C7jlYumz.js → search-Cj8ATqRF.js} +1 -1
  116. package/ui/dist/assets/{send-ChCVjnJo.js → send-DtssvT0u.js} +1 -1
  117. package/ui/dist/assets/{shield-check-D6grw3SN.js → shield-check-pgaUFXYM.js} +1 -1
  118. package/ui/dist/assets/{target-DBf96i46.js → target-CUujoxoz.js} +1 -1
  119. package/ui/dist/assets/{terminal-zMgZPhJl.js → terminal-CIe7bf1C.js} +1 -1
  120. package/ui/dist/assets/{toggle-right-BmvgI5zG.js → toggle-right-Du7JxqNs.js} +1 -1
  121. package/ui/dist/assets/{trash-2-t6zH5HEI.js → trash-2-qJTrlvIF.js} +1 -1
  122. package/ui/dist/assets/{trending-up-D3SLYrNk.js → trending-up-zdi70rCI.js} +1 -1
  123. package/ui/dist/assets/{trophy-TA1dWZTZ.js → trophy-BQWdmxE-.js} +1 -1
  124. package/ui/dist/assets/{users-CUNoFrKL.js → users-BZoer_xv.js} +1 -1
  125. package/ui/dist/assets/{wrench-CLSuUa12.js → wrench-zr5TlUlx.js} +1 -1
  126. package/ui/dist/index.html +1 -1
  127. package/ui/dist/sw.js +1 -1
  128. package/ui/dist/assets/InfraView-BYB9Fnf4.js +0 -2
  129. package/ui/dist/assets/IntelligenceView-DowcYNie.js +0 -2
  130. package/ui/dist/assets/SettingsView-BR5yH0Ud.js +0 -2
  131. package/ui/dist/assets/TitanCanvas-CS7kRvsF.js +0 -1072
@@ -338,23 +338,98 @@ function verifyVerify(input) {
338
338
  confidence: input.spawnResult.confidence ?? 0.7
339
339
  };
340
340
  }
341
+ function llmJudgeEnabled() {
342
+ const env = (process.env.TITAN_LLM_JUDGE_VERIFY ?? "").toLowerCase().trim();
343
+ if (env === "0" || env === "false" || env === "no" || env === "off") return false;
344
+ return true;
345
+ }
346
+ async function llmJudgeVerify(input, kindResult) {
347
+ if (input.kind === "verify") return kindResult;
348
+ if (!llmJudgeEnabled()) return kindResult;
349
+ try {
350
+ const { spawnSubAgent } = await import("./subAgent.js");
351
+ const reasoning = (input.spawnResult.reasoning || input.spawnResult.rawResponse || "").slice(0, 1800);
352
+ const artifactNote = input.spawnResult.artifacts?.length ? `
353
+ Artifacts produced: ${input.spawnResult.artifacts.map((a) => `${a.type}:${a.ref}`).join(", ")}` : "";
354
+ const judgePrompt = [
355
+ `You are a strict verification judge. Your ONE job is to decide whether the work below actually fulfilled the subtask intent.`,
356
+ ``,
357
+ `Subtask:`,
358
+ ` title: ${input.subtask.title}`,
359
+ ` description: ${input.subtask.description}`,
360
+ ``,
361
+ `Work produced (truncated to 1.8k chars):`,
362
+ reasoning,
363
+ artifactNote,
364
+ ``,
365
+ `Per-kind verifier ('${input.kind}') already passed with reason: ${kindResult.reason}`,
366
+ ``,
367
+ `Your job: does this work ACTUALLY address the subtask, or did it surface-pass without delivering?`,
368
+ ``,
369
+ `Common surface-pass failure modes to catch:`,
370
+ ` - Length OK but content is generic / vague / doesn't address the specific subtask`,
371
+ ` - Code compiles but doesn't do what was asked`,
372
+ ` - Research has citations but missed the actual question`,
373
+ ` - Report has the keywords but no real conclusion`,
374
+ ``,
375
+ `Return STRICT JSON on a single line: {"passed": true|false, "reason": "<\u2264140 chars why>"}.`,
376
+ `No markdown. No prose before or after the JSON.`
377
+ ].join("\n");
378
+ const judgeResult = await spawnSubAgent({
379
+ name: "llm-judge",
380
+ task: judgePrompt,
381
+ tier: "fast",
382
+ maxRounds: 1
383
+ });
384
+ const raw = (judgeResult.content || "").trim();
385
+ const jsonStart = raw.indexOf("{");
386
+ const jsonEnd = raw.lastIndexOf("}");
387
+ if (jsonStart < 0 || jsonEnd <= jsonStart) {
388
+ logger.info(COMPONENT, `LLM judge returned non-JSON, deferring to per-kind verdict`);
389
+ return kindResult;
390
+ }
391
+ const parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
392
+ if (typeof parsed.passed !== "boolean") return kindResult;
393
+ if (parsed.passed) return kindResult;
394
+ const judgeReason = typeof parsed.reason === "string" && parsed.reason.trim().length > 0 ? parsed.reason.trim().slice(0, 200) : "LLM judge said no without a reason";
395
+ logger.info(COMPONENT, `LLM judge OVERRIDE: per-kind passed but judge said fail \u2014 ${judgeReason}`);
396
+ return {
397
+ passed: false,
398
+ reason: `LLM judge: ${judgeReason}`,
399
+ verifier: `${input.kind}+llm-judge`,
400
+ confidence: kindResult.confidence,
401
+ details: `per-kind '${input.kind}' passed (${kindResult.reason}) but judge disagreed`
402
+ };
403
+ } catch (err) {
404
+ logger.warn(COMPONENT, `LLM judge threw (deferring to per-kind): ${err.message}`);
405
+ return kindResult;
406
+ }
407
+ }
341
408
  async function verifyByKind(input) {
409
+ let kindResult;
342
410
  try {
343
411
  switch (input.kind) {
344
412
  case "code":
345
- return await verifyCode(input);
413
+ kindResult = await verifyCode(input);
414
+ break;
346
415
  case "research":
347
- return verifyResearch(input);
416
+ kindResult = verifyResearch(input);
417
+ break;
348
418
  case "write":
349
- return await verifyWrite(input);
419
+ kindResult = await verifyWrite(input);
420
+ break;
350
421
  case "analysis":
351
- return verifyAnalysis(input);
422
+ kindResult = verifyAnalysis(input);
423
+ break;
352
424
  case "verify":
353
- return verifyVerify(input);
425
+ kindResult = verifyVerify(input);
426
+ break;
354
427
  case "shell":
355
- return await verifyShell(input);
428
+ kindResult = await verifyShell(input);
429
+ break;
356
430
  case "report":
357
- return verifyReport(input);
431
+ kindResult = verifyReport(input);
432
+ break;
358
433
  default:
359
434
  return { passed: false, reason: `Unknown kind: ${input.kind}`, verifier: "dispatch" };
360
435
  }
@@ -366,6 +441,10 @@ async function verifyByKind(input) {
366
441
  verifier: `${input.kind}:error`
367
442
  };
368
443
  }
444
+ if (kindResult.passed) {
445
+ return await llmJudgeVerify(input, kindResult);
446
+ }
447
+ return kindResult;
369
448
  }
370
449
  function readArtifactContent(path, maxBytes = 5e4) {
371
450
  try {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/agent/verifier.ts"],"sourcesContent":["/**\n * TITAN — Verifier (v4.10.0-local, Phase A)\n *\n * Per-kind verification that a subtask is actually done, not just\n * \"the LLM emitted 200 chars and called it a day.\" Returns a\n * VerificationResult the driver uses to decide: advance to the next\n * subtask (passed), retry with fallback (failed), or escalate to human\n * (blocked on clarification).\n *\n * Per-kind contracts:\n * code — run typecheck + build in workspace; all green\n * research — ≥200 chars, ≥2 source markers, no \"I don't know\"\n * write — spawn Analyst with rubric, require score ≥0.7\n * analysis — response contains structured output meeting schema\n * verify — nested verifier of the thing it claims to verify\n * shell — exit code 0 and (if pattern provided) stdout matches\n * report — ≥500 chars, keywords: \"goal\"/\"outcome\"/\"artifacts\"\n */\nimport { existsSync, readFileSync } from 'fs';\nimport { promisify } from 'util';\nimport { exec as execCb } from 'child_process';\nimport logger from '../utils/logger.js';\nimport type { SubtaskKind } from './subtaskTaxonomy.js';\nimport type { StructuredSpawnResult } from './structuredSpawnTypes.js';\nimport type { Subtask } from './goals.js';\n\nconst exec = promisify(execCb);\nconst COMPONENT = 'Verifier';\n\nexport interface VerificationInput {\n kind: SubtaskKind;\n subtask: Subtask;\n spawnResult: StructuredSpawnResult;\n /**\n * Workspace for code verifications — defaults to repo root.\n * For staged writes, this is the staging directory.\n */\n workspace?: string;\n /**\n * Optional expected-output regex for shell verifications.\n */\n expectedOutputPattern?: string;\n}\n\nexport interface VerificationResult {\n passed: boolean;\n reason: string;\n verifier: string;\n confidence?: number;\n /** Files/URLs/facts produced. */\n artifacts?: string[];\n /** Stderr/stdout snippets for code verifications — helpful in UI. */\n details?: string;\n}\n\n// ── Generic bail-out check (runs before per-kind) ────────────────\n\nfunction hasGiveUpPhrase(text: string): boolean {\n const lowered = text.toLowerCase();\n const giveups = [\n \"i don't have a specific task\",\n 'no specific task to act on',\n \"i don't know what to do\",\n 'not enough information',\n 'cannot complete without',\n 'unable to determine',\n \"i can't proceed\",\n ];\n return giveups.some(g => lowered.includes(g));\n}\n\n// v4.10.0-local fix: Detect \"thinking\" prose that indicates the specialist\n// is starting work but didn't follow JSON output instructions. These patterns\n// (\"Now let me check...\", \"Let me analyze...\") should trigger retry, not block.\nfunction hasThinkingPattern(text: string): boolean {\n const trimmed = text.trim();\n const patterns = [\n /^now let me /i,\n /^let me /i,\n /^i will /i,\n /^i'll /i,\n /^first,? let me /i,\n /^ok, let me /i,\n /^okay, let me /i,\n /^sure,? let me /i,\n /^alright,? let me /i,\n ];\n return patterns.some(p => p.test(trimmed));\n}\n\n// ── Per-kind verifiers ───────────────────────────────────────────\n\nasync function verifyCode(input: VerificationInput): Promise<VerificationResult> {\n const workspace = input.workspace || process.cwd();\n // Quick fail: were artifacts actually produced?\n const fileArtifacts = input.spawnResult.artifacts.filter(a => a.type === 'file').map(a => a.ref);\n if (fileArtifacts.length === 0) {\n return {\n passed: false,\n reason: 'No file artifacts reported by specialist',\n verifier: 'verifyCode',\n };\n }\n // Files actually exist?\n const missing = fileArtifacts.filter(p => !existsSync(p));\n if (missing.length > 0) {\n return {\n passed: false,\n reason: `Claimed files don't exist: ${missing.join(', ')}`,\n verifier: 'verifyCode',\n details: `Specialist claimed ${fileArtifacts.length} files but ${missing.length} are missing on disk.`,\n };\n }\n // Typecheck\n try {\n // Short timeout — typecheck usually 5-20s\n const { stdout: tcOut, stderr: tcErr } = await exec('npm run typecheck', {\n cwd: workspace,\n timeout: 120_000,\n maxBuffer: 10 * 1024 * 1024,\n });\n const tcOutput = (tcOut || '') + (tcErr || '');\n if (/error TS\\d+:/i.test(tcOutput) || /Found \\d+ error/i.test(tcOutput)) {\n return {\n passed: false,\n reason: 'TypeScript errors in workspace',\n verifier: 'verifyCode',\n details: tcOutput.slice(-2000),\n artifacts: fileArtifacts,\n };\n }\n } catch (err) {\n // typecheck failed non-zero — extract errors\n const msg = (err as { stdout?: string; stderr?: string; message: string }).stdout\n || (err as { stderr?: string }).stderr\n || (err as Error).message;\n return {\n passed: false,\n reason: 'npm run typecheck failed',\n verifier: 'verifyCode',\n details: String(msg).slice(-2000),\n artifacts: fileArtifacts,\n };\n }\n return {\n passed: true,\n reason: `Typecheck passed; ${fileArtifacts.length} file(s) exist`,\n verifier: 'verifyCode',\n confidence: 0.9,\n artifacts: fileArtifacts,\n };\n}\n\nfunction verifyResearch(input: VerificationInput): VerificationResult {\n const text = input.spawnResult.reasoning || input.spawnResult.rawResponse;\n if (hasGiveUpPhrase(text)) {\n return {\n passed: false,\n reason: \"Specialist gave up (give-up phrase detected)\",\n verifier: 'verifyResearch',\n };\n }\n // v4.10.0-local fix: catch thinking patterns that indicate JSON parsing failed\n if (hasThinkingPattern(text)) {\n return {\n passed: false,\n reason: \"Specialist returned thinking prose instead of structured JSON — needs retry\",\n verifier: 'verifyResearch',\n details: `Raw (200 chars): ${text.slice(0, 200)}`,\n };\n }\n // v4.10.0-local (post-deploy, Fix D): confidence+artifact escape hatch.\n // High-confidence done responses with ≥1 concrete artifact pass even\n // without prose markers. Prevents terse-but-correct specialists (e.g.\n // \"Done. 5 sources saved to memory.\") from looping on verification.\n // Gated on artifact count — pure confidence would let hallucinating\n // specialists self-certify.\n if (input.spawnResult.status === 'done'\n && input.spawnResult.confidence >= 0.85\n && (input.spawnResult.artifacts?.length ?? 0) >= 1) {\n return {\n passed: true,\n reason: `High confidence (${input.spawnResult.confidence.toFixed(2)}) + ${input.spawnResult.artifacts.length} artifact(s) — confidence-tier pass`,\n verifier: 'verifyResearch',\n confidence: input.spawnResult.confidence * 0.95,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n // v4.10.0-local polish: lenient short-form path. Internal research\n // goals (like \"check local tool output\") often produce 100-200 char\n // responses that are still valid — the specialist ran the right tool\n // and returned a terse finding. Require markers OR internal artifacts.\n if (text.length < 100) {\n return {\n passed: false,\n reason: `Response too short (${text.length} chars, need ≥100)`,\n verifier: 'verifyResearch',\n };\n }\n // Count source markers: URLs, [1]-style refs, \"source:\", \"according to\"\n const urlCount = (text.match(/https?:\\/\\/[^\\s)]+/g) || []).length;\n const refCount = (text.match(/\\[\\d+\\]/g) || []).length;\n const sourceWords = (text.match(/\\b(source|according to|per the|reference|from the|based on):/gi) || []).length;\n const toolFindings = (text.match(/\\b(found|returned|reports?|shows?|indicates?|displays?)\\b/gi) || []).length;\n const markers = urlCount + refCount + sourceWords;\n const artifactCount = input.spawnResult.artifacts.length;\n\n // Path A: short response with artifact + tool-finding language\n if (text.length < 200) {\n if (artifactCount >= 1 && toolFindings >= 1 && input.spawnResult.confidence >= 0.7) {\n return {\n passed: true,\n reason: `Concise research ${text.length} chars, ${artifactCount} artifact(s), confidence ${input.spawnResult.confidence.toFixed(2)} — lenient pass`,\n verifier: 'verifyResearch',\n confidence: input.spawnResult.confidence * 0.85,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n return {\n passed: false,\n reason: `Response too short (${text.length} chars, need ≥200 OR artifact+tool-finding+high-confidence)`,\n verifier: 'verifyResearch',\n };\n }\n // Path B: longer response needs source markers\n if (markers < 2 && artifactCount < 1) {\n return {\n passed: false,\n reason: `Insufficient source markers (${markers}, need ≥2 URLs/refs/source phrases, or ≥1 artifact)`,\n verifier: 'verifyResearch',\n details: `urls=${urlCount} refs=${refCount} sourcewords=${sourceWords}`,\n };\n }\n return {\n passed: true,\n reason: `${markers} source markers, ${artifactCount} artifacts, ${text.length} chars`,\n verifier: 'verifyResearch',\n confidence: 0.8,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n}\n\nasync function verifyWrite(input: VerificationInput): Promise<VerificationResult> {\n const text = input.spawnResult.reasoning || input.spawnResult.rawResponse;\n if (hasGiveUpPhrase(text)) {\n return { passed: false, reason: 'Specialist gave up', verifier: 'verifyWrite' };\n }\n // v4.10.0-local fix: catch thinking patterns that indicate JSON parsing failed\n if (hasThinkingPattern(text)) {\n return {\n passed: false,\n reason: 'Specialist returned thinking prose instead of structured JSON — needs retry',\n verifier: 'verifyWrite',\n details: `Raw (200 chars): ${text.slice(0, 200)}`,\n };\n }\n // v4.10.0-local (post-deploy, Fix D): confidence+artifact escape hatch.\n // See verifyResearch for rationale. Gated on artifact count.\n if (input.spawnResult.status === 'done'\n && input.spawnResult.confidence >= 0.85\n && (input.spawnResult.artifacts?.length ?? 0) >= 1) {\n return {\n passed: true,\n reason: `High confidence (${input.spawnResult.confidence.toFixed(2)}) + ${input.spawnResult.artifacts.length} artifact(s) — confidence-tier pass`,\n verifier: 'verifyWrite',\n confidence: input.spawnResult.confidence * 0.95,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n if (text.length < 100) {\n return {\n passed: false,\n reason: `Draft too short (${text.length} chars, need ≥100)`,\n verifier: 'verifyWrite',\n };\n }\n // Rubric-based check: use spawn confidence + basic heuristics\n // (Full LLM-rubric check deferred — driver can spawn Analyst to review\n // via the structured-spawn path; here we do a fast local sanity check.)\n const confidence = input.spawnResult.confidence ?? 0.5;\n if (confidence < 0.6) {\n return {\n passed: false,\n reason: `Self-reported confidence ${confidence.toFixed(2)} below 0.6`,\n verifier: 'verifyWrite',\n };\n }\n return {\n passed: true,\n reason: `Draft ${text.length} chars, confidence ${confidence.toFixed(2)}`,\n verifier: 'verifyWrite',\n confidence,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n}\n\nfunction verifyAnalysis(input: VerificationInput): VerificationResult {\n const text = input.spawnResult.reasoning || input.spawnResult.rawResponse;\n if (hasGiveUpPhrase(text)) {\n return { passed: false, reason: 'Specialist gave up', verifier: 'verifyAnalysis' };\n }\n // v4.10.0-local fix: catch thinking patterns that indicate JSON parsing failed\n if (hasThinkingPattern(text)) {\n return {\n passed: false,\n reason: 'Specialist returned thinking prose instead of structured JSON — needs retry',\n verifier: 'verifyAnalysis',\n details: `Raw (200 chars): ${text.slice(0, 200)}`,\n };\n }\n // v4.10.0-local (post-deploy, Fix D): confidence+artifact escape hatch.\n // Parallel to verifyResearch/verifyWrite. Sits below the existing\n // ≥3-artifact tier but catches the ≥0.85-confidence + ≥1-artifact case\n // that the stricter tier misses (e.g. a single bundle summary file).\n if (input.spawnResult.status === 'done'\n && input.spawnResult.confidence >= 0.85\n && (input.spawnResult.artifacts?.length ?? 0) >= 1) {\n return {\n passed: true,\n reason: `High confidence (${input.spawnResult.confidence.toFixed(2)}) + ${input.spawnResult.artifacts.length} artifact(s) — confidence-tier pass`,\n verifier: 'verifyAnalysis',\n confidence: input.spawnResult.confidence * 0.95,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n // v4.10.0-local polish (post-deploy): analysis verification now has\n // three tiers. Added an ARTIFACT tier to catch the common case where\n // the subtask was misclassified as \"analysis\" but the specialist\n // actually produced concrete artifacts (files, URLs, memory entries).\n // Previously those runs would ping-pong on verification forever\n // because the reasoning field was terse but the work was real.\n //\n // ARTIFACT tier: ≥3 concrete artifacts + status=done + confidence ≥ 0.7.\n // STRICT tier: needs reasoning markers OR bulleted list OR ≥200 chars + structure.\n // LENIENT tier: ≥80 chars AND status=done AND confidence ≥ 0.7.\n const artifactCount = input.spawnResult.artifacts?.length ?? 0;\n if (artifactCount >= 3 && input.spawnResult.status === 'done' && input.spawnResult.confidence >= 0.7) {\n return {\n passed: true,\n reason: `Analysis produced ${artifactCount} artifact(s), confidence ${input.spawnResult.confidence.toFixed(2)} — artifact-tier pass`,\n verifier: 'verifyAnalysis',\n confidence: input.spawnResult.confidence * 0.9,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n\n const hasReasoningMarker = /\\b(conclusion|because|therefore|thus|hence|as a result|this means|indicates|suggests|implies)\\b/i.test(text);\n const bulletCount = (text.match(/^\\s*[-*+]\\s+/gm) || []).length;\n const numericCount = (text.match(/\\b\\d+(?:\\.\\d+)?(?:%|\\s*(?:chars?|ms|s|m|ticks?|patterns?))?\\b/g) || []).length;\n const hasStructure = hasReasoningMarker || bulletCount >= 2 || numericCount >= 2;\n\n if (text.length < 80) {\n return {\n passed: false,\n reason: `Analysis too short (${text.length} chars, need ≥80)`,\n verifier: 'verifyAnalysis',\n };\n }\n\n // Lenient path: short-but-confident responses\n if (text.length < 200 && input.spawnResult.confidence >= 0.7 && input.spawnResult.status === 'done') {\n return {\n passed: true,\n reason: `Analysis ${text.length} chars, high confidence (${input.spawnResult.confidence.toFixed(2)}) — lenient pass`,\n verifier: 'verifyAnalysis',\n confidence: input.spawnResult.confidence * 0.85,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n\n // Strict path: longer responses need structural markers\n if (!hasStructure) {\n return {\n passed: false,\n reason: 'No reasoning markers, structured list, or numeric evidence found',\n verifier: 'verifyAnalysis',\n };\n }\n return {\n passed: true,\n reason: `Analysis ${text.length} chars with reasoning structure (markers=${hasReasoningMarker} bullets=${bulletCount} metrics=${numericCount})`,\n verifier: 'verifyAnalysis',\n confidence: 0.8,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n}\n\nasync function verifyShell(input: VerificationInput): Promise<VerificationResult> {\n // Shell subtask's \"verification\" is: did the spawn_result indicate success?\n // Structured spawn already captures status. Here we add: if we have an\n // expectedOutputPattern, match it against the spawn's raw response.\n if (input.spawnResult.status !== 'done') {\n return {\n passed: false,\n reason: `Spawn status = ${input.spawnResult.status}`,\n verifier: 'verifyShell',\n };\n }\n if (input.expectedOutputPattern) {\n const re = new RegExp(input.expectedOutputPattern);\n if (!re.test(input.spawnResult.rawResponse)) {\n return {\n passed: false,\n reason: `Output didn't match expected pattern: ${input.expectedOutputPattern}`,\n verifier: 'verifyShell',\n details: input.spawnResult.rawResponse.slice(0, 500),\n };\n }\n }\n return {\n passed: true,\n reason: 'Shell command returned success',\n verifier: 'verifyShell',\n confidence: 0.85,\n };\n}\n\nfunction verifyReport(input: VerificationInput): VerificationResult {\n const text = input.spawnResult.reasoning || input.spawnResult.rawResponse;\n if (text.length < 500) {\n return {\n passed: false,\n reason: `Report too short (${text.length} chars, need ≥500)`,\n verifier: 'verifyReport',\n };\n }\n const keywords = ['goal', 'outcome', 'artifact'];\n const missing = keywords.filter(k => !text.toLowerCase().includes(k));\n if (missing.length > 1) {\n return {\n passed: false,\n reason: `Report missing key sections: ${missing.join(', ')}`,\n verifier: 'verifyReport',\n };\n }\n return {\n passed: true,\n reason: `Report ${text.length} chars, all sections present`,\n verifier: 'verifyReport',\n confidence: 0.8,\n };\n}\n\n// verify-kind subtasks are meta — they recursively verify whatever the\n// spawn claims to verify. For now we trust the spawn's status.\nfunction verifyVerify(input: VerificationInput): VerificationResult {\n if (input.spawnResult.status !== 'done') {\n return { passed: false, reason: `verify spawn status=${input.spawnResult.status}`, verifier: 'verifyVerify' };\n }\n if (input.spawnResult.confidence !== undefined && input.spawnResult.confidence < 0.6) {\n return {\n passed: false,\n reason: `verify-of-verify confidence too low (${input.spawnResult.confidence.toFixed(2)})`,\n verifier: 'verifyVerify',\n };\n }\n return {\n passed: true,\n reason: 'verify subtask reported done with confidence ≥ 0.6',\n verifier: 'verifyVerify',\n confidence: input.spawnResult.confidence ?? 0.7,\n };\n}\n\n// ── Dispatch ─────────────────────────────────────────────────────\n\nexport async function verifyByKind(input: VerificationInput): Promise<VerificationResult> {\n try {\n switch (input.kind) {\n case 'code': return await verifyCode(input);\n case 'research': return verifyResearch(input);\n case 'write': return await verifyWrite(input);\n case 'analysis': return verifyAnalysis(input);\n case 'verify': return verifyVerify(input);\n case 'shell': return await verifyShell(input);\n case 'report': return verifyReport(input);\n default:\n return { passed: false, reason: `Unknown kind: ${input.kind}`, verifier: 'dispatch' };\n }\n } catch (err) {\n logger.warn(COMPONENT, `Verifier threw: ${(err as Error).message}`);\n return {\n passed: false,\n reason: `Verifier error: ${(err as Error).message}`,\n verifier: `${input.kind}:error`,\n };\n }\n}\n\n// ── Utility: read a file's content (used by higher-level UI for the driver panel) ──\nexport function readArtifactContent(path: string, maxBytes = 50_000): string | null {\n try {\n if (!existsSync(path)) return null;\n const content = readFileSync(path, 'utf-8');\n return content.length > maxBytes ? content.slice(0, maxBytes) + '\\n... [truncated]' : content;\n } catch { return null; }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/agent/verifier.ts"],"sourcesContent":["/**\n * TITAN — Verifier (v4.10.0-local, Phase A)\n *\n * Per-kind verification that a subtask is actually done, not just\n * \"the LLM emitted 200 chars and called it a day.\" Returns a\n * VerificationResult the driver uses to decide: advance to the next\n * subtask (passed), retry with fallback (failed), or escalate to human\n * (blocked on clarification).\n *\n * Per-kind contracts:\n * code — run typecheck + build in workspace; all green\n * research — ≥200 chars, ≥2 source markers, no \"I don't know\"\n * write — spawn Analyst with rubric, require score ≥0.7\n * analysis — response contains structured output meeting schema\n * verify — nested verifier of the thing it claims to verify\n * shell — exit code 0 and (if pattern provided) stdout matches\n * report — ≥500 chars, keywords: \"goal\"/\"outcome\"/\"artifacts\"\n */\nimport { existsSync, readFileSync } from 'fs';\nimport { promisify } from 'util';\nimport { exec as execCb } from 'child_process';\nimport logger from '../utils/logger.js';\nimport type { SubtaskKind } from './subtaskTaxonomy.js';\nimport type { StructuredSpawnResult } from './structuredSpawnTypes.js';\nimport type { Subtask } from './goals.js';\n\nconst exec = promisify(execCb);\nconst COMPONENT = 'Verifier';\n\nexport interface VerificationInput {\n kind: SubtaskKind;\n subtask: Subtask;\n spawnResult: StructuredSpawnResult;\n /**\n * Workspace for code verifications — defaults to repo root.\n * For staged writes, this is the staging directory.\n */\n workspace?: string;\n /**\n * Optional expected-output regex for shell verifications.\n */\n expectedOutputPattern?: string;\n}\n\nexport interface VerificationResult {\n passed: boolean;\n reason: string;\n verifier: string;\n confidence?: number;\n /** Files/URLs/facts produced. */\n artifacts?: string[];\n /** Stderr/stdout snippets for code verifications — helpful in UI. */\n details?: string;\n}\n\n// ── Generic bail-out check (runs before per-kind) ────────────────\n\nfunction hasGiveUpPhrase(text: string): boolean {\n const lowered = text.toLowerCase();\n const giveups = [\n \"i don't have a specific task\",\n 'no specific task to act on',\n \"i don't know what to do\",\n 'not enough information',\n 'cannot complete without',\n 'unable to determine',\n \"i can't proceed\",\n ];\n return giveups.some(g => lowered.includes(g));\n}\n\n// v4.10.0-local fix: Detect \"thinking\" prose that indicates the specialist\n// is starting work but didn't follow JSON output instructions. These patterns\n// (\"Now let me check...\", \"Let me analyze...\") should trigger retry, not block.\nfunction hasThinkingPattern(text: string): boolean {\n const trimmed = text.trim();\n const patterns = [\n /^now let me /i,\n /^let me /i,\n /^i will /i,\n /^i'll /i,\n /^first,? let me /i,\n /^ok, let me /i,\n /^okay, let me /i,\n /^sure,? let me /i,\n /^alright,? let me /i,\n ];\n return patterns.some(p => p.test(trimmed));\n}\n\n// ── Per-kind verifiers ───────────────────────────────────────────\n\nasync function verifyCode(input: VerificationInput): Promise<VerificationResult> {\n const workspace = input.workspace || process.cwd();\n // Quick fail: were artifacts actually produced?\n const fileArtifacts = input.spawnResult.artifacts.filter(a => a.type === 'file').map(a => a.ref);\n if (fileArtifacts.length === 0) {\n return {\n passed: false,\n reason: 'No file artifacts reported by specialist',\n verifier: 'verifyCode',\n };\n }\n // Files actually exist?\n const missing = fileArtifacts.filter(p => !existsSync(p));\n if (missing.length > 0) {\n return {\n passed: false,\n reason: `Claimed files don't exist: ${missing.join(', ')}`,\n verifier: 'verifyCode',\n details: `Specialist claimed ${fileArtifacts.length} files but ${missing.length} are missing on disk.`,\n };\n }\n // Typecheck\n try {\n // Short timeout — typecheck usually 5-20s\n const { stdout: tcOut, stderr: tcErr } = await exec('npm run typecheck', {\n cwd: workspace,\n timeout: 120_000,\n maxBuffer: 10 * 1024 * 1024,\n });\n const tcOutput = (tcOut || '') + (tcErr || '');\n if (/error TS\\d+:/i.test(tcOutput) || /Found \\d+ error/i.test(tcOutput)) {\n return {\n passed: false,\n reason: 'TypeScript errors in workspace',\n verifier: 'verifyCode',\n details: tcOutput.slice(-2000),\n artifacts: fileArtifacts,\n };\n }\n } catch (err) {\n // typecheck failed non-zero — extract errors\n const msg = (err as { stdout?: string; stderr?: string; message: string }).stdout\n || (err as { stderr?: string }).stderr\n || (err as Error).message;\n return {\n passed: false,\n reason: 'npm run typecheck failed',\n verifier: 'verifyCode',\n details: String(msg).slice(-2000),\n artifacts: fileArtifacts,\n };\n }\n return {\n passed: true,\n reason: `Typecheck passed; ${fileArtifacts.length} file(s) exist`,\n verifier: 'verifyCode',\n confidence: 0.9,\n artifacts: fileArtifacts,\n };\n}\n\nfunction verifyResearch(input: VerificationInput): VerificationResult {\n const text = input.spawnResult.reasoning || input.spawnResult.rawResponse;\n if (hasGiveUpPhrase(text)) {\n return {\n passed: false,\n reason: \"Specialist gave up (give-up phrase detected)\",\n verifier: 'verifyResearch',\n };\n }\n // v4.10.0-local fix: catch thinking patterns that indicate JSON parsing failed\n if (hasThinkingPattern(text)) {\n return {\n passed: false,\n reason: \"Specialist returned thinking prose instead of structured JSON — needs retry\",\n verifier: 'verifyResearch',\n details: `Raw (200 chars): ${text.slice(0, 200)}`,\n };\n }\n // v4.10.0-local (post-deploy, Fix D): confidence+artifact escape hatch.\n // High-confidence done responses with ≥1 concrete artifact pass even\n // without prose markers. Prevents terse-but-correct specialists (e.g.\n // \"Done. 5 sources saved to memory.\") from looping on verification.\n // Gated on artifact count — pure confidence would let hallucinating\n // specialists self-certify.\n if (input.spawnResult.status === 'done'\n && input.spawnResult.confidence >= 0.85\n && (input.spawnResult.artifacts?.length ?? 0) >= 1) {\n return {\n passed: true,\n reason: `High confidence (${input.spawnResult.confidence.toFixed(2)}) + ${input.spawnResult.artifacts.length} artifact(s) — confidence-tier pass`,\n verifier: 'verifyResearch',\n confidence: input.spawnResult.confidence * 0.95,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n // v4.10.0-local polish: lenient short-form path. Internal research\n // goals (like \"check local tool output\") often produce 100-200 char\n // responses that are still valid — the specialist ran the right tool\n // and returned a terse finding. Require markers OR internal artifacts.\n if (text.length < 100) {\n return {\n passed: false,\n reason: `Response too short (${text.length} chars, need ≥100)`,\n verifier: 'verifyResearch',\n };\n }\n // Count source markers: URLs, [1]-style refs, \"source:\", \"according to\"\n const urlCount = (text.match(/https?:\\/\\/[^\\s)]+/g) || []).length;\n const refCount = (text.match(/\\[\\d+\\]/g) || []).length;\n const sourceWords = (text.match(/\\b(source|according to|per the|reference|from the|based on):/gi) || []).length;\n const toolFindings = (text.match(/\\b(found|returned|reports?|shows?|indicates?|displays?)\\b/gi) || []).length;\n const markers = urlCount + refCount + sourceWords;\n const artifactCount = input.spawnResult.artifacts.length;\n\n // Path A: short response with artifact + tool-finding language\n if (text.length < 200) {\n if (artifactCount >= 1 && toolFindings >= 1 && input.spawnResult.confidence >= 0.7) {\n return {\n passed: true,\n reason: `Concise research ${text.length} chars, ${artifactCount} artifact(s), confidence ${input.spawnResult.confidence.toFixed(2)} — lenient pass`,\n verifier: 'verifyResearch',\n confidence: input.spawnResult.confidence * 0.85,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n return {\n passed: false,\n reason: `Response too short (${text.length} chars, need ≥200 OR artifact+tool-finding+high-confidence)`,\n verifier: 'verifyResearch',\n };\n }\n // Path B: longer response needs source markers\n if (markers < 2 && artifactCount < 1) {\n return {\n passed: false,\n reason: `Insufficient source markers (${markers}, need ≥2 URLs/refs/source phrases, or ≥1 artifact)`,\n verifier: 'verifyResearch',\n details: `urls=${urlCount} refs=${refCount} sourcewords=${sourceWords}`,\n };\n }\n return {\n passed: true,\n reason: `${markers} source markers, ${artifactCount} artifacts, ${text.length} chars`,\n verifier: 'verifyResearch',\n confidence: 0.8,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n}\n\nasync function verifyWrite(input: VerificationInput): Promise<VerificationResult> {\n const text = input.spawnResult.reasoning || input.spawnResult.rawResponse;\n if (hasGiveUpPhrase(text)) {\n return { passed: false, reason: 'Specialist gave up', verifier: 'verifyWrite' };\n }\n // v4.10.0-local fix: catch thinking patterns that indicate JSON parsing failed\n if (hasThinkingPattern(text)) {\n return {\n passed: false,\n reason: 'Specialist returned thinking prose instead of structured JSON — needs retry',\n verifier: 'verifyWrite',\n details: `Raw (200 chars): ${text.slice(0, 200)}`,\n };\n }\n // v4.10.0-local (post-deploy, Fix D): confidence+artifact escape hatch.\n // See verifyResearch for rationale. Gated on artifact count.\n if (input.spawnResult.status === 'done'\n && input.spawnResult.confidence >= 0.85\n && (input.spawnResult.artifacts?.length ?? 0) >= 1) {\n return {\n passed: true,\n reason: `High confidence (${input.spawnResult.confidence.toFixed(2)}) + ${input.spawnResult.artifacts.length} artifact(s) — confidence-tier pass`,\n verifier: 'verifyWrite',\n confidence: input.spawnResult.confidence * 0.95,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n if (text.length < 100) {\n return {\n passed: false,\n reason: `Draft too short (${text.length} chars, need ≥100)`,\n verifier: 'verifyWrite',\n };\n }\n // Rubric-based check: use spawn confidence + basic heuristics\n // (Full LLM-rubric check deferred — driver can spawn Analyst to review\n // via the structured-spawn path; here we do a fast local sanity check.)\n const confidence = input.spawnResult.confidence ?? 0.5;\n if (confidence < 0.6) {\n return {\n passed: false,\n reason: `Self-reported confidence ${confidence.toFixed(2)} below 0.6`,\n verifier: 'verifyWrite',\n };\n }\n return {\n passed: true,\n reason: `Draft ${text.length} chars, confidence ${confidence.toFixed(2)}`,\n verifier: 'verifyWrite',\n confidence,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n}\n\nfunction verifyAnalysis(input: VerificationInput): VerificationResult {\n const text = input.spawnResult.reasoning || input.spawnResult.rawResponse;\n if (hasGiveUpPhrase(text)) {\n return { passed: false, reason: 'Specialist gave up', verifier: 'verifyAnalysis' };\n }\n // v4.10.0-local fix: catch thinking patterns that indicate JSON parsing failed\n if (hasThinkingPattern(text)) {\n return {\n passed: false,\n reason: 'Specialist returned thinking prose instead of structured JSON — needs retry',\n verifier: 'verifyAnalysis',\n details: `Raw (200 chars): ${text.slice(0, 200)}`,\n };\n }\n // v4.10.0-local (post-deploy, Fix D): confidence+artifact escape hatch.\n // Parallel to verifyResearch/verifyWrite. Sits below the existing\n // ≥3-artifact tier but catches the ≥0.85-confidence + ≥1-artifact case\n // that the stricter tier misses (e.g. a single bundle summary file).\n if (input.spawnResult.status === 'done'\n && input.spawnResult.confidence >= 0.85\n && (input.spawnResult.artifacts?.length ?? 0) >= 1) {\n return {\n passed: true,\n reason: `High confidence (${input.spawnResult.confidence.toFixed(2)}) + ${input.spawnResult.artifacts.length} artifact(s) — confidence-tier pass`,\n verifier: 'verifyAnalysis',\n confidence: input.spawnResult.confidence * 0.95,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n // v4.10.0-local polish (post-deploy): analysis verification now has\n // three tiers. Added an ARTIFACT tier to catch the common case where\n // the subtask was misclassified as \"analysis\" but the specialist\n // actually produced concrete artifacts (files, URLs, memory entries).\n // Previously those runs would ping-pong on verification forever\n // because the reasoning field was terse but the work was real.\n //\n // ARTIFACT tier: ≥3 concrete artifacts + status=done + confidence ≥ 0.7.\n // STRICT tier: needs reasoning markers OR bulleted list OR ≥200 chars + structure.\n // LENIENT tier: ≥80 chars AND status=done AND confidence ≥ 0.7.\n const artifactCount = input.spawnResult.artifacts?.length ?? 0;\n if (artifactCount >= 3 && input.spawnResult.status === 'done' && input.spawnResult.confidence >= 0.7) {\n return {\n passed: true,\n reason: `Analysis produced ${artifactCount} artifact(s), confidence ${input.spawnResult.confidence.toFixed(2)} — artifact-tier pass`,\n verifier: 'verifyAnalysis',\n confidence: input.spawnResult.confidence * 0.9,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n\n const hasReasoningMarker = /\\b(conclusion|because|therefore|thus|hence|as a result|this means|indicates|suggests|implies)\\b/i.test(text);\n const bulletCount = (text.match(/^\\s*[-*+]\\s+/gm) || []).length;\n const numericCount = (text.match(/\\b\\d+(?:\\.\\d+)?(?:%|\\s*(?:chars?|ms|s|m|ticks?|patterns?))?\\b/g) || []).length;\n const hasStructure = hasReasoningMarker || bulletCount >= 2 || numericCount >= 2;\n\n if (text.length < 80) {\n return {\n passed: false,\n reason: `Analysis too short (${text.length} chars, need ≥80)`,\n verifier: 'verifyAnalysis',\n };\n }\n\n // Lenient path: short-but-confident responses\n if (text.length < 200 && input.spawnResult.confidence >= 0.7 && input.spawnResult.status === 'done') {\n return {\n passed: true,\n reason: `Analysis ${text.length} chars, high confidence (${input.spawnResult.confidence.toFixed(2)}) — lenient pass`,\n verifier: 'verifyAnalysis',\n confidence: input.spawnResult.confidence * 0.85,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n }\n\n // Strict path: longer responses need structural markers\n if (!hasStructure) {\n return {\n passed: false,\n reason: 'No reasoning markers, structured list, or numeric evidence found',\n verifier: 'verifyAnalysis',\n };\n }\n return {\n passed: true,\n reason: `Analysis ${text.length} chars with reasoning structure (markers=${hasReasoningMarker} bullets=${bulletCount} metrics=${numericCount})`,\n verifier: 'verifyAnalysis',\n confidence: 0.8,\n artifacts: input.spawnResult.artifacts.map(a => a.ref),\n };\n}\n\nasync function verifyShell(input: VerificationInput): Promise<VerificationResult> {\n // Shell subtask's \"verification\" is: did the spawn_result indicate success?\n // Structured spawn already captures status. Here we add: if we have an\n // expectedOutputPattern, match it against the spawn's raw response.\n if (input.spawnResult.status !== 'done') {\n return {\n passed: false,\n reason: `Spawn status = ${input.spawnResult.status}`,\n verifier: 'verifyShell',\n };\n }\n if (input.expectedOutputPattern) {\n const re = new RegExp(input.expectedOutputPattern);\n if (!re.test(input.spawnResult.rawResponse)) {\n return {\n passed: false,\n reason: `Output didn't match expected pattern: ${input.expectedOutputPattern}`,\n verifier: 'verifyShell',\n details: input.spawnResult.rawResponse.slice(0, 500),\n };\n }\n }\n return {\n passed: true,\n reason: 'Shell command returned success',\n verifier: 'verifyShell',\n confidence: 0.85,\n };\n}\n\nfunction verifyReport(input: VerificationInput): VerificationResult {\n const text = input.spawnResult.reasoning || input.spawnResult.rawResponse;\n if (text.length < 500) {\n return {\n passed: false,\n reason: `Report too short (${text.length} chars, need ≥500)`,\n verifier: 'verifyReport',\n };\n }\n const keywords = ['goal', 'outcome', 'artifact'];\n const missing = keywords.filter(k => !text.toLowerCase().includes(k));\n if (missing.length > 1) {\n return {\n passed: false,\n reason: `Report missing key sections: ${missing.join(', ')}`,\n verifier: 'verifyReport',\n };\n }\n return {\n passed: true,\n reason: `Report ${text.length} chars, all sections present`,\n verifier: 'verifyReport',\n confidence: 0.8,\n };\n}\n\n// verify-kind subtasks are meta — they recursively verify whatever the\n// spawn claims to verify. For now we trust the spawn's status.\nfunction verifyVerify(input: VerificationInput): VerificationResult {\n if (input.spawnResult.status !== 'done') {\n return { passed: false, reason: `verify spawn status=${input.spawnResult.status}`, verifier: 'verifyVerify' };\n }\n if (input.spawnResult.confidence !== undefined && input.spawnResult.confidence < 0.6) {\n return {\n passed: false,\n reason: `verify-of-verify confidence too low (${input.spawnResult.confidence.toFixed(2)})`,\n verifier: 'verifyVerify',\n };\n }\n return {\n passed: true,\n reason: 'verify subtask reported done with confidence ≥ 0.6',\n verifier: 'verifyVerify',\n confidence: input.spawnResult.confidence ?? 0.7,\n };\n}\n\n// ── LLM-judge layer (v6.0) ───────────────────────────────────────\n//\n// Runs AFTER the per-kind verifier passes, as a final sanity check that\n// the spawn output actually fulfilled the subtask intent. Cuts the\n// false-positive rate of the per-kind checks (which test surface\n// properties like length / exit code / keywords, not intent).\n//\n// Design:\n// - Only runs when per-kind passed (no judge on already-failed)\n// - Skipped for kind='verify' (avoids verify-of-verify recursion)\n// - Calls spawnSubAgent with the FAST tier — one short call, low cost\n// - Parses JSON {passed, reason} from the judge reply\n// - On judge throw / parse error → defers to the per-kind verdict\n// (never makes verification stricter than the per-kind alone)\n//\n// Toggle via env: TITAN_LLM_JUDGE_VERIFY=0 disables. Default is on.\n\nfunction llmJudgeEnabled(): boolean {\n const env = (process.env.TITAN_LLM_JUDGE_VERIFY ?? '').toLowerCase().trim();\n if (env === '0' || env === 'false' || env === 'no' || env === 'off') return false;\n return true;\n}\n\nasync function llmJudgeVerify(\n input: VerificationInput,\n kindResult: VerificationResult,\n): Promise<VerificationResult> {\n if (input.kind === 'verify') return kindResult;\n if (!llmJudgeEnabled()) return kindResult;\n\n try {\n const { spawnSubAgent } = await import('./subAgent.js');\n const reasoning = (input.spawnResult.reasoning || input.spawnResult.rawResponse || '').slice(0, 1800);\n const artifactNote = input.spawnResult.artifacts?.length\n ? `\\nArtifacts produced: ${input.spawnResult.artifacts.map(a => `${a.type}:${a.ref}`).join(', ')}`\n : '';\n const judgePrompt = [\n `You are a strict verification judge. Your ONE job is to decide whether the work below actually fulfilled the subtask intent.`,\n ``,\n `Subtask:`,\n ` title: ${input.subtask.title}`,\n ` description: ${input.subtask.description}`,\n ``,\n `Work produced (truncated to 1.8k chars):`,\n reasoning,\n artifactNote,\n ``,\n `Per-kind verifier ('${input.kind}') already passed with reason: ${kindResult.reason}`,\n ``,\n `Your job: does this work ACTUALLY address the subtask, or did it surface-pass without delivering?`,\n ``,\n `Common surface-pass failure modes to catch:`,\n ` - Length OK but content is generic / vague / doesn't address the specific subtask`,\n ` - Code compiles but doesn't do what was asked`,\n ` - Research has citations but missed the actual question`,\n ` - Report has the keywords but no real conclusion`,\n ``,\n `Return STRICT JSON on a single line: {\"passed\": true|false, \"reason\": \"<≤140 chars why>\"}.`,\n `No markdown. No prose before or after the JSON.`,\n ].join('\\n');\n\n const judgeResult = await spawnSubAgent({\n name: 'llm-judge',\n task: judgePrompt,\n tier: 'fast',\n maxRounds: 1,\n });\n const raw = (judgeResult.content || '').trim();\n const jsonStart = raw.indexOf('{');\n const jsonEnd = raw.lastIndexOf('}');\n if (jsonStart < 0 || jsonEnd <= jsonStart) {\n logger.info(COMPONENT, `LLM judge returned non-JSON, deferring to per-kind verdict`);\n return kindResult;\n }\n const parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1)) as { passed?: unknown; reason?: unknown };\n if (typeof parsed.passed !== 'boolean') return kindResult;\n if (parsed.passed) return kindResult; // judge agrees → keep the per-kind result\n const judgeReason = typeof parsed.reason === 'string' && parsed.reason.trim().length > 0\n ? parsed.reason.trim().slice(0, 200)\n : 'LLM judge said no without a reason';\n logger.info(COMPONENT, `LLM judge OVERRIDE: per-kind passed but judge said fail — ${judgeReason}`);\n return {\n passed: false,\n reason: `LLM judge: ${judgeReason}`,\n verifier: `${input.kind}+llm-judge`,\n confidence: kindResult.confidence,\n details: `per-kind '${input.kind}' passed (${kindResult.reason}) but judge disagreed`,\n };\n } catch (err) {\n logger.warn(COMPONENT, `LLM judge threw (deferring to per-kind): ${(err as Error).message}`);\n return kindResult;\n }\n}\n\n// ── Dispatch ─────────────────────────────────────────────────────\n\nexport async function verifyByKind(input: VerificationInput): Promise<VerificationResult> {\n let kindResult: VerificationResult;\n try {\n switch (input.kind) {\n case 'code': kindResult = await verifyCode(input); break;\n case 'research': kindResult = verifyResearch(input); break;\n case 'write': kindResult = await verifyWrite(input); break;\n case 'analysis': kindResult = verifyAnalysis(input); break;\n case 'verify': kindResult = verifyVerify(input); break;\n case 'shell': kindResult = await verifyShell(input); break;\n case 'report': kindResult = verifyReport(input); break;\n default:\n return { passed: false, reason: `Unknown kind: ${input.kind}`, verifier: 'dispatch' };\n }\n } catch (err) {\n logger.warn(COMPONENT, `Verifier threw: ${(err as Error).message}`);\n return {\n passed: false,\n reason: `Verifier error: ${(err as Error).message}`,\n verifier: `${input.kind}:error`,\n };\n }\n\n // v6.0 — LLM-judge layer. Runs only when per-kind passed.\n if (kindResult.passed) {\n return await llmJudgeVerify(input, kindResult);\n }\n return kindResult;\n}\n\n// ── Utility: read a file's content (used by higher-level UI for the driver panel) ──\nexport function readArtifactContent(path: string, maxBytes = 50_000): string | null {\n try {\n if (!existsSync(path)) return null;\n const content = readFileSync(path, 'utf-8');\n return content.length > maxBytes ? content.slice(0, maxBytes) + '\\n... [truncated]' : content;\n } catch { return null; }\n}\n"],"mappings":";AAkBA,SAAS,YAAY,oBAAoB;AACzC,SAAS,iBAAiB;AAC1B,SAAS,QAAQ,cAAc;AAC/B,OAAO,YAAY;AAKnB,MAAM,OAAO,UAAU,MAAM;AAC7B,MAAM,YAAY;AA8BlB,SAAS,gBAAgB,MAAuB;AAC5C,QAAM,UAAU,KAAK,YAAY;AACjC,QAAM,UAAU;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AACA,SAAO,QAAQ,KAAK,OAAK,QAAQ,SAAS,CAAC,CAAC;AAChD;AAKA,SAAS,mBAAmB,MAAuB;AAC/C,QAAM,UAAU,KAAK,KAAK;AAC1B,QAAM,WAAW;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AACA,SAAO,SAAS,KAAK,OAAK,EAAE,KAAK,OAAO,CAAC;AAC7C;AAIA,eAAe,WAAW,OAAuD;AAC7E,QAAM,YAAY,MAAM,aAAa,QAAQ,IAAI;AAEjD,QAAM,gBAAgB,MAAM,YAAY,UAAU,OAAO,OAAK,EAAE,SAAS,MAAM,EAAE,IAAI,OAAK,EAAE,GAAG;AAC/F,MAAI,cAAc,WAAW,GAAG;AAC5B,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU;AAAA,IACd;AAAA,EACJ;AAEA,QAAM,UAAU,cAAc,OAAO,OAAK,CAAC,WAAW,CAAC,CAAC;AACxD,MAAI,QAAQ,SAAS,GAAG;AACpB,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,8BAA8B,QAAQ,KAAK,IAAI,CAAC;AAAA,MACxD,UAAU;AAAA,MACV,SAAS,sBAAsB,cAAc,MAAM,cAAc,QAAQ,MAAM;AAAA,IACnF;AAAA,EACJ;AAEA,MAAI;AAEA,UAAM,EAAE,QAAQ,OAAO,QAAQ,MAAM,IAAI,MAAM,KAAK,qBAAqB;AAAA,MACrE,KAAK;AAAA,MACL,SAAS;AAAA,MACT,WAAW,KAAK,OAAO;AAAA,IAC3B,CAAC;AACD,UAAM,YAAY,SAAS,OAAO,SAAS;AAC3C,QAAI,gBAAgB,KAAK,QAAQ,KAAK,mBAAmB,KAAK,QAAQ,GAAG;AACrE,aAAO;AAAA,QACH,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,SAAS,SAAS,MAAM,IAAK;AAAA,QAC7B,WAAW;AAAA,MACf;AAAA,IACJ;AAAA,EACJ,SAAS,KAAK;AAEV,UAAM,MAAO,IAA8D,UACnE,IAA4B,UAC5B,IAAc;AACtB,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS,OAAO,GAAG,EAAE,MAAM,IAAK;AAAA,MAChC,WAAW;AAAA,IACf;AAAA,EACJ;AACA,SAAO;AAAA,IACH,QAAQ;AAAA,IACR,QAAQ,qBAAqB,cAAc,MAAM;AAAA,IACjD,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,WAAW;AAAA,EACf;AACJ;AAEA,SAAS,eAAe,OAA8C;AAClE,QAAM,OAAO,MAAM,YAAY,aAAa,MAAM,YAAY;AAC9D,MAAI,gBAAgB,IAAI,GAAG;AACvB,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU;AAAA,IACd;AAAA,EACJ;AAEA,MAAI,mBAAmB,IAAI,GAAG;AAC1B,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS,oBAAoB,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,IACnD;AAAA,EACJ;AAOA,MAAI,MAAM,YAAY,WAAW,UAC1B,MAAM,YAAY,cAAc,SAC/B,MAAM,YAAY,WAAW,UAAU,MAAM,GAAG;AACpD,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,oBAAoB,MAAM,YAAY,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,YAAY,UAAU,MAAM;AAAA,MAC5G,UAAU;AAAA,MACV,YAAY,MAAM,YAAY,aAAa;AAAA,MAC3C,WAAW,MAAM,YAAY,UAAU,IAAI,OAAK,EAAE,GAAG;AAAA,IACzD;AAAA,EACJ;AAKA,MAAI,KAAK,SAAS,KAAK;AACnB,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,uBAAuB,KAAK,MAAM;AAAA,MAC1C,UAAU;AAAA,IACd;AAAA,EACJ;AAEA,QAAM,YAAY,KAAK,MAAM,qBAAqB,KAAK,CAAC,GAAG;AAC3D,QAAM,YAAY,KAAK,MAAM,UAAU,KAAK,CAAC,GAAG;AAChD,QAAM,eAAe,KAAK,MAAM,gEAAgE,KAAK,CAAC,GAAG;AACzG,QAAM,gBAAgB,KAAK,MAAM,6DAA6D,KAAK,CAAC,GAAG;AACvG,QAAM,UAAU,WAAW,WAAW;AACtC,QAAM,gBAAgB,MAAM,YAAY,UAAU;AAGlD,MAAI,KAAK,SAAS,KAAK;AACnB,QAAI,iBAAiB,KAAK,gBAAgB,KAAK,MAAM,YAAY,cAAc,KAAK;AAChF,aAAO;AAAA,QACH,QAAQ;AAAA,QACR,QAAQ,oBAAoB,KAAK,MAAM,WAAW,aAAa,4BAA4B,MAAM,YAAY,WAAW,QAAQ,CAAC,CAAC;AAAA,QAClI,UAAU;AAAA,QACV,YAAY,MAAM,YAAY,aAAa;AAAA,QAC3C,WAAW,MAAM,YAAY,UAAU,IAAI,OAAK,EAAE,GAAG;AAAA,MACzD;AAAA,IACJ;AACA,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,uBAAuB,KAAK,MAAM;AAAA,MAC1C,UAAU;AAAA,IACd;AAAA,EACJ;AAEA,MAAI,UAAU,KAAK,gBAAgB,GAAG;AAClC,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,gCAAgC,OAAO;AAAA,MAC/C,UAAU;AAAA,MACV,SAAS,QAAQ,QAAQ,SAAS,QAAQ,gBAAgB,WAAW;AAAA,IACzE;AAAA,EACJ;AACA,SAAO;AAAA,IACH,QAAQ;AAAA,IACR,QAAQ,GAAG,OAAO,oBAAoB,aAAa,eAAe,KAAK,MAAM;AAAA,IAC7E,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,WAAW,MAAM,YAAY,UAAU,IAAI,OAAK,EAAE,GAAG;AAAA,EACzD;AACJ;AAEA,eAAe,YAAY,OAAuD;AAC9E,QAAM,OAAO,MAAM,YAAY,aAAa,MAAM,YAAY;AAC9D,MAAI,gBAAgB,IAAI,GAAG;AACvB,WAAO,EAAE,QAAQ,OAAO,QAAQ,sBAAsB,UAAU,cAAc;AAAA,EAClF;AAEA,MAAI,mBAAmB,IAAI,GAAG;AAC1B,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS,oBAAoB,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,IACnD;AAAA,EACJ;AAGA,MAAI,MAAM,YAAY,WAAW,UAC1B,MAAM,YAAY,cAAc,SAC/B,MAAM,YAAY,WAAW,UAAU,MAAM,GAAG;AACpD,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,oBAAoB,MAAM,YAAY,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,YAAY,UAAU,MAAM;AAAA,MAC5G,UAAU;AAAA,MACV,YAAY,MAAM,YAAY,aAAa;AAAA,MAC3C,WAAW,MAAM,YAAY,UAAU,IAAI,OAAK,EAAE,GAAG;AAAA,IACzD;AAAA,EACJ;AACA,MAAI,KAAK,SAAS,KAAK;AACnB,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,oBAAoB,KAAK,MAAM;AAAA,MACvC,UAAU;AAAA,IACd;AAAA,EACJ;AAIA,QAAM,aAAa,MAAM,YAAY,cAAc;AACnD,MAAI,aAAa,KAAK;AAClB,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,4BAA4B,WAAW,QAAQ,CAAC,CAAC;AAAA,MACzD,UAAU;AAAA,IACd;AAAA,EACJ;AACA,SAAO;AAAA,IACH,QAAQ;AAAA,IACR,QAAQ,SAAS,KAAK,MAAM,sBAAsB,WAAW,QAAQ,CAAC,CAAC;AAAA,IACvE,UAAU;AAAA,IACV;AAAA,IACA,WAAW,MAAM,YAAY,UAAU,IAAI,OAAK,EAAE,GAAG;AAAA,EACzD;AACJ;AAEA,SAAS,eAAe,OAA8C;AAClE,QAAM,OAAO,MAAM,YAAY,aAAa,MAAM,YAAY;AAC9D,MAAI,gBAAgB,IAAI,GAAG;AACvB,WAAO,EAAE,QAAQ,OAAO,QAAQ,sBAAsB,UAAU,iBAAiB;AAAA,EACrF;AAEA,MAAI,mBAAmB,IAAI,GAAG;AAC1B,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,SAAS,oBAAoB,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,IACnD;AAAA,EACJ;AAKA,MAAI,MAAM,YAAY,WAAW,UAC1B,MAAM,YAAY,cAAc,SAC/B,MAAM,YAAY,WAAW,UAAU,MAAM,GAAG;AACpD,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,oBAAoB,MAAM,YAAY,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,YAAY,UAAU,MAAM;AAAA,MAC5G,UAAU;AAAA,MACV,YAAY,MAAM,YAAY,aAAa;AAAA,MAC3C,WAAW,MAAM,YAAY,UAAU,IAAI,OAAK,EAAE,GAAG;AAAA,IACzD;AAAA,EACJ;AAWA,QAAM,gBAAgB,MAAM,YAAY,WAAW,UAAU;AAC7D,MAAI,iBAAiB,KAAK,MAAM,YAAY,WAAW,UAAU,MAAM,YAAY,cAAc,KAAK;AAClG,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,qBAAqB,aAAa,4BAA4B,MAAM,YAAY,WAAW,QAAQ,CAAC,CAAC;AAAA,MAC7G,UAAU;AAAA,MACV,YAAY,MAAM,YAAY,aAAa;AAAA,MAC3C,WAAW,MAAM,YAAY,UAAU,IAAI,OAAK,EAAE,GAAG;AAAA,IACzD;AAAA,EACJ;AAEA,QAAM,qBAAqB,mGAAmG,KAAK,IAAI;AACvI,QAAM,eAAe,KAAK,MAAM,gBAAgB,KAAK,CAAC,GAAG;AACzD,QAAM,gBAAgB,KAAK,MAAM,gEAAgE,KAAK,CAAC,GAAG;AAC1G,QAAM,eAAe,sBAAsB,eAAe,KAAK,gBAAgB;AAE/E,MAAI,KAAK,SAAS,IAAI;AAClB,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,uBAAuB,KAAK,MAAM;AAAA,MAC1C,UAAU;AAAA,IACd;AAAA,EACJ;AAGA,MAAI,KAAK,SAAS,OAAO,MAAM,YAAY,cAAc,OAAO,MAAM,YAAY,WAAW,QAAQ;AACjG,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,YAAY,KAAK,MAAM,4BAA4B,MAAM,YAAY,WAAW,QAAQ,CAAC,CAAC;AAAA,MAClG,UAAU;AAAA,MACV,YAAY,MAAM,YAAY,aAAa;AAAA,MAC3C,WAAW,MAAM,YAAY,UAAU,IAAI,OAAK,EAAE,GAAG;AAAA,IACzD;AAAA,EACJ;AAGA,MAAI,CAAC,cAAc;AACf,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU;AAAA,IACd;AAAA,EACJ;AACA,SAAO;AAAA,IACH,QAAQ;AAAA,IACR,QAAQ,YAAY,KAAK,MAAM,4CAA4C,kBAAkB,YAAY,WAAW,YAAY,YAAY;AAAA,IAC5I,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,WAAW,MAAM,YAAY,UAAU,IAAI,OAAK,EAAE,GAAG;AAAA,EACzD;AACJ;AAEA,eAAe,YAAY,OAAuD;AAI9E,MAAI,MAAM,YAAY,WAAW,QAAQ;AACrC,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,kBAAkB,MAAM,YAAY,MAAM;AAAA,MAClD,UAAU;AAAA,IACd;AAAA,EACJ;AACA,MAAI,MAAM,uBAAuB;AAC7B,UAAM,KAAK,IAAI,OAAO,MAAM,qBAAqB;AACjD,QAAI,CAAC,GAAG,KAAK,MAAM,YAAY,WAAW,GAAG;AACzC,aAAO;AAAA,QACH,QAAQ;AAAA,QACR,QAAQ,yCAAyC,MAAM,qBAAqB;AAAA,QAC5E,UAAU;AAAA,QACV,SAAS,MAAM,YAAY,YAAY,MAAM,GAAG,GAAG;AAAA,MACvD;AAAA,IACJ;AAAA,EACJ;AACA,SAAO;AAAA,IACH,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,EAChB;AACJ;AAEA,SAAS,aAAa,OAA8C;AAChE,QAAM,OAAO,MAAM,YAAY,aAAa,MAAM,YAAY;AAC9D,MAAI,KAAK,SAAS,KAAK;AACnB,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,qBAAqB,KAAK,MAAM;AAAA,MACxC,UAAU;AAAA,IACd;AAAA,EACJ;AACA,QAAM,WAAW,CAAC,QAAQ,WAAW,UAAU;AAC/C,QAAM,UAAU,SAAS,OAAO,OAAK,CAAC,KAAK,YAAY,EAAE,SAAS,CAAC,CAAC;AACpE,MAAI,QAAQ,SAAS,GAAG;AACpB,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,gCAAgC,QAAQ,KAAK,IAAI,CAAC;AAAA,MAC1D,UAAU;AAAA,IACd;AAAA,EACJ;AACA,SAAO;AAAA,IACH,QAAQ;AAAA,IACR,QAAQ,UAAU,KAAK,MAAM;AAAA,IAC7B,UAAU;AAAA,IACV,YAAY;AAAA,EAChB;AACJ;AAIA,SAAS,aAAa,OAA8C;AAChE,MAAI,MAAM,YAAY,WAAW,QAAQ;AACrC,WAAO,EAAE,QAAQ,OAAO,QAAQ,uBAAuB,MAAM,YAAY,MAAM,IAAI,UAAU,eAAe;AAAA,EAChH;AACA,MAAI,MAAM,YAAY,eAAe,UAAa,MAAM,YAAY,aAAa,KAAK;AAClF,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,wCAAwC,MAAM,YAAY,WAAW,QAAQ,CAAC,CAAC;AAAA,MACvF,UAAU;AAAA,IACd;AAAA,EACJ;AACA,SAAO;AAAA,IACH,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,YAAY,MAAM,YAAY,cAAc;AAAA,EAChD;AACJ;AAmBA,SAAS,kBAA2B;AAChC,QAAM,OAAO,QAAQ,IAAI,0BAA0B,IAAI,YAAY,EAAE,KAAK;AAC1E,MAAI,QAAQ,OAAO,QAAQ,WAAW,QAAQ,QAAQ,QAAQ,MAAO,QAAO;AAC5E,SAAO;AACX;AAEA,eAAe,eACX,OACA,YAC2B;AAC3B,MAAI,MAAM,SAAS,SAAU,QAAO;AACpC,MAAI,CAAC,gBAAgB,EAAG,QAAO;AAE/B,MAAI;AACA,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,eAAe;AACtD,UAAM,aAAa,MAAM,YAAY,aAAa,MAAM,YAAY,eAAe,IAAI,MAAM,GAAG,IAAI;AACpG,UAAM,eAAe,MAAM,YAAY,WAAW,SAC5C;AAAA,sBAAyB,MAAM,YAAY,UAAU,IAAI,OAAK,GAAG,EAAE,IAAI,IAAI,EAAE,GAAG,EAAE,EAAE,KAAK,IAAI,CAAC,KAC9F;AACN,UAAM,cAAc;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,MAAM,QAAQ,KAAK;AAAA,MAC/B,kBAAkB,MAAM,QAAQ,WAAW;AAAA,MAC3C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,uBAAuB,MAAM,IAAI,kCAAkC,WAAW,MAAM;AAAA,MACpF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ,EAAE,KAAK,IAAI;AAEX,UAAM,cAAc,MAAM,cAAc;AAAA,MACpC,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,WAAW;AAAA,IACf,CAAC;AACD,UAAM,OAAO,YAAY,WAAW,IAAI,KAAK;AAC7C,UAAM,YAAY,IAAI,QAAQ,GAAG;AACjC,UAAM,UAAU,IAAI,YAAY,GAAG;AACnC,QAAI,YAAY,KAAK,WAAW,WAAW;AACvC,aAAO,KAAK,WAAW,4DAA4D;AACnF,aAAO;AAAA,IACX;AACA,UAAM,SAAS,KAAK,MAAM,IAAI,MAAM,WAAW,UAAU,CAAC,CAAC;AAC3D,QAAI,OAAO,OAAO,WAAW,UAAW,QAAO;AAC/C,QAAI,OAAO,OAAQ,QAAO;AAC1B,UAAM,cAAc,OAAO,OAAO,WAAW,YAAY,OAAO,OAAO,KAAK,EAAE,SAAS,IACjF,OAAO,OAAO,KAAK,EAAE,MAAM,GAAG,GAAG,IACjC;AACN,WAAO,KAAK,WAAW,kEAA6D,WAAW,EAAE;AACjG,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,cAAc,WAAW;AAAA,MACjC,UAAU,GAAG,MAAM,IAAI;AAAA,MACvB,YAAY,WAAW;AAAA,MACvB,SAAS,aAAa,MAAM,IAAI,aAAa,WAAW,MAAM;AAAA,IAClE;AAAA,EACJ,SAAS,KAAK;AACV,WAAO,KAAK,WAAW,4CAA6C,IAAc,OAAO,EAAE;AAC3F,WAAO;AAAA,EACX;AACJ;AAIA,eAAsB,aAAa,OAAuD;AACtF,MAAI;AACJ,MAAI;AACA,YAAQ,MAAM,MAAM;AAAA,MAChB,KAAK;AAAY,qBAAa,MAAM,WAAW,KAAK;AAAO;AAAA,MAC3D,KAAK;AAAY,qBAAa,eAAe,KAAK;AAAS;AAAA,MAC3D,KAAK;AAAY,qBAAa,MAAM,YAAY,KAAK;AAAM;AAAA,MAC3D,KAAK;AAAY,qBAAa,eAAe,KAAK;AAAS;AAAA,MAC3D,KAAK;AAAY,qBAAa,aAAa,KAAK;AAAW;AAAA,MAC3D,KAAK;AAAY,qBAAa,MAAM,YAAY,KAAK;AAAM;AAAA,MAC3D,KAAK;AAAY,qBAAa,aAAa,KAAK;AAAW;AAAA,MAC3D;AACI,eAAO,EAAE,QAAQ,OAAO,QAAQ,iBAAiB,MAAM,IAAI,IAAI,UAAU,WAAW;AAAA,IAC5F;AAAA,EACJ,SAAS,KAAK;AACV,WAAO,KAAK,WAAW,mBAAoB,IAAc,OAAO,EAAE;AAClE,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,QAAQ,mBAAoB,IAAc,OAAO;AAAA,MACjD,UAAU,GAAG,MAAM,IAAI;AAAA,IAC3B;AAAA,EACJ;AAGA,MAAI,WAAW,QAAQ;AACnB,WAAO,MAAM,eAAe,OAAO,UAAU;AAAA,EACjD;AACA,SAAO;AACX;AAGO,SAAS,oBAAoB,MAAc,WAAW,KAAuB;AAChF,MAAI;AACA,QAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,UAAM,UAAU,aAAa,MAAM,OAAO;AAC1C,WAAO,QAAQ,SAAS,WAAW,QAAQ,MAAM,GAAG,QAAQ,IAAI,sBAAsB;AAAA,EAC1F,QAAQ;AAAE,WAAO;AAAA,EAAM;AAC3B;","names":[]}
@@ -1351,6 +1351,270 @@ async function startGateway(options) {
1351
1351
  res.status(500).json({ error: "time_travel_restore_failed", message: err.message });
1352
1352
  }
1353
1353
  });
1354
+ app.post("/api/mission/run", async (req, res) => {
1355
+ try {
1356
+ const description = String(req.body?.description ?? req.body?.prompt ?? "").trim();
1357
+ if (!description || description.length < 10) {
1358
+ res.status(400).json({ error: "description_required", message: "Provide a description (>=10 chars) of what TITAN should accomplish autonomously." });
1359
+ return;
1360
+ }
1361
+ const title = String(req.body?.title ?? "").trim() || description.split(/[.!?\n]/)[0].slice(0, 80);
1362
+ const priority = Math.max(1, Math.min(5, Number(req.body?.priority) || 3));
1363
+ const budgetLimit = Math.max(0.01, Math.min(50, Number(req.body?.budgetUsd) || 2));
1364
+ const tags = Array.isArray(req.body?.tags) ? req.body.tags.map(String) : ["mission", "autonomous"];
1365
+ let subtasks = void 0;
1366
+ try {
1367
+ const { spawnSubAgent } = await import("../agent/subAgent.js");
1368
+ const decomposePrompt = [
1369
+ `Decompose this autonomous mission into 3-6 concrete, independently-runnable subtasks.`,
1370
+ ``,
1371
+ `Mission: ${description}`,
1372
+ ``,
1373
+ `Return STRICT JSON: { "subtasks": [ { "title": "...", "description": "..." }, ... ] }.`,
1374
+ `Each title <= 60 chars, each description <= 200 chars.`,
1375
+ `Order subtasks dependency-aware (later ones may reference earlier output).`,
1376
+ `Do NOT add a verification subtask \u2014 the driver verifies automatically.`,
1377
+ `Do NOT wrap in markdown \u2014 pure JSON.`
1378
+ ].join("\n");
1379
+ const result = await spawnSubAgent({
1380
+ name: "mission-decomposer",
1381
+ task: decomposePrompt,
1382
+ tier: "fast",
1383
+ maxRounds: 1
1384
+ });
1385
+ const raw = (result.content || "").trim();
1386
+ const jsonStart = raw.indexOf("{");
1387
+ const jsonEnd = raw.lastIndexOf("}");
1388
+ if (jsonStart >= 0 && jsonEnd > jsonStart) {
1389
+ const parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
1390
+ if (Array.isArray(parsed.subtasks) && parsed.subtasks.length >= 1) {
1391
+ subtasks = parsed.subtasks.filter((s) => typeof s?.title === "string" && typeof s?.description === "string").slice(0, 6).map((s) => ({ title: String(s.title).slice(0, 80), description: String(s.description).slice(0, 280) }));
1392
+ }
1393
+ }
1394
+ } catch (err) {
1395
+ logger.warn(COMPONENT, `Mission decomposition failed, creating goal with no subtasks: ${err.message}`);
1396
+ }
1397
+ const { createGoal } = await import("../agent/goals.js");
1398
+ const goal = createGoal({
1399
+ title,
1400
+ description,
1401
+ priority,
1402
+ budgetLimit,
1403
+ tags,
1404
+ subtasks,
1405
+ force: req.body?.force === true
1406
+ });
1407
+ logger.info(COMPONENT, `[Mission] Created mission goal "${title}" (id=${goal.id}, subtasks=${subtasks?.length ?? 0})`);
1408
+ res.json({
1409
+ ok: true,
1410
+ goal: { id: goal.id, title: goal.title, status: goal.status, priority: goal.priority, subtaskCount: goal.subtasks?.length ?? 0 },
1411
+ message: subtasks?.length ? `Mission "${title}" created with ${subtasks.length} subtasks. Driver will pick it up on the next tick (within 10s).` : `Mission "${title}" created. Decomposition skipped \u2014 add subtasks via the Goals API or chat.`
1412
+ });
1413
+ } catch (err) {
1414
+ res.status(500).json({ error: "mission_create_failed", message: err.message });
1415
+ }
1416
+ });
1417
+ app.get("/api/missions/active", async (_req, res) => {
1418
+ try {
1419
+ const { listActiveDrivers } = await import("../agent/goalDriver.js");
1420
+ const { listGoals } = await import("../agent/goals.js");
1421
+ const drivers = listActiveDrivers();
1422
+ const goals = listGoals("active");
1423
+ const goalsById = new Map(goals.map((g) => [g.id, g]));
1424
+ const missions = drivers.map((d) => {
1425
+ const goal = goalsById.get(d.goalId);
1426
+ const completed = Object.values(d.subtaskStates).filter((s) => s.verificationResult?.passed === true).length;
1427
+ const total = Object.keys(d.subtaskStates).length || (goal?.subtasks?.length ?? 0);
1428
+ return {
1429
+ goalId: d.goalId,
1430
+ title: goal?.title || "(unknown goal)",
1431
+ phase: d.phase,
1432
+ startedAt: d.startedAt,
1433
+ currentSubtaskId: d.currentSubtaskId ?? null,
1434
+ subtasks: { total, completed },
1435
+ budget: d.budget,
1436
+ blockedReason: d.blockedReason ?? null,
1437
+ lastHistory: (d.history || []).slice(-3)
1438
+ };
1439
+ });
1440
+ res.json({ count: missions.length, missions });
1441
+ } catch (err) {
1442
+ res.status(500).json({ error: "missions_active_failed", message: err.message });
1443
+ }
1444
+ });
1445
+ app.get("/api/missions/recent", async (req, res) => {
1446
+ try {
1447
+ const hours = Math.max(1, Math.min(168, parseInt(String(req.query.hours ?? "24"), 10) || 24));
1448
+ const { listAllDrivers } = await import("../agent/goalDriver.js");
1449
+ const { listGoals } = await import("../agent/goals.js");
1450
+ const all = listAllDrivers();
1451
+ const cutoff = Date.now() - hours * 60 * 60 * 1e3;
1452
+ const goalsById = new Map(listGoals().map((g) => [g.id, g]));
1453
+ const recent = all.filter((d) => ["done", "failed", "cancelled"].includes(d.phase)).filter((d) => {
1454
+ const completedAt = d.history?.[d.history.length - 1]?.at ?? d.startedAt;
1455
+ return new Date(completedAt).getTime() >= cutoff;
1456
+ }).map((d) => {
1457
+ const goal = goalsById.get(d.goalId);
1458
+ const completedAt = d.history?.[d.history.length - 1]?.at ?? d.startedAt;
1459
+ return {
1460
+ goalId: d.goalId,
1461
+ title: goal?.title || "(unknown goal)",
1462
+ phase: d.phase,
1463
+ startedAt: d.startedAt,
1464
+ completedAt,
1465
+ durationMs: new Date(completedAt).getTime() - new Date(d.startedAt).getTime(),
1466
+ subtaskCount: Object.keys(d.subtaskStates).length || (goal?.subtasks?.length ?? 0),
1467
+ budget: d.budget,
1468
+ lessonsLearned: d.retrospective?.lessonsLearned ?? []
1469
+ };
1470
+ }).sort((a, b) => a.completedAt < b.completedAt ? 1 : -1);
1471
+ res.json({ hours, count: recent.length, missions: recent });
1472
+ } catch (err) {
1473
+ res.status(500).json({ error: "missions_recent_failed", message: err.message });
1474
+ }
1475
+ });
1476
+ app.get("/api/missions/digest", async (req, res) => {
1477
+ try {
1478
+ const hours = Math.max(1, Math.min(168, parseInt(String(req.query.hours ?? "24"), 10) || 24));
1479
+ const { listAllDrivers, listActiveDrivers } = await import("../agent/goalDriver.js");
1480
+ const { listGoals } = await import("../agent/goals.js");
1481
+ const cutoff = Date.now() - hours * 60 * 60 * 1e3;
1482
+ const all = listAllDrivers();
1483
+ const active = listActiveDrivers();
1484
+ const goalsById = new Map(listGoals().map((g) => [g.id, g]));
1485
+ const completed = all.filter((d) => d.phase === "done").filter((d) => {
1486
+ const at = d.history?.[d.history.length - 1]?.at ?? d.startedAt;
1487
+ return new Date(at).getTime() >= cutoff;
1488
+ });
1489
+ const failed = all.filter((d) => d.phase === "failed").filter((d) => {
1490
+ const at = d.history?.[d.history.length - 1]?.at ?? d.startedAt;
1491
+ return new Date(at).getTime() >= cutoff;
1492
+ });
1493
+ const blocked = active.filter((d) => d.phase === "blocked");
1494
+ const totalCostUsd = [...completed, ...failed].reduce((sum, d) => sum + (d.budget?.costUsd ?? 0), 0);
1495
+ const totalTokens = [...completed, ...failed].reduce((sum, d) => sum + (d.budget?.tokensUsed ?? 0), 0);
1496
+ const lessons = completed.flatMap((d) => d.retrospective?.lessonsLearned ?? []).slice(0, 10);
1497
+ const summary = [];
1498
+ summary.push(`Last ${hours}h: ${completed.length} mission(s) completed, ${failed.length} failed, ${active.length} active, ${blocked.length} blocked for approval.`);
1499
+ if (completed.length > 0) {
1500
+ summary.push(``);
1501
+ summary.push(`Completed:`);
1502
+ for (const d of completed.slice(0, 10)) {
1503
+ const g = goalsById.get(d.goalId);
1504
+ summary.push(` \u2022 ${g?.title ?? d.goalId} (${Math.round((d.budget?.costUsd ?? 0) * 100) / 100} USD, ${Math.round(((d.history?.[d.history.length - 1]?.at ? new Date(d.history[d.history.length - 1].at).getTime() : Date.now()) - new Date(d.startedAt).getTime()) / 1e3)}s)`);
1505
+ }
1506
+ }
1507
+ if (failed.length > 0) {
1508
+ summary.push(``);
1509
+ summary.push(`Failed (need review):`);
1510
+ for (const d of failed.slice(0, 5)) {
1511
+ const g = goalsById.get(d.goalId);
1512
+ summary.push(` \u2022 ${g?.title ?? d.goalId}`);
1513
+ }
1514
+ }
1515
+ if (blocked.length > 0) {
1516
+ summary.push(``);
1517
+ summary.push(`Blocked for approval:`);
1518
+ for (const d of blocked.slice(0, 5)) {
1519
+ const g = goalsById.get(d.goalId);
1520
+ summary.push(` \u2022 ${g?.title ?? d.goalId} \u2014 ${d.blockedReason?.question ?? "(no reason given)"}`);
1521
+ }
1522
+ }
1523
+ if (active.length > 0 && blocked.length < active.length) {
1524
+ summary.push(``);
1525
+ summary.push(`In progress:`);
1526
+ for (const d of active.filter((a) => a.phase !== "blocked").slice(0, 10)) {
1527
+ const g = goalsById.get(d.goalId);
1528
+ summary.push(` \u2022 ${g?.title ?? d.goalId} [${d.phase}]`);
1529
+ }
1530
+ }
1531
+ res.json({
1532
+ hours,
1533
+ stats: {
1534
+ completed: completed.length,
1535
+ failed: failed.length,
1536
+ active: active.length,
1537
+ blocked: blocked.length,
1538
+ totalCostUsd: Math.round(totalCostUsd * 1e3) / 1e3,
1539
+ totalTokens
1540
+ },
1541
+ summaryText: summary.join("\n"),
1542
+ recentLessons: lessons
1543
+ });
1544
+ } catch (err) {
1545
+ res.status(500).json({ error: "mission_digest_failed", message: err.message });
1546
+ }
1547
+ });
1548
+ app.get("/api/mission/:id", async (req, res) => {
1549
+ try {
1550
+ const goalId = String(req.params.id || "").trim();
1551
+ if (!goalId) {
1552
+ res.status(400).json({ error: "missing_goal_id" });
1553
+ return;
1554
+ }
1555
+ const { getGoal } = await import("../agent/goals.js");
1556
+ const { getDriverState } = await import("../agent/goalDriver.js");
1557
+ const goal = getGoal(goalId);
1558
+ if (!goal) {
1559
+ res.status(404).json({ error: "goal_not_found" });
1560
+ return;
1561
+ }
1562
+ const state = getDriverState(goalId);
1563
+ res.json({
1564
+ goal: {
1565
+ id: goal.id,
1566
+ title: goal.title,
1567
+ description: goal.description,
1568
+ status: goal.status,
1569
+ priority: goal.priority,
1570
+ progress: goal.progress,
1571
+ totalCost: goal.totalCost,
1572
+ createdAt: goal.createdAt,
1573
+ completedAt: goal.completedAt,
1574
+ tags: goal.tags ?? [],
1575
+ subtasks: (goal.subtasks ?? []).map((s) => ({
1576
+ id: s.id,
1577
+ title: s.title,
1578
+ description: s.description,
1579
+ status: s.status,
1580
+ dependsOn: s.dependsOn ?? [],
1581
+ retries: s.retries,
1582
+ completedAt: s.completedAt
1583
+ }))
1584
+ },
1585
+ driver: state ? {
1586
+ phase: state.phase,
1587
+ startedAt: state.startedAt,
1588
+ currentSubtaskId: state.currentSubtaskId ?? null,
1589
+ budget: state.budget,
1590
+ blockedReason: state.blockedReason ?? null,
1591
+ historyTail: (state.history ?? []).slice(-10),
1592
+ subtaskStates: state.subtaskStates
1593
+ } : null
1594
+ });
1595
+ } catch (err) {
1596
+ res.status(500).json({ error: "mission_get_failed", message: err.message });
1597
+ }
1598
+ });
1599
+ app.post("/api/mission/:id/cancel", async (req, res) => {
1600
+ try {
1601
+ const goalId = String(req.params.id || "").trim();
1602
+ if (!goalId) {
1603
+ res.status(400).json({ error: "missing_goal_id" });
1604
+ return;
1605
+ }
1606
+ const { getDriverState } = await import("../agent/goalDriver.js");
1607
+ const { updateGoal } = await import("../agent/goals.js");
1608
+ const state = getDriverState(goalId);
1609
+ if (state) {
1610
+ state.userControls.cancelRequested = true;
1611
+ }
1612
+ updateGoal(goalId, { status: "paused" });
1613
+ res.json({ ok: true, message: `Mission ${goalId} marked for cancellation. Driver will stop on the next tick.` });
1614
+ } catch (err) {
1615
+ res.status(500).json({ error: "mission_cancel_failed", message: err.message });
1616
+ }
1617
+ });
1354
1618
  app.post("/api/soma/gift", async (req, res) => {
1355
1619
  try {
1356
1620
  const userId = getUserIdFromReq(req);