@oxagen/cli 2.1.5-63 → 2.1.5-73

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.
Files changed (2) hide show
  1. package/oxagen.mjs +1168 -386
  2. package/package.json +1 -1
package/oxagen.mjs CHANGED
@@ -3589,15 +3589,15 @@ var init_types = __esm({
3589
3589
  }
3590
3590
  const valueType = this._def.valueType;
3591
3591
  function finalizeSet(elements2) {
3592
- const parsedSet = /* @__PURE__ */ new Set();
3592
+ const parsedSet2 = /* @__PURE__ */ new Set();
3593
3593
  for (const element of elements2) {
3594
3594
  if (element.status === "aborted")
3595
3595
  return INVALID;
3596
3596
  if (element.status === "dirty")
3597
3597
  status2.dirty();
3598
- parsedSet.add(element.value);
3598
+ parsedSet2.add(element.value);
3599
3599
  }
3600
- return { status: status2.value, value: parsedSet };
3600
+ return { status: status2.value, value: parsedSet2 };
3601
3601
  }
3602
3602
  const elements = [...ctx.data.values()].map((item, i2) => valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i2)));
3603
3603
  if (ctx.common.async) {
@@ -5656,7 +5656,7 @@ function bundleSkillChars(skill) {
5656
5656
  chars += file.path.length + file.content.length;
5657
5657
  return chars;
5658
5658
  }
5659
- var protocolTimestampSchema, machineGroupSchema, TACHO_BATCH_SCHEMA, TACHO_BUNDLE_SCHEMA, TACHO_COMMANDS_SCHEMA, TACHO_ENROLLMENT_CLAIMS_SCHEMA, TACHO_ENFORCEMENT_TIER_ATTR, TACHO_GATEWAY_TIER, TACHO_GATEWAY_SESSION_HEADER, TACHO_GATEWAY_GENESIS_HEADER, REFUSAL_META_KEY, MCP_STREAMABLE_HTTP_ACCEPT, TACHO_METERING_ATTR, TACHO_METERING_OBSERVED, TACHO_MODEL_SESSION_HEADER, TACHO_CREDENTIAL_BASIS_ATTR, TACHO_CREDENTIAL_GATEWAY_BROKERED, TACHO_CREDENTIAL_HARNESS_HELD, TACHO_CREDENTIAL_BASES, TACHO_RUN_TOKEN_ATTR, BUNDLE_FEATURE_GATEWAY_TOOLS, BUNDLE_FEATURE_MODEL_PRICES, BUNDLE_FEATURE_MODEL_ALLOWLIST, BUNDLE_FEATURE_INDEPENDENT_MODELS, BUNDLE_FEATURE_HOOK_FAIL_OPEN, BUNDLE_FEATURE_STEERING_MANIFEST, BUNDLE_FEATURE_STEERING_INCOMPLETE, BUNDLE_FEATURE_CONTAINMENT, BUNDLE_FEATURE_DAILY_BUDGET, BUNDLE_FEATURE_STEER_NEXT_STEP, BUNDLE_FEATURE_UNBOUND_REPO, BUNDLE_FEATURE_CEDAR, BUNDLE_FEATURE_CEDAR_TOOLBELT, BUNDLE_FEATURE_SKILLS, BUNDLE_FEATURE_CACHE_KEEP_ALIVE, BUNDLE_FEATURE_WORK_ORDERS, BUNDLE_FEATURE_WORK_ORDER_START, BUNDLE_FEATURE_GATEWAY_POLICY, GATEWAY_MIN_TIERS, SENSITIVE_SCREEN_ACTIONS, PROMPT_REDACTION_PATTERNS_MAX, PROMPT_REDACTION_PATTERN_MAX, gatewayPolicySchema, TACHO_BUNDLE_FEATURES, bundleFeaturesSchema, hostEnrollmentIdSchema, tachoPlatformSchema, tachoHarnessSchema, TACHO_HARNESS_LABELS, TACHO_HARNESS_TIERS, WRAPPED_HARNESSES, CONNECTED_HARNESSES, HARNESS_BINARY, MODEL_HARNESS_ROUTES, MODEL_ROUTED_HARNESSES, BROKERABLE_HARNESSES, BROKERABLE_HARNESS_PROVIDER, TACHO_TIER_SUMMARY, CUSTOM_AGENT_NAME_PATTERN, RESERVED_AGENT_NAMES, tachoHostStatusSchema, tachoBundleModeSchema, tachoCommandSchema, tachoCommandStatusSchema, tachoCommandAckStatusSchema, tachoDeliveryModeSchema, denyGenerationSchema, deliveredCommandSchema, deliveredCommandResponseSchema, WORK_ORDER_ID_PATTERN, WORK_ITEM_ID_PATTERN, workOrderCommandPayloadSchema, WORK_ORDER_CLAIM_PATH, WORK_ORDER_REJECT_PATH, workOrderClaimResponseSchema, workOrderRejectResponseSchema, WORK_CRITERION_CLAIM_PATH, WORK_CRITERION_ID_PATTERN, WORK_HEAD_SHA_PATTERN, WORK_CRITERION_CLAIM_TEXT_MAX, workCriterionClaimResponseSchema, WORK_ORDER_ENV, WORK_ORDER_ATTR, steeringForceSchema, steeringItemKindSchema, steeringCutReasonSchema, steeringManifestItemSchema, STEERING_MANIFEST_SCHEMA, steeringManifestSchema, steeringManifestFrameSchema, cedarIdSchema, cedarPrincipalSchema, cedarToolSchema, cedarBundleSchema, BUNDLE_SKILLS_MAX, BUNDLE_SKILL_FILES_MAX, BUNDLE_SKILLS_CHARS_MAX, BASE64_PATTERN, bundleSkillFileSchema, bundleSkillSchema, policyBundleSchema, enrollmentClaimsSchema, tachoEventWireSchema, daemonHealthSchema, TACHO_MAX_BATCH, TACHO_MAX_BODY_BYTES, TACHO_MAX_REQUEST_BYTES, TACHO_REQUEST_ENVELOPE_BYTES, tachoBodySchema, tachoBatchSchema, agentDaySpendSchema, controlEnvelopeSchema, ingestResponseSchema, bundleResponseSchema, commandAcknowledgementSchema, commandsResponseSchema, enrollmentResponseSchema, tokenEnrollmentResponseSchema;
5659
+ var protocolTimestampSchema, machineGroupSchema, TACHO_BATCH_SCHEMA, TACHO_BUNDLE_SCHEMA, TACHO_COMMANDS_SCHEMA, TACHO_ENROLLMENT_CLAIMS_SCHEMA, TACHO_ENFORCEMENT_TIER_ATTR, TACHO_GATEWAY_TIER, TACHO_GATEWAY_SESSION_HEADER, TACHO_GATEWAY_GENESIS_HEADER, REFUSAL_META_KEY, MCP_STREAMABLE_HTTP_ACCEPT, TACHO_METERING_ATTR, TACHO_METERING_OBSERVED, TACHO_MODEL_SESSION_HEADER, TACHO_CREDENTIAL_BASIS_ATTR, TACHO_CREDENTIAL_GATEWAY_BROKERED, TACHO_CREDENTIAL_HARNESS_HELD, TACHO_CREDENTIAL_BASES, TACHO_RUN_TOKEN_ATTR, BUNDLE_FEATURE_GATEWAY_TOOLS, BUNDLE_FEATURE_MODEL_PRICES, BUNDLE_FEATURE_MODEL_ALLOWLIST, BUNDLE_FEATURE_INDEPENDENT_MODELS, BUNDLE_FEATURE_HOOK_FAIL_OPEN, BUNDLE_FEATURE_STEERING_MANIFEST, BUNDLE_FEATURE_STEERING_INCOMPLETE, BUNDLE_FEATURE_CONTAINMENT, BUNDLE_FEATURE_DAILY_BUDGET, BUNDLE_FEATURE_STEER_NEXT_STEP, BUNDLE_FEATURE_UNBOUND_REPO, BUNDLE_FEATURE_CEDAR, BUNDLE_FEATURE_CEDAR_TOOLBELT, BUNDLE_FEATURE_SKILLS, BUNDLE_FEATURE_CACHE_KEEP_ALIVE, BUNDLE_FEATURE_WORK_ORDERS, BUNDLE_FEATURE_WORK_ORDER_START, BUNDLE_FEATURE_MCP_RULE_SETS, BUNDLE_FEATURE_GATEWAY_POLICY, GATEWAY_MIN_TIERS, SENSITIVE_SCREEN_ACTIONS, PROMPT_REDACTION_PATTERNS_MAX, PROMPT_REDACTION_PATTERN_MAX, gatewayPolicySchema, TACHO_BUNDLE_FEATURES, bundleFeaturesSchema, hostEnrollmentIdSchema, tachoPlatformSchema, tachoHarnessSchema, TACHO_HARNESS_LABELS, TACHO_HARNESS_TIERS, WRAPPED_HARNESSES, CONNECTED_HARNESSES, HARNESS_BINARY, MODEL_HARNESS_ROUTES, MODEL_ROUTED_HARNESSES, BROKERABLE_HARNESSES, BROKERABLE_HARNESS_PROVIDER, TACHO_TIER_SUMMARY, CUSTOM_AGENT_NAME_PATTERN, RESERVED_AGENT_NAMES, tachoHostStatusSchema, tachoBundleModeSchema, tachoCommandSchema, tachoCommandStatusSchema, tachoCommandAckStatusSchema, tachoDeliveryModeSchema, denyGenerationSchema, deliveredCommandSchema, deliveredCommandResponseSchema, WORK_ORDER_ID_PATTERN, WORK_ITEM_ID_PATTERN, workOrderCommandPayloadSchema, WORK_ORDER_CLAIM_PATH, WORK_ORDER_REJECT_PATH, workOrderClaimResponseSchema, workOrderRejectResponseSchema, WORK_CRITERION_CLAIM_PATH, WORK_CRITERION_ID_PATTERN, WORK_HEAD_SHA_PATTERN, WORK_CRITERION_CLAIM_TEXT_MAX, workCriterionClaimResponseSchema, WORK_ORDER_ENV, WORK_ORDER_ATTR, steeringForceSchema, steeringItemKindSchema, steeringCutReasonSchema, steeringManifestItemSchema, STEERING_MANIFEST_SCHEMA, steeringManifestSchema, steeringManifestFrameSchema, cedarIdSchema, cedarPrincipalSchema, cedarToolSchema, cedarBundleSchema, BUNDLE_SKILLS_MAX, BUNDLE_SKILL_FILES_MAX, BUNDLE_SKILLS_CHARS_MAX, BASE64_PATTERN, bundleSkillFileSchema, bundleSkillSchema, policyBundleSchema, enrollmentClaimsSchema, tachoEventWireSchema, daemonHealthSchema, TACHO_MAX_BATCH, TACHO_MAX_BODY_BYTES, TACHO_MAX_REQUEST_BYTES, TACHO_REQUEST_ENVELOPE_BYTES, tachoBodySchema, tachoBatchSchema, agentDaySpendSchema, controlEnvelopeSchema, ingestResponseSchema, bundleResponseSchema, commandAcknowledgementSchema, commandsResponseSchema, enrollmentResponseSchema, tokenEnrollmentResponseSchema;
5660
5660
  var init_wire = __esm({
5661
5661
  "../../packages/tacho/src/wire.ts"() {
5662
5662
  "use strict";
@@ -5709,6 +5709,7 @@ var init_wire = __esm({
5709
5709
  BUNDLE_FEATURE_CACHE_KEEP_ALIVE = "cache_keep_alive";
5710
5710
  BUNDLE_FEATURE_WORK_ORDERS = "work_orders";
5711
5711
  BUNDLE_FEATURE_WORK_ORDER_START = "work_order_start";
5712
+ BUNDLE_FEATURE_MCP_RULE_SETS = "mcp_rule_sets";
5712
5713
  BUNDLE_FEATURE_GATEWAY_POLICY = "gateway_policy";
5713
5714
  GATEWAY_MIN_TIERS = ["harness", "gateway", "contained"];
5714
5715
  SENSITIVE_SCREEN_ACTIONS = ["flag", "strip", "reject"];
@@ -5755,6 +5756,7 @@ var init_wire = __esm({
5755
5756
  BUNDLE_FEATURE_WORK_ORDERS,
5756
5757
  BUNDLE_FEATURE_WORK_ORDER_START,
5757
5758
  BUNDLE_FEATURE_STEERING_INCOMPLETE,
5759
+ BUNDLE_FEATURE_MCP_RULE_SETS,
5758
5760
  BUNDLE_FEATURE_GATEWAY_POLICY
5759
5761
  ];
5760
5762
  bundleFeaturesSchema = external_exports.array(external_exports.string().max(64)).max(32);
@@ -6072,6 +6074,25 @@ var init_wire = __esm({
6072
6074
  deny: external_exports.array(external_exports.string().max(512)).max(1024),
6073
6075
  ask: external_exports.array(external_exports.string().max(512)).max(1024)
6074
6076
  }).strict(),
6077
+ /**
6078
+ * The agent's MCP tool rules as the app holds them (#5398): one list per
6079
+ * rule set, each rule in its set's order. The host takes the first rule
6080
+ * that matches in each set and the most restrictive answer across the
6081
+ * sets (deny, then ask, then allow), and a set with no matching rule says
6082
+ * nothing. These rules are not also in `permissions`, whose fixed
6083
+ * deny-before-allow order would undo theirs.
6084
+ *
6085
+ * Emitted only to a host that advertised `BUNDLE_FEATURE_MCP_RULE_SETS`.
6086
+ * When it is absent, the same rules are sorted into `permissions`.
6087
+ */
6088
+ mcp_rule_sets: external_exports.array(
6089
+ external_exports.array(
6090
+ external_exports.object({
6091
+ rule: external_exports.string().max(512),
6092
+ effect: external_exports.enum(["allow", "deny", "ask"])
6093
+ }).strict()
6094
+ ).max(1024)
6095
+ ).max(256).optional(),
6075
6096
  tools: external_exports.record(
6076
6097
  external_exports.string().max(256),
6077
6098
  external_exports.object({
@@ -8121,12 +8142,12 @@ function cacheKeepAliveFinding(bundle2) {
8121
8142
  }
8122
8143
  function parseRule(raw) {
8123
8144
  const rule = raw.trim();
8124
- const open6 = rule.indexOf("(");
8125
- if (open6 === -1 || !rule.endsWith(")")) return { raw, tool: rule };
8145
+ const open7 = rule.indexOf("(");
8146
+ if (open7 === -1 || !rule.endsWith(")")) return { raw, tool: rule };
8126
8147
  return {
8127
8148
  raw,
8128
- tool: rule.slice(0, open6).trim(),
8129
- spec: rule.slice(open6 + 1, -1)
8149
+ tool: rule.slice(0, open7).trim(),
8150
+ spec: rule.slice(open7 + 1, -1)
8130
8151
  };
8131
8152
  }
8132
8153
  function escapeRegex(value) {
@@ -8427,6 +8448,29 @@ function firstMatch(rules, call2, context, effect) {
8427
8448
  }
8428
8449
  return void 0;
8429
8450
  }
8451
+ function parsedSet(set) {
8452
+ const cached3 = parsedSetCache.get(set);
8453
+ if (cached3 !== void 0 && cached3.length === set.length && cached3.every((rule, index) => rule.raw === set[index]?.rule))
8454
+ return cached3;
8455
+ const parsed2 = set.map((entry2) => parseRule(entry2.rule));
8456
+ parsedSetCache.set(set, parsed2);
8457
+ return parsed2;
8458
+ }
8459
+ function ruleSetsMatch(sets, call2, context) {
8460
+ if (sets === void 0) return void 0;
8461
+ let result2;
8462
+ for (const set of sets) {
8463
+ const parsed2 = parsedSet(set);
8464
+ for (const [index, rule] of parsed2.entries()) {
8465
+ const effect = set[index].effect;
8466
+ if (!callMatches(rule, call2, context ?? {}, effect)) continue;
8467
+ if (result2 === void 0 || EFFECT_RANK[effect] > EFFECT_RANK[result2.effect])
8468
+ result2 = { effect, rule: rule.raw };
8469
+ break;
8470
+ }
8471
+ }
8472
+ return result2;
8473
+ }
8430
8474
  function allowMatch(rules, call2, context) {
8431
8475
  const single = firstMatch(rules, call2, context, "allow");
8432
8476
  if (single !== void 0) return [single];
@@ -8559,12 +8603,8 @@ function evaluatePreToolUse(input) {
8559
8603
  };
8560
8604
  }
8561
8605
  const call2 = toolCallOf(toolName3, toolInput);
8562
- const denied = firstMatch(
8563
- bundle2.permissions.deny,
8564
- call2,
8565
- input.context,
8566
- "deny"
8567
- );
8606
+ const ordered = ruleSetsMatch(bundle2.mcp_rule_sets, call2, input.context);
8607
+ const denied = firstMatch(bundle2.permissions.deny, call2, input.context, "deny") ?? (ordered?.effect === "deny" ? ordered.rule : void 0);
8568
8608
  if (denied !== void 0) {
8569
8609
  return {
8570
8610
  decision: bundle2.mode === "observe" ? "allow" : "deny",
@@ -8588,7 +8628,7 @@ function evaluatePreToolUse(input) {
8588
8628
  stale
8589
8629
  };
8590
8630
  }
8591
- const ask = firstMatch(bundle2.permissions.ask, call2, input.context, "ask");
8631
+ const ask = firstMatch(bundle2.permissions.ask, call2, input.context, "ask") ?? (ordered?.effect === "ask" ? ordered.rule : void 0);
8592
8632
  if (ask !== void 0) {
8593
8633
  return {
8594
8634
  decision: bundle2.mode === "observe" ? "allow" : "ask",
@@ -8603,7 +8643,7 @@ function evaluatePreToolUse(input) {
8603
8643
  stale
8604
8644
  };
8605
8645
  }
8606
- const allowed = allowMatch(bundle2.permissions.allow, call2, input.context);
8646
+ const allowed = allowMatch(bundle2.permissions.allow, call2, input.context) ?? (ordered?.effect === "allow" ? [ordered.rule] : void 0);
8607
8647
  if (allowed !== void 0) {
8608
8648
  const rule = allowed.join(" and ");
8609
8649
  return {
@@ -8753,7 +8793,7 @@ function decideWithCedar(input) {
8753
8793
  };
8754
8794
  }
8755
8795
  }
8756
- var SHELL_OPERATORS, SHELL_LEADING_WORDS, WINDOWS_PATH, SHELL_TOOLS2, DECLARATION_SYNONYMS, GLOB_CACHE_LIMIT, globCache, parsedRuleCache, WRITE_EFFECTS;
8796
+ var SHELL_OPERATORS, SHELL_LEADING_WORDS, WINDOWS_PATH, SHELL_TOOLS2, DECLARATION_SYNONYMS, GLOB_CACHE_LIMIT, globCache, parsedRuleCache, EFFECT_RANK, parsedSetCache, WRITE_EFFECTS;
8757
8797
  var init_bundle = __esm({
8758
8798
  "../../packages/tacho/src/host/bundle.ts"() {
8759
8799
  "use strict";
@@ -8795,6 +8835,8 @@ var init_bundle = __esm({
8795
8835
  GLOB_CACHE_LIMIT = 1024;
8796
8836
  globCache = /* @__PURE__ */ new Map();
8797
8837
  parsedRuleCache = /* @__PURE__ */ new WeakMap();
8838
+ EFFECT_RANK = { allow: 0, ask: 1, deny: 2 };
8839
+ parsedSetCache = /* @__PURE__ */ new WeakMap();
8798
8840
  WRITE_EFFECTS = /* @__PURE__ */ new Set([
8799
8841
  "file_write",
8800
8842
  "file_edit",
@@ -8853,6 +8895,13 @@ function withRecordedHarnessFiles(paths, host) {
8853
8895
  claudeDesktopConfig: record.claude_desktop_config !== void 0 ? record.claude_desktop_config ?? void 0 : paths.claudeDesktopConfig
8854
8896
  };
8855
8897
  }
8898
+ function recordedHarnessHomes(host) {
8899
+ const record = host?.harness_files;
8900
+ return {
8901
+ ...record?.claude_settings !== void 0 ? { claudeConfigDir: dirname2(record.claude_settings) } : {},
8902
+ ...record?.codex_hooks !== void 0 ? { codexHome: dirname2(record.codex_hooks) } : {}
8903
+ };
8904
+ }
8856
8905
  function mcpEndpointOverrideRequestFrom(env) {
8857
8906
  const raw = env["TACHO_MCP_ENDPOINT"];
8858
8907
  if (typeof raw !== "string" || raw.length === 0)
@@ -9578,7 +9627,7 @@ var init_settings_writer = __esm({
9578
9627
  "Stop"
9579
9628
  ];
9580
9629
  STEER_WAIT_FLAG = "--await-steer";
9581
- STEER_WAIT_TIMEOUT_S = 86400;
9630
+ STEER_WAIT_TIMEOUT_S = 1814400;
9582
9631
  COMMAND_HOOK_TIMEOUTS_S = {
9583
9632
  SessionStart: 10,
9584
9633
  UserPromptSubmit: 10,
@@ -10917,12 +10966,12 @@ function procStartTicks(stat7) {
10917
10966
  return ticks !== void 0 && /^\d+$/.test(ticks) ? ticks : void 0;
10918
10967
  }
10919
10968
  function procParent(stat7) {
10920
- const open6 = stat7.indexOf("(");
10969
+ const open7 = stat7.indexOf("(");
10921
10970
  const close = stat7.lastIndexOf(")");
10922
- if (open6 < 0 || close < open6) return void 0;
10971
+ if (open7 < 0 || close < open7) return void 0;
10923
10972
  const ppid = stat7.slice(close + 1).trim().split(/\s+/)[1];
10924
10973
  if (ppid === void 0 || !/^\d+$/.test(ppid)) return void 0;
10925
- return { ppid: Number(ppid), comm: stat7.slice(open6 + 1, close) };
10974
+ return { ppid: Number(ppid), comm: stat7.slice(open7 + 1, close) };
10926
10975
  }
10927
10976
  function readProcParent(pid, read2 = readText) {
10928
10977
  const stat7 = read2(`/proc/${pid}/stat`);
@@ -23229,7 +23278,7 @@ function openCredentialStore(paths) {
23229
23278
  entriesSeen = { version: version2, entries: parsed2.data.entries };
23230
23279
  return parsed2.data.entries;
23231
23280
  }
23232
- function writeAll2(entries2) {
23281
+ function writeAll3(entries2) {
23233
23282
  if (entries2.length === 0) {
23234
23283
  if (existsSync5(paths.file)) unlinkSync4(paths.file);
23235
23284
  return;
@@ -23273,7 +23322,7 @@ function openCredentialStore(paths) {
23273
23322
  prefix: kind === "chatgpt" ? "login" : credentialPrefix(secret),
23274
23323
  sealed: seal(keyForWrite(), secret, sealingContext(provider, owner))
23275
23324
  };
23276
- writeAll2([
23325
+ writeAll3([
23277
23326
  ...readAll().filter((e) => !matches(e, provider, owner)),
23278
23327
  entry2
23279
23328
  ]);
@@ -23289,7 +23338,7 @@ function openCredentialStore(paths) {
23289
23338
  entry2.sealed,
23290
23339
  sealingContext(provider, owner)
23291
23340
  );
23292
- writeAll2(entries2.filter((e) => !matches(e, provider, owner)));
23341
+ writeAll3(entries2.filter((e) => !matches(e, provider, owner)));
23293
23342
  return { kind: entry2.kind, secret };
23294
23343
  },
23295
23344
  status: () => readAll().map(custodyOf),
@@ -26623,7 +26672,7 @@ import { readFileSync as readFileSync10 } from "node:fs";
26623
26672
  import { dirname as dirname9, resolve as resolve3 } from "node:path";
26624
26673
  import { fileURLToPath } from "node:url";
26625
26674
  function readVersion() {
26626
- if (true) return "2.1.5-63";
26675
+ if (true) return "2.1.5-73";
26627
26676
  try {
26628
26677
  const here = dirname9(fileURLToPath(import.meta.url));
26629
26678
  const pkg = JSON.parse(
@@ -28176,6 +28225,73 @@ function readTailLine(path2, size = statSync5(path2).size, window2 = SCAN_CHUNK_
28176
28225
  closeSync5(fd2);
28177
28226
  }
28178
28227
  }
28228
+ function* readLinesBackward(path2, size = statSync5(path2).size, window2 = SCAN_CHUNK_BYTES) {
28229
+ if (size === 0) return;
28230
+ const fd2 = openSync5(path2, "r");
28231
+ try {
28232
+ let buffer = Buffer.alloc(0);
28233
+ let start = size;
28234
+ let chunk2 = Math.max(1, window2);
28235
+ const largest = Math.max(chunk2, SCAN_CHUNK_BYTES);
28236
+ let cut = false;
28237
+ const readEarlier = (end2) => {
28238
+ if (start === 0) return false;
28239
+ const from = Math.max(0, start - chunk2);
28240
+ const length = start - from;
28241
+ const earlier = Buffer.allocUnsafe(length);
28242
+ let filled = 0;
28243
+ while (filled < length) {
28244
+ const read2 = readSync2(
28245
+ fd2,
28246
+ earlier,
28247
+ filled,
28248
+ length - filled,
28249
+ from + filled
28250
+ );
28251
+ if (read2 <= 0) break;
28252
+ filled += read2;
28253
+ }
28254
+ if (filled < length) {
28255
+ cut = true;
28256
+ return false;
28257
+ }
28258
+ buffer = Buffer.concat([earlier, buffer.subarray(0, end2 - start)]);
28259
+ start = from;
28260
+ chunk2 = Math.min(chunk2 * 2, largest);
28261
+ return true;
28262
+ };
28263
+ let end = size;
28264
+ while (end > 0) {
28265
+ if (end - 1 < start && !readEarlier(end)) return;
28266
+ let terminated = false;
28267
+ let lineEnd = end;
28268
+ if (buffer[end - 1 - start] === 10) {
28269
+ terminated = true;
28270
+ lineEnd = end - 1;
28271
+ }
28272
+ if (lineEnd === 0) return;
28273
+ let offset = 0;
28274
+ for (; ; ) {
28275
+ const at2 = lineEnd > start ? buffer.lastIndexOf(10, lineEnd - start - 1) : -1;
28276
+ if (at2 !== -1) {
28277
+ offset = start + at2 + 1;
28278
+ break;
28279
+ }
28280
+ if (!readEarlier(end)) break;
28281
+ }
28282
+ if (cut) return;
28283
+ yield {
28284
+ text: buffer.subarray(offset - start, lineEnd - start).toString("utf8"),
28285
+ offset,
28286
+ end: terminated ? lineEnd + 1 : lineEnd,
28287
+ terminated
28288
+ };
28289
+ end = offset;
28290
+ }
28291
+ } finally {
28292
+ closeSync5(fd2);
28293
+ }
28294
+ }
28179
28295
  function* readLinesFrom(path2, from = 0, chunkBytes = SCAN_CHUNK_BYTES) {
28180
28296
  const fd2 = openSync5(path2, "r");
28181
28297
  try {
@@ -28194,13 +28310,15 @@ function* readLinesFrom(path2, from = 0, chunkBytes = SCAN_CHUNK_BYTES) {
28194
28310
  closeSync5(fd2);
28195
28311
  }
28196
28312
  }
28197
- async function* readLinesFromAsync(path2, from = 0) {
28313
+ async function* readLinesFromAsync(path2, from = 0, to = Number.POSITIVE_INFINITY) {
28198
28314
  const handle = await open2(path2, "r");
28199
28315
  try {
28200
28316
  const splitter = new LineSplitter(from);
28201
28317
  let position = from;
28202
- for (; ; ) {
28203
- const chunk2 = Buffer.allocUnsafe(SCAN_CHUNK_BYTES);
28318
+ while (position < to) {
28319
+ const chunk2 = Buffer.allocUnsafe(
28320
+ Math.min(SCAN_CHUNK_BYTES, to - position)
28321
+ );
28204
28322
  const { bytesRead } = await handle.read(chunk2, 0, chunk2.length, position);
28205
28323
  if (bytesRead <= 0) break;
28206
28324
  position += bytesRead;
@@ -28542,6 +28660,7 @@ import {
28542
28660
  writeSync as writeSync4
28543
28661
  } from "node:fs";
28544
28662
  import { randomUUID } from "node:crypto";
28663
+ import { open as open3 } from "node:fs/promises";
28545
28664
  import { join as join18 } from "node:path";
28546
28665
  function activityAt(event) {
28547
28666
  const at2 = Date.parse(event.ts);
@@ -28576,7 +28695,66 @@ function isResumeMark(value) {
28576
28695
  function sizeOf(path2) {
28577
28696
  return statSync6(path2, { throwIfNoEntry: false })?.size ?? 0;
28578
28697
  }
28579
- var BODY_READ_SLICE, BODY_DROP_SLICE_BYTES, BODY_DROP_SCANS, NEWLINE, EVENT_TAIL_WINDOW, WalRecoveryConflict, Wal;
28698
+ function statIfExists2(path2) {
28699
+ return statSync6(path2, { throwIfNoEntry: false });
28700
+ }
28701
+ async function writeAll2(out, bytes) {
28702
+ let written2 = 0;
28703
+ while (written2 < bytes.length) {
28704
+ const { bytesWritten } = await out.write(
28705
+ bytes,
28706
+ written2,
28707
+ bytes.length - written2
28708
+ );
28709
+ written2 += bytesWritten;
28710
+ }
28711
+ }
28712
+ async function copyRange(path2, out, from, to, signal) {
28713
+ if (to <= from) return;
28714
+ const source = await open3(path2, "r");
28715
+ try {
28716
+ const buffer = Buffer.allocUnsafe(
28717
+ Math.min(BODY_PURGE_SLICE_BYTES, to - from)
28718
+ );
28719
+ for (let at2 = from; at2 < to; ) {
28720
+ signal?.throwIfAborted();
28721
+ const { bytesRead } = await source.read(
28722
+ buffer,
28723
+ 0,
28724
+ Math.min(buffer.length, to - at2),
28725
+ at2
28726
+ );
28727
+ if (bytesRead <= 0) return;
28728
+ await writeAll2(out, buffer.subarray(0, bytesRead));
28729
+ at2 += bytesRead;
28730
+ }
28731
+ } finally {
28732
+ await source.close();
28733
+ }
28734
+ }
28735
+ function readRangeSync(path2, from, to) {
28736
+ const buffer = Buffer.alloc(Math.max(0, to - from));
28737
+ if (buffer.length === 0) return buffer;
28738
+ const fd2 = openSync6(path2, "r");
28739
+ try {
28740
+ let filled = 0;
28741
+ while (filled < buffer.length) {
28742
+ const read2 = readSync3(
28743
+ fd2,
28744
+ buffer,
28745
+ filled,
28746
+ buffer.length - filled,
28747
+ from + filled
28748
+ );
28749
+ if (read2 <= 0) break;
28750
+ filled += read2;
28751
+ }
28752
+ return buffer.subarray(0, filled);
28753
+ } finally {
28754
+ closeSync6(fd2);
28755
+ }
28756
+ }
28757
+ var BODY_READ_SLICE, BODY_DROP_SLICE_BYTES, BODY_DROP_SCANS, BODY_PURGE_SLICE_BYTES, BODY_PURGE_SCANS, BODY_PURGE_CATCH_UPS, NEWLINE, EVENT_TAIL_WINDOW, WalRecoveryConflict, Wal;
28580
28758
  var init_wal = __esm({
28581
28759
  "../../packages/tacho/src/host/wal.ts"() {
28582
28760
  "use strict";
@@ -28586,6 +28764,9 @@ var init_wal = __esm({
28586
28764
  BODY_READ_SLICE = 32;
28587
28765
  BODY_DROP_SLICE_BYTES = 1024 * 1024;
28588
28766
  BODY_DROP_SCANS = 3;
28767
+ BODY_PURGE_SLICE_BYTES = 1024 * 1024;
28768
+ BODY_PURGE_SCANS = 3;
28769
+ BODY_PURGE_CATCH_UPS = 3;
28589
28770
  NEWLINE = 10;
28590
28771
  EVENT_TAIL_WINDOW = 16 * 1024;
28591
28772
  WalRecoveryConflict = class extends Error {
@@ -28685,6 +28866,18 @@ var init_wal = __esm({
28685
28866
  * WAL has ever opened. See `flush` for why this exists.
28686
28867
  */
28687
28868
  dirtyPaths = /* @__PURE__ */ new Set();
28869
+ /**
28870
+ * The awaited body sweeps running now, one entry per session scan
28871
+ * (`purgeBodiesOutsideMandateAsync`). Every write to a body file other than
28872
+ * an append marks the entries for its session, so a scan whose bytes moved
28873
+ * under it does not rename its copy over the file.
28874
+ */
28875
+ bodyWatches = /* @__PURE__ */ new Set();
28876
+ /**
28877
+ * The temporary files those sweeps are writing. `compact` removes a
28878
+ * temporary file a crashed rewrite left, and must not remove one of these.
28879
+ */
28880
+ liveTemps = /* @__PURE__ */ new Set();
28688
28881
  fileFor(sessionUuid2) {
28689
28882
  return join18(this.dir, `${sessionUuid2}.ndjson`);
28690
28883
  }
@@ -29407,6 +29600,7 @@ ${lines.join("\n")}
29407
29600
  if (next > 0) await new Promise((resolve26) => setImmediate(resolve26));
29408
29601
  const slice = this.blankSlice(path2, found, next, idems);
29409
29602
  if (slice === void 0) break;
29603
+ if (slice.blanked > 0) this.bodyRewritten(session);
29410
29604
  blanked += slice.blanked;
29411
29605
  moved ||= slice.moved;
29412
29606
  next = slice.next;
@@ -29663,6 +29857,7 @@ ${lines.join("\n")}
29663
29857
  this.dirtyPaths.add(path2);
29664
29858
  }
29665
29859
  this.bodyIndexes.invalidate(sessionUuid2);
29860
+ this.bodyRewritten(sessionUuid2);
29666
29861
  return cut;
29667
29862
  }
29668
29863
  /**
@@ -29703,11 +29898,11 @@ ${lines.join("\n")}
29703
29898
  *eventsBackward(sessionUuid2) {
29704
29899
  const path2 = this.fileFor(sessionUuid2);
29705
29900
  if (!existsSync12(path2)) return;
29706
- let end = statSync6(path2).size;
29707
- while (end > 0) {
29708
- const line = readTailLine(path2, end, EVENT_TAIL_WINDOW);
29709
- if (line === void 0) return;
29710
- end = line.offset;
29901
+ for (const line of readLinesBackward(
29902
+ path2,
29903
+ statSync6(path2).size,
29904
+ EVENT_TAIL_WINDOW
29905
+ )) {
29711
29906
  if (line.text.trim().length === 0) continue;
29712
29907
  let event;
29713
29908
  try {
@@ -29849,6 +30044,17 @@ ${lines.join("\n")}
29849
30044
  firstUnshippedAt(sessionUuid2, through) {
29850
30045
  const known = this.firstUnshipped.get(sessionUuid2);
29851
30046
  if (known?.through === through) return known.ts;
30047
+ const waiting = this.lastSeqOf(sessionUuid2) - through;
30048
+ if (!this.resume.has(sessionUuid2) && waiting > 0 && waiting <= through) {
30049
+ let first;
30050
+ for (const event of this.eventsBackward(sessionUuid2)) {
30051
+ if (event.seq <= through) break;
30052
+ first = event;
30053
+ }
30054
+ if (first === void 0) return void 0;
30055
+ this.firstUnshipped.set(sessionUuid2, { through, ts: first.ts });
30056
+ return first.ts;
30057
+ }
29852
30058
  for (const first of this.eventsAfterShipped(
29853
30059
  sessionUuid2,
29854
30060
  EVENT_TAIL_WINDOW
@@ -29989,27 +30195,241 @@ ${lines.join("\n")}
29989
30195
  * that withholds a body from a shipment. See `purgeBodiesNarrowedOut` in
29990
30196
  * the daemon, which calls this when a replacement bundle verifies and
29991
30197
  * narrows the clause.
30198
+ *
30199
+ * This reads every event file and every body file on the synchronous path,
30200
+ * which ADR-231 rules out on the daemon's thread. The daemon calls
30201
+ * `purgeBodiesOutsideMandateAsync`. It calls this only when it cannot record
30202
+ * the purge it owes, the one case where the erase has to finish before the
30203
+ * narrowed bundle is cached.
29992
30204
  */
29993
30205
  purgeBodiesOutsideMandate(retention) {
29994
30206
  let purged = 0;
29995
- const sessions = /* @__PURE__ */ new Set([
29996
- ...this.sessions(),
29997
- ...this.sessionsWithBodies()
29998
- ]);
29999
- for (const session of sessions) {
30207
+ for (const session of this.sessionsToPurge())
30208
+ purged += this.purgeSessionSync(session, retention);
30209
+ return purged;
30210
+ }
30211
+ /**
30212
+ * The same sweep, without holding the daemon's thread (#5176, ADR-231).
30213
+ * Every read is awaited, and a turn of the event loop separates two
30214
+ * sessions, so a hook or a proxied model call waits for one read and not
30215
+ * for the sweep.
30216
+ *
30217
+ * Each session is rewritten the way `rewriteBodies` rewrites it: the lines
30218
+ * kept go to a temporary file, and a rename puts it over the body file. A
30219
+ * crash part way leaves the old file whole, and the caller's record of the
30220
+ * purge it owes runs the sweep again. Hooks append to the file while the
30221
+ * scan awaits its reads, and three rules keep those appends:
30222
+ *
30223
+ * - The scan measures the body file and the event file in one synchronous
30224
+ * stretch, and reads each only up to that size. `append` writes a body
30225
+ * and then its event in one synchronous stretch too, so every body the
30226
+ * scan reads has its event in the part of the event file the scan reads,
30227
+ * or is a crash orphan. A body appended later is never judged an orphan.
30228
+ * - Bytes appended after that moment go to the new file as they are. They
30229
+ * were recorded under the mandate in force when they were appended, and
30230
+ * the daemon caches the narrowed mandate before it starts this sweep.
30231
+ * - The rename happens in one synchronous stretch, after a last check. When
30232
+ * the file was replaced, cut short, or written other than by an append
30233
+ * while the scan read it (`dropBodiesAsync` blanks lines in place), the
30234
+ * copy is thrown away and the session is scanned again. After
30235
+ * `BODY_PURGE_SCANS` scans the session goes through the synchronous sweep.
30236
+ *
30237
+ * The copy is synced to disk with an awaited call before that stretch, so
30238
+ * the stretch copies and syncs only what was appended since, about
30239
+ * `BODY_PURGE_SLICE_BYTES` at most.
30240
+ */
30241
+ async purgeBodiesOutsideMandateAsync(retention, options = {}) {
30242
+ const { signal } = options;
30243
+ let purged = 0;
30244
+ for (const session of this.sessionsToPurge()) {
30245
+ await new Promise((resolve26) => setImmediate(resolve26));
30246
+ signal?.throwIfAborted();
30247
+ await options.beforeSession?.(session);
30248
+ signal?.throwIfAborted();
30000
30249
  if (!existsSync12(this.bodyFileFor(session))) continue;
30001
- const kindOf2 = new Map(
30002
- this.read(session).map(
30003
- (event) => [event.event_id_idem, event.kind]
30004
- )
30005
- );
30006
- purged += this.rewriteBodies(
30007
- session,
30008
- (stored) => this.keeps(stored, kindOf2, retention)
30009
- );
30250
+ let swept = "moved";
30251
+ for (let scan = 0; scan < BODY_PURGE_SCANS && swept === "moved"; scan++)
30252
+ swept = await this.purgeSessionAsync(session, retention, signal);
30253
+ purged += swept === "moved" ? this.purgeSessionSync(session, retention) : swept;
30010
30254
  }
30011
30255
  return purged;
30012
30256
  }
30257
+ /**
30258
+ * Every session that has an event file OR a body file. `sessions()` lists
30259
+ * `.ndjson` only, so a crash between `append`'s body write and its first
30260
+ * event write leaves a `<uuid>.bodies.jsonl` with no `.ndjson` beside it,
30261
+ * invisible to a sweep that walked `sessions()` and to `compact`, which
30262
+ * does. Those bytes would outlive every mechanism meant to remove them.
30263
+ */
30264
+ sessionsToPurge() {
30265
+ return /* @__PURE__ */ new Set([...this.sessions(), ...this.sessionsWithBodies()]);
30266
+ }
30267
+ /** One session of `purgeBodiesOutsideMandate`, read and rewritten at once. */
30268
+ purgeSessionSync(session, retention) {
30269
+ if (!existsSync12(this.bodyFileFor(session))) return 0;
30270
+ const kindOf2 = new Map(
30271
+ this.read(session).map(
30272
+ (event) => [event.event_id_idem, event.kind]
30273
+ )
30274
+ );
30275
+ return this.rewriteBodies(
30276
+ session,
30277
+ (stored) => this.keeps(stored, kindOf2, retention)
30278
+ );
30279
+ }
30280
+ /**
30281
+ * One scan of `purgeBodiesOutsideMandateAsync` over one session. Answers
30282
+ * how many lines went, or `moved` when the file changed under the scan
30283
+ * other than by an append, in which case nothing was renamed.
30284
+ */
30285
+ async purgeSessionAsync(session, retention, signal) {
30286
+ const path2 = this.bodyFileFor(session);
30287
+ const start = statIfExists2(path2);
30288
+ if (start === void 0) return 0;
30289
+ const eventEnd = statIfExists2(this.fileFor(session))?.size ?? 0;
30290
+ const watch2 = { session, moved: false };
30291
+ this.bodyWatches.add(watch2);
30292
+ const tmp = `${path2}.${randomUUID()}.tmp`;
30293
+ let out;
30294
+ try {
30295
+ const kindOf2 = await this.eventKindsUpTo(session, eventEnd, signal);
30296
+ let cut = 0;
30297
+ let kept = 0;
30298
+ let held = [];
30299
+ let heldBytes = 0;
30300
+ const writeHeld = async (to) => {
30301
+ if (heldBytes === 0) return;
30302
+ await writeAll2(to, Buffer.concat(held, heldBytes));
30303
+ held = [];
30304
+ heldBytes = 0;
30305
+ };
30306
+ for await (const line of readLinesFromAsync(path2, 0, start.size)) {
30307
+ signal?.throwIfAborted();
30308
+ if (line.text.trim().length === 0) continue;
30309
+ if (this.keeps(parseStoredBody(line.text), kindOf2, retention)) {
30310
+ kept += 1;
30311
+ if (out === void 0) continue;
30312
+ const bytes = Buffer.from(`${line.text}
30313
+ `);
30314
+ held.push(bytes);
30315
+ heldBytes += bytes.length;
30316
+ if (heldBytes >= BODY_PURGE_SLICE_BYTES) await writeHeld(out);
30317
+ continue;
30318
+ }
30319
+ cut += 1;
30320
+ if (out === void 0) {
30321
+ this.liveTemps.add(tmp);
30322
+ out = await open3(tmp, "wx", 384);
30323
+ await copyRange(path2, out, 0, line.offset, signal);
30324
+ }
30325
+ }
30326
+ if (out === void 0) return watch2.moved ? "moved" : 0;
30327
+ await writeHeld(out);
30328
+ let copied = start.size;
30329
+ for (let round = 0; round < BODY_PURGE_CATCH_UPS; round += 1) {
30330
+ const size = statIfExists2(path2)?.size;
30331
+ if (size === void 0 || size - copied <= BODY_PURGE_SLICE_BYTES)
30332
+ break;
30333
+ await copyRange(path2, out, copied, size, signal);
30334
+ copied = size;
30335
+ }
30336
+ await out.sync();
30337
+ await out.close();
30338
+ out = void 0;
30339
+ signal?.throwIfAborted();
30340
+ if (watch2.moved) return "moved";
30341
+ return this.commitPurge(session, tmp, {
30342
+ start,
30343
+ copied,
30344
+ keptAny: kept > 0 || copied > start.size,
30345
+ cut
30346
+ });
30347
+ } catch (error) {
30348
+ if (error.code === "ENOENT" && !existsSync12(path2))
30349
+ return 0;
30350
+ throw error;
30351
+ } finally {
30352
+ this.bodyWatches.delete(watch2);
30353
+ if (out !== void 0) await out.close().catch(() => void 0);
30354
+ if (existsSync12(tmp)) unlinkSync9(tmp);
30355
+ this.liveTemps.delete(tmp);
30356
+ }
30357
+ }
30358
+ /**
30359
+ * The kind of every event in the first `end` bytes of a session's event
30360
+ * file, by event id, read with awaited reads. A line that does not parse is
30361
+ * reported and skipped, as `read` does. A line cut off at `end` was torn by
30362
+ * a crash, since `append` ends every write with a newline, and is skipped.
30363
+ */
30364
+ async eventKindsUpTo(session, end, signal) {
30365
+ const kindOf2 = /* @__PURE__ */ new Map();
30366
+ if (end === 0) return kindOf2;
30367
+ for await (const line of readLinesFromAsync(
30368
+ this.fileFor(session),
30369
+ 0,
30370
+ end
30371
+ )) {
30372
+ signal?.throwIfAborted();
30373
+ if (!line.terminated || line.text.trim().length === 0) continue;
30374
+ try {
30375
+ const event = parseEventLine(line.text);
30376
+ kindOf2.set(event.event_id_idem, event.kind);
30377
+ } catch (error) {
30378
+ this.reportEventParseFailure({
30379
+ session_uuid: session,
30380
+ reason: error instanceof Error ? error.message : String(error)
30381
+ });
30382
+ }
30383
+ }
30384
+ return kindOf2;
30385
+ }
30386
+ /**
30387
+ * The rename of `purgeSessionAsync`, in one synchronous stretch, so nothing
30388
+ * else can write the body file between the check and the rename. Copies
30389
+ * what was appended since `copied`, syncs it, and puts the copy over the
30390
+ * file, or removes the file when nothing in it is left.
30391
+ */
30392
+ commitPurge(session, tmp, scan) {
30393
+ const path2 = this.bodyFileFor(session);
30394
+ const now = statIfExists2(path2);
30395
+ if (now === void 0) return 0;
30396
+ if (now.ino !== scan.start.ino || now.dev !== scan.start.dev || now.size < scan.copied)
30397
+ return "moved";
30398
+ const tail = readRangeSync(path2, scan.copied, now.size);
30399
+ if (!scan.keptAny && tail.toString("utf8").trim().length === 0) {
30400
+ unlinkSync9(path2);
30401
+ } else {
30402
+ const fd2 = openSync6(tmp, "r+");
30403
+ try {
30404
+ let written2 = 0;
30405
+ const at2 = fstatSync(fd2).size;
30406
+ while (written2 < tail.length)
30407
+ written2 += writeSync4(
30408
+ fd2,
30409
+ tail,
30410
+ written2,
30411
+ tail.length - written2,
30412
+ at2 + written2
30413
+ );
30414
+ fdatasyncSync(fd2);
30415
+ } finally {
30416
+ closeSync6(fd2);
30417
+ }
30418
+ renameSync3(tmp, path2);
30419
+ }
30420
+ this.bodyIndexes.invalidate(session);
30421
+ this.bodyRewritten(session);
30422
+ return scan.cut;
30423
+ }
30424
+ /**
30425
+ * Tell every awaited sweep of this session that its body file changed
30426
+ * other than by an append. Each write that cuts, blanks, or replaces body
30427
+ * bytes calls this in the same synchronous stretch as the write.
30428
+ */
30429
+ bodyRewritten(session) {
30430
+ for (const watch2 of this.bodyWatches)
30431
+ if (watch2.session === session) watch2.moved = true;
30432
+ }
30013
30433
  /**
30014
30434
  * Whether one stored body survives this mandate. Three answers, each for a
30015
30435
  * reason:
@@ -30027,7 +30447,9 @@ ${lines.join("\n")}
30027
30447
  * the two writes and no event will ever arrive for it. Keeping it kept
30028
30448
  * prompt or tool bytes that no mandate covers and that nothing would
30029
30449
  * ever ship, delete or compact, because every one of those paths starts
30030
- * from the event.
30450
+ * from the event. The awaited sweep does let appends land while it reads,
30451
+ * so it judges only the bodies written before it measured the files, and
30452
+ * their events are all in the part of the event file it reads.
30031
30453
  * - Otherwise the class comes from the event's kind and the one retention
30032
30454
  * gate answers. A kind the table does not name has no class, so no mandate
30033
30455
  * can cover it and the body goes.
@@ -30089,6 +30511,7 @@ ${lines.join("\n")}
30089
30511
  if (kept === 0) unlinkSync9(path2);
30090
30512
  else renameSync3(tmp, path2);
30091
30513
  this.bodyIndexes.invalidate(sessionUuid2);
30514
+ this.bodyRewritten(sessionUuid2);
30092
30515
  return cut;
30093
30516
  } finally {
30094
30517
  if (existsSync12(tmp)) unlinkSync9(tmp);
@@ -30116,6 +30539,7 @@ ${lines.join("\n")}
30116
30539
  `^${uuid}\\.bodies\\.jsonl\\.${uuid}\\.tmp$`
30117
30540
  );
30118
30541
  for (const name of readdirSync7(this.dir)) {
30542
+ if (this.liveTemps.has(join18(this.dir, name))) continue;
30119
30543
  if (abandonedRewrite.test(name)) {
30120
30544
  try {
30121
30545
  unlinkSync9(join18(this.dir, name));
@@ -30199,6 +30623,7 @@ ${lines.join("\n")}
30199
30623
  const path2 = this.bodyFileFor(session);
30200
30624
  if (existsSync12(path2)) unlinkSync9(path2);
30201
30625
  this.bodyIndexes.invalidate(session);
30626
+ this.bodyRewritten(session);
30202
30627
  return true;
30203
30628
  } catch (error) {
30204
30629
  this.bodyFailure(session, "cleanup", error);
@@ -30551,8 +30976,12 @@ function daemonServiceSpec(daemonCommand, deps) {
30551
30976
  command: daemonCommand,
30552
30977
  env: {
30553
30978
  ...deps.env["TACHO_HOME"] !== void 0 ? { TACHO_HOME: deps.env["TACHO_HOME"] } : {},
30554
- // The harness homes the enrolling shell moved, so tachod reads the
30555
- // same files the hooks were written to.
30979
+ // The harness homes this shell moved. Each collector reads its agent's
30980
+ // harness files where that agent's host.json recorded them at enroll
30981
+ // (`harness_files`), so these decide nothing for an agent with a
30982
+ // record. They stay for an agent enrolled before the record existed.
30983
+ // One service serves every agent, so they come from whichever shell
30984
+ // installed it last (#5409).
30556
30985
  ...Object.fromEntries(
30557
30986
  HARNESS_HOME_VARS.flatMap((key) => {
30558
30987
  const value = deps.env[key];
@@ -43081,7 +43510,11 @@ async function renewStaticToken(harness, deps) {
43081
43510
  const label2 = TACHO_HARNESS_LABELS[harness];
43082
43511
  if (deps.host.host_status !== "active" || !deps.inCustody(provider, harness))
43083
43512
  return "skipped";
43084
- const dirs = deps.stellaHome !== void 0 ? { stellaHome: deps.stellaHome } : {};
43513
+ const dirs = {
43514
+ ...deps.stellaHome !== void 0 ? { stellaHome: deps.stellaHome } : {},
43515
+ ...deps.codexHome !== void 0 ? { codexHome: deps.codexHome } : {},
43516
+ ...deps.claudeConfigDir !== void 0 ? { claudeConfigDir: deps.claudeConfigDir } : {}
43517
+ };
43085
43518
  let brokered = false;
43086
43519
  try {
43087
43520
  brokered = (await readModelCredentialState({
@@ -44021,8 +44454,33 @@ var init_git_facts = __esm({
44021
44454
 
44022
44455
  // ../../packages/tacho/src/collector/work-order-autostart.ts
44023
44456
  import { spawn as spawn7 } from "node:child_process";
44024
- import { closeSync as closeSync7, existsSync as existsSync21, openSync as openSync7 } from "node:fs";
44457
+ import { closeSync as closeSync7, existsSync as existsSync21, openSync as openSync7, readSync as readSync4, statSync as statSync12 } from "node:fs";
44025
44458
  import { dirname as dirname20, join as join36 } from "node:path";
44459
+ function lastLogLine(path2) {
44460
+ let fd2;
44461
+ try {
44462
+ const size = statSync12(path2).size;
44463
+ const length = Math.min(size, 4096);
44464
+ const buffer = Buffer.alloc(length);
44465
+ fd2 = openSync7(path2, "r");
44466
+ readSync4(fd2, buffer, 0, length, size - length);
44467
+ const line = buffer.toString("utf8").replace(TERMINAL_CODE, "").split(/\r?\n/).map((text5) => text5.trim()).filter((text5) => text5.length > 0).pop();
44468
+ if (line === void 0) return void 0;
44469
+ return line.length > EXIT_REPORT_QUOTE ? `${line.slice(0, EXIT_REPORT_QUOTE - 1)}\u2026` : line;
44470
+ } catch {
44471
+ return void 0;
44472
+ } finally {
44473
+ if (fd2 !== void 0) {
44474
+ try {
44475
+ closeSync7(fd2);
44476
+ } catch {
44477
+ }
44478
+ }
44479
+ }
44480
+ }
44481
+ function exitPhrase(exit) {
44482
+ return exit.signal !== null ? `ended on ${exit.signal}` : `exited with code ${String(exit.code)}`;
44483
+ }
44026
44484
  function asError(error) {
44027
44485
  return error instanceof Error ? error : new Error(String(error));
44028
44486
  }
@@ -44037,6 +44495,10 @@ function isPlainBranchName(name) {
44037
44495
  function createWorkOrderAutostart(deps) {
44038
44496
  const { paths, log } = deps;
44039
44497
  const isAlive = deps.isAlive ?? processIsAlive;
44498
+ const wait = deps.wait ?? ((ms) => new Promise((done) => {
44499
+ setTimeout(done, ms).unref();
44500
+ }));
44501
+ const sessions = /* @__PURE__ */ new Set();
44040
44502
  const spawnHarness = deps.spawn ?? spawnDetached;
44041
44503
  let searchPath;
44042
44504
  const pathForHarness = () => {
@@ -44222,7 +44684,7 @@ function createWorkOrderAutostart(deps) {
44222
44684
  `work order ${id2}: could not clear its running mark: ${asError(error).message}`
44223
44685
  );
44224
44686
  }
44225
- const ended = exit === void 0 ? "ended while the daemon was stopped" : "never" in exit ? `never started (${exit.never})` : exit.signal !== null ? `ended on ${exit.signal}` : `exited with code ${String(exit.code)}`;
44687
+ const ended = exit === void 0 ? "ended while the daemon was stopped" : "never" in exit ? `never started (${exit.never})` : exitPhrase(exit);
44226
44688
  const { worktree, root, branch, base_sha: base2 } = mark;
44227
44689
  if (worktree === void 0 || !existsSync21(worktree)) {
44228
44690
  log(`work order ${id2}: the harness ${ended}`);
@@ -44276,13 +44738,28 @@ function createWorkOrderAutostart(deps) {
44276
44738
  `work order ${id2}: the harness ${ended} and left nothing that is not on a remote, so its worktree is removed${mark.log !== void 0 ? `. Its output is in ${mark.log}` : ""}.`
44277
44739
  );
44278
44740
  }
44279
- function watch2(id2, workspace, exited) {
44741
+ async function reportExit(id2, binary, exit, logPath) {
44742
+ if (sessions.has(id2)) return;
44743
+ await wait(EXIT_REPORT_GRACE_MS);
44744
+ if (sessions.has(id2)) return;
44745
+ const said = lastLogLine(logPath);
44746
+ const reason3 = `${binary} ${exitPhrase(exit)} before it opened a run.${said !== void 0 ? ` Its last output: ${said}` : ""} The full output is in ${logPath} on this machine.`;
44747
+ const failed3 = await refuseWorkOrder(deps.client, id2, reason3.slice(0, 512));
44748
+ log(
44749
+ failed3 === void 0 ? `work order ${id2}: ${reason3} Oxagen ended the send.` : `work order ${id2}: ${reason3} Oxagen kept the send (${failed3}).`
44750
+ );
44751
+ }
44752
+ function watch2(id2, binary, workspace, exited) {
44280
44753
  void exited.then((exit) => {
44281
- const done = cleanUp({ work_order: id2, ...workspace }, exit).catch((error) => {
44754
+ const done = Promise.all([
44755
+ cleanUp({ work_order: id2, ...workspace }, exit),
44756
+ reportExit(id2, binary, exit, workspace.log)
44757
+ ]).then(() => void 0).catch((error) => {
44282
44758
  log(
44283
44759
  `work order ${id2}: cleanup after the harness failed: ${asError(error).message}`
44284
44760
  );
44285
44761
  }).finally(() => {
44762
+ sessions.delete(id2);
44286
44763
  cleanups.delete(done);
44287
44764
  });
44288
44765
  cleanups.add(done);
@@ -44292,8 +44769,8 @@ function createWorkOrderAutostart(deps) {
44292
44769
  const id2 = order.work_order;
44293
44770
  const running = runningWorkOrder(paths, id2, isAlive);
44294
44771
  if (running !== void 0) return;
44295
- const wait = retry.get(id2);
44296
- if (wait !== void 0 && deps.now() < wait.at) return;
44772
+ const wait2 = retry.get(id2);
44773
+ if (wait2 !== void 0 && deps.now() < wait2.at) return;
44297
44774
  const outcome = await claimForStart(deps.client, deps.host(), id2);
44298
44775
  if (outcome.kind === "refused" && outcome.final) {
44299
44776
  log(
@@ -44389,7 +44866,7 @@ function createWorkOrderAutostart(deps) {
44389
44866
  log(
44390
44867
  `work order ${id2} for ${claim.work_order.item_number} started ${binary} (process ${started.pid}) in ${workspace.worktree}; output goes to ${workspace.log}`
44391
44868
  );
44392
- watch2(id2, workspace, started.exited);
44869
+ watch2(id2, binary, workspace, started.exited);
44393
44870
  }
44394
44871
  async function runPass() {
44395
44872
  let orders;
@@ -44438,6 +44915,9 @@ function createWorkOrderAutostart(deps) {
44438
44915
  }
44439
44916
  }
44440
44917
  },
44918
+ noteSession: (workOrder) => {
44919
+ sessions.add(workOrder);
44920
+ },
44441
44921
  idle: async () => {
44442
44922
  while (pass2 !== void 0 || cleanups.size > 0) {
44443
44923
  await Promise.allSettled([pass2, ...cleanups]);
@@ -44445,7 +44925,7 @@ function createWorkOrderAutostart(deps) {
44445
44925
  }
44446
44926
  };
44447
44927
  }
44448
- var SLOW_GIT_MS, RETRY_MIN_MS, RETRY_MAX_MS, spawnDetached;
44928
+ var SLOW_GIT_MS, RETRY_MIN_MS, RETRY_MAX_MS, EXIT_REPORT_GRACE_MS, TERMINAL_CODE, EXIT_REPORT_QUOTE, spawnDetached;
44449
44929
  var init_work_order_autostart = __esm({
44450
44930
  "../../packages/tacho/src/collector/work-order-autostart.ts"() {
44451
44931
  "use strict";
@@ -44459,6 +44939,12 @@ var init_work_order_autostart = __esm({
44459
44939
  SLOW_GIT_MS = 12e4;
44460
44940
  RETRY_MIN_MS = 15e3;
44461
44941
  RETRY_MAX_MS = 5 * 6e4;
44942
+ EXIT_REPORT_GRACE_MS = 6e4;
44943
+ TERMINAL_CODE = new RegExp(
44944
+ `${String.fromCharCode(27)}\\[[0-9;?]*[A-Za-z]`,
44945
+ "g"
44946
+ );
44947
+ EXIT_REPORT_QUOTE = 240;
44462
44948
  spawnDetached = (command2, args, options) => new Promise((resolve26) => {
44463
44949
  let fd2;
44464
44950
  try {
@@ -44468,10 +44954,10 @@ var init_work_order_autostart = __esm({
44468
44954
  resolve26({ ok: false, error: asError(error) });
44469
44955
  return;
44470
44956
  }
44471
- let open6 = true;
44957
+ let open7 = true;
44472
44958
  const close = () => {
44473
- if (!open6) return;
44474
- open6 = false;
44959
+ if (!open7) return;
44960
+ open7 = false;
44475
44961
  try {
44476
44962
  closeSync7(fd2);
44477
44963
  } catch {
@@ -44559,7 +45045,7 @@ import { createReadStream } from "node:fs";
44559
45045
  import {
44560
45046
  lstat,
44561
45047
  mkdir,
44562
- open as open3,
45048
+ open as open4,
44563
45049
  readdir,
44564
45050
  readlink,
44565
45051
  rename,
@@ -44612,7 +45098,7 @@ async function contentHash(absolutePath, kind, keep) {
44612
45098
  const temp2 = join37(keep.dir, `.partial-${randomUUID3()}`);
44613
45099
  copy = {
44614
45100
  temp: temp2,
44615
- handle: await open3(temp2, "wx", keep.executable ? 448 : 384)
45101
+ handle: await open4(temp2, "wx", keep.executable ? 448 : 384)
44616
45102
  };
44617
45103
  } catch {
44618
45104
  copy = null;
@@ -46380,22 +46866,41 @@ var init_interjection = __esm({
46380
46866
  function showsRefusedPrompt(harness) {
46381
46867
  return harness !== "stella";
46382
46868
  }
46869
+ function questionBoundary(harness) {
46870
+ return showsRefusedPrompt(harness) ? "UserPromptSubmit" : "SessionStart";
46871
+ }
46872
+ function holdToolCall(evaluation, record) {
46873
+ const held = record.control.interjection;
46874
+ if (held === void 0 || evaluation.decision === "deny") return evaluation;
46875
+ const refused2 = {
46876
+ ...evaluation,
46877
+ decision: "deny",
46878
+ evaluated: "deny",
46879
+ source: "bundle",
46880
+ reason_code: INTERJECTION_OPEN,
46881
+ reason: held.question
46882
+ };
46883
+ delete refused2.rule;
46884
+ delete refused2.rules;
46885
+ return refused2;
46886
+ }
46383
46887
  function isBound(clause, remote) {
46384
46888
  const bound = new Set(clause.bound_remote_digests);
46385
46889
  return bound.has(remote.remote_digest) || bound.has(remote.remote_digest_folded);
46386
46890
  }
46387
- function interjectionQuestion(clause) {
46891
+ function interjectionQuestion(clause, harness) {
46388
46892
  const minutes = Math.max(1, Math.round(clause.timeout_ms / 6e4));
46389
46893
  const slug = clause.workspace_slug;
46390
- return `Oxagen is holding this session before its first model call. It started in a repository that no workspace in your organization has bound, and skills are on for workspace ${slug}. Should Oxagen link this repository to ${slug}, or create a new workspace for it with skills off? A person with access answers on this run's page in Oxagen. With no answer in ${minutes} minutes, the session goes on without skills.`;
46894
+ const hold = showsRefusedPrompt(harness) ? `Oxagen is holding this session before its first model call. ` : `Oxagen is refusing this session's tool calls until a person answers. `;
46895
+ return hold + `It started in a repository that no workspace in your organization has bound, and skills are on for workspace ${slug}. Should Oxagen link this repository to ${slug}, or create a new workspace for it with skills off? A person with access answers on this run's page in Oxagen. With no answer in ${minutes} minutes, the session goes on without skills.`;
46391
46896
  }
46392
46897
  function proposedSlug(name) {
46393
46898
  const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+/, "").slice(0, SLUG_MAX).replace(/-+$/, "");
46394
46899
  return slug.length >= SLUG_MIN && SLUG_PATTERN.test(slug) ? slug : null;
46395
46900
  }
46396
- function raiseInterjection(record, clause, remote, now, fields) {
46901
+ function prepareInterjection(record, clause, remote, now) {
46397
46902
  const key = ulid(now);
46398
- const question = interjectionQuestion(clause);
46903
+ const question = interjectionQuestion(clause, record.harness);
46399
46904
  const expiresAt = toProtocolTimestamp(now + clause.timeout_ms);
46400
46905
  const name = remote.name !== void 0 && remote.name.length <= NAME_MAX2 ? remote.name : null;
46401
46906
  const unknown2 = repoUnknownBodySchema.safeParse({
@@ -46432,21 +46937,28 @@ function raiseInterjection(record, clause, remote, now, fields) {
46432
46937
  ]
46433
46938
  });
46434
46939
  if (!unknown2.success || !interject.success) return void 0;
46940
+ return {
46941
+ unknown: unknown2.data,
46942
+ interject: interject.data,
46943
+ held: {
46944
+ key,
46945
+ question,
46946
+ expiresAt,
46947
+ workspaceSlug: clause.workspace_slug,
46948
+ configVersion: clause.config_version
46949
+ }
46950
+ };
46951
+ }
46952
+ function sealInterjection(record, pending, fields) {
46435
46953
  const events = [
46436
- record.recorder.sealCollectorEvent("repo.unknown", unknown2.data, fields),
46954
+ record.recorder.sealCollectorEvent("repo.unknown", pending.unknown, fields),
46437
46955
  record.recorder.sealCollectorEvent(
46438
46956
  "control.interject",
46439
- interject.data,
46957
+ pending.interject,
46440
46958
  fields
46441
46959
  )
46442
46960
  ];
46443
- record.control.interjection = {
46444
- key,
46445
- question,
46446
- expiresAt,
46447
- workspaceSlug: clause.workspace_slug,
46448
- configVersion: clause.config_version
46449
- };
46961
+ record.control.interjection = { ...pending.held };
46450
46962
  return events;
46451
46963
  }
46452
46964
  function expireInterjection(record, now, fields) {
@@ -46533,7 +47045,7 @@ function settle(record, held, answer, payload, fields) {
46533
47045
  record.control.interjection = void 0;
46534
47046
  return events;
46535
47047
  }
46536
- var SLUG_PATTERN, SLUG_MIN, SLUG_MAX, NAME_MAX2, INTERJECTION_TIMED_OUT_TEXT;
47048
+ var SLUG_PATTERN, SLUG_MIN, SLUG_MAX, NAME_MAX2, INTERJECTION_OPEN, INTERJECTION_TIMED_OUT_TEXT;
46537
47049
  var init_interjection2 = __esm({
46538
47050
  "../../packages/tacho/src/collector/interjection.ts"() {
46539
47051
  "use strict";
@@ -46544,6 +47056,7 @@ var init_interjection2 = __esm({
46544
47056
  SLUG_MIN = 2;
46545
47057
  SLUG_MAX = 40;
46546
47058
  NAME_MAX2 = 120;
47059
+ INTERJECTION_OPEN = "interjection_open";
46547
47060
  INTERJECTION_TIMED_OUT_TEXT = "Oxagen asked a person whether to bind this repository to a workspace, and nobody answered in time. This session goes on without skills.";
46548
47061
  }
46549
47062
  });
@@ -46809,21 +47322,23 @@ function promptBlock(view, record) {
46809
47322
  const block = operatorBlock(view, record);
46810
47323
  if (block !== void 0) return block;
46811
47324
  const held = record.control.interjection;
46812
- if (held === void 0) return void 0;
46813
- return { code: "interjection_open", reason: held.question, source: "bundle" };
47325
+ if (held === void 0 || !showsRefusedPrompt(record.harness))
47326
+ return void 0;
47327
+ return { code: INTERJECTION_OPEN, reason: held.question, source: "bundle" };
46814
47328
  }
46815
- async function askAboutRepository(record, view, deps, replay, fields) {
46816
- if (record.control.repoChecked === true) return [];
47329
+ async function repositoryQuestion(record, view, deps, replay, boundary) {
47330
+ if (record.control.repoChecked === true || questionBoundary(record.harness) !== boundary)
47331
+ return void 0;
46817
47332
  record.control.repoChecked = true;
46818
47333
  const clause = view.verified ? view.bundle.unbound_repo : void 0;
46819
- if (clause === void 0 || replay !== void 0 || !showsRefusedPrompt(record.harness) || record.recorder.hasModelCall || record.cwd === void 0 || deps.repositoryRemote === void 0)
46820
- return [];
47334
+ if (clause === void 0 || replay !== void 0 || record.recorder.hasModelCall || record.cwd === void 0 || deps.repositoryRemote === void 0)
47335
+ return void 0;
46821
47336
  const remote = await readRepository(
46822
47337
  recallView(record),
46823
47338
  deps.repositoryRemote
46824
47339
  );
46825
- if (remote === void 0 || isBound(clause, remote)) return [];
46826
- return raiseInterjection(record, clause, remote, deps.now(), fields) ?? [];
47340
+ if (remote === void 0 || isBound(clause, remote)) return void 0;
47341
+ return prepareInterjection(record, clause, remote, deps.now());
46827
47342
  }
46828
47343
  function invocationToolUseId(raw, input, record) {
46829
47344
  const derived = input.tool_use_id;
@@ -47274,12 +47789,19 @@ async function routeHook(raw, env, deps, replay, harness, agent, hookId) {
47274
47789
  switch (input.hook_event_name) {
47275
47790
  case "SessionStart": {
47276
47791
  const block = operatorBlock(view, record);
47792
+ const question = input.agent_id === void 0 ? await repositoryQuestion(record, view, deps, replay, "SessionStart") : void 0;
47793
+ const questionText = question?.held.question;
47277
47794
  const steering = block === void 0 && replay === void 0 ? await runSteeringBundle(record, deps) ?? view.bundle : view.bundle;
47278
47795
  const context = steering.context.system;
47279
47796
  if (block === void 0 && replay === void 0)
47280
47797
  await placeSessionSkills(view, record, deps, env);
47281
- const messages = block === void 0 && replay === void 0 && input.agent_id === void 0 ? drainMessages(record, deps, events, context?.length ?? 0) : [];
47282
- const additional = block === void 0 ? [context ?? "", ...messages.map((m) => m.text)].filter((s2) => s2.length > 0).join(CONTEXT_JOINER) : "";
47798
+ const messages = block === void 0 && replay === void 0 && input.agent_id === void 0 ? drainMessages(
47799
+ record,
47800
+ deps,
47801
+ events,
47802
+ (context?.length ?? 0) + (questionText?.length ?? 0)
47803
+ ) : [];
47804
+ const additional = block === void 0 ? [questionText ?? "", context ?? "", ...messages.map((m) => m.text)].filter((s2) => s2.length > 0).join(CONTEXT_JOINER) : "";
47283
47805
  const delivered = replay === void 0 ? void 0 : readDeliveredContext(replay.deliveredContext);
47284
47806
  const deliveryAttrs = replay === void 0 ? {
47285
47807
  // A blocked start hands the agent nothing, so it names no text.
@@ -47306,6 +47828,13 @@ async function routeHook(raw, env, deps, replay, harness, agent, hookId) {
47306
47828
  })
47307
47829
  )
47308
47830
  );
47831
+ if (question !== void 0)
47832
+ events.push(
47833
+ ...sealInterjection(record, question, {
47834
+ hook_event_name: "SessionStart",
47835
+ attrs: replayed
47836
+ })
47837
+ );
47309
47838
  if (block !== void 0) {
47310
47839
  events.push(
47311
47840
  record.recorder.sealCollectorEvent(
@@ -47355,14 +47884,15 @@ async function routeHook(raw, env, deps, replay, harness, agent, hookId) {
47355
47884
  };
47356
47885
  const timedOut = replay === void 0 ? expireInterjection(record, deps.now(), questionFields) : void 0;
47357
47886
  if (timedOut !== void 0) events.push(...timedOut);
47358
- const asked = await askAboutRepository(
47887
+ const asked = await repositoryQuestion(
47359
47888
  record,
47360
47889
  view,
47361
47890
  deps,
47362
47891
  replay,
47363
- questionFields
47892
+ "UserPromptSubmit"
47364
47893
  );
47365
- events.push(...asked);
47894
+ if (asked !== void 0)
47895
+ events.push(...sealInterjection(record, asked, questionFields));
47366
47896
  const block = promptBlock(view, record);
47367
47897
  const notice = block === void 0 && timedOut !== void 0 ? INTERJECTION_TIMED_OUT_TEXT : void 0;
47368
47898
  const messages = block === void 0 && replay === void 0 && input.agent_id === void 0 && deliversMessages(record.harness, input.hook_event_name) ? drainMessages(record, deps, events, notice?.length ?? 0) : [];
@@ -47440,12 +47970,17 @@ async function routeHook(raw, env, deps, replay, harness, agent, hookId) {
47440
47970
  const toolInput = input.tool_input;
47441
47971
  noteToolCall(record, toolName3, toolInput);
47442
47972
  noteRecallHints(record, input.tool_name, toolInput);
47973
+ const timedOut = replay === void 0 ? expireInterjection(record, deps.now(), {
47974
+ hook_event_name: "PreToolUse",
47975
+ attrs: replayed
47976
+ }) : void 0;
47977
+ if (timedOut !== void 0) events.push(...timedOut);
47443
47978
  const decided = await decideToolCall(view, record, input, deps, replay, {
47444
47979
  toolName: toolName3,
47445
47980
  toolInput
47446
47981
  });
47447
47982
  const currentView = decided.view;
47448
- let evaluation = decided.evaluation;
47983
+ let evaluation = replay === void 0 ? holdToolCall(decided.evaluation, record) : decided.evaluation;
47449
47984
  if (replay === void 0 && evaluation.decision === "deny" && evaluation.reason_code === "session_paused" && record.control.pauseEffect === void 0)
47450
47985
  record.control.pauseEffect = "refused";
47451
47986
  const refusal = evaluation.decision === "deny" ? void 0 : refusesForSteer(record.harness, record.control.messages);
@@ -47471,7 +48006,7 @@ async function routeHook(raw, env, deps, replay, harness, agent, hookId) {
47471
48006
  };
47472
48007
  }
47473
48008
  }
47474
- if (evaluation.decision === "deny" && evaluation.source !== "human")
48009
+ if (evaluation.decision === "deny" && evaluation.source !== "human" && evaluation.reason_code !== INTERJECTION_OPEN)
47475
48010
  notePolicyDenial(record, toolName3);
47476
48011
  const facts = policyFacts2(evaluation, currentView);
47477
48012
  const attrs2 = policyAttrs(evaluation, replay);
@@ -47788,10 +48323,13 @@ var init_hook_handler = __esm({
47788
48323
  });
47789
48324
 
47790
48325
  // ../../packages/tacho/src/collector/steer-waiters.ts
48326
+ function answerBlocksAgent(response) {
48327
+ return response["decision"] === "block" || response["continue"] === false;
48328
+ }
47791
48329
  function owesText(session) {
47792
48330
  return session.control.messages.length > 0 || session.control.resumeOwed !== void 0;
47793
48331
  }
47794
- var STEER_WAIT_HOLD_MS, STEER_WAIT_BUSY_KEPT, STEER_WAIT_RELEASING_EVENTS, SteerWaiters;
48332
+ var STEER_WAIT_HOLD_MS, STEER_WAIT_BUSY_KEPT, STEER_WAIT_RELEASING_EVENTS, STEER_WAIT_RELEASED_AFTER_ANSWER, SteerWaiters;
47795
48333
  var init_steer_waiters = __esm({
47796
48334
  "../../packages/tacho/src/collector/steer-waiters.ts"() {
47797
48335
  "use strict";
@@ -47804,6 +48342,9 @@ var init_steer_waiters = __esm({
47804
48342
  "PostToolUseFailure",
47805
48343
  "SessionEnd"
47806
48344
  ]);
48345
+ STEER_WAIT_RELEASED_AFTER_ANSWER = /* @__PURE__ */ new Set([
48346
+ "UserPromptSubmit"
48347
+ ]);
47807
48348
  SteerWaiters = class {
47808
48349
  constructor(deps) {
47809
48350
  this.deps = deps;
@@ -47822,12 +48363,17 @@ var init_steer_waiters = __esm({
47822
48363
  * Hold one waiter request for the session `key` names. `signal` aborts
47823
48364
  * when the waiter's connection closes. `startedAt` is when the waiter
47824
48365
  * started; one whose session was busy since is told to stop.
48366
+ *
48367
+ * A stale request is refused before it touches the waiter that holds the
48368
+ * key. An old waiter can be told `wait`, and ask again after a prompt and
48369
+ * a later `Stop` installed a newer one. Settling the newer one first ended
48370
+ * both, and left the idle agent with no waiter until the next `Stop`.
47825
48371
  */
47826
48372
  wait(key, signal, startedAt) {
47827
- this.waiting.get(key)?.settle({ done: true });
47828
48373
  const busy = this.busyAt.get(key);
47829
48374
  if (startedAt !== void 0 && busy !== void 0 && busy >= startedAt)
47830
48375
  return Promise.resolve({ done: true });
48376
+ this.waiting.get(key)?.settle({ done: true });
47831
48377
  const session = this.deps.find(key);
47832
48378
  if (session !== void 0 && (session.sealed || session.pendingTerminal === true))
47833
48379
  return Promise.resolve({ done: true });
@@ -48182,6 +48728,14 @@ function expiredAt(command2, now) {
48182
48728
  }
48183
48729
  async function applyCommands(commands, deps) {
48184
48730
  const now = deps.now();
48731
+ if (newCommands(commands, deps).fresh.some(
48732
+ (command2) => command2.session_uuid === null && command2.command === "refresh_bundle" && !expiredAt(command2, now)
48733
+ ))
48734
+ await deps.refreshBundle();
48735
+ const seal2 = () => sealNewCommands(commands, deps, now);
48736
+ return deps.exclusive !== void 0 ? deps.exclusive(seal2) : seal2();
48737
+ }
48738
+ function newCommands(commands, deps) {
48185
48739
  const fresh = [];
48186
48740
  const repeated = [];
48187
48741
  for (const command2 of commands) {
@@ -48189,10 +48743,10 @@ async function applyCommands(commands, deps) {
48189
48743
  repeated.push(command2.id);
48190
48744
  else fresh.push(command2);
48191
48745
  }
48192
- if (fresh.some(
48193
- (command2) => command2.session_uuid === null && command2.command === "refresh_bundle" && !expiredAt(command2, now)
48194
- ))
48195
- await deps.refreshBundle();
48746
+ return { fresh, repeated };
48747
+ }
48748
+ function sealNewCommands(commands, deps, now) {
48749
+ const { fresh, repeated } = newCommands(commands, deps);
48196
48750
  const result2 = sealCommands(fresh, deps, now);
48197
48751
  for (const ack of result2.acknowledgements) deps.handled?.remember(ack);
48198
48752
  for (const id2 of repeated) {
@@ -53461,6 +54015,19 @@ import {
53461
54015
  } from "node:http";
53462
54016
  import { Agent as HttpsAgent, request as httpsRequest } from "node:https";
53463
54017
  import { brotliDecompress, gunzip, inflate, zstdDecompress } from "node:zlib";
54018
+ async function landedWithin(landing, ms) {
54019
+ let timer;
54020
+ try {
54021
+ return await Promise.race([
54022
+ landing.then(() => true),
54023
+ new Promise((resolve26) => {
54024
+ timer = setTimeout(() => resolve26(false), ms);
54025
+ })
54026
+ ]);
54027
+ } finally {
54028
+ if (timer !== void 0) clearTimeout(timer);
54029
+ }
54030
+ }
53464
54031
  function streamsAnthropicMessages(route, body2) {
53465
54032
  return route.api === "anthropic.messages" && body2?.["stream"] === true;
53466
54033
  }
@@ -53651,6 +54218,7 @@ function createModelProxy(deps) {
53651
54218
  const maxRequestBytes = deps.maxRequestBytes ?? DEFAULT_MAX_REQUEST_BYTES;
53652
54219
  const upstreamIdleMs = deps.upstreamIdleMs ?? DEFAULT_UPSTREAM_IDLE_MS;
53653
54220
  const hookTimeoutMs = deps.beforeForwardTimeoutMs ?? DEFAULT_BEFORE_FORWARD_TIMEOUT_MS;
54221
+ const refusalWaitMs = deps.refusalFrameWaitMs ?? DEFAULT_REFUSAL_FRAME_WAIT_MS;
53654
54222
  const httpAgent = new HttpAgent({ keepAlive: true, maxSockets: Infinity });
53655
54223
  const httpsAgent = new HttpsAgent({ keepAlive: true, maxSockets: Infinity });
53656
54224
  const inFlight = /* @__PURE__ */ new Map();
@@ -53776,10 +54344,10 @@ function createModelProxy(deps) {
53776
54344
  if (known !== void 0 && !known.sealed && !known.pendingTerminal)
53777
54345
  return { record: known, how };
53778
54346
  if (known !== void 0) {
53779
- const open6 = deps.registry.list().find(
54347
+ const open7 = deps.registry.list().find(
53780
54348
  (record2) => record2.harnessSessionId === id2 && !record2.sealed && !record2.pendingTerminal
53781
54349
  );
53782
- if (open6 !== void 0) return { record: open6, how };
54350
+ if (open7 !== void 0) return { record: open7, how };
53783
54351
  return { how: "session_closed" };
53784
54352
  }
53785
54353
  const { record } = deps.registry.ensure(id2, { harness, ambient: true });
@@ -54006,59 +54574,75 @@ function createModelProxy(deps) {
54006
54574
  found = { how: "unattributed" };
54007
54575
  }
54008
54576
  return {
54577
+ ...found.record !== void 0 ? { record: found.record } : {},
54009
54578
  recorder: found.record?.recorder ?? deps.hostRecorder(),
54010
54579
  how: found.how,
54011
54580
  attrs: attrsFor(route, found.how)
54012
54581
  };
54013
54582
  }
54583
+ function landingChain(record, how) {
54584
+ if (record === void 0) return { chain: deps.hostRecorder(), attrs: {} };
54585
+ const owner = deps.registry.byUuid(record.recorder.sessionUuid);
54586
+ if (owner !== void 0 && !owner.sealed && owner.pendingTerminal !== true)
54587
+ return { chain: owner.recorder, attrs: {} };
54588
+ return {
54589
+ chain: deps.hostRecorder(),
54590
+ attrs: {
54591
+ "oxagen.correlation": "session_closed",
54592
+ "oxagen.session_correlation": how,
54593
+ "oxagen.session_uuid": record.recorder.sessionUuid
54594
+ }
54595
+ };
54596
+ }
54014
54597
  function sealNotForwarded(req, route, attempt, reason3, status2) {
54015
54598
  if (attempt.done) return;
54016
54599
  attempt.done = true;
54017
54600
  if (route.api === "other") return;
54018
- let recorder;
54019
- let mark;
54601
+ let session;
54602
+ const lost = (error) => {
54603
+ deps.log(
54604
+ `model proxy: sealing the frame of a call it did not forward (${reason3}) failed: ${error instanceof Error ? error.message : String(error)} session=${session ?? "unattributed"}`
54605
+ );
54606
+ };
54020
54607
  try {
54021
54608
  const attribution2 = attempt.attribution ?? attributeByHeaders(req, route);
54022
- const chain = attribution2.recorder;
54023
- recorder = chain;
54024
- mark = chain.markChain();
54609
+ session = attribution2.recorder.sessionUuid;
54025
54610
  const at2 = deps.now();
54026
- deps.record([
54027
- chain.sealCollectorEvent(
54028
- "error",
54029
- {
54030
- provider: route.provider,
54031
- ...attempt.model !== void 0 ? { model: attempt.model.slice(0, 512) } : {},
54032
- ...status2 !== void 0 ? { api_status_code: status2 } : {},
54033
- api_error_class: reason3,
54034
- api_duration_ms: Math.max(0, at2 - attempt.startedAt)
54035
- },
54036
- {
54037
- ts: toProtocolTimestamp(at2),
54038
- fidelity: "proxy",
54039
- attrs: {
54040
- ...attribution2.attrs,
54041
- [NOT_FORWARDED_ATTR]: reason3,
54042
- "oxagen.provider": route.provider,
54043
- "oxagen.request_bytes_read": String(attempt.bytesRead),
54044
- ...attempt.request !== void 0 ? { "oxagen.request_digest": digestBytes(attempt.request) } : {}
54045
- }
54046
- }
54047
- )
54048
- ]);
54611
+ const body2 = {
54612
+ provider: route.provider,
54613
+ ...attempt.model !== void 0 ? { model: attempt.model.slice(0, 512) } : {},
54614
+ ...status2 !== void 0 ? { api_status_code: status2 } : {},
54615
+ api_error_class: reason3,
54616
+ api_duration_ms: Math.max(0, at2 - attempt.startedAt)
54617
+ };
54618
+ const attrs2 = {
54619
+ ...attribution2.attrs,
54620
+ [NOT_FORWARDED_ATTR]: reason3,
54621
+ "oxagen.provider": route.provider,
54622
+ "oxagen.request_bytes_read": String(attempt.bytesRead),
54623
+ ...attempt.request !== void 0 ? { "oxagen.request_digest": digestBytes(attempt.request) } : {}
54624
+ };
54625
+ const land = () => {
54626
+ const landing = landingChain(attribution2.record, attribution2.how);
54627
+ recordOnChain(
54628
+ landing.chain,
54629
+ (chain) => [
54630
+ chain.sealCollectorEvent("error", body2, {
54631
+ ts: toProtocolTimestamp(at2),
54632
+ fidelity: "proxy",
54633
+ attrs: { ...attrs2, ...landing.attrs }
54634
+ })
54635
+ ],
54636
+ deps.record
54637
+ );
54638
+ };
54639
+ if (attribution2.record === void 0) land();
54640
+ else
54641
+ void onSessionQueue(deps.exclusive, attribution2.record, land).catch(
54642
+ lost
54643
+ );
54049
54644
  } catch (error) {
54050
- if (recorder !== void 0 && mark !== void 0) {
54051
- try {
54052
- recorder.rollbackChain(mark);
54053
- } catch (rollbackError) {
54054
- deps.log(
54055
- `model proxy: rolling the chain back after a failed frame failed too: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)} session=${recorder.sessionUuid}`
54056
- );
54057
- }
54058
- }
54059
- deps.log(
54060
- `model proxy: sealing the frame of a call it did not forward (${reason3}) failed: ${error instanceof Error ? error.message : String(error)} session=${recorder?.sessionUuid ?? "unattributed"}`
54061
- );
54645
+ lost(error);
54062
54646
  }
54063
54647
  }
54064
54648
  function sendProviderError(res, route, status2, code, message, retryable = false) {
@@ -54347,29 +54931,6 @@ function createModelProxy(deps) {
54347
54931
  }
54348
54932
  callsObserved += 1;
54349
54933
  observed.set(sessionKey, (observed.get(sessionKey) ?? 0) + 1);
54350
- const closed2 = record.sealed || record.pendingTerminal === true;
54351
- const requestText = payload.body.toString("utf8");
54352
- const memory = new RequestPrefixMemory();
54353
- if (payload.priorShape !== void 0 && !closed2)
54354
- memory.remember("parent", {
54355
- text: "",
54356
- fullDigest: payload.priorShape.requestDigest,
54357
- fullBytes: 0,
54358
- storedBytes: 0,
54359
- prior: void 0,
54360
- shape: payload.priorShape,
54361
- priorPayload: void 0
54362
- });
54363
- const fold = memory.fold("parent", requestText);
54364
- const capped = cappedExchange(
54365
- fold.storedBytes > TACHO_MAX_BODY_BYTES ? void 0 : fold.text,
54366
- {
54367
- text: answer.responseText,
54368
- tooLarge: answer.responseTooLarge,
54369
- bytes: answer.responseBytes
54370
- }
54371
- );
54372
- const chain = closed2 ? deps.hostRecorder() : record.recorder;
54373
54934
  const callBody2 = llmCallBody({
54374
54935
  provider: "anthropic",
54375
54936
  model,
@@ -54385,10 +54946,33 @@ function createModelProxy(deps) {
54385
54946
  failed: answer.errorClass ?? (answer.status >= 400 ? `http_${answer.status}` : void 0),
54386
54947
  requestId: answer.requestId
54387
54948
  });
54388
- const mark = chain.markChain();
54389
- try {
54390
- deps.record(
54391
- [
54949
+ const land = () => {
54950
+ const owner = deps.registry.byUuid(record.recorder.sessionUuid);
54951
+ const closed2 = owner === void 0 || owner.sealed || owner.pendingTerminal === true;
54952
+ const requestText = payload.body.toString("utf8");
54953
+ const memory = new RequestPrefixMemory();
54954
+ if (payload.priorShape !== void 0 && !closed2)
54955
+ memory.remember("parent", {
54956
+ text: "",
54957
+ fullDigest: payload.priorShape.requestDigest,
54958
+ fullBytes: 0,
54959
+ storedBytes: 0,
54960
+ prior: void 0,
54961
+ shape: payload.priorShape,
54962
+ priorPayload: void 0
54963
+ });
54964
+ const fold = memory.fold("parent", requestText);
54965
+ const capped = cappedExchange(
54966
+ fold.storedBytes > TACHO_MAX_BODY_BYTES ? void 0 : fold.text,
54967
+ {
54968
+ text: answer.responseText,
54969
+ tooLarge: answer.responseTooLarge,
54970
+ bytes: answer.responseBytes
54971
+ }
54972
+ );
54973
+ recordOnChain(
54974
+ closed2 ? deps.hostRecorder() : owner.recorder,
54975
+ (chain) => [
54392
54976
  chain.sealCollectorEvent("llm_call", callBody2, {
54393
54977
  ts: toProtocolTimestamp(settledAt),
54394
54978
  fidelity: "proxy",
@@ -54427,20 +55011,16 @@ function createModelProxy(deps) {
54427
55011
  }
54428
55012
  })
54429
55013
  ],
54430
- chain.takeBodies()
55014
+ deps.record
54431
55015
  );
54432
- } catch (error) {
54433
- try {
54434
- chain.rollbackChain(mark);
54435
- } catch (rollbackError) {
55016
+ };
55017
+ void onSessionQueue(deps.exclusive, record, land).catch(
55018
+ (error) => {
54436
55019
  deps.log(
54437
- `model proxy: rolling the chain back after a failed keep-alive frame failed too: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)} session=${sessionKey}`
55020
+ `model proxy: sealing a cache keep-alive's frame failed: ${error instanceof Error ? error.message : String(error)} session=${sessionKey}`
54438
55021
  );
54439
55022
  }
54440
- deps.log(
54441
- `model proxy: sealing a cache keep-alive's frame failed: ${error instanceof Error ? error.message : String(error)} session=${sessionKey}`
54442
- );
54443
- }
55023
+ );
54444
55024
  return usage2.cacheReadTokens ?? 0;
54445
55025
  }
54446
55026
  async function forward(req, res, route, admitted, attempt) {
@@ -54481,7 +55061,12 @@ function createModelProxy(deps) {
54481
55061
  const sessionKey = record?.recorder.sessionUuid ?? HOST_KEY;
54482
55062
  const credential = resolveCredential(req, route);
54483
55063
  const attrs2 = attrsFor(route, how, credential);
54484
- attempt.attribution = { recorder, how, attrs: attrs2 };
55064
+ attempt.attribution = {
55065
+ ...record !== void 0 ? { record } : {},
55066
+ recorder,
55067
+ how,
55068
+ attrs: attrs2
55069
+ };
54485
55070
  const metered = route.api !== "other";
54486
55071
  const { model: askedModel, ambiguous: modelAmbiguous } = modelOf(
54487
55072
  readable,
@@ -54527,44 +55112,58 @@ function createModelProxy(deps) {
54527
55112
  if (refusal !== void 0) {
54528
55113
  refused2 += 1;
54529
55114
  const view = deps.policy();
54530
- const mark = recorder.markChain();
54531
- try {
54532
- deps.record([
54533
- recorder.sealCollectorEvent(
54534
- "policy_decision",
54535
- {
54536
- policy_decision: "deny",
54537
- policy_source: refusal.source,
54538
- policy_reason_code: refusal.code,
54539
- policy_reason_digest: digestText(refusal.message),
54540
- bundle_version: view.bundle.version,
54541
- bundle_mode: view.bundle.mode
54542
- },
54543
- {
55115
+ const decision = {
55116
+ policy_decision: "deny",
55117
+ policy_source: refusal.source,
55118
+ policy_reason_code: refusal.code,
55119
+ policy_reason_digest: digestText(refusal.message),
55120
+ bundle_version: view.bundle.version,
55121
+ bundle_mode: view.bundle.mode
55122
+ };
55123
+ const decisionAttrs = {
55124
+ ...attrs2,
55125
+ "oxagen.refused": "model_call",
55126
+ "oxagen.provider": route.provider,
55127
+ "oxagen.request_digest": digestBytes(body2),
55128
+ // The model the refusal was about, when the proxy could read one. A
55129
+ // `model_not_permitted` frame that does not name the model leaves the
55130
+ // operator guessing which entry to add.
55131
+ ...askedModel !== void 0 ? { "oxagen.model": askedModel.slice(0, 512) } : {},
55132
+ ...modelAmbiguous ? { "oxagen.model_ambiguous": "true" } : {},
55133
+ ...record !== void 0 ? {
55134
+ "oxagen.session_spend_usd_micros": String(spendFor(sessionKey))
55135
+ } : {},
55136
+ ..."attrs" in refusal ? refusal.attrs : {}
55137
+ };
55138
+ const land = () => {
55139
+ const landing = landingChain(record, how);
55140
+ recordOnChain(
55141
+ landing.chain,
55142
+ (chain) => [
55143
+ chain.sealCollectorEvent("policy_decision", decision, {
54544
55144
  fidelity: "proxy",
54545
- attrs: {
54546
- ...attrs2,
54547
- "oxagen.refused": "model_call",
54548
- "oxagen.provider": route.provider,
54549
- "oxagen.request_digest": digestBytes(body2),
54550
- // The model the refusal was about, when the proxy could read
54551
- // one. A `model_not_permitted` frame that does not name the
54552
- // model leaves the operator guessing which entry to add.
54553
- ...askedModel !== void 0 ? { "oxagen.model": askedModel.slice(0, 512) } : {},
54554
- ...modelAmbiguous ? { "oxagen.model_ambiguous": "true" } : {},
54555
- ...record !== void 0 ? {
54556
- "oxagen.session_spend_usd_micros": String(
54557
- spendFor(sessionKey)
54558
- )
54559
- } : {},
54560
- ..."attrs" in refusal ? refusal.attrs : {}
54561
- }
54562
- }
54563
- )
54564
- ]);
54565
- } catch (error) {
54566
- recorder.rollbackChain(mark);
54567
- throw error;
55145
+ attrs: { ...decisionAttrs, ...landing.attrs }
55146
+ })
55147
+ ],
55148
+ deps.record
55149
+ );
55150
+ };
55151
+ if (record === void 0) land();
55152
+ else {
55153
+ let answered = false;
55154
+ const written2 = onSessionQueue(deps.exclusive, record, land);
55155
+ void written2.catch((error) => {
55156
+ if (answered)
55157
+ deps.log(
55158
+ `model proxy: the frame of a refusal already answered was not written: ${error instanceof Error ? error.message : String(error)} session=${sessionKey}`
55159
+ );
55160
+ });
55161
+ if (!await landedWithin(written2, refusalWaitMs)) {
55162
+ answered = true;
55163
+ deps.log(
55164
+ `model proxy: answered a refused call before its frame could land, because the session's queue was busy for ${refusalWaitMs} ms session=${sessionKey}`
55165
+ );
55166
+ }
54568
55167
  }
54569
55168
  attempt.done = true;
54570
55169
  deps.log(
@@ -55245,9 +55844,9 @@ function createModelProxy(deps) {
55245
55844
  callsObservedFor: (sessionUuid2) => observed.get(sessionUuid2) ?? 0,
55246
55845
  keepAliveTick: () => keepAlive.tick(),
55247
55846
  stats: () => {
55248
- let open6 = 0;
55249
- for (const calls of inFlight.values()) open6 += calls.size;
55250
- return { callsObserved, refused: refused2, inFlight: open6 };
55847
+ let open7 = 0;
55848
+ for (const calls of inFlight.values()) open7 += calls.size;
55849
+ return { callsObserved, refused: refused2, inFlight: open7 };
55251
55850
  },
55252
55851
  close: () => {
55253
55852
  clearInterval(keepAliveTimer);
@@ -55262,7 +55861,7 @@ function createModelProxy(deps) {
55262
55861
  }
55263
55862
  };
55264
55863
  }
55265
- var DEFAULT_MAX_REQUEST_BYTES, NOT_FORWARDED_ATTR, DEFAULT_UPSTREAM_IDLE_MS, DEFAULT_BEFORE_FORWARD_TIMEOUT_MS, DEFAULT_UPSTREAM_CONNECT_MS, DEFAULT_PAUSE_HOLD_MS, DEFAULT_PAUSE_STREAM_HOLD_MS, DEFAULT_PAUSE_HOLD_PING_MS, DEFAULT_PAUSE_HOLD_CHECK_MS, MAX_HELD_CALLS_PER_SESSION, PAUSE_HOLD_CODES, ANTHROPIC_PING_EVENT, HELD_STREAM_ERROR_BYTES, FREE_SOCKET_TIMEOUT_MS, KEEP_ALIVE_TIMEOUT_MS, KEEP_ALIVE_MAX_RESPONSE_BYTES, BodyCapture, EXCHANGE_OPEN_BYTES, EXCHANGE_JOIN_BYTES, DECODERS, REQUEST_EFFORT_MAX, CREDENTIAL_MESSAGES;
55864
+ var DEFAULT_MAX_REQUEST_BYTES, NOT_FORWARDED_ATTR, DEFAULT_UPSTREAM_IDLE_MS, DEFAULT_BEFORE_FORWARD_TIMEOUT_MS, DEFAULT_UPSTREAM_CONNECT_MS, DEFAULT_REFUSAL_FRAME_WAIT_MS, DEFAULT_PAUSE_HOLD_MS, DEFAULT_PAUSE_STREAM_HOLD_MS, DEFAULT_PAUSE_HOLD_PING_MS, DEFAULT_PAUSE_HOLD_CHECK_MS, MAX_HELD_CALLS_PER_SESSION, PAUSE_HOLD_CODES, ANTHROPIC_PING_EVENT, HELD_STREAM_ERROR_BYTES, FREE_SOCKET_TIMEOUT_MS, KEEP_ALIVE_TIMEOUT_MS, KEEP_ALIVE_MAX_RESPONSE_BYTES, BodyCapture, EXCHANGE_OPEN_BYTES, EXCHANGE_JOIN_BYTES, DECODERS, REQUEST_EFFORT_MAX, CREDENTIAL_MESSAGES;
55266
55865
  var init_model_proxy = __esm({
55267
55866
  "../../packages/tacho/src/collector/model-proxy.ts"() {
55268
55867
  "use strict";
@@ -55293,6 +55892,7 @@ var init_model_proxy = __esm({
55293
55892
  DEFAULT_UPSTREAM_IDLE_MS = 10 * 6e4;
55294
55893
  DEFAULT_BEFORE_FORWARD_TIMEOUT_MS = 250;
55295
55894
  DEFAULT_UPSTREAM_CONNECT_MS = 3e4;
55895
+ DEFAULT_REFUSAL_FRAME_WAIT_MS = 1e3;
55296
55896
  DEFAULT_PAUSE_HOLD_MS = 12e4;
55297
55897
  DEFAULT_PAUSE_STREAM_HOLD_MS = 12 * 60 * 6e4;
55298
55898
  DEFAULT_PAUSE_HOLD_PING_MS = 15e3;
@@ -55477,7 +56077,7 @@ var init_model_proxy_listener = __esm({
55477
56077
  import {
55478
56078
  createServer as createServer5
55479
56079
  } from "node:http";
55480
- import { chmodSync as chmodSync9, existsSync as existsSync23, statSync as statSync12, unlinkSync as unlinkSync11 } from "node:fs";
56080
+ import { chmodSync as chmodSync9, existsSync as existsSync23, statSync as statSync13, unlinkSync as unlinkSync11 } from "node:fs";
55481
56081
  import { randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
55482
56082
  import { createConnection } from "node:net";
55483
56083
  function isExportFormat(value) {
@@ -55853,7 +56453,7 @@ function createRequestHandler(api, log, options = {}) {
55853
56453
  }
55854
56454
  async function removeStaleSocket(socketPath) {
55855
56455
  if (!existsSync23(socketPath)) return;
55856
- if (!statSync12(socketPath).isSocket()) {
56456
+ if (!statSync13(socketPath).isSocket()) {
55857
56457
  throw new Error(`collector socket path is not a socket: ${socketPath}`);
55858
56458
  }
55859
56459
  const stale = await new Promise((resolve26, reject) => {
@@ -56022,7 +56622,7 @@ var init_server = __esm({
56022
56622
  });
56023
56623
 
56024
56624
  // ../../packages/tacho/src/collector/refused.ts
56025
- import { readdirSync as readdirSync12, readFileSync as readFileSync20, statSync as statSync13 } from "node:fs";
56625
+ import { readdirSync as readdirSync12, readFileSync as readFileSync20, statSync as statSync14 } from "node:fs";
56026
56626
  import { join as join45 } from "node:path";
56027
56627
  function refusalRetryable(refusal) {
56028
56628
  return refusal === "malformed" || refusal === "parked";
@@ -56072,7 +56672,7 @@ function readRefusedEntry(file) {
56072
56672
  let mtimeMs;
56073
56673
  try {
56074
56674
  record = JSON.parse(readFileSync20(file, "utf8"));
56075
- mtimeMs = statSync13(file).mtimeMs;
56675
+ mtimeMs = statSync14(file).mtimeMs;
56076
56676
  } catch {
56077
56677
  return void 0;
56078
56678
  }
@@ -57025,7 +57625,7 @@ import {
57025
57625
  readdirSync as readdirSync13,
57026
57626
  readFileSync as readFileSync22,
57027
57627
  renameSync as renameSync4,
57028
- statSync as statSync14,
57628
+ statSync as statSync15,
57029
57629
  unlinkSync as unlinkSync13
57030
57630
  } from "node:fs";
57031
57631
  import { readdir as readdir3, readFile as readFile3, stat as stat2 } from "node:fs/promises";
@@ -57186,6 +57786,20 @@ function reconcileRestoredCursor(state, wal, log) {
57186
57786
  for (const child of Object.values(state.children))
57187
57787
  reconcileRestoredCursor(child.state, wal, log);
57188
57788
  }
57789
+ function startupClock(log, clock2 = () => performance.now()) {
57790
+ const began = clock2();
57791
+ let since = began;
57792
+ return {
57793
+ phase: (name) => {
57794
+ const at2 = clock2();
57795
+ log(`startup phase ${name} took ${Math.round(at2 - since)}ms`);
57796
+ since = at2;
57797
+ },
57798
+ ready: () => {
57799
+ log(`startup took ${Math.round(clock2() - began)}ms to ready`);
57800
+ }
57801
+ };
57802
+ }
57189
57803
  async function startDaemon(options) {
57190
57804
  const loaded = options.host ?? readHostFile(options.paths.hostFile);
57191
57805
  if (loaded === void 0) {
@@ -57201,6 +57815,7 @@ async function startDaemon(options) {
57201
57815
  `);
57202
57816
  });
57203
57817
  const server = createCollectorServer(() => ready, log);
57818
+ const clock2 = startupClock(log);
57204
57819
  let port;
57205
57820
  try {
57206
57821
  if (options.listen ?? true) {
@@ -57210,21 +57825,24 @@ async function startDaemon(options) {
57210
57825
  port: options.port ?? loaded.port
57211
57826
  })).port;
57212
57827
  }
57828
+ clock2.phase("listeners");
57213
57829
  return await initializeDaemon(
57214
57830
  { ...options, host: loaded, log },
57215
57831
  server,
57216
57832
  port,
57217
57833
  (api) => {
57218
57834
  ready = api;
57219
- }
57835
+ },
57836
+ clock2
57220
57837
  );
57221
57838
  } catch (error) {
57222
57839
  await server.close();
57223
57840
  throw error;
57224
57841
  }
57225
57842
  }
57226
- async function initializeDaemon(options, server, port, ready) {
57227
- const paths = options.paths;
57843
+ async function initializeDaemon(options, server, port, ready, clock2) {
57844
+ const paths = withRecordedHarnessFiles(options.paths, options.host);
57845
+ const harnessHomes = recordedHarnessHomes(options.host);
57228
57846
  const now = options.now ?? (() => Date.now());
57229
57847
  const log = options.log;
57230
57848
  const timers = { ...DEFAULT_TIMERS, ...options.timers };
@@ -57354,6 +57972,7 @@ async function initializeDaemon(options, server, port, ready) {
57354
57972
  },
57355
57973
  (movedTo) => logSetAside("WAL cursor", movedTo)
57356
57974
  );
57975
+ clock2.phase("setup");
57357
57976
  for (const session of wal.sessions()) {
57358
57977
  const outcome = wal.repairTail(session);
57359
57978
  if (outcome !== "ok")
@@ -57362,6 +57981,7 @@ async function initializeDaemon(options, server, port, ready) {
57362
57981
  const orphanBodies = wal.repairOrphanBodies();
57363
57982
  if (orphanBodies > 0)
57364
57983
  log(`WAL removed ${orphanBodies} body lines a crash left with no event`);
57984
+ clock2.phase("WAL repair");
57365
57985
  context.chainTail = (sessionUuid2) => {
57366
57986
  const head2 = wal.lastEvent(sessionUuid2);
57367
57987
  return head2 === void 0 ? void 0 : { ...cursorAfter(head2), stopped: head2.kind === "agent_stop" };
@@ -57374,16 +57994,21 @@ async function initializeDaemon(options, server, port, ready) {
57374
57994
  processStarts: hookProcessStarts,
57375
57995
  adoptedChain: (sessionUuid2) => backfillLedger.chainHead(sessionUuid2)
57376
57996
  });
57377
- const priorStateWrittenAt = existsSync25(paths.daemonState) ? statSync14(paths.daemonState).mtimeMs : void 0;
57997
+ const priorStateWrittenAt = existsSync25(paths.daemonState) ? statSync15(paths.daemonState).mtimeMs : void 0;
57378
57998
  const sealedState = parseSealedState(
57379
57999
  readJsonStateFile(
57380
58000
  paths.daemonSealedState,
57381
58001
  (movedTo) => logSetAside("daemon sealed state", movedTo)
57382
58002
  )
57383
58003
  );
58004
+ let reconcileMs = 0;
58005
+ const reconcile = (state) => {
58006
+ const at2 = performance.now();
58007
+ reconcileRestoredCursor(state, wal, log);
58008
+ reconcileMs += performance.now() - at2;
58009
+ };
57384
58010
  if (sealedState !== void 0) {
57385
- for (const session of sealedState.sessions)
57386
- reconcileRestoredCursor(session.recorder, wal, log);
58011
+ for (const session of sealedState.sessions) reconcile(session.recorder);
57387
58012
  registry2.restore(
57388
58013
  { schema: "tacho.daemon-state.v1", sessions: sealedState.sessions },
57389
58014
  { sealedFile: true }
@@ -57396,8 +58021,7 @@ async function initializeDaemon(options, server, port, ready) {
57396
58021
  )
57397
58022
  );
57398
58023
  if (persisted !== void 0) {
57399
- for (const session of persisted.sessions)
57400
- reconcileRestoredCursor(session.recorder, wal, log);
58024
+ for (const session of persisted.sessions) reconcile(session.recorder);
57401
58025
  registry2.restore(persisted);
57402
58026
  }
57403
58027
  const journaledHookIds = restoreHookIdJournal(
@@ -57405,6 +58029,8 @@ async function initializeDaemon(options, server, port, ready) {
57405
58029
  registry2,
57406
58030
  (uuid) => wal.lastEvent(uuid)?.seq
57407
58031
  );
58032
+ log(`startup phase cursor reconcile took ${Math.round(reconcileMs)}ms`);
58033
+ clock2.phase("state restore");
57408
58034
  const bootId = `tachod-${ulid(now())}`;
57409
58035
  const hostRecord = registry2.ensure(bootId, { pid: process.pid }).record;
57410
58036
  const hostRecorder = hostRecord.recorder;
@@ -57521,7 +58147,8 @@ async function initializeDaemon(options, server, port, ready) {
57521
58147
  },
57522
58148
  ...options.workOrders?.spawn !== void 0 ? { spawn: options.workOrders.spawn } : {},
57523
58149
  ...options.workOrders?.isAlive !== void 0 ? { isAlive: options.workOrders.isAlive } : {},
57524
- ...options.workOrders?.findCheckout !== void 0 ? { findCheckout: options.workOrders.findCheckout } : {}
58150
+ ...options.workOrders?.findCheckout !== void 0 ? { findCheckout: options.workOrders.findCheckout } : {},
58151
+ ...options.workOrders?.wait !== void 0 ? { wait: options.workOrders.wait } : {}
57525
58152
  });
57526
58153
  void workOrderAutostart.clearDeadMarks();
57527
58154
  function persistState(options2 = {}) {
@@ -57736,25 +58363,24 @@ async function initializeDaemon(options, server, port, ready) {
57736
58363
  });
57737
58364
  }
57738
58365
  async function applyCommandsRecorded(commands, deps) {
57739
- let marks = markCommandChains(commands);
57740
- let result2;
57741
- try {
57742
- result2 = await applyCommands2(commands, {
57743
- ...deps,
57744
- refreshBundle: async () => {
57745
- marks = [];
57746
- await deps.refreshBundle();
57747
- marks = markCommandChains(commands);
58366
+ return applyCommands2(commands, {
58367
+ ...deps,
58368
+ exclusive: (seal2) => queues.host(async () => {
58369
+ if (hostRecord.sealed) throw new Error(STOPPING);
58370
+ const marks = markCommandChains(commands);
58371
+ let result2;
58372
+ try {
58373
+ result2 = seal2();
58374
+ record(result2.events);
58375
+ return result2;
58376
+ } catch (error) {
58377
+ for (const { session, mark } of [...marks].reverse())
58378
+ session.recorder.rollbackChain(mark);
58379
+ result2?.restore();
58380
+ throw error;
57748
58381
  }
57749
- });
57750
- record(result2.events);
57751
- return result2;
57752
- } catch (error) {
57753
- for (const { session, mark } of [...marks].reverse())
57754
- session.recorder.rollbackChain(mark);
57755
- result2?.restore();
57756
- throw error;
57757
- }
58382
+ })
58383
+ });
57758
58384
  }
57759
58385
  let modelBaseUrls = [];
57760
58386
  const spoolFacts = /* @__PURE__ */ new Map();
@@ -57876,6 +58502,31 @@ async function initializeDaemon(options, server, port, ready) {
57876
58502
  }
57877
58503
  }
57878
58504
  function retryOwedBodyPurge() {
58505
+ if (existsSync25(bodyPurgeOwedPath)) void settleOwedBodyPurge("owed");
58506
+ }
58507
+ let bodyPurge;
58508
+ let bodyPurgeAgain = false;
58509
+ let bodyPurgeNarrowing = false;
58510
+ function settleOwedBodyPurge(trigger) {
58511
+ if (trigger === "narrowed") bodyPurgeNarrowing = true;
58512
+ if (bodyPurge !== void 0) {
58513
+ bodyPurgeAgain = true;
58514
+ return bodyPurge;
58515
+ }
58516
+ const run3 = (async () => {
58517
+ do {
58518
+ bodyPurgeAgain = false;
58519
+ const narrowing = bodyPurgeNarrowing;
58520
+ bodyPurgeNarrowing = false;
58521
+ await sweepOwedBodies(narrowing);
58522
+ } while (bodyPurgeAgain && !stopped2);
58523
+ })().finally(() => {
58524
+ if (bodyPurge === run3) bodyPurge = void 0;
58525
+ });
58526
+ bodyPurge = run3;
58527
+ return run3;
58528
+ }
58529
+ async function sweepOwedBodies(narrowing) {
57879
58530
  const owed = owedBodyPurge();
57880
58531
  if (owed === void 0) return;
57881
58532
  let clause = owed.retention;
@@ -57885,16 +58536,28 @@ async function initializeDaemon(options, server, port, ready) {
57885
58536
  clause = inForce.mandate;
57886
58537
  }
57887
58538
  try {
57888
- const purged = wal.purgeBodiesOutsideMandate(clause) + purgePendingEndBodies(clause);
58539
+ const purged = await wal.purgeBodiesOutsideMandateAsync(clause, {
58540
+ signal: stopping.signal
58541
+ }) + purgePendingEndBodies(clause);
57889
58542
  purgeQueuedPrompts(clause);
57890
- clearBodyPurgeOwed();
58543
+ if (!bodyPurgeAgain) clearBodyPurgeOwed();
57891
58544
  log(
57892
- `completed an owed body purge: erased ${purged} queued body(ies) the mandate does not cover`
58545
+ narrowing ? `mandate narrowed (${owed.classes.join(", ")}): erased ${purged} queued body(ies) from the WAL` : `completed an owed body purge: erased ${purged} queued body(ies) the mandate does not cover`
57893
58546
  );
57894
58547
  } catch (error) {
57895
- log(
57896
- `an owed body purge failed again; content remains in the WAL: ${error instanceof Error ? error.message : String(error)}; check free space and permissions under ${paths.wal}. The purge is retried at each bundle confirmation`
57897
- );
58548
+ const reason3 = error instanceof Error ? error.message : String(error);
58549
+ if (stopping.signal.aborted)
58550
+ log(
58551
+ "stop: left an owed body purge unfinished; the next start finishes it"
58552
+ );
58553
+ else if (narrowing)
58554
+ log(
58555
+ `failed to erase bodies the narrowed mandate no longer covers (${owed.classes.join(", ")}); content remains in the WAL: ${reason3}; check free space and permissions under ${paths.wal}. The purge is retried at each bundle confirmation`
58556
+ );
58557
+ else
58558
+ log(
58559
+ `an owed body purge failed again; content remains in the WAL: ${reason3}; check free space and permissions under ${paths.wal}. The purge is retried at each bundle confirmation`
58560
+ );
57898
58561
  }
57899
58562
  }
57900
58563
  function purgeQueuedPrompts(clause) {
@@ -57917,21 +58580,30 @@ async function initializeDaemon(options, server, port, ready) {
57917
58580
  const dropped = HOST_RETENTION_CLASSES.filter(
57918
58581
  (contentClass) => retentionAllows(previous, contentClass) && !retentionAllows(next, contentClass)
57919
58582
  );
57920
- if (dropped.length === 0) return true;
57921
- const recorded = markBodyPurgeOwed(next, dropped);
58583
+ if (dropped.length === 0) return "none";
58584
+ if (markBodyPurgeOwed(next, dropped)) {
58585
+ try {
58586
+ purgePendingEndBodies(next);
58587
+ purgeQueuedPrompts(next);
58588
+ } catch (error) {
58589
+ log(
58590
+ `failed to erase bodies the narrowed mandate no longer covers (${dropped.join(", ")}); content remains on disk: ${error instanceof Error ? error.message : String(error)}`
58591
+ );
58592
+ }
58593
+ return "owed";
58594
+ }
57922
58595
  try {
57923
58596
  const purged = wal.purgeBodiesOutsideMandate(next) + purgePendingEndBodies(next);
57924
58597
  purgeQueuedPrompts(next);
57925
- clearBodyPurgeOwed();
57926
58598
  log(
57927
58599
  `mandate narrowed (${dropped.join(", ")}): erased ${purged} queued body(ies) from the WAL`
57928
58600
  );
57929
- return true;
58601
+ return "swept";
57930
58602
  } catch (error) {
57931
58603
  log(
57932
58604
  `failed to erase bodies the narrowed mandate no longer covers (${dropped.join(", ")}); content remains in the WAL: ${error instanceof Error ? error.message : String(error)}`
57933
58605
  );
57934
- return recorded;
58606
+ return "refused";
57935
58607
  }
57936
58608
  }
57937
58609
  async function fetchBundle() {
@@ -57963,10 +58635,11 @@ async function initializeDaemon(options, server, port, ready) {
57963
58635
  );
57964
58636
  return "refused";
57965
58637
  }
57966
- if (!purgeBodiesNarrowedOut(
58638
+ const narrowing = purgeBodiesNarrowedOut(
57967
58639
  host.bundle.retention,
57968
58640
  response.bundle.retention
57969
- )) {
58641
+ );
58642
+ if (narrowing === "refused") {
57970
58643
  log(
57971
58644
  "refusing to cache a narrowed bundle this host can neither enforce on disk nor remember owing; the old etag stands so the next poll retries"
57972
58645
  );
@@ -57979,6 +58652,7 @@ async function initializeDaemon(options, server, port, ready) {
57979
58652
  bundleVerified = true;
57980
58653
  mandateConfirmedAt = now();
57981
58654
  log(`bundle ${response.bundle.version} (${response.bundle.etag}) cached`);
58655
+ if (narrowing === "owed") void settleOwedBodyPurge("narrowed");
57982
58656
  return "cached";
57983
58657
  } catch (error) {
57984
58658
  log(
@@ -58341,7 +59015,8 @@ async function initializeDaemon(options, server, port, ready) {
58341
59015
  lastIngestAt ?? -Infinity,
58342
59016
  lastCommandPollAt ?? -Infinity
58343
59017
  );
58344
- if (pendingAcks.length === 0 && now() - lastEnvelopeAt < timers.commandsPollMs) {
59018
+ const pollEveryMs = registry2.live().some((session) => session !== hostRecord) ? timers.commandsPollMs : timers.commandsPollMs * IDLE_COMMAND_POLL_FACTOR;
59019
+ if (pendingAcks.length === 0 && now() - lastEnvelopeAt < pollEveryMs) {
58345
59020
  return;
58346
59021
  }
58347
59022
  if (now() < commandPollNextAttemptAt) return;
@@ -58614,9 +59289,15 @@ async function initializeDaemon(options, server, port, ready) {
58614
59289
  }) : void 0;
58615
59290
  void drained?.catch(() => void 0);
58616
59291
  const answer = await queues.session(harnessSessionId, async () => {
58617
- if (options2.refuseWhenStopping && stopped2) throw new Error(STOPPING);
58618
- if (drained !== void 0) await drained;
58619
- return handleHookInner(envelope2, "session");
59292
+ let inner;
59293
+ try {
59294
+ if (options2.refuseWhenStopping && stopped2) throw new Error(STOPPING);
59295
+ if (drained !== void 0) await drained;
59296
+ inner = await handleHookInner(envelope2, "session");
59297
+ return inner;
59298
+ } finally {
59299
+ options2.answered?.(inner?.response);
59300
+ }
58620
59301
  });
58621
59302
  if (answer.durable !== void 0) await answer.durable;
58622
59303
  return answer.response;
@@ -58742,6 +59423,8 @@ async function initializeDaemon(options, server, port, ready) {
58742
59423
  restoreEveryQueue(queued);
58743
59424
  throw error;
58744
59425
  }
59426
+ const workOrder = workOrderIdOf(envelope2.env?.[WORK_ORDER_ENV]);
59427
+ if (workOrder !== void 0) workOrderAutostart.noteSession(workOrder);
58745
59428
  if (pending !== void 0) {
58746
59429
  const state = registry2.state();
58747
59430
  const key = sessionMapKey(harnessSessionId, {
@@ -59244,7 +59927,8 @@ async function initializeDaemon(options, server, port, ready) {
59244
59927
  const state = await readModelBaseUrlState({
59245
59928
  home: options.home ?? homedir8(),
59246
59929
  port: modelProxyPortFor(host),
59247
- stellaHome: dirname26(options.paths.stellaToml),
59930
+ stellaHome: dirname26(paths.stellaToml),
59931
+ ...harnessHomes,
59248
59932
  harnesses: ["claude-code", "codex", "stella"]
59249
59933
  });
59250
59934
  const next = {};
@@ -59568,18 +60252,24 @@ async function initializeDaemon(options, server, port, ready) {
59568
60252
  quarantineChanged();
59569
60253
  return Promise.reject(readable.error);
59570
60254
  }
59571
- if (envelope2.replay === void 0 && readable.data.agent_id === void 0 && STEER_WAIT_RELEASING_EVENTS.has(readable.data.hook_event_name))
59572
- steerWaiters.release(
59573
- sessionMapKey(readable.data.session_id, {
59574
- harness: envelope2.harness,
59575
- customAgent: envelope2.agent
59576
- }),
59577
- now()
59578
- );
60255
+ const waiterKey = envelope2.replay === void 0 && readable.data.agent_id === void 0 && STEER_WAIT_RELEASING_EVENTS.has(readable.data.hook_event_name) ? sessionMapKey(readable.data.session_id, {
60256
+ harness: envelope2.harness,
60257
+ customAgent: envelope2.agent
60258
+ }) : void 0;
60259
+ const arrivedAt = waiterKey !== void 0 ? now() : 0;
60260
+ const releasesAfterAnswer = waiterKey !== void 0 && STEER_WAIT_RELEASED_AFTER_ANSWER.has(readable.data.hook_event_name);
60261
+ if (waiterKey !== void 0 && !releasesAfterAnswer)
60262
+ steerWaiters.release(waiterKey, arrivedAt);
59579
60263
  const timed = envelope2.answer_by === void 0 && envelope2.answer_within_ms !== void 0 ? { ...envelope2, answer_by: now() + envelope2.answer_within_ms } : envelope2;
59580
60264
  return answerHook(timed, readable.data.session_id, {
59581
60265
  drainSpoolFirst: true,
59582
- refuseWhenStopping: true
60266
+ refuseWhenStopping: true,
60267
+ ...waiterKey !== void 0 && releasesAfterAnswer ? {
60268
+ answered: (response) => {
60269
+ if (response === void 0 || !answerBlocksAgent(response))
60270
+ steerWaiters.release(waiterKey, arrivedAt);
60271
+ }
60272
+ } : {}
59583
60273
  });
59584
60274
  },
59585
60275
  // Each session the post names takes the post's records on its own queue,
@@ -59736,6 +60426,7 @@ async function initializeDaemon(options, server, port, ready) {
59736
60426
  return exportSession(events, format);
59737
60427
  }
59738
60428
  };
60429
+ clock2.phase("services");
59739
60430
  if (!hostRecorder.hasStarted) {
59740
60431
  record([
59741
60432
  hostRecorder.sealCollectorEvent("agent_start", {
@@ -59771,6 +60462,7 @@ async function initializeDaemon(options, server, port, ready) {
59771
60462
  );
59772
60463
  }
59773
60464
  }
60465
+ clock2.phase("restart gaps");
59774
60466
  try {
59775
60467
  const day = utcDay(now());
59776
60468
  startupDaySpend = { day, micros: walDaySpendMicros(day) };
@@ -59779,7 +60471,9 @@ async function initializeDaemon(options, server, port, ready) {
59779
60471
  `day spend not read from the WAL at startup: ${error instanceof Error ? error.message : String(error)}`
59780
60472
  );
59781
60473
  }
60474
+ clock2.phase("day spend");
59782
60475
  ready(api);
60476
+ clock2.ready();
59783
60477
  servingSince = now();
59784
60478
  if (options.listen ?? true) {
59785
60479
  const unixSocket = (options.platform ?? process.platform) !== "win32";
@@ -59807,7 +60501,7 @@ async function initializeDaemon(options, server, port, ready) {
59807
60501
  if (!name.endsWith(".json")) continue;
59808
60502
  const file = join47(paths.quarantine, name);
59809
60503
  try {
59810
- const refusedAt = readRefusedEntry(file)?.refusedAt ?? statSync14(file).mtimeMs;
60504
+ const refusedAt = readRefusedEntry(file)?.refusedAt ?? statSync15(file).mtimeMs;
59811
60505
  if (at2 - refusedAt < retainMs) continue;
59812
60506
  unlinkSync13(file);
59813
60507
  removed += 1;
@@ -60080,7 +60774,11 @@ async function initializeDaemon(options, server, port, ready) {
60080
60774
  else if (typeof refreshed === "object")
60081
60775
  log(`ChatGPT login refresh: ${refreshed.failed}`);
60082
60776
  if (refreshed !== "none")
60083
- restateCodexLastRefresh(options.home ?? homedir8(), now());
60777
+ restateCodexLastRefresh(
60778
+ options.home ?? homedir8(),
60779
+ now(),
60780
+ harnessHomes
60781
+ );
60084
60782
  } catch (error) {
60085
60783
  log(
60086
60784
  `ChatGPT login refresh: ${error instanceof Error ? error.message : String(error)}`
@@ -60093,6 +60791,7 @@ async function initializeDaemon(options, server, port, ready) {
60093
60791
  await renewStaticToken(harness, {
60094
60792
  home: options.home ?? homedir8(),
60095
60793
  stellaHome: dirname26(paths.stellaToml),
60794
+ ...harnessHomes,
60096
60795
  host,
60097
60796
  runTokenKeyPath: paths.runTokenKey,
60098
60797
  now,
@@ -60222,6 +60921,9 @@ async function initializeDaemon(options, server, port, ready) {
60222
60921
  drainSpool: () => queues.host(() => drainSpool()),
60223
60922
  queues,
60224
60923
  refreshBundle: () => refreshBundle({ force: true }),
60924
+ bodyPurgeIdle: async () => {
60925
+ while (bodyPurge !== void 0) await bodyPurge;
60926
+ },
60225
60927
  workOrdersIdle: () => workOrderAutostart.idle(),
60226
60928
  stop: async () => {
60227
60929
  if (stopped2) return;
@@ -60240,7 +60942,8 @@ async function initializeDaemon(options, server, port, ready) {
60240
60942
  }
60241
60943
  const [gitSettled, memorySettled] = await Promise.all([
60242
60944
  settleWithin(gitLane, timers.stopLaneMs),
60243
- settleWithin(memoryScan, timers.stopLaneMs)
60945
+ settleWithin(memoryScan, timers.stopLaneMs),
60946
+ settleWithin(bodyPurge, timers.stopLaneMs)
60244
60947
  ]);
60245
60948
  if (!gitSettled)
60246
60949
  log(
@@ -60281,7 +60984,7 @@ async function initializeDaemon(options, server, port, ready) {
60281
60984
  }
60282
60985
  };
60283
60986
  }
60284
- var DEFAULT_TIMERS, STATIC_TOKEN_RENEWAL_CHECK_MS, CHATGPT_LOGIN_CHECK_MS, MEMORY_SCAN_MS, CLOUD_GATEWAY_ORIGIN, CLOUD_GATEWAY_BASE_PATH, QUEUED_PROMPT_CLASS, TRACE_DRAIN_TIMEOUT_MS, RECORDER_EVENTS_KEPT, owedBodyPurgeSchema, objectSchema, spoolFileSchema, otlpResourceListSchema, otlpPayloadSchema, defaultExecAsync, STOPPING, STOPPED_EXEC, STATE_WRITE_DEFER_MS;
60987
+ var IDLE_COMMAND_POLL_FACTOR, DEFAULT_TIMERS, STATIC_TOKEN_RENEWAL_CHECK_MS, CHATGPT_LOGIN_CHECK_MS, MEMORY_SCAN_MS, CLOUD_GATEWAY_ORIGIN, CLOUD_GATEWAY_BASE_PATH, QUEUED_PROMPT_CLASS, TRACE_DRAIN_TIMEOUT_MS, RECORDER_EVENTS_KEPT, owedBodyPurgeSchema, objectSchema, spoolFileSchema, otlpResourceListSchema, otlpPayloadSchema, defaultExecAsync, STOPPING, STOPPED_EXEC, STATE_WRITE_DEFER_MS;
60285
60988
  var init_daemon = __esm({
60286
60989
  "../../packages/tacho/src/collector/daemon.ts"() {
60287
60990
  "use strict";
@@ -60357,6 +61060,7 @@ var init_daemon = __esm({
60357
61060
  init_refused();
60358
61061
  init_session_heads();
60359
61062
  init_transcript_tailer();
61063
+ IDLE_COMMAND_POLL_FACTOR = 8;
60360
61064
  DEFAULT_TIMERS = {
60361
61065
  shipMs: 5e3,
60362
61066
  bundleRefreshMs: 5 * 6e4,
@@ -60650,7 +61354,7 @@ import {
60650
61354
  lstatSync as lstatSync8,
60651
61355
  mkdirSync as mkdirSync7,
60652
61356
  openSync as openSync8,
60653
- readSync as readSync4,
61357
+ readSync as readSync5,
60654
61358
  realpathSync as realpathSync6,
60655
61359
  writeFileSync as writeFileSync4
60656
61360
  } from "node:fs";
@@ -60696,11 +61400,11 @@ function readBounded(root, relative11, limit = ARP_MAX_BLOB_BYTES) {
60696
61400
  const bytes = Buffer.alloc(stat7.size);
60697
61401
  let offset = 0;
60698
61402
  while (offset < bytes.length) {
60699
- const count12 = readSync4(fd2, bytes, offset, bytes.length - offset, offset);
61403
+ const count12 = readSync5(fd2, bytes, offset, bytes.length - offset, offset);
60700
61404
  if (!count12) arpFail(`The bundle file ${relative11} changed while it was read.`);
60701
61405
  offset += count12;
60702
61406
  }
60703
- if (readSync4(fd2, Buffer.alloc(1), 0, 1, offset) !== 0)
61407
+ if (readSync5(fd2, Buffer.alloc(1), 0, 1, offset) !== 0)
60704
61408
  arpFail(`The bundle file ${relative11} changed while it was read.`);
60705
61409
  return bytes;
60706
61410
  } finally {
@@ -60883,13 +61587,13 @@ function verifySettledEvidence(events) {
60883
61587
  }
60884
61588
  }
60885
61589
  if (tools.size || children.size || approvals.size) {
60886
- const open6 = [
61590
+ const open7 = [
60887
61591
  ...[...tools].map((id2) => `tool ${id2}`),
60888
61592
  ...[...approvals].map((id2) => `approval ${id2}`),
60889
61593
  ...[...children].map((id2) => `child work ${id2}`)
60890
61594
  ];
60891
61595
  arpFail(
60892
- `The source has unsettled tools, approvals, or child work at its boundary: ${open6.join(", ")}.`
61596
+ `The source has unsettled tools, approvals, or child work at its boundary: ${open7.join(", ")}.`
60893
61597
  );
60894
61598
  }
60895
61599
  const present = (value) => value !== void 0 && value !== null && value !== false && !(Array.isArray(value) && value.length === 0);
@@ -61621,7 +62325,7 @@ import {
61621
62325
  lstatSync as lstatSync9,
61622
62326
  mkdirSync as mkdirSync8,
61623
62327
  openSync as openSync9,
61624
- readSync as readSync5,
62328
+ readSync as readSync6,
61625
62329
  realpathSync as realpathSync7,
61626
62330
  rmSync as rmSync9,
61627
62331
  writeFileSync as writeFileSync5
@@ -61647,14 +62351,14 @@ function readRegular(label2, path2, limit) {
61647
62351
  const bytes = Buffer.alloc(before.size);
61648
62352
  let offset = 0;
61649
62353
  while (offset < bytes.length) {
61650
- const count12 = readSync5(fd2, bytes, offset, bytes.length - offset, offset);
62354
+ const count12 = readSync6(fd2, bytes, offset, bytes.length - offset, offset);
61651
62355
  if (count12 === 0)
61652
62356
  arpFail(
61653
62357
  `${label2} ${path2} changed while it was read. Stop its writer and capture again.`
61654
62358
  );
61655
62359
  offset += count12;
61656
62360
  }
61657
- if (readSync5(fd2, Buffer.alloc(1), 0, 1, offset) !== 0)
62361
+ if (readSync6(fd2, Buffer.alloc(1), 0, 1, offset) !== 0)
61658
62362
  arpFail(
61659
62363
  `${label2} ${path2} grew while it was read. Stop its writer and capture again.`
61660
62364
  );
@@ -62672,7 +63376,7 @@ var init_package = __esm({
62672
63376
  "package.json"() {
62673
63377
  package_default = {
62674
63378
  name: "@oxagen/cli",
62675
- version: "2.1.5-63",
63379
+ version: "2.1.5-73",
62676
63380
  description: "Oxagen governance CLI \u2014 spend, lineage, grounding, credentials, and audit (agentic coding lives in the stella CLI)",
62677
63381
  type: "module",
62678
63382
  main: "./dist/index.js",
@@ -62768,7 +63472,7 @@ var init_atomic_write = __esm({
62768
63472
  // src/lib/config.ts
62769
63473
  import { homedir as homedir9 } from "os";
62770
63474
  import { join as join51 } from "path";
62771
- import { readFileSync as readFileSync25, mkdirSync as mkdirSync9, existsSync as existsSync27, statSync as statSync15 } from "fs";
63475
+ import { readFileSync as readFileSync25, mkdirSync as mkdirSync9, existsSync as existsSync27, statSync as statSync16 } from "fs";
62772
63476
  function getConfigDir() {
62773
63477
  return CONFIG_DIR;
62774
63478
  }
@@ -62798,7 +63502,7 @@ function parseConfig(raw) {
62798
63502
  function readConfig() {
62799
63503
  let identity;
62800
63504
  try {
62801
- const info = statSync15(CONFIG_FILE);
63505
+ const info = statSync16(CONFIG_FILE);
62802
63506
  identity = `${info.ino}:${info.size}:${info.mtimeMs}`;
62803
63507
  } catch {
62804
63508
  cached2 = void 0;
@@ -78078,7 +78782,7 @@ var init_json_wire = __esm({
78078
78782
 
78079
78783
  // ../../packages/run-evidence/src/contextgraph.ts
78080
78784
  function defineReadonly(target2, key, value) {
78081
- Object.defineProperty(target2, key, {
78785
+ intrinsicObjectDefineProperty(target2, key, {
78082
78786
  configurable: false,
78083
78787
  enumerable: true,
78084
78788
  value,
@@ -78086,13 +78790,13 @@ function defineReadonly(target2, key, value) {
78086
78790
  });
78087
78791
  }
78088
78792
  function createIndexedSnapshot(length) {
78089
- const snapshot = /* @__PURE__ */ Object.create(null);
78793
+ const snapshot = intrinsicObjectCreate(null);
78090
78794
  defineReadonly(snapshot, "length", length);
78091
78795
  return snapshot;
78092
78796
  }
78093
78797
  function captureDescriptor(descriptor) {
78094
- const isData = Object.hasOwn(descriptor, "value");
78095
- const snapshot = /* @__PURE__ */ Object.create(null);
78798
+ const isData = intrinsicObjectHasOwn(descriptor, "value");
78799
+ const snapshot = intrinsicObjectCreate(null);
78096
78800
  defineReadonly(snapshot, "configurable", descriptor.configurable === true);
78097
78801
  defineReadonly(snapshot, "enumerable", descriptor.enumerable === true);
78098
78802
  defineReadonly(snapshot, "isData", isData);
@@ -78104,29 +78808,29 @@ function captureDescriptor(descriptor) {
78104
78808
  defineReadonly(snapshot, "value", isData ? descriptor.value : void 0);
78105
78809
  defineReadonly(snapshot, "get", isData ? void 0 : descriptor.get);
78106
78810
  defineReadonly(snapshot, "set", isData ? void 0 : descriptor.set);
78107
- return Object.freeze(snapshot);
78811
+ return intrinsicObjectFreeze(snapshot);
78108
78812
  }
78109
78813
  function capturePrototype(label2, target2) {
78110
- const keys = Reflect.ownKeys(target2);
78814
+ const keys = intrinsicReflectOwnKeys(target2);
78111
78815
  const entries2 = createIndexedSnapshot(keys.length);
78112
78816
  for (let index = 0; index < keys.length; index += 1) {
78113
78817
  const key = keys[index];
78114
- const descriptor = Object.getOwnPropertyDescriptor(
78818
+ const descriptor = intrinsicObjectGetOwnPropertyDescriptor(
78115
78819
  target2,
78116
78820
  key
78117
78821
  );
78118
- const entry2 = /* @__PURE__ */ Object.create(null);
78822
+ const entry2 = intrinsicObjectCreate(null);
78119
78823
  defineReadonly(entry2, "key", key);
78120
78824
  defineReadonly(entry2, "descriptor", captureDescriptor(descriptor));
78121
- defineReadonly(entries2, String(index), Object.freeze(entry2));
78825
+ defineReadonly(entries2, `${index}`, intrinsicObjectFreeze(entry2));
78122
78826
  }
78123
- const snapshot = /* @__PURE__ */ Object.create(null);
78827
+ const snapshot = intrinsicObjectCreate(null);
78124
78828
  defineReadonly(snapshot, "label", label2);
78125
78829
  defineReadonly(snapshot, "target", target2);
78126
- defineReadonly(snapshot, "entries", Object.freeze(entries2));
78127
- return Object.freeze(snapshot);
78830
+ defineReadonly(snapshot, "entries", intrinsicObjectFreeze(entries2));
78831
+ return intrinsicObjectFreeze(snapshot);
78128
78832
  }
78129
- var intrinsicMathFround, intrinsicNumberIsFinite2, intrinsicObjectIs, u32Schema, finiteNumberSchema, f32NumberSchema, protectedPrototypes, contextFrameKindV1Schema, contextProvenanceV1Schema, contextFrameEmbeddingV1Schema, contextRelationV1Schema, contextRepresentationV1Schema, contextContentFidelityV1Schema, contextInlineContentRequirementV1Schema, contextContentRefV1Schema, contextTransformV1Schema, contextFrameObjectV1Schema, contextFrameV1Schema, contextQueryV1Schema;
78833
+ var intrinsicMathFround, intrinsicNumberIsFinite2, intrinsicObjectCreate, intrinsicObjectDefineProperty, intrinsicObjectFreeze, intrinsicObjectGetOwnPropertyDescriptor, intrinsicObjectHasOwn, intrinsicObjectIs, intrinsicReflectOwnKeys, u32Schema, finiteNumberSchema, f32NumberSchema, protectedTargets, protectedPrototypes, contextFrameKindV1Schema, contextProvenanceV1Schema, contextFrameEmbeddingV1Schema, contextRelationV1Schema, contextRepresentationV1Schema, contextContentFidelityV1Schema, contextInlineContentRequirementV1Schema, contextContentRefV1Schema, contextTransformV1Schema, contextFrameObjectV1Schema, contextFrameV1Schema, contextQueryV1Schema;
78130
78834
  var init_contextgraph = __esm({
78131
78835
  "../../packages/run-evidence/src/contextgraph.ts"() {
78132
78836
  "use strict";
@@ -78135,7 +78839,13 @@ var init_contextgraph = __esm({
78135
78839
  init_json_wire();
78136
78840
  intrinsicMathFround = Math.fround;
78137
78841
  intrinsicNumberIsFinite2 = Number.isFinite;
78842
+ intrinsicObjectCreate = Object.create;
78843
+ intrinsicObjectDefineProperty = Object.defineProperty;
78844
+ intrinsicObjectFreeze = Object.freeze;
78845
+ intrinsicObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
78846
+ intrinsicObjectHasOwn = Object.hasOwn;
78138
78847
  intrinsicObjectIs = Object.is;
78848
+ intrinsicReflectOwnKeys = Reflect.ownKeys;
78139
78849
  u32Schema = external_exports.number().int().min(0).max(4294967295).refine((value) => !intrinsicObjectIs(value, -0), {
78140
78850
  message: "u32 value must not be negative zero"
78141
78851
  });
@@ -78144,28 +78854,27 @@ var init_contextgraph = __esm({
78144
78854
  (value) => intrinsicNumberIsFinite2(intrinsicMathFround(value)),
78145
78855
  { message: "embedding value must be representable as a finite f32" }
78146
78856
  );
78147
- protectedPrototypes = createIndexedSnapshot(4);
78148
- defineReadonly(
78149
- protectedPrototypes,
78150
- "0",
78151
- capturePrototype("Array.prototype", Array.prototype)
78152
- );
78153
- defineReadonly(
78154
- protectedPrototypes,
78155
- "1",
78156
- capturePrototype("Object.prototype", Object.prototype)
78157
- );
78158
- defineReadonly(
78159
- protectedPrototypes,
78160
- "2",
78161
- capturePrototype("Set.prototype", Set.prototype)
78162
- );
78163
- defineReadonly(
78164
- protectedPrototypes,
78165
- "3",
78166
- capturePrototype("String.prototype", String.prototype)
78167
- );
78168
- Object.freeze(protectedPrototypes);
78857
+ protectedTargets = [
78858
+ ["Array.prototype", Array.prototype],
78859
+ ["Object.prototype", Object.prototype],
78860
+ ["Set.prototype", Set.prototype],
78861
+ ["String.prototype", String.prototype],
78862
+ ["Object", Object],
78863
+ ["Reflect", Reflect],
78864
+ ["JSON", JSON]
78865
+ ];
78866
+ protectedPrototypes = createIndexedSnapshot(
78867
+ protectedTargets.length
78868
+ );
78869
+ for (let index = 0; index < protectedTargets.length; index += 1) {
78870
+ const [label2, target2] = protectedTargets[index];
78871
+ defineReadonly(
78872
+ protectedPrototypes,
78873
+ `${index}`,
78874
+ capturePrototype(label2, target2)
78875
+ );
78876
+ }
78877
+ intrinsicObjectFreeze(protectedPrototypes);
78169
78878
  contextFrameKindV1Schema = external_exports.enum([
78170
78879
  "snippet",
78171
78880
  "symbol",
@@ -82174,6 +82883,16 @@ var init_run_list = __esm({
82174
82883
  * when the read did not look.
82175
82884
  */
82176
82885
  tokens: tokenCountsSchema.nullable().optional(),
82886
+ /**
82887
+ * `cost.run_totals.model_calls` and `tool_calls`: the model calls and
82888
+ * tool calls the run's rollup row counted (#4391). A wrapped run whose
82889
+ * row holds tool calls and no model call made its model calls where no
82890
+ * frame could report them, the rule `get_spend` counts such runs by.
82891
+ * Null when the run has no rollup row. Absent when the read did not
82892
+ * look.
82893
+ */
82894
+ modelCalls: external_exports.number().int().nonnegative().nullable().optional(),
82895
+ toolCalls: external_exports.number().int().nonnegative().nullable().optional(),
82177
82896
  /**
82178
82897
  * `cost.run_totals.cache_hit_rate`: cache reads over uncached input plus
82179
82898
  * cache reads, weighted by spend over the run's frames, from 0 to 1. Null
@@ -87507,7 +88226,7 @@ __export(verify_exports, {
87507
88226
  readBundle: () => readBundle,
87508
88227
  verifyBundle: () => verifyBundle3
87509
88228
  });
87510
- import { existsSync as existsSync30, readFileSync as readFileSync27, statSync as statSync16 } from "node:fs";
88229
+ import { existsSync as existsSync30, readFileSync as readFileSync27, statSync as statSync17 } from "node:fs";
87511
88230
  import { join as join61 } from "node:path";
87512
88231
  function readDirectory(dir) {
87513
88232
  const read2 = (name) => {
@@ -87558,7 +88277,7 @@ function readBundle(path2) {
87558
88277
  if (!existsSync30(path2)) {
87559
88278
  throw new BundleReadError(`${path2} does not exist.`);
87560
88279
  }
87561
- return statSync16(path2).isDirectory() ? readDirectory(path2) : readZip(path2);
88280
+ return statSync17(path2).isDirectory() ? readDirectory(path2) : readZip(path2);
87562
88281
  }
87563
88282
  function plural3(count12, one, many) {
87564
88283
  return `${count12} ${count12 === 1 ? one : many}`;
@@ -91257,7 +91976,7 @@ var init_upstream_tool = __esm({
91257
91976
  });
91258
91977
 
91259
91978
  // ../../packages/mcp-studio/src/contract/lock.ts
91260
- var LOCK_BYTES_MAX, serverVersionSchema, lockPackageSchema, pypiLockFileSchema, fileOnlyForPypi, registryLockPackageSchema, remoteLockSourceSchema, urlOrPackage, transportOnlyWithUrl, launchOnlyWithPackage, registryLockSourceSchema, localLockSourceSchema, mcpLockSourceSchema, DEFINITION_LABELS, fromFitsType, definitionLockSourceSchema, versionSchema, definitionHashSchema, upstreamHashSchema, mcpLockedToolSchema, definitionLockedToolSchema, mcpLockSchema, definitionLockSchema, mcpToolsLockSchema;
91979
+ var LOCK_BYTES_MAX, PACKAGE_NAME_MAX, PACKAGE_VERSION_MAX, LAUNCH_ARG_MAX, LAUNCH_ARGS_MAX, serverVersionSchema, lockPackageSchema, pypiLockFileSchema, fileOnlyForPypi, registryLockPackageSchema, remoteLockSourceSchema, urlOrPackage, transportOnlyWithUrl, launchOnlyWithPackage, registryLockSourceSchema, localLockSourceSchema, mcpLockSourceSchema, DEFINITION_LABELS, fromFitsType, definitionLockSourceSchema, versionSchema, definitionHashSchema, upstreamHashSchema, mcpLockedToolSchema, definitionLockedToolSchema, mcpLockSchema, definitionLockSchema, mcpToolsLockSchema;
91261
91980
  var init_lock = __esm({
91262
91981
  "../../packages/mcp-studio/src/contract/lock.ts"() {
91263
91982
  "use strict";
@@ -91270,14 +91989,18 @@ var init_lock = __esm({
91270
91989
  init_primitives();
91271
91990
  init_server2();
91272
91991
  LOCK_BYTES_MAX = 5 * 1024 * 1024;
91992
+ PACKAGE_NAME_MAX = 214;
91993
+ PACKAGE_VERSION_MAX = 64;
91994
+ LAUNCH_ARG_MAX = 4096;
91995
+ LAUNCH_ARGS_MAX = 1024;
91273
91996
  serverVersionSchema = external_exports.string().min(1).max(64).optional().describe(
91274
91997
  "The version the server reported in initialize, when it reported one."
91275
91998
  );
91276
91999
  lockPackageSchema = external_exports.object({
91277
- name: external_exports.string().min(1).max(214).describe(
92000
+ name: external_exports.string().min(1).max(PACKAGE_NAME_MAX).describe(
91278
92001
  "The package or binary: @modelcontextprotocol/server-filesystem."
91279
92002
  ),
91280
- version: external_exports.string().min(1).max(64),
92003
+ version: external_exports.string().min(1).max(PACKAGE_VERSION_MAX),
91281
92004
  digest: sha256Schema2.describe("SHA-256 of the package or binary.")
91282
92005
  }).strict();
91283
92006
  pypiLockFileSchema = external_exports.object({
@@ -91380,7 +92103,7 @@ var init_lock = __esm({
91380
92103
  command: external_exports.string().min(1).max(1024).optional().describe(
91381
92104
  "For a package: the command the local gateway runs, npx, uvx, docker, or dnx."
91382
92105
  ),
91383
- args: external_exports.array(external_exports.string().max(4096)).max(1024).optional().describe(
92106
+ args: external_exports.array(external_exports.string().max(LAUNCH_ARG_MAX)).max(LAUNCH_ARGS_MAX).optional().describe(
91384
92107
  "For a package: the command's arguments. ${NAME} stays for the local gateway to fill from the machine, and $$ writes one $."
91385
92108
  ),
91386
92109
  server_version: serverVersionSchema
@@ -92039,6 +92762,9 @@ function keyProblems(all, given, type) {
92039
92762
  }
92040
92763
  return problems;
92041
92764
  }
92765
+ function fromPackage(texts) {
92766
+ return texts.map((text5) => ({ text: text5, field: "source.registry_type" }));
92767
+ }
92042
92768
  function words(slot, given, problems) {
92043
92769
  const { argument, label: label2, key } = slot;
92044
92770
  const field = key === void 0 ? "source.registry_type" : `source.arguments.${key}`;
@@ -92078,7 +92804,38 @@ function words(slot, given, problems) {
92078
92804
  }
92079
92805
  return [];
92080
92806
  }
92081
- return argument.type === "named" ? [literal2(argument.name), value] : [value];
92807
+ const valueWord = { text: value, field };
92808
+ return argument.type === "named" ? [...fromPackage([literal2(argument.name)]), valueWord] : [valueWord];
92809
+ }
92810
+ function lockLimitProblems(type, pkg, version2, launch) {
92811
+ const problems = [];
92812
+ if (pkg.identifier.length > PACKAGE_NAME_MAX) {
92813
+ problems.push({
92814
+ field: "source.registry_type",
92815
+ message: `the ${type} package's identifier is ${pkg.identifier.length} characters long, and the lock holds at most ${PACKAGE_NAME_MAX}`
92816
+ });
92817
+ }
92818
+ if (version2.length > PACKAGE_VERSION_MAX) {
92819
+ problems.push({
92820
+ field: "source.registry_type",
92821
+ message: `the ${type} package's version is ${version2.length} characters long, and the lock holds at most ${PACKAGE_VERSION_MAX}`
92822
+ });
92823
+ }
92824
+ launch.forEach(({ text: text5, field }, index) => {
92825
+ if (text5.length > LAUNCH_ARG_MAX) {
92826
+ problems.push({
92827
+ field,
92828
+ message: `args[${index}] is ${text5.length} characters long once each $ is written $$, and the lock holds at most ${LAUNCH_ARG_MAX}`
92829
+ });
92830
+ }
92831
+ });
92832
+ if (launch.length > LAUNCH_ARGS_MAX) {
92833
+ problems.push({
92834
+ field: "source.registry_type",
92835
+ message: `the ${type} package's launch has ${launch.length} args, and the lock holds at most ${LAUNCH_ARGS_MAX}`
92836
+ });
92837
+ }
92838
+ return problems;
92082
92839
  }
92083
92840
  function registryLaunch({
92084
92841
  source,
@@ -92144,18 +92901,22 @@ function registryLaunch({
92144
92901
  const reference = pinned !== void 0 ? ["--from", literal2(pinned.url), literal2(pkg.identifier)] : [
92145
92902
  `${literal2(pkg.identifier)}@${runner.pin === "digest" ? digest6 : literal2(version2)}`
92146
92903
  ];
92147
- const args = [
92148
- ...runner.flags,
92149
- ...type === "oci" ? env.flatMap((name) => ["-e", name]) : [],
92904
+ const launch = [
92905
+ ...fromPackage(runner.flags),
92906
+ ...type === "oci" ? env.flatMap((name) => [
92907
+ ...fromPackage(["-e"]),
92908
+ { text: name, field: "source.env" }
92909
+ ]) : [],
92150
92910
  ...runtime.flatMap((slot) => words(slot, given, problems)),
92151
- ...reference,
92911
+ ...fromPackage(reference),
92152
92912
  ...packaged.flatMap((slot) => words(slot, given, problems))
92153
92913
  ];
92914
+ problems.push(...lockLimitProblems(type, pkg, version2, launch));
92154
92915
  if (problems.length > 0) return { ok: false, problems };
92155
92916
  return {
92156
92917
  ok: true,
92157
92918
  command: runner.command,
92158
- args,
92919
+ args: launch.map((word) => word.text),
92159
92920
  package: {
92160
92921
  name: pkg.identifier,
92161
92922
  version: version2,
@@ -92169,6 +92930,7 @@ var REGISTRY_RUNNERS2, ONE_VARIABLE;
92169
92930
  var init_registry_launch = __esm({
92170
92931
  "../../packages/mcp-studio/src/model/registry-launch.ts"() {
92171
92932
  "use strict";
92933
+ init_lock();
92172
92934
  REGISTRY_RUNNERS2 = {
92173
92935
  npm: { command: "npx", flags: ["--yes"], pin: "version" },
92174
92936
  pypi: { command: "uvx", flags: [], pin: "file" },
@@ -106314,15 +107076,15 @@ var init_print = __esm({
106314
107076
  // ── Extensions ─────────────────────────────────────────────────────────────
106315
107077
  /** `extend` blocks, one for each run of extensions of the same message. */
106316
107078
  extensions(fields, scope, path2) {
106317
- let open6;
107079
+ let open7;
106318
107080
  fields.forEach((field, index) => {
106319
107081
  const where = `the extension ${scope === "" ? "" : `${scope}.`}${field.name}`;
106320
107082
  if (field.extendee === "")
106321
107083
  this.refuse(`${where} names no message to extend`);
106322
107084
  if (isFieldSet(field, FieldDescriptorProtoSchema.field.oneofIndex))
106323
107085
  this.refuse(`${where} is in a oneof`);
106324
- if (field.extendee !== open6) {
106325
- if (open6 !== void 0) this.close();
107086
+ if (field.extendee !== open7) {
107087
+ if (open7 !== void 0) this.close();
106326
107088
  const extendee = this.typeName(
106327
107089
  field.extendee,
106328
107090
  scope,
@@ -106332,7 +107094,7 @@ var init_print = __esm({
106332
107094
  this.gap();
106333
107095
  this.line(`extend ${extendee} {`);
106334
107096
  this.depth += 1;
106335
- open6 = field.extendee;
107097
+ open7 = field.extendee;
106336
107098
  }
106337
107099
  this.comment([...path2, index]);
106338
107100
  this.line(
@@ -106345,7 +107107,7 @@ var init_print = __esm({
106345
107107
  )
106346
107108
  );
106347
107109
  });
106348
- if (open6 !== void 0) this.close();
107110
+ if (open7 !== void 0) this.close();
106349
107111
  }
106350
107112
  // ── Options ────────────────────────────────────────────────────────────────
106351
107113
  /**
@@ -106841,7 +107603,7 @@ function importOrder(sources, loaded) {
106841
107603
  const done = /* @__PURE__ */ new Set();
106842
107604
  const stack = [];
106843
107605
  const onStack = /* @__PURE__ */ new Set();
106844
- const open6 = (name, importer) => {
107606
+ const open7 = (name, importer) => {
106845
107607
  const entry2 = load(name, importer, sources);
106846
107608
  loaded.set(name, entry2);
106847
107609
  const imports = entry2.kind === "source" ? entry2.parsed.header.imports.map((each) => each.path) : entry2.proto.dependency;
@@ -106850,7 +107612,7 @@ function importOrder(sources, loaded) {
106850
107612
  };
106851
107613
  for (const root of [...sources.keys()].sort()) {
106852
107614
  if (done.has(root)) continue;
106853
- open6(root, void 0);
107615
+ open7(root, void 0);
106854
107616
  for (let top = stack.at(-1); top !== void 0; top = stack.at(-1)) {
106855
107617
  const next = top.imports[top.next];
106856
107618
  top.next += 1;
@@ -106863,7 +107625,7 @@ function importOrder(sources, loaded) {
106863
107625
  const at2 = stack.findIndex((frame) => frame.name === next);
106864
107626
  throw cycle([...stack.slice(at2).map((frame) => frame.name), next]);
106865
107627
  } else if (!done.has(next)) {
106866
- open6(next, top.name);
107628
+ open7(next, top.name);
106867
107629
  }
106868
107630
  }
106869
107631
  }
@@ -107350,8 +108112,8 @@ var init_json_schema4 = __esm({
107350
108112
  description: `Cut: messages nest past ${DEPTH_MAX} levels here.`
107351
108113
  };
107352
108114
  }
107353
- const open6 = this.active.get(name) ?? 0;
107354
- if (open6 >= RECURSION_DEPTH) {
108115
+ const open7 = this.active.get(name) ?? 0;
108116
+ if (open7 >= RECURSION_DEPTH) {
107355
108117
  this.note(
107356
108118
  `Import cut the recursive message ${name} at depth ${RECURSION_DEPTH}.`
107357
108119
  );
@@ -107369,12 +108131,12 @@ var init_json_schema4 = __esm({
107369
108131
  description: `Cut: this tool's schemas passed ${count8(TOOL_NODES_SOFT_MAX)} nodes.`
107370
108132
  };
107371
108133
  }
107372
- this.active.set(name, open6 + 1);
108134
+ this.active.set(name, open7 + 1);
107373
108135
  try {
107374
108136
  return this.fieldsOf(desc, direction, depth);
107375
108137
  } finally {
107376
- if (open6 === 0) this.active.delete(name);
107377
- else this.active.set(name, open6);
108138
+ if (open7 === 0) this.active.delete(name);
108139
+ else this.active.set(name, open7);
107378
108140
  }
107379
108141
  }
107380
108142
  fieldsOf(desc, direction, depth) {
@@ -107890,7 +108652,7 @@ function selectOutput(outputSchema, select) {
107890
108652
  return trimObject(outputSchema, selectTree(select));
107891
108653
  }
107892
108654
  function selectionProblem(selection) {
107893
- const open6 = [];
108655
+ const open7 = [];
107894
108656
  let quoted3 = false;
107895
108657
  for (let index = 0; index < selection.length; index += 1) {
107896
108658
  const char = selection[index];
@@ -107900,15 +108662,15 @@ function selectionProblem(selection) {
107900
108662
  continue;
107901
108663
  }
107902
108664
  if (char === '"') quoted3 = true;
107903
- else if (char === "{" || char === "(") open6.push(char);
108665
+ else if (char === "{" || char === "(") open7.push(char);
107904
108666
  else if (char === "}" || char === ")") {
107905
108667
  const expected = char === "}" ? "{" : "(";
107906
- if (open6.pop() !== expected)
108668
+ if (open7.pop() !== expected)
107907
108669
  return `selection has an unmatched ${char} at character ${index + 1}.`;
107908
108670
  }
107909
108671
  }
107910
108672
  if (quoted3) return "selection has a string with no closing quote.";
107911
- const unclosed = open6.pop();
108673
+ const unclosed = open7.pop();
107912
108674
  if (unclosed !== void 0) return `selection has an unclosed ${unclosed}.`;
107913
108675
  if (!/[_A-Za-z]/.test(selection)) return "selection names no field.";
107914
108676
  return void 0;
@@ -109691,8 +110453,7 @@ function locationWithoutQuery(location) {
109691
110453
  url = new URL(location);
109692
110454
  } catch {
109693
110455
  const cut = location.search(/[?#]/);
109694
- const head2 = cut === -1 ? location : location.slice(0, cut);
109695
- return head2.replace(/^\/\/[^/]*@/, "//");
110456
+ return withoutUserinfo(cut === -1 ? location : location.slice(0, cut));
109696
110457
  }
109697
110458
  url.username = "";
109698
110459
  url.password = "";
@@ -109700,6 +110461,13 @@ function locationWithoutQuery(location) {
109700
110461
  url.hash = "";
109701
110462
  return url.href;
109702
110463
  }
110464
+ function withoutUserinfo(head2) {
110465
+ const match = UNPARSED_USERINFO.exec(head2);
110466
+ if (match === null) return head2;
110467
+ const prefix = match[1] ?? "";
110468
+ const rest = head2.slice(match[0].length);
110469
+ return rest === "" || /^[\\/]/.test(rest) ? `${prefix}@${rest}` : `${prefix}${rest}`;
110470
+ }
109703
110471
  function recordedResponseHeaders(headers) {
109704
110472
  const out = /* @__PURE__ */ new Map();
109705
110473
  for (const [name, raw] of headers) {
@@ -109733,12 +110501,13 @@ function recordHttpResponse(response, bytes) {
109733
110501
  ...body2 === void 0 ? {} : { body: body2 }
109734
110502
  };
109735
110503
  }
109736
- var REFUSED_RESPONSE_HEADERS;
110504
+ var UNPARSED_USERINFO, REFUSED_RESPONSE_HEADERS;
109737
110505
  var init_exchange = __esm({
109738
110506
  "../../packages/mcp-studio/src/execute/exchange.ts"() {
109739
110507
  "use strict";
109740
110508
  init_tests_files();
109741
110509
  init_body();
110510
+ UNPARSED_USERINFO = /^([A-Za-z][A-Za-z0-9+.-]*:[\\/]*|[\\/]{2})[^/\\]*@/;
109742
110511
  REFUSED_RESPONSE_HEADERS = new Set(
109743
110512
  CREDENTIAL_RESPONSE_HEADERS
109744
110513
  );
@@ -110248,14 +111017,17 @@ async function notifyInitialized(session) {
110248
111017
  });
110249
111018
  return read2.ok ? { ok: true, value: void 0 } : read2;
110250
111019
  }
110251
- async function callTool2(session) {
110252
- const { template, args } = session.call;
110253
- const sent = await post(session, {
111020
+ function toolsCallMessage(template, args) {
111021
+ return {
110254
111022
  jsonrpc: "2.0",
110255
111023
  id: CALL_ID,
110256
111024
  method: "tools/call",
110257
111025
  params: { name: template.tool, arguments: args }
110258
- });
111026
+ };
111027
+ }
111028
+ async function callTool2(session) {
111029
+ const { template, args } = session.call;
111030
+ const sent = await post(session, toolsCallMessage(template, args));
110259
111031
  if (!sent.ok) return { attempt: sent, exchange: void 0 };
110260
111032
  const response = sent.value;
110261
111033
  if (response.status < 200 || response.status >= 300) {
@@ -111171,7 +111943,7 @@ async function attemptCall(call2, context, deadline, settings) {
111171
111943
  const controller = new AbortController();
111172
111944
  const clock2 = new Clock(deadline, context.signal, controller);
111173
111945
  try {
111174
- const opening = open4(context.transport, {
111946
+ const opening = open5(context.transport, {
111175
111947
  ...call2.request,
111176
111948
  deadline_ms: Math.max(1, deadline - Date.now()),
111177
111949
  signal: controller.signal
@@ -111191,7 +111963,7 @@ async function attemptCall(call2, context, deadline, settings) {
111191
111963
  clock2.dispose();
111192
111964
  }
111193
111965
  }
111194
- async function open4(transport, request3) {
111966
+ async function open5(transport, request3) {
111195
111967
  return transport.grpc(request3);
111196
111968
  }
111197
111969
  async function read(response, call2, clock2, settings) {
@@ -130596,6 +131368,19 @@ var init_transport2 = __esm({
130596
131368
  }
130597
131369
  });
130598
131370
 
131371
+ // ../../packages/mcp-studio/src/execute/request-size.ts
131372
+ var init_request_size = __esm({
131373
+ "../../packages/mcp-studio/src/execute/request-size.ts"() {
131374
+ "use strict";
131375
+ init_body();
131376
+ init_graphql2();
131377
+ init_http();
131378
+ init_mcp();
131379
+ init_shape_input();
131380
+ init_util4();
131381
+ }
131382
+ });
131383
+
130599
131384
  // ../../packages/mcp-studio/src/execute/index.ts
130600
131385
  var init_execute = __esm({
130601
131386
  "../../packages/mcp-studio/src/execute/index.ts"() {
@@ -130612,6 +131397,7 @@ var init_execute = __esm({
130612
131397
  init_http();
130613
131398
  init_mcp();
130614
131399
  init_senders();
131400
+ init_request_size();
130615
131401
  init_validate();
130616
131402
  init_body();
130617
131403
  init_http_call();
@@ -131388,15 +132174,11 @@ var init_env = __esm({
131388
132174
  // explicit "false", but the startup guard (assertRlsEnforcedInProduction)
131389
132175
  // refuses to boot in that state.
131390
132176
  TENANT_RLS_ENFORCEMENT_ENABLED: external_exports.union([external_exports.literal("true"), external_exports.literal("false")]).optional().transform((v) => v === void 0 ? isProductionRuntime() : v === "true"),
131391
- // ── Billing / usage-meter tuning (see @oxagen/billing pricing.ts) ──
131392
- // Target *blended* gross margin across all products, in (0,1). When set,
131393
- // it overrides DEFAULT_TARGET_MARGIN and re-derives the meter markup.
131394
- OXAGEN_TARGET_MARGIN: external_exports.coerce.number().gt(0).lt(1).optional(),
131395
- // Operators pin the exact solved meter markup here so the runtime gate
131396
- // never recomputes the blended-margin solve. `pnpm billing:stripe-sync`
131397
- // prints the value to set. Must be >= 1 (a markup below 1 would sell
131398
- // credits below provider cost).
131399
- OXAGEN_METER_MARKUP: external_exports.coerce.number().gte(1).optional(),
132177
+ // ── Billing / assistant credit meter (see @oxagen/billing pricing.ts) ──
132178
+ // The markup on the provider's list price for the in-app assistant's model
132179
+ // calls on Oxagen's key. Unset means 1.20. Must be >= 1 (a markup below 1
132180
+ // would sell credits below provider cost).
132181
+ OXAGEN_ASSISTANT_MARKUP: external_exports.coerce.number().gte(1).optional(),
131400
132182
  // ── Usage-purchase volume discount (hybrid SaaS + usage pricing) ──
131401
132183
  // When a customer buys usage credits they earn a volume discount of
131402
132184
  // OXAGEN_USAGE_DISCOUNT_PERCENT% off for every OXAGEN_USAGE_DISCOUNT_INCREMENT
@@ -133315,7 +134097,7 @@ import {
133315
134097
  mkdirSync as mkdirSync13,
133316
134098
  readFileSync as readFileSync30,
133317
134099
  readlinkSync as readlinkSync4,
133318
- statSync as statSync17
134100
+ statSync as statSync18
133319
134101
  } from "node:fs";
133320
134102
  import { dirname as dirname36, isAbsolute as isAbsolute18, join as join64, relative as relative9, resolve as resolve25, sep as sep12 } from "node:path";
133321
134103
  function git5(cwd, args, input) {
@@ -133428,14 +134210,14 @@ function chooseBase(root, ref2) {
133428
134210
  }
133429
134211
  function isFile(path2) {
133430
134212
  try {
133431
- return statSync17(path2).isFile();
134213
+ return statSync18(path2).isFile();
133432
134214
  } catch {
133433
134215
  return false;
133434
134216
  }
133435
134217
  }
133436
134218
  function isDir(path2) {
133437
134219
  try {
133438
- return statSync17(path2).isDirectory();
134220
+ return statSync18(path2).isDirectory();
133439
134221
  } catch {
133440
134222
  return false;
133441
134223
  }
@@ -136727,16 +137509,16 @@ function assignmentValue(bytes, assignment, nameStart, nameEnd) {
136727
137509
  if (!assignment.names.some((k) => containsLower(name, k))) return null;
136728
137510
  while (isBlank2(bytes[p])) p += 1;
136729
137511
  if (p >= n2) return null;
136730
- const open6 = bytes[p];
137512
+ const open7 = bytes[p];
136731
137513
  let start;
136732
137514
  let end;
136733
137515
  let quoted3;
136734
- if (open6 === QUOTE || open6 === APOSTROPHE || open6 === BACKTICK) {
137516
+ if (open7 === QUOTE || open7 === APOSTROPHE || open7 === BACKTICK) {
136735
137517
  start = p + 1;
136736
137518
  end = start;
136737
- while (end < n2 && bytes[end] !== open6 && bytes[end] !== NEWLINE2)
137519
+ while (end < n2 && bytes[end] !== open7 && bytes[end] !== NEWLINE2)
136738
137520
  end += bytes[end] === BACKSLASH ? 2 : 1;
136739
- if (end >= n2 || bytes[end] !== open6) return null;
137521
+ if (end >= n2 || bytes[end] !== open7) return null;
136740
137522
  quoted3 = true;
136741
137523
  } else {
136742
137524
  start = p;
@@ -137912,7 +138694,7 @@ __export(logs_exports, {
137912
138694
  handleLogs: () => handleLogs
137913
138695
  });
137914
138696
  import { watch } from "node:fs";
137915
- import { open as open5, stat as stat6 } from "node:fs/promises";
138697
+ import { open as open6, stat as stat6 } from "node:fs/promises";
137916
138698
  function formatEntry(entry2) {
137917
138699
  const head2 = `${entry2.ts} ${entry2.category.padEnd(11)} ${entry2.event}`;
137918
138700
  if (entry2.data === void 0) return head2;
@@ -137943,7 +138725,7 @@ async function logSize(file) {
137943
138725
  async function readAppended(file, offset) {
137944
138726
  let handle;
137945
138727
  try {
137946
- handle = await open5(file, "r");
138728
+ handle = await open6(file, "r");
137947
138729
  } catch {
137948
138730
  return { entries: [], offset: 0 };
137949
138731
  }