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.
@@ -5006,7 +5006,7 @@ async function ensureOnce(options) {
5006
5006
 
5007
5007
  // src/production.ts
5008
5008
  import { readFile as readFile22 } from "fs/promises";
5009
- import { createHash as createHash17, randomUUID as randomUUID18 } from "crypto";
5009
+ import { createHash as createHash17, randomUUID as randomUUID19 } from "crypto";
5010
5010
  import { homedir as homedir2, release as osRelease, tmpdir as tmpdir3, version as osVersion } from "os";
5011
5011
  import { basename as basename6, join as join24, relative as relative20, resolve as resolve27, sep as sep20 } from "path";
5012
5012
 
@@ -7484,11 +7484,18 @@ var DEFAULT_TOOL_OUTPUT_LIMITS = Object.freeze({
7484
7484
  perTurnChars: 2e5
7485
7485
  });
7486
7486
  var PreToolUsePayload = z9.object({
7487
+ // Optional for backwards-compatible hook updatedInput payloads; runtime
7488
+ // emissions always populate it.
7489
+ toolCallId: z9.string().optional(),
7487
7490
  tool: z9.string(),
7488
7491
  input: z9.unknown(),
7489
7492
  agent: z9.enum(["main", "subagent"])
7490
7493
  });
7491
- var PermissionRequestPayload = PreToolUsePayload.extend({ reason: z9.string().optional() });
7494
+ var PermissionRequestPayload = PreToolUsePayload.extend({
7495
+ reason: z9.string().optional(),
7496
+ toolCategory: z9.string(),
7497
+ allowAlways: z9.boolean()
7498
+ });
7492
7499
  var PostToolUsePayload = PreToolUsePayload.extend({ output: z9.unknown() });
7493
7500
  var PostToolUseFailurePayload = PreToolUsePayload.extend({
7494
7501
  error: z9.object({ code: z9.string(), message: z9.string() })
@@ -7605,30 +7612,31 @@ var ToolRuntime = class {
7605
7612
  const tool = this.#tools.get(call.name);
7606
7613
  if (tool === void 0) return { ok: false, error: { code: "unknown_tool", message: `Unknown tool: ${call.name}` } };
7607
7614
  const signal = context.signal ?? new AbortController().signal;
7615
+ const toolCallId = call.id ?? randomUUID7();
7608
7616
  const validation = this.validate(call);
7609
7617
  if (!validation.ok) {
7610
- return this.#fail(call.name, call.input, context.agent, validation.error.code, validation.error.message);
7618
+ return this.#fail(toolCallId, call.name, call.input, context.agent, validation.error.code, validation.error.message);
7611
7619
  }
7612
7620
  let input = validation.input;
7613
7621
  try {
7614
7622
  const pre = await this.#hooks.emit({
7615
7623
  version: 1,
7616
7624
  type: "PreToolUse",
7617
- payload: { tool: tool.name, input, agent: context.agent }
7625
+ payload: { toolCallId, tool: tool.name, input, agent: context.agent }
7618
7626
  });
7619
7627
  if (pre.updatedInput !== void 0) {
7620
7628
  const payload = PreToolUsePayload.parse(pre.updatedInput);
7621
7629
  if (payload.tool !== tool.name || payload.agent !== context.agent) {
7622
- return this.#fail(tool.name, input, context.agent, "invalid_input", "PreToolUse cannot change the tool or agent");
7630
+ return this.#fail(toolCallId, tool.name, input, context.agent, "invalid_input", "PreToolUse cannot change the tool or agent");
7623
7631
  }
7624
7632
  try {
7625
7633
  input = tool.inputSchema.parse(payload.input);
7626
7634
  } catch (error) {
7627
- return this.#fail(tool.name, payload.input, context.agent, "invalid_input", message(error));
7635
+ return this.#fail(toolCallId, tool.name, payload.input, context.agent, "invalid_input", message(error));
7628
7636
  }
7629
7637
  }
7630
7638
  if (pre.decision === "deny") {
7631
- return this.#fail(tool.name, input, context.agent, "hook_denied", pre.reason ?? "Tool use denied by hook");
7639
+ return this.#fail(toolCallId, tool.name, input, context.agent, "hook_denied", pre.reason ?? "Tool use denied by hook");
7632
7640
  }
7633
7641
  const request = {
7634
7642
  agent: context.agent,
@@ -7639,13 +7647,13 @@ var ToolRuntime = class {
7639
7647
  let permission = this.#permissions.decide(request);
7640
7648
  const nonCacheableApproval = request.allowAlways === false || permission.allowAlways === false;
7641
7649
  if (permission.decision === "deny") {
7642
- return this.#fail(tool.name, input, context.agent, "permission_denied", permission.reason ?? "Tool use denied");
7650
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", permission.reason ?? "Tool use denied");
7643
7651
  }
7644
7652
  if (permission.decision === "ask" && context.agent === "main" && !nonCacheableApproval && this.#permissions.mode === "auto" && this.#permissions.profile === "standard" && this.#classify !== void 0) {
7645
7653
  try {
7646
7654
  const classified = await this.#classify(request, signal);
7647
7655
  if (classified.decision === "deny") {
7648
- return this.#fail(tool.name, input, context.agent, "permission_denied", classified.reason ?? "Auto classifier denied tool use");
7656
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", classified.reason ?? "Auto classifier denied tool use");
7649
7657
  }
7650
7658
  if (classified.decision === "allow") permission = classified;
7651
7659
  else {
@@ -7662,13 +7670,21 @@ var ToolRuntime = class {
7662
7670
  const requestDecision = await this.#hooks.emit({
7663
7671
  version: 1,
7664
7672
  type: "PermissionRequest",
7665
- payload: { tool: tool.name, input: tool.permissionInput?.(input) ?? input, agent: context.agent, reason }
7673
+ payload: {
7674
+ toolCallId,
7675
+ tool: tool.name,
7676
+ input: tool.permissionInput?.(input) ?? input,
7677
+ agent: context.agent,
7678
+ reason,
7679
+ toolCategory: getToolCategory(tool.name),
7680
+ allowAlways
7681
+ }
7666
7682
  });
7667
7683
  if (requestDecision.decision === "deny") {
7668
- return this.#fail(tool.name, input, context.agent, "permission_denied", requestDecision.reason ?? "Permission request denied");
7684
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", requestDecision.reason ?? "Permission request denied");
7669
7685
  }
7670
7686
  if (requestDecision.updatedInput !== void 0) {
7671
- return this.#fail(tool.name, input, context.agent, "invalid_input", "PermissionRequest cannot modify an already-authorized tool call");
7687
+ return this.#fail(toolCallId, tool.name, input, context.agent, "invalid_input", "PermissionRequest cannot modify an already-authorized tool call");
7672
7688
  }
7673
7689
  if (this.#hooks.hasListeners("PermissionRequest") && requestDecision.decision === "allow") {
7674
7690
  const ctx = requestDecision.additionalContext ?? "";
@@ -7677,18 +7693,18 @@ var ToolRuntime = class {
7677
7693
  if (allowAlways && category !== "destructive") this.#alwaysAllowed.add(category);
7678
7694
  }
7679
7695
  } else if (context.agent !== "main" && this.#permissions.mode !== "bubble") {
7680
- return this.#fail(tool.name, input, context.agent, "approval_required", reason);
7696
+ return this.#fail(toolCallId, tool.name, input, context.agent, "approval_required", reason);
7681
7697
  } else if (signal.aborted) {
7682
7698
  throw signal.reason;
7683
7699
  } else {
7684
7700
  const category = getToolCategory(tool.name);
7685
7701
  if (allowAlways && this.#alwaysAllowed.has(category)) {
7686
7702
  } else if (this.#approve === void 0) {
7687
- return this.#fail(tool.name, input, context.agent, "permission_denied", reason);
7703
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", reason);
7688
7704
  } else {
7689
7705
  const decision = await this.#approve({ ...request, reason, ...allowAlways ? {} : { allowAlways: false } }, signal);
7690
7706
  if (decision === "deny") {
7691
- return this.#fail(tool.name, input, context.agent, "user_denied", reason);
7707
+ return this.#fail(toolCallId, tool.name, input, context.agent, "user_denied", reason);
7692
7708
  }
7693
7709
  if (decision === "always" && allowAlways && category !== "destructive") {
7694
7710
  this.#alwaysAllowed.add(category);
@@ -7702,7 +7718,7 @@ var ToolRuntime = class {
7702
7718
  try {
7703
7719
  rawOutput = tool.outputSchema === void 0 ? executed : tool.outputSchema.parse(executed);
7704
7720
  } catch (error) {
7705
- return this.#fail(tool.name, input, context.agent, "invalid_output", message(error));
7721
+ return this.#fail(toolCallId, tool.name, input, context.agent, "invalid_output", message(error));
7706
7722
  }
7707
7723
  const declaredPresentation = tool.presentResult?.(rawOutput, input);
7708
7724
  const presentation = declaredPresentation ?? getToolPresentation(rawOutput);
@@ -7713,7 +7729,7 @@ var ToolRuntime = class {
7713
7729
  await this.#hooks.emit({
7714
7730
  version: 1,
7715
7731
  type: "PostToolUse",
7716
- payload: { tool: tool.name, input, agent: context.agent, output }
7732
+ payload: { toolCallId, tool: tool.name, input, agent: context.agent, output }
7717
7733
  });
7718
7734
  return {
7719
7735
  ok: true,
@@ -7723,7 +7739,7 @@ var ToolRuntime = class {
7723
7739
  ...additionalContext === void 0 || additionalContext.length === 0 ? {} : { additionalContext }
7724
7740
  };
7725
7741
  } catch (error) {
7726
- return this.#fail(tool.name, input, context.agent, "tool_error", message(error));
7742
+ return this.#fail(toolCallId, tool.name, input, context.agent, "tool_error", message(error));
7727
7743
  }
7728
7744
  }
7729
7745
  async #limitOutput(tool, output) {
@@ -7771,13 +7787,13 @@ var ToolRuntime = class {
7771
7787
  const limited = await this.#limitOutput(tool, content);
7772
7788
  return typeof limited === "string" ? limited : serializeToolOutput(limited);
7773
7789
  }
7774
- async #fail(tool, input, agent, code, errorMessage2) {
7790
+ async #fail(toolCallId, tool, input, agent, code, errorMessage2) {
7775
7791
  const result = { ok: false, error: { code, message: errorMessage2 } };
7776
7792
  try {
7777
7793
  await this.#hooks.emit({
7778
7794
  version: 1,
7779
7795
  type: "PostToolUseFailure",
7780
- payload: { tool, input, agent, error: result.error }
7796
+ payload: { toolCallId, tool, input, agent, error: result.error }
7781
7797
  });
7782
7798
  } catch {
7783
7799
  }
@@ -7985,7 +8001,7 @@ function toModelTool(tool) {
7985
8001
 
7986
8002
  // src/harness/journal.ts
7987
8003
  import { createHash as createHash6, randomUUID as randomUUID8 } from "crypto";
7988
- import { closeSync, constants, existsSync as existsSync2, fsyncSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, realpathSync as realpathSync2, statSync, truncateSync, writeSync } from "fs";
8004
+ import { closeSync, constants, existsSync as existsSync2, fsyncSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, realpathSync as realpathSync2, renameSync, statSync, truncateSync, unlinkSync, writeSync } from "fs";
7989
8005
  import { join as join8, relative as relative7, resolve as resolve8, sep as sep7 } from "path";
7990
8006
  import { z as z10 } from "zod";
7991
8007
 
@@ -7996,8 +8012,10 @@ function harnessInvariantViolations(records) {
7996
8012
  const tools = /* @__PURE__ */ new Set();
7997
8013
  const models = /* @__PURE__ */ new Set();
7998
8014
  const turns = /* @__PURE__ */ new Set();
8015
+ const firstSequence = records[0]?.sequence ?? 1;
7999
8016
  records.forEach((record, index) => {
8000
- if (record.sequence !== index + 1) violations.push(`sequence ${record.sequence} is not ${index + 1}`);
8017
+ const expectedSequence = firstSequence + index;
8018
+ if (record.sequence !== expectedSequence) violations.push(`sequence ${record.sequence} is not ${expectedSequence}`);
8001
8019
  const id = typeof record.payload.id === "string" ? record.payload.id : "";
8002
8020
  if (record.type === "queue.admitted") {
8003
8021
  if (queues.has(id)) violations.push(`queue ${id} was admitted twice`);
@@ -8013,13 +8031,13 @@ function harnessInvariantViolations(records) {
8013
8031
  if (!tools.delete(id)) violations.push(`${record.type} references unknown tool ${id}`);
8014
8032
  } else if (record.type === "model.requested") {
8015
8033
  if (models.has(id)) violations.push(`model ${id} was requested twice`);
8016
- if (!Array.isArray(record.payload.messages)) violations.push(`model ${id} has no replayable messages`);
8034
+ if (!isHash(record.payload.messagesHash)) violations.push(`model ${id} has no valid messages hash`);
8017
8035
  models.add(id);
8018
8036
  } else if (record.type === "model.completed") {
8019
8037
  if (!models.delete(id)) violations.push(`model.completed references unknown model ${id}`);
8020
8038
  } else if (record.type === "turn.started") {
8021
8039
  if (turns.has(id)) violations.push(`turn ${id} was started twice`);
8022
- if (!("prompt" in record.payload)) violations.push(`turn ${id} has no replayable prompt`);
8040
+ if (!isHash(record.payload.promptHash)) violations.push(`turn ${id} has no valid prompt hash`);
8023
8041
  turns.add(id);
8024
8042
  } else if (record.type === "turn.completed" || record.type === "turn.interrupted") {
8025
8043
  if (!turns.delete(id)) violations.push(`${record.type} references unknown turn ${id}`);
@@ -8027,6 +8045,9 @@ function harnessInvariantViolations(records) {
8027
8045
  });
8028
8046
  return violations;
8029
8047
  }
8048
+ function isHash(value) {
8049
+ return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
8050
+ }
8030
8051
  function assertHarnessInvariants(records) {
8031
8052
  const violations = harnessInvariantViolations(records);
8032
8053
  if (violations.length > 0) throw new Error(`Harness invariant violation: ${violations.join("; ")}`);
@@ -8107,7 +8128,7 @@ var HarnessJournal = class {
8107
8128
  }
8108
8129
  startTurn(config, prompt) {
8109
8130
  const id = randomUUID8();
8110
- this.#append("turn.started", { id, config: structuredClone(config), configHash: hashJson(config), prompt: structuredClone(prompt), promptHash: hashJson(prompt) });
8131
+ this.#append("turn.started", { id, configHash: hashJson(config), promptHash: hashJson(prompt) });
8111
8132
  return id;
8112
8133
  }
8113
8134
  completeTurn(id) {
@@ -8118,7 +8139,14 @@ var HarnessJournal = class {
8118
8139
  }
8119
8140
  startModel(input) {
8120
8141
  const id = randomUUID8();
8121
- this.#append("model.requested", { id, ...structuredClone(input), messagesHash: hashJson(input.messages) });
8142
+ this.#append("model.requested", {
8143
+ id,
8144
+ agent: input.agent,
8145
+ model: input.model,
8146
+ iteration: input.iteration,
8147
+ attempt: input.attempt,
8148
+ messagesHash: hashJson(input.messages)
8149
+ });
8122
8150
  return id;
8123
8151
  }
8124
8152
  completeModel(id, completed, error) {
@@ -8126,11 +8154,11 @@ var HarnessJournal = class {
8126
8154
  }
8127
8155
  startTool(tool, input, retrySafe) {
8128
8156
  const id = randomUUID8();
8129
- this.#append("tool.started", { id, tool, input: structuredClone(input), inputHash: hashJson(input), retrySafe });
8157
+ this.#append("tool.started", { id, tool, inputHash: hashJson(input), retrySafe });
8130
8158
  return id;
8131
8159
  }
8132
8160
  completeTool(id, result) {
8133
- this.#append("tool.completed", { id, result: structuredClone(result) });
8161
+ this.#append("tool.completed", { id, resultHash: hashJson(result) });
8134
8162
  }
8135
8163
  interruptTool(id, reason) {
8136
8164
  this.#append("tool.interrupted", { id, reason });
@@ -8207,47 +8235,153 @@ var HarnessJournal = class {
8207
8235
  const committed = raw.endsWith("\n") ? raw : raw.slice(0, Math.max(0, raw.lastIndexOf("\n") + 1));
8208
8236
  if (committed.length !== raw.length) truncateSync(this.#path, Buffer.byteLength(committed));
8209
8237
  const lines = committed.split("\n").filter(Boolean);
8210
- let previousHash = null;
8238
+ let previousHash;
8239
+ let expectedSequence;
8211
8240
  for (const line of lines) {
8212
8241
  const record = JournalRecordSchema.parse(JSON.parse(line));
8213
- if (record.sequence !== this.#records.length + 1) throw new Error("Harness journal sequence is not contiguous");
8242
+ expectedSequence ??= record.sequence;
8243
+ previousHash ??= record.previousHash;
8244
+ if (record.sequence !== expectedSequence) throw new Error("Harness journal sequence is not contiguous");
8214
8245
  if (record.previousHash !== previousHash) throw new Error("Harness journal hash chain is broken");
8215
8246
  if (record.hash !== recordHash(record)) throw new Error("Harness journal record hash is invalid");
8216
8247
  this.#records.push(record);
8217
8248
  previousHash = record.hash;
8249
+ expectedSequence += 1;
8218
8250
  }
8219
- this.#sequence = this.#records.length;
8220
- this.#lastHash = previousHash;
8251
+ this.#sequence = this.#records.at(-1)?.sequence ?? 0;
8252
+ this.#lastHash = previousHash ?? null;
8221
8253
  assertHarnessInvariants(this.#records);
8254
+ const hasRotatedPrefix = (this.#records[0]?.sequence ?? 1) !== 1;
8255
+ if (metadata.size > this.#maxBytes * 0.75 || hasRotatedPrefix || this.#records.some(hasLegacyPayload)) this.#compact();
8222
8256
  }
8223
8257
  #append(type, payload) {
8224
8258
  const normalizedPayload = jsonSafeRecord(payload);
8259
+ let record = this.#createRecord(type, normalizedPayload);
8260
+ let line = `${JSON.stringify(record)}
8261
+ `;
8262
+ let priorBytes = existsSync2(this.#path) ? statSync(this.#path).size : 0;
8263
+ if (priorBytes + Buffer.byteLength(line) > this.#maxBytes) {
8264
+ this.#compact();
8265
+ record = this.#createRecord(type, normalizedPayload);
8266
+ line = `${JSON.stringify(record)}
8267
+ `;
8268
+ priorBytes = existsSync2(this.#path) ? statSync(this.#path).size : 0;
8269
+ if (priorBytes + Buffer.byteLength(line) > this.#maxBytes) {
8270
+ throw new Error(`Harness journal event exceeds the ${this.#maxBytes} byte bounded journal capacity`);
8271
+ }
8272
+ }
8273
+ const fd = openSync(this.#path, constants.O_CREAT | constants.O_APPEND | constants.O_WRONLY, 384);
8274
+ try {
8275
+ writeSync(fd, line, void 0, "utf8");
8276
+ fsyncSync(fd);
8277
+ } finally {
8278
+ closeSync(fd);
8279
+ }
8280
+ this.#records.push(record);
8281
+ this.#sequence = record.sequence;
8282
+ this.#lastHash = record.hash;
8283
+ }
8284
+ #createRecord(type, payload) {
8225
8285
  const unsigned = {
8226
8286
  version: HARNESS_JOURNAL_VERSION,
8227
8287
  sequence: this.#sequence + 1,
8228
8288
  id: randomUUID8(),
8229
8289
  timestamp: this.#now(),
8230
8290
  type,
8231
- payload: normalizedPayload,
8291
+ payload,
8232
8292
  previousHash: this.#lastHash
8233
8293
  };
8234
- const record = JournalRecordSchema.parse({ ...unsigned, hash: hashJson(unsigned) });
8235
- const line = `${JSON.stringify(record)}
8236
- `;
8237
- const priorBytes = existsSync2(this.#path) ? statSync(this.#path).size : 0;
8238
- if (priorBytes + Buffer.byteLength(line) > this.#maxBytes) throw new Error(`Harness journal exceeds ${this.#maxBytes} bytes`);
8239
- const fd = openSync(this.#path, constants.O_CREAT | constants.O_APPEND | constants.O_WRONLY, 384);
8294
+ return JournalRecordSchema.parse({ ...unsigned, hash: hashJson(unsigned) });
8295
+ }
8296
+ #compact() {
8297
+ const queues = /* @__PURE__ */ new Map();
8298
+ const claims = /* @__PURE__ */ new Map();
8299
+ const tools = /* @__PURE__ */ new Map();
8300
+ const models = /* @__PURE__ */ new Map();
8301
+ const turns = /* @__PURE__ */ new Map();
8302
+ let savepoint;
8303
+ for (const record of this.#records) {
8304
+ const id = typeof record.payload.id === "string" ? record.payload.id : "";
8305
+ if (record.type === "queue.admitted") queues.set(id, record);
8306
+ else if (record.type === "queue.claimed") claims.set(id, record);
8307
+ else if (record.type === "queue.released") claims.delete(id);
8308
+ else if (record.type === "queue.acked") {
8309
+ queues.delete(id);
8310
+ claims.delete(id);
8311
+ } else if (record.type === "tool.started") tools.set(id, record);
8312
+ else if (record.type === "tool.completed" || record.type === "tool.interrupted") tools.delete(id);
8313
+ else if (record.type === "model.requested") models.set(id, record);
8314
+ else if (record.type === "model.completed") models.delete(id);
8315
+ else if (record.type === "turn.started") turns.set(id, record);
8316
+ else if (record.type === "turn.completed" || record.type === "turn.interrupted") turns.delete(id);
8317
+ else if (record.type === "savepoint.created") savepoint = record;
8318
+ }
8319
+ const retained = [
8320
+ ...queues.values(),
8321
+ ...claims.values(),
8322
+ ...tools.values(),
8323
+ ...models.values(),
8324
+ ...turns.values(),
8325
+ ...savepoint === void 0 ? [] : [savepoint]
8326
+ ].sort((left, right) => left.sequence - right.sequence);
8327
+ const compacted = [];
8328
+ let previousHash = null;
8329
+ for (const source of retained) {
8330
+ const unsigned = {
8331
+ version: HARNESS_JOURNAL_VERSION,
8332
+ sequence: compacted.length + 1,
8333
+ id: randomUUID8(),
8334
+ timestamp: source.timestamp,
8335
+ type: source.type,
8336
+ payload: compactPayload(source),
8337
+ previousHash
8338
+ };
8339
+ const record = JournalRecordSchema.parse({ ...unsigned, hash: hashJson(unsigned) });
8340
+ compacted.push(record);
8341
+ previousHash = record.hash;
8342
+ }
8343
+ const contents = compacted.map((record) => `${JSON.stringify(record)}
8344
+ `).join("");
8345
+ if (Buffer.byteLength(contents) > this.#maxBytes) return;
8346
+ const temporary = `${this.#path}.compact-${process.pid}-${randomUUID8()}`;
8347
+ const fd = openSync(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
8240
8348
  try {
8241
- writeSync(fd, line, void 0, "utf8");
8349
+ writeSync(fd, contents, void 0, "utf8");
8242
8350
  fsyncSync(fd);
8243
8351
  } finally {
8244
8352
  closeSync(fd);
8245
8353
  }
8246
- this.#records.push(record);
8247
- this.#sequence = record.sequence;
8248
- this.#lastHash = record.hash;
8354
+ try {
8355
+ renameSync(temporary, this.#path);
8356
+ } catch (error) {
8357
+ if (existsSync2(temporary)) unlinkSync(temporary);
8358
+ throw error;
8359
+ }
8360
+ this.#records = compacted;
8361
+ this.#sequence = compacted.length;
8362
+ this.#lastHash = previousHash;
8249
8363
  }
8250
8364
  };
8365
+ function compactPayload(record) {
8366
+ const payload = record.payload;
8367
+ if (record.type === "queue.admitted") return payload;
8368
+ if (record.type === "queue.claimed") return { id: payload.id };
8369
+ if (record.type === "tool.started") return pick(payload, ["id", "tool", "inputHash", "retrySafe"]);
8370
+ if (record.type === "model.requested") return pick(payload, ["id", "agent", "model", "iteration", "attempt", "messagesHash"]);
8371
+ if (record.type === "turn.started") return pick(payload, ["id", "configHash", "promptHash"]);
8372
+ if (record.type === "savepoint.created") return pick(payload, ["id", "phase", "configHash"]);
8373
+ return payload;
8374
+ }
8375
+ function hasLegacyPayload(record) {
8376
+ if (record.type === "turn.started") return "config" in record.payload || "prompt" in record.payload;
8377
+ if (record.type === "model.requested") return "messages" in record.payload;
8378
+ if (record.type === "tool.started") return "input" in record.payload;
8379
+ if (record.type === "tool.completed") return "result" in record.payload;
8380
+ return false;
8381
+ }
8382
+ function pick(source, keys) {
8383
+ return Object.fromEntries(keys.flatMap((key) => key in source ? [[key, source[key]]] : []));
8384
+ }
8251
8385
  function jsonSafeRecord(value) {
8252
8386
  const json = JSON.stringify(value, (_key, item) => typeof item === "bigint" ? `${item}n` : item);
8253
8387
  return JSON.parse(json);
@@ -8280,6 +8414,7 @@ function isWithin2(root, target) {
8280
8414
 
8281
8415
  // src/hooks/bus.ts
8282
8416
  import { spawn as spawn4 } from "child_process";
8417
+ import { randomUUID as randomUUID9 } from "crypto";
8283
8418
 
8284
8419
  // src/hooks/types.ts
8285
8420
  import { z as z11 } from "zod";
@@ -8346,6 +8481,11 @@ var DEFAULT_MAX_OUTPUT_BYTES = 1048576;
8346
8481
  var HookBus = class {
8347
8482
  #handlers = /* @__PURE__ */ new Map();
8348
8483
  #payloadSchemas = /* @__PURE__ */ new Map();
8484
+ #options;
8485
+ #sequence = 0;
8486
+ constructor(options = {}) {
8487
+ this.#options = options;
8488
+ }
8349
8489
  registerPayloadSchema(type, schema) {
8350
8490
  const registrations = this.#payloadSchemas.get(type) ?? [];
8351
8491
  const registration = { schema };
@@ -8382,7 +8522,9 @@ var HookBus = class {
8382
8522
  async emit(rawEvent, externalSignal) {
8383
8523
  externalSignal?.throwIfAborted();
8384
8524
  let event = HookEventSchema.parse(rawEvent);
8385
- event = { ...event, payload: this.#validatePayload(event.type, event.payload) };
8525
+ const validatedPayload = this.#validatePayload(event.type, event.payload);
8526
+ const envelope = this.#eventEnvelope();
8527
+ event = { ...event, payload: decoratePayload(validatedPayload, envelope) };
8386
8528
  let aggregate2 = { decision: "allow" };
8387
8529
  for (const registered of this.#handlers.get(event.type) ?? []) {
8388
8530
  externalSignal?.throwIfAborted();
@@ -8397,8 +8539,8 @@ var HookBus = class {
8397
8539
  externalSignal?.throwIfAborted();
8398
8540
  if (decision.updatedInput !== void 0) {
8399
8541
  const payload = this.#validatePayload(event.type, decision.updatedInput);
8400
- event = HookEventSchema.parse({ ...event, payload });
8401
- aggregate2 = { ...aggregate2, updatedInput: event.payload };
8542
+ event = HookEventSchema.parse({ ...event, payload: decoratePayload(payload, envelope) });
8543
+ aggregate2 = { ...aggregate2, updatedInput: payload };
8402
8544
  }
8403
8545
  if (decision.additionalContext !== void 0) {
8404
8546
  aggregate2 = {
@@ -8415,6 +8557,15 @@ var HookBus = class {
8415
8557
  const schema = this.#payloadSchemas.get(type)?.at(-1)?.schema;
8416
8558
  return schema ? schema.parse(payload) : zodRecord(payload);
8417
8559
  }
8560
+ #eventEnvelope() {
8561
+ if (this.#options.eventContext === void 0) return void 0;
8562
+ return {
8563
+ ...this.#options.eventContext(),
8564
+ eventId: (this.#options.eventId ?? randomUUID9)(),
8565
+ sequence: ++this.#sequence,
8566
+ timestamp: (this.#options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))()
8567
+ };
8568
+ }
8418
8569
  async #invoke(registered, event, externalSignal) {
8419
8570
  const timeoutSignal = AbortSignal.timeout(registered.timeoutMs);
8420
8571
  const signal = externalSignal === void 0 ? timeoutSignal : AbortSignal.any([timeoutSignal, externalSignal]);
@@ -8426,6 +8577,9 @@ var HookBus = class {
8426
8577
  return HookDecisionSchema.parse(await Promise.race([invocation, timeout]));
8427
8578
  }
8428
8579
  };
8580
+ function decoratePayload(payload, envelope) {
8581
+ return envelope === void 0 ? payload : { ...payload, ...envelope };
8582
+ }
8429
8583
  function runShellHandler(descriptor, event, signal) {
8430
8584
  return new Promise((resolve28, reject) => {
8431
8585
  const child = spawn4(descriptor.command, [...descriptor.args ?? []], {
@@ -11303,7 +11457,7 @@ function formatPriorGaps(gaps) {
11303
11457
 
11304
11458
  // src/goal/store.ts
11305
11459
  import { constants as constants2 } from "fs";
11306
- import { randomUUID as randomUUID9 } from "crypto";
11460
+ import { randomUUID as randomUUID10 } from "crypto";
11307
11461
  import { lstat as lstat6, mkdir as mkdir11, open as open6, rename as rename4, rm as rm4 } from "fs/promises";
11308
11462
  import { dirname as dirname12, join as join14, relative as relative9, resolve as resolve11, sep as sep9 } from "path";
11309
11463
  var DEFAULT_MAX_BYTES = 1024 * 1024;
@@ -11327,7 +11481,7 @@ var GoalStore = class {
11327
11481
  if (Buffer.byteLength(body) > this.#maxBytes) {
11328
11482
  throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
11329
11483
  }
11330
- const temporary = join14(this.#root, `.${state.id}.${process.pid}.${randomUUID9()}.tmp`);
11484
+ const temporary = join14(this.#root, `.${state.id}.${process.pid}.${randomUUID10()}.tmp`);
11331
11485
  let handle;
11332
11486
  try {
11333
11487
  handle = await open6(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
@@ -11440,7 +11594,7 @@ async function ensureWorktreeDirectoryIgnored(root) {
11440
11594
  import { constants as constants3 } from "fs";
11441
11595
  import { lstat as lstat7, mkdir as mkdir13, open as open7, readFile as readFile11, realpath as realpath5, rename as rename5, rm as rm5, stat as stat5 } from "fs/promises";
11442
11596
  import { dirname as dirname13, join as join16, relative as relative10, resolve as resolve13, sep as sep10 } from "path";
11443
- import { randomUUID as randomUUID10 } from "crypto";
11597
+ import { randomUUID as randomUUID11 } from "crypto";
11444
11598
  var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
11445
11599
  var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
11446
11600
  var LoopStore = class {
@@ -11471,7 +11625,7 @@ var LoopStore = class {
11471
11625
  const body = `${JSON.stringify(state, null, 2)}
11472
11626
  `;
11473
11627
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
11474
- const temporary = join16(directory, `.state.${process.pid}.${randomUUID10()}.tmp`);
11628
+ const temporary = join16(directory, `.state.${process.pid}.${randomUUID11()}.tmp`);
11475
11629
  let handle;
11476
11630
  try {
11477
11631
  handle = await open7(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
@@ -11497,7 +11651,7 @@ var LoopStore = class {
11497
11651
  return state;
11498
11652
  } catch (error) {
11499
11653
  if (isCode4(error, "ENOENT")) throw new Error(`Loop "${loopId}" was not found`);
11500
- const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID10().slice(0, 8)}`;
11654
+ const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
11501
11655
  await rename5(target, quarantine).catch(() => void 0);
11502
11656
  throw new Error(`Loop "${loopId}" is corrupt and was quarantined: ${message(error)}`);
11503
11657
  }
@@ -14265,7 +14419,7 @@ function createSkillResourceTool(registry) {
14265
14419
  import { constants as constants5 } from "fs";
14266
14420
  import { lstat as lstat9, mkdir as mkdir14, open as open8, readFile as readFile16, readdir as readdir6, realpath as realpath7, rename as rename6, rm as rm6, stat as stat6 } from "fs/promises";
14267
14421
  import { basename as basename3, dirname as dirname15, join as join19, relative as relative13, resolve as resolve18, sep as sep13 } from "path";
14268
- import { randomUUID as randomUUID11 } from "crypto";
14422
+ import { randomUUID as randomUUID12 } from "crypto";
14269
14423
  import { z as z22 } from "zod";
14270
14424
  var SESSION_VERSION = 4;
14271
14425
  var DEFAULT_MAX_SESSION_BYTES = 5 * 1024 * 1024;
@@ -14423,7 +14577,7 @@ var SessionStore = class {
14423
14577
  const body = `${lines.join("\n")}
14424
14578
  `;
14425
14579
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
14426
- const temporary = join19(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID11()}.tmp`);
14580
+ const temporary = join19(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID12()}.tmp`);
14427
14581
  let handle;
14428
14582
  try {
14429
14583
  handle = await open8(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
@@ -14579,7 +14733,7 @@ var SessionStore = class {
14579
14733
  if (!isWithin5(canonicalRoot, canonicalSessions)) throw new Error("Session directory escapes the workspace");
14580
14734
  }
14581
14735
  async #quarantine(path) {
14582
- const target = `${path}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
14736
+ const target = `${path}.corrupt-${Date.now()}-${randomUUID12().slice(0, 8)}`;
14583
14737
  await rename6(path, target);
14584
14738
  return target;
14585
14739
  }
@@ -14799,7 +14953,7 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
14799
14953
 
14800
14954
  // src/sleep/organizer.ts
14801
14955
  import { constants as constants6 } from "fs";
14802
- import { randomUUID as randomUUID12 } from "crypto";
14956
+ import { randomUUID as randomUUID13 } from "crypto";
14803
14957
  import { lstat as lstat10, mkdir as mkdir15, open as open9, readdir as readdir7, realpath as realpath8, rename as rename7, rm as rm7 } from "fs/promises";
14804
14958
  import { isAbsolute as isAbsolute8, join as join20, relative as relative14, resolve as resolve19, sep as sep14 } from "path";
14805
14959
  import { z as z23 } from "zod";
@@ -14956,7 +15110,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
14956
15110
  return (await readdir7(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
14957
15111
  }
14958
15112
  async #atomicWrite(path, content) {
14959
- const temporary = join20(this.#sleepDirectory, `.${randomUUID12()}.tmp`);
15113
+ const temporary = join20(this.#sleepDirectory, `.${randomUUID13()}.tmp`);
14960
15114
  let handle;
14961
15115
  try {
14962
15116
  handle = await open9(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
@@ -15332,7 +15486,7 @@ function computeSessionStats(documents) {
15332
15486
  import { constants as constants7 } from "fs";
15333
15487
  import { mkdir as mkdir16, open as open10, readFile as readFile17, realpath as realpath9, rename as rename8, rm as rm8, stat as stat7 } from "fs/promises";
15334
15488
  import { dirname as dirname16, isAbsolute as isAbsolute9, relative as relative15, resolve as resolve20, sep as sep15 } from "path";
15335
- import { randomUUID as randomUUID13 } from "crypto";
15489
+ import { randomUUID as randomUUID14 } from "crypto";
15336
15490
  import { z as z24 } from "zod";
15337
15491
 
15338
15492
  // src/tools/file-diff.ts
@@ -15685,7 +15839,7 @@ function createPathGuard(workspace) {
15685
15839
  async function atomicWrite(path, content, signal, expectedVersion) {
15686
15840
  abortIfNeeded(signal);
15687
15841
  await mkdir16(dirname16(path), { recursive: true });
15688
- const temporary = `${path}.${randomUUID13()}.tmp`;
15842
+ const temporary = `${path}.${randomUUID14()}.tmp`;
15689
15843
  try {
15690
15844
  const handle = await open10(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
15691
15845
  try {
@@ -17082,18 +17236,40 @@ function hookAnswersFromUpdatedInput(updatedInput, questions) {
17082
17236
  var QuestionBridge = class {
17083
17237
  #pending;
17084
17238
  #onChange;
17085
- constructor(onChange) {
17239
+ #relay;
17240
+ #active = false;
17241
+ constructor(onChange, relay) {
17086
17242
  this.#onChange = onChange;
17243
+ this.#relay = relay;
17087
17244
  }
17088
17245
  get pending() {
17089
17246
  return this.#pending?.questions;
17090
17247
  }
17091
17248
  ask(questions, signal) {
17092
- if (this.#pending !== void 0) return Promise.reject(new Error("A question is already pending"));
17249
+ if (this.#active) return Promise.reject(new Error("A question is already pending"));
17093
17250
  if (signal.aborted) return Promise.reject(signal.reason);
17251
+ this.#active = true;
17252
+ if (this.#relay !== void 0) return this.#askRelayed(questions, signal);
17253
+ return this.#askLocally(questions, signal);
17254
+ }
17255
+ async #askRelayed(questions, signal) {
17256
+ try {
17257
+ const answers = await this.#relay(questions, signal);
17258
+ if (answers !== void 0) {
17259
+ this.#active = false;
17260
+ return answers;
17261
+ }
17262
+ return await this.#askLocally(questions, signal);
17263
+ } catch (error) {
17264
+ this.#active = false;
17265
+ throw error;
17266
+ }
17267
+ }
17268
+ #askLocally(questions, signal) {
17094
17269
  return new Promise((resolve28, reject) => {
17095
17270
  const onAbort = () => {
17096
17271
  this.#pending = void 0;
17272
+ this.#active = false;
17097
17273
  reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
17098
17274
  };
17099
17275
  signal.addEventListener("abort", onAbort, { once: true });
@@ -17102,12 +17278,14 @@ var QuestionBridge = class {
17102
17278
  resolve: (answers) => {
17103
17279
  signal.removeEventListener("abort", onAbort);
17104
17280
  this.#pending = void 0;
17281
+ this.#active = false;
17105
17282
  this.#onChange?.();
17106
17283
  resolve28(answers);
17107
17284
  },
17108
17285
  reject: (error) => {
17109
17286
  signal.removeEventListener("abort", onAbort);
17110
17287
  this.#pending = void 0;
17288
+ this.#active = false;
17111
17289
  this.#onChange?.();
17112
17290
  reject(error);
17113
17291
  }
@@ -17193,7 +17371,7 @@ function createTodoWriteTool() {
17193
17371
  }
17194
17372
 
17195
17373
  // src/tools/managed.ts
17196
- import { randomUUID as randomUUID14 } from "crypto";
17374
+ import { randomUUID as randomUUID15 } from "crypto";
17197
17375
  import { link as link2, lstat as lstat11, mkdir as mkdir17, readFile as readFile19, readdir as readdir8, unlink as unlink4, writeFile as writeFile10 } from "fs/promises";
17198
17376
  import { dirname as dirname18, join as join21, resolve as resolve22 } from "path";
17199
17377
  import { z as z31 } from "zod";
@@ -17480,7 +17658,7 @@ ${sourceUrl}`
17480
17658
  }
17481
17659
  async function writeExclusive(path, content) {
17482
17660
  await mkdir17(dirname18(path), { recursive: true });
17483
- const temporary = `${path}.${randomUUID14()}.tmp`;
17661
+ const temporary = `${path}.${randomUUID15()}.tmp`;
17484
17662
  try {
17485
17663
  await writeFile10(temporary, content, { encoding: "utf8", flag: "wx", mode: 384 });
17486
17664
  await link2(temporary, path);
@@ -19267,7 +19445,7 @@ function relativeDisplay(workspace, filePath) {
19267
19445
  }
19268
19446
 
19269
19447
  // src/jobs/registry.ts
19270
- import { randomUUID as randomUUID15 } from "crypto";
19448
+ import { randomUUID as randomUUID16 } from "crypto";
19271
19449
  var JobRegistry = class {
19272
19450
  #jobs = /* @__PURE__ */ new Map();
19273
19451
  #maxOutputChars;
@@ -19281,7 +19459,7 @@ var JobRegistry = class {
19281
19459
  }
19282
19460
  create(input) {
19283
19461
  this.#pruneCompleted();
19284
- const id = `job-${randomUUID15().slice(0, 12)}`;
19462
+ const id = `job-${randomUUID16().slice(0, 12)}`;
19285
19463
  const now = (/* @__PURE__ */ new Date()).toISOString();
19286
19464
  let settle;
19287
19465
  const done = new Promise((resolve28) => {
@@ -19407,7 +19585,7 @@ function snapshot2(job) {
19407
19585
  }
19408
19586
 
19409
19587
  // src/terminal/service.ts
19410
- import { randomUUID as randomUUID16 } from "crypto";
19588
+ import { randomUUID as randomUUID17 } from "crypto";
19411
19589
  import { createRequire as nodeCreateRequire } from "module";
19412
19590
  import { isAbsolute as isAbsolute11, relative as relative19, resolve as resolve25, sep as sep19 } from "path";
19413
19591
  var TerminalService = class {
@@ -19425,7 +19603,7 @@ var TerminalService = class {
19425
19603
  open(input) {
19426
19604
  const cwd = workspacePath(this.#root, input.cwd ?? ".");
19427
19605
  const shell = input.shell ?? defaultShell();
19428
- const id = `term-${randomUUID16().slice(0, 12)}`;
19606
+ const id = `term-${randomUUID17().slice(0, 12)}`;
19429
19607
  const pty = this.#factory(shell, input.args ?? [], {
19430
19608
  cwd,
19431
19609
  cols: input.columns ?? 100,
@@ -19651,7 +19829,7 @@ function palSocketAddress(options) {
19651
19829
  }
19652
19830
 
19653
19831
  // src/pals/client.ts
19654
- import { randomUUID as randomUUID17 } from "crypto";
19832
+ import { randomUUID as randomUUID18 } from "crypto";
19655
19833
  import { Socket as Socket3 } from "net";
19656
19834
  var DEFAULT_REQUEST_TIMEOUT_MS = 5e3;
19657
19835
  var DEFAULT_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -19727,7 +19905,7 @@ var PalClient = class {
19727
19905
  return this.starting;
19728
19906
  }
19729
19907
  async list() {
19730
- const data2 = await this.request({ version: 1, type: "list", requestId: randomUUID17() });
19908
+ const data2 = await this.request({ version: 1, type: "list", requestId: randomUUID18() });
19731
19909
  return PalPresenceListSchema.parse(data2);
19732
19910
  }
19733
19911
  async rename(alias) {
@@ -19740,38 +19918,38 @@ var PalClient = class {
19740
19918
  throw error;
19741
19919
  }
19742
19920
  }
19743
- async sendTask(target, message2, messageId = randomUUID17()) {
19744
- const data2 = await this.request({ version: 1, type: "task", requestId: randomUUID17(), messageId, target, goal: message2 });
19921
+ async sendTask(target, message2, messageId = randomUUID18()) {
19922
+ const data2 = await this.request({ version: 1, type: "task", requestId: randomUUID18(), messageId, target, goal: message2 });
19745
19923
  return DeliveryReceiptSchema.parse(data2);
19746
19924
  }
19747
- async sendChat(target, message2, messageId = randomUUID17()) {
19748
- const data2 = await this.request({ version: 1, type: "chat", requestId: randomUUID17(), messageId, target, message: message2 });
19925
+ async sendChat(target, message2, messageId = randomUUID18()) {
19926
+ const data2 = await this.request({ version: 1, type: "chat", requestId: randomUUID18(), messageId, target, message: message2 });
19749
19927
  return DeliveryReceiptSchema.parse(data2);
19750
19928
  }
19751
19929
  async startCoWork(input) {
19752
19930
  const data2 = await this.request({
19753
19931
  version: 1,
19754
19932
  type: "cowork-propose",
19755
- requestId: randomUUID17(),
19756
- coWorkId: input.coWorkId ?? randomUUID17(),
19933
+ requestId: randomUUID18(),
19934
+ coWorkId: input.coWorkId ?? randomUUID18(),
19757
19935
  goal: input.goal,
19758
19936
  participants: input.participants
19759
19937
  });
19760
19938
  return CoWorkSnapshotSchema.parse(data2);
19761
19939
  }
19762
19940
  async coWorkAction(action) {
19763
- const request = { version: 1, requestId: randomUUID17(), ...action };
19941
+ const request = { version: 1, requestId: randomUUID18(), ...action };
19764
19942
  return CoWorkSnapshotSchema.parse(await this.request(request));
19765
19943
  }
19766
19944
  async coWorkStatus(coWorkId) {
19767
- const data2 = await this.request({ version: 1, type: "cowork-get", requestId: randomUUID17(), coWorkId });
19945
+ const data2 = await this.request({ version: 1, type: "cowork-get", requestId: randomUUID18(), coWorkId });
19768
19946
  return CoWorkSnapshotSchema.parse(data2);
19769
19947
  }
19770
19948
  async integrateCoWork(input) {
19771
19949
  return this.coWorkAction({ type: "cowork-integration", ...input });
19772
19950
  }
19773
19951
  async cancelCoWork(coWorkId, reason) {
19774
- const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId: randomUUID17(), coWorkId, reason });
19952
+ const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId: randomUUID18(), coWorkId, reason });
19775
19953
  return CoWorkSnapshotSchema.parse(data2);
19776
19954
  }
19777
19955
  subscribe(listener) {
@@ -19783,7 +19961,7 @@ var PalClient = class {
19783
19961
  if (this.registered && this.socket !== void 0) {
19784
19962
  let disconnectTimer;
19785
19963
  await Promise.race([
19786
- this.request({ version: 1, type: "disconnect", requestId: randomUUID17() }).catch(() => void 0),
19964
+ this.request({ version: 1, type: "disconnect", requestId: randomUUID18() }).catch(() => void 0),
19787
19965
  new Promise((resolve28) => {
19788
19966
  disconnectTimer = setTimeout(resolve28, Math.min(this.requestTimeoutMs, 250));
19789
19967
  })
@@ -19874,7 +20052,7 @@ var PalClient = class {
19874
20052
  const data2 = await this.request({
19875
20053
  version: 1,
19876
20054
  type: "register",
19877
- requestId: randomUUID17(),
20055
+ requestId: randomUUID18(),
19878
20056
  authToken: this.authToken,
19879
20057
  presence: { version: 1, ...this.registration, connectedAt: now, lastSeenAt: now }
19880
20058
  });
@@ -19986,7 +20164,7 @@ var PalClient = class {
19986
20164
  if (!this.registered) return;
19987
20165
  if (this.heartbeatPending) return;
19988
20166
  this.heartbeatPending = true;
19989
- void this.request({ version: 1, type: "heartbeat", requestId: randomUUID17() }).catch(() => void 0).finally(() => {
20167
+ void this.request({ version: 1, type: "heartbeat", requestId: randomUUID18() }).catch(() => void 0).finally(() => {
19990
20168
  this.heartbeatPending = false;
19991
20169
  });
19992
20170
  }, this.heartbeatIntervalMs);
@@ -20273,7 +20451,7 @@ var ApprovalBridge = class {
20273
20451
  request(request, signal = new AbortController().signal) {
20274
20452
  if (this.#settle !== void 0) return Promise.resolve("deny");
20275
20453
  if (signal.aborted) return Promise.resolve("deny");
20276
- this.#pending = { id: randomUUID18(), ...request };
20454
+ this.#pending = { id: randomUUID19(), ...request };
20277
20455
  this.#onChange?.();
20278
20456
  return new Promise((resolvePromise) => {
20279
20457
  this.#settle = resolvePromise;
@@ -20392,7 +20570,14 @@ async function createProductionRuntime(options) {
20392
20570
  environment.OPENAI_API_KEY,
20393
20571
  environment.ANTHROPIC_API_KEY
20394
20572
  ].filter((value) => typeof value === "string" && value.length > 0);
20395
- const hooks = new HookBus();
20573
+ let hookSessionId;
20574
+ const hooks = new HookBus({
20575
+ eventContext: () => ({
20576
+ protocolVersion: 2,
20577
+ workspace,
20578
+ ...hookSessionId === void 0 ? {} : { sessionId: hookSessionId }
20579
+ })
20580
+ });
20396
20581
  hooks.on(
20397
20582
  "PostToolUseFailure",
20398
20583
  createIncidentReporter({
@@ -20412,8 +20597,7 @@ async function createProductionRuntime(options) {
20412
20597
  diagnostics.push(...permissionPolicy.diagnostics);
20413
20598
  const approvals = new ApprovalBridge(options.onApprovalChange);
20414
20599
  const resolveToolApproval = options.approvalPolicy === "deny" && options.rpcToolApprovals !== true ? () => "deny" : (request, signal) => approvals.request(request, signal);
20415
- const questions = new QuestionBridge(options.onApprovalChange);
20416
- const askUserQuestionHandler = async (qs, signal) => {
20600
+ const relayQuestions = async (qs, signal) => {
20417
20601
  if (options.approvalPolicy === "deny") throw new Error("AskUserQuestion is not available in non-interactive mode");
20418
20602
  if (hooks.hasListeners("PermissionRequest")) {
20419
20603
  let decision;
@@ -20431,8 +20615,10 @@ async function createProductionRuntime(options) {
20431
20615
  const hookAnswers = hookAnswersFromUpdatedInput(decision.updatedInput, qs);
20432
20616
  if (hookAnswers !== void 0) return hookAnswers;
20433
20617
  }
20434
- return questions.ask(qs, signal);
20618
+ return void 0;
20435
20619
  };
20620
+ const questions = new QuestionBridge(options.onApprovalChange, relayQuestions);
20621
+ const askUserQuestionHandler = (qs, signal) => questions.ask(qs, signal);
20436
20622
  const executionEnvironment = createExecutionEnvironment(workspace, config.execution);
20437
20623
  const jobs = new JobRegistry();
20438
20624
  const terminals = new TerminalService(workspace, { jobs });
@@ -20756,7 +20942,8 @@ async function createProductionRuntime(options) {
20756
20942
  let taskResults = { ...recovered?.tasks.results ?? {} };
20757
20943
  const subagentStartedAt = {};
20758
20944
  const subagentElapsedMs = {};
20759
- let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`;
20945
+ let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID19().slice(0, 8)}`;
20946
+ hookSessionId = sessionId;
20760
20947
  let harnessJournal = new HarnessJournal({ workspace, sessionId });
20761
20948
  const harnessRecovery = harnessJournal.recover();
20762
20949
  if (harnessRecovery.queue.length > 0 || harnessRecovery.incompleteTools.length > 0 || harnessRecovery.incompleteModelIds.length > 0 || harnessRecovery.interruptedTurnIds.length > 0) {
@@ -20840,7 +21027,13 @@ async function createProductionRuntime(options) {
20840
21027
  const publishTaskState = async () => {
20841
21028
  harness.main.context.updateTaskState(serializedTaskState());
20842
21029
  await persist();
20843
- emitOutput({ type: "tasks", snapshot: taskSnapshot() });
21030
+ const snapshot4 = taskSnapshot();
21031
+ emitOutput({ type: "tasks", snapshot: snapshot4 });
21032
+ await hooks.emit({
21033
+ version: 1,
21034
+ type: "Notification",
21035
+ payload: { kind: "task_snapshot", taskSnapshot: snapshot4 }
21036
+ }).catch(() => void 0);
20844
21037
  };
20845
21038
  for (const tool of createTaskPlanTools({
20846
21039
  getPlan: () => taskPlan,
@@ -21398,7 +21591,7 @@ async function createProductionRuntime(options) {
21398
21591
  return answers[0] === "Continue" ? "approved" : "rejected";
21399
21592
  },
21400
21593
  fingerprint: workspaceFingerprint,
21401
- idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`
21594
+ idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID19().slice(0, 8)}`
21402
21595
  });
21403
21596
  const goalStore = new GoalStore({ workspace });
21404
21597
  const goalOrchestrator = new GoalOrchestrator({
@@ -21417,7 +21610,7 @@ async function createProductionRuntime(options) {
21417
21610
  executionEnvironment
21418
21611
  ),
21419
21612
  now: () => (/* @__PURE__ */ new Date()).toISOString(),
21420
- idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`,
21613
+ idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID19().slice(0, 8)}`,
21421
21614
  runWorker: ({ workspace: goalWorkspace, prompt, signal }) => runLoopWorker({ workspace: goalWorkspace, prompt, signal })
21422
21615
  });
21423
21616
  let sessionHistory = await SessionHistory.open({
@@ -21530,7 +21723,7 @@ async function createProductionRuntime(options) {
21530
21723
  runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
21531
21724
  persist
21532
21725
  ),
21533
- runLoop: (goal, signal) => runLoopSession(loopOrchestrator, goal, signal),
21726
+ runLoop: (goal, signal) => runLoopSession(loopOrchestrator, hooks, goal, signal),
21534
21727
  runGoal: (goal, signal) => persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
21535
21728
  mcp: async (command, signal) => {
21536
21729
  signal.throwIfAborted();
@@ -21688,7 +21881,8 @@ ${summary2}`;
21688
21881
  for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
21689
21882
  for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
21690
21883
  const previousIdeSessionId = ideSessionId;
21691
- sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`;
21884
+ sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID19().slice(0, 8)}`;
21885
+ hookSessionId = sessionId;
21692
21886
  harnessJournal = new HarnessJournal({ workspace, sessionId });
21693
21887
  setUsageSession(sessionId);
21694
21888
  ideSessionId = sessionId;
@@ -21987,9 +22181,10 @@ function formatMcpReconnect(server) {
21987
22181
  if (server.status === "disabled") return `MCP server "${server.name}" is disabled. Enable it before reconnecting.`;
21988
22182
  return `Failed to reconnect MCP server "${server.name}": ${server.error ?? "unknown error"}`;
21989
22183
  }
21990
- async function* runLoopSession(orchestrator, goal, signal) {
22184
+ async function* runLoopSession(orchestrator, hooks, goal, signal) {
21991
22185
  let totalInputTokens = 0;
21992
22186
  let totalOutputTokens = 0;
22187
+ let latestVerification;
21993
22188
  for await (const event of orchestrator.run({ goal, signal })) {
21994
22189
  if (event.type === "worker-event") {
21995
22190
  if (event.event.type === "usage") {
@@ -22003,8 +22198,19 @@ async function* runLoopSession(orchestrator, goal, signal) {
22003
22198
  } else if (event.event.type !== "done") yield event.event;
22004
22199
  continue;
22005
22200
  }
22201
+ if (event.type === "loop-verification") latestVerification = event.evidence;
22006
22202
  yield loopProgressEvent(event);
22007
22203
  if (event.type === "loop-terminal") {
22204
+ await hooks.emit({
22205
+ version: 1,
22206
+ type: "LoopEnd",
22207
+ payload: {
22208
+ loopId: event.loopId,
22209
+ outcome: event.status,
22210
+ reason: event.reason,
22211
+ verification: latestVerification ?? null
22212
+ }
22213
+ }).catch(() => void 0);
22008
22214
  yield { type: "done", usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens } };
22009
22215
  }
22010
22216
  }
@@ -22718,7 +22924,7 @@ function memoryTranscriptHash(messages) {
22718
22924
  return createHash17("sha256").update(visible, "utf8").digest("hex");
22719
22925
  }
22720
22926
  function createMemoryTaskId() {
22721
- return `memory-${randomUUID18()}`;
22927
+ return `memory-${randomUUID19()}`;
22722
22928
  }
22723
22929
 
22724
22930
  export {