@iowarp/clio-coder 0.3.3 → 0.3.4
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/CHANGELOG.md +39 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +3 -3
- package/dist/{acp-P2AQILE2.js → acp-S5R4RR5B.js} +7 -6
- package/dist/{agents-72W3BI7I.js → agents-P6DMMVZY.js} +24 -21
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-5TWEIYDN.js → auth-2XCZLPKS.js} +12 -8
- package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
- package/dist/{chunk-2DJ2KNFG.js → chunk-2LZI5CAG.js} +133 -13
- package/dist/{chunk-OAO4GE4M.js → chunk-2TZWSW76.js} +2 -2
- package/dist/{chunk-OOJYHWRB.js → chunk-34475P3I.js} +2 -2
- package/dist/{chunk-4XUGQOHA.js → chunk-35MKKU5R.js} +4 -4
- package/dist/{chunk-LM5TQCJZ.js → chunk-3HZ5RWN2.js} +5 -5
- package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
- package/dist/{chunk-KZWTDYJF.js → chunk-4JUF2NNX.js} +7 -7
- package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
- package/dist/chunk-5M54SPOL.js +926 -0
- package/dist/{chunk-STBPMHSX.js → chunk-7RXG6QRZ.js} +51 -11
- package/dist/{chunk-X6IAEBZR.js → chunk-A2GZF7DC.js} +5 -5
- package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
- package/dist/chunk-AOCYTWAV.js +449 -0
- package/dist/chunk-BEY543CS.js +258 -0
- package/dist/{chunk-6N5PTWMY.js → chunk-BP4OYD6A.js} +32 -13
- package/dist/chunk-BPGS2WCQ.js +612 -0
- package/dist/{chunk-V6RTAOC2.js → chunk-BRXQQJFP.js} +8 -8
- package/dist/chunk-CFGTUFWB.js +67 -0
- package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
- package/dist/{chunk-DUYJ5IO6.js → chunk-EDRHSCIE.js} +4 -4
- package/dist/{chunk-XBXAASKX.js → chunk-EFADSJET.js} +2 -2
- package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
- package/dist/chunk-FYYLNIL5.js +313 -0
- package/dist/{chunk-FNTMWMX5.js → chunk-HV5X7OR2.js} +14 -12
- package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
- package/dist/{chunk-5UFT4SUX.js → chunk-K6WL7QZT.js} +3 -3
- package/dist/chunk-K7VKOLQQ.js +15 -0
- package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
- package/dist/{chunk-OC7FIQPC.js → chunk-KRPY7NTG.js} +10 -7
- package/dist/chunk-LL4KHSZI.js +22 -0
- package/dist/{chunk-DSELYM6W.js → chunk-MEQ45TQ4.js} +15 -9
- package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
- package/dist/{chunk-6SGHMWE3.js → chunk-N4CZJQRK.js} +5 -5
- package/dist/{chunk-TZK7PACC.js → chunk-NILBFAPG.js} +14 -8
- package/dist/chunk-OZNBF4L3.js +23 -0
- package/dist/{verify-375KUB3Y.js → chunk-PCZJO5TI.js} +127 -42
- package/dist/chunk-QQK64KLB.js +1360 -0
- package/dist/{chunk-SRDMMSEP.js → chunk-QQL5RT5M.js} +979 -1619
- package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
- package/dist/{chunk-2TLUCQVG.js → chunk-RD5U66HV.js} +3 -3
- package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
- package/dist/{chunk-OQ33BKR3.js → chunk-TTNYS3EA.js} +3 -60
- package/dist/chunk-TW3WDMVS.js +677 -0
- package/dist/chunk-TZSKNMZG.js +434 -0
- package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
- package/dist/{chunk-PIWWS5BL.js → chunk-UZHIZC5S.js} +7 -7
- package/dist/{chunk-UFIIWP2H.js → chunk-VAWWTKDP.js} +8 -8
- package/dist/{chunk-COU2UHX6.js → chunk-VEZEGCGW.js} +170 -2
- package/dist/{chunk-LW6DSM3M.js → chunk-VMNQ6OZA.js} +98 -202
- package/dist/chunk-VSNATDE6.js +122 -0
- package/dist/chunk-W6GROXXM.js +69 -0
- package/dist/chunk-WPQLXFOZ.js +375 -0
- package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
- package/dist/{chunk-PAJK6MAQ.js → chunk-X6COSD2O.js} +5 -5
- package/dist/chunk-ZGVHUX3M.js +66 -0
- package/dist/{chunk-LWLEKMDQ.js → chunk-ZYKPLLNQ.js} +510 -547
- package/dist/cli/index.js +27 -23
- package/dist/{clio-JOU4FXVA.js → clio-J5JIOIDS.js} +7 -6
- package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
- package/dist/{config-XCDVKR23.js → config-OEBMIN2U.js} +37 -27
- package/dist/{configure-4GAP54ZW.js → configure-PUQOSIXQ.js} +16 -13
- package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
- package/dist/{context-4UOGGLQ5.js → context-MGSE4Z2T.js} +33 -23
- package/dist/{context-77FM5DV5.js → context-URSXPBCK.js} +17 -9
- package/dist/{context-clear-XXJRLCJJ.js → context-clear-KDAJRNUK.js} +33 -23
- package/dist/context-working-set-SBKMPPI2.js +1552 -0
- package/dist/{dispatch-runner-QPRDDBDX.js → dispatch-runner-MSWN72NK.js} +43 -29
- package/dist/{doctor-HR46URBJ.js → doctor-7BSE27PJ.js} +10 -10
- package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
- package/dist/{evidence-6HG2PY2B.js → evidence-SR7WXB5B.js} +51 -23
- package/dist/{evolve-K7YU3NCY.js → evolve-K7VE2CBX.js} +30 -20
- package/dist/{fleet-VY3HHKN6.js → fleet-7XMJNQNF.js} +48 -38
- package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
- package/dist/{init-JYGXI3FK.js → init-JGNPAYXT.js} +41 -31
- package/dist/{memory-WFZMGYHX.js → memory-4ALKDJ4Q.js} +32 -22
- package/dist/{models-I5QWSEOM.js → models-ZMMLFJNN.js} +22 -19
- package/dist/{monitor-GE4ID3IA.js → monitor-2F3T5KHP.js} +55 -43
- package/dist/{orchestrator-EM5MC3HM.js → orchestrator-ORHT43JB.js} +572 -388
- package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
- package/dist/{run-ZU3QMZPZ.js → run-RF4WJGMT.js} +51 -41
- package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
- package/dist/{skills-X5VXCRNQ.js → skills-PSACKC5Q.js} +2 -2
- package/dist/{skills-eval-WKIHWTHR.js → skills-eval-WJSI55RZ.js} +34 -24
- package/dist/{targets-SNCPI2NR.js → targets-PIIRAOYS.js} +23 -20
- package/dist/{terminal-lease-BNAHVHBS.js → terminal-lease-ULWXWNVY.js} +4 -3
- package/dist/{upgrade-JQHHPQ4K.js → upgrade-346TZ6AV.js} +18 -17
- package/dist/{usage-OR4O5SMZ.js → usage-6KKXR32N.js} +34 -24
- package/dist/verifiers-4UUM6TEE.js +1214 -0
- package/dist/verify-X5HDROLA.js +25 -0
- package/dist/{wiki-generate-UEXP2ARI.js → wiki-generate-7STOCIFZ.js} +42 -31
- package/dist/worker/entry.js +33 -24
- package/docs/README.md +7 -6
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +53 -21
- package/docs/config-knobs-audit.md +1 -2
- package/docs/configuration-and-targets.md +15 -1
- package/docs/context-engine.md +64 -12
- package/docs/context-working-set.md +194 -0
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +5 -5
- package/docs/documentation-guide.md +6 -5
- package/docs/environment-variables.md +2 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +14 -1
- package/docs/evidence-and-memory.md +74 -2
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +2 -2
- package/docs/fleet-dispatch.md +22 -7
- package/docs/glossary.md +21 -1
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +7 -9
- package/docs/observability.md +4 -4
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +4 -4
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +31 -31
- package/docs/safety-model.md +23 -4
- package/docs/scientific-validation.md +21 -3
- package/docs/session-lifecycle.md +3 -3
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +79 -12
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +11 -1
- package/package.json +8 -11
- package/skills/meta/clio-test/SKILL.md +20 -17
- package/skills/meta/clio-test/evals.md +3 -3
- package/skills/meta/clio-test/references/harness.md +35 -6
- package/skills/meta/clio-test/references/test-map.md +20 -10
- package/skills/registry.yaml +2 -2
- package/skills/skill-marketplace.json +1 -1
- package/src/cli/context-working-set.ts +513 -0
- package/src/cli/context.ts +8 -0
- package/src/cli/evidence.ts +20 -2
- package/src/cli/index.ts +4 -0
- package/src/cli/verifiers.ts +325 -0
- package/src/core/bash-exec.ts +39 -14
- package/src/core/bus-events.ts +19 -4
- package/src/core/config.ts +54 -0
- package/src/core/defaults.ts +50 -3
- package/src/core/verification-scripts.ts +6 -0
- package/src/domains/agents/builtins/verifier.md +3 -0
- package/src/domains/config/classify.ts +1 -0
- package/src/domains/context/working-set/contract.ts +161 -0
- package/src/domains/context/working-set/defaults.ts +28 -0
- package/src/domains/context/working-set/engine.ts +203 -0
- package/src/domains/context/working-set/fold.ts +62 -0
- package/src/domains/context/working-set/horizon.ts +38 -0
- package/src/domains/context/working-set/marker.ts +103 -0
- package/src/domains/context/working-set/path-index.ts +436 -0
- package/src/domains/context/working-set/payload.ts +152 -0
- package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
- package/src/domains/context/working-set/policies/index.ts +21 -0
- package/src/domains/context/working-set/policies/structural.ts +160 -0
- package/src/domains/context/working-set/project.ts +132 -0
- package/src/domains/context/working-set/protect.ts +109 -0
- package/src/domains/context/working-set/recall.ts +177 -0
- package/src/domains/context/working-set/replay/controls.ts +112 -0
- package/src/domains/context/working-set/replay/load-clio.ts +199 -0
- package/src/domains/context/working-set/replay/metrics.ts +185 -0
- package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
- package/src/domains/context/working-set/replay/report.ts +139 -0
- package/src/domains/context/working-set/replay/runner.ts +325 -0
- package/src/domains/context/working-set/replay/synthetic.ts +422 -0
- package/src/domains/context/working-set/replay/trace.ts +21 -0
- package/src/domains/context/working-set/visible.ts +54 -0
- package/src/domains/evidence/build.ts +112 -45
- package/src/domains/evidence/eval.ts +24 -7
- package/src/domains/evidence/index.ts +53 -0
- package/src/domains/evidence/ordering.ts +12 -0
- package/src/domains/evidence/run-trust.ts +221 -0
- package/src/domains/evidence/store.ts +46 -6
- package/src/domains/evidence/trust-status.ts +854 -0
- package/src/domains/evidence/types.ts +26 -0
- package/src/domains/middleware/memory-intervention.ts +3 -0
- package/src/domains/middleware/stalled-turn.ts +165 -4
- package/src/domains/safety/autonomy.ts +1 -1
- package/src/domains/safety/default-path-policy.ts +8 -0
- package/src/domains/safety/finish-contract.ts +4 -3
- package/src/domains/safety/policy-engine.ts +48 -6
- package/src/domains/session/compaction/compact.ts +23 -1
- package/src/domains/session/compaction/cut-point.ts +2 -0
- package/src/domains/session/compaction/tokens.ts +16 -1
- package/src/domains/session/context-ledger.ts +2 -0
- package/src/domains/session/entries.ts +107 -1
- package/src/domains/session/manager.ts +9 -2
- package/src/domains/session/migrations/index.ts +22 -3
- package/src/engine/acp/server.ts +3 -0
- package/src/engine/agent.ts +18 -1
- package/src/engine/session.ts +9 -3
- package/src/entry/orchestrator.ts +16 -4
- package/src/interactive/chat-loop-messages.ts +18 -6
- package/src/interactive/chat-panel.ts +17 -1
- package/src/interactive/chat-renderer.ts +30 -21
- package/src/interactive/context-meter.ts +10 -0
- package/src/interactive/context-overlay.ts +81 -6
- package/src/interactive/context-recall-command.ts +110 -0
- package/src/interactive/interactive-slash-runtime.ts +37 -1
- package/src/interactive/model-session-replay.ts +21 -0
- package/src/interactive/overlay-general-openers.ts +6 -0
- package/src/interactive/overlay-session-lifecycle.ts +8 -4
- package/src/interactive/renderers/tool-execution.ts +18 -2
- package/src/interactive/session-transcript.ts +2 -2
- package/src/interactive/slash-commands.ts +29 -2
- package/src/interactive/turn-context.ts +238 -88
- package/src/interactive/turn-middleware.ts +6 -6
- package/src/tools/agent-tools.ts +11 -4
- package/src/tools/bash.ts +144 -82
- package/src/tools/builtin-tool-catalog.ts +11 -5
- package/src/tools/context/index.ts +105 -3
- package/src/tools/context/surface.ts +3 -2
- package/src/tools/core-bootstrap.ts +21 -0
- package/src/tools/dispatch-runner.ts +9 -7
- package/src/tools/monitor.ts +28 -20
- package/src/tools/registry.ts +59 -7
- package/src/tools/result-disposition.ts +550 -0
- package/src/tools/result-shaping.ts +262 -19
- package/src/tools/safe-exec.ts +2 -0
- package/src/tools/verify/authoring.ts +1119 -0
- package/src/tools/verify/catalog.ts +346 -0
- package/src/tools/verify/index.ts +13 -3
- package/src/tools/verify/scripts.ts +135 -37
- package/src/tools/verify/surface.ts +9 -5
- package/src/tools/worker-evidence.ts +35 -12
- package/dist/chunk-J7CWMCQD.js +0 -255
- package/dist/chunk-T6YILFSB.js +0 -80
- package/dist/chunk-VAKQQHWR.js +0 -434
- package/dist/chunk-VPAYEGVX.js +0 -184
|
@@ -0,0 +1,1360 @@
|
|
|
1
|
+
import { createRequire as __clioCreateRequire } from "node:module"; const require = __clioCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
probeWorkspace
|
|
4
|
+
} from "./chunk-G4BMMOKF.js";
|
|
5
|
+
import {
|
|
6
|
+
entryBelongsToPath
|
|
7
|
+
} from "./chunk-W6GROXXM.js";
|
|
8
|
+
import {
|
|
9
|
+
protectArtifact
|
|
10
|
+
} from "./chunk-MV3K5QF2.js";
|
|
11
|
+
import {
|
|
12
|
+
isSessionEntry,
|
|
13
|
+
isSessionHeader
|
|
14
|
+
} from "./chunk-BEY543CS.js";
|
|
15
|
+
import {
|
|
16
|
+
stripTokenizerSentinels
|
|
17
|
+
} from "./chunk-CFGTUFWB.js";
|
|
18
|
+
import {
|
|
19
|
+
CURRENT_SESSION_FORMAT_VERSION,
|
|
20
|
+
appendSessionFileEntry,
|
|
21
|
+
atomicWrite,
|
|
22
|
+
createSession,
|
|
23
|
+
cwdHash,
|
|
24
|
+
openSession,
|
|
25
|
+
readSessionFileEntries,
|
|
26
|
+
readSessionMeta,
|
|
27
|
+
resumeSession,
|
|
28
|
+
sessionPaths
|
|
29
|
+
} from "./chunk-FO5ZOVUY.js";
|
|
30
|
+
import {
|
|
31
|
+
clioStateDir,
|
|
32
|
+
stateRootRemoved
|
|
33
|
+
} from "./chunk-BNAZZHFG.js";
|
|
34
|
+
import {
|
|
35
|
+
BusChannels
|
|
36
|
+
} from "./chunk-TTNYS3EA.js";
|
|
37
|
+
import {
|
|
38
|
+
init_esm_shims
|
|
39
|
+
} from "./chunk-3R73A4XB.js";
|
|
40
|
+
|
|
41
|
+
// src/domains/session/compaction/session-entries.ts
|
|
42
|
+
init_esm_shims();
|
|
43
|
+
function collectSessionEntries(turns, filePath) {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const raw of turns) {
|
|
46
|
+
if (!isSessionEntry(raw)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`session ledger contains an unreadable entry (missing kind discriminant): ${filePath}. Remove or compact the session to reset.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
out.push(raw);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/domains/session/archive-readers.ts
|
|
57
|
+
init_esm_shims();
|
|
58
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
59
|
+
import { join as join3 } from "node:path";
|
|
60
|
+
|
|
61
|
+
// src/domains/session/index.ts
|
|
62
|
+
init_esm_shims();
|
|
63
|
+
|
|
64
|
+
// src/domains/session/extension.ts
|
|
65
|
+
init_esm_shims();
|
|
66
|
+
|
|
67
|
+
// src/domains/session/checkpoint.ts
|
|
68
|
+
init_esm_shims();
|
|
69
|
+
async function performCheckpoint(state, reason) {
|
|
70
|
+
await state.writer.persistTree();
|
|
71
|
+
if (stateRootRemoved()) return;
|
|
72
|
+
const paths = sessionPaths(state.meta);
|
|
73
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
74
|
+
const enriched = {
|
|
75
|
+
...state.meta,
|
|
76
|
+
lastCheckpointAt: at,
|
|
77
|
+
lastCheckpointReason: reason ?? null
|
|
78
|
+
};
|
|
79
|
+
atomicWrite(paths.meta, JSON.stringify(enriched, null, 2));
|
|
80
|
+
Object.assign(state.meta, enriched);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/domains/session/history.ts
|
|
84
|
+
init_esm_shims();
|
|
85
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
86
|
+
import { join } from "node:path";
|
|
87
|
+
function listSessionsForCwd(cwd) {
|
|
88
|
+
const hash = cwdHash(cwd);
|
|
89
|
+
const dir = join(clioStateDir(), "sessions", hash);
|
|
90
|
+
if (!existsSync(dir)) return [];
|
|
91
|
+
const metas = [];
|
|
92
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
93
|
+
if (!entry.isDirectory()) continue;
|
|
94
|
+
const sessionDir = join(dir, entry.name);
|
|
95
|
+
const metaPath = join(sessionDir, "meta.json");
|
|
96
|
+
if (!existsSync(metaPath)) continue;
|
|
97
|
+
let meta;
|
|
98
|
+
try {
|
|
99
|
+
if (!statSync(metaPath).isFile()) continue;
|
|
100
|
+
const raw = readFileSync(metaPath, "utf8");
|
|
101
|
+
meta = JSON.parse(raw);
|
|
102
|
+
} catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
enrichMetaForListing(meta, join(sessionDir, "current.jsonl"));
|
|
106
|
+
metas.push(meta);
|
|
107
|
+
}
|
|
108
|
+
metas.sort((a, b) => {
|
|
109
|
+
const aKey = a.lastActivityAt ?? a.createdAt ?? "";
|
|
110
|
+
const bKey = b.lastActivityAt ?? b.createdAt ?? "";
|
|
111
|
+
return aKey === bKey ? 0 : aKey > bKey ? -1 : 1;
|
|
112
|
+
});
|
|
113
|
+
return metas;
|
|
114
|
+
}
|
|
115
|
+
var PREVIEW_MAX_CHARS = 240;
|
|
116
|
+
function collapseWhitespace(text) {
|
|
117
|
+
return text.replace(/\s+/g, " ").trim();
|
|
118
|
+
}
|
|
119
|
+
function extractUserText(payload) {
|
|
120
|
+
if (typeof payload === "string") {
|
|
121
|
+
const collapsed = collapseWhitespace(payload);
|
|
122
|
+
return collapsed.length > 0 ? collapsed : null;
|
|
123
|
+
}
|
|
124
|
+
if (!payload || typeof payload !== "object") return null;
|
|
125
|
+
const obj = payload;
|
|
126
|
+
if (typeof obj.text === "string") {
|
|
127
|
+
const collapsed = collapseWhitespace(obj.text);
|
|
128
|
+
if (collapsed.length > 0) return collapsed;
|
|
129
|
+
}
|
|
130
|
+
if (Array.isArray(obj.content)) {
|
|
131
|
+
const parts = [];
|
|
132
|
+
for (const part of obj.content) {
|
|
133
|
+
if (part && typeof part === "object" && part.type === "text" && typeof part.text === "string") {
|
|
134
|
+
parts.push(part.text);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const joined = collapseWhitespace(parts.join(" "));
|
|
138
|
+
if (joined.length > 0) return joined;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function scanCurrentJsonl(currentPath) {
|
|
143
|
+
const result = {
|
|
144
|
+
firstUserMessage: null,
|
|
145
|
+
messageCount: 0,
|
|
146
|
+
lastTimestamp: null,
|
|
147
|
+
name: null,
|
|
148
|
+
labels: /* @__PURE__ */ new Map()
|
|
149
|
+
};
|
|
150
|
+
if (!existsSync(currentPath)) return result;
|
|
151
|
+
const records = readSessionFileEntries(currentPath).filter((entry) => !isSessionHeader(entry));
|
|
152
|
+
for (const parsed of collectSessionEntries(records, currentPath)) {
|
|
153
|
+
if (result.lastTimestamp === null || parsed.timestamp > result.lastTimestamp) {
|
|
154
|
+
result.lastTimestamp = parsed.timestamp;
|
|
155
|
+
}
|
|
156
|
+
if (parsed.kind === "message" && parsed.role === "user") {
|
|
157
|
+
result.messageCount += 1;
|
|
158
|
+
if (result.firstUserMessage === null) {
|
|
159
|
+
const text = extractUserText(parsed.payload);
|
|
160
|
+
if (text !== null) result.firstUserMessage = text;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (parsed.kind === "sessionInfo") {
|
|
164
|
+
const info = parsed;
|
|
165
|
+
if (info.name !== void 0) {
|
|
166
|
+
const trimmed = info.name.trim();
|
|
167
|
+
result.name = trimmed.length > 0 ? trimmed : null;
|
|
168
|
+
}
|
|
169
|
+
if (info.targetTurnId && info.label !== void 0) {
|
|
170
|
+
const existing = result.labels.get(info.targetTurnId);
|
|
171
|
+
if (!existing || existing.timestamp <= info.timestamp) {
|
|
172
|
+
result.labels.set(info.targetTurnId, { label: info.label, timestamp: info.timestamp });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (parsed.kind === "label") {
|
|
177
|
+
const label = parsed;
|
|
178
|
+
const existing = result.labels.get(label.targetTurnId);
|
|
179
|
+
if (!existing || existing.timestamp <= label.timestamp) {
|
|
180
|
+
result.labels.set(label.targetTurnId, { label: label.label ?? "", timestamp: label.timestamp });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
function enrichMetaForListing(meta, currentPath) {
|
|
187
|
+
const scan = scanCurrentJsonl(currentPath);
|
|
188
|
+
if (scan.name) meta.name = scan.name;
|
|
189
|
+
const labelValues = [...scan.labels.values()].map((entry) => entry.label.trim()).filter((label) => label.length > 0);
|
|
190
|
+
if (labelValues.length > 0) meta.labels = labelValues;
|
|
191
|
+
if (scan.firstUserMessage) {
|
|
192
|
+
meta.firstMessagePreview = scan.firstUserMessage.length > PREVIEW_MAX_CHARS ? `${scan.firstUserMessage.slice(0, PREVIEW_MAX_CHARS - 1)}\u2026` : scan.firstUserMessage;
|
|
193
|
+
}
|
|
194
|
+
if (scan.messageCount > 0) meta.messageCount = scan.messageCount;
|
|
195
|
+
const fallbackMtime = readMtimeIso(currentPath);
|
|
196
|
+
const activity = scan.lastTimestamp ?? fallbackMtime ?? meta.endedAt ?? meta.createdAt ?? null;
|
|
197
|
+
if (activity) meta.lastActivityAt = activity;
|
|
198
|
+
}
|
|
199
|
+
function readMtimeIso(path) {
|
|
200
|
+
try {
|
|
201
|
+
return statSync(path).mtime.toISOString();
|
|
202
|
+
} catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function enrichForkMeta(meta, parentSessionId, parentTurnId) {
|
|
207
|
+
Object.assign(meta, { parentSessionId, parentTurnId });
|
|
208
|
+
const paths = sessionPaths(meta);
|
|
209
|
+
atomicWrite(paths.meta, JSON.stringify(meta, null, 2));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/domains/session/manager.ts
|
|
213
|
+
init_esm_shims();
|
|
214
|
+
|
|
215
|
+
// node_modules/uuid/dist-node/index.js
|
|
216
|
+
init_esm_shims();
|
|
217
|
+
|
|
218
|
+
// node_modules/uuid/dist-node/stringify.js
|
|
219
|
+
init_esm_shims();
|
|
220
|
+
var byteToHex = [];
|
|
221
|
+
for (let i = 0; i < 256; ++i) {
|
|
222
|
+
byteToHex.push((i + 256).toString(16).slice(1));
|
|
223
|
+
}
|
|
224
|
+
function unsafeStringify(arr, offset = 0) {
|
|
225
|
+
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// node_modules/uuid/dist-node/rng.js
|
|
229
|
+
init_esm_shims();
|
|
230
|
+
var rnds8 = new Uint8Array(16);
|
|
231
|
+
function rng() {
|
|
232
|
+
return crypto.getRandomValues(rnds8);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// node_modules/uuid/dist-node/v7.js
|
|
236
|
+
init_esm_shims();
|
|
237
|
+
var _state = {};
|
|
238
|
+
function v7(options, buf, offset) {
|
|
239
|
+
let bytes;
|
|
240
|
+
if (options) {
|
|
241
|
+
bytes = v7Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.seq, buf, offset);
|
|
242
|
+
} else {
|
|
243
|
+
const now = Date.now();
|
|
244
|
+
const rnds = rng();
|
|
245
|
+
updateV7State(_state, now, rnds);
|
|
246
|
+
bytes = v7Bytes(rnds, _state.msecs, _state.seq, buf, offset);
|
|
247
|
+
}
|
|
248
|
+
return buf ?? unsafeStringify(bytes);
|
|
249
|
+
}
|
|
250
|
+
function updateV7State(state, now, rnds) {
|
|
251
|
+
state.msecs ??= -Infinity;
|
|
252
|
+
state.seq ??= 0;
|
|
253
|
+
if (now > state.msecs) {
|
|
254
|
+
state.seq = rnds[6] << 23 | rnds[7] << 16 | rnds[8] << 8 | rnds[9];
|
|
255
|
+
state.msecs = now;
|
|
256
|
+
} else {
|
|
257
|
+
state.seq = state.seq + 1 | 0;
|
|
258
|
+
if (state.seq === 0) {
|
|
259
|
+
state.msecs++;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return state;
|
|
263
|
+
}
|
|
264
|
+
function v7Bytes(rnds, msecs, seq, buf, offset = 0) {
|
|
265
|
+
if (rnds.length < 16) {
|
|
266
|
+
throw new Error("Random bytes length must be >= 16");
|
|
267
|
+
}
|
|
268
|
+
if (!buf) {
|
|
269
|
+
buf = new Uint8Array(16);
|
|
270
|
+
offset = 0;
|
|
271
|
+
} else {
|
|
272
|
+
if (offset < 0 || offset + 16 > buf.length) {
|
|
273
|
+
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
msecs ??= Date.now();
|
|
277
|
+
seq ??= rnds[6] * 127 << 24 | rnds[7] << 16 | rnds[8] << 8 | rnds[9];
|
|
278
|
+
buf[offset++] = msecs / 1099511627776 & 255;
|
|
279
|
+
buf[offset++] = msecs / 4294967296 & 255;
|
|
280
|
+
buf[offset++] = msecs / 16777216 & 255;
|
|
281
|
+
buf[offset++] = msecs / 65536 & 255;
|
|
282
|
+
buf[offset++] = msecs / 256 & 255;
|
|
283
|
+
buf[offset++] = msecs & 255;
|
|
284
|
+
buf[offset++] = 112 | seq >>> 28 & 15;
|
|
285
|
+
buf[offset++] = seq >>> 20 & 255;
|
|
286
|
+
buf[offset++] = 128 | seq >>> 14 & 63;
|
|
287
|
+
buf[offset++] = seq >>> 6 & 255;
|
|
288
|
+
buf[offset++] = seq << 2 & 255 | rnds[10] & 3;
|
|
289
|
+
buf[offset++] = rnds[11];
|
|
290
|
+
buf[offset++] = rnds[12];
|
|
291
|
+
buf[offset++] = rnds[13];
|
|
292
|
+
buf[offset++] = rnds[14];
|
|
293
|
+
buf[offset++] = rnds[15];
|
|
294
|
+
return buf;
|
|
295
|
+
}
|
|
296
|
+
var v7_default = v7;
|
|
297
|
+
|
|
298
|
+
// src/domains/session/migrations/index.ts
|
|
299
|
+
init_esm_shims();
|
|
300
|
+
var OLDEST_READABLE_SESSION_FORMAT_VERSION = 3;
|
|
301
|
+
function runMigrations(meta, sessionPath) {
|
|
302
|
+
const from = meta.sessionFormatVersion ?? 1;
|
|
303
|
+
if (from < OLDEST_READABLE_SESSION_FORMAT_VERSION) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`session metadata has an unsupported format version (expected version ${CURRENT_SESSION_FORMAT_VERSION}, got ${from}): ${sessionPath}. Remove the session directory to start a new session.`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
if (from > CURRENT_SESSION_FORMAT_VERSION) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`session was written by a newer Clio (format version ${from}, this build reads version ${CURRENT_SESSION_FORMAT_VERSION}): ${sessionPath}. Upgrade clio-coder to resume this session.`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
if (from < CURRENT_SESSION_FORMAT_VERSION) {
|
|
314
|
+
return { migrated: true, from, to: CURRENT_SESSION_FORMAT_VERSION };
|
|
315
|
+
}
|
|
316
|
+
return { migrated: false, from, to: from };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/domains/session/manager.ts
|
|
320
|
+
function newTurnId() {
|
|
321
|
+
return v7_default();
|
|
322
|
+
}
|
|
323
|
+
function startSession(input) {
|
|
324
|
+
const { meta, writer } = createSession({
|
|
325
|
+
cwd: input.cwd,
|
|
326
|
+
model: input.model ?? null,
|
|
327
|
+
target: input.target ?? null,
|
|
328
|
+
...input.initialEntries !== void 0 ? { initialEntries: input.initialEntries } : {},
|
|
329
|
+
...input.initialTree !== void 0 ? { initialTree: input.initialTree } : {},
|
|
330
|
+
...input.parentSession !== void 0 ? { parentSession: input.parentSession } : {},
|
|
331
|
+
...input.parentTurnId !== void 0 ? { parentTurnId: input.parentTurnId } : {}
|
|
332
|
+
});
|
|
333
|
+
return { meta, writer };
|
|
334
|
+
}
|
|
335
|
+
function persistSessionMeta(state) {
|
|
336
|
+
if (stateRootRemoved()) return;
|
|
337
|
+
atomicWrite(sessionPaths(state.meta).meta, JSON.stringify(state.meta, null, 2));
|
|
338
|
+
}
|
|
339
|
+
function resumeSessionState(sessionId) {
|
|
340
|
+
const persistedMeta = readSessionMeta(sessionId);
|
|
341
|
+
const paths = sessionPaths(persistedMeta);
|
|
342
|
+
const migration = runMigrations(persistedMeta, paths.meta);
|
|
343
|
+
const { meta, writer, tree } = resumeSession(sessionId);
|
|
344
|
+
const state = { meta, writer };
|
|
345
|
+
if (migration.migrated) {
|
|
346
|
+
state.meta.sessionFormatVersion = migration.to;
|
|
347
|
+
persistSessionMeta(state);
|
|
348
|
+
}
|
|
349
|
+
return { state, nodes: tree };
|
|
350
|
+
}
|
|
351
|
+
function appendTurn(state, input) {
|
|
352
|
+
const record = {
|
|
353
|
+
id: input.id ?? newTurnId(),
|
|
354
|
+
parentId: input.parentId,
|
|
355
|
+
at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
356
|
+
kind: input.kind,
|
|
357
|
+
payload: input.payload
|
|
358
|
+
};
|
|
359
|
+
const entry = {
|
|
360
|
+
kind: "message",
|
|
361
|
+
turnId: record.id,
|
|
362
|
+
parentTurnId: record.parentId,
|
|
363
|
+
timestamp: record.at,
|
|
364
|
+
role: record.kind,
|
|
365
|
+
payload: record.payload
|
|
366
|
+
};
|
|
367
|
+
state.writer.appendEntry(entry, {
|
|
368
|
+
treeNode: {
|
|
369
|
+
id: record.id,
|
|
370
|
+
parentId: record.parentId,
|
|
371
|
+
at: record.at,
|
|
372
|
+
kind: record.kind
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
return record;
|
|
376
|
+
}
|
|
377
|
+
function appendEntry(state, input) {
|
|
378
|
+
const turnId = input.turnId ?? newTurnId();
|
|
379
|
+
const timestamp = input.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
380
|
+
const entry = { ...input, turnId, timestamp };
|
|
381
|
+
if (!isSessionEntry(entry)) throw new Error(`session.appendEntry: invalid ${String(input.kind)} entry`);
|
|
382
|
+
state.writer.appendEntry(entry);
|
|
383
|
+
return entry;
|
|
384
|
+
}
|
|
385
|
+
function replaceEntries(state, entries) {
|
|
386
|
+
for (const entry of entries) {
|
|
387
|
+
if (!isSessionEntry(entry)) throw new Error("session.replaceEntries: invalid entry");
|
|
388
|
+
}
|
|
389
|
+
state.writer.replaceEntries(entries);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/domains/session/tree/fork.ts
|
|
393
|
+
init_esm_shims();
|
|
394
|
+
function linkedRecordFromEntry(entry) {
|
|
395
|
+
return {
|
|
396
|
+
id: entry.turnId,
|
|
397
|
+
parentId: entry.parentTurnId,
|
|
398
|
+
timestamp: entry.timestamp,
|
|
399
|
+
treeKind: entry.kind === "message" ? entry.role : null,
|
|
400
|
+
raw: entry
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
function traceAncestry(records, leafTurnId) {
|
|
404
|
+
const byId = /* @__PURE__ */ new Map();
|
|
405
|
+
for (const record of records) byId.set(record.id, record);
|
|
406
|
+
const path = [];
|
|
407
|
+
let current = byId.get(leafTurnId);
|
|
408
|
+
if (!current) throw new Error(`session.fork: parent turn not found: ${leafTurnId}`);
|
|
409
|
+
const seen = /* @__PURE__ */ new Set();
|
|
410
|
+
while (current) {
|
|
411
|
+
if (seen.has(current.id)) throw new Error(`session.fork: cycle in parent chain at ${current.id}`);
|
|
412
|
+
seen.add(current.id);
|
|
413
|
+
path.unshift(current);
|
|
414
|
+
if (current.parentId === null) break;
|
|
415
|
+
const next = byId.get(current.parentId);
|
|
416
|
+
if (!next) throw new Error(`session.fork: broken parent chain at ${current.id}`);
|
|
417
|
+
current = next;
|
|
418
|
+
}
|
|
419
|
+
return path;
|
|
420
|
+
}
|
|
421
|
+
function treeFromLinearPath(path) {
|
|
422
|
+
return path.filter((record) => record.treeKind !== null).map((record) => ({
|
|
423
|
+
id: record.id,
|
|
424
|
+
parentId: record.parentId,
|
|
425
|
+
at: record.timestamp,
|
|
426
|
+
kind: record.treeKind
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
429
|
+
function sessionEntryBelongsToPath(entry, pathIds, atOrBeforeForkPoint) {
|
|
430
|
+
if (entry.kind === "compactionSummary" && !atOrBeforeForkPoint) return false;
|
|
431
|
+
return entryBelongsToPath(entry, pathIds, atOrBeforeForkPoint);
|
|
432
|
+
}
|
|
433
|
+
function branchEntriesFromParent(parentMeta, leafTurnId) {
|
|
434
|
+
const parentCurrentPath = sessionPaths(parentMeta).current;
|
|
435
|
+
const records = readSessionFileEntries(parentCurrentPath).filter((entry) => !isSessionHeader(entry));
|
|
436
|
+
const parsed = collectSessionEntries(records, parentCurrentPath);
|
|
437
|
+
const linked = parsed.map(linkedRecordFromEntry);
|
|
438
|
+
const path = traceAncestry(linked, leafTurnId);
|
|
439
|
+
const pathIds = new Set(path.map((record) => record.id));
|
|
440
|
+
const forkPointIndex = parsed.findIndex((entry) => linkedRecordFromEntry(entry).id === leafTurnId);
|
|
441
|
+
const entries = parsed.filter(
|
|
442
|
+
(entry, index) => sessionEntryBelongsToPath(entry, pathIds, forkPointIndex >= 0 && index <= forkPointIndex)
|
|
443
|
+
);
|
|
444
|
+
return {
|
|
445
|
+
parentCurrentPath,
|
|
446
|
+
entries,
|
|
447
|
+
tree: treeFromLinearPath(path)
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function forkFromState(input) {
|
|
451
|
+
const parentMeta = input.from.meta;
|
|
452
|
+
input.from.writer.flushAppends();
|
|
453
|
+
const branch = branchEntriesFromParent(parentMeta, input.parentTurnId);
|
|
454
|
+
const cwd = input.cwd ?? parentMeta.cwd;
|
|
455
|
+
const next = startSession({
|
|
456
|
+
cwd,
|
|
457
|
+
model: parentMeta.model,
|
|
458
|
+
target: parentMeta.target,
|
|
459
|
+
initialEntries: branch.entries,
|
|
460
|
+
initialTree: branch.tree,
|
|
461
|
+
parentSession: branch.parentCurrentPath,
|
|
462
|
+
parentTurnId: input.parentTurnId
|
|
463
|
+
});
|
|
464
|
+
enrichForkMeta(next.meta, parentMeta.id, input.parentTurnId);
|
|
465
|
+
void input.from.writer.close();
|
|
466
|
+
return { next, parentMeta, nodes: branch.tree };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/domains/session/tree/manager.ts
|
|
470
|
+
init_esm_shims();
|
|
471
|
+
import { existsSync as existsSync2, renameSync, rmSync } from "node:fs";
|
|
472
|
+
import { dirname, join as join2 } from "node:path";
|
|
473
|
+
function readTreeBundle(sessionId) {
|
|
474
|
+
const reader = openSession(sessionId);
|
|
475
|
+
const meta = reader.meta();
|
|
476
|
+
const paths = sessionPaths(meta);
|
|
477
|
+
const nodes = [...reader.tree()];
|
|
478
|
+
const entries = collectSessionEntries(reader.turns(), paths.current);
|
|
479
|
+
const labels = resolveLabelMap(entries);
|
|
480
|
+
return { sessionId, meta, nodes, entries, labels };
|
|
481
|
+
}
|
|
482
|
+
function resolveLabelMap(entries) {
|
|
483
|
+
const out = /* @__PURE__ */ new Map();
|
|
484
|
+
for (const entry of entries) {
|
|
485
|
+
const info = labelFields(entry);
|
|
486
|
+
if (!info) continue;
|
|
487
|
+
const existing = out.get(info.targetTurnId);
|
|
488
|
+
if (existing && existing.timestamp > info.timestamp) continue;
|
|
489
|
+
if (info.label === void 0 || info.label === "") {
|
|
490
|
+
out.set(info.targetTurnId, { label: "", timestamp: info.timestamp });
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
out.set(info.targetTurnId, { label: info.label, timestamp: info.timestamp });
|
|
494
|
+
}
|
|
495
|
+
return out;
|
|
496
|
+
}
|
|
497
|
+
function labelFields(entry) {
|
|
498
|
+
if (entry.kind === "label") return entry;
|
|
499
|
+
if (entry.kind !== "sessionInfo") return null;
|
|
500
|
+
const info = entry;
|
|
501
|
+
if (!info.targetTurnId) return null;
|
|
502
|
+
const out = {
|
|
503
|
+
targetTurnId: info.targetTurnId,
|
|
504
|
+
timestamp: info.timestamp
|
|
505
|
+
};
|
|
506
|
+
if (info.label !== void 0) out.label = info.label;
|
|
507
|
+
return out;
|
|
508
|
+
}
|
|
509
|
+
function appendEntryToSessionFile(sessionId, entry) {
|
|
510
|
+
const meta = readSessionMeta(sessionId);
|
|
511
|
+
const paths = sessionPaths(meta);
|
|
512
|
+
appendSessionFileEntry(paths.current, entry);
|
|
513
|
+
}
|
|
514
|
+
function removeSessionDirectory(sessionId) {
|
|
515
|
+
const meta = readSessionMeta(sessionId);
|
|
516
|
+
const paths = sessionPaths(meta);
|
|
517
|
+
const dir = dirname(paths.meta);
|
|
518
|
+
rmSync(dir, { recursive: true, force: true });
|
|
519
|
+
}
|
|
520
|
+
function tombstoneSession(sessionId) {
|
|
521
|
+
const meta = readSessionMeta(sessionId);
|
|
522
|
+
const paths = sessionPaths(meta);
|
|
523
|
+
if (!existsSync2(paths.meta)) return;
|
|
524
|
+
const tombstone = join2(dirname(paths.meta), "meta.deleted.json");
|
|
525
|
+
renameSync(paths.meta, tombstone);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/domains/session/tree/navigator.ts
|
|
529
|
+
init_esm_shims();
|
|
530
|
+
function pickMeta(meta) {
|
|
531
|
+
const out = {
|
|
532
|
+
id: meta.id,
|
|
533
|
+
cwd: meta.cwd,
|
|
534
|
+
createdAt: meta.createdAt,
|
|
535
|
+
endedAt: meta.endedAt,
|
|
536
|
+
model: meta.model,
|
|
537
|
+
target: meta.target
|
|
538
|
+
};
|
|
539
|
+
if (meta.parentSessionId !== void 0) out.parentSessionId = meta.parentSessionId;
|
|
540
|
+
if (meta.parentTurnId !== void 0) out.parentTurnId = meta.parentTurnId;
|
|
541
|
+
return out;
|
|
542
|
+
}
|
|
543
|
+
function computeLeafId(nodes) {
|
|
544
|
+
if (nodes.length === 0) return null;
|
|
545
|
+
const hasChild = /* @__PURE__ */ new Set();
|
|
546
|
+
for (const node of nodes) {
|
|
547
|
+
if (node.parentId) hasChild.add(node.parentId);
|
|
548
|
+
}
|
|
549
|
+
let leaf = null;
|
|
550
|
+
for (const node of nodes) {
|
|
551
|
+
if (hasChild.has(node.id)) continue;
|
|
552
|
+
if (!leaf || node.at >= leaf.at) leaf = node;
|
|
553
|
+
}
|
|
554
|
+
return leaf?.id ?? null;
|
|
555
|
+
}
|
|
556
|
+
function buildTreeSnapshot(input) {
|
|
557
|
+
const nodesById = {};
|
|
558
|
+
for (const node of input.nodes) {
|
|
559
|
+
const entry = {
|
|
560
|
+
id: node.id,
|
|
561
|
+
parentId: node.parentId,
|
|
562
|
+
at: node.at,
|
|
563
|
+
kind: node.kind,
|
|
564
|
+
children: []
|
|
565
|
+
};
|
|
566
|
+
const resolved = input.labels.get(node.id);
|
|
567
|
+
if (resolved && resolved.label !== "") entry.label = resolved.label;
|
|
568
|
+
const preview = input.previews?.get(node.id);
|
|
569
|
+
if (preview !== void 0 && preview.length > 0) entry.preview = preview;
|
|
570
|
+
nodesById[node.id] = entry;
|
|
571
|
+
}
|
|
572
|
+
const rootIds = [];
|
|
573
|
+
for (const node of input.nodes) {
|
|
574
|
+
const parent = node.parentId ? nodesById[node.parentId] : void 0;
|
|
575
|
+
if (!parent) {
|
|
576
|
+
rootIds.push(node.id);
|
|
577
|
+
} else {
|
|
578
|
+
parent.children.push(node.id);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const byTime = (a, b) => {
|
|
582
|
+
const na = nodesById[a];
|
|
583
|
+
const nb = nodesById[b];
|
|
584
|
+
if (!na || !nb) return a < b ? -1 : a > b ? 1 : 0;
|
|
585
|
+
if (na.at === nb.at) return na.id < nb.id ? -1 : na.id > nb.id ? 1 : 0;
|
|
586
|
+
return na.at < nb.at ? -1 : 1;
|
|
587
|
+
};
|
|
588
|
+
rootIds.sort(byTime);
|
|
589
|
+
for (const key of Object.keys(nodesById)) {
|
|
590
|
+
const entry = nodesById[key];
|
|
591
|
+
if (entry) entry.children.sort(byTime);
|
|
592
|
+
}
|
|
593
|
+
const leafId = input.leafId !== void 0 ? input.leafId : computeLeafId(input.nodes);
|
|
594
|
+
return {
|
|
595
|
+
sessionId: input.meta.id,
|
|
596
|
+
meta: pickMeta(input.meta),
|
|
597
|
+
leafId,
|
|
598
|
+
nodesById,
|
|
599
|
+
rootIds
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/domains/session/tree/preview.ts
|
|
604
|
+
init_esm_shims();
|
|
605
|
+
var TURN_PREVIEW_MAX_CHARS = 60;
|
|
606
|
+
var ESC = String.fromCharCode(27);
|
|
607
|
+
var ANSI_CSI_RE = new RegExp(`${ESC}\\[[0-9;?]*[ -/]*[@-~]`, "g");
|
|
608
|
+
var ANSI_FALLBACK_RE = new RegExp(`${ESC}[@-_]`, "g");
|
|
609
|
+
function stripAnsi(text) {
|
|
610
|
+
return text.replace(ANSI_CSI_RE, "").replace(ANSI_FALLBACK_RE, "");
|
|
611
|
+
}
|
|
612
|
+
function collapseSingleLine(text) {
|
|
613
|
+
return text.replace(/\s+/g, " ").trim();
|
|
614
|
+
}
|
|
615
|
+
function sanitize(text) {
|
|
616
|
+
return collapseSingleLine(stripTokenizerSentinels(stripAnsi(text)));
|
|
617
|
+
}
|
|
618
|
+
function clamp(text, max) {
|
|
619
|
+
if (text.length <= max) return text;
|
|
620
|
+
if (max <= 1) return text.slice(0, max);
|
|
621
|
+
return `${text.slice(0, max - 1)}\u2026`;
|
|
622
|
+
}
|
|
623
|
+
function extractPlainText(payload) {
|
|
624
|
+
if (typeof payload === "string") {
|
|
625
|
+
const sanitized = sanitize(payload);
|
|
626
|
+
return sanitized.length > 0 ? sanitized : null;
|
|
627
|
+
}
|
|
628
|
+
if (!payload || typeof payload !== "object") return null;
|
|
629
|
+
const obj = payload;
|
|
630
|
+
if (typeof obj.text === "string") {
|
|
631
|
+
const sanitized = sanitize(obj.text);
|
|
632
|
+
if (sanitized.length > 0) return sanitized;
|
|
633
|
+
}
|
|
634
|
+
if (Array.isArray(obj.content)) {
|
|
635
|
+
const parts = [];
|
|
636
|
+
for (const block of obj.content) {
|
|
637
|
+
if (!block || typeof block !== "object") continue;
|
|
638
|
+
const b = block;
|
|
639
|
+
if (b.type === "text" && typeof b.text === "string") parts.push(b.text);
|
|
640
|
+
}
|
|
641
|
+
if (parts.length > 0) {
|
|
642
|
+
const sanitized = sanitize(parts.join(" "));
|
|
643
|
+
if (sanitized.length > 0) return sanitized;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
function pickToolArg(toolName, args) {
|
|
649
|
+
if (!args || typeof args !== "object") return "";
|
|
650
|
+
const a = args;
|
|
651
|
+
const preferred = {
|
|
652
|
+
read: ["path"],
|
|
653
|
+
write: ["path"],
|
|
654
|
+
edit: ["path"],
|
|
655
|
+
bash: ["command"],
|
|
656
|
+
code_nav: ["query", "mode"],
|
|
657
|
+
git: ["op"],
|
|
658
|
+
verify: ["check"],
|
|
659
|
+
grep: ["pattern", "query"],
|
|
660
|
+
search: ["query", "pattern"],
|
|
661
|
+
web_fetch: ["url"],
|
|
662
|
+
web_search: ["query"]
|
|
663
|
+
};
|
|
664
|
+
const order = preferred[toolName] ?? [];
|
|
665
|
+
for (const key of order) {
|
|
666
|
+
const value = a[key];
|
|
667
|
+
if (typeof value === "string" && value.length > 0) return sanitize(value);
|
|
668
|
+
if (typeof value === "number") return String(value);
|
|
669
|
+
}
|
|
670
|
+
for (const [, value] of Object.entries(a)) {
|
|
671
|
+
if (typeof value === "string" && value.length > 0) return sanitize(value);
|
|
672
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
673
|
+
}
|
|
674
|
+
return "";
|
|
675
|
+
}
|
|
676
|
+
function extractToolCall(payload) {
|
|
677
|
+
if (!payload || typeof payload !== "object") return null;
|
|
678
|
+
const p = payload;
|
|
679
|
+
const toolName = typeof p.name === "string" ? p.name : typeof p.toolName === "string" ? p.toolName : null;
|
|
680
|
+
if (!toolName) return null;
|
|
681
|
+
const args = p.args !== void 0 ? p.args : p.arguments ?? p.input;
|
|
682
|
+
return { toolName, args };
|
|
683
|
+
}
|
|
684
|
+
function extractToolResult(payload) {
|
|
685
|
+
if (!payload || typeof payload !== "object") return null;
|
|
686
|
+
const p = payload;
|
|
687
|
+
if (!("toolCallId" in p) && !("result" in p) && !("isError" in p)) return null;
|
|
688
|
+
const isError = p.isError === true;
|
|
689
|
+
let text = "";
|
|
690
|
+
const result = p.result;
|
|
691
|
+
if (typeof result === "string") text = result;
|
|
692
|
+
else if (result && typeof result === "object") {
|
|
693
|
+
const r = result;
|
|
694
|
+
if (Array.isArray(r.content)) {
|
|
695
|
+
const parts = [];
|
|
696
|
+
for (const block of r.content) {
|
|
697
|
+
if (!block || typeof block !== "object") continue;
|
|
698
|
+
const b = block;
|
|
699
|
+
if (b.type === "text" && typeof b.text === "string") parts.push(b.text);
|
|
700
|
+
}
|
|
701
|
+
text = parts.join(" ");
|
|
702
|
+
} else if (typeof r.text === "string") {
|
|
703
|
+
text = r.text;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (text.length === 0 && typeof p.errorMessage === "string") text = p.errorMessage;
|
|
707
|
+
return { isError, text: sanitize(text) };
|
|
708
|
+
}
|
|
709
|
+
function assistantStopMarker(payload) {
|
|
710
|
+
if (!payload || typeof payload !== "object") return null;
|
|
711
|
+
const sr = payload.stopReason;
|
|
712
|
+
if (sr === "aborted") return "(aborted)";
|
|
713
|
+
if (sr === "error") return "(error)";
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
function buildTurnPreview(turn, max = TURN_PREVIEW_MAX_CHARS) {
|
|
717
|
+
const budget = Math.max(1, max);
|
|
718
|
+
switch (turn.kind) {
|
|
719
|
+
case "user": {
|
|
720
|
+
const text = extractPlainText(turn.payload);
|
|
721
|
+
return text === null ? "(empty)" : clamp(text, budget);
|
|
722
|
+
}
|
|
723
|
+
case "assistant": {
|
|
724
|
+
const stop = assistantStopMarker(turn.payload);
|
|
725
|
+
const text = extractPlainText(turn.payload);
|
|
726
|
+
if (text !== null) return clamp(text, budget);
|
|
727
|
+
if (stop !== null) return stop;
|
|
728
|
+
if (turn.payload && typeof turn.payload === "object" && Array.isArray(turn.payload.content)) {
|
|
729
|
+
const content = turn.payload.content;
|
|
730
|
+
const toolCalls = content.filter(
|
|
731
|
+
(b) => b && typeof b === "object" && b.type === "toolCall"
|
|
732
|
+
);
|
|
733
|
+
if (toolCalls.length > 0) {
|
|
734
|
+
const names = toolCalls.map((b) => b.name).filter((n) => typeof n === "string");
|
|
735
|
+
if (names.length > 0) return clamp(`(tool calls) ${names.join(", ")}`, budget);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return "(empty)";
|
|
739
|
+
}
|
|
740
|
+
case "tool_call": {
|
|
741
|
+
const call = extractToolCall(turn.payload);
|
|
742
|
+
if (!call) return "(empty)";
|
|
743
|
+
const arg = pickToolArg(call.toolName, call.args);
|
|
744
|
+
const inner = arg.length > 0 ? `"${clamp(arg, Math.max(8, budget - call.toolName.length - 4))}"` : "";
|
|
745
|
+
const composed = `${call.toolName}(${inner})`;
|
|
746
|
+
return clamp(composed, budget);
|
|
747
|
+
}
|
|
748
|
+
case "tool_result": {
|
|
749
|
+
const result = extractToolResult(turn.payload);
|
|
750
|
+
if (!result) return "(empty)";
|
|
751
|
+
const status = result.isError ? "err" : "ok";
|
|
752
|
+
const head = `[${status}]`;
|
|
753
|
+
if (result.text.length === 0) return head;
|
|
754
|
+
const remaining = Math.max(4, budget - head.length - 1);
|
|
755
|
+
return `${head} ${clamp(result.text, remaining)}`;
|
|
756
|
+
}
|
|
757
|
+
case "system": {
|
|
758
|
+
const text = extractPlainText(turn.payload);
|
|
759
|
+
return text === null ? "(system)" : clamp(text, budget);
|
|
760
|
+
}
|
|
761
|
+
case "checkpoint":
|
|
762
|
+
return "(checkpoint)";
|
|
763
|
+
default:
|
|
764
|
+
return `(${String(turn.kind)})`;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/domains/session/extension.ts
|
|
769
|
+
function createSessionBundle(context) {
|
|
770
|
+
let state = null;
|
|
771
|
+
let currentTurnId = null;
|
|
772
|
+
function emitPark(sessionId, reason) {
|
|
773
|
+
context.bus.emit(BusChannels.SessionParked, { sessionId, reason, at: Date.now() });
|
|
774
|
+
}
|
|
775
|
+
function emitResume(sessionId, via) {
|
|
776
|
+
context.bus.emit(BusChannels.SessionResumed, { sessionId, via, at: Date.now() });
|
|
777
|
+
}
|
|
778
|
+
function emitTurnSwitched(sessionId, turnId) {
|
|
779
|
+
context.bus.emit(BusChannels.SessionTurnSwitched, { sessionId, turnId, at: Date.now() });
|
|
780
|
+
}
|
|
781
|
+
function resolveLeafOnOpen(resumed) {
|
|
782
|
+
const pinned = resumed.state.meta.pinnedLeafTurnId;
|
|
783
|
+
if (pinned && resumed.nodes.some((node) => node.id === pinned)) return pinned;
|
|
784
|
+
return computeLeafId(resumed.nodes);
|
|
785
|
+
}
|
|
786
|
+
async function closeCurrent(reason = "close") {
|
|
787
|
+
if (!state) return;
|
|
788
|
+
const s = state;
|
|
789
|
+
emitPark(s.meta.id, reason);
|
|
790
|
+
state = null;
|
|
791
|
+
currentTurnId = null;
|
|
792
|
+
await s.writer.close();
|
|
793
|
+
}
|
|
794
|
+
async function flushIfCurrent(sessionId) {
|
|
795
|
+
if (state?.meta.id === sessionId) {
|
|
796
|
+
await state.writer.persistTree();
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function structuralTreeNodes(entries, previews) {
|
|
800
|
+
const nodes = [];
|
|
801
|
+
for (const entry of entries) {
|
|
802
|
+
if (entry.kind === "compactionSummary") {
|
|
803
|
+
nodes.push({ id: entry.turnId, parentId: entry.parentTurnId, at: entry.timestamp, kind: "compaction" });
|
|
804
|
+
const summarized = entry.messagesSummarized === void 0 ? "history" : `${entry.messagesSummarized} entries`;
|
|
805
|
+
const after = entry.tokensAfter === void 0 ? "" : ` -> ~${entry.tokensAfter}`;
|
|
806
|
+
previews.set(entry.turnId, `${summarized} summarized, ~${entry.tokensBefore}${after} tokens`);
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
if (entry.kind === "branchSummary") {
|
|
810
|
+
nodes.push({ id: entry.turnId, parentId: entry.parentTurnId, at: entry.timestamp, kind: "branch" });
|
|
811
|
+
const text = entry.summary.split("\n", 1)[0] ?? "";
|
|
812
|
+
previews.set(
|
|
813
|
+
entry.turnId,
|
|
814
|
+
text.length > 0 ? `returned from ${entry.fromTurnId}: ${text}` : `returned from ${entry.fromTurnId}`
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return nodes;
|
|
819
|
+
}
|
|
820
|
+
function snapshotFor(sessionId) {
|
|
821
|
+
const bundle = readTreeBundle(sessionId);
|
|
822
|
+
const meta = state?.meta.id === sessionId ? state.meta : bundle.meta;
|
|
823
|
+
const previews = /* @__PURE__ */ new Map();
|
|
824
|
+
let structural = [];
|
|
825
|
+
try {
|
|
826
|
+
for (const entry of bundle.entries) {
|
|
827
|
+
if (entry.kind !== "message") continue;
|
|
828
|
+
const text = buildTurnPreview({ kind: entry.role, payload: entry.payload });
|
|
829
|
+
if (text.length > 0) previews.set(entry.turnId, text);
|
|
830
|
+
}
|
|
831
|
+
structural = structuralTreeNodes(bundle.entries, previews);
|
|
832
|
+
} catch {
|
|
833
|
+
}
|
|
834
|
+
const persistedLeaf = bundle.meta.pinnedLeafTurnId && bundle.nodes.some((node) => node.id === bundle.meta.pinnedLeafTurnId) ? bundle.meta.pinnedLeafTurnId : computeLeafId(bundle.nodes);
|
|
835
|
+
return buildTreeSnapshot({
|
|
836
|
+
meta,
|
|
837
|
+
nodes: [...bundle.nodes, ...structural],
|
|
838
|
+
labels: bundle.labels,
|
|
839
|
+
previews,
|
|
840
|
+
// The leaf is the next append point, so it is computed over turn nodes
|
|
841
|
+
// only: a structural node marks a moment in the timeline, never a place
|
|
842
|
+
// the session continues from.
|
|
843
|
+
leafId: state?.meta.id === sessionId ? currentTurnId : persistedLeaf
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
const contract = {
|
|
847
|
+
current: () => state?.meta ?? null,
|
|
848
|
+
create(input) {
|
|
849
|
+
const cwd = input?.cwd ?? process.cwd();
|
|
850
|
+
const startInput = { cwd };
|
|
851
|
+
if (input?.model !== void 0) startInput.model = input.model;
|
|
852
|
+
if (input?.target !== void 0) startInput.target = input.target;
|
|
853
|
+
if (state) {
|
|
854
|
+
const prior = state;
|
|
855
|
+
emitPark(prior.meta.id, "create_new");
|
|
856
|
+
state = null;
|
|
857
|
+
void prior.writer.close();
|
|
858
|
+
}
|
|
859
|
+
const next = startSession(startInput);
|
|
860
|
+
next.meta.workspace = probeWorkspace(cwd);
|
|
861
|
+
persistSessionMeta(next);
|
|
862
|
+
state = next;
|
|
863
|
+
currentTurnId = null;
|
|
864
|
+
return next.meta;
|
|
865
|
+
},
|
|
866
|
+
append(turn) {
|
|
867
|
+
if (!state) throw new Error("session.append: no current session");
|
|
868
|
+
if (turn.parentId !== currentTurnId) {
|
|
869
|
+
throw new Error(
|
|
870
|
+
`session.append: turn parentId ${turn.parentId ?? "null"} is not this session's current leaf (expected ${currentTurnId ?? "null"}); refusing to parent a turn onto another session's turn`
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
const record = appendTurn(state, turn);
|
|
874
|
+
currentTurnId = record.id;
|
|
875
|
+
if (state.meta.pinnedLeafTurnId !== void 0 && state.meta.pinnedLeafTurnId !== null) {
|
|
876
|
+
state.meta.pinnedLeafTurnId = null;
|
|
877
|
+
persistSessionMeta(state);
|
|
878
|
+
}
|
|
879
|
+
return record;
|
|
880
|
+
},
|
|
881
|
+
appendEntry(entry) {
|
|
882
|
+
if (!state) throw new Error("session.appendEntry: no current session");
|
|
883
|
+
return appendEntry(state, entry);
|
|
884
|
+
},
|
|
885
|
+
replaceEntries(entries) {
|
|
886
|
+
if (!state) throw new Error("session.replaceEntries: no current session");
|
|
887
|
+
replaceEntries(state, entries);
|
|
888
|
+
},
|
|
889
|
+
recordSkillActivation(activation) {
|
|
890
|
+
if (!state) throw new Error("session.recordSkillActivation: no current session");
|
|
891
|
+
appendEntry(state, {
|
|
892
|
+
kind: "skillActivation",
|
|
893
|
+
parentTurnId: activation.turnId ?? null,
|
|
894
|
+
activation
|
|
895
|
+
});
|
|
896
|
+
const existing = state.meta.skillActivations ?? [];
|
|
897
|
+
state.meta.skillActivations = [...existing, activation];
|
|
898
|
+
persistSessionMeta(state);
|
|
899
|
+
return activation;
|
|
900
|
+
},
|
|
901
|
+
async checkpoint(reason) {
|
|
902
|
+
if (!state) throw new Error("session.checkpoint: no current session");
|
|
903
|
+
await performCheckpoint(state, reason);
|
|
904
|
+
},
|
|
905
|
+
flushAppends() {
|
|
906
|
+
if (!state) throw new Error("session.flushAppends: no current session");
|
|
907
|
+
state.writer.flushAppends();
|
|
908
|
+
},
|
|
909
|
+
resume(sessionId) {
|
|
910
|
+
if (state && state.meta.id === sessionId) return state.meta;
|
|
911
|
+
const resumed = resumeSessionState(sessionId);
|
|
912
|
+
if (state) {
|
|
913
|
+
const prior = state;
|
|
914
|
+
emitPark(prior.meta.id, "resume_other");
|
|
915
|
+
void prior.writer.close();
|
|
916
|
+
}
|
|
917
|
+
state = resumed.state;
|
|
918
|
+
currentTurnId = resolveLeafOnOpen(resumed);
|
|
919
|
+
emitResume(resumed.state.meta.id, "resume");
|
|
920
|
+
return resumed.state.meta;
|
|
921
|
+
},
|
|
922
|
+
fork(parentTurnId, input) {
|
|
923
|
+
if (!state) throw new Error("session.fork: no current session to fork from");
|
|
924
|
+
const prior = state;
|
|
925
|
+
const { next, nodes } = forkFromState({
|
|
926
|
+
from: prior,
|
|
927
|
+
parentTurnId,
|
|
928
|
+
...input?.cwd !== void 0 ? { cwd: input.cwd } : {}
|
|
929
|
+
});
|
|
930
|
+
emitPark(prior.meta.id, "fork");
|
|
931
|
+
state = next;
|
|
932
|
+
currentTurnId = computeLeafId(nodes);
|
|
933
|
+
return next.meta;
|
|
934
|
+
},
|
|
935
|
+
tree(sessionId) {
|
|
936
|
+
const id = sessionId ?? state?.meta.id;
|
|
937
|
+
if (!id) throw new Error("session.tree: no sessionId provided and no current session");
|
|
938
|
+
if (state?.meta.id === id) void flushIfCurrent(id);
|
|
939
|
+
return snapshotFor(id);
|
|
940
|
+
},
|
|
941
|
+
switchBranch(sessionId) {
|
|
942
|
+
if (state?.meta.id === sessionId) return state.meta;
|
|
943
|
+
const resumed = resumeSessionState(sessionId);
|
|
944
|
+
if (state) {
|
|
945
|
+
const prior = state;
|
|
946
|
+
emitPark(prior.meta.id, "switch_branch");
|
|
947
|
+
void prior.writer.close();
|
|
948
|
+
}
|
|
949
|
+
state = resumed.state;
|
|
950
|
+
currentTurnId = resolveLeafOnOpen(resumed);
|
|
951
|
+
emitResume(resumed.state.meta.id, "switch_branch");
|
|
952
|
+
return resumed.state.meta;
|
|
953
|
+
},
|
|
954
|
+
switchTurn(turnId) {
|
|
955
|
+
if (!state) throw new Error("session.switchTurn: no current session");
|
|
956
|
+
void flushIfCurrent(state.meta.id);
|
|
957
|
+
const nodes = readTreeBundle(state.meta.id).nodes;
|
|
958
|
+
if (!nodes.some((node) => node.id === turnId)) {
|
|
959
|
+
throw new Error(`session.switchTurn: turn not found: ${turnId}`);
|
|
960
|
+
}
|
|
961
|
+
currentTurnId = turnId;
|
|
962
|
+
state.meta.pinnedLeafTurnId = turnId;
|
|
963
|
+
persistSessionMeta(state);
|
|
964
|
+
emitTurnSwitched(state.meta.id, turnId);
|
|
965
|
+
return state.meta;
|
|
966
|
+
},
|
|
967
|
+
editLabel(turnId, label, sessionId) {
|
|
968
|
+
const targetId = sessionId ?? state?.meta.id;
|
|
969
|
+
if (!targetId) throw new Error("session.editLabel: no sessionId provided and no current session");
|
|
970
|
+
if (state && state.meta.id === targetId) {
|
|
971
|
+
appendEntry(state, {
|
|
972
|
+
kind: "label",
|
|
973
|
+
parentTurnId: null,
|
|
974
|
+
targetTurnId: turnId,
|
|
975
|
+
label
|
|
976
|
+
});
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const entry = {
|
|
980
|
+
kind: "label",
|
|
981
|
+
turnId: newTurnId(),
|
|
982
|
+
parentTurnId: null,
|
|
983
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
984
|
+
targetTurnId: turnId,
|
|
985
|
+
label
|
|
986
|
+
};
|
|
987
|
+
appendEntryToSessionFile(targetId, entry);
|
|
988
|
+
},
|
|
989
|
+
setName(name, sessionId) {
|
|
990
|
+
const targetId = sessionId ?? state?.meta.id;
|
|
991
|
+
if (!targetId) throw new Error("session.setName: no sessionId provided and no current session");
|
|
992
|
+
if (state && state.meta.id === targetId) {
|
|
993
|
+
appendEntry(state, {
|
|
994
|
+
kind: "sessionInfo",
|
|
995
|
+
parentTurnId: null,
|
|
996
|
+
name
|
|
997
|
+
});
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const entry = {
|
|
1001
|
+
kind: "sessionInfo",
|
|
1002
|
+
turnId: newTurnId(),
|
|
1003
|
+
parentTurnId: null,
|
|
1004
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1005
|
+
name
|
|
1006
|
+
};
|
|
1007
|
+
appendEntryToSessionFile(targetId, entry);
|
|
1008
|
+
},
|
|
1009
|
+
deleteSession(id, opts) {
|
|
1010
|
+
if (state?.meta.id === id) {
|
|
1011
|
+
throw new Error("session.deleteSession: refusing to delete the currently open session; close() first");
|
|
1012
|
+
}
|
|
1013
|
+
const options = opts ?? {};
|
|
1014
|
+
if (options.keepFiles) {
|
|
1015
|
+
tombstoneSession(id);
|
|
1016
|
+
} else {
|
|
1017
|
+
removeSessionDirectory(id);
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
history() {
|
|
1021
|
+
const cwd = state?.meta.cwd ?? process.cwd();
|
|
1022
|
+
return listSessionsForCwd(cwd);
|
|
1023
|
+
},
|
|
1024
|
+
async close() {
|
|
1025
|
+
await closeCurrent();
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
const extension = {
|
|
1029
|
+
async start() {
|
|
1030
|
+
},
|
|
1031
|
+
async stop() {
|
|
1032
|
+
if (!state) return;
|
|
1033
|
+
try {
|
|
1034
|
+
await performCheckpoint(state, "shutdown");
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
process.stderr.write(
|
|
1037
|
+
`[clio:session] shutdown checkpoint failed: ${err instanceof Error ? err.message : String(err)}
|
|
1038
|
+
`
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
await closeCurrent("shutdown");
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
return { extension, contract };
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// src/domains/session/manifest.ts
|
|
1048
|
+
init_esm_shims();
|
|
1049
|
+
var SessionManifest = {
|
|
1050
|
+
name: "session",
|
|
1051
|
+
dependsOn: ["config"]
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
// src/domains/session/protected-artifacts.ts
|
|
1055
|
+
init_esm_shims();
|
|
1056
|
+
function protectedArtifactEntryFromArtifact(artifact, context) {
|
|
1057
|
+
const entry = {
|
|
1058
|
+
kind: "protectedArtifact",
|
|
1059
|
+
parentTurnId: context.parentTurnId,
|
|
1060
|
+
action: "protect",
|
|
1061
|
+
artifact: artifactSnapshot(artifact)
|
|
1062
|
+
};
|
|
1063
|
+
if (context.toolName !== void 0) entry.toolName = context.toolName;
|
|
1064
|
+
if (context.toolCallId !== void 0) entry.toolCallId = context.toolCallId;
|
|
1065
|
+
if (context.runId !== void 0) entry.runId = context.runId;
|
|
1066
|
+
if (context.correlationId !== void 0) entry.correlationId = context.correlationId;
|
|
1067
|
+
return entry;
|
|
1068
|
+
}
|
|
1069
|
+
function protectedArtifactFromSessionEntry(entry) {
|
|
1070
|
+
return protectedArtifactFromSnapshot(entry.artifact);
|
|
1071
|
+
}
|
|
1072
|
+
function protectedArtifactStateFromSessionEntries(entries) {
|
|
1073
|
+
let state = { artifacts: [] };
|
|
1074
|
+
const protectedEntries = entries.map((entry, index) => ({ entry, index })).filter((item) => item.entry.kind === "protectedArtifact").sort(compareProtectedEntryPositions);
|
|
1075
|
+
for (const item of protectedEntries) {
|
|
1076
|
+
if (item.entry.action !== "protect") continue;
|
|
1077
|
+
state = protectArtifact(state, protectedArtifactFromSessionEntry(item.entry));
|
|
1078
|
+
}
|
|
1079
|
+
return state;
|
|
1080
|
+
}
|
|
1081
|
+
function artifactSnapshot(artifact) {
|
|
1082
|
+
const snapshot = {
|
|
1083
|
+
path: artifact.path,
|
|
1084
|
+
protectedAt: artifact.protectedAt,
|
|
1085
|
+
reason: artifact.reason,
|
|
1086
|
+
source: artifact.source
|
|
1087
|
+
};
|
|
1088
|
+
if (artifact.validationCommand !== void 0) snapshot.validationCommand = artifact.validationCommand;
|
|
1089
|
+
if (artifact.validationExitCode !== void 0) snapshot.validationExitCode = artifact.validationExitCode;
|
|
1090
|
+
return snapshot;
|
|
1091
|
+
}
|
|
1092
|
+
function protectedArtifactFromSnapshot(snapshot) {
|
|
1093
|
+
const artifact = {
|
|
1094
|
+
path: snapshot.path,
|
|
1095
|
+
protectedAt: snapshot.protectedAt,
|
|
1096
|
+
reason: snapshot.reason,
|
|
1097
|
+
source: snapshot.source
|
|
1098
|
+
};
|
|
1099
|
+
if (snapshot.validationCommand !== void 0) artifact.validationCommand = snapshot.validationCommand;
|
|
1100
|
+
if (snapshot.validationExitCode !== void 0) artifact.validationExitCode = snapshot.validationExitCode;
|
|
1101
|
+
return artifact;
|
|
1102
|
+
}
|
|
1103
|
+
function compareProtectedEntryPositions(left, right) {
|
|
1104
|
+
return compareStrings(left.entry.timestamp, right.entry.timestamp) || compareStrings(left.entry.turnId, right.entry.turnId) || left.index - right.index;
|
|
1105
|
+
}
|
|
1106
|
+
function compareStrings(left, right) {
|
|
1107
|
+
if (left < right) return -1;
|
|
1108
|
+
if (left > right) return 1;
|
|
1109
|
+
return 0;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// src/domains/session/usage.ts
|
|
1113
|
+
init_esm_shims();
|
|
1114
|
+
function numberAt(source, key) {
|
|
1115
|
+
const value = source[key];
|
|
1116
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
1117
|
+
}
|
|
1118
|
+
function reasoningTokensOf(usage) {
|
|
1119
|
+
const direct = numberAt(usage, "reasoningTokens");
|
|
1120
|
+
if (direct > 0) return direct;
|
|
1121
|
+
const details = usage.outputTokensDetails ?? usage.completionTokensDetails;
|
|
1122
|
+
if (details && typeof details === "object") return numberAt(details, "reasoningTokens");
|
|
1123
|
+
return 0;
|
|
1124
|
+
}
|
|
1125
|
+
function stringAt(source, ...keys) {
|
|
1126
|
+
for (const key of keys) {
|
|
1127
|
+
const value = source[key];
|
|
1128
|
+
if (typeof value === "string" && value.trim().length > 0) return value;
|
|
1129
|
+
}
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1132
|
+
function ledgerUsageCalls(entries, defaults = {}) {
|
|
1133
|
+
const calls = [];
|
|
1134
|
+
let currentTarget = defaults.target ?? null;
|
|
1135
|
+
let currentModel = defaults.model ?? null;
|
|
1136
|
+
for (const entry of entries) {
|
|
1137
|
+
if (entry?.kind === "compactionSummary") {
|
|
1138
|
+
const usage2 = entry.usage;
|
|
1139
|
+
if (!usage2 || usage2.totalTokens <= 0) continue;
|
|
1140
|
+
calls.push({
|
|
1141
|
+
providerId: currentTarget ?? "unknown",
|
|
1142
|
+
modelId: currentModel ?? "unknown",
|
|
1143
|
+
input: usage2.input,
|
|
1144
|
+
output: usage2.output,
|
|
1145
|
+
cacheRead: usage2.cacheRead,
|
|
1146
|
+
cacheWrite: usage2.cacheWrite,
|
|
1147
|
+
reasoningTokens: usage2.reasoning,
|
|
1148
|
+
totalTokens: usage2.totalTokens,
|
|
1149
|
+
costUsd: usage2.cost.total,
|
|
1150
|
+
// A split turn runs two summarization streams under one entry; the
|
|
1151
|
+
// provider served that many calls even though one row records them.
|
|
1152
|
+
apiCalls: Math.max(1, Math.round(usage2.apiCalls))
|
|
1153
|
+
});
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (entry?.kind === "modelChange") {
|
|
1157
|
+
const change = entry;
|
|
1158
|
+
if (typeof change.target === "string" && change.target.length > 0) currentTarget = change.target;
|
|
1159
|
+
else if (typeof change.provider === "string" && change.provider.length > 0) currentTarget = change.provider;
|
|
1160
|
+
if (typeof change.modelId === "string" && change.modelId.length > 0) currentModel = change.modelId;
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (entry?.kind !== "message" || entry.role !== "assistant") continue;
|
|
1164
|
+
const payload = entry.payload;
|
|
1165
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) continue;
|
|
1166
|
+
const record = payload;
|
|
1167
|
+
const stopReason = record.stopReason;
|
|
1168
|
+
if (stopReason === "aborted" || stopReason === "error") continue;
|
|
1169
|
+
const rawUsage = record.usage;
|
|
1170
|
+
if (!rawUsage || typeof rawUsage !== "object" || Array.isArray(rawUsage)) continue;
|
|
1171
|
+
const usage = rawUsage;
|
|
1172
|
+
const input = numberAt(usage, "input");
|
|
1173
|
+
const output = numberAt(usage, "output");
|
|
1174
|
+
const cacheRead = numberAt(usage, "cacheRead");
|
|
1175
|
+
const cacheWrite = numberAt(usage, "cacheWrite");
|
|
1176
|
+
const totalTokens = numberAt(usage, "totalTokens") || input + output + cacheRead + cacheWrite;
|
|
1177
|
+
if (totalTokens === 0) continue;
|
|
1178
|
+
const cost = usage.cost;
|
|
1179
|
+
const costUsd = cost && typeof cost === "object" ? numberAt(cost, "total") : 0;
|
|
1180
|
+
calls.push({
|
|
1181
|
+
providerId: currentTarget ?? stringAt(record, "provider", "api") ?? "unknown",
|
|
1182
|
+
modelId: currentModel ?? stringAt(record, "responseModel", "model") ?? "unknown",
|
|
1183
|
+
input,
|
|
1184
|
+
output,
|
|
1185
|
+
cacheRead,
|
|
1186
|
+
cacheWrite,
|
|
1187
|
+
reasoningTokens: reasoningTokensOf(usage),
|
|
1188
|
+
totalTokens,
|
|
1189
|
+
costUsd
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
return calls;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// src/domains/session/index.ts
|
|
1196
|
+
var SessionDomainModule = {
|
|
1197
|
+
manifest: SessionManifest,
|
|
1198
|
+
createExtension: createSessionBundle
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
// src/domains/session/archive-readers.ts
|
|
1202
|
+
async function readAuditRows(stateDir) {
|
|
1203
|
+
const root = join3(stateDir, "audit");
|
|
1204
|
+
let files;
|
|
1205
|
+
try {
|
|
1206
|
+
files = await readdir(root);
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
const err = error;
|
|
1209
|
+
if (err.code === "ENOENT") return { rows: [], errors: [] };
|
|
1210
|
+
return { rows: [], errors: [`audit root read error: ${err.message ?? String(err)}`] };
|
|
1211
|
+
}
|
|
1212
|
+
const rows = [];
|
|
1213
|
+
const errors = [];
|
|
1214
|
+
for (const file of files.filter((name) => name.endsWith(".jsonl")).sort(compareStrings2)) {
|
|
1215
|
+
const path = join3(root, file);
|
|
1216
|
+
let raw;
|
|
1217
|
+
try {
|
|
1218
|
+
raw = await readFile(path, "utf8");
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
const err = error;
|
|
1221
|
+
errors.push(`${path}: ${err.message ?? String(err)}`);
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
const lines = raw.split("\n");
|
|
1225
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1226
|
+
const line = lines[index];
|
|
1227
|
+
if (line === void 0 || line.length === 0) continue;
|
|
1228
|
+
let parsed;
|
|
1229
|
+
try {
|
|
1230
|
+
parsed = JSON.parse(line);
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1233
|
+
errors.push(`${path}:${index + 1}: invalid JSON: ${message}`);
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
if (!isRecord(parsed)) {
|
|
1237
|
+
errors.push(`${path}:${index + 1}: expected object`);
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
const auditKind = readOptionalString(parsed.kind) ?? "tool_call";
|
|
1241
|
+
rows.push({
|
|
1242
|
+
file,
|
|
1243
|
+
line: index + 1,
|
|
1244
|
+
row: parsed,
|
|
1245
|
+
ts: readOptionalString(parsed.ts),
|
|
1246
|
+
auditKind,
|
|
1247
|
+
correlationId: readOptionalString(parsed.correlationId) ?? ""
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return { rows, errors };
|
|
1252
|
+
}
|
|
1253
|
+
async function readSessionEntriesForId(stateDir, sessionId) {
|
|
1254
|
+
const root = join3(stateDir, "sessions");
|
|
1255
|
+
let cwdHashes;
|
|
1256
|
+
try {
|
|
1257
|
+
cwdHashes = await readdir(root);
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
const err = error;
|
|
1260
|
+
if (err.code === "ENOENT") return { entries: [], missing: true, errors: [] };
|
|
1261
|
+
return { entries: [], missing: false, errors: [`session root read error: ${err.message ?? String(err)}`] };
|
|
1262
|
+
}
|
|
1263
|
+
for (const cwdHash2 of cwdHashes.sort(compareStrings2)) {
|
|
1264
|
+
const currentPath = join3(root, cwdHash2, sessionId, "current.jsonl");
|
|
1265
|
+
let raw;
|
|
1266
|
+
let source = currentPath;
|
|
1267
|
+
try {
|
|
1268
|
+
raw = await readFile(currentPath, "utf8");
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
const err = error;
|
|
1271
|
+
if (err.code === "ENOENT") {
|
|
1272
|
+
const tmpPath = `${currentPath}.tmp`;
|
|
1273
|
+
try {
|
|
1274
|
+
raw = await readFile(tmpPath, "utf8");
|
|
1275
|
+
source = tmpPath;
|
|
1276
|
+
} catch {
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
} else if (err.code === "ENOTDIR") {
|
|
1280
|
+
continue;
|
|
1281
|
+
} else {
|
|
1282
|
+
return { entries: [], missing: false, errors: [`${currentPath}: ${err.message ?? String(err)}`] };
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return parseSessionEntries(raw, source);
|
|
1286
|
+
}
|
|
1287
|
+
return { entries: [], missing: true, errors: [] };
|
|
1288
|
+
}
|
|
1289
|
+
function parseSessionEntries(raw, source) {
|
|
1290
|
+
const entries = [];
|
|
1291
|
+
const errors = [];
|
|
1292
|
+
const lines = raw.split("\n");
|
|
1293
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1294
|
+
const line = lines[index];
|
|
1295
|
+
if (line === void 0 || line.trim().length === 0) continue;
|
|
1296
|
+
let parsed;
|
|
1297
|
+
try {
|
|
1298
|
+
parsed = JSON.parse(line);
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1301
|
+
errors.push(`${source}:${index + 1}: invalid JSON: ${message}`);
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
if (isSessionHeader(parsed)) continue;
|
|
1305
|
+
if (isSessionEntry(parsed)) {
|
|
1306
|
+
entries.push(parsed);
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
errors.push(
|
|
1310
|
+
`session ledger contains an unreadable entry (missing kind discriminant): ${source}. Remove or compact the session to reset.`
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
return { entries, missing: false, errors };
|
|
1314
|
+
}
|
|
1315
|
+
async function listSessionLedgerRefs(stateDir, onlyCwdHash) {
|
|
1316
|
+
const root = join3(stateDir, "sessions");
|
|
1317
|
+
let cwdHashes;
|
|
1318
|
+
try {
|
|
1319
|
+
cwdHashes = await readdir(root);
|
|
1320
|
+
} catch {
|
|
1321
|
+
return [];
|
|
1322
|
+
}
|
|
1323
|
+
const refs = [];
|
|
1324
|
+
for (const cwdHash2 of cwdHashes.sort(compareStrings2)) {
|
|
1325
|
+
if (onlyCwdHash !== void 0 && cwdHash2 !== onlyCwdHash) continue;
|
|
1326
|
+
let sessionIds;
|
|
1327
|
+
try {
|
|
1328
|
+
sessionIds = await readdir(join3(root, cwdHash2));
|
|
1329
|
+
} catch {
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
for (const sessionId of sessionIds.sort(compareStrings2)) {
|
|
1333
|
+
refs.push({ cwdHash: cwdHash2, sessionId, path: join3(root, cwdHash2, sessionId, "current.jsonl") });
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
return refs;
|
|
1337
|
+
}
|
|
1338
|
+
function readOptionalString(value) {
|
|
1339
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1340
|
+
}
|
|
1341
|
+
function isRecord(value) {
|
|
1342
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1343
|
+
}
|
|
1344
|
+
function compareStrings2(a, b) {
|
|
1345
|
+
return a.localeCompare(b);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
export {
|
|
1349
|
+
collectSessionEntries,
|
|
1350
|
+
readAuditRows,
|
|
1351
|
+
readSessionEntriesForId,
|
|
1352
|
+
parseSessionEntries,
|
|
1353
|
+
listSessionLedgerRefs,
|
|
1354
|
+
protectedArtifactEntryFromArtifact,
|
|
1355
|
+
protectedArtifactFromSessionEntry,
|
|
1356
|
+
protectedArtifactStateFromSessionEntries,
|
|
1357
|
+
ledgerUsageCalls,
|
|
1358
|
+
SessionDomainModule
|
|
1359
|
+
};
|
|
1360
|
+
//# sourceMappingURL=chunk-QQK64KLB.js.map
|