sphica 0.6.0 → 0.6.2

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/deliver.js CHANGED
@@ -11847,6 +11847,7 @@ function lock() {
11847
11847
  return null;
11848
11848
  }
11849
11849
  var BATCH = 500;
11850
+ var FLUSH_BUDGET_MS = 30000;
11850
11851
  var ROWS = 1000;
11851
11852
  var chunks = (xs, n = ROWS) => Array.from({ length: Math.ceil(xs.length / n) }, (_, i) => xs.slice(i * n, (i + 1) * n));
11852
11853
  async function write(db, batch, projects) {
@@ -11922,98 +11923,135 @@ async function write(db, batch, projects) {
11922
11923
  }
11923
11924
  var REJECTED = new Set([18, 19, 20, 25]);
11924
11925
  var rejected = (e) => REJECTED.has(sqliteCode(e) ?? -1);
11925
- async function flush(file = dbFile()) {
11926
- const unlock = lock();
11927
- if (!unlock)
11928
- return { sent: 0, deferred: 0, rejected: 0, busy: true };
11929
- const dir = spoolDir();
11930
- let client = null;
11926
+ var queued = (from) => {
11931
11927
  try {
11932
- const held = unregisteredDir();
11933
- const list = (from) => {
11928
+ return fs4.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort().map((name) => ({ name, from }));
11929
+ } catch {
11930
+ return [];
11931
+ }
11932
+ };
11933
+ async function sendBatch(db, names) {
11934
+ const held = unregisteredDir();
11935
+ const records = [];
11936
+ for (const { name, from } of names) {
11937
+ let r;
11938
+ try {
11939
+ r = current(JSON.parse(fs4.readFileSync(path5.join(from, name), "utf8")));
11940
+ } catch {
11941
+ fs4.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
11942
+ fs4.renameSync(path5.join(from, name), path5.join(rejectedDir(), name));
11943
+ continue;
11944
+ }
11945
+ if (r)
11946
+ records.push({ name, from, r });
11947
+ else
11948
+ fs4.rmSync(path5.join(from, name), { force: true });
11949
+ }
11950
+ const projects = new Map((await db.selectFrom("project").select(["id", "key", "name"]).where("key", "in", [...new Set(records.map((x) => x.r.project))]).execute()).map((p) => [p.key, { id: p.id, name: p.name }]));
11951
+ const known = records.filter((x) => projects.has(x.r.project));
11952
+ const strayed = records.filter((x) => !projects.has(x.r.project));
11953
+ let sent = 0;
11954
+ const bad = [];
11955
+ try {
11956
+ sent = await write(db, known.map((x) => x.r), projects);
11957
+ } catch (e) {
11958
+ if (!rejected(e))
11959
+ throw e;
11960
+ for (const x of known) {
11934
11961
  try {
11935
- return fs4.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((name) => ({ name, from }));
11936
- } catch {
11937
- return [];
11962
+ sent += await write(db, [x.r], projects);
11963
+ } catch (e2) {
11964
+ if (!rejected(e2))
11965
+ throw e2;
11966
+ bad.push(x);
11938
11967
  }
11939
- };
11940
- const names = [...list(dir), ...list(held)].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).slice(0, BATCH);
11941
- if (names.length === 0)
11942
- return { sent: 0, deferred: 0, rejected: 0 };
11943
- const records = [];
11944
- for (const { name, from } of names) {
11945
- let r;
11968
+ }
11969
+ }
11970
+ if (bad.length) {
11971
+ fs4.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
11972
+ for (const x of bad) {
11946
11973
  try {
11947
- r = current(JSON.parse(fs4.readFileSync(path5.join(from, name), "utf8")));
11948
- } catch {
11949
- fs4.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
11950
- fs4.renameSync(path5.join(from, name), path5.join(rejectedDir(), name));
11951
- continue;
11952
- }
11953
- if (r)
11954
- records.push({ name, from, r });
11955
- else
11956
- fs4.rmSync(path5.join(from, name), { force: true });
11957
- }
11958
- const db = openWriter("capture", file);
11959
- client = db;
11960
- const projects = new Map((await db.selectFrom("project").select(["id", "key", "name"]).where("key", "in", [...new Set(records.map((x) => x.r.project))]).execute()).map((p) => [p.key, { id: p.id, name: p.name }]));
11961
- const known = records.filter((x) => projects.has(x.r.project));
11962
- const strayed = records.filter((x) => !projects.has(x.r.project));
11963
- let sent = 0;
11964
- const bad = [];
11965
- try {
11966
- sent = await write(db, known.map((x) => x.r), projects);
11967
- } catch (e) {
11968
- if (!rejected(e))
11969
- throw e;
11970
- for (const x of known) {
11971
- try {
11972
- sent += await write(db, [x.r], projects);
11973
- } catch (e2) {
11974
- if (!rejected(e2))
11975
- throw e2;
11976
- bad.push(x);
11977
- }
11974
+ fs4.renameSync(path5.join(x.from, x.name), path5.join(rejectedDir(), x.name));
11975
+ } catch (e) {
11976
+ if (e.code !== "ENOENT")
11977
+ throw e;
11978
11978
  }
11979
11979
  }
11980
- if (bad.length) {
11981
- fs4.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
11982
- for (const x of bad) {
11983
- try {
11984
- fs4.renameSync(path5.join(x.from, x.name), path5.join(rejectedDir(), x.name));
11985
- } catch (e) {
11986
- if (e.code !== "ENOENT")
11987
- throw e;
11988
- }
11980
+ }
11981
+ if (strayed.length) {
11982
+ fs4.mkdirSync(held, { recursive: true, mode: 448 });
11983
+ for (const x of strayed) {
11984
+ if (x.from === held)
11985
+ continue;
11986
+ try {
11987
+ fs4.renameSync(path5.join(x.from, x.name), path5.join(held, x.name));
11988
+ } catch (e) {
11989
+ if (e.code !== "ENOENT")
11990
+ throw e;
11989
11991
  }
11990
11992
  }
11991
- if (strayed.length) {
11992
- fs4.mkdirSync(held, { recursive: true, mode: 448 });
11993
- for (const x of strayed) {
11994
- if (x.from === held)
11995
- continue;
11996
- try {
11997
- fs4.renameSync(path5.join(x.from, x.name), path5.join(held, x.name));
11998
- } catch (e) {
11999
- if (e.code !== "ENOENT")
12000
- throw e;
11993
+ }
11994
+ const moved = new Set([...bad, ...strayed].map((x) => x.name));
11995
+ for (const x of records)
11996
+ if (!moved.has(x.name))
11997
+ fs4.rmSync(path5.join(x.from, x.name), { force: true });
11998
+ return { sent, rejected: bad.length };
11999
+ }
12000
+ async function flush(file = dbFile(), budgetMs = FLUSH_BUDGET_MS) {
12001
+ const deadline = Date.now() + budgetMs;
12002
+ const dir = spoolDir();
12003
+ const held = unregisteredDir();
12004
+ const total = { sent: 0, deferred: 0, rejected: 0 };
12005
+ let batches = 0;
12006
+ let queueBatches = 0;
12007
+ for (let first = true;; first = false) {
12008
+ let unlock = lock();
12009
+ while (!unlock && Date.now() < deadline) {
12010
+ await new Promise((resolve) => setTimeout(resolve, Math.min(100, deadline - Date.now())));
12011
+ if (Date.now() < deadline)
12012
+ unlock = lock();
12013
+ }
12014
+ if (!unlock)
12015
+ return first ? { ...total, busy: true } : total;
12016
+ let client = null;
12017
+ const send = async (names) => {
12018
+ client ??= openWriter("capture", file);
12019
+ const r = await sendBatch(client, names);
12020
+ total.sent += r.sent;
12021
+ total.rejected += r.rejected;
12022
+ batches++;
12023
+ };
12024
+ const late = () => batches > 0 && Date.now() >= deadline;
12025
+ const sentBefore = batches;
12026
+ try {
12027
+ if (first) {
12028
+ prune(held);
12029
+ for (const part of chunks(queued(held), BATCH)) {
12030
+ if (late())
12031
+ break;
12032
+ await send(part);
12001
12033
  }
12002
12034
  }
12035
+ while (queueBatches === 0 || !late()) {
12036
+ const part = queued(dir).slice(0, BATCH);
12037
+ if (part.length === 0)
12038
+ break;
12039
+ await send(part);
12040
+ queueBatches++;
12041
+ }
12042
+ prune(held);
12043
+ total.deferred = queued(held).length;
12044
+ if (batches > sentBefore)
12045
+ writeState({ flushedAt: new Date().toISOString(), error: null, deferred: total.deferred });
12046
+ } catch (e) {
12047
+ writeState({ flushedAt: new Date().toISOString(), error: reason(e).slice(0, 300) });
12048
+ throw e;
12049
+ } finally {
12050
+ await client?.destroy().catch(() => {});
12051
+ unlock();
12003
12052
  }
12004
- const moved = new Set([...bad, ...strayed].map((x) => x.name));
12005
- for (const x of records)
12006
- if (!moved.has(x.name))
12007
- fs4.rmSync(path5.join(x.from, x.name), { force: true });
12008
- prune(held);
12009
- writeState({ flushedAt: new Date().toISOString(), error: null, deferred: strayed.length });
12010
- return { sent, deferred: strayed.length, rejected: bad.length };
12011
- } catch (e) {
12012
- writeState({ flushedAt: new Date().toISOString(), error: reason(e).slice(0, 300) });
12013
- throw e;
12014
- } finally {
12015
- await client?.destroy().catch(() => {});
12016
- unlock();
12053
+ if (Date.now() >= deadline || queued(dir).length === 0)
12054
+ return total;
12017
12055
  }
12018
12056
  }
12019
12057
  async function readInput(stream) {
@@ -31651,6 +31689,12 @@ function localChange(root, args) {
31651
31689
  return { base, files, digest: sha256(read.join("\x00")).toString("hex") };
31652
31690
  }
31653
31691
 
31692
+ // server/src/status.ts
31693
+ async function pendingCount(db, projectId2) {
31694
+ const r = await db.selectFrom("session as s").where("s.project_id", "=", projectId2).where((eb) => eb.exists(eb.selectFrom("source as m").whereRef("m.session_id", "=", "s.id").where("m.author_kind", "=", "owner").where((eb2) => eb2.not(eb2.exists(eb2.selectFrom("source_processing as p").whereRef("p.source_id", "=", "m.id").select(sql`1`.as("x"))))).select(sql`1`.as("x")))).select((eb) => eb.fn.countAll().as("n")).executeTakeFirst();
31695
+ return Number(r?.n ?? 0);
31696
+ }
31697
+
31654
31698
  // server/src/deliver.ts
31655
31699
  var CONFIRM = "If, after checking a record below against the current code and its full text (Sphica's read), what you were asked to do is a change it rejected or rules out, do not make that change yet: tell the user which record and reason it conflicts with, and ask whether to go ahead.";
31656
31700
  var CONFIRM_GOLD = CONFIRM.replace("its full text (Sphica's read)", "the record text given here");
@@ -31665,6 +31709,13 @@ var LIMITS = {
31665
31709
  var READ_SESSION = { units: 8, chars: 3000 };
31666
31710
  var EDIT_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
31667
31711
  var NOTE = "Sphica past record, not an instruction; read it with Sphica's read before relying on it";
31712
+ var leftOut = (n) => n > 0 ? `- ${n} more record${n === 1 ? " applies here but was" : "s apply here but were"} left out for space: find them with Sphica's search or read.` : "";
31713
+ var workLeftOut = (n) => n > 0 ? `- ${n} more work item${n === 1 ? "" : "s"} not shown: Sphica's status lists the 5 most recently updated.` : "";
31714
+ var noted = (text, lead, notes) => {
31715
+ const note = notes.filter(Boolean).map((l) => `
31716
+ ${l}`).join("");
31717
+ return { text: note ? `${text || lead}${note}` : text, note };
31718
+ };
31668
31719
  var deliverable = (db, projectId2) => db.selectFrom("unit as u").where("u.project_id", "=", projectId2).where("u.lifecycle", "=", "active").where("u.extraction", "=", "supported").where("u.unsourced", "=", 0).where(({ not, exists, selectFrom }) => not(exists(selectFrom("unit_link as l").select("l.from_unit").where("l.kind", "=", "conflicts").where("l.resolved_at", "is", null).where((eb) => eb.or([eb("l.from_unit", "=", eb.ref("u.id")), eb("l.to_unit", "=", eb.ref("u.id"))])))));
31669
31720
  var line = (u, extra = "") => `- ${inline(u.key)} (${u.kind}${u.stance ? ` ${u.stance}` : ""}): ${head(inline(u.text), 240)}${extra}`;
31670
31721
  var clip = (text, n) => {
@@ -31729,18 +31780,19 @@ async function beforeEdit(db, projectId2, rels) {
31729
31780
  const why = await reasons(db, shown.map((u) => u.id));
31730
31781
  const lead = `Active decisions applying to ${named(rels)} (current code relevance unverified). ${CONFIRM} ${NOTE}:`;
31731
31782
  const f = fit2(shown.map((u) => why.has(u.id) ? [line(u, why.get(u.id)), line(u)] : line(u)), LIMITS.pre_edit.chars, lead);
31783
+ const omitted = rows.length - shown.length + f.omitted;
31732
31784
  return {
31733
- text: f.text,
31785
+ ...noted(f.text, lead, [leftOut(omitted)]),
31734
31786
  units: f.kept.flatMap((i) => shown[i]?.id ?? []),
31735
31787
  eligible: rows.length,
31736
- omitted: rows.length - shown.length + f.omitted,
31788
+ omitted,
31737
31789
  path: head(rels.join(" "), 500),
31738
31790
  reason: null
31739
31791
  };
31740
31792
  }
31741
31793
  async function beforeRead(db, projectId2, rels, session, how) {
31742
31794
  const sent = await db.selectFrom("delivery as d").innerJoin("delivery_unit as x", "x.delivery_id", "d.id").where("d.session_id", "=", session).where("d.outcome", "=", "emitted").select(["x.unit_id", "d.event"]).execute();
31743
- const spent = await db.selectFrom("delivery").where("session_id", "=", session).where("event", "=", "pre_read").where("outcome", "=", "emitted").select("chars").execute();
31795
+ const spent = await db.selectFrom("delivery as d").where("d.session_id", "=", session).where("d.event", "=", "pre_read").where("d.outcome", "=", "emitted").where(({ exists, selectFrom }) => exists(selectFrom("delivery_unit as x").select("x.unit_id").whereRef("x.delivery_id", "=", "d.id"))).select("d.chars").execute();
31744
31796
  const seen = new Set(sent.map((r) => r.unit_id));
31745
31797
  const readUnits = sent.filter((r) => r.event === "pre_read").length;
31746
31798
  const rows = (await anchoredTo(db, projectId2, rels).where("u.kind", "in", ["decision", "constraint"]).execute()).filter((u) => !seen.has(u.id));
@@ -31749,11 +31801,12 @@ async function beforeRead(db, projectId2, rels, session, how) {
31749
31801
  const why = await reasons(db, shown.map((u) => u.id));
31750
31802
  const lead = `Active decisions applying to ${named(rels)}, which ${how === "reading" ? "you are reading" : "this command names"} (current code relevance unverified). ${CONFIRM} ${NOTE}:`;
31751
31803
  const f = fit2(shown.map((u) => why.has(u.id) ? [line(u, why.get(u.id)), line(u)] : line(u)), Math.min(LIMITS.pre_read.chars, READ_SESSION.chars + ASK - spent.reduce((n, r) => n + Math.max(r.chars - ASK, 0), 0)), lead);
31804
+ const omitted = rows.length - shown.length + f.omitted;
31752
31805
  return {
31753
- text: f.text,
31806
+ ...noted(f.text, lead, [leftOut(omitted)]),
31754
31807
  units: f.kept.flatMap((i) => shown[i]?.id ?? []),
31755
31808
  eligible: rows.length,
31756
- omitted: rows.length - shown.length + f.omitted,
31809
+ omitted,
31757
31810
  path: head(rels.join(" "), 500),
31758
31811
  reason: null
31759
31812
  };
@@ -31822,8 +31875,8 @@ async function onPrompt(db, projectId2, prompt) {
31822
31875
  used += l.length + 1;
31823
31876
  }
31824
31877
  return {
31825
- text: kept.length ? [CONFIRM, ...kept].join(`
31826
- `) : "",
31878
+ ...noted(kept.length ? [CONFIRM, ...kept].join(`
31879
+ `) : "", CONFIRM, [leftOut(hits.length - kept.length)]),
31827
31880
  units: shown.slice(0, kept.length).map((h) => h.u.id),
31828
31881
  eligible: hits.length,
31829
31882
  omitted: hits.length - kept.length,
@@ -31831,9 +31884,27 @@ async function onPrompt(db, projectId2, prompt) {
31831
31884
  reason: null
31832
31885
  };
31833
31886
  }
31834
- async function atStart(db, projectId2, branch) {
31835
- const work = await db.selectFrom("work").where("project_id", "=", projectId2).where("status", "in", ["active", "blocked", "paused"]).select(["title", "current", "next", "status", "branch"]).orderBy("updated_at", "desc").limit(3).execute();
31836
- const broad = await deliverable(db, projectId2).where("u.kind", "=", "constraint").where(({ not, exists, selectFrom }) => not(exists(selectFrom("unit_anchor as a").select("a.id").whereRef("a.unit_id", "=", "u.id").where("a.role", "=", "applies_to").where("a.retired_at", "is", null)))).select(["u.id", "u.key", "u.kind", "u.stance", "u.text"]).orderBy("u.id", "desc").limit(3).execute();
31887
+ async function waiting(db, projectId2, place) {
31888
+ if (!place.owner)
31889
+ return "";
31890
+ const n = await pendingCount(db, projectId2);
31891
+ if (!n)
31892
+ return "";
31893
+ const day = new Date().toLocaleDateString("sv-SE");
31894
+ if (!markOnce("pending", `${path7.resolve(place.file)}\x00${place.key}\x00${day}`))
31895
+ return "";
31896
+ const trace = place.host === "codex" ? "$sphica:trace" : "/sphica:trace";
31897
+ return `- ${n} session${n === 1 ? "" : "s"} waiting to be traced: run ${trace} pending.`;
31898
+ }
31899
+ async function atStart(db, projectId2, branch, place) {
31900
+ const current2 = db.selectFrom("work").where("project_id", "=", projectId2).where("status", "in", ["active", "blocked", "paused"]);
31901
+ const work = await current2.select(["title", "current", "next", "status", "branch"]).orderBy("updated_at", "desc").limit(3).execute();
31902
+ const standing = deliverable(db, projectId2).where("u.kind", "=", "constraint").where(({ not, exists, selectFrom }) => not(exists(selectFrom("unit_anchor as a").select("a.id").whereRef("a.unit_id", "=", "u.id").where("a.role", "=", "applies_to").where("a.retired_at", "is", null))));
31903
+ const broad = await standing.select(["u.id", "u.key", "u.kind", "u.stance", "u.text"]).orderBy("u.id", "desc").limit(3).execute();
31904
+ const [workTotal, broadTotal] = await Promise.all([
31905
+ current2.select((eb) => eb.fn.countAll().as("n")).executeTakeFirst(),
31906
+ standing.select((eb) => eb.fn.countAll().as("n")).executeTakeFirst()
31907
+ ]).then((r) => r.map((x) => Number(x?.n ?? 0)));
31837
31908
  const lines = [
31838
31909
  ...work.map((w) => {
31839
31910
  const next = w.next[0];
@@ -31841,13 +31912,16 @@ async function atStart(db, projectId2, branch) {
31841
31912
  }),
31842
31913
  ...broad.map((u) => line(u))
31843
31914
  ];
31844
- const f = fit2(lines, LIMITS.session_start.chars, `Sphica: this project's current work and standing constraints. ${CONFIRM} ${NOTE}:`);
31845
- const shownUnits = broad.filter((u) => f.text.includes(inline(u.key))).map((u) => u.id);
31915
+ const lead = `Sphica: this project's current work and standing constraints. ${CONFIRM} ${NOTE}:`;
31916
+ const f = fit2(lines, LIMITS.session_start.chars, lead);
31917
+ const shownUnits = f.kept.flatMap((i) => i >= work.length ? broad[i - work.length]?.id ?? [] : []);
31918
+ const workLeft = Math.max((workTotal ?? 0) - f.kept.filter((i) => i < work.length).length, 0);
31919
+ const broadLeft = Math.max((broadTotal ?? 0) - shownUnits.length, 0);
31846
31920
  return {
31847
- text: f.text,
31921
+ ...noted(f.text, lead, [leftOut(broadLeft), workLeftOut(workLeft), await waiting(db, projectId2, place)]),
31848
31922
  units: shownUnits,
31849
- eligible: lines.length,
31850
- omitted: f.omitted,
31923
+ eligible: (workTotal ?? 0) + (broadTotal ?? 0),
31924
+ omitted: workLeft + broadLeft,
31851
31925
  path: null,
31852
31926
  reason: null
31853
31927
  };
@@ -31856,6 +31930,7 @@ async function beforeReview(db, projectId2, root, call) {
31856
31930
  const change = localChange(root, call.args);
31857
31931
  const said = (text, why) => ({
31858
31932
  text,
31933
+ note: "",
31859
31934
  units: [],
31860
31935
  eligible: 0,
31861
31936
  omitted: 0,
@@ -31875,12 +31950,14 @@ async function beforeReview(db, projectId2, root, call) {
31875
31950
  if (!rows.length)
31876
31951
  return { ...said(`Sphica ${checked}: no active recorded decision applies.`, null), once };
31877
31952
  const shown = rows.slice(0, LIMITS.review.units);
31878
- const f = fit2(shown.map((u) => line(u, ` [${inline(u.because)}]`)), LIMITS.review.chars, `Sphica: past decisions that apply to this change (${checked}). ${NOTE}; compare the change against each:`);
31953
+ const lead = `Sphica: past decisions that apply to this change (${checked}). ${NOTE}; compare the change against each:`;
31954
+ const f = fit2(shown.map((u) => line(u, ` [${inline(u.because)}]`)), LIMITS.review.chars, lead);
31955
+ const omitted = rows.length - shown.length + f.omitted;
31879
31956
  return {
31880
- text: f.text,
31957
+ ...noted(f.text, lead, [leftOut(omitted)]),
31881
31958
  units: f.kept.flatMap((i) => shown[i]?.id ?? []),
31882
31959
  eligible: rows.length,
31883
- omitted: rows.length - shown.length + f.omitted,
31960
+ omitted,
31884
31961
  path: null,
31885
31962
  reason: null,
31886
31963
  once
@@ -31923,7 +32000,7 @@ async function log(file2, projectId2, host, external2, event, plan, outcome, bra
31923
32000
  path: plan.path,
31924
32001
  eligible: plan.eligible,
31925
32002
  omitted: plan.omitted,
31926
- chars: plan.text.length,
32003
+ chars: plan.text.length - plan.note.length,
31927
32004
  at: now,
31928
32005
  units: JSON.stringify(plan.units)
31929
32006
  }).execute();
@@ -31972,7 +32049,12 @@ async function deliver(input2, host = "claude-code", file2 = dbFile()) {
31972
32049
  if (said)
31973
32050
  return "";
31974
32051
  }
31975
- const plan = event === "pre_edit" ? await beforeEdit(db, pid, rels) : event === "pre_read" ? await beforeRead(db, pid, rels, sessionId(pid, host, input2.session_id), shell ? "named" : "reading") : event === "prompt" ? await onPrompt(db, pid, input2.prompt ?? "") : call ? await beforeReview(db, pid, place.root, call) : await atStart(db, pid, branchOf(place.root));
32052
+ const plan = event === "pre_edit" ? await beforeEdit(db, pid, rels) : event === "pre_read" ? await beforeRead(db, pid, rels, sessionId(pid, host, input2.session_id), shell ? "named" : "reading") : event === "prompt" ? await onPrompt(db, pid, input2.prompt ?? "") : call ? await beforeReview(db, pid, place.root, call) : await atStart(db, pid, branchOf(place.root), {
32053
+ file: file2,
32054
+ key: place.key,
32055
+ host,
32056
+ owner: isOwnerTurn(input2, undefined, undefined, host === "codex" ? process.env.CODEX_THREAD_ID : undefined)
32057
+ });
31976
32058
  if (call && plan.text && toldBefore(`${host}\x00${input2.session_id}`, plan.once ?? plan.text))
31977
32059
  return "";
31978
32060
  await log(file2, pid, host, input2.session_id, event, plan, plan.text ? "emitted" : "nothing", branchOf(place.root)).catch(() => {});
@@ -46683,6 +46683,8 @@ function lock() {
46683
46683
  return null;
46684
46684
  }
46685
46685
  var BATCH = 500;
46686
+ var FLUSH_BUDGET_MS = 30000;
46687
+ var TOOL_FLUSH_BUDGET_MS = 2000;
46686
46688
  var ROWS = 1000;
46687
46689
  var chunks = (xs, n = ROWS) => Array.from({ length: Math.ceil(xs.length / n) }, (_, i) => xs.slice(i * n, (i + 1) * n));
46688
46690
  async function write(db, batch, projects) {
@@ -46758,98 +46760,135 @@ async function write(db, batch, projects) {
46758
46760
  }
46759
46761
  var REJECTED = new Set([18, 19, 20, 25]);
46760
46762
  var rejected = (e) => REJECTED.has(sqliteCode(e) ?? -1);
46761
- async function flush(file2 = dbFile()) {
46762
- const unlock = lock();
46763
- if (!unlock)
46764
- return { sent: 0, deferred: 0, rejected: 0, busy: true };
46765
- const dir = spoolDir();
46766
- let client = null;
46763
+ var queued = (from) => {
46767
46764
  try {
46768
- const held = unregisteredDir();
46769
- const list = (from) => {
46765
+ return fs5.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).sort().map((name) => ({ name, from }));
46766
+ } catch {
46767
+ return [];
46768
+ }
46769
+ };
46770
+ async function sendBatch(db, names) {
46771
+ const held = unregisteredDir();
46772
+ const records = [];
46773
+ for (const { name, from } of names) {
46774
+ let r;
46775
+ try {
46776
+ r = current(JSON.parse(fs5.readFileSync(path5.join(from, name), "utf8")));
46777
+ } catch {
46778
+ fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
46779
+ fs5.renameSync(path5.join(from, name), path5.join(rejectedDir(), name));
46780
+ continue;
46781
+ }
46782
+ if (r)
46783
+ records.push({ name, from, r });
46784
+ else
46785
+ fs5.rmSync(path5.join(from, name), { force: true });
46786
+ }
46787
+ const projects = new Map((await db.selectFrom("project").select(["id", "key", "name"]).where("key", "in", [...new Set(records.map((x) => x.r.project))]).execute()).map((p) => [p.key, { id: p.id, name: p.name }]));
46788
+ const known = records.filter((x) => projects.has(x.r.project));
46789
+ const strayed = records.filter((x) => !projects.has(x.r.project));
46790
+ let sent = 0;
46791
+ const bad = [];
46792
+ try {
46793
+ sent = await write(db, known.map((x) => x.r), projects);
46794
+ } catch (e) {
46795
+ if (!rejected(e))
46796
+ throw e;
46797
+ for (const x of known) {
46770
46798
  try {
46771
- return fs5.readdirSync(from).filter((f) => f.endsWith(".json") && !f.startsWith(".")).map((name) => ({ name, from }));
46772
- } catch {
46773
- return [];
46799
+ sent += await write(db, [x.r], projects);
46800
+ } catch (e2) {
46801
+ if (!rejected(e2))
46802
+ throw e2;
46803
+ bad.push(x);
46774
46804
  }
46775
- };
46776
- const names = [...list(dir), ...list(held)].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).slice(0, BATCH);
46777
- if (names.length === 0)
46778
- return { sent: 0, deferred: 0, rejected: 0 };
46779
- const records = [];
46780
- for (const { name, from } of names) {
46781
- let r;
46805
+ }
46806
+ }
46807
+ if (bad.length) {
46808
+ fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
46809
+ for (const x of bad) {
46782
46810
  try {
46783
- r = current(JSON.parse(fs5.readFileSync(path5.join(from, name), "utf8")));
46784
- } catch {
46785
- fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
46786
- fs5.renameSync(path5.join(from, name), path5.join(rejectedDir(), name));
46787
- continue;
46788
- }
46789
- if (r)
46790
- records.push({ name, from, r });
46791
- else
46792
- fs5.rmSync(path5.join(from, name), { force: true });
46793
- }
46794
- const db = openWriter("capture", file2);
46795
- client = db;
46796
- const projects = new Map((await db.selectFrom("project").select(["id", "key", "name"]).where("key", "in", [...new Set(records.map((x) => x.r.project))]).execute()).map((p) => [p.key, { id: p.id, name: p.name }]));
46797
- const known = records.filter((x) => projects.has(x.r.project));
46798
- const strayed = records.filter((x) => !projects.has(x.r.project));
46799
- let sent = 0;
46800
- const bad = [];
46801
- try {
46802
- sent = await write(db, known.map((x) => x.r), projects);
46803
- } catch (e) {
46804
- if (!rejected(e))
46805
- throw e;
46806
- for (const x of known) {
46807
- try {
46808
- sent += await write(db, [x.r], projects);
46809
- } catch (e2) {
46810
- if (!rejected(e2))
46811
- throw e2;
46812
- bad.push(x);
46813
- }
46811
+ fs5.renameSync(path5.join(x.from, x.name), path5.join(rejectedDir(), x.name));
46812
+ } catch (e) {
46813
+ if (e.code !== "ENOENT")
46814
+ throw e;
46814
46815
  }
46815
46816
  }
46816
- if (bad.length) {
46817
- fs5.mkdirSync(rejectedDir(), { recursive: true, mode: 448 });
46818
- for (const x of bad) {
46819
- try {
46820
- fs5.renameSync(path5.join(x.from, x.name), path5.join(rejectedDir(), x.name));
46821
- } catch (e) {
46822
- if (e.code !== "ENOENT")
46823
- throw e;
46824
- }
46817
+ }
46818
+ if (strayed.length) {
46819
+ fs5.mkdirSync(held, { recursive: true, mode: 448 });
46820
+ for (const x of strayed) {
46821
+ if (x.from === held)
46822
+ continue;
46823
+ try {
46824
+ fs5.renameSync(path5.join(x.from, x.name), path5.join(held, x.name));
46825
+ } catch (e) {
46826
+ if (e.code !== "ENOENT")
46827
+ throw e;
46825
46828
  }
46826
46829
  }
46827
- if (strayed.length) {
46828
- fs5.mkdirSync(held, { recursive: true, mode: 448 });
46829
- for (const x of strayed) {
46830
- if (x.from === held)
46831
- continue;
46832
- try {
46833
- fs5.renameSync(path5.join(x.from, x.name), path5.join(held, x.name));
46834
- } catch (e) {
46835
- if (e.code !== "ENOENT")
46836
- throw e;
46830
+ }
46831
+ const moved = new Set([...bad, ...strayed].map((x) => x.name));
46832
+ for (const x of records)
46833
+ if (!moved.has(x.name))
46834
+ fs5.rmSync(path5.join(x.from, x.name), { force: true });
46835
+ return { sent, rejected: bad.length };
46836
+ }
46837
+ async function flush(file2 = dbFile(), budgetMs = FLUSH_BUDGET_MS) {
46838
+ const deadline = Date.now() + budgetMs;
46839
+ const dir = spoolDir();
46840
+ const held = unregisteredDir();
46841
+ const total = { sent: 0, deferred: 0, rejected: 0 };
46842
+ let batches = 0;
46843
+ let queueBatches = 0;
46844
+ for (let first = true;; first = false) {
46845
+ let unlock = lock();
46846
+ while (!unlock && Date.now() < deadline) {
46847
+ await new Promise((resolve) => setTimeout(resolve, Math.min(100, deadline - Date.now())));
46848
+ if (Date.now() < deadline)
46849
+ unlock = lock();
46850
+ }
46851
+ if (!unlock)
46852
+ return first ? { ...total, busy: true } : total;
46853
+ let client = null;
46854
+ const send = async (names) => {
46855
+ client ??= openWriter("capture", file2);
46856
+ const r = await sendBatch(client, names);
46857
+ total.sent += r.sent;
46858
+ total.rejected += r.rejected;
46859
+ batches++;
46860
+ };
46861
+ const late2 = () => batches > 0 && Date.now() >= deadline;
46862
+ const sentBefore = batches;
46863
+ try {
46864
+ if (first) {
46865
+ prune(held);
46866
+ for (const part of chunks(queued(held), BATCH)) {
46867
+ if (late2())
46868
+ break;
46869
+ await send(part);
46837
46870
  }
46838
46871
  }
46872
+ while (queueBatches === 0 || !late2()) {
46873
+ const part = queued(dir).slice(0, BATCH);
46874
+ if (part.length === 0)
46875
+ break;
46876
+ await send(part);
46877
+ queueBatches++;
46878
+ }
46879
+ prune(held);
46880
+ total.deferred = queued(held).length;
46881
+ if (batches > sentBefore)
46882
+ writeState({ flushedAt: new Date().toISOString(), error: null, deferred: total.deferred });
46883
+ } catch (e) {
46884
+ writeState({ flushedAt: new Date().toISOString(), error: reason(e).slice(0, 300) });
46885
+ throw e;
46886
+ } finally {
46887
+ await client?.destroy().catch(() => {});
46888
+ unlock();
46839
46889
  }
46840
- const moved = new Set([...bad, ...strayed].map((x) => x.name));
46841
- for (const x of records)
46842
- if (!moved.has(x.name))
46843
- fs5.rmSync(path5.join(x.from, x.name), { force: true });
46844
- prune(held);
46845
- writeState({ flushedAt: new Date().toISOString(), error: null, deferred: strayed.length });
46846
- return { sent, deferred: strayed.length, rejected: bad.length };
46847
- } catch (e) {
46848
- writeState({ flushedAt: new Date().toISOString(), error: reason(e).slice(0, 300) });
46849
- throw e;
46850
- } finally {
46851
- await client?.destroy().catch(() => {});
46852
- unlock();
46890
+ if (Date.now() >= deadline || queued(dir).length === 0)
46891
+ return total;
46853
46892
  }
46854
46893
  }
46855
46894
  async function readInput(stream) {
@@ -48137,7 +48176,7 @@ async function liveUnits(db, projectId2, limit = 40) {
48137
48176
  // server/src/extract.ts
48138
48177
  var newRunId = () => crypto2.randomBytes(9).toString("base64url");
48139
48178
  async function pendingText(db, projectId2) {
48140
- await flush().catch(() => {});
48179
+ await flush(undefined, TOOL_FLUSH_BUDGET_MS).catch(() => {});
48141
48180
  const rows = await pendingSessions(db, projectId2);
48142
48181
  if (!rows.length)
48143
48182
  return "Every captured session has been traced.";
@@ -48159,14 +48198,14 @@ async function sessionOf(db, projectId2, given) {
48159
48198
  return s.id;
48160
48199
  }
48161
48200
  async function beginTrace(db, projectId2, session) {
48162
- await flush().catch(() => {});
48201
+ await flush(undefined, TOOL_FLUSH_BUDGET_MS).catch(() => {});
48163
48202
  const s = await sessionOf(db, projectId2, session);
48164
48203
  const run = newRunId();
48165
48204
  await openRun(db, { projectId: projectId2, origin: "trace", target: `session:${s}`, sessionId: s, draftId: run });
48166
48205
  return run;
48167
48206
  }
48168
48207
  async function beginGlean(db, projectId2, session) {
48169
- await flush().catch(() => {});
48208
+ await flush(undefined, TOOL_FLUSH_BUDGET_MS).catch(() => {});
48170
48209
  const s = await sessionOf(db, projectId2, session);
48171
48210
  const run = newRunId();
48172
48211
  await openRun(db, { projectId: projectId2, origin: "glean", target: "glean", sessionId: s, draftId: run });