@cnwenf/occ 2.1.313 → 2.1.315

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/dist/cli.js +431 -144
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- globalThis.MACRO={"VERSION":"2.1.313","BINARY_NAME":"occ","BUILD_TIME":"2026-08-26T18:15:46.386Z","FEEDBACK_CHANNEL":"","ISSUES_EXPLAINER":"","NATIVE_PACKAGE_URL":"","PACKAGE_URL":"@cnwenf/occ","VERSION_CHANGELOG":""};
2
+ globalThis.MACRO={"VERSION":"2.1.315","BINARY_NAME":"occ","BUILD_TIME":"2026-08-28T20:42:08.546Z","FEEDBACK_CHANNEL":"","ISSUES_EXPLAINER":"","NATIVE_PACKAGE_URL":"","PACKAGE_URL":"@cnwenf/occ","VERSION_CHANGELOG":""};
3
3
  // @bun
4
4
  var __create = Object.create;
5
5
  var __getProtoOf = Object.getPrototypeOf;
@@ -231222,6 +231222,7 @@ async function loadAgentFromFile(filePath, pluginName, namespace, sourceName, pl
231222
231222
  }
231223
231223
  const isolationRaw = frontmatter.isolation;
231224
231224
  const isolation = isolationRaw === "worktree" ? "worktree" : undefined;
231225
+ const cacheTtl = extractAgentCacheTtl(frontmatter);
231225
231226
  const effortRaw = frontmatter.effort;
231226
231227
  const effort = effortRaw !== undefined ? parseEffortValue(effortRaw) : undefined;
