flavor-code 1.3.3 → 1.3.6

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.
@@ -1,6 +1,6 @@
1
1
  // src/desktop/main.ts
2
2
  import { appendFileSync } from "fs";
3
- import { createHash as createHash27, randomUUID as randomUUID27 } from "crypto";
3
+ import { createHash as createHash27, randomUUID as randomUUID28 } from "crypto";
4
4
  import { readFile as readFile39, stat as stat18, writeFile as writeFile19 } from "fs/promises";
5
5
  import { basename as basename11, dirname as dirname24, join as join46, resolve as resolve40 } from "path";
6
6
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -4218,7 +4218,7 @@ ${summarizeReport(report2)}`).join("\n\n")
4218
4218
 
4219
4219
  // src/production.ts
4220
4220
  import { readFile as readFile27 } from "fs/promises";
4221
- import { createHash as createHash20, randomUUID as randomUUID23 } from "crypto";
4221
+ import { createHash as createHash20, randomUUID as randomUUID24 } from "crypto";
4222
4222
  import { homedir as homedir2, release as osRelease, tmpdir as tmpdir3, version as osVersion } from "os";
4223
4223
  import { basename as basename8, join as join31, relative as relative23, resolve as resolve32, sep as sep23 } from "path";
4224
4224
 
@@ -7524,11 +7524,18 @@ var DEFAULT_TOOL_OUTPUT_LIMITS = Object.freeze({
7524
7524
  perTurnChars: 2e5
7525
7525
  });
7526
7526
  var PreToolUsePayload = z14.object({
7527
+ // Optional for backwards-compatible hook updatedInput payloads; runtime
7528
+ // emissions always populate it.
7529
+ toolCallId: z14.string().optional(),
7527
7530
  tool: z14.string(),
7528
7531
  input: z14.unknown(),
7529
7532
  agent: z14.enum(["main", "subagent"])
7530
7533
  });
7531
- var PermissionRequestPayload = PreToolUsePayload.extend({ reason: z14.string().optional() });
7534
+ var PermissionRequestPayload = PreToolUsePayload.extend({
7535
+ reason: z14.string().optional(),
7536
+ toolCategory: z14.string(),
7537
+ allowAlways: z14.boolean()
7538
+ });
7532
7539
  var PostToolUsePayload = PreToolUsePayload.extend({ output: z14.unknown() });
7533
7540
  var PostToolUseFailurePayload = PreToolUsePayload.extend({
7534
7541
  error: z14.object({ code: z14.string(), message: z14.string() })
@@ -7645,30 +7652,31 @@ var ToolRuntime = class {
7645
7652
  const tool = this.#tools.get(call.name);
7646
7653
  if (tool === void 0) return { ok: false, error: { code: "unknown_tool", message: `Unknown tool: ${call.name}` } };
7647
7654
  const signal = context.signal ?? new AbortController().signal;
7655
+ const toolCallId = call.id ?? randomUUID7();
7648
7656
  const validation = this.validate(call);
7649
7657
  if (!validation.ok) {
7650
- return this.#fail(call.name, call.input, context.agent, validation.error.code, validation.error.message);
7658
+ return this.#fail(toolCallId, call.name, call.input, context.agent, validation.error.code, validation.error.message);
7651
7659
  }
7652
7660
  let input = validation.input;
7653
7661
  try {
7654
7662
  const pre = await this.#hooks.emit({
7655
7663
  version: 1,
7656
7664
  type: "PreToolUse",
7657
- payload: { tool: tool.name, input, agent: context.agent }
7665
+ payload: { toolCallId, tool: tool.name, input, agent: context.agent }
7658
7666
  });
7659
7667
  if (pre.updatedInput !== void 0) {
7660
7668
  const payload = PreToolUsePayload.parse(pre.updatedInput);
7661
7669
  if (payload.tool !== tool.name || payload.agent !== context.agent) {
7662
- return this.#fail(tool.name, input, context.agent, "invalid_input", "PreToolUse cannot change the tool or agent");
7670
+ return this.#fail(toolCallId, tool.name, input, context.agent, "invalid_input", "PreToolUse cannot change the tool or agent");
7663
7671
  }
7664
7672
  try {
7665
7673
  input = tool.inputSchema.parse(payload.input);
7666
7674
  } catch (error) {
7667
- return this.#fail(tool.name, payload.input, context.agent, "invalid_input", message(error));
7675
+ return this.#fail(toolCallId, tool.name, payload.input, context.agent, "invalid_input", message(error));
7668
7676
  }
7669
7677
  }
7670
7678
  if (pre.decision === "deny") {
7671
- return this.#fail(tool.name, input, context.agent, "hook_denied", pre.reason ?? "Tool use denied by hook");
7679
+ return this.#fail(toolCallId, tool.name, input, context.agent, "hook_denied", pre.reason ?? "Tool use denied by hook");
7672
7680
  }
7673
7681
  const request = {
7674
7682
  agent: context.agent,
@@ -7679,13 +7687,13 @@ var ToolRuntime = class {
7679
7687
  let permission = this.#permissions.decide(request);
7680
7688
  const nonCacheableApproval = request.allowAlways === false || permission.allowAlways === false;
7681
7689
  if (permission.decision === "deny") {
7682
- return this.#fail(tool.name, input, context.agent, "permission_denied", permission.reason ?? "Tool use denied");
7690
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", permission.reason ?? "Tool use denied");
7683
7691
  }
7684
7692
  if (permission.decision === "ask" && context.agent === "main" && !nonCacheableApproval && this.#permissions.mode === "auto" && this.#permissions.profile === "standard" && this.#classify !== void 0) {
7685
7693
  try {
7686
7694
  const classified = await this.#classify(request, signal);
7687
7695
  if (classified.decision === "deny") {
7688
- return this.#fail(tool.name, input, context.agent, "permission_denied", classified.reason ?? "Auto classifier denied tool use");
7696
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", classified.reason ?? "Auto classifier denied tool use");
7689
7697
  }
7690
7698
  if (classified.decision === "allow") permission = classified;
7691
7699
  else {
@@ -7702,13 +7710,21 @@ var ToolRuntime = class {
7702
7710
  const requestDecision = await this.#hooks.emit({
7703
7711
  version: 1,
7704
7712
  type: "PermissionRequest",
7705
- payload: { tool: tool.name, input: tool.permissionInput?.(input) ?? input, agent: context.agent, reason }
7713
+ payload: {
7714
+ toolCallId,
7715
+ tool: tool.name,
7716
+ input: tool.permissionInput?.(input) ?? input,
7717
+ agent: context.agent,
7718
+ reason,
7719
+ toolCategory: getToolCategory(tool.name),
7720
+ allowAlways
7721
+ }
7706
7722
  });
7707
7723
  if (requestDecision.decision === "deny") {
7708
- return this.#fail(tool.name, input, context.agent, "permission_denied", requestDecision.reason ?? "Permission request denied");
7724
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", requestDecision.reason ?? "Permission request denied");
7709
7725
  }
7710
7726
  if (requestDecision.updatedInput !== void 0) {
7711
- return this.#fail(tool.name, input, context.agent, "invalid_input", "PermissionRequest cannot modify an already-authorized tool call");
7727
+ return this.#fail(toolCallId, tool.name, input, context.agent, "invalid_input", "PermissionRequest cannot modify an already-authorized tool call");
7712
7728
  }
7713
7729
  if (this.#hooks.hasListeners("PermissionRequest") && requestDecision.decision === "allow") {
7714
7730
  const ctx = requestDecision.additionalContext ?? "";
@@ -7717,18 +7733,18 @@ var ToolRuntime = class {
7717
7733
  if (allowAlways && category !== "destructive") this.#alwaysAllowed.add(category);
7718
7734
  }
7719
7735
  } else if (context.agent !== "main" && this.#permissions.mode !== "bubble") {
7720
- return this.#fail(tool.name, input, context.agent, "approval_required", reason);
7736
+ return this.#fail(toolCallId, tool.name, input, context.agent, "approval_required", reason);
7721
7737
  } else if (signal.aborted) {
7722
7738
  throw signal.reason;
7723
7739
  } else {
7724
7740
  const category = getToolCategory(tool.name);
7725
7741
  if (allowAlways && this.#alwaysAllowed.has(category)) {
7726
7742
  } else if (this.#approve === void 0) {
7727
- return this.#fail(tool.name, input, context.agent, "permission_denied", reason);
7743
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", reason);
7728
7744
  } else {
7729
7745
  const decision = await this.#approve({ ...request, reason, ...allowAlways ? {} : { allowAlways: false } }, signal);
7730
7746
  if (decision === "deny") {
7731
- return this.#fail(tool.name, input, context.agent, "user_denied", reason);
7747
+ return this.#fail(toolCallId, tool.name, input, context.agent, "user_denied", reason);
7732
7748
  }
7733
7749
  if (decision === "always" && allowAlways && category !== "destructive") {
7734
7750
  this.#alwaysAllowed.add(category);
@@ -7742,7 +7758,7 @@ var ToolRuntime = class {
7742
7758
  try {
7743
7759
  rawOutput = tool.outputSchema === void 0 ? executed : tool.outputSchema.parse(executed);
7744
7760
  } catch (error) {
7745
- return this.#fail(tool.name, input, context.agent, "invalid_output", message(error));
7761
+ return this.#fail(toolCallId, tool.name, input, context.agent, "invalid_output", message(error));
7746
7762
  }
7747
7763
  const declaredPresentation = tool.presentResult?.(rawOutput, input);
7748
7764
  const presentation = declaredPresentation ?? getToolPresentation(rawOutput);
@@ -7753,7 +7769,7 @@ var ToolRuntime = class {
7753
7769
  await this.#hooks.emit({
7754
7770
  version: 1,
7755
7771
  type: "PostToolUse",
7756
- payload: { tool: tool.name, input, agent: context.agent, output }
7772
+ payload: { toolCallId, tool: tool.name, input, agent: context.agent, output }
7757
7773
  });
7758
7774
  return {
7759
7775
  ok: true,
@@ -7763,7 +7779,7 @@ var ToolRuntime = class {
7763
7779
  ...additionalContext === void 0 || additionalContext.length === 0 ? {} : { additionalContext }
7764
7780
  };
7765
7781
  } catch (error) {
7766
- return this.#fail(tool.name, input, context.agent, "tool_error", message(error));
7782
+ return this.#fail(toolCallId, tool.name, input, context.agent, "tool_error", message(error));
7767
7783
  }
7768
7784
  }
7769
7785
  async #limitOutput(tool, output) {
@@ -7811,13 +7827,13 @@ var ToolRuntime = class {
7811
7827
  const limited = await this.#limitOutput(tool, content);
7812
7828
  return typeof limited === "string" ? limited : serializeToolOutput(limited);
7813
7829
  }
7814
- async #fail(tool, input, agent, code, errorMessage3) {
7830
+ async #fail(toolCallId, tool, input, agent, code, errorMessage3) {
7815
7831
  const result = { ok: false, error: { code, message: errorMessage3 } };
7816
7832
  try {
7817
7833
  await this.#hooks.emit({
7818
7834
  version: 1,
7819
7835
  type: "PostToolUseFailure",
7820
- payload: { tool, input, agent, error: result.error }
7836
+ payload: { toolCallId, tool, input, agent, error: result.error }
7821
7837
  });
7822
7838
  } catch {
7823
7839
  }
@@ -8025,7 +8041,7 @@ function toModelTool(tool) {
8025
8041
 
8026
8042
  // src/harness/journal.ts
8027
8043
  import { createHash as createHash6, randomUUID as randomUUID8 } from "crypto";
8028
- import { closeSync, constants as constants2, existsSync as existsSync2, fsyncSync, mkdirSync, openSync, readFileSync, realpathSync as realpathSync2, statSync, truncateSync, writeSync } from "fs";
8044
+ import { closeSync, constants as constants2, existsSync as existsSync2, fsyncSync, mkdirSync, openSync, readFileSync, realpathSync as realpathSync2, renameSync, statSync, truncateSync, unlinkSync, writeSync } from "fs";
8029
8045
  import { join as join8, relative as relative6, resolve as resolve8, sep as sep6 } from "path";
8030
8046
  import { z as z15 } from "zod";
8031
8047
 
@@ -8036,8 +8052,10 @@ function harnessInvariantViolations(records) {
8036
8052
  const tools = /* @__PURE__ */ new Set();
8037
8053
  const models = /* @__PURE__ */ new Set();
8038
8054
  const turns = /* @__PURE__ */ new Set();
8055
+ const firstSequence = records[0]?.sequence ?? 1;
8039
8056
  records.forEach((record3, index) => {
8040
- if (record3.sequence !== index + 1) violations.push(`sequence ${record3.sequence} is not ${index + 1}`);
8057
+ const expectedSequence = firstSequence + index;
8058
+ if (record3.sequence !== expectedSequence) violations.push(`sequence ${record3.sequence} is not ${expectedSequence}`);
8041
8059
  const id = typeof record3.payload.id === "string" ? record3.payload.id : "";
8042
8060
  if (record3.type === "queue.admitted") {
8043
8061
  if (queues.has(id)) violations.push(`queue ${id} was admitted twice`);
@@ -8053,13 +8071,13 @@ function harnessInvariantViolations(records) {
8053
8071
  if (!tools.delete(id)) violations.push(`${record3.type} references unknown tool ${id}`);
8054
8072
  } else if (record3.type === "model.requested") {
8055
8073
  if (models.has(id)) violations.push(`model ${id} was requested twice`);
8056
- if (!Array.isArray(record3.payload.messages)) violations.push(`model ${id} has no replayable messages`);
8074
+ if (!isHash(record3.payload.messagesHash)) violations.push(`model ${id} has no valid messages hash`);
8057
8075
  models.add(id);
8058
8076
  } else if (record3.type === "model.completed") {
8059
8077
  if (!models.delete(id)) violations.push(`model.completed references unknown model ${id}`);
8060
8078
  } else if (record3.type === "turn.started") {
8061
8079
  if (turns.has(id)) violations.push(`turn ${id} was started twice`);
8062
- if (!("prompt" in record3.payload)) violations.push(`turn ${id} has no replayable prompt`);
8080
+ if (!isHash(record3.payload.promptHash)) violations.push(`turn ${id} has no valid prompt hash`);
8063
8081
  turns.add(id);
8064
8082
  } else if (record3.type === "turn.completed" || record3.type === "turn.interrupted") {
8065
8083
  if (!turns.delete(id)) violations.push(`${record3.type} references unknown turn ${id}`);
@@ -8067,6 +8085,9 @@ function harnessInvariantViolations(records) {
8067
8085
  });
8068
8086
  return violations;
8069
8087
  }
8088
+ function isHash(value) {
8089
+ return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
8090
+ }
8070
8091
  function assertHarnessInvariants(records) {
8071
8092
  const violations = harnessInvariantViolations(records);
8072
8093
  if (violations.length > 0) throw new Error(`Harness invariant violation: ${violations.join("; ")}`);
@@ -8147,7 +8168,7 @@ var HarnessJournal = class {
8147
8168
  }
8148
8169
  startTurn(config, prompt) {
8149
8170
  const id = randomUUID8();
8150
- this.#append("turn.started", { id, config: structuredClone(config), configHash: hashJson(config), prompt: structuredClone(prompt), promptHash: hashJson(prompt) });
8171
+ this.#append("turn.started", { id, configHash: hashJson(config), promptHash: hashJson(prompt) });
8151
8172
  return id;
8152
8173
  }
8153
8174
  completeTurn(id) {
@@ -8158,7 +8179,14 @@ var HarnessJournal = class {
8158
8179
  }
8159
8180
  startModel(input) {
8160
8181
  const id = randomUUID8();
8161
- this.#append("model.requested", { id, ...structuredClone(input), messagesHash: hashJson(input.messages) });
8182
+ this.#append("model.requested", {
8183
+ id,
8184
+ agent: input.agent,
8185
+ model: input.model,
8186
+ iteration: input.iteration,
8187
+ attempt: input.attempt,
8188
+ messagesHash: hashJson(input.messages)
8189
+ });
8162
8190
  return id;
8163
8191
  }
8164
8192
  completeModel(id, completed, error) {
@@ -8166,11 +8194,11 @@ var HarnessJournal = class {
8166
8194
  }
8167
8195
  startTool(tool, input, retrySafe) {
8168
8196
  const id = randomUUID8();
8169
- this.#append("tool.started", { id, tool, input: structuredClone(input), inputHash: hashJson(input), retrySafe });
8197
+ this.#append("tool.started", { id, tool, inputHash: hashJson(input), retrySafe });
8170
8198
  return id;
8171
8199
  }
8172
8200
  completeTool(id, result) {
8173
- this.#append("tool.completed", { id, result: structuredClone(result) });
8201
+ this.#append("tool.completed", { id, resultHash: hashJson(result) });
8174
8202
  }
8175
8203
  interruptTool(id, reason) {
8176
8204
  this.#append("tool.interrupted", { id, reason });
@@ -8247,47 +8275,153 @@ var HarnessJournal = class {
8247
8275
  const committed = raw.endsWith("\n") ? raw : raw.slice(0, Math.max(0, raw.lastIndexOf("\n") + 1));
8248
8276
  if (committed.length !== raw.length) truncateSync(this.#path, Buffer.byteLength(committed));
8249
8277
  const lines = committed.split("\n").filter(Boolean);
8250
- let previousHash = null;
8278
+ let previousHash;
8279
+ let expectedSequence;
8251
8280
  for (const line of lines) {
8252
8281
  const record3 = JournalRecordSchema.parse(JSON.parse(line));
8253
- if (record3.sequence !== this.#records.length + 1) throw new Error("Harness journal sequence is not contiguous");
8282
+ expectedSequence ??= record3.sequence;
8283
+ previousHash ??= record3.previousHash;
8284
+ if (record3.sequence !== expectedSequence) throw new Error("Harness journal sequence is not contiguous");
8254
8285
  if (record3.previousHash !== previousHash) throw new Error("Harness journal hash chain is broken");
8255
8286
  if (record3.hash !== recordHash(record3)) throw new Error("Harness journal record hash is invalid");
8256
8287
  this.#records.push(record3);
8257
8288
  previousHash = record3.hash;
8289
+ expectedSequence += 1;
8258
8290
  }
8259
- this.#sequence = this.#records.length;
8260
- this.#lastHash = previousHash;
8291
+ this.#sequence = this.#records.at(-1)?.sequence ?? 0;
8292
+ this.#lastHash = previousHash ?? null;
8261
8293
  assertHarnessInvariants(this.#records);
8294
+ const hasRotatedPrefix = (this.#records[0]?.sequence ?? 1) !== 1;
8295
+ if (metadata.size > this.#maxBytes * 0.75 || hasRotatedPrefix || this.#records.some(hasLegacyPayload)) this.#compact();
8262
8296
  }
8263
8297
  #append(type, payload) {
8264
8298
  const normalizedPayload = jsonSafeRecord(payload);
8299
+ let record3 = this.#createRecord(type, normalizedPayload);
8300
+ let line = `${JSON.stringify(record3)}
8301
+ `;
8302
+ let priorBytes = existsSync2(this.#path) ? statSync(this.#path).size : 0;
8303
+ if (priorBytes + Buffer.byteLength(line) > this.#maxBytes) {
8304
+ this.#compact();
8305
+ record3 = this.#createRecord(type, normalizedPayload);
8306
+ line = `${JSON.stringify(record3)}
8307
+ `;
8308
+ priorBytes = existsSync2(this.#path) ? statSync(this.#path).size : 0;
8309
+ if (priorBytes + Buffer.byteLength(line) > this.#maxBytes) {
8310
+ throw new Error(`Harness journal event exceeds the ${this.#maxBytes} byte bounded journal capacity`);
8311
+ }
8312
+ }
8313
+ const fd = openSync(this.#path, constants2.O_CREAT | constants2.O_APPEND | constants2.O_WRONLY, 384);
8314
+ try {
8315
+ writeSync(fd, line, void 0, "utf8");
8316
+ fsyncSync(fd);
8317
+ } finally {
8318
+ closeSync(fd);
8319
+ }
8320
+ this.#records.push(record3);
8321
+ this.#sequence = record3.sequence;
8322
+ this.#lastHash = record3.hash;
8323
+ }
8324
+ #createRecord(type, payload) {
8265
8325
  const unsigned = {
8266
8326
  version: HARNESS_JOURNAL_VERSION,
8267
8327
  sequence: this.#sequence + 1,
8268
8328
  id: randomUUID8(),
8269
8329
  timestamp: this.#now(),
8270
8330
  type,
8271
- payload: normalizedPayload,
8331
+ payload,
8272
8332
  previousHash: this.#lastHash
8273
8333
  };
