@bunshin-cc/bunshin 0.4.1 → 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 +106 -16
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -879,6 +879,8 @@ function firstHeading(text2) {
|
|
|
879
879
|
function applyTextOp(text2, op, legacyAnchor = "") {
|
|
880
880
|
const anchor = "anchor" in op && op.anchor || legacyAnchor;
|
|
881
881
|
switch (op.op) {
|
|
882
|
+
case "situation":
|
|
883
|
+
return { ok: true, text: text2 };
|
|
882
884
|
case "rewrite":
|
|
883
885
|
return { ok: true, text: op.text };
|
|
884
886
|
case "create":
|
|
@@ -1744,6 +1746,11 @@ ${robot ? `- Values that belong to THIS robot or cell (calibration, pixel column
|
|
|
1744
1746
|
- Code that should change (a hard-coded constant, a helper) \u2192 describe it as an append to the
|
|
1745
1747
|
skill's section, stating the change; reconciliation turns it into a harness patch.` : `- Machine specifics and personal preferences \u2192 your own user layer.`}
|
|
1746
1748
|
- Never include secrets, credentials or customer data.
|
|
1749
|
+
- A retrieval miss: if a document in the list above would have helped, but the agent did not use
|
|
1750
|
+
it (the user had to point at it, asked why it was not used, or the agent found it only late), add
|
|
1751
|
+
op "situation" to that document: the request as the user actually phrased it, made general (names
|
|
1752
|
+
and ticket ids replaced by what they stand for), e.g. "run me through an e2e test and what it
|
|
1753
|
+
does". Recall learns from it, so the next such request finds the document.
|
|
1747
1754
|
|
|
1748
1755
|
## Output
|
|
1749
1756
|
|
|
@@ -1755,7 +1762,8 @@ At most ${max} suggestions. Schema:
|
|
|
1755
1762
|
| {"op": "create", "section": {"kind": "skill"|"procedure"|"spec"|"verification"|"rule"|"guardrail"|"reference",
|
|
1756
1763
|
"title": string, "body": string, "when": string,
|
|
1757
1764
|
"paths"?: string[], "entities"?: string[], "skills"?: string[]}}
|
|
1758
|
-
| {"op": "param", "key": string, "value": string|number|boolean}
|
|
1765
|
+
| {"op": "param", "key": string, "value": string|number|boolean}
|
|
1766
|
+
| {"op": "situation", "text": string},
|
|
1759
1767
|
"rationale": string, "urgency": "normal"|"urgent"}]}`;
|
|
1760
1768
|
}
|
|
1761
1769
|
function extractJson(text2) {
|
|
@@ -1797,6 +1805,10 @@ function parseOp(raw) {
|
|
|
1797
1805
|
section: { kind, title, body, when: when || title, paths: strList(s.paths), entities: strList(s.entities).map((e) => e.toLowerCase()), skills: strList(s.skills) }
|
|
1798
1806
|
};
|
|
1799
1807
|
}
|
|
1808
|
+
case "situation": {
|
|
1809
|
+
const text2 = clean(o.text);
|
|
1810
|
+
return text2 ? { op: "situation", text: text2.slice(0, 500) } : null;
|
|
1811
|
+
}
|
|
1800
1812
|
case "param": {
|
|
1801
1813
|
const key = typeof o.key === "string" ? o.key.trim() : "";
|
|
1802
1814
|
const v = o.value;
|
|
@@ -2375,6 +2387,17 @@ var init_store2 = __esm({
|
|
|
2375
2387
|
this.writeSession(s);
|
|
2376
2388
|
return s;
|
|
2377
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
|
+
}
|
|
2378
2401
|
readCurrent() {
|
|
2379
2402
|
return readJson(this.p("sessions", "current.json"), null);
|
|
2380
2403
|
}
|
|
@@ -3599,6 +3622,8 @@ function summarize(op) {
|
|
|
3599
3622
|
return clip(op.content === null ? `delete ${op.path}` : `file ${op.path}`);
|
|
3600
3623
|
case "retire":
|
|
3601
3624
|
return clip(`retire: ${op.reason}`);
|
|
3625
|
+
case "situation":
|
|
3626
|
+
return clip(`also applies to: ${op.text}`);
|
|
3602
3627
|
}
|
|
3603
3628
|
}
|
|
3604
3629
|
function describeTarget(s, view) {
|
|
@@ -3776,8 +3801,8 @@ __export(sync_exports, {
|
|
|
3776
3801
|
import fs9 from "node:fs";
|
|
3777
3802
|
import path8 from "node:path";
|
|
3778
3803
|
function episodePayload(e) {
|
|
3779
|
-
const { orgId: _o, uploaded: _u, servedReported: _s, ...rest } = e;
|
|
3780
|
-
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;
|
|
3781
3806
|
return rest;
|
|
3782
3807
|
}
|
|
3783
3808
|
function suggestionPayload(s) {
|
|
@@ -3817,15 +3842,18 @@ async function syncV2(store, opts = {}) {
|
|
|
3817
3842
|
await call("POST", "/episodes", { episode: episodePayload(e) });
|
|
3818
3843
|
const fresh = v2.readEpisode(e.id) ?? e;
|
|
3819
3844
|
const next = { ...fresh, uploaded: JSON.stringify(episodePayload(fresh)) === JSON.stringify(episodePayload(e)) ? true : fresh.uploaded };
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
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 {
|
|
3829
3857
|
}
|
|
3830
3858
|
v2.writeEpisode(next);
|
|
3831
3859
|
report.uploadedEpisodes++;
|
|
@@ -8016,6 +8044,10 @@ init_store2();
|
|
|
8016
8044
|
init_view();
|
|
8017
8045
|
var NUDGE_TEXT = "This looks worth capturing. Consider /learn.";
|
|
8018
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;
|
|
8019
8051
|
var MAX_PUSH = 3;
|
|
8020
8052
|
var additional = (hookEventName, text2) => text2.trim() ? { hookSpecificOutput: { hookEventName, additionalContext: text2.trim() } } : {};
|
|
8021
8053
|
function touch(v2, ctx, patch = () => {
|
|
@@ -8058,6 +8090,11 @@ async function onSessionStart(ctx) {
|
|
|
8058
8090
|
v2.writeCurrent({ sessionId: id, cwd: ctx.cwd, at: now });
|
|
8059
8091
|
const config = ctx.store.config();
|
|
8060
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
|
+
}
|
|
8061
8098
|
const budget = Math.min(3e3, ctx.deadline - Date.now() - 400);
|
|
8062
8099
|
if (budget > 100) {
|
|
8063
8100
|
try {
|
|
@@ -8240,6 +8277,16 @@ function onStop(ctx) {
|
|
|
8240
8277
|
const config = ctx.store.config();
|
|
8241
8278
|
if (ctx.input.stop_hook_active) return {};
|
|
8242
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
|
+
}
|
|
8243
8290
|
const check = learningCheck(v2, session, scan.facts.toolCalls, now);
|
|
8244
8291
|
if (check) return check;
|
|
8245
8292
|
if (!config.reflect.nudge) return {};
|
|
@@ -8271,11 +8318,11 @@ function learningCheck(v2, session, toolCalls, now) {
|
|
|
8271
8318
|
const reason = doc.body.replace(/^\s*<!--\s*bunshin:[\s\S]*?-->\s*/, "").trim().replace(/\{delta\}/g, String(toolCalls - last));
|
|
8272
8319
|
return { decision: "block", reason };
|
|
8273
8320
|
}
|
|
8274
|
-
function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new Date()) {
|
|
8321
|
+
function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new Date(), opts = {}) {
|
|
8275
8322
|
const v2 = new V2Store(store);
|
|
8276
8323
|
const config = store.config();
|
|
8277
8324
|
const view = localView(v2, config, session.cwd, { index: false });
|
|
8278
|
-
const scan = scanTranscript(transcriptPath);
|
|
8325
|
+
const scan = opts.scan ?? scanTranscript(transcriptPath ?? session.transcriptPath);
|
|
8279
8326
|
const outcome = v2.readOutcome(session.sessionId);
|
|
8280
8327
|
const signals = { ...sessionSignals({ scan, startedAt: session.startedAt, lastActivityAt: session.lastActivityAt }), ...outcome ? { outcome: outcome.status } : {} };
|
|
8281
8328
|
const gate = sessionGate(signals, { now: now.toISOString(), cacheTtlMs: config.reflect.cacheTtlMs });
|
|
@@ -8292,7 +8339,7 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
|
|
|
8292
8339
|
harness: ["claude-code", "codex", "pi", "other"].includes(session.harness) ? session.harness : config.harness,
|
|
8293
8340
|
task: session.task,
|
|
8294
8341
|
startedAt: session.startedAt,
|
|
8295
|
-
endedAt: now.toISOString(),
|
|
8342
|
+
endedAt: opts.endedAt ?? now.toISOString(),
|
|
8296
8343
|
sectionsServed: session.served,
|
|
8297
8344
|
sectionsLoaded: session.loaded,
|
|
8298
8345
|
captured: session.captured,
|
|
@@ -8305,10 +8352,53 @@ function buildEpisode(store, session, transcriptPath, now = /* @__PURE__ */ new
|
|
|
8305
8352
|
...snippets.length ? { snippets } : {},
|
|
8306
8353
|
...outcome ? { outcome } : {},
|
|
8307
8354
|
...view.bundle?.release?.version ? { harnessVersion: view.bundle.release.version } : {},
|
|
8308
|
-
uploaded: false
|
|
8355
|
+
uploaded: false,
|
|
8356
|
+
...previous?.reportedServed ? { reportedServed: previous.reportedServed } : {},
|
|
8357
|
+
...previous?.reportedLoaded ? { reportedLoaded: previous.reportedLoaded } : {},
|
|
8358
|
+
...previous?.servedReported ? { servedReported: true } : {}
|
|
8309
8359
|
};
|
|
8310
8360
|
return { episode, gate, scan };
|
|
8311
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
|
+
}
|
|
8312
8402
|
function recordTrialEvidence(v2, session, outcome) {
|
|
8313
8403
|
if (!outcome || outcome.status === "unknown") return 0;
|
|
8314
8404
|
const used = /* @__PURE__ */ new Set([...session.loaded, ...session.pushed]);
|