chainlesschain 0.162.31 → 0.162.32
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-Cg_uWAVl.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CXwMgOvX.js → ActionButton-DSFtQ1c2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DAebZ4IY.js → Analytics-BMxpkw8y.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CYsqYoME.js → AppLayout-tgVxlmsx.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-BbTtX1Nf.js → Audit-DwzGllcp.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DgqY2Eb-.js → Backup-BG28Y2MV.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Cq2ZuSoO.js → BaseInput-TXthbazl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-D2kqpUyO.js → Chat-D096SxaD.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-PIx0Eu9I.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-_9swHpyc.js → Checkbox-Czttw1JS.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Cr9YbCPl.js → Codegen-DZtMgv4q.js} +1 -1
- package/src/assets/web-panel/assets/{Col--wdpCMxx.js → Col-D3DnfExY.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DuFcVnLu.js → Community-Bj5AdwqY.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-1yzYeT04.js → Compact-BQ8Zszub.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Dq3aU9Df.js → Compliance-DXacb34n.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CrWcnIg8.js → Cowork-BgMUBTkw.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-Bh6fKZ0h.js → Cron-fqBWOqlN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-8ofPaWVW.js → Crosschain-E4oa1MWy.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D3EiYm3w.js → DID-pwgfYZaV.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BFjEdFne.js → Dashboard-n8mdLFIR.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-pYVPcP6O.js → Dropdown--6DYqxk7.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-zBPodwJ1.js → EmailListRenderer-CkjQluz3.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CyQTW6PW.js → FamilyGuardDashboard-u-QTQ-OC.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Ctaq3zYq.js → Federation-D219M5Qc.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CWYJCLq1.js → FormItemContext-BBU_aopC.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B1g6t9R9.js → GenericCardRenderer-pTMCIHcM.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DH-v8iwd.js → Git-ClcCARWt.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-jZxXvOs5.js → Governance-CvUi3I93.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-D07LRghn.js → Inference-DT-a4pVg.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DnGtRZhx.js → KnowledgeGraph-DHMs2LY8.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-D2pM9C4W.js → Logs-D2s4eV1N.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-UyIO7C7r.js → Marketplace-YC5-fx-6.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Bf1gvZPf.js → McpTools-7JHTEC4T.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-C1bWj4RN.js → Memory-BudotVLD.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-C_Ot1H_a.js → MobileBridge-CAiRyLVU.js} +2 -2
- package/src/assets/web-panel/assets/{MobileProjects-zr-PpsT_.js → MobileProjects-CrJJOCFw.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CnzFUz5J.js → Mtc-d0iY0CeK.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CAAh99wz.js → MtcAudit-aI2cG1UP.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-D6IAg6HE.js → Multisig-4bF70khG.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BFMarxb0.js → NLProgramming-CwLib1S7.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BRp9ro3t.js → Notes-Wt7AuFRU.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C0Au3Cxb.js → NotificationSettings-D081vV_7.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-ISp6btRY.js → OrderTableRenderer-DCPei1L9.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DYoxLBRX.js → Organization-BNEsUNdP.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-rO8JJWGJ.js → Overflow-B_1iUXDD.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DJleeXIK.js → P2P-Dbc-kNwJ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DM5qghFp.js → PdhVaultBrowser-D8Xh289k.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D5v4Beya.js → Permissions-C77mM6-n.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-c2ZTX0Pv.js → PersonalDataHub-Dj0J3r_K.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-Crrkyhpz.js → Pipeline-B6F0WQ2C.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DZVyrJKa.js → Privacy-eDKOkyyq.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DKg7J0gz.js → ProjectInit-DAWwhr5_.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-3ndmTvVH.js → ProjectSettings-DwdK8k6I.js} +2 -2
- package/src/assets/web-panel/assets/Projects-Cb3p5QAP.js +1 -0
- package/src/assets/web-panel/assets/{Providers-BeqBVMhB.js → Providers--DcYxQfN.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DKAAxzuA.js → QuickAsk-DU268niT.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Byu7IGei.js → Recommend-ChnflhV1.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-BKhWAmCu.js → Reputation-DSsY3bQG.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BFtn11O6.js → Row-Zb-EjmgQ.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D5a0PT0k.js → RssFeed-CGLiixZB.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DAkuaZNe.js → Search-Dhr_po-U.js} +1 -1
- package/src/assets/web-panel/assets/{Security-C79Ml2Ms.js → Security-GMYNhGsR.js} +2 -2
- package/src/assets/web-panel/assets/{Services-BBk_jH6-.js → Services-DiOpnVY0.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Cy0VvL0M.js → Skeleton-DG3ez6ME.js} +1 -1
- package/src/assets/web-panel/assets/Skills-DZGptytP.js +1 -0
- package/src/assets/web-panel/assets/{Sla-CbX1f8xN.js → Sla-CtGpE3xA.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BIkoUjws.js → SpeechSettings-DQFw6Cf9.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DG6Swk7G.js → SyncSettings-C8X78RpX.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-C9R8sgyi.js → Tasks-DtVkhWCV.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-AaJPeCIz.js → Templates-SF9_ZWsV.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-jVFRofww.js → Tenant-BbIQSVZz.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DHBMzfK6.js → Terminal-DKr5zDwu.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-9RFfOHSI.js → TimelineRenderer-BtLaNaWr.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-ZTfwuABF.js → Tokens-CfYbk2NG.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Xo7uZNQs.js → Trigger-BLX_XDP0.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-C0cTPYvn.js → Trust-BWxUv9PR.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DmMKio71.js → UkeySign-DRwTyQD4.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DP7B-oGT.js → VideoEditing-BsC4VOSo.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-B1kZDARo.js → Wallet-CSsO1NJU.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Bo5kBx27.js → WebAuthn-z1MxiFzS.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DGI9SNHH.js → WorkflowEditor-B1vV7uuJ.js} +1 -1
- package/src/assets/web-panel/assets/{chat-y97W1CIG.js → chat-C0NJRaL2.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DtTNo0sH.js → colors-CHRiteWF.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-D0q0exuS.js → compact-item-2XmBBKPD.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D7pLFs2I.js → createContext-DkedHC38.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DmNpkOdC.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CXjG5B2j.js → hasIn-Bpn9Xrlw.js} +1 -1
- package/src/assets/web-panel/assets/index-7nAysteg.js +1 -0
- package/src/assets/web-panel/assets/{index-B3y_4OdG.js → index-B5NGWgHp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJUf19Wd.js → index-BItcSqan.js} +3 -3
- package/src/assets/web-panel/assets/index-BKWSQilQ.js +1 -0
- package/src/assets/web-panel/assets/{index-POaFzYGS.js → index-BN068mCR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSdhC7Qo.js → index-BOsIgPge.js} +1 -1
- package/src/assets/web-panel/assets/{index-4mWZhCzz.js → index-BYUd69vM.js} +1 -1
- package/src/assets/web-panel/assets/{index-_3wPBMKt.js → index-BYmwEaIk.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4dPdrvC.js → index-BZ1gOoiG.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_hjkMtX.js → index-BfY9U3X5.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dr45Nm9V.js → index-BveL_4n3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgmvrPJH.js → index-CCg6ZY4t.js} +1 -1
- package/src/assets/web-panel/assets/{index-gFLQe31v.js → index-CJOoo72F.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY6KLlgG.js → index-CToQxpWz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkGFqlYX.js → index-CWgWrrWs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BO644Q4S.js → index-CdR7RfRP.js} +1 -1
- package/src/assets/web-panel/assets/{index-kvV0f4tV.js → index-Cljnfuxu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BU944DeT.js → index-CxvA72CP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKrbutAQ.js → index-CyJpmSHZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-qoB3whR9.js → index-D7U411hK.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cbqu804A.js → index-D9mNfpxi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjCawXk1.js → index-DAFLFMXQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdhEYW2a.js → index-DAeHmElB.js} +1 -1
- package/src/assets/web-panel/assets/{index-EaIfumgW.js → index-DDy_RDjs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzCPx1cq.js → index-DE5Qm9UI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgyrM0UN.js → index-DM9JrnYi.js} +1 -1
- package/src/assets/web-panel/assets/{index-8jxbZupG.js → index-DMbF-Euw.js} +1 -1
- package/src/assets/web-panel/assets/{index-1dwtkcJv.js → index-DUBsq_1G.js} +1 -1
- package/src/assets/web-panel/assets/{index-TrBGgrwG.js → index-De49R7TX.js} +1 -1
- package/src/assets/web-panel/assets/{index-aarO4HT9.js → index-De5vOO9V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnLrbXDA.js → index-Dk7P-q3n.js} +1 -1
- package/src/assets/web-panel/assets/{index-YWOEx3rP.js → index-DryKGM_t.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPXhU-jp.js → index-DtU4qZRF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFsPe2N7.js → index-NuBsCRaR.js} +1 -1
- package/src/assets/web-panel/assets/{index-6np5ESBM.js → index-Sk3-3tKa.js} +1 -1
- package/src/assets/web-panel/assets/{index-bVJvqDAz.js → index-alGjpoM1.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ct6xtKkc.js → index-cfSUlOfY.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_4WcI1V.js → index-i4W_EAuh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqVjUN8b.js → index-uHGxyZtQ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BnXISaAa.js → initDefaultProps-DlDE-QgI.js} +1 -1
- package/src/assets/web-panel/assets/{motion-ChY7C0zJ.js → motion-CodUbIRF.js} +1 -1
- package/src/assets/web-panel/assets/{move-ByFZMFM5.js → move-DaLwsHeR.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BYeliY1H.js → omit-DdVg-3rL.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B9dcAKnu.js → pickAttrs-KLR1EVCo.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-D3F_txz7.js → placementArrow-ChV7HvNw.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-ClkwY7wS.js → responsiveObserve-BB_A8dBt.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BNgy2Eea.js → slide-Bc1tQnIK.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Bv3heMCD.js → statusUtils-CgrveSb0.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DVdlHbQm.js → styleChecker-vXAYhhjz.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-alrRY5BK.js → useFlexGapSupport-BCIMPfq9.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CcVh0-Vu.js → useFs-DMZGdr6G.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CkkHPhyq.js → usePersonalDataHub-118tWI_Z.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DWi0X9WN.js → vnode-Z7O2Y7JP.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DCbqxxLH.js → zoom-BXym6zmD.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +27 -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-context.js +87 -0
- package/src/lib/goal-store.js +308 -0
- package/src/repl/agent-repl.js +43 -7
- package/src/runtime/agent-core.js +245 -1
- package/src/runtime/headless-runner.js +25 -0
- package/src/runtime/headless-stream.js +13 -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/Projects-ll5wnj2L.js +0 -1
- package/src/assets/web-panel/assets/Skills-OQNky3uI.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
|
@@ -1,42 +1,182 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cc checkpoint —
|
|
2
|
+
* cc checkpoint — file-state snapshot / rewind (Claude-Code rewind parity).
|
|
3
3
|
*
|
|
4
|
-
* cc checkpoint create
|
|
4
|
+
* cc checkpoint create [paths...] [--label <l>] snapshot the work tree (git)
|
|
5
|
+
* or the given paths (fallback)
|
|
5
6
|
* cc checkpoint list list checkpoints
|
|
6
7
|
* cc checkpoint show <id> [--diff] manifest, or diff vs current
|
|
7
|
-
* cc checkpoint restore <id> [--dry-run] [--force] roll
|
|
8
|
+
* cc checkpoint restore <id> [--dry-run] [--force] roll back (alias: rewind)
|
|
8
9
|
* cc checkpoint delete <id> [--force] remove a checkpoint
|
|
10
|
+
* cc checkpoint clear remove all (a session)
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
+
* Engine: inside a git work tree it uses git-plumbing shadow commits
|
|
13
|
+
* (whole-tree, content-addressed, .gitignore-aware, accurate add/modify/delete
|
|
14
|
+
* rewind — refs/cc-checkpoints/*). Outside git it falls back to the copy-based
|
|
15
|
+
* store (file-checkpoint.js) which snapshots the explicit paths you name.
|
|
16
|
+
*
|
|
17
|
+
* Restore takes an automatic safety snapshot of the current state first, so a
|
|
18
|
+
* rewind is itself reversible. Distinct from `cc workflow checkpoint`
|
|
12
19
|
* (workflow execution state, not files).
|
|
13
20
|
*/
|
|
14
21
|
|
|
15
22
|
import chalk from "chalk";
|
|
23
|
+
import { resolve } from "path";
|
|
16
24
|
import { logger } from "../lib/logger.js";
|
|
17
25
|
|
|
26
|
+
/** git-plumbing engine adapter (normalized interface). */
|
|
27
|
+
function gitEngine(gs, dir, session) {
|
|
28
|
+
return {
|
|
29
|
+
kind: "git",
|
|
30
|
+
create: ({ label }) => {
|
|
31
|
+
const r = gs.createCheckpoint(dir, { session, label });
|
|
32
|
+
return {
|
|
33
|
+
id: r.id,
|
|
34
|
+
label: r.label,
|
|
35
|
+
createdAt: r.createdAt,
|
|
36
|
+
fileCount: r.files ?? r.fileCount,
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
list: () =>
|
|
40
|
+
gs.listCheckpoints(dir, { session }).map((r) => ({
|
|
41
|
+
id: r.id,
|
|
42
|
+
label: r.label,
|
|
43
|
+
createdAt: r.createdAt,
|
|
44
|
+
fileCount: null,
|
|
45
|
+
})),
|
|
46
|
+
show: (id) => gs.showCheckpoint(dir, id, { session }),
|
|
47
|
+
status: (id) => gs.statusAgainst(dir, id, { session }),
|
|
48
|
+
diffText: (id, o) => gs.diffCheckpoint(dir, id, { session, stat: o?.stat }),
|
|
49
|
+
restore: (id, o) => {
|
|
50
|
+
const r = gs.rewindTo(dir, id, { session, dryRun: o?.dryRun });
|
|
51
|
+
return {
|
|
52
|
+
dryRun: !!r.dryRun,
|
|
53
|
+
restoredCount: r.modified + r.recreated,
|
|
54
|
+
modified: r.modified,
|
|
55
|
+
recreated: r.recreated,
|
|
56
|
+
deleted: r.deleted,
|
|
57
|
+
safetyId: r.safetyId,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
remove: (id) => gs.deleteCheckpoint(dir, id, { session }),
|
|
61
|
+
clear: () => gs.clearCheckpoints(dir, { session }),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** copy-based engine adapter (normalized interface). */
|
|
66
|
+
function copyEngine(cs, dir) {
|
|
67
|
+
return {
|
|
68
|
+
kind: "copy",
|
|
69
|
+
create: ({ paths, label }) => {
|
|
70
|
+
const m = cs.createCheckpoint(paths, { cwd: dir, label });
|
|
71
|
+
return {
|
|
72
|
+
id: m.id,
|
|
73
|
+
label: m.label,
|
|
74
|
+
createdAt: m.createdAt,
|
|
75
|
+
fileCount: m.fileCount,
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
list: () =>
|
|
79
|
+
cs.listCheckpoints().map((c) => ({
|
|
80
|
+
id: c.id,
|
|
81
|
+
label: c.label,
|
|
82
|
+
createdAt: c.createdAt,
|
|
83
|
+
fileCount: c.fileCount,
|
|
84
|
+
})),
|
|
85
|
+
show: (id) => {
|
|
86
|
+
const m = cs.getCheckpoint(id);
|
|
87
|
+
if (!m) throw new Error(`no such checkpoint: ${id}`);
|
|
88
|
+
return {
|
|
89
|
+
id: m.id,
|
|
90
|
+
label: m.label,
|
|
91
|
+
createdAt: m.createdAt,
|
|
92
|
+
fileCount: m.fileCount,
|
|
93
|
+
files: m.files.map((f) => ({ rel: f.rel, bytes: f.bytes })),
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
status: (id) => {
|
|
97
|
+
const d = cs.diffCheckpoint(id);
|
|
98
|
+
return { modified: d.modified, added: [], deleted: d.deleted };
|
|
99
|
+
},
|
|
100
|
+
diffText: () => null, // copy engine has no raw patch — caller uses status()
|
|
101
|
+
restore: (id, o) => {
|
|
102
|
+
const r = cs.restoreCheckpoint(id, { cwd: dir, dryRun: o?.dryRun });
|
|
103
|
+
return {
|
|
104
|
+
dryRun: !!r.dryRun,
|
|
105
|
+
restoredCount: r.restored.length,
|
|
106
|
+
restored: r.restored,
|
|
107
|
+
missingBlob: r.missingBlob,
|
|
108
|
+
safetyId: r.safetyId,
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
remove: (id) => cs.deleteCheckpoint(id),
|
|
112
|
+
clear: () => {
|
|
113
|
+
const all = cs.listCheckpoints();
|
|
114
|
+
for (const c of all) cs.deleteCheckpoint(c.id);
|
|
115
|
+
return all.length;
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Choose the engine for `dir`: git-plumbing when available, else copy-based. */
|
|
121
|
+
async function pickEngine(dir, session) {
|
|
122
|
+
const gs = await import("../lib/checkpoint-store.js");
|
|
123
|
+
if (gs.isCheckpointAvailable(dir)) return gitEngine(gs, dir, session);
|
|
124
|
+
const cs = await import("../lib/file-checkpoint.js");
|
|
125
|
+
return copyEngine(cs, dir);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const tag = (engine) => chalk.dim(engine.kind === "git" ? "[git]" : "[copy]");
|
|
129
|
+
|
|
18
130
|
export function registerCheckpointCommand(program) {
|
|
19
131
|
const cp = program
|
|
20
132
|
.command("checkpoint")
|
|
21
|
-
.description("Snapshot / rewind file state (
|
|
133
|
+
.description("Snapshot / rewind file state (git-plumbing, copy fallback)");
|
|
22
134
|
|
|
23
|
-
cp.command("create
|
|
24
|
-
.description(
|
|
135
|
+
cp.command("create [paths...]")
|
|
136
|
+
.description(
|
|
137
|
+
"Snapshot the work tree (git) or the given files/dirs (fallback)",
|
|
138
|
+
)
|
|
139
|
+
.option("-d, --dir <dir>", "Target directory", ".")
|
|
140
|
+
.option("-s, --session <id>", "Checkpoint session (git engine)", "default")
|
|
25
141
|
.option("--label <label>", "Human label for this checkpoint")
|
|
26
142
|
.option("--json", "Output as JSON")
|
|
27
143
|
.action(async (paths, options) => {
|
|
28
144
|
try {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
145
|
+
const dir = resolve(options.dir);
|
|
146
|
+
const engine = await pickEngine(dir, options.session);
|
|
147
|
+
if (engine.kind === "copy" && (!paths || paths.length === 0)) {
|
|
148
|
+
logger.error(
|
|
149
|
+
chalk.red(
|
|
150
|
+
"Not a git repo here — specify paths to snapshot: cc checkpoint create <paths...>",
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
process.exitCode = 1;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (engine.kind === "git" && paths && paths.length > 0) {
|
|
157
|
+
logger.log(
|
|
158
|
+
chalk.gray(
|
|
159
|
+
" (git engine snapshots the whole work tree; paths ignored)",
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const m = engine.create({ paths, label: options.label });
|
|
31
164
|
if (options.json) {
|
|
32
|
-
console.log(JSON.stringify(m, null, 2));
|
|
165
|
+
console.log(JSON.stringify({ ...m, engine: engine.kind }, null, 2));
|
|
33
166
|
return;
|
|
34
167
|
}
|
|
35
168
|
logger.log(
|
|
36
169
|
chalk.green(`✓ checkpoint ${chalk.bold(m.id)}`) +
|
|
170
|
+
` ${tag(engine)}` +
|
|
37
171
|
(m.label ? chalk.gray(` "${m.label}"`) : ""),
|
|
38
172
|
);
|
|
39
173
|
logger.log(chalk.gray(` ${m.fileCount} file(s) snapshotted`));
|
|
174
|
+
logger.log(
|
|
175
|
+
chalk.gray(
|
|
176
|
+
` rewind with: cc checkpoint restore ${m.id}` +
|
|
177
|
+
(options.session !== "default" ? ` -s ${options.session}` : ""),
|
|
178
|
+
),
|
|
179
|
+
);
|
|
40
180
|
} catch (err) {
|
|
41
181
|
logger.error(chalk.red(`checkpoint create failed: ${err.message}`));
|
|
42
182
|
process.exitCode = 1;
|
|
@@ -46,11 +186,14 @@ export function registerCheckpointCommand(program) {
|
|
|
46
186
|
cp.command("list")
|
|
47
187
|
.alias("ls")
|
|
48
188
|
.description("List checkpoints (newest first)")
|
|
189
|
+
.option("-d, --dir <dir>", "Target directory", ".")
|
|
190
|
+
.option("-s, --session <id>", "Checkpoint session (git engine)", "default")
|
|
49
191
|
.option("--json", "Output as JSON")
|
|
50
192
|
.action(async (options) => {
|
|
51
193
|
try {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
194
|
+
const dir = resolve(options.dir);
|
|
195
|
+
const engine = await pickEngine(dir, options.session);
|
|
196
|
+
const all = engine.list();
|
|
54
197
|
if (options.json) {
|
|
55
198
|
console.log(JSON.stringify(all, null, 2));
|
|
56
199
|
return;
|
|
@@ -58,15 +201,19 @@ export function registerCheckpointCommand(program) {
|
|
|
58
201
|
if (all.length === 0) {
|
|
59
202
|
logger.log(
|
|
60
203
|
chalk.gray(
|
|
61
|
-
"No checkpoints. Create one: cc checkpoint create
|
|
204
|
+
"No checkpoints. Create one: cc checkpoint create" +
|
|
205
|
+
(engine.kind === "copy" ? " <paths...>" : ""),
|
|
62
206
|
),
|
|
63
207
|
);
|
|
64
208
|
return;
|
|
65
209
|
}
|
|
66
210
|
for (const c of all) {
|
|
211
|
+
const count =
|
|
212
|
+
c.fileCount == null
|
|
213
|
+
? ""
|
|
214
|
+
: `${String(c.fileCount).padStart(4)} files`;
|
|
67
215
|
logger.log(
|
|
68
|
-
`${chalk.cyan(c.id.padEnd(22))} ${chalk.gray(c.createdAt)} ` +
|
|
69
|
-
`${String(c.fileCount).padStart(4)} files` +
|
|
216
|
+
`${chalk.cyan(c.id.padEnd(22))} ${chalk.gray(c.createdAt)} ${count}` +
|
|
70
217
|
(c.label ? chalk.gray(` "${c.label}"`) : ""),
|
|
71
218
|
);
|
|
72
219
|
}
|
|
@@ -78,16 +225,30 @@ export function registerCheckpointCommand(program) {
|
|
|
78
225
|
|
|
79
226
|
cp.command("show <id>")
|
|
80
227
|
.description("Show a checkpoint's files, or its diff vs current state")
|
|
228
|
+
.option("-d, --dir <dir>", "Target directory", ".")
|
|
229
|
+
.option("-s, --session <id>", "Checkpoint session (git engine)", "default")
|
|
81
230
|
.option("--diff", "Compare snapshot against current on-disk files")
|
|
231
|
+
.option("--stat", "With --diff: summary (diffstat) instead of full patch")
|
|
82
232
|
.option("--json", "Output as JSON")
|
|
83
233
|
.action(async (id, options) => {
|
|
84
234
|
try {
|
|
85
|
-
const
|
|
86
|
-
|
|
235
|
+
const dir = resolve(options.dir);
|
|
236
|
+
const engine = await pickEngine(dir, options.session);
|
|
237
|
+
|
|
87
238
|
if (options.diff) {
|
|
88
|
-
const
|
|
239
|
+
const text = engine.diffText(id, { stat: options.stat });
|
|
240
|
+
if (text != null) {
|
|
241
|
+
if (options.json) {
|
|
242
|
+
console.log(JSON.stringify({ id, diff: text }, null, 2));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (!text.trim()) logger.info(`No changes since checkpoint ${id}.`);
|
|
246
|
+
else logger.log(text);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const d = engine.status(id);
|
|
89
250
|
if (options.json) {
|
|
90
|
-
console.log(JSON.stringify(d, null, 2));
|
|
251
|
+
console.log(JSON.stringify({ id, ...d }, null, 2));
|
|
91
252
|
return;
|
|
92
253
|
}
|
|
93
254
|
logger.log(chalk.bold(`Diff vs current — ${id}`));
|
|
@@ -95,20 +256,15 @@ export function registerCheckpointCommand(program) {
|
|
|
95
256
|
d.modified.forEach((f) => logger.log(chalk.yellow(` M ${f}`)));
|
|
96
257
|
logger.log(` ${chalk.red("deleted")}: ${d.deleted.length}`);
|
|
97
258
|
d.deleted.forEach((f) => logger.log(chalk.red(` D ${f}`)));
|
|
98
|
-
logger.log(chalk.gray(` unchanged: ${d.unchanged.length}`));
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const m = getCheckpoint(id);
|
|
102
|
-
if (!m) {
|
|
103
|
-
logger.error(chalk.red(`no such checkpoint: ${id}`));
|
|
104
|
-
process.exitCode = 1;
|
|
105
259
|
return;
|
|
106
260
|
}
|
|
261
|
+
|
|
262
|
+
const m = engine.show(id);
|
|
107
263
|
if (options.json) {
|
|
108
264
|
console.log(JSON.stringify(m, null, 2));
|
|
109
265
|
return;
|
|
110
266
|
}
|
|
111
|
-
logger.log(chalk.bold(`Checkpoint ${m.id}`));
|
|
267
|
+
logger.log(chalk.bold(`Checkpoint ${m.id}`) + ` ${tag(engine)}`);
|
|
112
268
|
if (m.label) logger.log(chalk.gray(` label: ${m.label}`));
|
|
113
269
|
logger.log(
|
|
114
270
|
chalk.gray(` created: ${m.createdAt} files: ${m.fileCount}`),
|
|
@@ -123,31 +279,30 @@ export function registerCheckpointCommand(program) {
|
|
|
123
279
|
});
|
|
124
280
|
|
|
125
281
|
cp.command("restore <id>")
|
|
282
|
+
.alias("rewind")
|
|
126
283
|
.description(
|
|
127
284
|
"Restore files from a checkpoint (auto-snapshots current state first)",
|
|
128
285
|
)
|
|
286
|
+
.option("-d, --dir <dir>", "Target directory", ".")
|
|
287
|
+
.option("-s, --session <id>", "Checkpoint session (git engine)", "default")
|
|
129
288
|
.option("--dry-run", "Show what would change without writing")
|
|
130
289
|
.option("--force", "Restore without the interactive confirm prompt")
|
|
131
290
|
.option("--json", "Output as JSON")
|
|
132
291
|
.action(async (id, options) => {
|
|
133
292
|
try {
|
|
134
|
-
const
|
|
135
|
-
|
|
293
|
+
const dir = resolve(options.dir);
|
|
294
|
+
const engine = await pickEngine(dir, options.session);
|
|
136
295
|
|
|
137
296
|
if (options.dryRun) {
|
|
138
|
-
const r =
|
|
297
|
+
const r = engine.restore(id, { dryRun: true });
|
|
139
298
|
if (options.json) {
|
|
140
299
|
console.log(JSON.stringify(r, null, 2));
|
|
141
300
|
return;
|
|
142
301
|
}
|
|
143
302
|
logger.log(chalk.bold(`Dry-run restore — ${id}`));
|
|
144
|
-
logger.log(` would restore: ${r.
|
|
145
|
-
r.
|
|
146
|
-
|
|
147
|
-
if (r.missingBlob.length) {
|
|
148
|
-
logger.log(
|
|
149
|
-
chalk.red(` missing blobs: ${r.missingBlob.join(", ")}`),
|
|
150
|
-
);
|
|
303
|
+
logger.log(` would restore: ${r.restoredCount} file(s)`);
|
|
304
|
+
if (typeof r.deleted === "number") {
|
|
305
|
+
logger.log(` would remove: ${r.deleted} file(s) created since`);
|
|
151
306
|
}
|
|
152
307
|
return;
|
|
153
308
|
}
|
|
@@ -155,12 +310,13 @@ export function registerCheckpointCommand(program) {
|
|
|
155
310
|
// Destructive: overwrites current files. Require --force when not a TTY;
|
|
156
311
|
// prompt when interactive.
|
|
157
312
|
if (!options.force) {
|
|
158
|
-
const d =
|
|
159
|
-
const willChange =
|
|
313
|
+
const d = engine.status(id);
|
|
314
|
+
const willChange =
|
|
315
|
+
d.modified.length + d.deleted.length + (d.added?.length || 0);
|
|
160
316
|
if (process.stdin.isTTY) {
|
|
161
317
|
const { confirm } = await import("@inquirer/prompts");
|
|
162
318
|
const ok = await confirm({
|
|
163
|
-
message: `Restore ${id}? ${willChange} file(s)
|
|
319
|
+
message: `Restore ${id}? ${willChange} file(s) affected (a safety checkpoint is taken first).`,
|
|
164
320
|
default: false,
|
|
165
321
|
}).catch(() => false);
|
|
166
322
|
if (!ok) {
|
|
@@ -178,23 +334,30 @@ export function registerCheckpointCommand(program) {
|
|
|
178
334
|
}
|
|
179
335
|
}
|
|
180
336
|
|
|
181
|
-
const r =
|
|
337
|
+
const r = engine.restore(id);
|
|
182
338
|
if (options.json) {
|
|
183
339
|
console.log(JSON.stringify(r, null, 2));
|
|
184
340
|
return;
|
|
185
341
|
}
|
|
186
342
|
logger.log(
|
|
187
|
-
chalk.green(`✓ restored ${r.
|
|
343
|
+
chalk.green(`✓ restored ${r.restoredCount} file(s) from ${id}`) +
|
|
344
|
+
(typeof r.deleted === "number" && r.deleted > 0
|
|
345
|
+
? chalk.gray(` (${r.deleted} removed)`)
|
|
346
|
+
: ""),
|
|
188
347
|
);
|
|
189
|
-
r.restored.forEach((f) => logger.log(chalk.gray(` ~ ${f}`)));
|
|
190
348
|
if (r.safetyId) {
|
|
191
349
|
logger.log(
|
|
192
350
|
chalk.gray(
|
|
193
|
-
` safety checkpoint of prior state: ${r.safetyId}
|
|
351
|
+
` safety checkpoint of prior state: ${r.safetyId}` +
|
|
352
|
+
` (undo with: cc checkpoint restore ${r.safetyId}` +
|
|
353
|
+
(options.session !== "default"
|
|
354
|
+
? ` -s ${options.session}`
|
|
355
|
+
: "") +
|
|
356
|
+
`)`,
|
|
194
357
|
),
|
|
195
358
|
);
|
|
196
359
|
}
|
|
197
|
-
if (r.missingBlob.length) {
|
|
360
|
+
if (r.missingBlob && r.missingBlob.length) {
|
|
198
361
|
logger.log(
|
|
199
362
|
chalk.red(` missing blobs (skipped): ${r.missingBlob.join(", ")}`),
|
|
200
363
|
);
|
|
@@ -208,20 +371,51 @@ export function registerCheckpointCommand(program) {
|
|
|
208
371
|
cp.command("delete <id>")
|
|
209
372
|
.alias("rm")
|
|
210
373
|
.description("Delete a checkpoint")
|
|
374
|
+
.option("-d, --dir <dir>", "Target directory", ".")
|
|
375
|
+
.option("-s, --session <id>", "Checkpoint session (git engine)", "default")
|
|
211
376
|
.option("--force", "Skip confirmation")
|
|
212
377
|
.action(async (id, options) => {
|
|
213
378
|
try {
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
if (!
|
|
379
|
+
const dir = resolve(options.dir);
|
|
380
|
+
const engine = await pickEngine(dir, options.session);
|
|
381
|
+
if (!options.force && process.stdin.isTTY) {
|
|
382
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
383
|
+
const ok = await confirm({
|
|
384
|
+
message: `Delete checkpoint ${id}?`,
|
|
385
|
+
default: false,
|
|
386
|
+
}).catch(() => false);
|
|
387
|
+
if (!ok) {
|
|
388
|
+
logger.log(chalk.gray("Aborted."));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const existed = engine.remove(id);
|
|
393
|
+
if (!existed) {
|
|
217
394
|
logger.error(chalk.red(`no such checkpoint: ${id}`));
|
|
218
395
|
process.exitCode = 1;
|
|
219
396
|
return;
|
|
220
397
|
}
|
|
398
|
+
logger.log(chalk.green(`✓ deleted ${id}`));
|
|
399
|
+
} catch (err) {
|
|
400
|
+
logger.error(chalk.red(`checkpoint delete failed: ${err.message}`));
|
|
401
|
+
process.exitCode = 1;
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
cp.command("clear")
|
|
406
|
+
.description("Delete all checkpoints (in a session, for the git engine)")
|
|
407
|
+
.option("-d, --dir <dir>", "Target directory", ".")
|
|
408
|
+
.option("-s, --session <id>", "Checkpoint session (git engine)", "default")
|
|
409
|
+
.option("--force", "Skip confirmation")
|
|
410
|
+
.option("--json", "Output as JSON")
|
|
411
|
+
.action(async (options) => {
|
|
412
|
+
try {
|
|
413
|
+
const dir = resolve(options.dir);
|
|
414
|
+
const engine = await pickEngine(dir, options.session);
|
|
221
415
|
if (!options.force && process.stdin.isTTY) {
|
|
222
416
|
const { confirm } = await import("@inquirer/prompts");
|
|
223
417
|
const ok = await confirm({
|
|
224
|
-
message: `Delete
|
|
418
|
+
message: `Delete ALL checkpoints${engine.kind === "git" ? ` in session "${options.session}"` : ""}?`,
|
|
225
419
|
default: false,
|
|
226
420
|
}).catch(() => false);
|
|
227
421
|
if (!ok) {
|
|
@@ -229,10 +423,16 @@ export function registerCheckpointCommand(program) {
|
|
|
229
423
|
return;
|
|
230
424
|
}
|
|
231
425
|
}
|
|
232
|
-
|
|
233
|
-
|
|
426
|
+
const removed = engine.clear();
|
|
427
|
+
if (options.json) {
|
|
428
|
+
console.log(
|
|
429
|
+
JSON.stringify({ removed, engine: engine.kind }, null, 2),
|
|
430
|
+
);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
logger.log(chalk.green(`✓ removed ${removed} checkpoint(s)`));
|
|
234
434
|
} catch (err) {
|
|
235
|
-
logger.error(chalk.red(`checkpoint
|
|
435
|
+
logger.error(chalk.red(`checkpoint clear failed: ${err.message}`));
|
|
236
436
|
process.exitCode = 1;
|
|
237
437
|
}
|
|
238
438
|
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cc compact — compact a stored session's history (Claude-Code `/compact` parity,
|
|
3
|
+
* headless). Complements the interactive agent REPL's `/compact`, which only
|
|
4
|
+
* works on the live in-memory conversation.
|
|
5
|
+
*
|
|
6
|
+
* cc compact <session-id> compact and persist (writes a `compact`
|
|
7
|
+
* checkpoint event the resume path honors)
|
|
8
|
+
* cc compact <session-id> --dry-run preview the reduction, write nothing
|
|
9
|
+
*
|
|
10
|
+
* Engine: the existing PromptCompressor (snip + dedup + collapse + truncate).
|
|
11
|
+
* Runs OFFLINE and deterministic — no LLM summarization is wired here, so a
|
|
12
|
+
* compaction never makes a network call and is reproducible. By default it
|
|
13
|
+
* sizes its thresholds to the session's recorded model/provider context window;
|
|
14
|
+
* override with --model/--provider or the hard --max-tokens/--max-messages.
|
|
15
|
+
*
|
|
16
|
+
* After compaction the new history is appended as a JSONL `compact` event;
|
|
17
|
+
* `rebuildMessages()` already rebuilds from the last such event, so a later
|
|
18
|
+
* `cc agent --resume <id>` picks up the shortened history automatically.
|
|
19
|
+
* Distinct from `cc checkpoint` (file state) and `cc workflow checkpoint`
|
|
20
|
+
* (execution state).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import chalk from "chalk";
|
|
24
|
+
import { logger } from "../lib/logger.js";
|
|
25
|
+
import {
|
|
26
|
+
sessionExists,
|
|
27
|
+
readEvents,
|
|
28
|
+
rebuildMessages,
|
|
29
|
+
appendCompactEvent,
|
|
30
|
+
} from "../harness/jsonl-session-store.js";
|
|
31
|
+
import { PromptCompressor } from "../harness/prompt-compressor.js";
|
|
32
|
+
|
|
33
|
+
/** Build a compressor sized to the session (or explicit overrides). */
|
|
34
|
+
function buildCompressor(options, recorded) {
|
|
35
|
+
const maxTokens = options.maxTokens ? Number(options.maxTokens) : undefined;
|
|
36
|
+
const maxMessages = options.maxMessages
|
|
37
|
+
? Number(options.maxMessages)
|
|
38
|
+
: undefined;
|
|
39
|
+
if (maxTokens || maxMessages) {
|
|
40
|
+
// Hard thresholds win — adaptive sizing is bypassed by the constructor.
|
|
41
|
+
return new PromptCompressor({ maxTokens, maxMessages });
|
|
42
|
+
}
|
|
43
|
+
const model = options.model || recorded.model || undefined;
|
|
44
|
+
const provider = options.provider || recorded.provider || undefined;
|
|
45
|
+
// model/provider → adaptive context-window thresholds; neither → defaults.
|
|
46
|
+
return new PromptCompressor({ model, provider });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function registerCompactCommand(program) {
|
|
50
|
+
program
|
|
51
|
+
.command("compact <session-id>")
|
|
52
|
+
.description(
|
|
53
|
+
"Compact a stored session's history (offline; persists for --resume)",
|
|
54
|
+
)
|
|
55
|
+
.option(
|
|
56
|
+
"-m, --model <model>",
|
|
57
|
+
"Model for adaptive context-window sizing (default: session's recorded model)",
|
|
58
|
+
)
|
|
59
|
+
.option(
|
|
60
|
+
"-p, --provider <provider>",
|
|
61
|
+
"Provider for adaptive sizing (default: session's recorded provider)",
|
|
62
|
+
)
|
|
63
|
+
.option("--max-tokens <n>", "Override the token threshold (skips adaptive)")
|
|
64
|
+
.option(
|
|
65
|
+
"--max-messages <n>",
|
|
66
|
+
"Override the message-count threshold (skips adaptive)",
|
|
67
|
+
)
|
|
68
|
+
.option("--dry-run", "Preview the reduction without writing")
|
|
69
|
+
.option("--json", "Output as JSON")
|
|
70
|
+
.action(async (sessionId, options) => {
|
|
71
|
+
try {
|
|
72
|
+
if (!sessionExists(sessionId)) {
|
|
73
|
+
logger.error(chalk.red(`no such session: ${sessionId}`));
|
|
74
|
+
logger.log(chalk.gray(" list sessions with: cc session list"));
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const events = readEvents(sessionId);
|
|
80
|
+
const start = events.find((e) => e.type === "session_start");
|
|
81
|
+
const recorded = {
|
|
82
|
+
model: start?.data?.model || "",
|
|
83
|
+
provider: start?.data?.provider || "",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const messages = rebuildMessages(sessionId);
|
|
87
|
+
const compressor = buildCompressor(options, recorded);
|
|
88
|
+
const { messages: compacted, stats } =
|
|
89
|
+
await compressor.compress(messages);
|
|
90
|
+
|
|
91
|
+
const reduced = stats.saved > 0 || compacted.length < messages.length;
|
|
92
|
+
|
|
93
|
+
if (!reduced) {
|
|
94
|
+
if (options.json) {
|
|
95
|
+
console.log(
|
|
96
|
+
JSON.stringify({ sessionId, compacted: false, stats }, null, 2),
|
|
97
|
+
);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
logger.log(
|
|
101
|
+
chalk.gray(
|
|
102
|
+
`Nothing to compact — ${messages.length} message(s), ${stats.originalTokens} tokens (under threshold).`,
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!options.dryRun) {
|
|
109
|
+
appendCompactEvent(sessionId, { ...stats, messages: compacted });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (options.json) {
|
|
113
|
+
console.log(
|
|
114
|
+
JSON.stringify(
|
|
115
|
+
{ sessionId, dryRun: !!options.dryRun, stats },
|
|
116
|
+
null,
|
|
117
|
+
2,
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const verb = options.dryRun ? "Would compact" : "Compacted";
|
|
124
|
+
logger.log(
|
|
125
|
+
(options.dryRun ? chalk.cyan : chalk.green)(
|
|
126
|
+
`${options.dryRun ? "" : "✓ "}${verb} ${sessionId}`,
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
logger.log(
|
|
130
|
+
chalk.gray(
|
|
131
|
+
` ${stats.originalMessages} → ${stats.compressedMessages} messages` +
|
|
132
|
+
`, ${stats.originalTokens} → ${stats.compressedTokens} tokens` +
|
|
133
|
+
` (saved ${stats.saved}, ${stats.strategy})`,
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
if (options.dryRun) {
|
|
137
|
+
logger.log(
|
|
138
|
+
chalk.gray(` re-run without --dry-run to persist the compaction`),
|
|
139
|
+
);
|
|
140
|
+
} else {
|
|
141
|
+
logger.log(
|
|
142
|
+
chalk.gray(` resume with: cc agent --resume ${sessionId}`),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
logger.error(chalk.red(`compact failed: ${err.message}`));
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|