scream-code 0.15.0 → 0.15.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.
@@ -34205,14 +34205,27 @@ function convertMediaUrl(url, fallbackMimeType) {
34205
34205
  function createAbortError() {
34206
34206
  return new DOMException("The operation was aborted.", "AbortError");
34207
34207
  }
34208
- async function abortPromise(signal) {
34209
- if (signal === void 0) return new Promise(() => {});
34208
+ /**
34209
+ * Race async work against an abort signal, ALWAYS detaching the listener.
34210
+ * The Google GenAI SDK does not accept an AbortSignal, so callers race the
34211
+ * SDK call against the signal manually. A bare `addEventListener({ once })`
34212
+ * inside a losing promise leaks the listener on the caller's signal for the
34213
+ * rest of its lifetime - with a session-scoped signal that accumulates one
34214
+ * listener per request. This wrapper guarantees cleanup in both outcomes.
34215
+ */
34216
+ async function abortRace(signal, work) {
34217
+ if (signal === void 0) return work;
34210
34218
  if (signal.aborted) throw createAbortError();
34211
- return new Promise((_, reject) => {
34212
- signal.addEventListener("abort", () => {
34213
- reject(createAbortError());
34214
- }, { once: true });
34219
+ let onAbort = void 0;
34220
+ const abortPromise = new Promise((_, reject) => {
34221
+ onAbort = () => reject(createAbortError());
34222
+ signal.addEventListener("abort", onAbort, { once: true });
34215
34223
  });
34224
+ try {
34225
+ return await Promise.race([work, abortPromise]);
34226
+ } finally {
34227
+ if (onAbort !== void 0) signal.removeEventListener("abort", onAbort);
34228
+ }
34216
34229
  }
34217
34230
  function messageToGoogleGenAI(message) {
34218
34231
  if (message.role === "tool") throw new ChatProviderError("Tool messages must be converted via messagesToGoogleGenAIContents.");
@@ -34582,8 +34595,8 @@ var GoogleGenAIChatProvider = class {
34582
34595
  contents,
34583
34596
  config
34584
34597
  };
34585
- if (this._stream) return new GoogleGenAIStreamedMessage(await Promise.race([models.generateContentStream(params), abortPromise(options?.signal)]), true, options?.signal);
34586
- return new GoogleGenAIStreamedMessage(await Promise.race([models.generateContent(params), abortPromise(options?.signal)]), false, options?.signal);
34598
+ if (this._stream) return new GoogleGenAIStreamedMessage(await abortRace(options?.signal, models.generateContentStream(params)), true, options?.signal);
34599
+ return new GoogleGenAIStreamedMessage(await abortRace(options?.signal, models.generateContent(params)), false, options?.signal);
34587
34600
  } catch (error) {
34588
34601
  if (error instanceof DOMException && error.name === "AbortError") throw error;
34589
34602
  throw convertGoogleGenAIError(error);
@@ -53403,6 +53416,17 @@ var MemoryMemoStore = class MemoryMemoStore {
53403
53416
  async append(entry) {
53404
53417
  return this.withWriteLock(() => this.appendInternal(entry));
53405
53418
  }
53419
+ /**
53420
+ * True when a memo with the same source session + user need + approach
53421
+ * already exists. Used by compaction-summary recovery to re-store memos
53422
+ * idempotently after a crash between compaction apply and extraction.
53423
+ * The approach field keeps two distinct memos that happen to share the
53424
+ * same user need from being collapsed into one.
53425
+ */
53426
+ existsBySourceAndNeed(sourceSessionId, userNeed, approach) {
53427
+ if (this.db === void 0) return false;
53428
+ return this.db.prepare("SELECT 1 FROM memos WHERE source_session_id = ? AND user_need = ? AND approach = ? LIMIT 1").get(sourceSessionId, userNeed, approach) !== void 0;
53429
+ }
53406
53430
  /** Delete a memo by id. */
53407
53431
  async delete(id) {
53408
53432
  return this.withWriteLock(() => this.deleteInternal(id));
@@ -79164,6 +79188,31 @@ function extractPreviousSummary(history) {
79164
79188
  const text = head.content.filter((p) => p.type === "text").map((p) => p.text).join("");
79165
79189
  return text.length > 0 ? text : null;
79166
79190
  }
79191
+ /**
79192
+ * Crash-recovery path for compaction memos. `context.apply_compaction` is
79193
+ * written to wire before the extraction step runs; if the process dies in
79194
+ * that window the memos are lost forever (nothing re-extracts them). On wire
79195
+ * replay the summary is available again, so re-parse it and store anything
79196
+ * missing. Idempotent: a healthy replay finds the memos already stored
79197
+ * (existsBySourceAndNeed) and skips them, so recovery never duplicates.
79198
+ */
79199
+ async function recoverMemosFromCompactionSummary(agent, summary) {
79200
+ const memoStore = agent.memoStore;
79201
+ if (!memoStore || summary === void 0 || summary.trim().length === 0) return;
79202
+ const memos = parseMemoryMemos(summary);
79203
+ if (memos.length === 0) return;
79204
+ const sessionId = agent.homedir ? basename$1(dirname$2(dirname$2(agent.homedir))) : "unknown";
79205
+ const sessionTitle = await agent.getSessionTitle().catch(() => void 0);
79206
+ const projectDir = agent.config.cwd;
79207
+ const failed = (await Promise.allSettled(memos.map(async (memo) => {
79208
+ if (memoStore.existsBySourceAndNeed(sessionId, memo.userNeed, memo.approach)) return;
79209
+ memo.sourceSessionId = sessionId;
79210
+ memo.sourceSessionTitle = sessionTitle ?? "";
79211
+ memo.projectDir = projectDir;
79212
+ await memoStore.append(memo);
79213
+ }))).filter((result) => result.status === "rejected").length;
79214
+ if (failed > 0) agent.log.warn("Some recovered memory memos failed to store", { failed });
79215
+ }
79167
79216
  //#endregion
79168
79217
  //#region ../../packages/agent-core/src/flags/registry.ts
79169
79218
  /**
@@ -84844,6 +84893,7 @@ var FileSystemAgentRecordPersistence = class {
84844
84893
  pendingRecords = [];
84845
84894
  shouldClear = false;
84846
84895
  directorySynced = false;
84896
+ rewriteSeq = 0;
84847
84897
  flushPromise;
84848
84898
  error;
84849
84899
  constructor(filePath, options = {}) {
@@ -84953,7 +85003,21 @@ var FileSystemAgentRecordPersistence = class {
84953
85003
  const content = (this.options.blobStore !== void 0 ? await Promise.all(batch.map((record) => this.options.blobStore.offload(record))) : batch).map((e) => JSON.stringify(e) + "\n").join("");
84954
85004
  const directory = dirname$2(this.filePath);
84955
85005
  await mkdir(directory, { recursive: true });
84956
- const fh = await open(this.filePath, shouldClear ? "w" : "a");
85006
+ if (shouldClear) {
85007
+ const tmpPath = `${this.filePath}.${process.pid}.${this.rewriteSeq++}.tmp`;
85008
+ const tmp = await open(tmpPath, "w");
85009
+ try {
85010
+ if (content.length > 0) await tmp.writeFile(content, "utf8");
85011
+ await tmp.sync();
85012
+ } finally {
85013
+ await tmp.close();
85014
+ }
85015
+ await rename(tmpPath, this.filePath);
85016
+ await syncDir(directory);
85017
+ this.directorySynced = true;
85018
+ return;
85019
+ }
85020
+ const fh = await open(this.filePath, "a");
84957
85021
  try {
84958
85022
  if (content.length > 0) await fh.writeFile(content, "utf8");
84959
85023
  await fh.sync();
@@ -85310,6 +85374,7 @@ function restoreAgentRecord(agent, input) {
85310
85374
  return;
85311
85375
  case "context.apply_compaction":
85312
85376
  agent.context.applyCompaction(input);
85377
+ recoverMemosFromCompactionSummary(agent, input.summary);
85313
85378
  return;
85314
85379
  case "context.snapshot":
85315
85380
  agent.context.restoreJSONSnapshot(input.snapshot);
@@ -85406,11 +85471,19 @@ var AgentRecords = class {
85406
85471
  snapshotIndex = i;
85407
85472
  break;
85408
85473
  }
85474
+ let foldedCompactionSummary;
85409
85475
  for (let i = 0; i < replayedRecords.length; i++) {
85410
85476
  const record = replayedRecords[i];
85411
85477
  if (!record) continue;
85412
- if (i < snapshotIndex && isSnapshotFoldedContextRecord(record.type)) continue;
85478
+ if (i < snapshotIndex && isSnapshotFoldedContextRecord(record.type)) {
85479
+ if (record.type === "context.apply_compaction") foldedCompactionSummary = record.summary;
85480
+ continue;
85481
+ }
85413
85482
  this.restore(record);
85483
+ if (record.type === "context.snapshot" && foldedCompactionSummary !== void 0) {
85484
+ recoverMemosFromCompactionSummary(this.agent, foldedCompactionSummary);
85485
+ foldedCompactionSummary = void 0;
85486
+ }
85414
85487
  }
85415
85488
  this.agent.context.dropVacuousOpenMessages();
85416
85489
  if (shouldRewrite) {
@@ -105566,6 +105639,9 @@ var SessionSubagentHost = class {
105566
105639
  backgroundTaskTimeoutMs;
105567
105640
  modelBindings;
105568
105641
  activeChildren = /* @__PURE__ */ new Map();
105642
+ /** Per-child per-model usage already folded into the parent totals, so a
105643
+ * resumed child's aggregation only adds the delta. */
105644
+ aggregatedChildUsage = /* @__PURE__ */ new WeakMap();
105569
105645
  constructor(session, ownerAgentId, backgroundTaskTimeoutMs, modelBindings) {
105570
105646
  this.session = session;
105571
105647
  this.ownerAgentId = ownerAgentId;
@@ -105709,6 +105785,21 @@ var SessionSubagentHost = class {
105709
105785
  result = lastAssistantText$1(child);
105710
105786
  }
105711
105787
  const usage = child.usage.data().total;
105788
+ const childByModel = child.usage.data().byModel ?? {};
105789
+ const previous = this.aggregatedChildUsage.get(child) ?? {};
105790
+ for (const [model, childUsage] of Object.entries(childByModel)) {
105791
+ const delta = previous[model] === void 0 ? childUsage : subtractUsage(childUsage, previous[model]);
105792
+ if (isZeroUsage(delta)) continue;
105793
+ try {
105794
+ parent.usage.record(model, delta, "session");
105795
+ } catch (error) {
105796
+ parent.log.warn("Failed to aggregate subagent usage", {
105797
+ model,
105798
+ error: String(error)
105799
+ });
105800
+ }
105801
+ }
105802
+ this.aggregatedChildUsage.set(child, childByModel);
105712
105803
  let findingsBlock = "";
105713
105804
  if (profileName === "reviewer") {
105714
105805
  const findings = getFindingsFromStore(child.tools.toolStore);
@@ -105813,6 +105904,18 @@ var SessionSubagentHost = class {
105813
105904
  });
105814
105905
  }
105815
105906
  };
105907
+ /** Element-wise subtraction clamped at zero (usage can never go negative). */
105908
+ function subtractUsage(current, previous) {
105909
+ return {
105910
+ inputOther: Math.max(0, current.inputOther - previous.inputOther),
105911
+ output: Math.max(0, current.output - previous.output),
105912
+ inputCacheRead: Math.max(0, current.inputCacheRead - previous.inputCacheRead),
105913
+ inputCacheCreation: Math.max(0, current.inputCacheCreation - previous.inputCacheCreation)
105914
+ };
105915
+ }
105916
+ function isZeroUsage(usage) {
105917
+ return usage.inputOther === 0 && usage.output === 0 && usage.inputCacheRead === 0 && usage.inputCacheCreation === 0;
105918
+ }
105816
105919
  async function runChildTurnToCompletion(child, signal) {
105817
105920
  const completion = await child.turn.waitForCurrentTurn(signal);
105818
105921
  const turnEnded = completion.event;
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
3
3
  import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
- import { Kt as handleConnectCommand } from "./dispatch-D8ovLbmr.mjs";
6
+ import { Kt as handleConnectCommand } from "./dispatch-B_ViYZer.mjs";
7
7
  export { handleConnectCommand };
package/dist/main.mjs CHANGED
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
7
  //#region src/main.ts
8
8
  try {
9
- (await import("./app-CPu6R7nd.mjs")).main();
9
+ (await import("./app-6Hcxs2tP.mjs")).main();
10
10
  } catch (error) {
11
11
  process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
12
12
  process.exit(1);