@bunshin-cc/bunshin 0.4.2 → 0.4.3
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/dist/bin.js +91 -15
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -2387,6 +2387,17 @@ var init_store2 = __esm({
|
|
|
2387
2387
|
this.writeSession(s);
|
|
2388
2388
|
return s;
|
|
2389
2389
|
}
|
|
2390
|
+
listSessions() {
|
|
2391
|
+
const dir = this.p("sessions");
|
|
2392
|
+
if (!fileExists(dir)) return [];
|
|
2393
|
+
const out = [];
|
|
2394
|
+
for (const f of fs4.readdirSync(dir)) {
|
|
2395
|
+
if (!f.endsWith(".json") || f === "current.json") continue;
|
|
2396
|
+
const raw = readJson(path3.join(dir, f), null);
|
|
2397
|
+
if (raw?.sessionId) out.push(normalizeSession(raw.sessionId, raw));
|
|
2398
|
+
}
|
|
2399
|
+
return out;
|
|
2400
|
+
}
|
|
2390
2401
|
readCurrent() {
|
|
2391
2402
|
return readJson(this.p("sessions", "current.json"), null);
|
|
2392
2403
|
}
|
|
@@ -3790,8 +3801,8 @@ __export(sync_exports, {
|
|
|
3790
3801
|
import fs9 from "node:fs";
|
|
3791
3802
|
import path8 from "node:path";
|
|
3792
3803
|
function episodePayload(e) {
|
|
3793
|
-
const { orgId: _o, uploaded: _u, servedReported: _s, ...rest } = e;
|
|
3794
|
-
void _o, _u, _s;
|
|
3804
|
+
const { orgId: _o, uploaded: _u, servedReported: _s, reportedServed: _rs, reportedLoaded: _rl, ...rest } = e;
|
|
3805
|
+
void _o, _u, _s, _rs, _rl;
|
|
3795
3806
|
return rest;
|
|
3796
3807
|
}
|
|
3797
3808
|
function suggestionPayload(s) {
|
|
@@ -3831,15 +3842,18 @@ async function syncV2(store, opts = {}) {
|
|
|
3831
3842
|
await call("POST", "/episodes", { episode: episodePayload(e) });
|
|
3832
3843
|
const fresh = v2.readEpisode(e.id) ?? e;
|
|
3833
3844
|
const next = { ...fresh, uploaded: JSON.stringify(episodePayload(fresh)) === JSON.stringify(episodePayload(e)) ? true : fresh.uploaded };
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3845
|
+
const legacy = fresh.servedReported && !fresh.reportedServed;
|
|
3846
|
+
const doneServed = new Set(legacy ? e.sectionsServed : fresh.reportedServed ?? []);
|
|
3847
|
+
const doneLoaded = new Set(legacy ? e.sectionsLoaded : fresh.reportedLoaded ?? []);
|
|
3848
|
+
const served = e.sectionsServed.filter((id) => !isLocalId(id) && !doneServed.has(id));
|
|
3849
|
+
const loaded = e.sectionsLoaded.filter((id) => !isLocalId(id) && !doneLoaded.has(id));
|
|
3850
|
+
try {
|
|
3851
|
+
if (served.length) await call("POST", "/recall/served", { sectionIds: served });
|
|
3852
|
+
if (loaded.length) await call("POST", "/recall/served", { sectionIds: loaded, loaded: true });
|
|
3853
|
+
next.reportedServed = [...doneServed, ...served];
|
|
3854
|
+
next.reportedLoaded = [...doneLoaded, ...loaded];
|
|
3855
|
+
delete next.servedReported;
|
|
3856
|
+
} catch {
|
|
3843
3857
|
}
|
|
3844
3858
|
v2.writeEpisode(next);
|
|
3845
3859
|
report.uploadedEpisodes++;
|
|
@@ -8030,6 +8044,10 @@ init_store2();
|
|
|
8030
8044
|
init_view();
|
|
8031
8045
|
var NUDGE_TEXT = "This looks worth capturing. Consider /learn.";
|
|
8032
8046
|
var NUDGE_INTERVAL_MS = 60 * 6e4;
|
|
8047
|
+
var CHECKPOINT_MS = 30 * 6e4;
|
|
8048
|
+
var LEFTOVER_IDLE_MS = 60 * 6e4;
|
|
8049
|
+
var LEFTOVER_MAX_AGE_MS = 14 * 24 * 60 * 6e4;
|
|
8050
|
+
var LEFTOVERS_PER_START = 5;
|
|
8033
8051
|
var MAX_PUSH = 3;
|
|
8034
8052
|
var additional = (hookEventName, text2) => text2.trim() ? { hookSpecificOutput: { hookEventName, additionalContext: text2.trim() } } : {};
|
|
8035
8053
|
function touch(v2, ctx, patch = () => {
|
|
@@ -8072,6 +8090,11 @@ async function onSessionStart(ctx) {
|
|
|
8072
8090
|
v2.writeCurrent({ sessionId: id, cwd: ctx.cwd, at: now });
|
|
8073
8091
|
const config = ctx.store.config();
|
|
8074
8092
|
if (canCall(config)) {
|
|
8093
|
+
try {
|
|
8094
|
+
const swept = checkpointLeftovers(ctx.store, id);
|
|
8095
|
+
if (swept.length) v2.log("v2-leftovers", { session: id, episodes: swept });
|
|
8096
|
+
} catch {
|
|
8097
|
+
}
|
|
8075
8098
|
const budget = Math.min(3e3, ctx.deadline - Date.now() - 400);
|
|
8076
8099
|
if (budget > 100) {
|
|
8077
8100
|
try {
|
|
@@ -8254,6 +8277,16 @@ function onStop(ctx) {
|
|
|
8254
8277
|
const config = ctx.store.config();
|
|
8255
8278
|
if (ctx.input.stop_hook_active) return {};
|
|
8256
8279
|
const scan = scanTranscript(ctx.input.transcript_path ?? session.transcriptPath);
|
|
8280
|
+
if (canCall(config) && checkpointDue(session, scan, now)) {
|
|
8281
|
+
try {
|
|
8282
|
+
const { episode } = buildEpisode(ctx.store, session, void 0, now, { scan });
|
|
8283
|
+
v2.writeEpisode(episode);
|
|
8284
|
+
markCheckpoint(v2, session, scan, now);
|
|
8285
|
+
ctx.detach(["--root", ctx.store.root, "sync", "--quiet"], { cwd: ctx.cwd });
|
|
8286
|
+
v2.log("v2-checkpoint", { session: session.sessionId, episode: episode.id });
|
|
8287
|
+
} catch {
|
|
8288
|
+
}
|
|
8289
|
+
}
|
|
8257
8290
|
const check = learningCheck(v2, session, scan.facts.toolCalls, now);
|
|
8258
8291
|
if (check) return check;
|
|
8259
8292
|
if (!config.reflect.nudge) return {};
|
|
@@ -8285,11 +8318,11 @@ function learningCheck(v2, session, toolCalls, now) {
|
|
|
8285
8318
|
const reason = doc.body.replace(/^\s*<!--\s*bunshin:[\s\S]*?-->\s*/, "").trim().replace(/\{delta\}/g, String(toolCalls - last));
|
|
8286
8319
|
return { decision: "block", reason };
|
|
8287
8320
|
}
|
|
8288
|
-
function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new Date()) {
|
|
8321
|
+
function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new Date(), opts = {}) {
|
|
8289
8322
|
const v2 = new V2Store(store);
|
|
8290
8323
|
const config = store.config();
|
|
8291
8324
|
const view = localView(v2, config, session.cwd, { index: false });
|
|
8292
|
-
const scan = scanTranscript(transcriptPath);
|
|
8325
|
+
const scan = opts.scan ?? scanTranscript(transcriptPath ?? session.transcriptPath);
|
|
8293
8326
|
const outcome = v2.readOutcome(session.sessionId);
|
|
8294
8327
|
const signals = { ...sessionSignals({ scan, startedAt: session.startedAt, lastActivityAt: session.lastActivityAt }), ...outcome ? { outcome: outcome.status } : {} };
|
|
8295
8328
|
const gate = sessionGate(signals, { now: now.toISOString(), cacheTtlMs: config.reflect.cacheTtlMs });
|
|
@@ -8306,7 +8339,7 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
|
|
|
8306
8339
|
harness: ["claude-code", "codex", "pi", "other"].includes(session.harness) ? session.harness : config.harness,
|
|
8307
8340
|
task: session.task,
|
|
8308
8341
|
startedAt: session.startedAt,
|
|
8309
|
-
endedAt: now.toISOString(),
|
|
8342
|
+
endedAt: opts.endedAt ?? now.toISOString(),
|
|
8310
8343
|
sectionsServed: session.served,
|
|
8311
8344
|
sectionsLoaded: session.loaded,
|
|
8312
8345
|
captured: session.captured,
|
|
@@ -8319,10 +8352,53 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
|
|
|
8319
8352
|
...snippets.length ? { snippets } : {},
|
|
8320
8353
|
...outcome ? { outcome } : {},
|
|
8321
8354
|
...view.bundle?.release?.version ? { harnessVersion: view.bundle.release.version } : {},
|
|
8322
|
-
uploaded: false
|
|
8355
|
+
uploaded: false,
|
|
8356
|
+
...previous?.reportedServed ? { reportedServed: previous.reportedServed } : {},
|
|
8357
|
+
...previous?.reportedLoaded ? { reportedLoaded: previous.reportedLoaded } : {},
|
|
8358
|
+
...previous?.servedReported ? { servedReported: true } : {}
|
|
8323
8359
|
};
|
|
8324
8360
|
return { episode, gate, scan };
|
|
8325
8361
|
}
|
|
8362
|
+
var progressOf = (scan) => scan.facts.toolCalls + scan.facts.turns.length;
|
|
8363
|
+
function checkpointDue(session, scan, now) {
|
|
8364
|
+
const since = Date.parse(session.checkpointAt ?? session.startedAt);
|
|
8365
|
+
if (!Number.isFinite(since) || now.getTime() - since < CHECKPOINT_MS) return false;
|
|
8366
|
+
return progressOf(scan) > (session.checkpointProgress ?? 0);
|
|
8367
|
+
}
|
|
8368
|
+
function markCheckpoint(v2, session, scan, now) {
|
|
8369
|
+
v2.updateSession(session.sessionId, session.cwd, (s) => {
|
|
8370
|
+
s.checkpointAt = now.toISOString();
|
|
8371
|
+
s.checkpointProgress = progressOf(scan);
|
|
8372
|
+
s.checkpointActivity = s.lastActivityAt;
|
|
8373
|
+
});
|
|
8374
|
+
}
|
|
8375
|
+
function checkpointLeftovers(store, currentSessionId, now = /* @__PURE__ */ new Date()) {
|
|
8376
|
+
const v2 = new V2Store(store);
|
|
8377
|
+
const out = [];
|
|
8378
|
+
const candidates = v2.listSessions().filter((s) => {
|
|
8379
|
+
if (s.sessionId === currentSessionId || !s.transcriptPath) return false;
|
|
8380
|
+
const idle = now.getTime() - Date.parse(s.lastActivityAt);
|
|
8381
|
+
if (!(idle >= LEFTOVER_IDLE_MS && idle <= LEFTOVER_MAX_AGE_MS)) return false;
|
|
8382
|
+
return s.checkpointActivity !== s.lastActivityAt;
|
|
8383
|
+
}).sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt));
|
|
8384
|
+
for (const s of candidates) {
|
|
8385
|
+
if (out.length >= LEFTOVERS_PER_START) break;
|
|
8386
|
+
const config = store.config();
|
|
8387
|
+
const id = episodeIdFor(localView(v2, config, s.cwd, { index: false }).ctx.id, s.sessionId);
|
|
8388
|
+
const prev = v2.readEpisode(id);
|
|
8389
|
+
if (prev && prev.endedAt >= s.lastActivityAt) {
|
|
8390
|
+
v2.updateSession(s.sessionId, s.cwd, (x) => {
|
|
8391
|
+
x.checkpointActivity = x.lastActivityAt;
|
|
8392
|
+
});
|
|
8393
|
+
continue;
|
|
8394
|
+
}
|
|
8395
|
+
const { episode, scan } = buildEpisode(store, s, void 0, now, { endedAt: s.lastActivityAt });
|
|
8396
|
+
v2.writeEpisode(episode);
|
|
8397
|
+
markCheckpoint(v2, s, scan, now);
|
|
8398
|
+
out.push(episode.id);
|
|
8399
|
+
}
|
|
8400
|
+
return out;
|
|
8401
|
+
}
|
|
8326
8402
|
function recordTrialEvidence(v2, session, outcome) {
|
|
8327
8403
|
if (!outcome || outcome.status === "unknown") return 0;
|
|
8328
8404
|
const used = /* @__PURE__ */ new Set([...session.loaded, ...session.pushed]);
|