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.
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-BqWP6FKu.js → AIOps-3TazCYWE.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CXwMgOvX.js → ActionButton-DUPN0PST.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DAebZ4IY.js → Analytics-CemvhkzD.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CYsqYoME.js → AppLayout-BL_tAU3M.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-BbTtX1Nf.js → Audit-Dl9l-cxF.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DgqY2Eb-.js → Backup-BKDDX75m.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Cq2ZuSoO.js → BaseInput-CDYePvMI.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-D2kqpUyO.js → Chat-CGtR0sg3.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DZjc9uKn.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-_9swHpyc.js → Checkbox-CwYIHOOo.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Cr9YbCPl.js → Codegen-CIF5tbtd.js} +1 -1
- package/src/assets/web-panel/assets/{Col--wdpCMxx.js → Col-z7d4kxeP.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DuFcVnLu.js → Community-DUlDrqF7.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-1yzYeT04.js → Compact-CJ1o8QQR.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Dq3aU9Df.js → Compliance-D3i9d_uO.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CrWcnIg8.js → Cowork-Wm7JTkfB.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-Bh6fKZ0h.js → Cron-B0QnHhZx.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-8ofPaWVW.js → Crosschain-3yPrnNgd.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D3EiYm3w.js → DID-cfdkiDWF.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BFjEdFne.js → Dashboard-DFkgM4gT.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-pYVPcP6O.js → Dropdown-YYWE81DL.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-zBPodwJ1.js → EmailListRenderer-BXfHK1Bn.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CyQTW6PW.js → FamilyGuardDashboard-DInUxJ2G.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Ctaq3zYq.js → Federation-DNUYeFsv.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CWYJCLq1.js → FormItemContext-Cr7eVEBB.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B1g6t9R9.js → GenericCardRenderer-_gF4cmDa.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DH-v8iwd.js → Git-BqldmUbO.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-jZxXvOs5.js → Governance-BF59ZiQ8.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-D07LRghn.js → Inference-Cy7y1eb9.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DnGtRZhx.js → KnowledgeGraph-B3fVocTO.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-D2pM9C4W.js → Logs-BDirsUVk.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-UyIO7C7r.js → Marketplace-GhXpZgp2.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Bf1gvZPf.js → McpTools-0VvfIhKx.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-C1bWj4RN.js → Memory-CJLBgAUT.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-C_Ot1H_a.js → MobileBridge-BMedY9Yg.js} +2 -2
- package/src/assets/web-panel/assets/MobileProjects-mdohgRlL.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-CnzFUz5J.js → Mtc-CgEuUg0g.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CAAh99wz.js → MtcAudit-1pWNe_xi.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-D6IAg6HE.js → Multisig-DPIQ7oZL.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BFMarxb0.js → NLProgramming-W__P_P4Z.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BRp9ro3t.js → Notes-C_MCDhFk.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C0Au3Cxb.js → NotificationSettings-CDFotapL.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Dtht0cEs.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DYoxLBRX.js → Organization-D6lMumhD.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-rO8JJWGJ.js → Overflow-BMOvUMW6.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DJleeXIK.js → P2P-DsQTEw1t.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DM5qghFp.js → PdhVaultBrowser-CncRtN1Z.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D5v4Beya.js → Permissions-DDC-DkUl.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-c2ZTX0Pv.js → PersonalDataHub-DVKY_NnT.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-Crrkyhpz.js → Pipeline-C7oDVTl-.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DZVyrJKa.js → Privacy-DReGvTEJ.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DKg7J0gz.js → ProjectInit-C-j2dzxJ.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-3ndmTvVH.js → ProjectSettings-DcUsvFnc.js} +2 -2
- package/src/assets/web-panel/assets/Projects-jSjWnmr6.js +1 -0
- package/src/assets/web-panel/assets/{Providers-BeqBVMhB.js → Providers-DIpohWG5.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DKAAxzuA.js → QuickAsk-DdvLtpEU.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Byu7IGei.js → Recommend-DPAi2zo3.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-BKhWAmCu.js → Reputation-DJD7qXSI.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BFtn11O6.js → Row-XERdPDHk.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D5a0PT0k.js → RssFeed-Cl_VlCLg.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DAkuaZNe.js → Search-C-poG9P5.js} +1 -1
- package/src/assets/web-panel/assets/{Security-C79Ml2Ms.js → Security-DjjCrw8v.js} +2 -2
- package/src/assets/web-panel/assets/{Services-BBk_jH6-.js → Services-BuWeB4YJ.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Cy0VvL0M.js → Skeleton-VZXOKwC_.js} +1 -1
- package/src/assets/web-panel/assets/Skills-B76ONTfP.js +1 -0
- package/src/assets/web-panel/assets/{Sla-CbX1f8xN.js → Sla-DIj1KREq.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BIkoUjws.js → SpeechSettings-BrAp3Yk3.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DG6Swk7G.js → SyncSettings--mJcpccF.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-DM8cMr83.js +1 -0
- package/src/assets/web-panel/assets/{Templates-AaJPeCIz.js → Templates-kOBK6m1Z.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-jVFRofww.js → Tenant-BjSzYPzn.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DHBMzfK6.js → Terminal-DwpY-Ay7.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-9RFfOHSI.js → TimelineRenderer-aoI0DazM.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-ZTfwuABF.js → Tokens-YwE0LqSZ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Xo7uZNQs.js → Trigger-CwSKzvlX.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-C0cTPYvn.js → Trust-B__Jqdzn.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DmMKio71.js → UkeySign-mty0jwmx.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DP7B-oGT.js → VideoEditing-Ddsx_OQ6.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-B1kZDARo.js → Wallet-D4Q8yXZm.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Bo5kBx27.js → WebAuthn-CLUaKUr5.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DGI9SNHH.js → WorkflowEditor-Di5pOaeC.js} +1 -1
- package/src/assets/web-panel/assets/{chat-y97W1CIG.js → chat-CELatHkT.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DtTNo0sH.js → colors-CawDLjXV.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-D0q0exuS.js → compact-item-DeMp-K0j.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D7pLFs2I.js → createContext-zY9kXivd.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-zLjV7g6r.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CXjG5B2j.js → hasIn-VEBMW8E4.js} +1 -1
- package/src/assets/web-panel/assets/{index-TrBGgrwG.js → index-8kqE_cVD.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_4WcI1V.js → index-B8AZpx7d.js} +1 -1
- package/src/assets/web-panel/assets/{index-YWOEx3rP.js → index-BFc0vBN9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqVjUN8b.js → index-BVkrfyuk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnLrbXDA.js → index-BfGGKoo8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKrbutAQ.js → index-BjctklSd.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFsPe2N7.js → index-BqJ2r12F.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSdhC7Qo.js → index-C0GhuYLk.js} +1 -1
- package/src/assets/web-panel/assets/index-CDtUWCtX.js +1 -0
- package/src/assets/web-panel/assets/{index-BO644Q4S.js → index-CHqvj9uz.js} +1 -1
- package/src/assets/web-panel/assets/{index-gFLQe31v.js → index-CHxHLv2b.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgyrM0UN.js → index-CWbbB1MI.js} +1 -1
- package/src/assets/web-panel/assets/{index-1dwtkcJv.js → index-CbJZzK9B.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3y_4OdG.js → index-CfZV3FXN.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dr45Nm9V.js → index-Cr7lnIeI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkGFqlYX.js → index-CtLZammH.js} +1 -1
- package/src/assets/web-panel/assets/{index-6np5ESBM.js → index-CtoauqWt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BU944DeT.js → index-CyeYs7SG.js} +1 -1
- package/src/assets/web-panel/assets/{index-POaFzYGS.js → index-DALuVdhu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjCawXk1.js → index-DClGYjBM.js} +1 -1
- package/src/assets/web-panel/assets/{index-8jxbZupG.js → index-DPHe9NYG.js} +1 -1
- package/src/assets/web-panel/assets/{index-_3wPBMKt.js → index-DSiL_W2n.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cbqu804A.js → index-DXNe_zIP.js} +1 -1
- package/src/assets/web-panel/assets/{index-EaIfumgW.js → index-DhsfyHcg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzCPx1cq.js → index-Dna2psGz.js} +1 -1
- package/src/assets/web-panel/assets/{index-kvV0f4tV.js → index-GRNVdvoA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdhEYW2a.js → index-JseP3-5X.js} +1 -1
- package/src/assets/web-panel/assets/{index-aarO4HT9.js → index-KcOEkUCM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgmvrPJH.js → index-S9JZDSaa.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY6KLlgG.js → index-SrQIPYq8.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ct6xtKkc.js → index-TfXODan7.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_hjkMtX.js → index-V3K9gvKR.js} +1 -1
- package/src/assets/web-panel/assets/{index-4mWZhCzz.js → index-VJnHvkv2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJUf19Wd.js → index-XFyv3Sg_.js} +3 -3
- package/src/assets/web-panel/assets/{index-B4dPdrvC.js → index-b3ZuAreb.js} +1 -1
- package/src/assets/web-panel/assets/index-d_RPqH7u.js +1 -0
- package/src/assets/web-panel/assets/{index-BPXhU-jp.js → index-fBNVDEf2.js} +1 -1
- package/src/assets/web-panel/assets/{index-bVJvqDAz.js → index-u_1aiNTA.js} +1 -1
- package/src/assets/web-panel/assets/{index-qoB3whR9.js → index-vaD1iHg5.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BnXISaAa.js → initDefaultProps-Sd7Eayz4.js} +1 -1
- package/src/assets/web-panel/assets/{motion-ChY7C0zJ.js → motion-DlToY72q.js} +1 -1
- package/src/assets/web-panel/assets/{move-ByFZMFM5.js → move-DvS7EmAP.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BYeliY1H.js → omit-CzLq4QKW.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B9dcAKnu.js → pickAttrs-BcM75Jx_.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-D3F_txz7.js → placementArrow-B7xXXiwd.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-ClkwY7wS.js → responsiveObserve-CrYPRB-g.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BNgy2Eea.js → slide-CSYTtsRt.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Bv3heMCD.js → statusUtils-CeSuOVT_.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DVdlHbQm.js → styleChecker-KiQethca.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-alrRY5BK.js → useFlexGapSupport-CSQnQdiv.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CcVh0-Vu.js → useFs-Br8Kr1pr.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CkkHPhyq.js → usePersonalDataHub-DGJtDcMm.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DWi0X9WN.js → vnode-C-jVtGka.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DCbqxxLH.js → zoom-CeWySTPF.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +47 -0
- package/src/commands/checkpoint.js +253 -53
- package/src/commands/compact.js +150 -0
- package/src/commands/goal.js +417 -0
- package/src/commands/hub.js +7 -0
- package/src/harness/prompt-compressor.js +71 -1
- package/src/index.js +4 -0
- package/src/lib/agent-core.js +1 -0
- package/src/lib/checkpoint-store.js +523 -0
- package/src/lib/goal-assess.js +228 -0
- package/src/lib/goal-context.js +87 -0
- package/src/lib/goal-store.js +341 -0
- package/src/repl/agent-repl.js +43 -7
- package/src/runtime/agent-core.js +267 -1
- package/src/runtime/headless-runner.js +147 -0
- package/src/runtime/headless-stream.js +34 -0
- package/src/runtime/mcp-config.js +139 -0
- package/src/runtime/policies/agent-policy.js +1 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-C-svYkrC.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-zr-PpsT_.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-ISp6btRY.js +0 -1
- package/src/assets/web-panel/assets/Projects-ll5wnj2L.js +0 -1
- package/src/assets/web-panel/assets/Skills-OQNky3uI.js +0 -1
- package/src/assets/web-panel/assets/Tasks-C9R8sgyi.js +0 -1
- package/src/assets/web-panel/assets/devWarning-BDK34w0I.js +0 -1
- package/src/assets/web-panel/assets/index-B6SaRuCI.js +0 -1
- package/src/assets/web-panel/assets/index-B9ekWb3I.js +0 -1
package/src/repl/agent-repl.js
CHANGED
|
@@ -92,8 +92,18 @@ async function executeTool(name, args) {
|
|
|
92
92
|
*/
|
|
93
93
|
async function agentLoop(messages, options) {
|
|
94
94
|
const usageEvents = [];
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
// The REPL runs its own auto-compaction (after each turn, with metrics +
|
|
96
|
+
// persisted compact events), so opt out of the agent loop's in-loop
|
|
97
|
+
// compaction to avoid compacting the same history twice.
|
|
98
|
+
for await (const event of coreAgentLoop(messages, {
|
|
99
|
+
autoCompact: false,
|
|
100
|
+
...options,
|
|
101
|
+
})) {
|
|
102
|
+
if (event.type === "checkpoint") {
|
|
103
|
+
process.stdout.write(
|
|
104
|
+
chalk.gray(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`),
|
|
105
|
+
);
|
|
106
|
+
} else if (event.type === "tool-executing") {
|
|
97
107
|
process.stdout.write(
|
|
98
108
|
chalk.gray(
|
|
99
109
|
` [${event.tool}] ${formatToolArgs(event.tool, event.args)}\n`,
|
|
@@ -158,6 +168,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
158
168
|
const additionalDirectories = Array.isArray(options.additionalDirectories)
|
|
159
169
|
? options.additionalDirectories
|
|
160
170
|
: [];
|
|
171
|
+
// Snapshot the work tree before each mutating tool (git engine) so the user
|
|
172
|
+
// can `cc checkpoint restore` to just before any tool call.
|
|
173
|
+
const autoCheckpoint = options.autoCheckpoint === true;
|
|
161
174
|
|
|
162
175
|
// --fallback-model: retry a turn's LLM call once on a backup model when the
|
|
163
176
|
// primary errors out (overload / network). Built once; passed into every
|
|
@@ -718,8 +731,10 @@ export async function startAgentRepl(options = {}) {
|
|
|
718
731
|
|
|
719
732
|
if (trimmed === "/compact") {
|
|
720
733
|
if (_compressor && messages.length > 3) {
|
|
721
|
-
const { messages: compacted, stats } =
|
|
722
|
-
|
|
734
|
+
const { messages: compacted, stats } = await _compressor.compress(
|
|
735
|
+
messages,
|
|
736
|
+
{ preserveToolPairs: true },
|
|
737
|
+
);
|
|
723
738
|
messages.length = 0;
|
|
724
739
|
messages.push(...compacted);
|
|
725
740
|
recordCompressionMetric(stats, {
|
|
@@ -1521,6 +1536,23 @@ export async function startAgentRepl(options = {}) {
|
|
|
1521
1536
|
try {
|
|
1522
1537
|
process.stdout.write("\n");
|
|
1523
1538
|
const iterationBudget = new IterationBudget({ owner: sessionId });
|
|
1539
|
+
// Bind a cross-session goal (cc goal) into this run, if one resolves.
|
|
1540
|
+
// Composes WITH defaultPrepareCall — never replaces it. Best-effort.
|
|
1541
|
+
let prepareCall = defaultPrepareCall;
|
|
1542
|
+
try {
|
|
1543
|
+
const { resolveActiveGoal } = await import("../lib/goal-store.js");
|
|
1544
|
+
const boundGoal = resolveActiveGoal({ sessionId });
|
|
1545
|
+
if (boundGoal) {
|
|
1546
|
+
const { goalPrepareCall, composePrepareCall } =
|
|
1547
|
+
await import("../lib/goal-context.js");
|
|
1548
|
+
prepareCall = composePrepareCall([
|
|
1549
|
+
defaultPrepareCall,
|
|
1550
|
+
goalPrepareCall(boundGoal),
|
|
1551
|
+
]);
|
|
1552
|
+
}
|
|
1553
|
+
} catch (_e) {
|
|
1554
|
+
/* goal binding is best-effort — fall back to defaultPrepareCall */
|
|
1555
|
+
}
|
|
1524
1556
|
const { content: response, usageEvents } = await agentLoop(messages, {
|
|
1525
1557
|
provider,
|
|
1526
1558
|
model: activeModel,
|
|
@@ -1531,7 +1563,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
1531
1563
|
sessionId,
|
|
1532
1564
|
cwd: process.cwd(),
|
|
1533
1565
|
additionalDirectories,
|
|
1534
|
-
|
|
1566
|
+
autoCheckpoint,
|
|
1567
|
+
checkpointSession: sessionId,
|
|
1568
|
+
prepareCall,
|
|
1535
1569
|
approvalGate: _approvalGate,
|
|
1536
1570
|
mcpClient: _bundleMcpClient || undefined,
|
|
1537
1571
|
chatFn: _fallbackChatFn,
|
|
@@ -1613,8 +1647,10 @@ export async function startAgentRepl(options = {}) {
|
|
|
1613
1647
|
_compressor.shouldAutoCompact(messages)
|
|
1614
1648
|
) {
|
|
1615
1649
|
try {
|
|
1616
|
-
const { messages: compacted, stats } =
|
|
1617
|
-
|
|
1650
|
+
const { messages: compacted, stats } = await _compressor.compress(
|
|
1651
|
+
messages,
|
|
1652
|
+
{ preserveToolPairs: true },
|
|
1653
|
+
);
|
|
1618
1654
|
messages.length = 0;
|
|
1619
1655
|
messages.push(...compacted);
|
|
1620
1656
|
recordCompressionMetric(stats, {
|
|
@@ -1663,7 +1663,21 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
1663
1663
|
throwIfAborted(signal);
|
|
1664
1664
|
|
|
1665
1665
|
if (provider === "ollama") {
|
|
1666
|
-
const
|
|
1666
|
+
const apiUrl = `${baseUrl}/api/chat`;
|
|
1667
|
+
// Real-time token deltas (Claude-Code `--include-partial-messages`): when
|
|
1668
|
+
// the caller supplies an onToken hook, stream the response and forward each
|
|
1669
|
+
// content chunk as it arrives. Tool calls + usage are accumulated and the
|
|
1670
|
+
// same {message, usage} shape is returned, so the agent loop is unchanged.
|
|
1671
|
+
// Without onToken we keep the cheaper single-shot non-streaming request.
|
|
1672
|
+
if (typeof options.onToken === "function") {
|
|
1673
|
+
return await _chatOllamaStreaming(
|
|
1674
|
+
apiUrl,
|
|
1675
|
+
{ model, messages, tools },
|
|
1676
|
+
options.onToken,
|
|
1677
|
+
signal,
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
const response = await fetch(apiUrl, {
|
|
1667
1681
|
method: "POST",
|
|
1668
1682
|
headers: { "Content-Type": "application/json" },
|
|
1669
1683
|
signal,
|
|
@@ -1817,6 +1831,109 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
1817
1831
|
return out;
|
|
1818
1832
|
}
|
|
1819
1833
|
|
|
1834
|
+
// ─── Ollama streaming (token deltas for --include-partial-messages) ─────────
|
|
1835
|
+
//
|
|
1836
|
+
// Ollama `/api/chat` with `stream:true` returns NDJSON: one JSON object per
|
|
1837
|
+
// line, each carrying an incremental `message.content` chunk, optional
|
|
1838
|
+
// `message.tool_calls` (emitted whole, not byte-streamed), and a final line
|
|
1839
|
+
// with `done:true` + `prompt_eval_count`/`eval_count` token totals. We reduce
|
|
1840
|
+
// the stream line-by-line so onToken fires live, then finalize into the same
|
|
1841
|
+
// {message, usage} shape the non-streaming branch returns.
|
|
1842
|
+
|
|
1843
|
+
function _ollamaInitState() {
|
|
1844
|
+
return {
|
|
1845
|
+
role: "assistant",
|
|
1846
|
+
content: "",
|
|
1847
|
+
toolCalls: null,
|
|
1848
|
+
promptEval: 0,
|
|
1849
|
+
evalCount: 0,
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
function _ollamaReduceLine(state, line, onToken) {
|
|
1854
|
+
const s = (line || "").trim();
|
|
1855
|
+
if (!s) return state;
|
|
1856
|
+
let obj;
|
|
1857
|
+
try {
|
|
1858
|
+
obj = JSON.parse(s);
|
|
1859
|
+
} catch {
|
|
1860
|
+
return state; // tolerate partial/garbage lines mid-stream
|
|
1861
|
+
}
|
|
1862
|
+
const msg = obj.message;
|
|
1863
|
+
if (msg) {
|
|
1864
|
+
if (msg.role) state.role = msg.role;
|
|
1865
|
+
if (typeof msg.content === "string" && msg.content) {
|
|
1866
|
+
state.content += msg.content;
|
|
1867
|
+
if (typeof onToken === "function") {
|
|
1868
|
+
try {
|
|
1869
|
+
onToken(msg.content);
|
|
1870
|
+
} catch {
|
|
1871
|
+
// A failing UI hook must never break the agent run.
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
if (Array.isArray(msg.tool_calls) && msg.tool_calls.length) {
|
|
1876
|
+
state.toolCalls = (state.toolCalls || []).concat(msg.tool_calls);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
if (obj.prompt_eval_count) state.promptEval = obj.prompt_eval_count;
|
|
1880
|
+
if (obj.eval_count) state.evalCount = obj.eval_count;
|
|
1881
|
+
return state;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
function _ollamaFinalize(state) {
|
|
1885
|
+
const message = { role: state.role, content: state.content };
|
|
1886
|
+
if (state.toolCalls && state.toolCalls.length) {
|
|
1887
|
+
message.tool_calls = state.toolCalls;
|
|
1888
|
+
}
|
|
1889
|
+
const data = { message };
|
|
1890
|
+
if (state.promptEval || state.evalCount) {
|
|
1891
|
+
data.usage = {
|
|
1892
|
+
input_tokens: state.promptEval,
|
|
1893
|
+
output_tokens: state.evalCount,
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
return data;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
/**
|
|
1900
|
+
* Pure reducer over an iterable of Ollama NDJSON lines. Exported for tests so
|
|
1901
|
+
* the parse/accumulate logic can be exercised without a live HTTP stream.
|
|
1902
|
+
*/
|
|
1903
|
+
export function _accumulateOllamaStream(lines, onToken) {
|
|
1904
|
+
const state = _ollamaInitState();
|
|
1905
|
+
for (const line of lines) _ollamaReduceLine(state, line, onToken);
|
|
1906
|
+
return _ollamaFinalize(state);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
async function _chatOllamaStreaming(apiUrl, body, onToken, signal) {
|
|
1910
|
+
const response = await fetch(apiUrl, {
|
|
1911
|
+
method: "POST",
|
|
1912
|
+
headers: { "Content-Type": "application/json" },
|
|
1913
|
+
signal,
|
|
1914
|
+
body: JSON.stringify({ ...body, stream: true }),
|
|
1915
|
+
});
|
|
1916
|
+
if (!response.ok) {
|
|
1917
|
+
throw new Error(`Ollama error: ${response.status}`);
|
|
1918
|
+
}
|
|
1919
|
+
const state = _ollamaInitState();
|
|
1920
|
+
const reader = response.body.getReader();
|
|
1921
|
+
const decoder = new TextDecoder();
|
|
1922
|
+
let buf = "";
|
|
1923
|
+
for (;;) {
|
|
1924
|
+
const { done, value } = await reader.read();
|
|
1925
|
+
if (done) break;
|
|
1926
|
+
buf += decoder.decode(value, { stream: true });
|
|
1927
|
+
let idx;
|
|
1928
|
+
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
1929
|
+
_ollamaReduceLine(state, buf.slice(0, idx), onToken);
|
|
1930
|
+
buf = buf.slice(idx + 1);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
if (buf.trim()) _ollamaReduceLine(state, buf, onToken);
|
|
1934
|
+
return _ollamaFinalize(state);
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1820
1937
|
function _normalizeAnthropicResponse(data) {
|
|
1821
1938
|
const content = data.content || [];
|
|
1822
1939
|
const textBlocks = content.filter((b) => b.type === "text");
|
|
@@ -1843,11 +1960,58 @@ function _normalizeAnthropicResponse(data) {
|
|
|
1843
1960
|
|
|
1844
1961
|
// ─── Agent loop (async generator) ─────────────────────────────────────────
|
|
1845
1962
|
|
|
1963
|
+
// Tools that never mutate the workspace — auto-checkpoint skips these.
|
|
1964
|
+
const _CHECKPOINT_READ_ONLY = new Set([
|
|
1965
|
+
"read_file",
|
|
1966
|
+
"search_files",
|
|
1967
|
+
"list_dir",
|
|
1968
|
+
"list_skills",
|
|
1969
|
+
"search_sessions",
|
|
1970
|
+
]);
|
|
1971
|
+
|
|
1972
|
+
let _checkpointStoreP = null;
|
|
1973
|
+
function _loadCheckpointStore() {
|
|
1974
|
+
if (!_checkpointStoreP) {
|
|
1975
|
+
_checkpointStoreP = import("../lib/checkpoint-store.js");
|
|
1976
|
+
}
|
|
1977
|
+
return _checkpointStoreP;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
/**
|
|
1981
|
+
* Best-effort auto-checkpoint of the working tree BEFORE a mutating tool runs,
|
|
1982
|
+
* so a later `cc checkpoint restore` can roll back to just before that tool.
|
|
1983
|
+
* Enabled via toolContext.autoCheckpoint; uses the git engine only (no-op
|
|
1984
|
+
* outside a git work tree). Never throws — checkpointing must not block a tool.
|
|
1985
|
+
*
|
|
1986
|
+
* @returns {Promise<string|null>} the checkpoint id, or null when skipped
|
|
1987
|
+
*/
|
|
1988
|
+
async function _autoCheckpointBeforeTool(toolContext, toolName, toolArgs) {
|
|
1989
|
+
if (!toolContext?.autoCheckpoint) return null;
|
|
1990
|
+
if (_CHECKPOINT_READ_ONLY.has(toolName)) return null;
|
|
1991
|
+
const cwd = toolContext.cwd || process.cwd();
|
|
1992
|
+
try {
|
|
1993
|
+
const store = await _loadCheckpointStore();
|
|
1994
|
+
if (!store.isCheckpointAvailable(cwd)) return null;
|
|
1995
|
+
const res = store.createCheckpoint(cwd, {
|
|
1996
|
+
session: toolContext.checkpointSession || "agent",
|
|
1997
|
+
label: `before ${toolName}: ${formatToolArgs(toolName, toolArgs)}`.slice(
|
|
1998
|
+
0,
|
|
1999
|
+
120,
|
|
2000
|
+
),
|
|
2001
|
+
skipIfUnchanged: true,
|
|
2002
|
+
});
|
|
2003
|
+
return res?.id || null;
|
|
2004
|
+
} catch {
|
|
2005
|
+
return null; // checkpoint failure must never block the tool
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
|
|
1846
2009
|
/**
|
|
1847
2010
|
* Async generator that drives the agentic tool-use loop.
|
|
1848
2011
|
*
|
|
1849
2012
|
* Yields events:
|
|
1850
2013
|
* { type: "slot-filling", slot, question } — when asking user for missing info
|
|
2014
|
+
* { type: "checkpoint", id, tool } — auto-checkpoint before a mutating tool
|
|
1851
2015
|
* { type: "tool-executing", tool, args }
|
|
1852
2016
|
* { type: "tool-result", tool, result, error }
|
|
1853
2017
|
* { type: "response-complete", content }
|
|
@@ -1855,6 +2019,38 @@ function _normalizeAnthropicResponse(data) {
|
|
|
1855
2019
|
* @param {Array} messages - mutable messages array (will be appended to)
|
|
1856
2020
|
* @param {object} options - provider, model, baseUrl, apiKey, contextEngine, hookDb, skillLoader, cwd, slotFiller, interaction
|
|
1857
2021
|
*/
|
|
2022
|
+
/**
|
|
2023
|
+
* Lazily build (and cache on `options`) the PromptCompressor used for in-loop
|
|
2024
|
+
* auto-compaction. Returns null when the feature is off or the module can't be
|
|
2025
|
+
* loaded — callers treat that as "don't compact". Cached (including null) so we
|
|
2026
|
+
* import once per run, not once per iteration.
|
|
2027
|
+
*/
|
|
2028
|
+
async function _getAutoCompactor(options) {
|
|
2029
|
+
if (Object.prototype.hasOwnProperty.call(options, "_autoCompactor")) {
|
|
2030
|
+
return options._autoCompactor;
|
|
2031
|
+
}
|
|
2032
|
+
let compressor = null;
|
|
2033
|
+
try {
|
|
2034
|
+
const { feature } = await import("../lib/feature-flags.js");
|
|
2035
|
+
if (feature("PROMPT_COMPRESSOR")) {
|
|
2036
|
+
const { PromptCompressor } =
|
|
2037
|
+
await import("../harness/prompt-compressor.js");
|
|
2038
|
+
compressor = new PromptCompressor({
|
|
2039
|
+
model: options.model,
|
|
2040
|
+
provider: options.provider,
|
|
2041
|
+
});
|
|
2042
|
+
}
|
|
2043
|
+
} catch {
|
|
2044
|
+
compressor = null;
|
|
2045
|
+
}
|
|
2046
|
+
try {
|
|
2047
|
+
options._autoCompactor = compressor;
|
|
2048
|
+
} catch {
|
|
2049
|
+
// options may be frozen — fine, we just re-import next iteration
|
|
2050
|
+
}
|
|
2051
|
+
return compressor;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
1858
2054
|
export async function* agentLoop(messages, options) {
|
|
1859
2055
|
// Shared iteration budget — replaces hardcoded MAX_ITERATIONS.
|
|
1860
2056
|
// When options.iterationBudget is provided (e.g. from parent agent),
|
|
@@ -1879,6 +2075,9 @@ export async function* agentLoop(messages, options) {
|
|
|
1879
2075
|
approvalGate: options.approvalGate || null,
|
|
1880
2076
|
shellConfirm: options.shellConfirm || null,
|
|
1881
2077
|
additionalDirectories: options.additionalDirectories || null,
|
|
2078
|
+
autoCheckpoint: options.autoCheckpoint || false,
|
|
2079
|
+
checkpointSession:
|
|
2080
|
+
options.checkpointSession || options.sessionId || "agent",
|
|
1882
2081
|
};
|
|
1883
2082
|
|
|
1884
2083
|
throwIfAborted(signal);
|
|
@@ -1979,6 +2178,64 @@ export async function* agentLoop(messages, options) {
|
|
|
1979
2178
|
};
|
|
1980
2179
|
}
|
|
1981
2180
|
|
|
2181
|
+
// Headless auto-compaction (Claude-Code `--print` parity). Keeps long
|
|
2182
|
+
// `-p` / `--resume` runs under the model's context window instead of
|
|
2183
|
+
// growing until the provider rejects the request. Opt-out with
|
|
2184
|
+
// `autoCompact: false` (the interactive REPL does this — it compacts on its
|
|
2185
|
+
// own schedule). Default-on, gated by the PROMPT_COMPRESSOR flag + a size
|
|
2186
|
+
// threshold inside the compressor, so it only fires for genuinely large
|
|
2187
|
+
// contexts. Safe to compact here: the previous iteration always finishes
|
|
2188
|
+
// its full tool_call→tool_result cycle before we loop, so `messages` has no
|
|
2189
|
+
// dangling call; `preserveToolPairs` then guarantees compaction never
|
|
2190
|
+
// orphans a tool result. Best-effort — a failure never aborts the run.
|
|
2191
|
+
if (options.autoCompact !== false && messages.length > 4) {
|
|
2192
|
+
try {
|
|
2193
|
+
const compactor = await _getAutoCompactor(options);
|
|
2194
|
+
if (compactor && compactor.shouldAutoCompact(messages)) {
|
|
2195
|
+
const { messages: compacted, stats } = await compactor.compress(
|
|
2196
|
+
messages,
|
|
2197
|
+
{ preserveToolPairs: true },
|
|
2198
|
+
);
|
|
2199
|
+
if (stats.saved > 0 && compacted.length < messages.length) {
|
|
2200
|
+
messages.splice(0, messages.length, ...compacted);
|
|
2201
|
+
// Persist the compaction so a later --resume rebuilds from the
|
|
2202
|
+
// shortened history. An explicit `onCompaction` hook (if a caller
|
|
2203
|
+
// provides one) takes precedence; otherwise self-persist a `compact`
|
|
2204
|
+
// event — but only when the session is already being persisted to
|
|
2205
|
+
// disk (the JSONL file exists), so a one-shot `cc agent -p` (which
|
|
2206
|
+
// never creates a session file) writes nothing. Opt out with
|
|
2207
|
+
// `persistCompaction: false`. Best-effort throughout.
|
|
2208
|
+
if (typeof options.onCompaction === "function") {
|
|
2209
|
+
try {
|
|
2210
|
+
options.onCompaction(stats, compacted);
|
|
2211
|
+
} catch {
|
|
2212
|
+
// persistence is best-effort
|
|
2213
|
+
}
|
|
2214
|
+
} else if (
|
|
2215
|
+
options.sessionId &&
|
|
2216
|
+
options.persistCompaction !== false
|
|
2217
|
+
) {
|
|
2218
|
+
try {
|
|
2219
|
+
const store = await import("../harness/jsonl-session-store.js");
|
|
2220
|
+
if (store.sessionExists(options.sessionId)) {
|
|
2221
|
+
store.appendCompactEvent(options.sessionId, {
|
|
2222
|
+
...stats,
|
|
2223
|
+
messages: compacted,
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
} catch {
|
|
2227
|
+
// self-persist is best-effort
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
yield { type: "compaction", stats, runId };
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
} catch (_e) {
|
|
2234
|
+
if (isAbortError(_e) || signal?.aborted) throw _e;
|
|
2235
|
+
// Compaction is best-effort — proceed with the uncompacted messages.
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
1982
2239
|
// Turn-scoped context injection (open-agents prepareCall parity).
|
|
1983
2240
|
// prepareCall runs fresh each iteration and returns an ephemeral
|
|
1984
2241
|
// system-message supplement that is NOT persisted to messages history.
|
|
@@ -2050,6 +2307,15 @@ export async function* agentLoop(messages, options) {
|
|
|
2050
2307
|
toolArgs = {};
|
|
2051
2308
|
}
|
|
2052
2309
|
|
|
2310
|
+
// Auto-checkpoint the work tree before a mutating tool (opt-in), so the
|
|
2311
|
+
// user can `cc checkpoint restore` back to just before this call.
|
|
2312
|
+
const cpId = await _autoCheckpointBeforeTool(
|
|
2313
|
+
toolContext,
|
|
2314
|
+
toolName,
|
|
2315
|
+
toolArgs,
|
|
2316
|
+
);
|
|
2317
|
+
if (cpId) yield { type: "checkpoint", id: cpId, tool: toolName };
|
|
2318
|
+
|
|
2053
2319
|
yield { type: "tool-executing", tool: toolName, args: toolArgs };
|
|
2054
2320
|
|
|
2055
2321
|
let toolResult;
|
|
@@ -26,12 +26,14 @@ import {
|
|
|
26
26
|
agentLoop as coreAgentLoop,
|
|
27
27
|
formatToolArgs,
|
|
28
28
|
} from "./agent-core.js";
|
|
29
|
+
import { loadMcpConfig } from "./mcp-config.js";
|
|
29
30
|
import { IterationBudget } from "../lib/iteration-budget.js";
|
|
30
31
|
import {
|
|
31
32
|
startSession as jsonlStartSession,
|
|
32
33
|
appendUserMessage as jsonlAppendUserMessage,
|
|
33
34
|
appendAssistantMessage as jsonlAppendAssistantMessage,
|
|
34
35
|
appendTokenUsage as jsonlAppendTokenUsage,
|
|
36
|
+
appendCompactEvent as jsonlAppendCompactEvent,
|
|
35
37
|
rebuildMessages as jsonlRebuildMessages,
|
|
36
38
|
sessionExists as jsonlSessionExists,
|
|
37
39
|
getLastSessionId as jsonlGetLastSessionId,
|
|
@@ -190,6 +192,8 @@ export function resolveHeadlessSession(options = {}, store = {}, fallbackId) {
|
|
|
190
192
|
* (no id) → most-recent session.
|
|
191
193
|
* @param {boolean} [options.continueSession] Resume the most-recent session.
|
|
192
194
|
* @param {boolean} [options.persistSession] Force persistence without resume.
|
|
195
|
+
* @param {boolean} [options.autoCheckpoint] Snapshot the work tree before each
|
|
196
|
+
* mutating tool (git engine only).
|
|
193
197
|
* @param {boolean} [options.expandFileRefs=true] Expand `@path` file references
|
|
194
198
|
* in the prompt into context blocks.
|
|
195
199
|
* @param {object} [deps] Injection seam for tests.
|
|
@@ -243,6 +247,7 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
243
247
|
appendAssistantMessage:
|
|
244
248
|
deps.appendAssistantMessage || jsonlAppendAssistantMessage,
|
|
245
249
|
appendTokenUsage: deps.appendTokenUsage || jsonlAppendTokenUsage,
|
|
250
|
+
appendCompactEvent: deps.appendCompactEvent || jsonlAppendCompactEvent,
|
|
246
251
|
getLastSessionId: deps.getLastSessionId || jsonlGetLastSessionId,
|
|
247
252
|
};
|
|
248
253
|
const isStream = outputFormat === "stream-json";
|
|
@@ -365,6 +370,26 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
365
370
|
}
|
|
366
371
|
}
|
|
367
372
|
|
|
373
|
+
// --mcp-config: connect ad-hoc MCP servers for this run and expose their
|
|
374
|
+
// tools to the LLM (Claude-Code parity). Connection is best-effort — a server
|
|
375
|
+
// that fails to connect is logged to stderr and contributes no tools; a
|
|
376
|
+
// missing/empty config file fails fast (the user explicitly asked for MCP).
|
|
377
|
+
let mcp = null;
|
|
378
|
+
if (options.mcpConfig) {
|
|
379
|
+
const doLoadMcp = deps.loadMcpConfig || loadMcpConfig;
|
|
380
|
+
try {
|
|
381
|
+
mcp = await doLoadMcp(options.mcpConfig, { writeErr });
|
|
382
|
+
if (isText) {
|
|
383
|
+
for (const c of mcp.connected) {
|
|
384
|
+
writeErr(` mcp: ${c.server} (${c.tools} tools)\n`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} catch (err) {
|
|
388
|
+
writeErr(`Error: ${err.message}\n`);
|
|
389
|
+
return { exitCode: 1, result: err.message, isError: true };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
368
393
|
const loopOptions = {
|
|
369
394
|
model,
|
|
370
395
|
provider,
|
|
@@ -373,16 +398,55 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
373
398
|
cwd,
|
|
374
399
|
additionalDirectories,
|
|
375
400
|
sessionId,
|
|
401
|
+
autoCheckpoint: options.autoCheckpoint || false,
|
|
402
|
+
checkpointSession: options.checkpointSession || sessionId,
|
|
376
403
|
hookDb: db,
|
|
377
404
|
approvalGate,
|
|
378
405
|
enabledToolNames,
|
|
379
406
|
disabledTools,
|
|
380
407
|
iterationBudget: budget,
|
|
408
|
+
// --mcp-config wiring: tool defs for the LLM + dispatch map + live client.
|
|
409
|
+
mcpClient: mcp?.mcpClient || null,
|
|
410
|
+
extraToolDefinitions: mcp?.extraToolDefinitions || undefined,
|
|
411
|
+
externalToolExecutors: mcp?.externalToolExecutors || undefined,
|
|
412
|
+
externalToolDescriptors: mcp?.externalToolDescriptors || undefined,
|
|
381
413
|
// chatFn passthrough lets tests drive the loop deterministically.
|
|
382
414
|
chatFn: deps.chatFn || options.chatFn || undefined,
|
|
383
415
|
signal: options.signal || undefined,
|
|
384
416
|
};
|
|
385
417
|
|
|
418
|
+
// Goal binding (cc goal, Phase 1). `--goal <id>` binds explicitly; `--goal`
|
|
419
|
+
// with no value (options.goal === true) auto-resolves from active/session.
|
|
420
|
+
// When omitted, headless stays goal-free (no behavior change). Best-effort:
|
|
421
|
+
// a failure here must never fail the run.
|
|
422
|
+
let boundGoalId = null;
|
|
423
|
+
if (options.goal !== undefined && options.goal !== false) {
|
|
424
|
+
try {
|
|
425
|
+
const explicitId = typeof options.goal === "string" ? options.goal : null;
|
|
426
|
+
const { resolveActiveGoal, linkSession } =
|
|
427
|
+
await import("../lib/goal-store.js");
|
|
428
|
+
const goal = (deps.resolveActiveGoal || resolveActiveGoal)({
|
|
429
|
+
explicitId,
|
|
430
|
+
sessionId,
|
|
431
|
+
});
|
|
432
|
+
if (goal) {
|
|
433
|
+
const { goalPrepareCall } = await import("../lib/goal-context.js");
|
|
434
|
+
loopOptions.prepareCall = goalPrepareCall(goal);
|
|
435
|
+
boundGoalId = goal.id;
|
|
436
|
+
// Link the session so a later `--continue`/`--resume` keeps this goal.
|
|
437
|
+
if (explicitId && persist !== false) {
|
|
438
|
+
try {
|
|
439
|
+
linkSession(goal.id, sessionId);
|
|
440
|
+
} catch {
|
|
441
|
+
/* linking is optional polish — never fatal */
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
/* goal binding is best-effort — proceed without it */
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
386
450
|
const startedAt = deps.now ? deps.now() : Date.now();
|
|
387
451
|
const toolCalls = [];
|
|
388
452
|
const usage = { input_tokens: 0, output_tokens: 0 };
|
|
@@ -393,6 +457,21 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
393
457
|
if (isStream) writeOut(JSON.stringify(obj) + "\n");
|
|
394
458
|
};
|
|
395
459
|
|
|
460
|
+
// --include-partial-messages: forward live assistant-text deltas as
|
|
461
|
+
// `stream_event` NDJSON lines (Claude-Code parity). Only meaningful for
|
|
462
|
+
// stream-json output, where the agent loop's onToken hook feeds chunks as
|
|
463
|
+
// they arrive from a streaming provider.
|
|
464
|
+
if (isStream && options.includePartialMessages) {
|
|
465
|
+
loopOptions.onToken = (text) =>
|
|
466
|
+
emitStream({
|
|
467
|
+
type: "stream_event",
|
|
468
|
+
event: {
|
|
469
|
+
type: "content_block_delta",
|
|
470
|
+
delta: { type: "text_delta", text },
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
396
475
|
emitStream({
|
|
397
476
|
type: "system",
|
|
398
477
|
subtype: "init",
|
|
@@ -405,11 +484,18 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
405
484
|
resumed_from: resumeId,
|
|
406
485
|
history_messages: history.length,
|
|
407
486
|
additional_directories: additionalDirectories,
|
|
487
|
+
goal_id: boundGoalId,
|
|
408
488
|
});
|
|
409
489
|
|
|
410
490
|
try {
|
|
411
491
|
for await (const event of runLoop(messages, loopOptions)) {
|
|
412
492
|
switch (event.type) {
|
|
493
|
+
case "checkpoint": {
|
|
494
|
+
if (isText)
|
|
495
|
+
writeErr(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`);
|
|
496
|
+
emitStream({ type: "checkpoint", id: event.id, tool: event.tool });
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
413
499
|
case "tool-executing": {
|
|
414
500
|
const line = ` [${event.tool}] ${formatToolArgs(event.tool, event.args)}`;
|
|
415
501
|
if (isText) writeErr(line + "\n");
|
|
@@ -497,6 +583,17 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
497
583
|
writeErr(`Error: ${message}\n`);
|
|
498
584
|
}
|
|
499
585
|
return { exitCode: 1, result: message, isError: true };
|
|
586
|
+
} finally {
|
|
587
|
+
// Tear down ad-hoc MCP servers (--mcp-config) before returning, whether the
|
|
588
|
+
// loop completed or threw. Best-effort: a failed disconnect never masks the
|
|
589
|
+
// run's own outcome.
|
|
590
|
+
if (mcp?.mcpClient) {
|
|
591
|
+
try {
|
|
592
|
+
await mcp.mcpClient.disconnectAll();
|
|
593
|
+
} catch {
|
|
594
|
+
// ignore — disconnect is best-effort
|
|
595
|
+
}
|
|
596
|
+
}
|
|
500
597
|
}
|
|
501
598
|
|
|
502
599
|
// coreAgentLoop emits run-ended reason "budget-exhausted" when the iteration
|
|
@@ -518,6 +615,56 @@ export async function runAgentHeadless(options = {}, deps = {}) {
|
|
|
518
615
|
}
|
|
519
616
|
}
|
|
520
617
|
|
|
618
|
+
// Run-end goal self-assessment (cc goal Phase 2, opt-in via --goal-assess).
|
|
619
|
+
// Spends one extra completion to judge whether the run advanced the bound
|
|
620
|
+
// goal, then persists progress / key-result / drift updates. Best-effort: it
|
|
621
|
+
// must never change the run's own outcome.
|
|
622
|
+
if (options.goalAssess && boundGoalId && !isError) {
|
|
623
|
+
try {
|
|
624
|
+
const { getGoal } = await import("../lib/goal-store.js");
|
|
625
|
+
const goal = (deps.getGoal || getGoal)(boundGoalId);
|
|
626
|
+
if (goal) {
|
|
627
|
+
const { assessGoalProgress } = await import("../lib/goal-assess.js");
|
|
628
|
+
const doAssess = deps.assessGoalProgress || assessGoalProgress;
|
|
629
|
+
const assessChat =
|
|
630
|
+
deps.assessChat ||
|
|
631
|
+
(async (assessPrompt) => {
|
|
632
|
+
const { chatWithTools } = await import("./agent-core.js");
|
|
633
|
+
const r = await chatWithTools(
|
|
634
|
+
[{ role: "user", content: assessPrompt }],
|
|
635
|
+
{ model, provider, baseUrl, apiKey, enabledToolNames: [] },
|
|
636
|
+
);
|
|
637
|
+
return r?.message?.content || "";
|
|
638
|
+
});
|
|
639
|
+
const { assessment } = await doAssess({
|
|
640
|
+
goal,
|
|
641
|
+
transcript: { prompt: options.prompt, finalText, toolCalls },
|
|
642
|
+
chat: assessChat,
|
|
643
|
+
});
|
|
644
|
+
if (assessment) {
|
|
645
|
+
if (isText) {
|
|
646
|
+
writeErr(
|
|
647
|
+
` ◎ goal ${boundGoalId}: ${assessment.advanced ? "advanced" : "no progress"}` +
|
|
648
|
+
(assessment.progress != null
|
|
649
|
+
? ` (${assessment.progress}%)`
|
|
650
|
+
: "") +
|
|
651
|
+
"\n",
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
emitStream({
|
|
655
|
+
type: "goal_assessment",
|
|
656
|
+
goal_id: boundGoalId,
|
|
657
|
+
advanced: assessment.advanced,
|
|
658
|
+
progress: assessment.progress,
|
|
659
|
+
note: assessment.note,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
} catch {
|
|
664
|
+
/* assessment is best-effort — never affect the run outcome */
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
521
668
|
if (isStream) {
|
|
522
669
|
emitStream({
|
|
523
670
|
type: "result",
|
|
@@ -207,6 +207,26 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
207
207
|
additional_directories: additionalDirectories,
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
+
// Goal binding (cc goal, Phase 1) — resolved once and injected on every turn.
|
|
211
|
+
// `--goal <id>` binds explicitly; `--goal` with no value auto-resolves.
|
|
212
|
+
let goalPrepareCallFn;
|
|
213
|
+
if (options.goal !== undefined && options.goal !== false) {
|
|
214
|
+
try {
|
|
215
|
+
const explicitId = typeof options.goal === "string" ? options.goal : null;
|
|
216
|
+
const { resolveActiveGoal } = await import("../lib/goal-store.js");
|
|
217
|
+
const goal = (deps.resolveActiveGoal || resolveActiveGoal)({
|
|
218
|
+
explicitId,
|
|
219
|
+
sessionId,
|
|
220
|
+
});
|
|
221
|
+
if (goal) {
|
|
222
|
+
const { goalPrepareCall } = await import("../lib/goal-context.js");
|
|
223
|
+
goalPrepareCallFn = goalPrepareCall(goal);
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
/* goal binding is best-effort — proceed without it */
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
210
230
|
const loopOptionsBase = {
|
|
211
231
|
model,
|
|
212
232
|
provider,
|
|
@@ -219,8 +239,22 @@ export async function runAgentHeadlessStream(options = {}, deps = {}) {
|
|
|
219
239
|
approvalGate,
|
|
220
240
|
enabledToolNames,
|
|
221
241
|
disabledTools,
|
|
242
|
+
prepareCall: goalPrepareCallFn,
|
|
222
243
|
chatFn: deps.chatFn || options.chatFn || undefined,
|
|
223
244
|
signal: options.signal || undefined,
|
|
245
|
+
// --include-partial-messages: stream live assistant-text deltas as
|
|
246
|
+
// `stream_event` lines. Output here is always NDJSON, so gating on the
|
|
247
|
+
// flag alone suffices.
|
|
248
|
+
onToken: options.includePartialMessages
|
|
249
|
+
? (text) =>
|
|
250
|
+
emit({
|
|
251
|
+
type: "stream_event",
|
|
252
|
+
event: {
|
|
253
|
+
type: "content_block_delta",
|
|
254
|
+
delta: { type: "text_delta", text },
|
|
255
|
+
},
|
|
256
|
+
})
|
|
257
|
+
: undefined,
|
|
224
258
|
};
|
|
225
259
|
|
|
226
260
|
let turns = 0;
|