@stitchdb/cli 0.7.2 → 0.7.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/cli.js +32 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -502,6 +502,10 @@ async function cmdHook(args) {
|
|
|
502
502
|
// Stitch hook entries — never touching non-Stitch ones.
|
|
503
503
|
if (eventName === 'SessionStart') {
|
|
504
504
|
ensureHooksUpToDate();
|
|
505
|
+
// Catch-up distillation: if a previous session ended with undistilled
|
|
506
|
+
// turns (user exited under threshold), distill them now in the
|
|
507
|
+
// background so they surface in this session's context very soon.
|
|
508
|
+
maybeAutoDistill(threadName, { force: true }).catch(() => { });
|
|
505
509
|
}
|
|
506
510
|
// Strategy: prefer distilled memories (dense facts) over raw turns. Only
|
|
507
511
|
// include raw turns for the last 5 to give the agent immediate continuation.
|
|
@@ -964,9 +968,13 @@ async function cmdLink(args) {
|
|
|
964
968
|
// Triggered manually (`stitch distill`), and automatically by the Stop hook
|
|
965
969
|
// when conditions are met (cooldown + new-turn threshold).
|
|
966
970
|
const DISTILL_STATE_FILE = path.join(CONFIG_DIR, 'distill-state.json');
|
|
967
|
-
|
|
968
|
-
|
|
971
|
+
// No time-based cooldown — purely turn-count driven. The previous time gate
|
|
972
|
+
// could leave turns undistilled if the user exited Claude before the next
|
|
973
|
+
// firing window. Now: every Stop checks the pending-turn count, and if the
|
|
974
|
+
// user exits below threshold, the next SessionStart catches up.
|
|
975
|
+
const DISTILL_MIN_NEW_TURNS = 5; // distill once 5+ new turns are pending
|
|
969
976
|
const DISTILL_BATCH_SIZE = 30; // turns per distillation pass
|
|
977
|
+
const DISTILL_DEBOUNCE_MS = 90 * 1000; // small in-flight guard so a flurry of Stops doesn't fire N spawns
|
|
970
978
|
function loadDistillState() {
|
|
971
979
|
try {
|
|
972
980
|
return JSON.parse(fs.readFileSync(DISTILL_STATE_FILE, 'utf8'));
|
|
@@ -1203,14 +1211,24 @@ async function distillClear(args) {
|
|
|
1203
1211
|
}
|
|
1204
1212
|
console.log(`Cleared ${deleted} memories.`);
|
|
1205
1213
|
}
|
|
1206
|
-
// Triggered from
|
|
1207
|
-
|
|
1214
|
+
// Triggered from Stop hook AND SessionStart catch-up. Fire-and-forget.
|
|
1215
|
+
//
|
|
1216
|
+
// Decision logic:
|
|
1217
|
+
// - If `pending = currentTurns - lastDistilledTurns` >= DISTILL_MIN_NEW_TURNS,
|
|
1218
|
+
// spawn a distill pass. No time-based cooldown — leaving turns undistilled
|
|
1219
|
+
// because the user happened to exit before the next firing window is the
|
|
1220
|
+
// wrong behaviour.
|
|
1221
|
+
// - SessionStart calls this with `force = true` when ANY pending turns exist
|
|
1222
|
+
// so a session that ended mid-batch (e.g. 3 pending) still gets caught up.
|
|
1223
|
+
// - DISTILL_DEBOUNCE_MS is a tiny in-process guard so a flurry of Stop
|
|
1224
|
+
// events from a single agent run doesn't trigger N parallel claude -p
|
|
1225
|
+
// spawns; once one is in flight, others within the window become no-ops.
|
|
1226
|
+
async function maybeAutoDistill(thread, opts = {}) {
|
|
1208
1227
|
const state = loadDistillState();
|
|
1209
1228
|
const meta = state.threads[thread] || { lastDistilledAt: 0, lastTurnAt: 0, lastTurnCount: 0 };
|
|
1210
|
-
//
|
|
1211
|
-
if (Date.now() - meta.lastDistilledAt <
|
|
1229
|
+
// In-flight debounce: if we just spawned one, skip.
|
|
1230
|
+
if (Date.now() - meta.lastDistilledAt < DISTILL_DEBOUNCE_MS)
|
|
1212
1231
|
return;
|
|
1213
|
-
// Need at least N new turns since last pass.
|
|
1214
1232
|
const cfg = loadConfig();
|
|
1215
1233
|
const stitch = client(cfg);
|
|
1216
1234
|
let recallSize = 0;
|
|
@@ -1221,17 +1239,21 @@ async function maybeAutoDistill(thread) {
|
|
|
1221
1239
|
catch {
|
|
1222
1240
|
return;
|
|
1223
1241
|
}
|
|
1224
|
-
|
|
1242
|
+
const pending = recallSize - meta.lastTurnCount;
|
|
1243
|
+
// Default path: only distill once enough turns have piled up.
|
|
1244
|
+
// SessionStart catch-up: distill if there's ANY pending turn.
|
|
1245
|
+
const threshold = opts.force ? 1 : DISTILL_MIN_NEW_TURNS;
|
|
1246
|
+
if (pending < threshold)
|
|
1225
1247
|
return;
|
|
1226
1248
|
// Mark BEFORE running so we don't double-fire on overlapping Stop events.
|
|
1227
1249
|
state.threads[thread] = { lastDistilledAt: Date.now(), lastTurnAt: Date.now(), lastTurnCount: recallSize };
|
|
1228
1250
|
saveDistillState(state);
|
|
1229
|
-
// Detach: spawn a background process so the
|
|
1230
|
-
// The detached child runs `stitch distill` for this thread.
|
|
1251
|
+
// Detach: spawn a background process so the hook returns immediately.
|
|
1231
1252
|
try {
|
|
1232
1253
|
const child = spawn(process.argv[0], [process.argv[1] || (await import('node:url')).fileURLToPath(import.meta.url), 'distill', '--thread', thread, '--n', String(DISTILL_BATCH_SIZE)], {
|
|
1233
1254
|
detached: true,
|
|
1234
1255
|
stdio: 'ignore',
|
|
1256
|
+
env: { ...process.env, STITCH_HOOKS_DISABLED: '1' },
|
|
1235
1257
|
});
|
|
1236
1258
|
child.unref();
|
|
1237
1259
|
}
|