8274
- const record3 = JournalRecordSchema.parse({ ...unsigned, hash: hashJson(unsigned) });
8275
- const line = `${JSON.stringify(record3)}
8276
- `;
8277
- const priorBytes = existsSync2(this.#path) ? statSync(this.#path).size : 0;
8278
- if (priorBytes + Buffer.byteLength(line) > this.#maxBytes) throw new Error(`Harness journal exceeds ${this.#maxBytes} bytes`);
8279
- const fd = openSync(this.#path, constants2.O_CREAT | constants2.O_APPEND | constants2.O_WRONLY, 384);
8334
+ return JournalRecordSchema.parse({ ...unsigned, hash: hashJson(unsigned) });
8335
+ }
8336
+ #compact() {
8337
+ const queues = /* @__PURE__ */ new Map();
8338
+ const claims = /* @__PURE__ */ new Map();
8339
+ const tools = /* @__PURE__ */ new Map();
8340
+ const models = /* @__PURE__ */ new Map();
8341
+ const turns = /* @__PURE__ */ new Map();
8342
+ let savepoint;
8343
+ for (const record3 of this.#records) {
8344
+ const id = typeof record3.payload.id === "string" ? record3.payload.id : "";
8345
+ if (record3.type === "queue.admitted") queues.set(id, record3);
8346
+ else if (record3.type === "queue.claimed") claims.set(id, record3);
8347
+ else if (record3.type === "queue.released") claims.delete(id);
8348
+ else if (record3.type === "queue.acked") {
8349
+ queues.delete(id);
8350
+ claims.delete(id);
8351
+ } else if (record3.type === "tool.started") tools.set(id, record3);
8352
+ else if (record3.type === "tool.completed" || record3.type === "tool.interrupted") tools.delete(id);
8353
+ else if (record3.type === "model.requested") models.set(id, record3);
8354
+ else if (record3.type === "model.completed") models.delete(id);
8355
+ else if (record3.type === "turn.started") turns.set(id, record3);
8356
+ else if (record3.type === "turn.completed" || record3.type === "turn.interrupted") turns.delete(id);
8357
+ else if (record3.type === "savepoint.created") savepoint = record3;
8358
+ }
8359
+ const retained = [
8360
+ ...queues.values(),
8361
+ ...claims.values(),
8362
+ ...tools.values(),
8363
+ ...models.values(),
8364
+ ...turns.values(),
8365
+ ...savepoint === void 0 ? [] : [savepoint]
8366
+ ].sort((left, right) => left.sequence - right.sequence);
8367
+ const compacted = [];
8368
+ let previousHash = null;
8369
+ for (const source of retained) {
8370
+ const unsigned = {
8371
+ version: HARNESS_JOURNAL_VERSION,
8372
+ sequence: compacted.length + 1,
8373
+ id: randomUUID8(),
8374
+ timestamp: source.timestamp,
8375
+ type: source.type,
8376
+ payload: compactPayload(source),
8377
+ previousHash
8378
+ };
8379
+ const record3 = JournalRecordSchema.parse({ ...unsigned, hash: hashJson(unsigned) });
8380
+ compacted.push(record3);
8381
+ previousHash = record3.hash;
8382
+ }
8383
+ const contents = compacted.map((record3) => `${JSON.stringify(record3)}
8384
+ `).join("");
8385
+ if (Buffer.byteLength(contents) > this.#maxBytes) return;
8386
+ const temporary = `${this.#path}.compact-${process.pid}-${randomUUID8()}`;
8387
+ const fd = openSync(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
8280
8388
  try {
8281
- writeSync(fd, line, void 0, "utf8");
8389
+ writeSync(fd, contents, void 0, "utf8");
8282
8390
  fsyncSync(fd);
8283
8391
  } finally {
8284
8392
  closeSync(fd);
8285
8393
  }
8286
- this.#records.push(record3);
8287
- this.#sequence = record3.sequence;
8288
- this.#lastHash = record3.hash;
8394
+ try {
8395
+ renameSync(temporary, this.#path);
8396
+ } catch (error) {
8397
+ if (existsSync2(temporary)) unlinkSync(temporary);
8398
+ throw error;
8399
+ }
8400
+ this.#records = compacted;
8401
+ this.#sequence = compacted.length;
8402
+ this.#lastHash = previousHash;
8289
8403
  }
8290
8404
  };
8405
+ function compactPayload(record3) {
8406
+ const payload = record3.payload;
8407
+ if (record3.type === "queue.admitted") return payload;
8408
+ if (record3.type === "queue.claimed") return { id: payload.id };
8409
+ if (record3.type === "tool.started") return pick(payload, ["id", "tool", "inputHash", "retrySafe"]);
8410
+ if (record3.type === "model.requested") return pick(payload, ["id", "agent", "model", "iteration", "attempt", "messagesHash"]);
8411
+ if (record3.type === "turn.started") return pick(payload, ["id", "configHash", "promptHash"]);
8412
+ if (record3.type === "savepoint.created") return pick(payload, ["id", "phase", "configHash"]);
8413
+ return payload;
8414
+ }
8415
+ function hasLegacyPayload(record3) {
8416
+ if (record3.type === "turn.started") return "config" in record3.payload || "prompt" in record3.payload;
8417
+ if (record3.type === "model.requested") return "messages" in record3.payload;
8418
+ if (record3.type === "tool.started") return "input" in record3.payload;
8419
+ if (record3.type === "tool.completed") return "result" in record3.payload;
8420
+ return false;
8421
+ }
8422
+ function pick(source, keys) {
8423
+ return Object.fromEntries(keys.flatMap((key) => key in source ? [[key, source[key]]] : []));
8424
+ }
8291
8425
  function jsonSafeRecord(value) {
8292
8426
  const json = JSON.stringify(value, (_key, item) => typeof item === "bigint" ? `${item}n` : item);
8293
8427
  return JSON.parse(json);
@@ -8320,6 +8454,7 @@ function isWithin2(root, target) {
8320
8454
 
8321
8455
  // src/hooks/bus.ts
8322
8456
  import { spawn } from "child_process";
8457
+ import { randomUUID as randomUUID9 } from "crypto";
8323
8458
 
8324
8459
  // src/hooks/types.ts
8325
8460
  import { z as z16 } from "zod";
@@ -8386,6 +8521,11 @@ var DEFAULT_MAX_OUTPUT_BYTES = 1048576;
8386
8521
  var HookBus = class {
8387
8522
  #handlers = /* @__PURE__ */ new Map();
8388
8523
  #payloadSchemas = /* @__PURE__ */ new Map();
8524
+ #options;
8525
+ #sequence = 0;
8526
+ constructor(options = {}) {
8527
+ this.#options = options;
8528
+ }
8389
8529
  registerPayloadSchema(type, schema) {
8390
8530
  const registrations = this.#payloadSchemas.get(type) ?? [];
8391
8531
  const registration = { schema };
@@ -8422,7 +8562,9 @@ var HookBus = class {
8422
8562
  async emit(rawEvent, externalSignal) {
8423
8563
  externalSignal?.throwIfAborted();
8424
8564
  let event = HookEventSchema.parse(rawEvent);
8425
- event = { ...event, payload: this.#validatePayload(event.type, event.payload) };
8565
+ const validatedPayload = this.#validatePayload(event.type, event.payload);
8566
+ const envelope = this.#eventEnvelope();
8567
+ event = { ...event, payload: decoratePayload(validatedPayload, envelope) };
8426
8568
  let aggregate2 = { decision: "allow" };
8427
8569
  for (const registered of this.#handlers.get(event.type) ?? []) {
8428
8570
  externalSignal?.throwIfAborted();
@@ -8437,8 +8579,8 @@ var HookBus = class {
8437
8579
  externalSignal?.throwIfAborted();
8438
8580
  if (decision.updatedInput !== void 0) {
8439
8581
  const payload = this.#validatePayload(event.type, decision.updatedInput);
8440
- event = HookEventSchema.parse({ ...event, payload });
8441
- aggregate2 = { ...aggregate2, updatedInput: event.payload };
8582
+ event = HookEventSchema.parse({ ...event, payload: decoratePayload(payload, envelope) });
8583
+ aggregate2 = { ...aggregate2, updatedInput: payload };
8442
8584
  }
8443
8585
  if (decision.additionalContext !== void 0) {
8444
8586
  aggregate2 = {
@@ -8455,6 +8597,15 @@ var HookBus = class {
8455
8597
  const schema = this.#payloadSchemas.get(type)?.at(-1)?.schema;
8456
8598
  return schema ? schema.parse(payload) : zodRecord(payload);
8457
8599
  }
8600
+ #eventEnvelope() {
8601
+ if (this.#options.eventContext === void 0) return void 0;
8602
+ return {
8603
+ ...this.#options.eventContext(),
8604
+ eventId: (this.#options.eventId ?? randomUUID9)(),
8605
+ sequence: ++this.#sequence,
8606
+ timestamp: (this.#options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))()
8607
+ };
8608
+ }
8458
8609
  async #invoke(registered, event, externalSignal) {
8459
8610
  const timeoutSignal = AbortSignal.timeout(registered.timeoutMs);
8460
8611
  const signal = externalSignal === void 0 ? timeoutSignal : AbortSignal.any([timeoutSignal, externalSignal]);
@@ -8466,6 +8617,9 @@ var HookBus = class {
8466
8617
  return HookDecisionSchema.parse(await Promise.race([invocation, timeout]));
8467
8618
  }
8468
8619
  };
8620
+ function decoratePayload(payload, envelope) {
8621
+ return envelope === void 0 ? payload : { ...payload, ...envelope };
8622
+ }
8469
8623
  function runShellHandler(descriptor, event, signal) {
8470
8624
  return new Promise((resolve41, reject) => {
8471
8625
  const child = spawn(descriptor.command, [...descriptor.args ?? []], {
@@ -11862,7 +12016,7 @@ function formatPriorGaps(gaps) {
11862
12016
 
11863
12017
  // src/goal/store.ts
11864
12018
  import { constants as constants3 } from "fs";
11865
- import { randomUUID as randomUUID9 } from "crypto";
12019
+ import { randomUUID as randomUUID10 } from "crypto";
11866
12020
  import { lstat as lstat5, mkdir as mkdir11, open as open5, rename as rename4, rm as rm6 } from "fs/promises";
11867
12021
  import { dirname as dirname10, join as join15, relative as relative9, resolve as resolve11, sep as sep9 } from "path";
11868
12022
  var DEFAULT_MAX_BYTES = 1024 * 1024;
@@ -11886,7 +12040,7 @@ var GoalStore = class {
11886
12040
  if (Buffer.byteLength(body) > this.#maxBytes) {
11887
12041
  throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
11888
12042
  }
11889
- const temporary = join15(this.#root, `.${state.id}.${process.pid}.${randomUUID9()}.tmp`);
12043
+ const temporary = join15(this.#root, `.${state.id}.${process.pid}.${randomUUID10()}.tmp`);
11890
12044
  let handle;
11891
12045
  try {
11892
12046
  handle = await open5(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
@@ -11999,7 +12153,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
11999
12153
  import { constants as constants4 } from "fs";
12000
12154
  import { lstat as lstat6, mkdir as mkdir13, open as open6, readFile as readFile12, realpath as realpath8, rename as rename5, rm as rm7, stat as stat6 } from "fs/promises";
12001
12155
  import { dirname as dirname11, join as join17, relative as relative10, resolve as resolve13, sep as sep10 } from "path";
12002
- import { randomUUID as randomUUID10 } from "crypto";
12156
+ import { randomUUID as randomUUID11 } from "crypto";
12003
12157
  var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
12004
12158
  var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
12005
12159
  var LoopStore = class {
@@ -12030,7 +12184,7 @@ var LoopStore = class {
12030
12184
  const body = `${JSON.stringify(state, null, 2)}
12031
12185
  `;
12032
12186
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
12033
- const temporary = join17(directory, `.state.${process.pid}.${randomUUID10()}.tmp`);
12187
+ const temporary = join17(directory, `.state.${process.pid}.${randomUUID11()}.tmp`);
12034
12188
  let handle;
12035
12189
  try {
12036
12190
  handle = await open6(temporary, constants4.O_CREAT | constants4.O_EXCL | constants4.O_WRONLY, 384);
@@ -12056,7 +12210,7 @@ var LoopStore = class {
12056
12210
  return state;
12057
12211
  } catch (error) {
12058
12212
  if (isCode5(error, "ENOENT")) throw new Error(`Loop "${loopId}" was not found`);
12059
- const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID10().slice(0, 8)}`;
12213
+ const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
12060
12214
  await rename5(target, quarantine).catch(() => void 0);
12061
12215
  throw new Error(`Loop "${loopId}" is corrupt and was quarantined: ${message(error)}`);
12062
12216
  }
@@ -14915,7 +15069,7 @@ function createSkillResourceTool(registry) {
14915
15069
  import { constants as constants6 } from "fs";
14916
15070
  import { lstat as lstat8, mkdir as mkdir15, open as open7, readFile as readFile17, readdir as readdir6, realpath as realpath10, rename as rename6, rm as rm8, stat as stat7 } from "fs/promises";
14917
15071
  import { basename as basename5, dirname as dirname15, join as join22, relative as relative13, resolve as resolve19, sep as sep13 } from "path";
14918
- import { randomUUID as randomUUID11 } from "crypto";
15072
+ import { randomUUID as randomUUID12 } from "crypto";
14919
15073
  import { z as z27 } from "zod";
14920
15074
 
14921
15075
  // src/ui/transcript.ts
@@ -15603,7 +15757,7 @@ var SessionStore = class {
15603
15757
  const body = `${lines.join("\n")}
15604
15758
  `;
15605
15759
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
15606
- const temporary = join22(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID11()}.tmp`);
15760
+ const temporary = join22(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID12()}.tmp`);
15607
15761
  let handle;
15608
15762
  try {
15609
15763
  handle = await open7(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
@@ -15759,7 +15913,7 @@ var SessionStore = class {
15759
15913
  if (!isWithin5(canonicalRoot, canonicalSessions)) throw new Error("Session directory escapes the workspace");
15760
15914
  }
15761
15915
  async #quarantine(path) {
15762
- const target = `${path}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
15916
+ const target = `${path}.corrupt-${Date.now()}-${randomUUID12().slice(0, 8)}`;
15763
15917
  await rename6(path, target);
15764
15918
  return target;
15765
15919
  }
@@ -15978,12 +16132,12 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
15978
16132
  }
15979
16133
 
15980
16134
  // src/session/tree.ts
15981
- import { randomUUID as randomUUID13 } from "crypto";
16135
+ import { randomUUID as randomUUID14 } from "crypto";
15982
16136
  import { mkdir as mkdir17, readFile as readFile19, rename as rename8, writeFile as writeFile11 } from "fs/promises";
15983
16137
  import { dirname as dirname17, join as join24, resolve as resolve21 } from "path";
15984
16138
 
15985
16139
  // src/session/checkpoint.ts
15986
- import { createHash as createHash14, randomUUID as randomUUID12 } from "crypto";
16140
+ import { createHash as createHash14, randomUUID as randomUUID13 } from "crypto";
15987
16141
  import { execFile as execFile2 } from "child_process";
15988
16142
  import { lstat as lstat9, mkdir as mkdir16, open as open8, readFile as readFile18, readdir as readdir7, rename as rename7, rm as rm9, writeFile as writeFile10 } from "fs/promises";
15989
16143
  import { dirname as dirname16, join as join23, relative as relative14, resolve as resolve20, sep as sep14 } from "path";
@@ -16036,7 +16190,7 @@ var WorkspaceCheckpointStore = class {
16036
16190
  }
16037
16191
  const checkpoint = {
16038
16192
  version: 1,
16039
- id: `checkpoint-${Date.now()}-${randomUUID12().slice(0, 8)}`,
16193
+ id: `checkpoint-${Date.now()}-${randomUUID13().slice(0, 8)}`,
16040
16194
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
16041
16195
  ...label === void 0 || label.trim().length === 0 ? {} : { label: label.trim() },
16042
16196
  files
@@ -16073,7 +16227,7 @@ var WorkspaceCheckpointStore = class {
16073
16227
  const target = contained(this.#workspace, file.path);
16074
16228
  const object = objects.get(file.digest);
16075
16229
  await mkdir16(dirname16(target), { recursive: true });
16076
- const temporary = `${target}.${process.pid}.${randomUUID12()}.tmp`;
16230
+ const temporary = `${target}.${process.pid}.${randomUUID13()}.tmp`;
16077
16231
  await writeFile10(temporary, object, { mode: file.mode });
16078
16232
  await rename7(temporary, target);
16079
16233
  }
@@ -16145,7 +16299,7 @@ function contained(workspace, path) {
16145
16299
  }
16146
16300
  async function atomicJson(path, value) {
16147
16301
  await mkdir16(dirname16(path), { recursive: true });
16148
- const temporary = `${path}.${process.pid}.${randomUUID12()}.tmp`;
16302
+ const temporary = `${path}.${process.pid}.${randomUUID13()}.tmp`;
16149
16303
  await writeFile10(temporary, `${JSON.stringify(value)}
16150
16304
  `, { encoding: "utf8", mode: 384 });
16151
16305
  await rename7(temporary, path);
@@ -16190,7 +16344,7 @@ var SessionHistory = class _SessionHistory {
16190
16344
  async append(input) {
16191
16345
  const checkpoint = await this.#checkpoints.create(input.label);
16192
16346
  const node = {
16193
- id: `turn-${Date.now()}-${randomUUID13().slice(0, 8)}`,
16347
+ id: `turn-${Date.now()}-${randomUUID14().slice(0, 8)}`,
16194
16348
  parentId: this.#leafId,
16195
16349
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
16196
16350
  prompt: input.prompt,
@@ -16248,7 +16402,7 @@ var SessionHistory = class _SessionHistory {
16248
16402
  nodes: this.#nodes
16249
16403
  };
16250
16404
  await mkdir17(dirname17(this.#path), { recursive: true });
16251
- const temporary = `${this.#path}.${process.pid}.${randomUUID13()}.tmp`;
16405
+ const temporary = `${this.#path}.${process.pid}.${randomUUID14()}.tmp`;
16252
16406
  await writeFile11(temporary, `${JSON.stringify(document)}
16253
16407
  `, { encoding: "utf8", mode: 384 });
16254
16408
  await rename8(temporary, this.#path);
@@ -16274,7 +16428,7 @@ function isCode10(error, code) {
16274
16428
 
16275
16429
  // src/sleep/organizer.ts
16276
16430
  import { constants as constants7 } from "fs";
16277
- import { randomUUID as randomUUID14 } from "crypto";
16431
+ import { randomUUID as randomUUID15 } from "crypto";
16278
16432
  import { lstat as lstat10, mkdir as mkdir18, open as open9, readdir as readdir8, realpath as realpath11, rename as rename9, rm as rm10 } from "fs/promises";
16279
16433
  import { isAbsolute as isAbsolute10, join as join25, relative as relative15, resolve as resolve22, sep as sep15 } from "path";
16280
16434
  import { z as z28 } from "zod";
@@ -16431,7 +16585,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
16431
16585
  return (await readdir8(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
16432
16586
  }
16433
16587
  async #atomicWrite(path, content) {
16434
- const temporary = join25(this.#sleepDirectory, `.${randomUUID14()}.tmp`);
16588
+ const temporary = join25(this.#sleepDirectory, `.${randomUUID15()}.tmp`);
16435
16589
  let handle;
16436
16590
  try {
16437
16591
  handle = await open9(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
@@ -16807,7 +16961,7 @@ function computeSessionStats(documents) {
16807
16961
  import { constants as constants8 } from "fs";
16808
16962
  import { mkdir as mkdir19, open as open10, readFile as readFile20, realpath as realpath12, rename as rename10, rm as rm11, stat as stat8 } from "fs/promises";
16809
16963
  import { dirname as dirname18, isAbsolute as isAbsolute11, relative as relative16, resolve as resolve23, sep as sep16 } from "path";
16810
- import { randomUUID as randomUUID15 } from "crypto";
16964
+ import { randomUUID as randomUUID16 } from "crypto";
16811
16965
  import { z as z29 } from "zod";
16812
16966
 
16813
16967
  // src/tools/file-diff.ts
@@ -17160,7 +17314,7 @@ function createPathGuard(workspace) {
17160
17314
  async function atomicWrite(path, content, signal, expectedVersion) {
17161
17315
  abortIfNeeded(signal);
17162
17316
  await mkdir19(dirname18(path), { recursive: true });
17163
- const temporary = `${path}.${randomUUID15()}.tmp`;
17317
+ const temporary = `${path}.${randomUUID16()}.tmp`;
17164
17318
  try {
17165
17319
  const handle = await open10(temporary, constants8.O_CREAT | constants8.O_EXCL | constants8.O_WRONLY, 384);
17166
17320
  try {
@@ -19222,18 +19376,40 @@ function hookAnswersFromUpdatedInput(updatedInput, questions) {
19222
19376
  var QuestionBridge = class {
19223
19377
  #pending;
19224
19378
  #onChange;
19225
- constructor(onChange) {
19379
+ #relay;
19380
+ #active = false;
19381
+ constructor(onChange, relay) {
19226
19382
  this.#onChange = onChange;
19383
+ this.#relay = relay;
19227
19384
  }
19228
19385
  get pending() {
19229
19386
  return this.#pending?.questions;
19230
19387
  }
19231
19388
  ask(questions, signal) {
19232
- if (this.#pending !== void 0) return Promise.reject(new Error("A question is already pending"));
19389
+ if (this.#active) return Promise.reject(new Error("A question is already pending"));
19233
19390
  if (signal.aborted) return Promise.reject(signal.reason);
19391
+ this.#active = true;
19392
+ if (this.#relay !== void 0) return this.#askRelayed(questions, signal);
19393
+ return this.#askLocally(questions, signal);
19394
+ }
19395
+ async #askRelayed(questions, signal) {
19396
+ try {
19397
+ const answers = await this.#relay(questions, signal);
19398
+ if (answers !== void 0) {
19399
+ this.#active = false;
19400
+ return answers;
19401
+ }
19402
+ return await this.#askLocally(questions, signal);
19403
+ } catch (error) {
19404
+ this.#active = false;
19405
+ throw error;
19406
+ }
19407
+ }
19408
+ #askLocally(questions, signal) {
19234
19409
  return new Promise((resolve41, reject) => {
19235
19410
  const onAbort = () => {
19236
19411
  this.#pending = void 0;
19412
+ this.#active = false;
19237
19413
  reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
19238
19414
  };
19239
19415
  signal.addEventListener("abort", onAbort, { once: true });
@@ -19242,12 +19418,14 @@ var QuestionBridge = class {
19242
19418
  resolve: (answers) => {
19243
19419
  signal.removeEventListener("abort", onAbort);
19244
19420
  this.#pending = void 0;
19421
+ this.#active = false;
19245
19422
  this.#onChange?.();
19246
19423
  resolve41(answers);
19247
19424
  },
19248
19425
  reject: (error) => {
19249
19426
  signal.removeEventListener("abort", onAbort);
19250
19427
  this.#pending = void 0;
19428
+ this.#active = false;
19251
19429
  this.#onChange?.();
19252
19430
  reject(error);
19253
19431
  }
@@ -19333,7 +19511,7 @@ function createTodoWriteTool() {
19333
19511
  }
19334
19512
 
19335
19513
  // src/tools/managed.ts
19336
- import { randomUUID as randomUUID16 } from "crypto";
19514
+ import { randomUUID as randomUUID17 } from "crypto";
19337
19515
  import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile22, readdir as readdir9, unlink as unlink2, writeFile as writeFile12 } from "fs/promises";
19338
19516
  import { dirname as dirname21, join as join26, resolve as resolve26 } from "path";
19339
19517
  import { z as z37 } from "zod";
@@ -19620,7 +19798,7 @@ ${sourceUrl}`
19620
19798
  }
19621
19799
  async function writeExclusive(path, content) {
19622
19800
  await mkdir20(dirname21(path), { recursive: true });
19623
- const temporary = `${path}.${randomUUID16()}.tmp`;
19801
+ const temporary = `${path}.${randomUUID17()}.tmp`;
19624
19802
  try {
19625
19803
  await writeFile12(temporary, content, { encoding: "utf8", flag: "wx", mode: 384 });
19626
19804
  await link(temporary, path);
@@ -22848,7 +23026,7 @@ function relativeDisplay(workspace, filePath) {
22848
23026
  }
22849
23027
 
22850
23028
  // src/jobs/registry.ts
22851
- import { randomUUID as randomUUID17 } from "crypto";
23029
+ import { randomUUID as randomUUID18 } from "crypto";
22852
23030
  var JobRegistry = class {
22853
23031
  #jobs = /* @__PURE__ */ new Map();
22854
23032
  #maxOutputChars;
@@ -22862,7 +23040,7 @@ var JobRegistry = class {
22862
23040
  }
22863
23041
  create(input) {
22864
23042
  this.#pruneCompleted();
22865
- const id = `job-${randomUUID17().slice(0, 12)}`;
23043
+ const id = `job-${randomUUID18().slice(0, 12)}`;
22866
23044
  const now = (/* @__PURE__ */ new Date()).toISOString();
22867
23045
  let settle;
22868
23046
  const done = new Promise((resolve41) => {
@@ -22988,7 +23166,7 @@ function snapshot2(job) {
22988
23166
  }
22989
23167
 
22990
23168
  // src/terminal/service.ts
22991
- import { randomUUID as randomUUID18 } from "crypto";
23169
+ import { randomUUID as randomUUID19 } from "crypto";
22992
23170
  import { createRequire as nodeCreateRequire } from "module";
22993
23171
  import { isAbsolute as isAbsolute14, relative as relative22, resolve as resolve30, sep as sep22 } from "path";
22994
23172
  var TerminalService = class {
@@ -23006,7 +23184,7 @@ var TerminalService = class {
23006
23184
  open(input) {
23007
23185
  const cwd = workspacePath(this.#root, input.cwd ?? ".");
23008
23186
  const shell2 = input.shell ?? defaultShell();
23009
- const id = `term-${randomUUID18().slice(0, 12)}`;
23187
+ const id = `term-${randomUUID19().slice(0, 12)}`;
23010
23188
  const pty = this.#factory(shell2, input.args ?? [], {
23011
23189
  cwd,
23012
23190
  cols: input.columns ?? 100,
@@ -23239,13 +23417,13 @@ import { join as join30 } from "path";
23239
23417
  import { spawn as spawn7 } from "child_process";
23240
23418
 
23241
23419
  // src/pals/broker.ts
23242
- import { createHash as createHash18, randomUUID as randomUUID21, timingSafeEqual } from "crypto";
23420
+ import { createHash as createHash18, randomUUID as randomUUID22, timingSafeEqual } from "crypto";
23243
23421
  import { chmod as chmod2, lstat as lstat12, mkdir as mkdir23, unlink as unlink5 } from "fs/promises";
23244
23422
  import { createServer as createServer3, Socket } from "net";
23245
23423
  import { dirname as dirname23 } from "path";
23246
23424
 
23247
23425
  // src/pals/auth.ts
23248
- import { randomBytes as randomBytes3, randomUUID as randomUUID19 } from "crypto";
23426
+ import { randomBytes as randomBytes3, randomUUID as randomUUID20 } from "crypto";
23249
23427
  import { chmod, link as link2, mkdir as mkdir21, open as open12, readFile as readFile25, unlink as unlink3 } from "fs/promises";
23250
23428
  import { homedir } from "os";
23251
23429
  import { join as join29 } from "path";
@@ -23257,7 +23435,7 @@ async function loadOrCreatePalAuthToken(options = {}) {
23257
23435
  await mkdir21(directory, { recursive: true, mode: 448 });
23258
23436
  await chmod(directory, 448);
23259
23437
  const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
23260
- const temporaryPath = join29(directory, `.auth-token-${process.pid}-${randomUUID19()}.tmp`);
23438
+ const temporaryPath = join29(directory, `.auth-token-${process.pid}-${randomUUID20()}.tmp`);
23261
23439
  let handle;
23262
23440
  try {
23263
23441
  handle = await open12(temporaryPath, "wx", 384);
@@ -23291,7 +23469,7 @@ function validatePalAuthToken(token) {
23291
23469
  }
23292
23470
 
23293
23471
  // src/pals/lifecycle.ts
23294
- import { randomUUID as randomUUID20 } from "crypto";
23472
+ import { randomUUID as randomUUID21 } from "crypto";
23295
23473
  import { mkdir as mkdir22, open as open13, readFile as readFile26, rename as rename11, stat as stat11, unlink as unlink4 } from "fs/promises";
23296
23474
  import { dirname as dirname22 } from "path";
23297
23475
  function defaultProcessAlive(pid) {
@@ -23317,7 +23495,7 @@ async function acquirePalFileLock(options) {
23317
23495
  await mkdir22(dirname22(options.path), { recursive: true, mode: 448 });
23318
23496
  const pid = options.pid ?? process.pid;
23319
23497
  const now = options.now ?? Date.now;
23320
- const nonce = randomUUID20();
23498
+ const nonce = randomUUID21();
23321
23499
  const metadata = { pid, createdAt: now(), nonce };
23322
23500
  let handle;
23323
23501
  try {
@@ -23436,7 +23614,7 @@ async function ensureOnce(options) {
23436
23614
  }
23437
23615
 
23438
23616
  // src/pals/client.ts
23439
- import { randomUUID as randomUUID22 } from "crypto";
23617
+ import { randomUUID as randomUUID23 } from "crypto";
23440
23618
  import { Socket as Socket3 } from "net";
23441
23619
  var DEFAULT_REQUEST_TIMEOUT_MS = 5e3;
23442
23620
  var DEFAULT_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -23512,7 +23690,7 @@ var PalClient = class {
23512
23690
  return this.starting;
23513
23691
  }
23514
23692
  async list() {
23515
- const data2 = await this.request({ version: 1, type: "list", requestId: randomUUID22() });
23693
+ const data2 = await this.request({ version: 1, type: "list", requestId: randomUUID23() });
23516
23694
  return PalPresenceListSchema.parse(data2);
23517
23695
  }
23518
23696
  async rename(alias) {
@@ -23525,38 +23703,38 @@ var PalClient = class {
23525
23703
  throw error;
23526
23704
  }
23527
23705
  }
23528
- async sendTask(target, message2, messageId = randomUUID22()) {
23529
- const data2 = await this.request({ version: 1, type: "task", requestId: randomUUID22(), messageId, target, goal: message2 });
23706
+ async sendTask(target, message2, messageId = randomUUID23()) {
23707
+ const data2 = await this.request({ version: 1, type: "task", requestId: randomUUID23(), messageId, target, goal: message2 });
23530
23708
  return DeliveryReceiptSchema.parse(data2);
23531
23709
  }
23532
- async sendChat(target, message2, messageId = randomUUID22()) {
23533
- const data2 = await this.request({ version: 1, type: "chat", requestId: randomUUID22(), messageId, target, message: message2 });
23710
+ async sendChat(target, message2, messageId = randomUUID23()) {
23711
+ const data2 = await this.request({ version: 1, type: "chat", requestId: randomUUID23(), messageId, target, message: message2 });
23534
23712
  return DeliveryReceiptSchema.parse(data2);
23535
23713
  }
23536
23714
  async startCoWork(input) {
23537
23715
  const data2 = await this.request({
23538
23716
  version: 1,
23539
23717
  type: "cowork-propose",
23540
- requestId: randomUUID22(),
23541
- coWorkId: input.coWorkId ?? randomUUID22(),
23718
+ requestId: randomUUID23(),
23719
+ coWorkId: input.coWorkId ?? randomUUID23(),
23542
23720
  goal: input.goal,
23543
23721
  participants: input.participants
23544
23722
  });
23545
23723
  return CoWorkSnapshotSchema.parse(data2);
23546
23724
  }
23547
23725
  async coWorkAction(action) {
23548
- const request = { version: 1, requestId: randomUUID22(), ...action };
23726
+ const request = { version: 1, requestId: randomUUID23(), ...action };
23549
23727
  return CoWorkSnapshotSchema.parse(await this.request(request));
23550
23728
  }
23551
23729
  async coWorkStatus(coWorkId) {
23552
- const data2 = await this.request({ version: 1, type: "cowork-get", requestId: randomUUID22(), coWorkId });
23730
+ const data2 = await this.request({ version: 1, type: "cowork-get", requestId: randomUUID23(), coWorkId });
23553
23731
  return CoWorkSnapshotSchema.parse(data2);
23554
23732
  }
23555
23733
  async integrateCoWork(input) {
23556
23734
  return this.coWorkAction({ type: "cowork-integration", ...input });
23557
23735
  }
23558
23736
  async cancelCoWork(coWorkId, reason) {
23559
- const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId: randomUUID22(), coWorkId, reason });
23737
+ const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId: randomUUID23(), coWorkId, reason });
23560
23738
  return CoWorkSnapshotSchema.parse(data2);
23561
23739
  }
23562
23740
  subscribe(listener) {
@@ -23568,7 +23746,7 @@ var PalClient = class {
23568
23746
  if (this.registered && this.socket !== void 0) {
23569
23747
  let disconnectTimer;
23570
23748
  await Promise.race([
23571
- this.request({ version: 1, type: "disconnect", requestId: randomUUID22() }).catch(() => void 0),
23749
+ this.request({ version: 1, type: "disconnect", requestId: randomUUID23() }).catch(() => void 0),
23572
23750
  new Promise((resolve41) => {
23573
23751
  disconnectTimer = setTimeout(resolve41, Math.min(this.requestTimeoutMs, 250));
23574
23752
  })
@@ -23659,7 +23837,7 @@ var PalClient = class {
23659
23837
  const data2 = await this.request({
23660
23838
  version: 1,
23661
23839
  type: "register",
23662
- requestId: randomUUID22(),
23840
+ requestId: randomUUID23(),
23663
23841
  authToken: this.authToken,
23664
23842
  presence: { version: 1, ...this.registration, connectedAt: now, lastSeenAt: now }
23665
23843
  });
@@ -23771,7 +23949,7 @@ var PalClient = class {
23771
23949
  if (!this.registered) return;
23772
23950
  if (this.heartbeatPending) return;
23773
23951
  this.heartbeatPending = true;
23774
- void this.request({ version: 1, type: "heartbeat", requestId: randomUUID22() }).catch(() => void 0).finally(() => {
23952
+ void this.request({ version: 1, type: "heartbeat", requestId: randomUUID23() }).catch(() => void 0).finally(() => {
23775
23953
  this.heartbeatPending = false;
23776
23954
  });
23777
23955
  }, this.heartbeatIntervalMs);
@@ -24058,7 +24236,7 @@ var ApprovalBridge = class {
24058
24236
  request(request, signal = new AbortController().signal) {
24059
24237
  if (this.#settle !== void 0) return Promise.resolve("deny");
24060
24238
  if (signal.aborted) return Promise.resolve("deny");
24061
- this.#pending = { id: randomUUID23(), ...request };
24239
+ this.#pending = { id: randomUUID24(), ...request };
24062
24240
  this.#onChange?.();
24063
24241
  return new Promise((resolvePromise) => {
24064
24242
  this.#settle = resolvePromise;
@@ -24177,7 +24355,14 @@ async function createProductionRuntime(options) {
24177
24355
  environment.OPENAI_API_KEY,
24178
24356
  environment.ANTHROPIC_API_KEY
24179
24357
  ].filter((value) => typeof value === "string" && value.length > 0);
24180
- const hooks = new HookBus();
24358
+ let hookSessionId;
24359
+ const hooks = new HookBus({
24360
+ eventContext: () => ({
24361
+ protocolVersion: 2,
24362
+ workspace,
24363
+ ...hookSessionId === void 0 ? {} : { sessionId: hookSessionId }
24364
+ })
24365
+ });
24181
24366
  hooks.on(
24182
24367
  "PostToolUseFailure",
24183
24368
  createIncidentReporter({
@@ -24197,8 +24382,7 @@ async function createProductionRuntime(options) {
24197
24382
  diagnostics.push(...permissionPolicy.diagnostics);
24198
24383
  const approvals = new ApprovalBridge(options.onApprovalChange);
24199
24384
  const resolveToolApproval = options.approvalPolicy === "deny" && options.rpcToolApprovals !== true ? () => "deny" : (request, signal) => approvals.request(request, signal);
24200
- const questions = new QuestionBridge(options.onApprovalChange);
24201
- const askUserQuestionHandler = async (qs, signal) => {
24385
+ const relayQuestions = async (qs, signal) => {
24202
24386
  if (options.approvalPolicy === "deny") throw new Error("AskUserQuestion is not available in non-interactive mode");
24203
24387
  if (hooks.hasListeners("PermissionRequest")) {
24204
24388
  let decision;
@@ -24216,8 +24400,10 @@ async function createProductionRuntime(options) {
24216
24400
  const hookAnswers = hookAnswersFromUpdatedInput(decision.updatedInput, qs);
24217
24401
  if (hookAnswers !== void 0) return hookAnswers;
24218
24402
  }
24219
- return questions.ask(qs, signal);
24403
+ return void 0;
24220
24404
  };
24405
+ const questions = new QuestionBridge(options.onApprovalChange, relayQuestions);
24406
+ const askUserQuestionHandler = (qs, signal) => questions.ask(qs, signal);
24221
24407
  const executionEnvironment = createExecutionEnvironment(workspace, config.execution);
24222
24408
  const jobs = new JobRegistry();
24223
24409
  const terminals = new TerminalService(workspace, { jobs });
@@ -24541,7 +24727,8 @@ async function createProductionRuntime(options) {
24541
24727
  let taskResults = { ...recovered?.tasks.results ?? {} };
24542
24728
  const subagentStartedAt = {};
24543
24729
  const subagentElapsedMs = {};
24544
- let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID23().slice(0, 8)}`;
24730
+ let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID24().slice(0, 8)}`;
24731
+ hookSessionId = sessionId;
24545
24732
  let harnessJournal = new HarnessJournal({ workspace, sessionId });
24546
24733
  const harnessRecovery = harnessJournal.recover();
24547
24734
  if (harnessRecovery.queue.length > 0 || harnessRecovery.incompleteTools.length > 0 || harnessRecovery.incompleteModelIds.length > 0 || harnessRecovery.interruptedTurnIds.length > 0) {
@@ -24625,7 +24812,13 @@ async function createProductionRuntime(options) {
24625
24812
  const publishTaskState = async () => {
24626
24813
  harness.main.context.updateTaskState(serializedTaskState());
24627
24814
  await persist();
24628
- emitOutput({ type: "tasks", snapshot: taskSnapshot() });
24815
+ const snapshot4 = taskSnapshot();
24816
+ emitOutput({ type: "tasks", snapshot: snapshot4 });
24817
+ await hooks.emit({
24818
+ version: 1,
24819
+ type: "Notification",
24820
+ payload: { kind: "task_snapshot", taskSnapshot: snapshot4 }
24821
+ }).catch(() => void 0);
24629
24822
  };
24630
24823
  for (const tool of createTaskPlanTools({
24631
24824
  getPlan: () => taskPlan,
@@ -25183,7 +25376,7 @@ async function createProductionRuntime(options) {
25183
25376
  return answers[0] === "Continue" ? "approved" : "rejected";
25184
25377
  },
25185
25378
  fingerprint: workspaceFingerprint,
25186
- idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID23().slice(0, 8)}`
25379
+ idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID24().slice(0, 8)}`
25187
25380
  });
25188
25381
  const goalStore = new GoalStore({ workspace });
25189
25382
  const goalOrchestrator = new GoalOrchestrator({
@@ -25202,7 +25395,7 @@ async function createProductionRuntime(options) {
25202
25395
  executionEnvironment
25203
25396
  ),
25204
25397
  now: () => (/* @__PURE__ */ new Date()).toISOString(),
25205
- idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID23().slice(0, 8)}`,
25398
+ idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID24().slice(0, 8)}`,
25206
25399
  runWorker: ({ workspace: goalWorkspace, prompt, signal }) => runLoopWorker({ workspace: goalWorkspace, prompt, signal })
25207
25400
  });
25208
25401
  let sessionHistory = await SessionHistory.open({
@@ -25315,7 +25508,7 @@ async function createProductionRuntime(options) {
25315
25508
  runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
25316
25509
  persist
25317
25510
  ),
25318
- runLoop: (goal, signal) => runLoopSession(loopOrchestrator, goal, signal),
25511
+ runLoop: (goal, signal) => runLoopSession(loopOrchestrator, hooks, goal, signal),
25319
25512
  runGoal: (goal, signal) => persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
25320
25513
  mcp: async (command, signal) => {
25321
25514
  signal.throwIfAborted();
@@ -25473,7 +25666,8 @@ ${summary2}`;
25473
25666
  for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
25474
25667
  for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
25475
25668
  const previousIdeSessionId = ideSessionId;
25476
- sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID23().slice(0, 8)}`;
25669
+ sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID24().slice(0, 8)}`;
25670
+ hookSessionId = sessionId;
25477
25671
  harnessJournal = new HarnessJournal({ workspace, sessionId });
25478
25672
  setUsageSession(sessionId);
25479
25673
  ideSessionId = sessionId;
@@ -25772,9 +25966,10 @@ function formatMcpReconnect(server) {
25772
25966
  if (server.status === "disabled") return `MCP server "${server.name}" is disabled. Enable it before reconnecting.`;
25773
25967
  return `Failed to reconnect MCP server "${server.name}": ${server.error ?? "unknown error"}`;
25774
25968
  }
25775
- async function* runLoopSession(orchestrator, goal, signal) {
25969
+ async function* runLoopSession(orchestrator, hooks, goal, signal) {
25776
25970
  let totalInputTokens = 0;
25777
25971
  let totalOutputTokens = 0;
25972
+ let latestVerification;
25778
25973
  for await (const event of orchestrator.run({ goal, signal })) {
25779
25974
  if (event.type === "worker-event") {
25780
25975
  if (event.event.type === "usage") {
@@ -25788,8 +25983,19 @@ async function* runLoopSession(orchestrator, goal, signal) {
25788
25983
  } else if (event.event.type !== "done") yield event.event;
25789
25984
  continue;
25790
25985
  }
25986
+ if (event.type === "loop-verification") latestVerification = event.evidence;
25791
25987
  yield loopProgressEvent(event);
25792
25988
  if (event.type === "loop-terminal") {
25989
+ await hooks.emit({
25990
+ version: 1,
25991
+ type: "LoopEnd",
25992
+ payload: {
25993
+ loopId: event.loopId,
25994
+ outcome: event.status,
25995
+ reason: event.reason,
25996
+ verification: latestVerification ?? null
25997
+ }
25998
+ }).catch(() => void 0);
25793
25999
  yield { type: "done", usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens } };
25794
26000
  }
25795
26001
  }
@@ -26503,7 +26709,7 @@ function memoryTranscriptHash(messages) {
26503
26709
  return createHash20("sha256").update(visible, "utf8").digest("hex");
26504
26710
  }
26505
26711
  function createMemoryTaskId() {
26506
- return `memory-${randomUUID23()}`;
26712
+ return `memory-${randomUUID24()}`;
26507
26713
  }
26508
26714
 
26509
26715
  // src/desktop/runtime-controller.ts
@@ -27487,13 +27693,13 @@ ${managed.output().slice(-4e3)}`);
27487
27693
  }
27488
27694
 
27489
27695
  // src/d2c/product.ts
27490
- import { createHash as createHash23, randomUUID as randomUUID25 } from "crypto";
27696
+ import { createHash as createHash23, randomUUID as randomUUID26 } from "crypto";
27491
27697
  import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile32, rename as rename13, stat as stat12, unlink as unlink6, writeFile as writeFile15 } from "fs/promises";
27492
27698
  import { join as join36 } from "path";
27493
27699
  import { z as z44 } from "zod";
27494
27700
 
27495
27701
  // src/e2e/prd-governance.ts
27496
- import { createHash as createHash22, randomUUID as randomUUID24 } from "crypto";
27702
+ import { createHash as createHash22, randomUUID as randomUUID25 } from "crypto";
27497
27703
  import { readFile as readFile31, rename as rename12, rm as rm13, writeFile as writeFile14 } from "fs/promises";
27498
27704
  function hashPrd(markdown) {
27499
27705
  return createHash22("sha256").update(markdown, "utf8").digest("hex");
@@ -27593,7 +27799,7 @@ ${body}
27593
27799
 
27594
27800
  `;
27595
27801
  const markdown = `${current.slice(0, section.contentStart)}${replacement}${current.slice(section.contentEnd)}`;
27596
- const temporary = `${path}.${process.pid}.${randomUUID24()}.tmp`;
27802
+ const temporary = `${path}.${process.pid}.${randomUUID25()}.tmp`;
27597
27803
  try {
27598
27804
  await writeFile14(temporary, markdown, { encoding: "utf8", flag: "wx", mode: 384 });
27599
27805
  await rename12(temporary, path);
@@ -27793,7 +27999,7 @@ async function writeD2cProductPlan(workspace, plan, operations = defaultPlanFile
27793
27999
  const target = planPath(workspace, parsed.task);
27794
28000
  await serializePlanWrite(target, async () => {
27795
28001
  await mkdir25(taskDir(workspace, parsed.task), { recursive: true });
27796
- const temporary = `${target}.${process.pid}.${randomUUID25()}.tmp`;
28002
+ const temporary = `${target}.${process.pid}.${randomUUID26()}.tmp`;
27797
28003
  await writeFile15(temporary, `${JSON.stringify(parsed, null, 2)}
27798
28004
  `, { encoding: "utf8", mode: 384 });
27799
28005
  await replaceD2cProductPlanFile(temporary, target, operations);
@@ -30246,7 +30452,7 @@ function safeGitTarget(value) {
30246
30452
  }
30247
30453
 
30248
30454
  // src/desktop/worktree-manager.ts
30249
- import { randomUUID as randomUUID26 } from "crypto";
30455
+ import { randomUUID as randomUUID27 } from "crypto";
30250
30456
  import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as stat15, writeFile as writeFile18 } from "fs/promises";
30251
30457
  import { basename as basename10, isAbsolute as isAbsolute19, join as join43, relative as relative27, resolve as resolve37, sep as sep27 } from "path";
30252
30458
  var DesktopWorktreeManager = class {
@@ -30259,7 +30465,7 @@ var DesktopWorktreeManager = class {
30259
30465
  async create(label) {
30260
30466
  if (!await isGitRepository(this.#repository)) throw new Error("Task worktrees require a Git repository");
30261
30467
  const safe = label.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 36) || "task";
30262
- const suffix = randomUUID26().slice(0, 8);
30468
+ const suffix = randomUUID27().slice(0, 8);
30263
30469
  const id = `worktree-${safe}-${suffix}`;
30264
30470
  const branch = `flavor/desktop-${safe}-${suffix}`;
30265
30471
  const path = this.#safeStoragePath(id);
@@ -30723,7 +30929,7 @@ function emitManagedDesktopEvent(workspace, owner2, event) {
30723
30929
  }
30724
30930
  }
30725
30931
  function createDesktopController(workspace, owner2) {
30726
- const collaborationId = randomUUID27();
30932
+ const collaborationId = randomUUID28();
30727
30933
  return new DesktopRuntimeController({
30728
30934
  emit: (event) => emitManagedDesktopEvent(workspace, owner2, event),
30729
30935
  runD2cInteractionTests: (manifest, baseUrl, mockUrl) => d2cEmbedded.run(manifest, baseUrl, mockUrl),