231227
231228
  if (effortRaw !== undefined && effort === undefined) {
@@ -231274,7 +231275,8 @@ async function loadAgentFromFile(filePath, pluginName, namespace, sourceName, pl
231274
231275
  ...memory ? { memory } : {},
231275
231276
  ...isolation ? { isolation } : {},
231276
231277
  ...effort !== undefined ? { effort } : {},
231277
- ...maxTurns !== undefined ? { maxTurns } : {}
231278
+ ...maxTurns !== undefined ? { maxTurns } : {},
231279
+ ...cacheTtl !== undefined ? { cacheTtl } : {}
231278
231280
  };
231279
231281
  } catch (error52) {
231280
231282
  logForDebugging(`Failed to load agent from ${filePath}: ${error52}`, {
@@ -231291,6 +231293,7 @@ var init_loadPluginAgents = __esm(() => {
231291
231293
  init_memoize();
231292
231294
  init_paths();
231293
231295
  init_agentMemory();
231296
+ init_loadAgentsDir();
231294
231297
  init_prompt3();
231295
231298
  init_prompt4();
231296
231299
  init_debug();
@@ -232235,6 +232238,7 @@ __export(exports_loadAgentsDir, {
232235
232238
  getAgentDefinitionsWithOverrides: () => getAgentDefinitionsWithOverrides,
232236
232239
  getActiveAgentsFromList: () => getActiveAgentsFromList,
232237
232240
  filterAgentsByMcpRequirements: () => filterAgentsByMcpRequirements,
232241
+ extractAgentCacheTtl: () => extractAgentCacheTtl,
232238
232242
  clearAgentDefinitionsCache: () => clearAgentDefinitionsCache
232239
232243
  });
232240
232244
  import { basename as basename12, dirname as dirname26, resolve as resolve23, join as join51 } from "path";
@@ -232438,6 +232442,14 @@ function parseAgentsFromJson(agentsJson, source = "flagSettings") {
232438
232442
  return [];
232439
232443
  }
232440
232444
  }
232445
+ function extractAgentCacheTtl(frontmatter) {
232446
+ const experimental = frontmatter.experimental;
232447
+ if (typeof experimental !== "object" || experimental === null) {
232448
+ return;
232449
+ }
232450
+ const raw = Object.entries(experimental).find(([key2]) => key2.toLowerCase() === "cachettl")?.[1];
232451
+ return raw === "5m" || raw === "1h" ? raw : undefined;
232452
+ }
232441
232453
  function parseAgentFromMarkdown(filePath, baseDir, frontmatter, content, source) {
232442
232454
  try {
232443
232455
  const agentType = frontmatter["name"];
@@ -232487,6 +232499,7 @@ function parseAgentFromMarkdown(filePath, baseDir, frontmatter, content, source)
232487
232499
  logForDebugging(`Agent file ${filePath} has invalid isolation value '${isolationRaw}'. Valid options: ${VALID_ISOLATION_MODES.join(", ")}`);
232488
232500
  }
232489
232501
  }
232502
+ const cacheTtl = extractAgentCacheTtl(frontmatter);
232490
232503
  const effortRaw = frontmatter["effort"];
232491
232504
  const parsedEffort = effortRaw !== undefined ? parseEffortValue(effortRaw) : undefined;
232492
232505
  if (effortRaw !== undefined && parsedEffort === undefined) {
@@ -232564,7 +232577,8 @@ function parseAgentFromMarkdown(filePath, baseDir, frontmatter, content, source)
232564
232577
  ...maxTurns !== undefined ? { maxTurns } : {},
232565
232578
  ...background ? { background } : {},
232566
232579
  ...memory ? { memory } : {},
232567
- ...isolation ? { isolation } : {}
232580
+ ...isolation ? { isolation } : {},
232581
+ ...cacheTtl !== undefined ? { cacheTtl } : {}
232568
232582
  };
232569
232583
  return agentDef;
232570
232584
  } catch (error52) {
@@ -252352,7 +252366,8 @@ var init_TaskOutput = __esm(() => {
252352
252366
  const tail = safeJoinLines(recent, `
252353
252367
  `);
252354
252368
  const sizeKB = Math.round(this.#totalBytes / 1024);
252355
- const notice = `
252369
+ const notice = this.#disk.failing || this.#disk.lostOutput ? `
252370
+ Output truncated (${sizeKB}KB total). The full output could not all be saved to ${this.path}; that file may be missing or incomplete.` : `
252356
252371
  Output truncated (${sizeKB}KB total). Full output saved to: ${this.path}`;
252357
252372
  return tail ? tail + notice : notice.trimStart();
252358
252373
  }
@@ -472394,7 +472409,8 @@ async function* runAgent({
472394
472409
  userContext: resolvedUserContext,
472395
472410
  systemContext: resolvedSystemContext,
472396
472411
  toolUseContext: agentToolUseContext,
472397
- forkContextMessages: initialMessages
472412
+ forkContextMessages: initialMessages,
472413
+ ...agentDefinition.cacheTtl !== undefined ? { agentCacheTtlOverride: agentDefinition.cacheTtl } : {}
472398
472414
  });
472399
472415
  }
472400
472416
  recordSidechainTranscript(initialMessages, agentId).catch((_err) => logForDebugging(`Failed to record sidechain transcript: ${_err}`));
@@ -472421,7 +472437,8 @@ async function* runAgent({
472421
472437
  canUseTool,
472422
472438
  toolUseContext: agentToolUseContext,
472423
472439
  querySource,
472424
- maxTurns: maxTurns ?? agentDefinition.maxTurns
472440
+ maxTurns: maxTurns ?? agentDefinition.maxTurns,
472441
+ agentCacheTtlOverride: agentDefinition.cacheTtl
472425
472442
  })) {
472426
472443
  onQueryProgress?.();
472427
472444
  if (message.type === "stream_event" && message.event.type === "message_start" && message.ttftMs != null) {
@@ -597566,7 +597583,8 @@ async function* queryLoop(params, consumedCommandUuids) {
597566
597583
  fallbackModel,
597567
597584
  querySource,
597568
597585
  maxTurns,
597569
- skipCacheWrite
597586
+ skipCacheWrite,
597587
+ agentCacheTtlOverride
597570
597588
  } = params;
597571
597589
  const deps = params.deps ?? productionDeps();
597572
597590
  let state3 = {
@@ -597779,6 +597797,7 @@ async function* queryLoop(params, consumedCommandUuids) {
597779
597797
  effortValue: appState.effortValue,
597780
597798
  advisorModel: appState.advisorModel,
597781
597799
  skipCacheWrite,
597800
+ agentCacheTtlOverride,
597782
597801
  agentId: toolUseContext.agentId,
597783
597802
  addNotification: toolUseContext.addNotification,
597784
597803
  ...params.taskBudget && {
@@ -599249,7 +599268,8 @@ async function runForkedAgent({
599249
599268
  userContext,
599250
599269
  systemContext,
599251
599270
  toolUseContext,
599252
- forkContextMessages
599271
+ forkContextMessages,
599272
+ agentCacheTtlOverride
599253
599273
  } = cacheSafeParams;
599254
599274
  const isolatedToolUseContext = createSubagentContext(toolUseContext, overrides);
599255
599275
  const initialMessages = [...forkContextMessages, ...promptMessages];
@@ -599270,7 +599290,8 @@ async function runForkedAgent({
599270
599290
  querySource,
599271
599291
  maxOutputTokensOverride: maxOutputTokens,
599272
599292
  maxTurns,
599273
- skipCacheWrite
599293
+ skipCacheWrite,
599294
+ agentCacheTtlOverride
599274
599295
  })) {
599275
599296
  if (message.type === "stream_event") {
599276
599297
  if ("event" in message && message.event?.type === "message_delta" && message.event.usage) {
@@ -631304,11 +631325,12 @@ function getPromptCachingEnabled(model) {
631304
631325
  }
631305
631326
  function getCacheControl({
631306
631327
  scope,
631307
- querySource
631328
+ querySource,
631329
+ agentCacheTtlOverride
631308
631330
  } = {}) {
631309
631331
  return {
631310
631332
  type: "ephemeral",
631311
- ...should1hCacheTTL(querySource) && { ttl: "1h" },
631333
+ ...should1hCacheTTL(querySource, agentCacheTtlOverride) && { ttl: "1h" },
631312
631334
  ...scope === "global" && { scope }
631313
631335
  };
631314
631336
  }
@@ -631321,7 +631343,7 @@ function isMainThreadQuerySource(querySource) {
631321
631343
  function parsePromptCacheTtlEnv(value) {
631322
631344
  return value === "5m" || value === "1h" ? value : undefined;
631323
631345
  }
631324
- function resolvePromptCacheTtlOverride(querySource) {
631346
+ function resolvePromptCacheTtlOverride(querySource, agentCacheTtlOverride, isUsingOverage = false) {
631325
631347
  if (isEnvTruthy(process.env.FORCE_PROMPT_CACHING_5M)) {
631326
631348
  return { ttl: "5m", reason: "force_5m_env" };
631327
631349
  }
@@ -631333,16 +631355,21 @@ function resolvePromptCacheTtlOverride(querySource) {
631333
631355
  const settingTtl = isMainThread ? settings.promptCacheTtl : settings.subagentPromptCacheTtl;
631334
631356
  if (settingTtl !== undefined)
631335
631357
  return { ttl: settingTtl, reason: "setting" };
631358
+ if (agentCacheTtlOverride !== undefined && !(agentCacheTtlOverride === "1h" && isUsingOverage)) {
631359
+ return { ttl: agentCacheTtlOverride, reason: "agent_frontmatter" };
631360
+ }
631336
631361
  if (isEnvTruthy(process.env.ENABLE_PROMPT_CACHING_1H) || getAPIProvider() === "bedrock" && isEnvTruthy(process.env.ENABLE_PROMPT_CACHING_1H_BEDROCK)) {
631337
631362
  return { ttl: "1h", reason: "enable_1h_env" };
631338
631363
  }
631339
631364
  return;
631340
631365
  }
631341
- function resolvePromptCacheTtl(querySource) {
631342
- const override = resolvePromptCacheTtlOverride(querySource);
631366
+ function resolvePromptCacheTtl(querySource, options) {
631367
+ const isSubscriber2 = isClaudeAISubscriber();
631368
+ const isOverage = isSubscriber2 && options?.ignoreOverage !== true && currentLimits.isUsingOverage === true;
631369
+ const override = resolvePromptCacheTtlOverride(querySource, options?.agentCacheTtlOverride, isOverage);
631343
631370
  if (override !== undefined)
631344
631371
  return override;
631345
- if (!isClaudeAISubscriber() || currentLimits.isUsingOverage) {
631372
+ if (!isSubscriber2 || isOverage) {
631346
631373
  return { ttl: "5m", reason: "default" };
631347
631374
  }
631348
631375
  let allowlist = getPromptCache1hAllowlist();
@@ -631355,8 +631382,8 @@ function resolvePromptCacheTtl(querySource) {
631355
631382
  }
631356
631383
  return querySourceMatchesPatterns(querySource, allowlist) ? { ttl: "1h", reason: "subscriber" } : { ttl: "5m", reason: "default" };
631357
631384
  }
631358
- function should1hCacheTTL(querySource) {
631359
- return resolvePromptCacheTtl(querySource).ttl === "1h";
631385
+ function should1hCacheTTL(querySource, agentCacheTtlOverride) {
631386
+ return resolvePromptCacheTtl(querySource, { agentCacheTtlOverride }).ttl === "1h";
631360
631387
  }
631361
631388
  function configureEffortParams(effortValue, outputConfig, extraBodyParams, betas, model) {
631362
631389
  if (!modelSupportsEffort(model) || "effort" in outputConfig) {
@@ -631447,7 +631474,7 @@ async function verifyApiKey(apiKey, isNonInteractiveSession) {
631447
631474
  throw error52;
631448
631475
  }
631449
631476
  }
631450
- function userMessageToMessageParam(message, addCache = false, enablePromptCaching, querySource) {
631477
+ function userMessageToMessageParam(message, addCache = false, enablePromptCaching, querySource, agentCacheTtlOverride) {
631451
631478
  if (addCache) {
631452
631479
  if (typeof message.message.content === "string") {
631453
631480
  return {
@@ -631457,7 +631484,10 @@ function userMessageToMessageParam(message, addCache = false, enablePromptCachin
631457
631484
  type: "text",
631458
631485
  text: message.message.content,
631459
631486
  ...enablePromptCaching && {
631460
- cache_control: getCacheControl({ querySource })
631487
+ cache_control: getCacheControl({
631488
+ querySource,
631489
+ agentCacheTtlOverride
631490
+ })
631461
631491
  }
631462
631492
  }
631463
631493
  ]
@@ -631467,7 +631497,12 @@ function userMessageToMessageParam(message, addCache = false, enablePromptCachin
631467
631497
  role: "user",
631468
631498
  content: message.message.content.map((_4, i6) => ({
631469
631499
  ..._4,
631470
- ...i6 === message.message.content.length - 1 ? enablePromptCaching ? { cache_control: getCacheControl({ querySource }) } : {} : {}
631500
+ ...i6 === message.message.content.length - 1 ? enablePromptCaching ? {
631501
+ cache_control: getCacheControl({
631502
+ querySource,
631503
+ agentCacheTtlOverride
631504
+ })
631505
+ } : {} : {}
631471
631506
  }))
631472
631507
  };
631473
631508
  }
@@ -631477,7 +631512,7 @@ function userMessageToMessageParam(message, addCache = false, enablePromptCachin
631477
631512
  content: Array.isArray(message.message.content) ? [...message.message.content] : message.message.content
631478
631513
  };
631479
631514
  }
631480
- function assistantMessageToMessageParam(message, addCache = false, enablePromptCaching, querySource) {
631515
+ function assistantMessageToMessageParam(message, addCache = false, enablePromptCaching, querySource, agentCacheTtlOverride) {
631481
631516
  if (addCache) {
631482
631517
  if (typeof message.message.content === "string") {
631483
631518
  return {
@@ -631487,7 +631522,10 @@ function assistantMessageToMessageParam(message, addCache = false, enablePromptC
631487
631522
  type: "text",
631488
631523
  text: message.message.content,
631489
631524
  ...enablePromptCaching && {
631490
- cache_control: getCacheControl({ querySource })
631525
+ cache_control: getCacheControl({
631526
+ querySource,
631527
+ agentCacheTtlOverride
631528
+ })
631491
631529
  }
631492
631530
  }
631493
631531
  ]
@@ -631497,7 +631535,12 @@ function assistantMessageToMessageParam(message, addCache = false, enablePromptC
631497
631535
  role: "assistant",
631498
631536
  content: message.message.content.map((_4, i6) => ({
631499
631537
  ..._4,
631500
- ...i6 === message.message.content.length - 1 && _4.type !== "thinking" && _4.type !== "redacted_thinking" && (feature("CONNECTOR_TEXT") ? !isConnectorTextBlock(_4) : true) ? enablePromptCaching ? { cache_control: getCacheControl({ querySource }) } : {} : {}
631538
+ ...i6 === message.message.content.length - 1 && _4.type !== "thinking" && _4.type !== "redacted_thinking" && (feature("CONNECTOR_TEXT") ? !isConnectorTextBlock(_4) : true) ? enablePromptCaching ? {
631539
+ cache_control: getCacheControl({
631540
+ querySource,
631541
+ agentCacheTtlOverride
631542
+ })
631543
+ } : {} : {}
631501
631544
  }))
631502
631545
  };
631503
631546
  }
@@ -631719,7 +631762,7 @@ async function* queryModel(messages, systemPrompt, thinkingConfig, tools, signal
631719
631762
  queryCheckpoint("query_tool_schema_build_start");
631720
631763
  const isAgenticQuery = options.querySource.startsWith("repl_main_thread") || options.querySource.startsWith("agent:") || options.querySource === "sdk" || options.querySource === "hook_agent" || options.querySource === "verification_agent";
631721
631764
  const betas = getMergedBetas(options.model, { isAgenticQuery });
631722
- if (should1hCacheTTL(options.querySource) && shouldSendExtendedCacheTtlBeta() && !betas.includes(EXTENDED_CACHE_TTL_BETA_HEADER)) {
631765
+ if (should1hCacheTTL(options.querySource, options.agentCacheTtlOverride) && shouldSendExtendedCacheTtlBeta() && !betas.includes(EXTENDED_CACHE_TTL_BETA_HEADER)) {
631723
631766
  betas.push(EXTENDED_CACHE_TTL_BETA_HEADER);
631724
631767
  }
631725
631768
  if (isAdvisorEnabled()) {
@@ -631871,7 +631914,8 @@ ${deferredToolList}
631871
631914
  const enablePromptCaching = options.enablePromptCaching ?? getPromptCachingEnabled(options.model);
631872
631915
  const system = buildSystemPromptBlocks(systemPrompt, enablePromptCaching, {
631873
631916
  skipGlobalCacheForSystemPrompt: needsToolBasedCacheMarker,
631874
- querySource: options.querySource
631917
+ querySource: options.querySource,
631918
+ agentCacheTtlOverride: options.agentCacheTtlOverride
631875
631919
  });
631876
631920
  const useBetas = betas.length > 0;
631877
631921
  const extraToolSchemas = [...options.extraToolSchemas ?? []];
@@ -632026,7 +632070,7 @@ ${deferredToolList}
632026
632070
  lastRequestBetas = betasParams;
632027
632071
  return {
632028
632072
  model: normalizeModelStringForAPI(options.model),
632029
- messages: addCacheBreakpoints(messagesForAPI, enablePromptCaching2, options.querySource, useCachedMC, consumedCacheEdits, consumedPinnedEdits, options.skipCacheWrite),
632073
+ messages: addCacheBreakpoints(messagesForAPI, enablePromptCaching2, options.querySource, useCachedMC, consumedCacheEdits, consumedPinnedEdits, options.skipCacheWrite, options.agentCacheTtlOverride),
632030
632074
  system,
632031
632075
  tools: allTools,
632032
632076
  tool_choice: options.toolChoice,
@@ -632880,7 +632924,7 @@ function accumulateUsage(totalUsage, messageUsage) {
632880
632924
  function isToolResultBlock2(block) {
632881
632925
  return block !== null && typeof block === "object" && "type" in block && block.type === "tool_result" && "tool_use_id" in block;
632882
632926
  }
632883
- function addCacheBreakpoints(messages, enablePromptCaching, querySource, useCachedMC = false, newCacheEdits, pinnedEdits, skipCacheWrite = false) {
632927
+ function addCacheBreakpoints(messages, enablePromptCaching, querySource, useCachedMC = false, newCacheEdits, pinnedEdits, skipCacheWrite = false, agentCacheTtlOverride) {
632884
632928
  logEvent2("tengu_api_cache_breakpoints", {
632885
632929
  totalMessageCount: messages.length,
632886
632930
  cachingEnabled: enablePromptCaching,
@@ -632890,9 +632934,9 @@ function addCacheBreakpoints(messages, enablePromptCaching, querySource, useCach
632890
632934
  const result = messages.map((msg, index2) => {
632891
632935
  const addCache = index2 === markerIndex;
632892
632936
  if (msg.type === "user") {
632893
- return userMessageToMessageParam(msg, addCache, enablePromptCaching, querySource);
632937
+ return userMessageToMessageParam(msg, addCache, enablePromptCaching, querySource, agentCacheTtlOverride);
632894
632938
  }
632895
- return assistantMessageToMessageParam(msg, addCache, enablePromptCaching, querySource);
632939
+ return assistantMessageToMessageParam(msg, addCache, enablePromptCaching, querySource, agentCacheTtlOverride);
632896
632940
  });
632897
632941
  if (!useCachedMC) {
632898
632942
  return result;
@@ -632983,7 +633027,8 @@ function buildSystemPromptBlocks(systemPrompt, enablePromptCaching, options) {
632983
633027
  ...enablePromptCaching && block.cacheScope !== null && {
632984
633028
  cache_control: getCacheControl({
632985
633029
  scope: block.cacheScope,
632986
- querySource: options?.querySource
633030
+ querySource: options?.querySource,
633031
+ agentCacheTtlOverride: options?.agentCacheTtlOverride
632987
633032
  })
632988
633033
  }
632989
633034
  };
@@ -726092,6 +726137,7 @@ __export(exports_diskOutput, {
726092
726137
  appendTaskOutput: () => appendTaskOutput,
726093
726138
  _resetTaskOutputDirForTest: () => _resetTaskOutputDirForTest,
726094
726139
  _clearOutputsForTest: () => _clearOutputsForTest,
726140
+ OUTPUT_OMITTED_MARKER_FOR_TEST: () => OUTPUT_OMITTED_MARKER_FOR_TEST,
726095
726141
  MAX_TASK_OUTPUT_BYTES_DISPLAY: () => MAX_TASK_OUTPUT_BYTES_DISPLAY,
726096
726142
  MAX_TASK_OUTPUT_BYTES: () => MAX_TASK_OUTPUT_BYTES,
726097
726143
  DiskTaskOutput: () => DiskTaskOutput
@@ -726132,6 +726178,11 @@ class DiskTaskOutput {
726132
726178
  #queue = [];
726133
726179
  #bytesWritten = 0;
726134
726180
  #capped = false;
726181
+ #unwrittenChars = 0;
726182
+ #cancelCount = 0;
726183
+ #failing = false;
726184
+ #seenFailureKeys = new Set;
726185
+ #lostOutput = false;
726135
726186
  #flushPromise = null;
726136
726187
  #flushResolve = null;
726137
726188
  constructor(taskId) {
@@ -726144,11 +726195,14 @@ class DiskTaskOutput {
726144
726195
  this.#bytesWritten += content.length;
726145
726196
  if (this.#bytesWritten > MAX_TASK_OUTPUT_BYTES) {
726146
726197
  this.#capped = true;
726147
- this.#queue.push(`
726198
+ const marker = `
726148
726199
  [output truncated: exceeded ${MAX_TASK_OUTPUT_BYTES_DISPLAY} disk cap]
726149
- `);
726200
+ `;
726201
+ this.#queue.push(marker);
726202
+ this.#unwrittenChars += marker.length;
726150
726203
  } else {
726151
726204
  this.#queue.push(content);
726205
+ this.#unwrittenChars += content.length;
726152
726206
  }
726153
726207
  if (!this.#flushPromise) {
726154
726208
  this.#flushPromise = new Promise((resolve59) => {
@@ -726160,8 +726214,19 @@ class DiskTaskOutput {
726160
726214
  flush() {
726161
726215
  return this.#flushPromise ?? Promise.resolve();
726162
726216
  }
726217
+ get failing() {
726218
+ return this.#failing;
726219
+ }
726220
+ get lostOutput() {
726221
+ return this.#lostOutput;
726222
+ }
726223
+ get unwrittenChars() {
726224
+ return this.#unwrittenChars;
726225
+ }
726163
726226
  cancel() {
726227
+ this.#cancelCount += 1;
726164
726228
  this.#queue.length = 0;
726229
+ this.#unwrittenChars = 0;
726165
726230
  }
726166
726231
  async#drainAllChunks() {
726167
726232
  while (true) {
@@ -726171,7 +726236,17 @@ class DiskTaskOutput {
726171
726236
  this.#fileHandle = await open15(this.#path, process.platform === "win32" ? "a" : fsConstants9.O_WRONLY | fsConstants9.O_APPEND | fsConstants9.O_CREAT | O_NOFOLLOW2);
726172
726237
  }
726173
726238
  while (true) {
726174
- await this.#writeAllChunks();
726239
+ const cancelCount = this.#cancelCount;
726240
+ try {
726241
+ await this.#writeAllChunks();
726242
+ } catch (e4) {
726243
+ if (this.#cancelCount === cancelCount) {
726244
+ this.#lostOutput = true;
726245
+ this.#queue.unshift(OUTPUT_OMITTED_MARKER);
726246
+ this.#unwrittenChars += OUTPUT_OMITTED_MARKER.length;
726247
+ }
726248
+ throw e4;
726249
+ }
726175
726250
  if (this.#queue.length === 0) {
726176
726251
  break;
726177
726252
  }
@@ -726194,6 +726269,7 @@ class DiskTaskOutput {
726194
726269
  }
726195
726270
  #queueToBuffers() {
726196
726271
  const queue2 = this.#queue.splice(0, this.#queue.length);
726272
+ this.#unwrittenChars = 0;
726197
726273
  let totalLength = 0;
726198
726274
  for (const str2 of queue2) {
726199
726275
  totalLength += Buffer.byteLength(str2, "utf8");
@@ -726208,13 +726284,18 @@ class DiskTaskOutput {
726208
726284
  async#drain() {
726209
726285
  try {
726210
726286
  await this.#drainAllChunks();
726287
+ this.#clearFailureState();
726211
726288
  } catch (e4) {
726212
- logError2(e4);
726289
+ if (!this.#failing) {
726290
+ this.#failing = true;
726291
+ logError2(new Error(`Task output drain failed (will retry once): ${e4}`));
726292
+ }
726213
726293
  if (this.#queue.length > 0) {
726214
726294
  try {
726215
726295
  await this.#drainAllChunks();
726296
+ this.#clearFailureState();
726216
726297
  } catch (e22) {
726217
- logError2(e22);
726298
+ this.#handleFinalDrainFailure(e22);
726218
726299
  }
726219
726300
  }
726220
726301
  } finally {
@@ -726224,6 +726305,30 @@ class DiskTaskOutput {
726224
726305
  resolve59();
726225
726306
  }
726226
726307
  }
726308
+ #clearFailureState() {
726309
+ this.#failing = false;
726310
+ this.#seenFailureKeys.clear();
726311
+ }
726312
+ #handleFinalDrainFailure(e4) {
726313
+ const code = getErrnoCode(e4);
726314
+ const kind = code !== undefined && DISK_EXHAUSTION_ERRNOS.has(code) ? "exhaustion" : "unexpected";
726315
+ const failureKey = `${kind}:${code ?? "no errno"}`;
726316
+ if (!this.#seenFailureKeys.has(failureKey)) {
726317
+ this.#seenFailureKeys.add(failureKey);
726318
+ if (kind === "exhaustion") {
726319
+ logError2(new Error(`Task output drain retry failed (${code}): ${e4}`));
726320
+ } else {
726321
+ logError2(e4);
726322
+ }
726323
+ }
726324
+ if (this.#unwrittenChars > MAX_UNWRITTEN_CHARS_BEFORE_DROP) {
726325
+ logError2(new Error(`Task output still cannot be written (${code ?? "no errno"}); dropped ${this.#unwrittenChars} chars of unwritten output`));
726326
+ this.#lostOutput = true;
726327
+ this.#queue.length = 0;
726328
+ this.#queue.push(OUTPUT_OMITTED_MARKER);
726329
+ this.#unwrittenChars = OUTPUT_OMITTED_MARKER.length;
726330
+ }
726331
+ }
726227
726332
  }
726228
726333
  async function _clearOutputsForTest() {
726229
726334
  for (const output2 of outputs.values()) {
@@ -726242,6 +726347,14 @@ function getOrCreateOutput(taskId) {
726242
726347
  }
726243
726348
  return output2;
726244
726349
  }
726350
+ function logTaskOutputFailure(context8, e4) {
726351
+ const code = getErrnoCode(e4);
726352
+ if (code && DISK_EXHAUSTION_ERRNOS.has(code)) {
726353
+ logError2(new Error(`${context8} failed (${code}): ${e4}`));
726354
+ } else {
726355
+ logError2(e4);
726356
+ }
726357
+ }
726245
726358
  function appendTaskOutput(taskId, content) {
726246
726359
  getOrCreateOutput(taskId).append(content);
726247
726360
  }
@@ -726256,6 +726369,9 @@ function evictTaskOutput(taskId) {
726256
726369
  const output2 = outputs.get(taskId);
726257
726370
  if (output2) {
726258
726371
  await output2.flush();
726372
+ if (output2.failing && output2.unwrittenChars > 0) {
726373
+ logError2(new Error(`Task output writer evicted while failing; discarded ${output2.unwrittenChars} chars of unwritten output`));
726374
+ }
726259
726375
  outputs.delete(taskId);
726260
726376
  }
726261
726377
  })());
@@ -726275,7 +726391,7 @@ async function getTaskOutputDelta(taskId, fromOffset, maxBytes = DEFAULT_MAX_REA
726275
726391
  if (code === "ENOENT") {
726276
726392
  return { content: "", newOffset: fromOffset };
726277
726393
  }
726278
- logError2(e4);
726394
+ logTaskOutputFailure("getTaskOutputDelta", e4);
726279
726395
  return { content: "", newOffset: fromOffset };
726280
726396
  }
726281
726397
  }
@@ -726292,7 +726408,7 @@ ${content}`;
726292
726408
  if (code === "ENOENT") {
726293
726409
  return "";
726294
726410
  }
726295
- logError2(e4);
726411
+ logTaskOutputFailure("getTaskOutput", e4);
726296
726412
  return "";
726297
726413
  }
726298
726414
  }
@@ -726304,7 +726420,7 @@ async function getTaskOutputSize(taskId) {
726304
726420
  if (code === "ENOENT") {
726305
726421
  return 0;
726306
726422
  }
726307
- logError2(e4);
726423
+ logTaskOutputFailure("getTaskOutputSize", e4);
726308
726424
  return 0;
726309
726425
  }
726310
726426
  }
@@ -726321,7 +726437,7 @@ async function cleanupTaskOutput(taskId) {
726321
726437
  if (code === "ENOENT") {
726322
726438
  return;
726323
726439
  }
726324
- logError2(e4);
726440
+ logTaskOutputFailure("cleanupTaskOutput", e4);
726325
726441
  }
726326
726442
  }
726327
726443
  function initTaskOutput(taskId) {
@@ -726351,7 +726467,9 @@ function initTaskOutputAsSymlink(taskId, targetPath) {
726351
726467
  }
726352
726468
  })());
726353
726469
  }
726354
- var O_NOFOLLOW2, DEFAULT_MAX_READ_BYTES, MAX_TASK_OUTPUT_BYTES, MAX_TASK_OUTPUT_BYTES_DISPLAY = "5GB", _taskOutputDir, _pendingOps, outputs;
726470
+ var O_NOFOLLOW2, DEFAULT_MAX_READ_BYTES, MAX_TASK_OUTPUT_BYTES, MAX_TASK_OUTPUT_BYTES_DISPLAY = "5GB", MAX_UNWRITTEN_CHARS_BEFORE_DROP, OUTPUT_OMITTED_MARKER = `
726471
+ [output omitted: it could not be written to disk]
726472
+ `, OUTPUT_OMITTED_MARKER_FOR_TEST, DISK_EXHAUSTION_ERRNOS, _taskOutputDir, _pendingOps, outputs;
726355
726473
  var init_diskOutput = __esm(() => {
726356
726474
  init_state();
726357
726475
  init_errors();
@@ -726361,6 +726479,14 @@ var init_diskOutput = __esm(() => {
726361
726479
  O_NOFOLLOW2 = fsConstants9.O_NOFOLLOW ?? 0;
726362
726480
  DEFAULT_MAX_READ_BYTES = 8 * 1024 * 1024;
726363
726481
  MAX_TASK_OUTPUT_BYTES = 5 * 1024 * 1024 * 1024;
726482
+ MAX_UNWRITTEN_CHARS_BEFORE_DROP = 16 * 1024 * 1024;
726483
+ OUTPUT_OMITTED_MARKER_FOR_TEST = OUTPUT_OMITTED_MARKER;
726484
+ DISK_EXHAUSTION_ERRNOS = new Set([
726485
+ "ENOSPC",
726486
+ "EDQUOT",
726487
+ "ENFILE",
726488
+ "EMFILE"
726489
+ ]);
726364
726490
  _pendingOps = new Set;
726365
726491
  outputs = new Map;
726366
726492
  });
@@ -727073,21 +727199,6 @@ var init_execPromptHook = __esm(() => {
727073
727199
  init_hookHelpers();
727074
727200
  });
727075
727201
 
727076
- // src/utils/hooks/hookExit2Block.ts
727077
- function exit2BlockReason(params) {
727078
- const { status: status2, validationError, hasJson, stderr, command: command11 } = params;
727079
- if (status2 !== 2) {
727080
- return null;
727081
- }
727082
- if (hasJson && !validationError) {
727083
- return null;
727084
- }
727085
- return {
727086
- blockingError: `[${command11}]: ${stderr || "No stderr output"}`,
727087
- command: command11
727088
- };
727089
- }
727090
-
727091
727202
  // src/utils/hooks/execAgentHook.ts
727092
727203
  import { randomUUID as randomUUID40 } from "crypto";
727093
727204
  async function execAgentHook(hook, hookName, hookEvent, jsonInput, signal, toolUseContext, toolUseID, _messages, agentName) {
@@ -727692,10 +727803,16 @@ var init_execMcpToolHook = __esm(() => {
727692
727803
  // src/utils/hooks.ts
727693
727804
  var exports_hooks2 = {};
727694
727805
  __export(exports_hooks2, {
727806
+ wrapHookErrorWithStderr: () => wrapHookErrorWithStderr,
727695
727807
  skipFrontmatterHooksForUntrustedOrigin: () => skipFrontmatterHooksForUntrustedOrigin,
727696
727808
  shouldSkipHookDueToTrust: () => shouldSkipHookDueToTrust,
727697
727809
  processHookJSONOutput: () => processHookJSONOutput,
727810
+ parseHookOutput: () => parseHookOutput,
727811
+ looksLikeMissingHookScript: () => looksLikeMissingHookScript,
727698
727812
  isPerHookCallbackTimeout: () => isPerHookCallbackTimeout,
727813
+ isMultipleJsonDocuments: () => isMultipleJsonDocuments,
727814
+ isAsyncHookAnnouncement: () => isAsyncHookAnnouncement,
727815
+ hookOutputSchemaHint: () => hookOutputSchemaHint,
727699
727816
  hookCallbackTimeoutMessage: () => hookCallbackTimeoutMessage,
727700
727817
  hasWorktreeCreateHook: () => hasWorktreeCreateHook,
727701
727818
  hasInstructionsLoadedHook: () => hasInstructionsLoadedHook,
@@ -727708,6 +727825,7 @@ __export(exports_hooks2, {
727708
727825
  getSessionEndHookTimeoutMs: () => getSessionEndHookTimeoutMs,
727709
727826
  getPreToolHookBlockingMessage: () => getPreToolHookBlockingMessage,
727710
727827
  getMatchingHooks: () => getMatchingHooks,
727828
+ formatHookJsonValidationError: () => formatHookJsonValidationError,
727711
727829
  executeWorktreeRemoveHook: () => executeWorktreeRemoveHook,
727712
727830
  executeWorktreeCreateHook: () => executeWorktreeCreateHook,
727713
727831
  executeUserPromptSubmitHooks: () => executeUserPromptSubmitHooks,
@@ -727911,6 +728029,109 @@ function getSessionCronsForHookInput() {
727911
728029
  prompt: capHookString(task.prompt ?? "", HOOK_STRING_CAP)
727912
728030
  }));
727913
728031
  }
728032
+ function isHookJsonPlainObject(value) {
728033
+ return typeof value === "object" && value !== null && !Array.isArray(value);
728034
+ }
728035
+ function isHookJsonDiscriminatorIssue(issue2) {
728036
+ const last3 = issue2.path.at(-1);
728037
+ return issue2.code === "invalid_value" && typeof last3 === "string" && HOOK_JSON_DISCRIMINATOR_KEYS.has(last3);
728038
+ }
728039
+ function formatHookJsonIssue(issue2, parentPath) {
728040
+ const path39 = [...parentPath, ...issue2.path];
728041
+ if (issue2.code !== "invalid_union" || (issue2.errors?.length ?? 0) === 0) {
728042
+ return { path: path39, message: issue2.message.replace(/^Invalid input: /, "") };
728043
+ }
728044
+ const errors8 = issue2.errors;
728045
+ const relevant = errors8.find((branch2) => branch2.length > 0 && !branch2.some(isHookJsonDiscriminatorIssue))?.[0];
728046
+ if (relevant) {
728047
+ return formatHookJsonIssue(relevant, path39);
728048
+ }
728049
+ const discriminatorIssues = errors8.flat().filter(isHookJsonDiscriminatorIssue);
728050
+ const first2 = discriminatorIssues[0];
728051
+ if (first2?.code !== "invalid_value") {
728052
+ return { path: path39, message: issue2.message };
728053
+ }
728054
+ const values3 = [
728055
+ ...new Set(discriminatorIssues.flatMap((discriminator) => discriminator.code === "invalid_value" ? (discriminator.values ?? []).map((value) => jsonStringify(value)) : []))
728056
+ ];
728057
+ const preview = values3.slice(0, 6).join(" | ");
728058
+ return {
728059
+ path: [...path39, ...first2.path],
728060
+ message: `expected one of ${preview}${values3.length > 6 ? " | \u2026" : ""}`
728061
+ };
728062
+ }
728063
+ function formatHookJsonPath(path39) {
728064
+ return path39.map(String).join(".") || "(root)";
728065
+ }
728066
+ function formatHookJsonValidationError(parsed, issues) {
728067
+ const hookSpecificOutput = isHookJsonPlainObject(parsed) ? parsed.hookSpecificOutput : undefined;
728068
+ const formatted = issues.map((issue2) => formatHookJsonIssue(issue2, []));
728069
+ const first2 = formatted[0];
728070
+ let primary = first2 ? `${formatHookJsonPath(first2.path)}: ${first2.message}` : "unknown error";
728071
+ if (isHookJsonPlainObject(hookSpecificOutput) && !("hookEventName" in hookSpecificOutput)) {
728072
+ primary = 'hookSpecificOutput is missing required field "hookEventName"';
728073
+ } else if (isHookJsonPlainObject(hookSpecificOutput) && hookSpecificOutput.hookEventName === "PermissionRequest" && !isHookJsonPlainObject(hookSpecificOutput.decision) && first2?.path[0] === "hookSpecificOutput" && first2.path[1] === "decision") {
728074
+ primary += ' (PermissionRequest decision must be {"behavior": "allow"} or {"behavior": "deny", "message": "..."})';
728075
+ } else if (isHookJsonPlainObject(parsed) && first2?.path.length === 1 && first2.path[0] === "decision" && (parsed.decision === "allow" || parsed.decision === "deny" || parsed.decision === "ask")) {
728076
+ primary += parsed.decision === "ask" ? ' (top-level decision is the legacy approve|block field; for "ask" use hookSpecificOutput.permissionDecision in a PreToolUse hook)' : ` (top-level decision is the legacy approve|block field; for "${parsed.decision}" use hookSpecificOutput.permissionDecision in a PreToolUse hook, or hookSpecificOutput.decision: {"behavior": "${parsed.decision}"} in a PermissionRequest hook)`;
728077
+ }
728078
+ const rest = formatted.slice(1).map((entry) => ` - ${formatHookJsonPath(entry.path)}: ${entry.message}`).join(`
728079
+ `);
728080
+ return `${HOOK_JSON_VALIDATION_ERROR_PREFIX}${primary}${rest ? `
728081
+ ${rest}` : ""}
728082
+
728083
+ The hook's output was: ${jsonStringify(parsed, null, 2)}`;
728084
+ }
728085
+ function hookOutputSchemaHint() {
728086
+ return jsonStringify({
728087
+ continue: "boolean (optional)",
728088
+ suppressOutput: "boolean (optional)",
728089
+ stopReason: "string (optional)",
728090
+ decision: '"approve" | "block" (optional)',
728091
+ reason: "string (optional)",
728092
+ systemMessage: "string (optional)",
728093
+ terminalSequence: "string (optional)",
728094
+ hookSpecificOutput: {
728095
+ "for PreToolUse": {
728096
+ hookEventName: '"PreToolUse"',
728097
+ permissionDecision: '"allow" | "deny" | "ask" | "defer" (optional)',
728098
+ permissionDecisionReason: "string (optional)",
728099
+ updatedInput: "object (optional) - Modified tool input to use"
728100
+ },
728101
+ "for PermissionRequest": {
728102
+ hookEventName: '"PermissionRequest"',
728103
+ decision: {
728104
+ "to allow": {
728105
+ behavior: '"allow"',
728106
+ updatedInput: "object (optional) - Modified tool input to use",
728107
+ updatedPermissions: "array (optional) - Permission updates"
728108
+ },
728109
+ "to deny": {
728110
+ behavior: '"deny"',
728111
+ message: "string (optional)",
728112
+ interrupt: "boolean (optional)"
728113
+ }
728114
+ }
728115
+ },
728116
+ "for UserPromptSubmit": {
728117
+ hookEventName: '"UserPromptSubmit"',
728118
+ additionalContext: "string (optional)"
728119
+ },
728120
+ "for PostToolUse": {
728121
+ hookEventName: '"PostToolUse"',
728122
+ additionalContext: "string (optional)"
728123
+ },
728124
+ "for PostToolBatch": {
728125
+ hookEventName: '"PostToolBatch"',
728126
+ additionalContext: "string (optional)"
728127
+ },
728128
+ "for Stop / SubagentStop": {
728129
+ hookEventName: '"Stop" | "SubagentStop"',
728130
+ additionalContext: "string (optional) - Feedback for the model; the conversation continues so the model can act on it"
728131
+ }
728132
+ }
728133
+ }, null, 2);
728134
+ }
727914
728135
  function validateHookJson(jsonString) {
727915
728136
  const parsed = jsonParse(jsonString);
727916
728137
  const validation2 = hookJSONOutputSchema().safeParse(parsed);
@@ -727918,21 +728139,57 @@ function validateHookJson(jsonString) {
727918
728139
  logForDebugging("Successfully parsed and validated hook JSON output");
727919
728140
  return { json: validation2.data };
727920
728141
  }
727921
- const errors8 = validation2.error.issues.map((err2) => ` - ${err2.path.join(".")}: ${err2.message}`).join(`
727922
- `);
727923
728142
  return {
727924
- validationError: `Hook JSON output validation failed:
727925
- ${errors8}
727926
-
727927
- The hook's output was: ${jsonStringify(parsed, null, 2)}`
728143
+ validationError: formatHookJsonValidationError(parsed, validation2.error.issues)
727928
728144
  };
727929
728145
  }
728146
+ function isAsyncHookAnnouncement(stdout) {
728147
+ const firstLine = firstLineOf(stdout).trim();
728148
+ if (!firstLine.startsWith("{")) {
728149
+ return false;
728150
+ }
728151
+ try {
728152
+ const parsed = jsonParse(firstLine);
728153
+ return typeof parsed === "object" && parsed !== null && "async" in parsed && parsed.async === true;
728154
+ } catch {
728155
+ return false;
728156
+ }
728157
+ }
728158
+ function isMultipleJsonDocuments(output2) {
728159
+ const lines2 = output2.split(`
728160
+ `).filter((line) => line.trim() !== "");
728161
+ if (lines2.length < 2) {
728162
+ return false;
728163
+ }
728164
+ return lines2.every((line) => {
728165
+ try {
728166
+ const validation2 = hookJSONOutputSchema().safeParse(jsonParse(line.trim()));
728167
+ return !validation2.success || Object.keys(validation2.data).length === 0;
728168
+ } catch {
728169
+ return false;
728170
+ }
728171
+ });
728172
+ }
728173
+ function wrapHookErrorWithStderr(validationError, exitCode, stderr) {
728174
+ const trimmedStderr = stderr.trim();
728175
+ return exitCode !== 0 && trimmedStderr ? `${validationError}
728176
+
728177
+ Hook exited ${exitCode} with stderr:
728178
+ ${trimmedStderr}` : validationError;
728179
+ }
728180
+ function looksLikeMissingHookScript(params) {
728181
+ const { hookEvent, stdout, stderr, pluginId } = params;
728182
+ return (MISSING_SCRIPT_HOOK_EVENTS.has(hookEvent) || Boolean(pluginId) && hookEvent === "UserPromptSubmit") && !stdout.trim() && /no such file|can't open/i.test(stderr);
728183
+ }
727930
728184
  function parseHookOutput(stdout) {
727931
728185
  const trimmed = stdout.trim();
727932
728186
  if (!trimmed.startsWith("{")) {
727933
728187
  logForDebugging("Hook output does not start with {, treating as plain text");
727934
728188
  return { plainText: stdout };
727935
728189
  }
728190
+ if (isAsyncHookAnnouncement(stdout)) {
728191
+ return { json: { async: true } };
728192
+ }
727936
728193
  try {
727937
728194
  const result = validateHookJson(trimmed);
727938
728195
  if ("json" in result) {
@@ -727941,37 +728198,22 @@ function parseHookOutput(stdout) {
727941
728198
  const errorMessage3 = `${result.validationError}
727942
728199
 
727943
728200
  Expected schema:
727944
- ${jsonStringify({
727945
- continue: "boolean (optional)",
727946
- suppressOutput: "boolean (optional)",
727947
- stopReason: "string (optional)",
727948
- decision: '"approve" | "block" (optional)',
727949
- reason: "string (optional)",
727950
- systemMessage: "string (optional)",
727951
- permissionDecision: '"allow" | "deny" | "ask" (optional)',
727952
- hookSpecificOutput: {
727953
- "for PreToolUse": {
727954
- hookEventName: '"PreToolUse"',
727955
- permissionDecision: '"allow" | "deny" | "ask" (optional)',
727956
- permissionDecisionReason: "string (optional)",
727957
- updatedInput: "object (optional) - Modified tool input to use"
727958
- },
727959
- "for UserPromptSubmit": {
727960
- hookEventName: '"UserPromptSubmit"',
727961
- additionalContext: "string (required)",
727962
- sessionTitle: "string (optional) - Set the session title (same effect as /rename)"
727963
- },
727964
- "for PostToolUse": {
727965
- hookEventName: '"PostToolUse"',
727966
- additionalContext: "string (optional)"
727967
- }
727968
- }
727969
- }, null, 2)}`;
728201
+ ${hookOutputSchemaHint()}`;
727970
728202
  logForDebugging(errorMessage3);
727971
728203
  return { plainText: stdout, validationError: errorMessage3 };
727972
728204
  } catch (e4) {
727973
- logForDebugging(`Failed to parse hook output as JSON: ${e4}`);
727974
- return { plainText: stdout };
728205
+ const parseError = e4 instanceof Error ? e4.message : String(e4);
728206
+ if (!trimmed.endsWith("}")) {
728207
+ logForDebugging(`Hook output starts with { but is not a JSON object, treating as plain text: ${parseError}`);
728208
+ return { plainText: stdout };
728209
+ }
728210
+ if (isMultipleJsonDocuments(trimmed)) {
728211
+ logForDebugging("Hook output is several JSON documents, treating as plain text");
728212
+ return { plainText: stdout };
728213
+ }
728214
+ const errorMessage3 = `Hook output looks like a JSON object but is not valid JSON \u2014 ${parseError}. Emit the payload with a JSON encoder (jq, ConvertTo-Json, json.dumps) rather than string concatenation so backslashes and quotes inside strings are escaped.`;
728215
+ logForDebugging(errorMessage3);
728216
+ return { plainText: stdout, validationError: errorMessage3 };
727975
728217
  }
727976
728218
  }
727977
728219
  function parseHttpHookOutput(body) {
@@ -729130,7 +729372,7 @@ async function* executeHooks({
729130
729372
  hookEvent,
729131
729373
  output: httpResult.body,
729132
729374
  stdout: httpResult.body,
729133
- stderr: `JSON validation failed: ${httpValidationError}`,
729375
+ stderr: httpValidationError,
729134
729376
  exitCode: httpResult.statusCode,
729135
729377
  outcome: "error"
729136
729378
  });
@@ -729140,7 +729382,7 @@ async function* executeHooks({
729140
729382
  hookName,
729141
729383
  toolUseID,
729142
729384
  hookEvent,
729143
- stderr: `JSON validation failed: ${httpValidationError}`,
729385
+ stderr: httpValidationError,
729144
729386
  stdout: httpResult.body,
729145
729387
  exitCode: httpResult.statusCode ?? 0,
729146
729388
  command: hook.url,
@@ -729271,43 +729513,16 @@ async function* executeHooks({
729271
729513
  return;
729272
729514
  }
729273
729515
  const { json: json2, plainText, validationError } = parseHookOutput(result.stdout);
729274
- if (validationError) {
729275
- const exit2Block = exit2BlockReason({
729276
- status: result.status,
729277
- validationError,
729278
- hasJson: !!json2,
729279
- stderr: result.stderr,
729280
- command: hookCommand
729281
- });
729282
- if (exit2Block) {
729283
- emitHookResponse({
729284
- hookId,
729285
- hookName,
729286
- hookEvent,
729287
- output: result.output,
729288
- stdout: result.stdout,
729289
- stderr: result.stderr,
729290
- exitCode: result.status,
729291
- outcome: "error"
729292
- });
729293
- yield {
729294
- blockingError: {
729295
- blockingError: exit2Block.blockingError,
729296
- command: exit2Block.command
729297
- },
729298
- outcome: "blocking",
729299
- hook
729300
- };
729301
- return;
729302
- }
729516
+ if (validationError && result.status !== 2) {
729517
+ const stderr = wrapHookErrorWithStderr(validationError, result.status, result.stderr);
729303
729518
  emitHookResponse({
729304
729519
  hookId,
729305
729520
  hookName,
729306
729521
  hookEvent,
729307
729522
  output: result.output,
729308
729523
  stdout: result.stdout,
729309
- stderr: `JSON validation failed: ${validationError}`,
729310
- exitCode: 1,
729524
+ stderr,
729525
+ exitCode: result.status,
729311
729526
  outcome: "error"
729312
729527
  });
729313
729528
  yield {
@@ -729316,9 +729531,9 @@ async function* executeHooks({
729316
729531
  hookName,
729317
729532
  toolUseID,
729318
729533
  hookEvent,
729319
- stderr: `JSON validation failed: ${validationError}`,
729534
+ stderr,
729320
729535
  stdout: result.stdout,
729321
- exitCode: 1,
729536
+ exitCode: result.status,
729322
729537
  command: hookCommand,
729323
729538
  durationMs
729324
729539
  }),
@@ -729329,6 +729544,55 @@ async function* executeHooks({
729329
729544
  }
729330
729545
  if (json2) {
729331
729546
  if (isAsyncHookJSONOutput(json2)) {
729547
+ if (result.status === 2) {
729548
+ emitHookResponse({
729549
+ hookId,
729550
+ hookName,
729551
+ hookEvent,
729552
+ output: result.output,
729553
+ stdout: result.stdout,
729554
+ stderr: result.stderr,
729555
+ exitCode: result.status,
729556
+ outcome: "error"
729557
+ });
729558
+ yield {
729559
+ blockingError: {
729560
+ blockingError: `[${hook.command}]: ${result.stderr || "No stderr output"}`,
729561
+ command: hook.command
729562
+ },
729563
+ outcome: "blocking",
729564
+ hook
729565
+ };
729566
+ return;
729567
+ }
729568
+ if (result.status !== 0) {
729569
+ emitHookResponse({
729570
+ hookId,
729571
+ hookName,
729572
+ hookEvent,
729573
+ output: result.output,
729574
+ stdout: result.stdout,
729575
+ stderr: result.stderr,
729576
+ exitCode: result.status,
729577
+ outcome: "error"
729578
+ });
729579
+ yield {
729580
+ message: createAttachmentMessage({
729581
+ type: "hook_non_blocking_error",
729582
+ hookName,
729583
+ toolUseID,
729584
+ hookEvent,
729585
+ stderr: `Announced async, then failed with status code ${result.status}: ${result.stderr.trim() || "No stderr output"}`,
729586
+ stdout: result.stdout,
729587
+ exitCode: result.status,
729588
+ command: hookCommand,
729589
+ durationMs
729590
+ }),
729591
+ outcome: "non_blocking_error",
729592
+ hook
729593
+ };
729594
+ return;
729595
+ }
729332
729596
  yield {
729333
729597
  outcome: "success",
729334
729598
  hook
@@ -729431,6 +729695,39 @@ async function* executeHooks({
729431
729695
  };
729432
729696
  return;
729433
729697
  }
729698
+ if (result.status === 2 && looksLikeMissingHookScript({
729699
+ hookEvent,
729700
+ stdout: result.stdout,
729701
+ stderr: result.stderr,
729702
+ pluginId
729703
+ })) {
729704
+ emitHookResponse({
729705
+ hookId,
729706
+ hookName,
729707
+ hookEvent,
729708
+ output: result.output,
729709
+ stdout: result.stdout,
729710
+ stderr: result.stderr,
729711
+ exitCode: result.status,
729712
+ outcome: "error"
729713
+ });
729714
+ yield {
729715
+ message: createAttachmentMessage({
729716
+ type: "hook_non_blocking_error",
729717
+ hookName,
729718
+ toolUseID,
729719
+ hookEvent,
729720
+ stderr: `Hook script appears to be missing \u2014 "${hookCommand}" exited 2 with: ${result.stderr.trim()}. Treating as non-blocking. ${pluginId ? `Run \`/plugin\` to reinstall '${pluginId}' or remove it from settings.` : "If this is a plugin hook, check the plugin install (run /plugin)."}`,
729721
+ stdout: result.stdout,
729722
+ exitCode: result.status,
729723
+ command: hookCommand,
729724
+ durationMs
729725
+ }),
729726
+ outcome: "non_blocking_error",
729727
+ hook
729728
+ };
729729
+ return;
729730
+ }
729434
729731
  if (result.status === 2) {
729435
729732
  emitHookResponse({
729436
729733
  hookId,
@@ -729878,25 +730175,8 @@ async function executeHooksOutsideREPL({
729878
730175
  }
729879
730176
  logForDebugging(`${hookName} [${hook.command}] completed with status ${result.status}`);
729880
730177
  const { json: json2, validationError } = parseHookOutput(result.stdout);
729881
- if (validationError) {
729882
- const exit2Block = exit2BlockReason({
729883
- status: result.status,
729884
- validationError,
729885
- hasJson: !!json2,
729886
- stderr: result.stderr,
729887
- command: hook.command
729888
- });
729889
- if (exit2Block) {
729890
- return {
729891
- command: hook.command,
729892
- succeeded: false,
729893
- output: result.stderr || "",
729894
- blocked: true,
729895
- watchPaths: undefined,
729896
- systemMessage: undefined
729897
- };
729898
- }
729899
- throw new Error(validationError);
730178
+ if (validationError && result.status !== 2) {
730179
+ throw new Error(wrapHookErrorWithStderr(validationError, result.status, result.stderr));
729900
730180
  }
729901
730181
  if (json2 && !isAsyncHookJSONOutput(json2)) {
729902
730182
  logForDebugging(`Parsed JSON output from hook: ${jsonStringify(json2)}`, { level: "verbose" });
@@ -730926,7 +731206,7 @@ async function* executeMessageDisplayHooks(display, getAppState, agentId, signal
730926
731206
  timeoutMs
730927
731207
  });
730928
731208
  }
730929
- var TOOL_HOOK_EXECUTION_TIMEOUT_MS, PRE_TOOL_USE_HOOK_TIMEOUT_MESSAGE = "PreToolUse hook did not respond before its timeout (host client may be unreachable). The tool call was not executed; other configured hooks may not have completed.", SESSION_END_HOOK_TIMEOUT_MS_DEFAULT = 1500, BACKGROUND_TASK_TYPE_LABELS, HOOK_STRING_CAP = 1000, MATCHER_COMMA_HYPHEN_EVENTS;
731209
+ var TOOL_HOOK_EXECUTION_TIMEOUT_MS, PRE_TOOL_USE_HOOK_TIMEOUT_MESSAGE = "PreToolUse hook did not respond before its timeout (host client may be unreachable). The tool call was not executed; other configured hooks may not have completed.", SESSION_END_HOOK_TIMEOUT_MS_DEFAULT = 1500, BACKGROUND_TASK_TYPE_LABELS, HOOK_STRING_CAP = 1000, HOOK_JSON_VALIDATION_ERROR_PREFIX = "Hook JSON output validation failed \u2014 ", HOOK_JSON_DISCRIMINATOR_KEYS, MISSING_SCRIPT_HOOK_EVENTS, MATCHER_COMMA_HYPHEN_EVENTS;
730930
731210
  var init_hooks5 = __esm(() => {
730931
731211
  init_file();
730932
731212
  init_envValidation();
@@ -730989,6 +731269,13 @@ var init_hooks5 = __esm(() => {
730989
731269
  dream: "dream",
730990
731270
  remote_agent: "cloud session"
730991
731271
  };
731272
+ HOOK_JSON_DISCRIMINATOR_KEYS = new Set(["async", "hookEventName", "behavior"]);
731273
+ MISSING_SCRIPT_HOOK_EVENTS = new Set([
731274
+ "Stop",
731275
+ "SubagentStop",
731276
+ "TaskCompleted",
731277
+ "TeammateIdle"
731278
+ ]);
730992
731279
  MATCHER_COMMA_HYPHEN_EVENTS = new Set([
730993
731280
  "PreToolUse",
730994
731281
  "PostToolUse",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cnwenf/occ",
3
- "version": "2.1.313",
3
+ "version": "2.1.315",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {