chainlesschain 0.162.31 → 0.162.33

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 (170) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/assets/{AIOps-BqWP6FKu.js → AIOps-3TazCYWE.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CXwMgOvX.js → ActionButton-DUPN0PST.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DAebZ4IY.js → Analytics-CemvhkzD.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-CYsqYoME.js → AppLayout-BL_tAU3M.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-BbTtX1Nf.js → Audit-Dl9l-cxF.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-DgqY2Eb-.js → Backup-BKDDX75m.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-Cq2ZuSoO.js → BaseInput-CDYePvMI.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-D2kqpUyO.js → Chat-CGtR0sg3.js} +5 -5
  10. package/src/assets/web-panel/assets/ChatBubbleRenderer-DZjc9uKn.js +1 -0
  11. package/src/assets/web-panel/assets/{Checkbox-_9swHpyc.js → Checkbox-CwYIHOOo.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-Cr9YbCPl.js → Codegen-CIF5tbtd.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col--wdpCMxx.js → Col-z7d4kxeP.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-DuFcVnLu.js → Community-DUlDrqF7.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-1yzYeT04.js → Compact-CJ1o8QQR.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-Dq3aU9Df.js → Compliance-D3i9d_uO.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CrWcnIg8.js → Cowork-Wm7JTkfB.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron-Bh6fKZ0h.js → Cron-B0QnHhZx.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-8ofPaWVW.js → Crosschain-3yPrnNgd.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-D3EiYm3w.js → DID-cfdkiDWF.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-BFjEdFne.js → Dashboard-DFkgM4gT.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-pYVPcP6O.js → Dropdown-YYWE81DL.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-zBPodwJ1.js → EmailListRenderer-BXfHK1Bn.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CyQTW6PW.js → FamilyGuardDashboard-DInUxJ2G.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-Ctaq3zYq.js → Federation-DNUYeFsv.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-CWYJCLq1.js → FormItemContext-Cr7eVEBB.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-B1g6t9R9.js → GenericCardRenderer-_gF4cmDa.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-DH-v8iwd.js → Git-BqldmUbO.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-jZxXvOs5.js → Governance-BF59ZiQ8.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-D07LRghn.js → Inference-Cy7y1eb9.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-DnGtRZhx.js → KnowledgeGraph-B3fVocTO.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-D2pM9C4W.js → Logs-BDirsUVk.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-UyIO7C7r.js → Marketplace-GhXpZgp2.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-Bf1gvZPf.js → McpTools-0VvfIhKx.js} +3 -3
  35. package/src/assets/web-panel/assets/{Memory-C1bWj4RN.js → Memory-CJLBgAUT.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-C_Ot1H_a.js → MobileBridge-BMedY9Yg.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-mdohgRlL.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-CnzFUz5J.js → Mtc-CgEuUg0g.js} +5 -5
  39. package/src/assets/web-panel/assets/{MtcAudit-CAAh99wz.js → MtcAudit-1pWNe_xi.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-D6IAg6HE.js → Multisig-DPIQ7oZL.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-BFMarxb0.js → NLProgramming-W__P_P4Z.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-BRp9ro3t.js → Notes-C_MCDhFk.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-C0Au3Cxb.js → NotificationSettings-CDFotapL.js} +1 -1
  44. package/src/assets/web-panel/assets/OrderTableRenderer-Dtht0cEs.js +1 -0
  45. package/src/assets/web-panel/assets/{Organization-DYoxLBRX.js → Organization-D6lMumhD.js} +2 -2
  46. package/src/assets/web-panel/assets/{Overflow-rO8JJWGJ.js → Overflow-BMOvUMW6.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-DJleeXIK.js → P2P-DsQTEw1t.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-DM5qghFp.js → PdhVaultBrowser-CncRtN1Z.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-D5v4Beya.js → Permissions-DDC-DkUl.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-c2ZTX0Pv.js → PersonalDataHub-DVKY_NnT.js} +4 -4
  51. package/src/assets/web-panel/assets/{Pipeline-Crrkyhpz.js → Pipeline-C7oDVTl-.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-DZVyrJKa.js → Privacy-DReGvTEJ.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-DKg7J0gz.js → ProjectInit-C-j2dzxJ.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-3ndmTvVH.js → ProjectSettings-DcUsvFnc.js} +2 -2
  55. package/src/assets/web-panel/assets/Projects-jSjWnmr6.js +1 -0
  56. package/src/assets/web-panel/assets/{Providers-BeqBVMhB.js → Providers-DIpohWG5.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-DKAAxzuA.js → QuickAsk-DdvLtpEU.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-Byu7IGei.js → Recommend-DPAi2zo3.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-BKhWAmCu.js → Reputation-DJD7qXSI.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-BFtn11O6.js → Row-XERdPDHk.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-D5a0PT0k.js → RssFeed-Cl_VlCLg.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-DAkuaZNe.js → Search-C-poG9P5.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-C79Ml2Ms.js → Security-DjjCrw8v.js} +2 -2
  64. package/src/assets/web-panel/assets/{Services-BBk_jH6-.js → Services-BuWeB4YJ.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-Cy0VvL0M.js → Skeleton-VZXOKwC_.js} +1 -1
  66. package/src/assets/web-panel/assets/Skills-B76ONTfP.js +1 -0
  67. package/src/assets/web-panel/assets/{Sla-CbX1f8xN.js → Sla-DIj1KREq.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-BIkoUjws.js → SpeechSettings-BrAp3Yk3.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-DG6Swk7G.js → SyncSettings--mJcpccF.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-DM8cMr83.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-AaJPeCIz.js → Templates-kOBK6m1Z.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-jVFRofww.js → Tenant-BjSzYPzn.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-DHBMzfK6.js → Terminal-DwpY-Ay7.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-9RFfOHSI.js → TimelineRenderer-aoI0DazM.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-ZTfwuABF.js → Tokens-YwE0LqSZ.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-Xo7uZNQs.js → Trigger-CwSKzvlX.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-C0cTPYvn.js → Trust-B__Jqdzn.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-DmMKio71.js → UkeySign-mty0jwmx.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-DP7B-oGT.js → VideoEditing-Ddsx_OQ6.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-B1kZDARo.js → Wallet-D4Q8yXZm.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-Bo5kBx27.js → WebAuthn-CLUaKUr5.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-DGI9SNHH.js → WorkflowEditor-Di5pOaeC.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-y97W1CIG.js → chat-CELatHkT.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-DtTNo0sH.js → colors-CawDLjXV.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-D0q0exuS.js → compact-item-DeMp-K0j.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-D7pLFs2I.js → createContext-zY9kXivd.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-zLjV7g6r.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-CXjG5B2j.js → hasIn-VEBMW8E4.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-TrBGgrwG.js → index-8kqE_cVD.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-D_4WcI1V.js → index-B8AZpx7d.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-YWOEx3rP.js → index-BFc0vBN9.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-BqVjUN8b.js → index-BVkrfyuk.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-BnLrbXDA.js → index-BfGGKoo8.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-CKrbutAQ.js → index-BjctklSd.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CFsPe2N7.js → index-BqJ2r12F.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CSdhC7Qo.js → index-C0GhuYLk.js} +1 -1
  97. package/src/assets/web-panel/assets/index-CDtUWCtX.js +1 -0
  98. package/src/assets/web-panel/assets/{index-BO644Q4S.js → index-CHqvj9uz.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-gFLQe31v.js → index-CHxHLv2b.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BgyrM0UN.js → index-CWbbB1MI.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-1dwtkcJv.js → index-CbJZzK9B.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-B3y_4OdG.js → index-CfZV3FXN.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Dr45Nm9V.js → index-Cr7lnIeI.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CkGFqlYX.js → index-CtLZammH.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-6np5ESBM.js → index-CtoauqWt.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BU944DeT.js → index-CyeYs7SG.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-POaFzYGS.js → index-DALuVdhu.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DjCawXk1.js → index-DClGYjBM.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-8jxbZupG.js → index-DPHe9NYG.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-_3wPBMKt.js → index-DSiL_W2n.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Cbqu804A.js → index-DXNe_zIP.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-EaIfumgW.js → index-DhsfyHcg.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BzCPx1cq.js → index-Dna2psGz.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-kvV0f4tV.js → index-GRNVdvoA.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BdhEYW2a.js → index-JseP3-5X.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-aarO4HT9.js → index-KcOEkUCM.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BgmvrPJH.js → index-S9JZDSaa.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DY6KLlgG.js → index-SrQIPYq8.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Ct6xtKkc.js → index-TfXODan7.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B_hjkMtX.js → index-V3K9gvKR.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-4mWZhCzz.js → index-VJnHvkv2.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BJUf19Wd.js → index-XFyv3Sg_.js} +3 -3
  123. package/src/assets/web-panel/assets/{index-B4dPdrvC.js → index-b3ZuAreb.js} +1 -1
  124. package/src/assets/web-panel/assets/index-d_RPqH7u.js +1 -0
  125. package/src/assets/web-panel/assets/{index-BPXhU-jp.js → index-fBNVDEf2.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-bVJvqDAz.js → index-u_1aiNTA.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-qoB3whR9.js → index-vaD1iHg5.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-BnXISaAa.js → initDefaultProps-Sd7Eayz4.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-ChY7C0zJ.js → motion-DlToY72q.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-ByFZMFM5.js → move-DvS7EmAP.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-BYeliY1H.js → omit-CzLq4QKW.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-B9dcAKnu.js → pickAttrs-BcM75Jx_.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-D3F_txz7.js → placementArrow-B7xXXiwd.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-ClkwY7wS.js → responsiveObserve-CrYPRB-g.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-BNgy2Eea.js → slide-CSYTtsRt.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-Bv3heMCD.js → statusUtils-CeSuOVT_.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-DVdlHbQm.js → styleChecker-KiQethca.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-alrRY5BK.js → useFlexGapSupport-CSQnQdiv.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-CcVh0-Vu.js → useFs-Br8Kr1pr.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-CkkHPhyq.js → usePersonalDataHub-DGJtDcMm.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-DWi0X9WN.js → vnode-C-jVtGka.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-DCbqxxLH.js → zoom-CeWySTPF.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +47 -0
  145. package/src/commands/checkpoint.js +253 -53
  146. package/src/commands/compact.js +150 -0
  147. package/src/commands/goal.js +417 -0
  148. package/src/commands/hub.js +7 -0
  149. package/src/harness/prompt-compressor.js +71 -1
  150. package/src/index.js +4 -0
  151. package/src/lib/agent-core.js +1 -0
  152. package/src/lib/checkpoint-store.js +523 -0
  153. package/src/lib/goal-assess.js +228 -0
  154. package/src/lib/goal-context.js +87 -0
  155. package/src/lib/goal-store.js +341 -0
  156. package/src/repl/agent-repl.js +43 -7
  157. package/src/runtime/agent-core.js +267 -1
  158. package/src/runtime/headless-runner.js +147 -0
  159. package/src/runtime/headless-stream.js +34 -0
  160. package/src/runtime/mcp-config.js +139 -0
  161. package/src/runtime/policies/agent-policy.js +1 -0
  162. package/src/assets/web-panel/assets/ChatBubbleRenderer-C-svYkrC.js +0 -1
  163. package/src/assets/web-panel/assets/MobileProjects-zr-PpsT_.js +0 -1
  164. package/src/assets/web-panel/assets/OrderTableRenderer-ISp6btRY.js +0 -1
  165. package/src/assets/web-panel/assets/Projects-ll5wnj2L.js +0 -1
  166. package/src/assets/web-panel/assets/Skills-OQNky3uI.js +0 -1
  167. package/src/assets/web-panel/assets/Tasks-C9R8sgyi.js +0 -1
  168. package/src/assets/web-panel/assets/devWarning-BDK34w0I.js +0 -1
  169. package/src/assets/web-panel/assets/index-B6SaRuCI.js +0 -1
  170. package/src/assets/web-panel/assets/index-B9ekWb3I.js +0 -1
@@ -0,0 +1,228 @@
1
+ /**
2
+ * goal-assess — run-end LLM self-assessment of goal progress (cc goal Phase 2).
3
+ *
4
+ * After an agent run bound to a goal (--goal-assess), an LLM is asked to judge,
5
+ * from the run transcript, whether the goal advanced — and to propose key-result
6
+ * updates, a one-line progress note, and any concerns. The proposal is then
7
+ * persisted through goal-store (progress / key results / agent note / drift).
8
+ *
9
+ * This is OPT-IN because it spends extra tokens (one bonus completion per run).
10
+ * The LLM call is injected as `chat(promptString) => Promise<string>` so the
11
+ * orchestration is deterministically unit-testable without a provider.
12
+ *
13
+ * Parsing is deliberately tolerant: models wrap JSON in prose / code fences, so
14
+ * we extract the first balanced `{...}` block and validate the fields we use.
15
+ */
16
+
17
+ import {
18
+ recordProgress,
19
+ setKeyResult,
20
+ addDriftFlags,
21
+ getGoal,
22
+ } from "./goal-store.js";
23
+
24
+ /** Bound the transcript we feed the judge so the assessment stays cheap. */
25
+ const MAX_FINAL_TEXT = 2000;
26
+ const MAX_TOOLS = 40;
27
+
28
+ /**
29
+ * Build the assessment prompt. Asks for a strict JSON object describing whether
30
+ * the goal advanced + proposed updates.
31
+ * @param {object} goal
32
+ * @param {object} transcript { prompt, finalText, toolCalls:[{tool,args}] }
33
+ */
34
+ export function buildAssessPrompt(goal, transcript = {}) {
35
+ const krLines = (goal.keyResults || [])
36
+ .map(
37
+ (k) =>
38
+ ` - id=${k.id} | "${k.text}"` +
39
+ (k.target != null ? ` | ${k.current ?? 0}/${k.target}` : "") +
40
+ (k.done ? " | DONE" : ""),
41
+ )
42
+ .join("\n");
43
+
44
+ const tools = (transcript.toolCalls || [])
45
+ .slice(0, MAX_TOOLS)
46
+ .map((t) => t.tool)
47
+ .join(", ");
48
+
49
+ const finalText = String(transcript.finalText || "").slice(0, MAX_FINAL_TEXT);
50
+
51
+ return [
52
+ "You are assessing whether a single agent run advanced a long-running goal.",
53
+ "",
54
+ `GOAL: ${goal.objective}`,
55
+ `CURRENT PROGRESS: ${goal.progress ?? 0}%`,
56
+ goal.keyResults?.length
57
+ ? `KEY RESULTS:\n${krLines}`
58
+ : "KEY RESULTS: (none)",
59
+ "",
60
+ `RUN TASK: ${String(transcript.prompt || "").slice(0, 500)}`,
61
+ tools ? `TOOLS USED: ${tools}` : "TOOLS USED: (none)",
62
+ `RUN RESULT:\n${finalText || "(no final text)"}`,
63
+ "",
64
+ "Reply with ONLY a JSON object (no prose, no code fence) of this shape:",
65
+ "{",
66
+ ' "advanced": true|false, // did this run move the goal forward?',
67
+ ' "progress": <0-100 or null>, // your estimate of new overall progress, or null to leave unchanged',
68
+ ' "keyResults": [ // updates for specific key results (omit if none)',
69
+ ' {"id": "<kr-id>", "current": <number or null>, "done": true|false}',
70
+ " ],",
71
+ ' "note": "<one short sentence summarizing what changed>",',
72
+ ' "concerns": ["<short concern>", ...] // anything blocking/at-risk (omit if none)',
73
+ "}",
74
+ ].join("\n");
75
+ }
76
+
77
+ /**
78
+ * Tolerantly parse the judge's reply into a normalized assessment, or null.
79
+ * @param {string} text
80
+ * @returns {{advanced:boolean, progress:number|null, keyResults:object[], note:string, concerns:string[]}|null}
81
+ */
82
+ export function parseAssessment(text) {
83
+ if (!text || typeof text !== "string") return null;
84
+ const json = extractFirstJsonObject(text);
85
+ if (!json) return null;
86
+ let obj;
87
+ try {
88
+ obj = JSON.parse(json);
89
+ } catch {
90
+ return null;
91
+ }
92
+ if (!obj || typeof obj !== "object") return null;
93
+
94
+ const progress =
95
+ obj.progress == null || obj.progress === "" ? null : clampPct(obj.progress);
96
+
97
+ const keyResults = Array.isArray(obj.keyResults)
98
+ ? obj.keyResults
99
+ .filter((k) => k && k.id)
100
+ .map((k) => ({
101
+ id: String(k.id),
102
+ current:
103
+ k.current == null || k.current === "" ? null : Number(k.current),
104
+ done: k.done === true,
105
+ }))
106
+ : [];
107
+
108
+ const concerns = Array.isArray(obj.concerns)
109
+ ? obj.concerns.map((c) => String(c)).filter(Boolean)
110
+ : [];
111
+
112
+ return {
113
+ advanced: obj.advanced === true,
114
+ progress,
115
+ keyResults,
116
+ note: obj.note ? String(obj.note) : "",
117
+ concerns,
118
+ };
119
+ }
120
+
121
+ function clampPct(v) {
122
+ const n = Math.round(Number(v));
123
+ if (!Number.isFinite(n)) return null;
124
+ return Math.max(0, Math.min(100, n));
125
+ }
126
+
127
+ /** Extract the first balanced top-level {...} block from text (fence-tolerant). */
128
+ function extractFirstJsonObject(text) {
129
+ const start = text.indexOf("{");
130
+ if (start < 0) return null;
131
+ let depth = 0;
132
+ let inStr = false;
133
+ let esc = false;
134
+ for (let i = start; i < text.length; i++) {
135
+ const ch = text[i];
136
+ if (inStr) {
137
+ if (esc) esc = false;
138
+ else if (ch === "\\") esc = true;
139
+ else if (ch === '"') inStr = false;
140
+ continue;
141
+ }
142
+ if (ch === '"') inStr = true;
143
+ else if (ch === "{") depth++;
144
+ else if (ch === "}") {
145
+ depth--;
146
+ if (depth === 0) return text.slice(start, i + 1);
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+
152
+ /**
153
+ * Persist a parsed assessment to the goal via goal-store. Each sub-update is
154
+ * isolated (a bad key-result id can't void the progress note). Returns the
155
+ * final goal state.
156
+ * @param {string} goalId
157
+ * @param {object} a output of parseAssessment
158
+ * @param {object} [opts] { root }
159
+ */
160
+ export function applyAssessment(goalId, a, opts = {}) {
161
+ if (!a) return getGoal(goalId, opts);
162
+
163
+ // Key-result updates first (they may drive derived progress).
164
+ for (const kr of a.keyResults || []) {
165
+ try {
166
+ setKeyResult(goalId, kr.id, { current: kr.current, done: kr.done }, opts);
167
+ } catch {
168
+ /* unknown / stale key-result id — skip, don't void the rest */
169
+ }
170
+ }
171
+
172
+ // Explicit progress and/or the agent note.
173
+ if (a.progress != null || a.note) {
174
+ try {
175
+ recordProgress(
176
+ goalId,
177
+ { pct: a.progress, note: a.note, by: "agent" },
178
+ opts,
179
+ );
180
+ } catch {
181
+ /* best-effort */
182
+ }
183
+ }
184
+
185
+ // Drift: a no-advance run, plus any explicit concerns.
186
+ const flags = [];
187
+ if (!a.advanced) {
188
+ flags.push({ kind: "no-progress", detail: "run did not advance the goal" });
189
+ }
190
+ for (const c of a.concerns || []) flags.push({ kind: "concern", detail: c });
191
+ if (flags.length) {
192
+ try {
193
+ addDriftFlags(goalId, flags, opts);
194
+ } catch {
195
+ /* best-effort */
196
+ }
197
+ }
198
+
199
+ return getGoal(goalId, opts);
200
+ }
201
+
202
+ /**
203
+ * Full orchestration: prompt → chat → parse → apply. Returns
204
+ * `{ assessment, goal }` (assessment is null when the judge gave no usable JSON).
205
+ * @param {object} params { goal, transcript, chat, opts }
206
+ * chat: (promptString) => Promise<string>
207
+ */
208
+ export async function assessGoalProgress({
209
+ goal,
210
+ transcript,
211
+ chat,
212
+ opts = {},
213
+ }) {
214
+ if (!goal || typeof chat !== "function") {
215
+ return { assessment: null, goal: goal || null };
216
+ }
217
+ const prompt = buildAssessPrompt(goal, transcript);
218
+ let reply;
219
+ try {
220
+ reply = await chat(prompt);
221
+ } catch {
222
+ return { assessment: null, goal };
223
+ }
224
+ const assessment = parseAssessment(reply);
225
+ if (!assessment) return { assessment: null, goal };
226
+ const updated = applyAssessment(goal.id, assessment, opts);
227
+ return { assessment, goal: updated };
228
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * goal-context — turn-scoped injection of the active goal into the agent loop.
3
+ *
4
+ * When a goal is bound to a run, a compact reminder of its objective + open key
5
+ * results is supplemented into each LLM call (via agent-core's `prepareCall`
6
+ * seam), so every turn is measured against the goal without polluting the
7
+ * persistent message history.
8
+ *
9
+ * IMPORTANT: this composes WITH the existing `defaultPrepareCall` (turn-context)
10
+ * rather than replacing it — both suffixes are concatenated. Keep the produced
11
+ * text terse: it is re-sent on every iteration and is billed each time.
12
+ */
13
+
14
+ /** Hard cap on the open key results we list, to bound per-turn token cost. */
15
+ const MAX_KEY_RESULTS = 8;
16
+
17
+ /**
18
+ * Build the compact system-prompt supplement for a goal, or null when the goal
19
+ * is missing / not active.
20
+ * @param {object|null} goal
21
+ * @returns {string|null}
22
+ */
23
+ export function buildGoalContext(goal) {
24
+ if (!goal || goal.status !== "active") return null;
25
+ const objective = String(goal.objective || "").trim();
26
+ if (!objective) return null;
27
+
28
+ const pct = Number.isFinite(goal.progress) ? goal.progress : 0;
29
+ const lines = [`Active goal (${pct}% complete): ${objective}`];
30
+
31
+ const open = (goal.keyResults || []).filter((k) => k && !k.done);
32
+ for (const kr of open.slice(0, MAX_KEY_RESULTS)) {
33
+ const target =
34
+ kr.target != null ? ` [${kr.current ?? 0}/${kr.target}]` : "";
35
+ lines.push(`- key result: ${kr.text}${target}`);
36
+ }
37
+ if (open.length > MAX_KEY_RESULTS) {
38
+ lines.push(`- (+${open.length - MAX_KEY_RESULTS} more key results)`);
39
+ }
40
+
41
+ lines.push(
42
+ "Each turn, prefer actions that advance these key results; if you must diverge, briefly say why.",
43
+ );
44
+ return lines.join("\n");
45
+ }
46
+
47
+ /**
48
+ * A `prepareCall`-shaped function bound to a goal. Defensive: never throws
49
+ * (agent-core swallows prepareCall errors, but we avoid relying on that).
50
+ * @param {object|null} goal
51
+ * @returns {(ctx:object) => ({systemSuffix:string}|null)}
52
+ */
53
+ export function goalPrepareCall(goal) {
54
+ return () => {
55
+ try {
56
+ const suffix = buildGoalContext(goal);
57
+ return suffix ? { systemSuffix: suffix } : null;
58
+ } catch {
59
+ return null;
60
+ }
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Compose several `prepareCall` functions into one. Each is invoked per turn
66
+ * and their non-empty `systemSuffix` strings are concatenated. A failing
67
+ * member is skipped, never fatal.
68
+ * @param {Array<Function|null|undefined>} fns
69
+ * @returns {(ctx:object) => Promise<{systemSuffix:string}|null>}
70
+ */
71
+ export function composePrepareCall(fns) {
72
+ const list = (fns || []).filter((f) => typeof f === "function");
73
+ return async (ctx) => {
74
+ const parts = [];
75
+ for (const fn of list) {
76
+ try {
77
+ const r = await fn(ctx);
78
+ if (r && typeof r.systemSuffix === "string" && r.systemSuffix.trim()) {
79
+ parts.push(r.systemSuffix);
80
+ }
81
+ } catch {
82
+ /* a failing member must not break the turn */
83
+ }
84
+ }
85
+ return parts.length ? { systemSuffix: parts.join("\n\n") } : null;
86
+ };
87
+ }
@@ -0,0 +1,341 @@
1
+ /**
2
+ * goal-store — cross-session persistent goals / OKRs for `cc goal`.
3
+ *
4
+ * Unlike a session (short-lived context) or a checkpoint (file state), a goal
5
+ * is a long-lived objective the agent should advance toward across many
6
+ * sessions. This is the standalone store + ops; wiring the goal into the agent
7
+ * loop (so each turn is measured against it) lives in goal-context.js.
8
+ *
9
+ * On-disk layout (under <home>/goals, overridable via opts.root for tests):
10
+ * <root>/<id>.json one goal per file
11
+ *
12
+ * Distinct from:
13
+ * - cc session (short-term conversation context)
14
+ * - cc memory (durable facts, not objectives)
15
+ * - cc planmode (a single run's plan, not a cross-session objective)
16
+ * - cc workflow (execution orchestration, not intent)
17
+ */
18
+
19
+ import fs from "node:fs";
20
+ import path from "node:path";
21
+ import { getHomeDir } from "./paths.js";
22
+
23
+ /** Valid goal lifecycle states. `active` goals are the ones injected. */
24
+ export const GOAL_STATUS = Object.freeze({
25
+ ACTIVE: "active",
26
+ PAUSED: "paused",
27
+ DONE: "done",
28
+ ABANDONED: "abandoned",
29
+ });
30
+
31
+ const STATUS_VALUES = new Set(Object.values(GOAL_STATUS));
32
+
33
+ function defaultRoot() {
34
+ return path.join(getHomeDir(), "goals");
35
+ }
36
+
37
+ function ensureDir(dir) {
38
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
39
+ }
40
+
41
+ function newId(prefix) {
42
+ // Date.now/random are fine here (plain CLI lib, not a resumable workflow).
43
+ const rand = Math.random().toString(36).slice(2, 8);
44
+ return `${prefix}-${Date.now()}-${rand}`;
45
+ }
46
+
47
+ function nowIso() {
48
+ return new Date().toISOString();
49
+ }
50
+
51
+ function goalFile(root, id) {
52
+ return path.join(root, `${id}.json`);
53
+ }
54
+
55
+ /** Clamp a value to a 0–100 integer percentage, or null when not a number. */
56
+ function clampPct(v) {
57
+ if (v == null || v === "") return null;
58
+ const n = Math.round(Number(v));
59
+ if (!Number.isFinite(n)) return null;
60
+ return Math.max(0, Math.min(100, n));
61
+ }
62
+
63
+ /**
64
+ * Derive a 0–100 progress from completed key results. Returns null when there
65
+ * are no key results (caller may keep a manually-set progress instead).
66
+ */
67
+ function derivedProgress(keyResults) {
68
+ if (!Array.isArray(keyResults) || keyResults.length === 0) return null;
69
+ const done = keyResults.filter((k) => k.done).length;
70
+ return Math.round((done / keyResults.length) * 100);
71
+ }
72
+
73
+ /**
74
+ * Create a goal.
75
+ * @param {object} input { objective, title, keyResults?: string[]|object[] }
76
+ * @param {object} [opts] { root }
77
+ * @returns {object} the persisted goal
78
+ */
79
+ export function createGoal(input = {}, opts = {}) {
80
+ const root = opts.root || defaultRoot();
81
+ const objective = String(input.objective || "").trim();
82
+ if (!objective) {
83
+ throw new Error("createGoal requires an objective");
84
+ }
85
+ const id = input.id || newId("goal");
86
+ const keyResults = (input.keyResults || []).map((kr) => normalizeKr(kr));
87
+ const goal = {
88
+ id,
89
+ title: String(input.title || objective).trim(),
90
+ objective,
91
+ keyResults,
92
+ status: GOAL_STATUS.ACTIVE,
93
+ progress: derivedProgress(keyResults) ?? 0,
94
+ linkedSessions: [],
95
+ notes: [],
96
+ drift: { lastProgressAt: null, flags: [] },
97
+ createdAt: nowIso(),
98
+ updatedAt: nowIso(),
99
+ };
100
+ ensureDir(root);
101
+ fs.writeFileSync(goalFile(root, id), JSON.stringify(goal, null, 2), "utf-8");
102
+ return goal;
103
+ }
104
+
105
+ function normalizeKr(kr) {
106
+ if (typeof kr === "string") {
107
+ return {
108
+ id: newId("kr"),
109
+ text: kr.trim(),
110
+ target: null,
111
+ current: 0,
112
+ done: false,
113
+ };
114
+ }
115
+ return {
116
+ id: kr.id || newId("kr"),
117
+ text: String(kr.text || "").trim(),
118
+ target: kr.target == null ? null : Number(kr.target),
119
+ current: Number(kr.current) || 0,
120
+ done: !!kr.done,
121
+ };
122
+ }
123
+
124
+ /** Load a goal by id, or null. */
125
+ export function getGoal(id, opts = {}) {
126
+ const root = opts.root || defaultRoot();
127
+ const file = goalFile(root, id);
128
+ if (!fs.existsSync(file)) return null;
129
+ try {
130
+ return JSON.parse(fs.readFileSync(file, "utf-8"));
131
+ } catch {
132
+ return null;
133
+ }
134
+ }
135
+
136
+ function saveGoal(goal, opts = {}) {
137
+ const root = opts.root || defaultRoot();
138
+ ensureDir(root);
139
+ goal.updatedAt = nowIso();
140
+ fs.writeFileSync(
141
+ goalFile(root, goal.id),
142
+ JSON.stringify(goal, null, 2),
143
+ "utf-8",
144
+ );
145
+ return goal;
146
+ }
147
+
148
+ /** List goals, newest first. Optionally filter by status. */
149
+ export function listGoals(opts = {}) {
150
+ const root = opts.root || defaultRoot();
151
+ if (!fs.existsSync(root)) return [];
152
+ const out = [];
153
+ for (const name of fs.readdirSync(root)) {
154
+ if (!name.endsWith(".json")) continue;
155
+ const g = getGoal(name.slice(0, -5), { root });
156
+ if (!g) continue;
157
+ if (opts.status && g.status !== opts.status) continue;
158
+ out.push(g);
159
+ }
160
+ return out.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
161
+ }
162
+
163
+ /** Mutate a goal via `fn(goal)`; throws if not found. */
164
+ function mutate(id, fn, opts = {}) {
165
+ const goal = getGoal(id, opts);
166
+ if (!goal) throw new Error(`no such goal: ${id}`);
167
+ fn(goal);
168
+ return saveGoal(goal, opts);
169
+ }
170
+
171
+ /** Add a key result to a goal. */
172
+ export function addKeyResult(id, text, krOpts = {}, opts = {}) {
173
+ return mutate(
174
+ id,
175
+ (g) => {
176
+ g.keyResults.push(normalizeKr({ text, target: krOpts.target }));
177
+ const dp = derivedProgress(g.keyResults);
178
+ if (dp != null) g.progress = dp;
179
+ },
180
+ opts,
181
+ );
182
+ }
183
+
184
+ /** Update a key result (current value and/or done flag). */
185
+ export function setKeyResult(id, krId, patch = {}, opts = {}) {
186
+ return mutate(
187
+ id,
188
+ (g) => {
189
+ const kr = g.keyResults.find((k) => k.id === krId);
190
+ if (!kr) throw new Error(`no such key result: ${krId}`);
191
+ if (patch.current != null) kr.current = Number(patch.current);
192
+ if (patch.done != null) kr.done = !!patch.done;
193
+ if (
194
+ kr.target != null &&
195
+ patch.current != null &&
196
+ Number(patch.current) >= kr.target
197
+ ) {
198
+ kr.done = true;
199
+ }
200
+ const dp = derivedProgress(g.keyResults);
201
+ if (dp != null) g.progress = dp;
202
+ g.drift.lastProgressAt = nowIso();
203
+ },
204
+ opts,
205
+ );
206
+ }
207
+
208
+ /**
209
+ * Record progress: set an explicit percentage and/or append a note.
210
+ * @param {object} input { pct?, note?, by? }
211
+ */
212
+ export function recordProgress(id, input = {}, opts = {}) {
213
+ return mutate(
214
+ id,
215
+ (g) => {
216
+ const pct = clampPct(input.pct);
217
+ if (pct != null) g.progress = pct;
218
+ if (input.note) {
219
+ g.notes.push({
220
+ at: nowIso(),
221
+ text: String(input.note),
222
+ by: input.by === "agent" ? "agent" : "user",
223
+ });
224
+ }
225
+ g.drift.lastProgressAt = nowIso();
226
+ },
227
+ opts,
228
+ );
229
+ }
230
+
231
+ /** Attach a session id to a goal (idempotent). */
232
+ export function linkSession(id, sessionId, opts = {}) {
233
+ if (!sessionId) throw new Error("linkSession requires a sessionId");
234
+ return mutate(
235
+ id,
236
+ (g) => {
237
+ if (!g.linkedSessions.includes(sessionId)) {
238
+ g.linkedSessions.push(sessionId);
239
+ }
240
+ },
241
+ opts,
242
+ );
243
+ }
244
+
245
+ /** Detach a session id from a goal. */
246
+ export function unlinkSession(id, sessionId, opts = {}) {
247
+ return mutate(
248
+ id,
249
+ (g) => {
250
+ g.linkedSessions = g.linkedSessions.filter((s) => s !== sessionId);
251
+ },
252
+ opts,
253
+ );
254
+ }
255
+
256
+ /** Set a goal's status (active/paused/done/abandoned). */
257
+ export function setStatus(id, status, opts = {}) {
258
+ if (!STATUS_VALUES.has(status)) {
259
+ throw new Error(
260
+ `invalid status "${status}" — expected one of: ${[...STATUS_VALUES].join(", ")}`,
261
+ );
262
+ }
263
+ return mutate(
264
+ id,
265
+ (g) => {
266
+ g.status = status;
267
+ if (status === GOAL_STATUS.DONE && g.progress < 100) g.progress = 100;
268
+ },
269
+ opts,
270
+ );
271
+ }
272
+
273
+ /**
274
+ * Append drift flags to a goal's `drift.flags` list. Each flag is normalized to
275
+ * `{ at, kind, detail }`. Used by the run-end assessment (cc goal Phase 2) to
276
+ * record "no progress this run" / concern signals. Capped at 20 (newest kept).
277
+ * @param {string} id
278
+ * @param {Array<string|object>} flags
279
+ */
280
+ export function addDriftFlags(id, flags, opts = {}) {
281
+ const list = (Array.isArray(flags) ? flags : [flags]).filter(Boolean);
282
+ if (list.length === 0) return getGoal(id, opts);
283
+ return mutate(
284
+ id,
285
+ (g) => {
286
+ for (const f of list) {
287
+ const flag =
288
+ typeof f === "string"
289
+ ? { at: nowIso(), kind: "concern", detail: f }
290
+ : {
291
+ at: nowIso(),
292
+ kind: f.kind || "concern",
293
+ detail: f.detail || "",
294
+ };
295
+ g.drift.flags.push(flag);
296
+ }
297
+ // Keep only the most recent 20 to bound the file size.
298
+ if (g.drift.flags.length > 20) {
299
+ g.drift.flags = g.drift.flags.slice(-20);
300
+ }
301
+ },
302
+ opts,
303
+ );
304
+ }
305
+
306
+ /** Delete a goal. Returns true if it existed. */
307
+ export function deleteGoal(id, opts = {}) {
308
+ const root = opts.root || defaultRoot();
309
+ const file = goalFile(root, id);
310
+ if (!fs.existsSync(file)) return false;
311
+ fs.rmSync(file);
312
+ return true;
313
+ }
314
+
315
+ /**
316
+ * Resolve the goal that should be bound to the current run, in priority order:
317
+ * 1. explicit id (--goal <id>)
318
+ * 2. an active goal linked to the current session
319
+ * 3. when exactly one active goal exists, that one
320
+ * 4. null
321
+ *
322
+ * Only `active` goals are ever auto-resolved (steps 2–3); an explicit id is
323
+ * honored regardless of status so a user can re-inspect a paused goal.
324
+ *
325
+ * @param {object} [sel] { explicitId, sessionId }
326
+ * @param {object} [opts] { root }
327
+ */
328
+ export function resolveActiveGoal(sel = {}, opts = {}) {
329
+ if (sel.explicitId) {
330
+ return getGoal(sel.explicitId, opts);
331
+ }
332
+ const active = listGoals({ ...opts, status: GOAL_STATUS.ACTIVE });
333
+ if (active.length === 0) return null;
334
+ if (sel.sessionId) {
335
+ const linked = active.find((g) => g.linkedSessions.includes(sel.sessionId));
336
+ if (linked) return linked;
337
+ }
338
+ if (active.length === 1) return active[0];
339
+ // Ambiguous (multiple active, none linked) — caller must pick explicitly.
340
+ return null;
341
+ }