@drisp/cli 0.5.24 → 0.5.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  ATHENA_HOOK_SOCKET_ENV,
3
3
  createAskUserQuestionResult,
4
+ createCompactBlockResult,
4
5
  createPermissionRequestAllowResult,
5
6
  createPermissionRequestDenyResult,
6
7
  createPreToolUseAllowResult,
@@ -12,30 +13,35 @@ import {
12
13
  isToolEvent,
13
14
  isValidHookEventEnvelope,
14
15
  resolveHookSocketPath
15
- } from "./chunk-BTKQ67RE.js";
16
+ } from "./chunk-USK5VSQ7.js";
16
17
  import {
17
18
  TransportUnreachableError,
18
19
  createUdsClientTransport,
20
+ createWsClientTransport,
19
21
  generateChannelRequestId,
20
22
  openVersionedDb,
21
23
  readDashboardClientConfig,
22
24
  refreshDashboardAccessToken,
23
25
  resolveGatewayPaths,
24
- traceGatewayFrame,
25
26
  trackGatewayTransportReconnect,
26
- writeGatewayTrace
27
- } from "./chunk-YU2WMPRC.js";
27
+ writeGatewayTrace,
28
+ wsClientOptionsForEndpoint
29
+ } from "./chunk-VAFS446Y.js";
28
30
  import {
31
+ DEFAULT_MAX_TURN_TOKEN_COUNT,
29
32
  compileWorkflowPlan,
30
33
  createWorkflowRunner,
34
+ deserializeRunMemory,
31
35
  installWorkflowFromSource,
36
+ isMarketplaceRef,
32
37
  readConfig,
33
38
  readGlobalConfig,
34
39
  resolveActiveWorkflow,
40
+ resolveMarketplacePlugin,
35
41
  resolveWorkflow,
36
42
  resolveWorkflowInstall,
37
43
  resolveWorkflowPlugins
38
- } from "./chunk-7UUPLAP4.js";
44
+ } from "./chunk-2QV7YSX6.js";
39
45
 
40
46
  // src/infra/daemon/stateDir.ts
41
47
  import fs from "fs";
@@ -238,6 +244,16 @@ var RULES = {
238
244
  defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
239
245
  canBlock: false
240
246
  },
247
+ // Observation-only: the batch fires after every call in it has already
248
+ // resolved, so nothing downstream produces a decision or block for it. The
249
+ // claim is not cosmetic — hook dispatch keeps every decision-capable event
250
+ // on Claude's critical path, so a false `canBlock` would make every
251
+ // parallel tool batch wait on the forwarder for a reply never sent.
252
+ "tool.batch": {
253
+ expectsDecision: false,
254
+ defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
255
+ canBlock: false
256
+ },
241
257
  "tool.delta": {
242
258
  expectsDecision: false,
243
259
  defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
@@ -278,16 +294,33 @@ var RULES = {
278
294
  defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
279
295
  canBlock: false
280
296
  },
297
+ // Decision-bearing for Handover (ADR 0014): a Workflow Run's orchestrator
298
+ // blocks the compaction and forks the conversation instead. Both claims are
299
+ // required — `canBlock` alone leaves the event off the decision-waiting
300
+ // path and the block reply is never sent. The timeout is deliberately a
301
+ // number, not null: with no decision (non-workflow session, orchestration
302
+ // failure) the adapter fires a passthrough and Claude proceeds with normal
303
+ // vendor compaction — Handover degrades, it never hangs.
281
304
  "compact.pre": {
282
- expectsDecision: false,
305
+ expectsDecision: true,
283
306
  defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
284
- canBlock: false
307
+ canBlock: true
285
308
  },
286
309
  "user.prompt": {
287
310
  expectsDecision: false,
288
311
  defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
289
312
  canBlock: true
290
313
  },
314
+ // Observation-only: nothing in the pipeline produces a decision or a block
315
+ // for an expansion, so it must not claim `canBlock`. The claim is not
316
+ // cosmetic — hook dispatch keeps every decision-capable event on Claude's
317
+ // critical path, so a false `canBlock` would make every slash command wait
318
+ // on the forwarder for a reply that is never sent.
319
+ "prompt.expansion": {
320
+ expectsDecision: false,
321
+ defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
322
+ canBlock: false
323
+ },
291
324
  "turn.start": {
292
325
  expectsDecision: false,
293
326
  defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
@@ -420,6 +453,15 @@ function asRecord(value) {
420
453
  }
421
454
  return {};
422
455
  }
456
+ function readToolBatchCalls(value) {
457
+ if (!Array.isArray(value)) return [];
458
+ return value.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => asRecord(entry)).map((call) => ({
459
+ tool_name: call["tool_name"],
460
+ tool_input: call["tool_input"],
461
+ tool_use_id: call["tool_use_id"],
462
+ tool_response: call["tool_response"]
463
+ }));
464
+ }
423
465
  function translateClaudeEnvelope(envelope) {
424
466
  const payload = asRecord(envelope.payload);
425
467
  let toolName;
@@ -444,7 +486,9 @@ function translateClaudeEnvelope(envelope) {
444
486
  kind: "session.start",
445
487
  data: {
446
488
  source: payload["source"],
447
- agent_type: payload["agent_type"]
489
+ model: payload["model"],
490
+ agent_type: payload["agent_type"],
491
+ session_title: payload["session_title"]
448
492
  }
449
493
  };
450
494
  case "SessionEnd":
@@ -458,6 +502,22 @@ function translateClaudeEnvelope(envelope) {
458
502
  return {
459
503
  kind: "user.prompt",
460
504
  data: {
505
+ prompt: payload["prompt"],
506
+ cwd: payload["cwd"],
507
+ permission_mode: payload["permission_mode"]
508
+ }
509
+ };
510
+ // Fires immediately before UserPromptSubmit, sharing its prompt_id,
511
+ // when a typed command expands into a prompt. Field names are pinned
512
+ // to the payload captured in the #116 spike.
513
+ case "UserPromptExpansion":
514
+ return {
515
+ kind: "prompt.expansion",
516
+ data: {
517
+ expansion_type: payload["expansion_type"],
518
+ command_name: payload["command_name"],
519
+ command_args: payload["command_args"],
520
+ command_source: payload["command_source"],
461
521
  prompt: payload["prompt"],
462
522
  permission_mode: payload["permission_mode"]
463
523
  }
@@ -485,6 +545,18 @@ function translateClaudeEnvelope(envelope) {
485
545
  tool_response: payload["tool_response"]
486
546
  }
487
547
  };
548
+ // Fires once per assistant tool batch, after every PostToolUse in it.
549
+ // Field names are pinned to the payload captured in the #116 spike;
550
+ // note that `tool_calls[].tool_response` is a STRING there, unlike
551
+ // PostToolUse's structured `tool_response`.
552
+ case "PostToolBatch":
553
+ return {
554
+ kind: "tool.batch",
555
+ data: {
556
+ tool_calls: readToolBatchCalls(payload["tool_calls"]),
557
+ permission_mode: payload["permission_mode"]
558
+ }
559
+ };
488
560
  case "PostToolUseFailure":
489
561
  return {
490
562
  kind: "tool.failure",
@@ -495,7 +567,10 @@ function translateClaudeEnvelope(envelope) {
495
567
  tool_input: payload["tool_input"] ?? {},
496
568
  tool_use_id: toolUseId,
497
569
  error: payload["error"],
498
- is_interrupt: payload["is_interrupt"]
570
+ is_interrupt: payload["is_interrupt"],
571
+ exit_code: payload["exit_code"],
572
+ output: payload["output"],
573
+ error_code: payload["error_code"]
499
574
  }
500
575
  };
501
576
  case "PermissionRequest":
@@ -721,6 +796,12 @@ function buildClaudeDisplay(kind, data) {
721
796
  }
722
797
 
723
798
  // src/harnesses/claude/runtime/mapper.ts
799
+ function readEffortLevel(payload) {
800
+ const effort = payload["effort"];
801
+ if (typeof effort !== "object" || effort === null) return void 0;
802
+ const level = effort["level"];
803
+ return typeof level === "string" ? level : void 0;
804
+ }
724
805
  function mapEnvelopeToRuntimeEvent(envelope) {
725
806
  const payload = envelope.payload;
726
807
  const safePayload = typeof payload === "object" && payload !== null ? payload : { value: payload };
@@ -738,6 +819,13 @@ function mapEnvelopeToRuntimeEvent(envelope) {
738
819
  data: translated.data,
739
820
  hookName: envelope.hook_event_name,
740
821
  sessionId: envelope.session_id,
822
+ // prompt_id is a Claude common input field (v2.1.196+) on every hook
823
+ // payload; carried harness-neutrally as the Prompt identity (ADR 0009).
824
+ promptId: safePayloadRecord["prompt_id"],
825
+ // `effort` is likewise a common input field ({level}), observed on
826
+ // PostToolUse-class payloads and never on SessionStart, so it is read here
827
+ // rather than in any one hook's translation.
828
+ effortLevel: readEffortLevel(safePayloadRecord),
741
829
  toolName: translated.toolName,
742
830
  toolUseId: translated.toolUseId,
743
831
  agentId: translated.agentId,
@@ -780,6 +868,8 @@ function mapDecisionToResult(_event, decision) {
780
868
  return createPreToolUseDenyResult(intent.reason);
781
869
  case "stop_block":
782
870
  return createStopBlockResult(intent.reason);
871
+ case "compact_block":
872
+ return createCompactBlockResult(intent.reason);
783
873
  default:
784
874
  return { action: "passthrough" };
785
875
  }
@@ -855,9 +945,23 @@ function extractResultText(content) {
855
945
  }
856
946
  return parts.join("\n");
857
947
  }
858
- function createStreamJsonToolParser(onToolResult, onToolUse) {
948
+ function extractAssistantText(content) {
949
+ if (!Array.isArray(content)) return "";
950
+ const parts = [];
951
+ for (const block of content) {
952
+ if (typeof block === "object" && block !== null) {
953
+ const rec = block;
954
+ if (rec["type"] === "text" && typeof rec["text"] === "string") {
955
+ parts.push(rec["text"]);
956
+ }
957
+ }
958
+ }
959
+ return parts.join("");
960
+ }
961
+ function createStreamJsonToolParser(onToolResult, onToolUse, onMessageDelta, onMessageComplete) {
859
962
  let buffer = "";
860
963
  const toolNameById = /* @__PURE__ */ new Map();
964
+ let currentMessageId;
861
965
  function processLine(line) {
862
966
  const trimmed = line.trim();
863
967
  if (!trimmed) return;
@@ -868,6 +972,19 @@ function createStreamJsonToolParser(onToolResult, onToolUse) {
868
972
  return;
869
973
  }
870
974
  const record = parsed.type === "stream_event" && parsed.event ? parsed.event : parsed;
975
+ if (record.type === "message_start") {
976
+ const id = record.message?.["id"];
977
+ if (typeof id === "string") currentMessageId = id;
978
+ }
979
+ if (record.type === "content_block_delta") {
980
+ const delta = record["delta"];
981
+ if (typeof delta === "object" && delta !== null) {
982
+ const d = delta;
983
+ if (d["type"] === "text_delta" && typeof d["text"] === "string") {
984
+ onMessageDelta?.({ item_id: currentMessageId, delta: d["text"] });
985
+ }
986
+ }
987
+ }
871
988
  const contentBlocks = (record.type === "assistant" ? record.message?.content : record.type === "message" && record.role === "assistant" ? record.content : void 0) ?? [];
872
989
  for (const block of contentBlocks) {
873
990
  if (typeof block !== "object" || block === null) continue;
@@ -877,6 +994,14 @@ function createStreamJsonToolParser(onToolResult, onToolUse) {
877
994
  onToolUse?.({ tool_use_id: rec["id"], tool_name: rec["name"] });
878
995
  }
879
996
  }
997
+ if (record.type === "assistant" || record.type === "message" && record.role === "assistant") {
998
+ const messageText = extractAssistantText(contentBlocks);
999
+ if (messageText) {
1000
+ const idValue = record.type === "assistant" ? record.message?.["id"] : record["id"];
1001
+ const itemId = typeof idValue === "string" ? idValue : currentMessageId;
1002
+ onMessageComplete?.({ item_id: itemId, message: messageText });
1003
+ }
1004
+ }
880
1005
  if (record.type === "tool_result") {
881
1006
  const toolUseId = typeof record.tool_use_id === "string" ? record.tool_use_id : void 0;
882
1007
  const text = extractResultText(record.content);
@@ -975,6 +1100,38 @@ function createServer2(opts) {
975
1100
  },
976
1101
  () => {
977
1102
  transportStats.streamToolUses++;
1103
+ },
1104
+ (messageDelta) => {
1105
+ emit({
1106
+ id: `stream-msg-delta-${messageDelta.item_id ?? "unknown"}-${Date.now()}`,
1107
+ timestamp: Date.now(),
1108
+ kind: "message.delta",
1109
+ data: {
1110
+ item_id: messageDelta.item_id,
1111
+ delta: messageDelta.delta
1112
+ },
1113
+ hookName: "stream-json",
1114
+ sessionId,
1115
+ context: { cwd: projectDir, transcriptPath: "" },
1116
+ interaction: getInteractionHints("message.delta"),
1117
+ payload: null
1118
+ });
1119
+ },
1120
+ (messageComplete) => {
1121
+ emit({
1122
+ id: `stream-msg-complete-${messageComplete.item_id ?? "unknown"}-${Date.now()}`,
1123
+ timestamp: Date.now(),
1124
+ kind: "message.complete",
1125
+ data: {
1126
+ item_id: messageComplete.item_id,
1127
+ message: messageComplete.message
1128
+ },
1129
+ hookName: "stream-json",
1130
+ sessionId,
1131
+ context: { cwd: projectDir, transcriptPath: "" },
1132
+ interaction: getInteractionHints("message.complete"),
1133
+ payload: null
1134
+ });
978
1135
  }
979
1136
  );
980
1137
  function emit(event) {
@@ -1375,6 +1532,10 @@ var TOOL_HOOK_EVENTS = [
1375
1532
  "PreToolUse",
1376
1533
  "PostToolUse",
1377
1534
  "PostToolUseFailure",
1535
+ // Batch-level, not per-tool, but Claude accepts (and requires) a matcher
1536
+ // here: the #116 capture registered it with `matcher: '*'` and it fired in
1537
+ // every run. Matcher-less registration is unverified.
1538
+ "PostToolBatch",
1378
1539
  "PermissionRequest",
1379
1540
  "PermissionDenied",
1380
1541
  "Elicitation",
@@ -1389,6 +1550,7 @@ var NON_TOOL_HOOK_EVENTS = [
1389
1550
  "SubagentStart",
1390
1551
  "SubagentStop",
1391
1552
  "UserPromptSubmit",
1553
+ "UserPromptExpansion",
1392
1554
  "PreCompact",
1393
1555
  "PostCompact",
1394
1556
  "Setup",
@@ -1401,6 +1563,31 @@ var NON_TOOL_HOOK_EVENTS = [
1401
1563
  "WorktreeCreate",
1402
1564
  "WorktreeRemove"
1403
1565
  ];
1566
+ var SYNC_HOOK_EVENTS = [
1567
+ // Decision/blocking — kept in step with `runtime/interactionRules.ts`.
1568
+ "PreToolUse",
1569
+ "PermissionRequest",
1570
+ "UserPromptSubmit",
1571
+ "Stop",
1572
+ "StopFailure",
1573
+ "SubagentStop",
1574
+ "Elicitation",
1575
+ "ElicitationResult",
1576
+ "TeammateIdle",
1577
+ "TaskCreated",
1578
+ "TaskCompleted",
1579
+ "ConfigChange",
1580
+ // Handover interception (ADR 0014): the forwarder may reply with a
1581
+ // compaction block. Dispatched async, Claude would ignore the stdout and
1582
+ // silently drop the block.
1583
+ "PreCompact",
1584
+ // Teardown — see (2) above.
1585
+ "SessionEnd"
1586
+ ];
1587
+ var SYNC_HOOK_EVENT_SET = new Set(SYNC_HOOK_EVENTS);
1588
+ function isAsyncHookEvent(event) {
1589
+ return !SYNC_HOOK_EVENT_SET.has(event);
1590
+ }
1404
1591
  function quoteShellArg(value) {
1405
1592
  return `'${value.replace(/'/g, `'"'"'`)}'`;
1406
1593
  }
@@ -1448,23 +1635,20 @@ function generateHookSettings(tempDir, authOverlay) {
1448
1635
  if (process.env["ATHENA_DEBUG"]) {
1449
1636
  console.error("[athena-debug] Hook forwarder path:", hookForwarder.command);
1450
1637
  }
1451
- const hookCommand = {
1452
- type: "command",
1453
- command: hookForwarder.command
1454
- };
1638
+ const hookCommandFor = (event) => isAsyncHookEvent(event) ? { type: "command", command: hookForwarder.command, async: true } : { type: "command", command: hookForwarder.command };
1455
1639
  const hooks = {};
1456
1640
  for (const event of TOOL_HOOK_EVENTS) {
1457
1641
  hooks[event] = [
1458
1642
  {
1459
1643
  matcher: "*",
1460
- hooks: [hookCommand]
1644
+ hooks: [hookCommandFor(event)]
1461
1645
  }
1462
1646
  ];
1463
1647
  }
1464
1648
  for (const event of NON_TOOL_HOOK_EVENTS) {
1465
1649
  hooks[event] = [
1466
1650
  {
1467
- hooks: [hookCommand]
1651
+ hooks: [hookCommandFor(event)]
1468
1652
  }
1469
1653
  ];
1470
1654
  }
@@ -2824,6 +3008,17 @@ import os8 from "os";
2824
3008
  import path10 from "path";
2825
3009
 
2826
3010
  // src/harnesses/claude/config/isolation.ts
3011
+ var CLAUDE_EFFORT_LEVELS = [
3012
+ "low",
3013
+ "medium",
3014
+ "high",
3015
+ "xhigh",
3016
+ "max"
3017
+ ];
3018
+ function normalizeEffort(value) {
3019
+ if (value === void 0) return void 0;
3020
+ return CLAUDE_EFFORT_LEVELS.includes(value) ? value : void 0;
3021
+ }
2827
3022
  var DISALLOWED_FIRST_PARTY_MCPS = [
2828
3023
  "mcp__claude_ai_Gmail__*",
2829
3024
  "mcp__claude_ai_Google_Calendar__*",
@@ -2934,6 +3129,7 @@ var FLAG_REGISTRY = [
2934
3129
  { field: "additionalDirectories", flag: "--add-dir", kind: "array" },
2935
3130
  // === Model & Agent ===
2936
3131
  { field: "model", flag: "--model", kind: "value" },
3132
+ { field: "effort", flag: "--effort", kind: "value" },
2937
3133
  { field: "fallbackModel", flag: "--fallback-model", kind: "value" },
2938
3134
  { field: "agent", flag: "--agent", kind: "value" },
2939
3135
  { field: "agents", flag: "--agents", kind: "json" },
@@ -3237,8 +3433,14 @@ function spawnClaude(options) {
3237
3433
  cwd: projectDir,
3238
3434
  stdio: ["ignore", "pipe", "pipe"],
3239
3435
  env: {
3240
- // Claude compacts around 95% of this window; 185k keeps the trigger above 175k tokens used.
3241
- CLAUDE_CODE_AUTO_COMPACT_WINDOW: "185000",
3436
+ // Claude compacts around 95% of this window. The default sits well
3437
+ // under the model window (ADR 0014 §5): Handover needs headroom to
3438
+ // hold the conversation AND emit a Handoff file in the fork — the old
3439
+ // 185k default sat at the window and defeated that. Precedence:
3440
+ // default < process.env < extraEnv, so a workflow's explicit
3441
+ // maxTurnTokenCount (delivered via extraEnv) beats a user env var,
3442
+ // while the bare default stays env-overridable.
3443
+ CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(DEFAULT_MAX_TURN_TOKEN_COUNT),
3242
3444
  ...process.env,
3243
3445
  ...extraEnv ?? {},
3244
3446
  ATHENA_INSTANCE_ID: String(instanceId),
@@ -3384,6 +3586,16 @@ function mergeIsolation(base, pluginMcpConfig, perCommand) {
3384
3586
  ...pluginMcpConfig ? { mcpConfig: pluginMcpConfig } : {}
3385
3587
  };
3386
3588
  }
3589
+ function resolveWorkflowSpawnEnv(workflow) {
3590
+ const maxTurnTokenCount = workflow?.loop?.maxTurnTokenCount;
3591
+ if (maxTurnTokenCount === void 0) {
3592
+ return workflow?.env;
3593
+ }
3594
+ return {
3595
+ ...workflow?.env,
3596
+ CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(maxTurnTokenCount)
3597
+ };
3598
+ }
3387
3599
  function resolveClaudeSessionId(continuation) {
3388
3600
  if (!continuation || continuation.mode === "fresh") {
3389
3601
  return void 0;
@@ -3608,7 +3820,7 @@ function useClaudeProcess(projectDir, instanceId, isolation, pluginMcpConfig, ve
3608
3820
  pluginMcpConfig,
3609
3821
  perCallIsolation
3610
3822
  ),
3611
- env: workflow?.env,
3823
+ env: resolveWorkflowSpawnEnv(workflow),
3612
3824
  ...verbose ? {
3613
3825
  jqFilter: JQ_ASSISTANT_TEXT_FILTER,
3614
3826
  onFilteredStdout: (data) => {
@@ -3747,6 +3959,7 @@ function createClaudeSessionController(input) {
3747
3959
  const tokenAccumulator = createTokenAccumulator();
3748
3960
  const messageAccumulator = createAssistantMessageAccumulator();
3749
3961
  let lastStderr = "";
3962
+ let stderrTail = "";
3750
3963
  if (supportsTransportDiagnostics) {
3751
3964
  runtime.beginTurn();
3752
3965
  }
@@ -3765,6 +3978,7 @@ function createClaudeSessionController(input) {
3765
3978
  tokens: tokenAccumulator.getUsage(),
3766
3979
  streamMessage: messageAccumulator.getLastMessage(),
3767
3980
  lastStderr,
3981
+ stderrTail,
3768
3982
  diagnostics: supportsTransportDiagnostics ? { transport: runtime.getTransportStats() } : void 0
3769
3983
  });
3770
3984
  };
@@ -3779,7 +3993,7 @@ function createClaudeSessionController(input) {
3779
3993
  input.pluginMcpConfig,
3780
3994
  configOverride
3781
3995
  ),
3782
- env: input.workflow?.env,
3996
+ env: resolveWorkflowSpawnEnv(input.workflow),
3783
3997
  onStdout: (data) => {
3784
3998
  tokenAccumulator.feed(data);
3785
3999
  messageAccumulator.feed(data);
@@ -3792,6 +4006,7 @@ function createClaudeSessionController(input) {
3792
4006
  if (!lastStderr) {
3793
4007
  lastStderr = trimmed;
3794
4008
  }
4009
+ stderrTail = (stderrTail + "\n" + trimmed).slice(-2048).trim();
3795
4010
  if (!input.verbose) return;
3796
4011
  onStderrLine?.(trimmed);
3797
4012
  },
@@ -3831,20 +4046,49 @@ function buildClaudeCompatibleIsolationConfig({
3831
4046
  additionalDirectories,
3832
4047
  pluginDirs,
3833
4048
  verbose,
3834
- configuredModel
4049
+ configuredModel,
4050
+ configuredEffort
3835
4051
  }) {
3836
4052
  return {
3837
4053
  preset: isolationPreset,
3838
4054
  additionalDirectories,
3839
4055
  pluginDirs: pluginDirs.length > 0 ? pluginDirs : void 0,
3840
4056
  debug: verbose,
3841
- model: configuredModel
4057
+ model: configuredModel,
4058
+ effort: normalizeEffort(configuredEffort)
3842
4059
  };
3843
4060
  }
4061
+ var CLAUDE_MODEL_OPTIONS = [
4062
+ {
4063
+ value: "sonnet",
4064
+ label: "Sonnet",
4065
+ description: "Balanced default for day-to-day coding work."
4066
+ },
4067
+ {
4068
+ value: "opus",
4069
+ label: "Opus",
4070
+ description: "Stronger reasoning for complex architecture and debugging."
4071
+ },
4072
+ {
4073
+ value: "haiku",
4074
+ label: "Haiku",
4075
+ description: "Fastest option for lighter tasks."
4076
+ },
4077
+ {
4078
+ value: "opusplan",
4079
+ label: "OpusPlan",
4080
+ description: "Uses Opus for planning and Sonnet for execution."
4081
+ }
4082
+ ];
3844
4083
  var CLAUDE_CONFIG_PROFILE = {
3845
4084
  harness: "claude-code",
3846
4085
  buildIsolationConfig: buildClaudeCompatibleIsolationConfig,
3847
- resolveModelName: ({ projectDir, configuredModel }) => resolveClaudeModel({ projectDir, configuredModel })
4086
+ resolveModelName: ({ projectDir, configuredModel }) => resolveClaudeModel({ projectDir, configuredModel }),
4087
+ pluginDelivery: {
4088
+ mergeWorkflowPluginDirs: true,
4089
+ registrationBuildsMcpConfig: true,
4090
+ workflowPluginsVia: "registration"
4091
+ }
3848
4092
  };
3849
4093
  var claudeHarnessAdapter = {
3850
4094
  id: "claude-code",
@@ -3854,7 +4098,9 @@ var claudeHarnessAdapter = {
3854
4098
  conversationModel: "fresh_per_turn",
3855
4099
  killWaitsForTurnSettlement: true,
3856
4100
  supportsEphemeralSessions: false,
3857
- supportsConfigurableIsolation: true
4101
+ supportsConfigurableIsolation: true,
4102
+ emitsStartupDiagnostics: true,
4103
+ extraAllowedTools: []
3858
4104
  },
3859
4105
  verify: () => verifyClaudeHarness(),
3860
4106
  createRuntime: (input) => createClaudeHookRuntime({
@@ -3890,7 +4136,8 @@ var claudeHarnessAdapter = {
3890
4136
  };
3891
4137
  return controller;
3892
4138
  },
3893
- resolveConfigProfile: () => CLAUDE_CONFIG_PROFILE
4139
+ resolveConfigProfile: () => CLAUDE_CONFIG_PROFILE,
4140
+ listModels: async () => CLAUDE_MODEL_OPTIONS
3894
4141
  };
3895
4142
 
3896
4143
  // src/harnesses/codex/runtime/appServerManager.ts
@@ -5584,6 +5831,10 @@ function mapToV2Decision(event, decision) {
5584
5831
  return { decision: wantsSessionApproval ? "acceptForSession" : "accept" };
5585
5832
  case "stop_block":
5586
5833
  return { decision: "cancel" };
5834
+ // Claude-specific Handover interception; Codex has no PreCompact
5835
+ // approval, so an (unexpected) compact_block on an approval request
5836
+ // falls through to accept like any other unmappable intent.
5837
+ case "compact_block":
5587
5838
  default:
5588
5839
  return { decision: wantsSessionApproval ? "acceptForSession" : "accept" };
5589
5840
  }
@@ -6702,7 +6953,7 @@ function resolveIsolation(preset) {
6702
6953
  case "strict":
6703
6954
  return { approvalPolicy: "on-request", sandbox: "read-only" };
6704
6955
  case "permissive":
6705
- return { approvalPolicy: "auto-edit", sandbox: "danger-full-access" };
6956
+ return { approvalPolicy: "never", sandbox: "danger-full-access" };
6706
6957
  case "minimal":
6707
6958
  case void 0:
6708
6959
  return { approvalPolicy: "on-request", sandbox: "workspace-write" };
@@ -6721,7 +6972,11 @@ function buildCodexPromptOptions(input) {
6721
6972
  agentRoots: input.workflowPlan?.agentRoots && input.workflowPlan.agentRoots.length > 0 ? input.workflowPlan.agentRoots : void 0,
6722
6973
  plugins: resolveCodexWorkflowPlugins(input.workflowPlan),
6723
6974
  config: {
6724
- model_auto_compact_token_limit: 175e3,
6975
+ // The harness-neutral maxTurnTokenCount (ADR 0014 §5): the bound sits
6976
+ // well under the model window so a Handover has headroom to hold the
6977
+ // conversation and emit a Handoff file — the old 175k default sat at
6978
+ // the window and defeated that.
6979
+ model_auto_compact_token_limit: input.workflowPlan?.workflow.loop?.maxTurnTokenCount ?? DEFAULT_MAX_TURN_TOKEN_COUNT,
6725
6980
  // Steer Codex's history compaction toward a handoff-style summary.
6726
6981
  // `compact_prompt` replaces the default summarization prompt.
6727
6982
  compact_prompt: HANDOFF_COMPACT_PROMPT,
@@ -7029,7 +7284,12 @@ var CODEX_CONFIG_PROFILE = {
7029
7284
  preset: isolationPreset,
7030
7285
  model: configuredModel
7031
7286
  }),
7032
- resolveModelName: ({ configuredModel }) => configuredModel ?? null
7287
+ resolveModelName: ({ configuredModel }) => configuredModel ?? null,
7288
+ pluginDelivery: {
7289
+ mergeWorkflowPluginDirs: false,
7290
+ registrationBuildsMcpConfig: false,
7291
+ workflowPluginsVia: "generated-mcp"
7292
+ }
7033
7293
  };
7034
7294
  var codexHarnessAdapter = {
7035
7295
  id: "openai-codex",
@@ -7039,7 +7299,12 @@ var codexHarnessAdapter = {
7039
7299
  conversationModel: "persistent_thread",
7040
7300
  killWaitsForTurnSettlement: true,
7041
7301
  supportsEphemeralSessions: true,
7042
- supportsConfigurableIsolation: true
7302
+ supportsConfigurableIsolation: true,
7303
+ emitsStartupDiagnostics: false,
7304
+ // Codex auto-approves Bash/Edit (outside-sandbox commandExecution /
7305
+ // fileChange) and the scoped 'Permissions' capability, on top of the
7306
+ // shared 'mcp__*' baseline seeded via allowedTools (see buildInitialRules).
7307
+ extraAllowedTools: ["Permissions", "Bash", "Edit"]
7043
7308
  },
7044
7309
  verify: () => verifyCodexHarness(),
7045
7310
  createRuntime: (input) => createCodexRuntime({
@@ -7066,8 +7331,23 @@ var codexHarnessAdapter = {
7066
7331
  };
7067
7332
  return controller;
7068
7333
  },
7069
- resolveConfigProfile: () => CODEX_CONFIG_PROFILE
7334
+ resolveConfigProfile: () => CODEX_CONFIG_PROFILE,
7335
+ listModels: async (runtime) => {
7336
+ if (!isCodexRuntimeWithModels(runtime)) {
7337
+ throw new Error("Codex runtime is not available");
7338
+ }
7339
+ const models = await runtime.listModels();
7340
+ return models.map((model) => ({
7341
+ value: model.model,
7342
+ label: model.displayName,
7343
+ description: model.description,
7344
+ isDefault: model.isDefault
7345
+ }));
7346
+ }
7070
7347
  };
7348
+ function isCodexRuntimeWithModels(runtime) {
7349
+ return Boolean(runtime && "listModels" in runtime);
7350
+ }
7071
7351
 
7072
7352
  // src/harnesses/registry.ts
7073
7353
  function buildClaudeCompatibleIsolationConfig2({
@@ -7093,7 +7373,9 @@ var opencodeHarnessAdapter = {
7093
7373
  conversationModel: "fresh_per_turn",
7094
7374
  killWaitsForTurnSettlement: true,
7095
7375
  supportsEphemeralSessions: false,
7096
- supportsConfigurableIsolation: false
7376
+ supportsConfigurableIsolation: false,
7377
+ emitsStartupDiagnostics: false,
7378
+ extraAllowedTools: []
7097
7379
  },
7098
7380
  createRuntime: (input) => claudeHarnessAdapter.createRuntime(input),
7099
7381
  createSessionController: (input) => claudeHarnessAdapter.createSessionController(input),
@@ -7101,8 +7383,14 @@ var opencodeHarnessAdapter = {
7101
7383
  resolveConfigProfile: () => ({
7102
7384
  harness: "opencode",
7103
7385
  buildIsolationConfig: (input) => buildClaudeCompatibleIsolationConfig2(input),
7104
- resolveModelName: ({ configuredModel }) => configuredModel ?? null
7105
- })
7386
+ resolveModelName: ({ configuredModel }) => configuredModel ?? null,
7387
+ pluginDelivery: {
7388
+ mergeWorkflowPluginDirs: true,
7389
+ registrationBuildsMcpConfig: true,
7390
+ workflowPluginsVia: "registration"
7391
+ }
7392
+ }),
7393
+ listModels: async () => []
7106
7394
  };
7107
7395
  var HARNESS_ADAPTERS = [
7108
7396
  claudeHarnessAdapter,
@@ -7218,6 +7506,28 @@ function loadPlugin(pluginDir) {
7218
7506
  }
7219
7507
  return commands2;
7220
7508
  }
7509
+ function loadPersonalSkills(skills) {
7510
+ const commands2 = [];
7511
+ for (const skill of skills) {
7512
+ const skillPath = path12.join(skill.path, "SKILL.md");
7513
+ if (!fs14.existsSync(skillPath)) {
7514
+ console.warn(
7515
+ `Skipping personal skill '${skill.name}' [${skill.sourceLayer}]: SKILL.md not found at ${skill.path}`
7516
+ );
7517
+ continue;
7518
+ }
7519
+ try {
7520
+ const parsed = parseFrontmatter(fs14.readFileSync(skillPath, "utf-8"));
7521
+ commands2.push(skillToCommand(parsed.frontmatter, parsed.body));
7522
+ } catch (error) {
7523
+ const message = error instanceof Error ? error.message : String(error);
7524
+ console.warn(
7525
+ `Skipping personal skill '${skill.name}' [${skill.sourceLayer}]: ${message}`
7526
+ );
7527
+ }
7528
+ }
7529
+ return commands2;
7530
+ }
7221
7531
  function skillToCommand(frontmatter, body, mcpConfigPath) {
7222
7532
  const args = frontmatter["argument-hint"] ? [
7223
7533
  {
@@ -7241,8 +7551,9 @@ function skillToCommand(frontmatter, body, mcpConfigPath) {
7241
7551
  }
7242
7552
 
7243
7553
  // src/infra/plugins/register.ts
7244
- function buildPluginMcpConfig(pluginDirs, mcpServerOptions) {
7554
+ function buildPluginMcpConfig(pluginDirs, mcpServerOptions, personalMcpServers = []) {
7245
7555
  const mergedServers = {};
7556
+ const conflicts = [];
7246
7557
  for (const dir of pluginDirs) {
7247
7558
  const mcpPath = path13.join(dir, ".mcp.json");
7248
7559
  if (!fs15.existsSync(mcpPath)) {
@@ -7268,22 +7579,99 @@ function buildPluginMcpConfig(pluginDirs, mcpServerOptions) {
7268
7579
  mergedServers[serverName] = rest;
7269
7580
  }
7270
7581
  }
7582
+ for (const personal of personalMcpServers) {
7583
+ const { name, sourceLayer: _sourceLayer, ...server } = personal;
7584
+ if (name in mergedServers) {
7585
+ conflicts.push(personal);
7586
+ continue;
7587
+ }
7588
+ mergedServers[name] = server;
7589
+ }
7271
7590
  if (Object.keys(mergedServers).length === 0) {
7272
- return void 0;
7591
+ return { mcpConfig: void 0, conflicts };
7273
7592
  }
7274
7593
  const mcpConfig = path13.join(os10.tmpdir(), `athena-mcp-${process.pid}.json`);
7275
7594
  fs15.writeFileSync(mcpConfig, JSON.stringify({ mcpServers: mergedServers }));
7276
- return mcpConfig;
7595
+ return { mcpConfig, conflicts };
7277
7596
  }
7278
- function registerPlugins(pluginDirs, mcpServerOptions, includeMcpConfig = true) {
7597
+ function registerPlugins(pluginDirs, mcpServerOptions, includeMcpConfig = true, personalMcpServers = [], personalSkills = []) {
7279
7598
  for (const dir of pluginDirs) {
7280
7599
  const commands2 = loadPlugin(dir);
7281
7600
  for (const command of commands2) {
7282
7601
  register(command);
7283
7602
  }
7284
7603
  }
7285
- const mcpConfig = includeMcpConfig ? buildPluginMcpConfig(pluginDirs, mcpServerOptions) : void 0;
7286
- return { mcpConfig };
7604
+ const skillByName = new Map(personalSkills.map((skill) => [skill.name, skill]));
7605
+ const skillConflicts = [];
7606
+ for (const command of loadPersonalSkills(personalSkills)) {
7607
+ if (get(command.name)) {
7608
+ const shadowed = skillByName.get(command.name);
7609
+ if (shadowed) {
7610
+ skillConflicts.push(shadowed);
7611
+ }
7612
+ continue;
7613
+ }
7614
+ register(command);
7615
+ }
7616
+ const mcpResult = includeMcpConfig ? buildPluginMcpConfig(pluginDirs, mcpServerOptions, personalMcpServers) : { mcpConfig: void 0, conflicts: [] };
7617
+ return {
7618
+ mcpConfig: mcpResult.mcpConfig,
7619
+ conflicts: { mcpServers: mcpResult.conflicts, skills: skillConflicts }
7620
+ };
7621
+ }
7622
+
7623
+ // src/infra/plugins/pluginRefResolution.ts
7624
+ function resolvePluginDirs(entries) {
7625
+ const dirs = [];
7626
+ const warnings = [];
7627
+ for (const entry of entries) {
7628
+ if (!isMarketplaceRef(entry)) {
7629
+ dirs.push(entry);
7630
+ continue;
7631
+ }
7632
+ try {
7633
+ dirs.push(resolveMarketplacePlugin(entry));
7634
+ } catch (error) {
7635
+ warnings.push(`Skipping plugin "${entry}": ${error.message}`);
7636
+ }
7637
+ }
7638
+ return { dirs, warnings };
7639
+ }
7640
+
7641
+ // src/infra/capabilities/effective.ts
7642
+ function resolveEffectiveCapabilities(input) {
7643
+ const { globalConfig, projectConfig } = input;
7644
+ return {
7645
+ mcpServers: mergeMcpServers(
7646
+ globalConfig.mcpServers,
7647
+ projectConfig.mcpServers
7648
+ ),
7649
+ skills: mergeSkills(globalConfig.skills, projectConfig.skills)
7650
+ };
7651
+ }
7652
+ function mergeMcpServers(global, project) {
7653
+ const projectNames = new Set(Object.keys(project ?? {}));
7654
+ const result = [];
7655
+ for (const [name, server] of Object.entries(global ?? {})) {
7656
+ if (projectNames.has(name)) continue;
7657
+ result.push({ name, ...server, sourceLayer: "global" });
7658
+ }
7659
+ for (const [name, server] of Object.entries(project ?? {})) {
7660
+ result.push({ name, ...server, sourceLayer: "project" });
7661
+ }
7662
+ return result;
7663
+ }
7664
+ function mergeSkills(global, project) {
7665
+ const projectNames = new Set((project ?? []).map((s) => s.name));
7666
+ const result = [];
7667
+ for (const skill of global ?? []) {
7668
+ if (projectNames.has(skill.name)) continue;
7669
+ result.push({ ...skill, sourceLayer: "global" });
7670
+ }
7671
+ for (const skill of project ?? []) {
7672
+ result.push({ ...skill, sourceLayer: "project" });
7673
+ }
7674
+ return result;
7287
7675
  }
7288
7676
 
7289
7677
  // src/setup/shouldResolveWorkflow.ts
@@ -7293,6 +7681,89 @@ function shouldResolveWorkflow({
7293
7681
  return Boolean(workflowName);
7294
7682
  }
7295
7683
 
7684
+ // src/core/workflows/builtins/handoffSkill.ts
7685
+ import fs16 from "fs";
7686
+ import os11 from "os";
7687
+ import path14 from "path";
7688
+ var DEFAULT_HANDOFF_FILE_PATH = ".athena/handoff.md";
7689
+ var PLUGIN_MANIFEST = `{
7690
+ "name": "athena-handoff",
7691
+ "description": "Athena's first-party handoff skill: distill the current conversation into a Handoff file",
7692
+ "version": "1.0.0"
7693
+ }
7694
+ `;
7695
+ var SKILL_CONTENT = `---
7696
+ name: handoff
7697
+ description: Distill the current conversation into a Handoff file on disk so a fresh agent session can continue the work without re-reading the history. Use when asked to hand off, or before context is reset.
7698
+ argument-hint: '[output-path] \u2014 where to write the Handoff file (default: ${DEFAULT_HANDOFF_FILE_PATH})'
7699
+ user-invocable: false
7700
+ ---
7701
+
7702
+ # Handoff
7703
+
7704
+ Write a **Handoff file**: a distillation of this conversation that lets a fresh
7705
+ agent session \u2014 with none of your context \u2014 continue the work seamlessly.
7706
+
7707
+ ## Output path
7708
+
7709
+ Write the file to: \`$ARGUMENTS\`
7710
+
7711
+ If no path was provided above (it reads "(none provided)"), write to
7712
+ \`${DEFAULT_HANDOFF_FILE_PATH}\` relative to the current working directory.
7713
+ Create parent directories as needed. Overwrite an existing file at that path.
7714
+
7715
+ ## What the file must contain
7716
+
7717
+ Structure the Handoff file with these sections, in this order:
7718
+
7719
+ 1. **Task and status** \u2014 what we are trying to accomplish, and exactly where it
7720
+ stands right now: what is done and verified, what is in flight, what has not
7721
+ been started. Include the concrete state of any in-flight step (e.g. "test X
7722
+ written but failing on Y").
7723
+ 2. **Decisions and rationale** \u2014 every decision already made, each with the
7724
+ *why*. A fresh session that doesn't know why a decision was made will
7725
+ re-litigate it and may silently reverse it.
7726
+ 3. **Open questions** \u2014 anything unresolved, ambiguous, or awaiting an answer,
7727
+ including from whom or where the answer should come.
7728
+ 4. **Files touched** \u2014 every file read, written, or relevant, referenced **by
7729
+ path** (repo-relative where possible). Never paste file contents \u2014 the next
7730
+ session can read the files itself.
7731
+ 5. **Suggested next steps / skills** \u2014 what the next session should do first,
7732
+ and which skills, commands, or workflows to invoke for it.
7733
+
7734
+ ## Rules
7735
+
7736
+ - **Reference, don't duplicate.** Point to existing artifacts \u2014 PRDs, plans,
7737
+ ADRs, issues, PRs, commits, diffs, trackers \u2014 by path or URL instead of
7738
+ copying their content into the Handoff file.
7739
+ - **Redact secrets.** Never write API keys, passwords, tokens, credentials, or
7740
+ personally identifiable information into the file. Replace any such value
7741
+ with \`[REDACTED]\` and, if needed, name where the real value lives (e.g. an
7742
+ env var name).
7743
+ - **Be selective, not exhaustive.** The file is a briefing, not a transcript.
7744
+ Everything the next session needs; nothing it can rediscover cheaply.
7745
+ - After writing the file, reply with only the absolute path of the file you
7746
+ wrote \u2014 no other prose.
7747
+ `;
7748
+ function writeIfChanged(filePath, content) {
7749
+ if (!fs16.existsSync(filePath) || fs16.readFileSync(filePath, "utf-8") !== content) {
7750
+ fs16.mkdirSync(path14.dirname(filePath), { recursive: true });
7751
+ fs16.writeFileSync(filePath, content, "utf-8");
7752
+ }
7753
+ }
7754
+ function ensureHandoffSkillPlugin(baseDir = path14.join(os11.homedir(), ".config", "athena", "builtins")) {
7755
+ const pluginDir = path14.join(baseDir, "handoff");
7756
+ writeIfChanged(
7757
+ path14.join(pluginDir, ".claude-plugin", "plugin.json"),
7758
+ PLUGIN_MANIFEST
7759
+ );
7760
+ writeIfChanged(
7761
+ path14.join(pluginDir, "skills", "handoff", "SKILL.md"),
7762
+ SKILL_CONTENT
7763
+ );
7764
+ return pluginDir;
7765
+ }
7766
+
7296
7767
  // src/app/runtime/createRuntime.ts
7297
7768
  var DEFAULT_HARNESS = "claude-code";
7298
7769
  function createRuntime(input) {
@@ -7339,6 +7810,9 @@ function bootstrapRuntimeConfig({
7339
7810
  const projectConfig = providedProjectConfig ?? readConfig(projectDir);
7340
7811
  const warnings = [];
7341
7812
  const harness = harnessOverride ?? projectConfig.harness ?? globalConfig.harness ?? DEFAULT_HARNESS;
7813
+ const harnessConfigProfile = resolveHarnessConfigProfile(harness);
7814
+ const pluginDelivery = harnessConfigProfile.pluginDelivery;
7815
+ const workflowPluginsAsGeneratedMcp = pluginDelivery.workflowPluginsVia === "generated-mcp";
7342
7816
  const activeWorkflowSelection = resolveActiveWorkflow({
7343
7817
  globalConfig,
7344
7818
  projectConfig,
@@ -7360,35 +7834,55 @@ function bootstrapRuntimeConfig({
7360
7834
  workflowPluginDirs = workflowResolvedPlugins.map(
7361
7835
  (plugin) => plugin.claudeArtifactDir
7362
7836
  );
7837
+ if (pluginDelivery.mergeWorkflowPluginDirs) {
7838
+ try {
7839
+ workflowPluginDirs.push(ensureHandoffSkillPlugin());
7840
+ } catch (error) {
7841
+ warnings.push(
7842
+ `Failed to materialize the handoff skill plugin: ${error instanceof Error ? error.message : String(error)}`
7843
+ );
7844
+ }
7845
+ }
7363
7846
  }
7847
+ const globalPluginResolution = resolvePluginDirs(globalConfig.plugins);
7848
+ const projectPluginResolution = resolvePluginDirs(projectConfig.plugins);
7849
+ warnings.push(
7850
+ ...globalPluginResolution.warnings,
7851
+ ...projectPluginResolution.warnings
7852
+ );
7364
7853
  const pluginDirs = mergePluginDirs({
7365
- workflowPluginDirs: harness === "openai-codex" ? [] : workflowPluginDirs,
7366
- globalPlugins: globalConfig.plugins,
7367
- projectPlugins: projectConfig.plugins,
7854
+ workflowPluginDirs: pluginDelivery.mergeWorkflowPluginDirs ? workflowPluginDirs : [],
7855
+ globalPlugins: globalPluginResolution.dirs,
7856
+ projectPlugins: projectPluginResolution.dirs,
7368
7857
  pluginFlags
7369
7858
  });
7370
- const pluginResult = pluginDirs.length > 0 ? registerPlugins(
7859
+ const effectiveCapabilities = harness === "openai-codex" ? { mcpServers: [], skills: [] } : resolveEffectiveCapabilities({ globalConfig, projectConfig });
7860
+ const personalMcpServers = effectiveCapabilities.mcpServers;
7861
+ const personalSkills = effectiveCapabilities.skills;
7862
+ const pluginResult = pluginDirs.length > 0 || personalMcpServers.length > 0 || personalSkills.length > 0 ? registerPlugins(
7371
7863
  pluginDirs,
7372
7864
  workflowToResolve ? activeWorkflowConfig.workflowSelections?.[workflowToResolve]?.mcpServerOptions : void 0,
7373
- harness !== "openai-codex"
7374
- ) : { mcpConfig: void 0 };
7375
- const workflowPluginMcpConfig = harness === "openai-codex" ? buildPluginMcpConfig(
7865
+ pluginDelivery.registrationBuildsMcpConfig,
7866
+ personalMcpServers,
7867
+ personalSkills
7868
+ ) : { mcpConfig: void 0, conflicts: { mcpServers: [], skills: [] } };
7869
+ const workflowPluginMcpConfig = workflowPluginsAsGeneratedMcp ? buildPluginMcpConfig(
7376
7870
  workflowPluginDirs,
7377
7871
  workflowToResolve ? activeWorkflowConfig.workflowSelections?.[workflowToResolve]?.mcpServerOptions : void 0
7378
- ) : void 0;
7379
- const pluginMcpConfig = harness === "openai-codex" ? void 0 : pluginResult.mcpConfig;
7872
+ ).mcpConfig : void 0;
7873
+ const pluginMcpConfig = workflowPluginsAsGeneratedMcp ? void 0 : pluginResult.mcpConfig;
7380
7874
  const activeWorkflow = resolvedWorkflow;
7381
7875
  const additionalDirectories = [
7382
7876
  ...globalConfig.additionalDirectories,
7383
7877
  ...projectConfig.additionalDirectories
7384
7878
  ];
7385
- const harnessConfigProfile = resolveHarnessConfigProfile(harness);
7386
7879
  const workflowPlan = compileWorkflowPlan({
7387
7880
  workflow: activeWorkflow,
7388
7881
  resolvedPlugins: activeWorkflow && resolvedWorkflow?.name === activeWorkflow.name ? workflowResolvedPlugins : void 0,
7389
- pluginMcpConfig: harness === "openai-codex" && activeWorkflow && resolvedWorkflow?.name === activeWorkflow.name ? workflowPluginMcpConfig : pluginResult.mcpConfig
7882
+ pluginMcpConfig: workflowPluginsAsGeneratedMcp && activeWorkflow && resolvedWorkflow?.name === activeWorkflow.name ? workflowPluginMcpConfig : pluginResult.mcpConfig
7390
7883
  });
7391
7884
  const configModel = projectConfig.model || globalConfig.model || activeWorkflow?.model;
7885
+ const configEffort = activeWorkflow?.effort;
7392
7886
  let isolationPreset = initialIsolationPreset;
7393
7887
  if (activeWorkflow?.isolation) {
7394
7888
  const presetOrder = ["strict", "minimal", "permissive"];
@@ -7407,7 +7901,8 @@ function bootstrapRuntimeConfig({
7407
7901
  additionalDirectories,
7408
7902
  pluginDirs,
7409
7903
  verbose,
7410
- configuredModel: configModel
7904
+ configuredModel: configModel,
7905
+ configuredEffort: configEffort
7411
7906
  });
7412
7907
  const modelName = harnessConfigProfile.resolveModelName({
7413
7908
  projectDir,
@@ -7423,6 +7918,9 @@ function bootstrapRuntimeConfig({
7423
7918
  workflow: activeWorkflow,
7424
7919
  workflowPlan,
7425
7920
  modelName,
7921
+ personalMcpServers,
7922
+ personalSkills,
7923
+ capabilityConflicts: pluginResult.conflicts,
7426
7924
  warnings
7427
7925
  };
7428
7926
  }
@@ -7444,7 +7942,7 @@ var EXEC_EXIT_CODE = {
7444
7942
 
7445
7943
  // src/app/exec/runner.ts
7446
7944
  import crypto2 from "crypto";
7447
- import path17 from "path";
7945
+ import path18 from "path";
7448
7946
 
7449
7947
  // src/core/feed/entities.ts
7450
7948
  var ActorRegistry = class {
@@ -7635,8 +8133,10 @@ function generateTitle(event, ascii = false) {
7635
8133
  }
7636
8134
  function generateNeutralTitle(event, g) {
7637
8135
  switch (event.kind) {
7638
- case "session.start":
7639
- return `Session started (${event.data.source})`;
8136
+ case "session.start": {
8137
+ const sessionTitle = event.data.session_title?.trim();
8138
+ return sessionTitle ? truncate(sessionTitle) : `Session started (${event.data.source})`;
8139
+ }
7640
8140
  case "session.end":
7641
8141
  return `Session ended (${event.data.reason})`;
7642
8142
  case "run.start":
@@ -7645,6 +8145,8 @@ function generateNeutralTitle(event, g) {
7645
8145
  return `Run ${event.data.status}`;
7646
8146
  case "user.prompt":
7647
8147
  return truncate(event.data.prompt);
8148
+ case "prompt.expansion":
8149
+ return event.data.command_name ? truncate(`Expanded /${event.data.command_name}`) : "Prompt expanded";
7648
8150
  case "plan.update":
7649
8151
  return truncate(
7650
8152
  event.data.explanation || event.data.delta || "Plan updated"
@@ -7659,6 +8161,10 @@ function generateNeutralTitle(event, g) {
7659
8161
  return `${g["tool.bullet"]} ${event.data.tool_name}`;
7660
8162
  case "tool.post":
7661
8163
  return `${g["tool.gutter"]} ${event.data.tool_name} result`;
8164
+ case "tool.batch": {
8165
+ const n = event.data.tool_calls.length;
8166
+ return `Batch of ${n} tool call${n === 1 ? "" : "s"}`;
8167
+ }
7662
8168
  case "tool.failure":
7663
8169
  return truncate(
7664
8170
  `${g["status.blocked"]} ${event.data.tool_name} failed: ${event.data.error}`
@@ -7809,27 +8315,27 @@ function generateNeutralTitle(event, g) {
7809
8315
  }
7810
8316
 
7811
8317
  // src/core/feed/transcript.ts
7812
- import * as fs16 from "fs";
8318
+ import * as fs17 from "fs";
7813
8319
  function createTranscriptReader() {
7814
8320
  const offsets = /* @__PURE__ */ new Map();
7815
8321
  function readNewAssistantMessages(transcriptPath) {
7816
8322
  const offset = offsets.get(transcriptPath) ?? 0;
7817
8323
  let fileSize;
7818
8324
  try {
7819
- fileSize = fs16.statSync(transcriptPath).size;
8325
+ fileSize = fs17.statSync(transcriptPath).size;
7820
8326
  if (fileSize <= offset) return [];
7821
8327
  } catch {
7822
8328
  return [];
7823
8329
  }
7824
8330
  let fd;
7825
8331
  try {
7826
- fd = fs16.openSync(transcriptPath, "r");
8332
+ fd = fs17.openSync(transcriptPath, "r");
7827
8333
  } catch {
7828
8334
  return [];
7829
8335
  }
7830
8336
  try {
7831
8337
  const buf = Buffer.alloc(fileSize - offset);
7832
- const bytesRead = fs16.readSync(fd, buf, 0, buf.length, offset);
8338
+ const bytesRead = fs17.readSync(fd, buf, 0, buf.length, offset);
7833
8339
  let lastNewline = -1;
7834
8340
  for (let i = bytesRead - 1; i >= 0; i--) {
7835
8341
  if (buf[i] === 10) {
@@ -7873,7 +8379,7 @@ function createTranscriptReader() {
7873
8379
  }
7874
8380
  return messages;
7875
8381
  } finally {
7876
- fs16.closeSync(fd);
8382
+ fs17.closeSync(fd);
7877
8383
  }
7878
8384
  }
7879
8385
  function getOffset(transcriptPath) {
@@ -7889,6 +8395,7 @@ function createRunLifecycle(boundary) {
7889
8395
  let currentRun = null;
7890
8396
  let seq = 0;
7891
8397
  let runSeq = 0;
8398
+ let currentPromptId;
7892
8399
  function getRunId() {
7893
8400
  const sessId = currentSession?.session_id ?? "unknown";
7894
8401
  return `${sessId}:R${runSeq}`;
@@ -7937,7 +8444,13 @@ function createRunLifecycle(boundary) {
7937
8444
  );
7938
8445
  }
7939
8446
  function beginRun(runtimeEvent, triggerType = "other", promptPreview) {
7940
- if (currentRun && triggerType === "other") return [];
8447
+ const promptId = runtimeEvent.promptId;
8448
+ const isExplicitContextTrigger = triggerType === "resume" || triggerType === "clear" || triggerType === "compact";
8449
+ if (promptId !== void 0 && !isExplicitContextTrigger) {
8450
+ if (currentRun && promptId === currentPromptId) return [];
8451
+ } else if (promptId === void 0 && currentRun && triggerType === "other") {
8452
+ return [];
8453
+ }
7941
8454
  const results = [];
7942
8455
  const closeEvt = closeRunIntoEvent(runtimeEvent, "completed");
7943
8456
  if (closeEvt) results.push(closeEvt);
@@ -7948,6 +8461,7 @@ function createRunLifecycle(boundary) {
7948
8461
  triggerType,
7949
8462
  promptPreview
7950
8463
  );
8464
+ currentPromptId = promptId;
7951
8465
  results.push(
7952
8466
  makeEvent(
7953
8467
  "run.start",
@@ -8027,6 +8541,7 @@ function createRunLifecycle(boundary) {
8027
8541
  if (e.kind === "tool.failure") currentRun.counters.tool_failures++;
8028
8542
  if (e.kind === "permission.request")
8029
8543
  currentRun.counters.permission_requests++;
8544
+ if (e.prompt_id !== void 0) currentPromptId = e.prompt_id;
8030
8545
  }
8031
8546
  }
8032
8547
  }
@@ -8538,6 +9053,15 @@ function toolUseCause(toolUseId, parentId) {
8538
9053
  ...parentId ? { parent_event_id: parentId } : {}
8539
9054
  };
8540
9055
  }
9056
+ function readToolBatchCalls2(value) {
9057
+ if (!Array.isArray(value)) return [];
9058
+ return value.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => readObject(entry)).map((call) => ({
9059
+ tool_name: readString(call["tool_name"]),
9060
+ tool_input: readObject(call["tool_input"]),
9061
+ tool_use_id: readString(call["tool_use_id"]),
9062
+ tool_response: readString(call["tool_response"])
9063
+ }));
9064
+ }
8541
9065
  function createToolProjection(args) {
8542
9066
  const {
8543
9067
  ensureRunArray,
@@ -8594,6 +9118,21 @@ function createToolProjection(args) {
8594
9118
  return {
8595
9119
  mapToolEvent(event, data) {
8596
9120
  const results = ensureRunArray(event);
9121
+ if (event.kind === "tool.batch") {
9122
+ results.push(
9123
+ makeEvent(
9124
+ "tool.batch",
9125
+ "info",
9126
+ resolveToolActor(),
9127
+ {
9128
+ tool_calls: readToolBatchCalls2(data["tool_calls"]),
9129
+ permission_mode: readString(data["permission_mode"])
9130
+ },
9131
+ event
9132
+ )
9133
+ );
9134
+ return results;
9135
+ }
8597
9136
  const toolUseId = resolveToolUseId(event, data);
8598
9137
  const toolName = event.toolName ?? readString(data["tool_name"]) ?? "Unknown";
8599
9138
  const toolInput = readObject(data["tool_input"]);
@@ -9372,7 +9911,8 @@ function createRunSessionProjection(args) {
9372
9911
  {
9373
9912
  source,
9374
9913
  agent_type: readString(data["agent_type"]),
9375
- model: readString(data["model"])
9914
+ model: readString(data["model"]),
9915
+ session_title: readString(data["session_title"])
9376
9916
  },
9377
9917
  event
9378
9918
  )
@@ -9417,6 +9957,34 @@ function createRunSessionProjection(args) {
9417
9957
  );
9418
9958
  return results;
9419
9959
  }
9960
+ if (event.kind === "prompt.expansion") {
9961
+ if (event.promptId !== void 0) {
9962
+ results.push(
9963
+ ...ensureRunArray(
9964
+ event,
9965
+ "user_prompt_submit",
9966
+ readString(data["prompt"])?.slice(0, 80)
9967
+ )
9968
+ );
9969
+ }
9970
+ results.push(
9971
+ makeEvent(
9972
+ "prompt.expansion",
9973
+ "info",
9974
+ "user",
9975
+ {
9976
+ expansion_type: readString(data["expansion_type"]),
9977
+ command_name: readString(data["command_name"]),
9978
+ command_args: readString(data["command_args"]),
9979
+ command_source: readString(data["command_source"]),
9980
+ prompt: readString(data["prompt"]),
9981
+ permission_mode: event.context.permissionMode ?? readString(data["permission_mode"])
9982
+ },
9983
+ event
9984
+ )
9985
+ );
9986
+ return results;
9987
+ }
9420
9988
  if (event.kind === "turn.start") {
9421
9989
  agentMessageStream.clearPending();
9422
9990
  const prompt = readString(data["prompt"]);
@@ -9649,6 +10217,7 @@ var RUN_SESSION_EVENT_KINDS = /* @__PURE__ */ new Set([
9649
10217
  "session.start",
9650
10218
  "session.end",
9651
10219
  "user.prompt",
10220
+ "prompt.expansion",
9652
10221
  "turn.start",
9653
10222
  "message.delta",
9654
10223
  "message.complete",
@@ -9661,6 +10230,7 @@ var TOOL_EVENT_KINDS = /* @__PURE__ */ new Set([
9661
10230
  "tool.delta",
9662
10231
  "tool.pre",
9663
10232
  "tool.post",
10233
+ "tool.batch",
9664
10234
  "tool.failure"
9665
10235
  ]);
9666
10236
  var DECISION_EVENT_KINDS = /* @__PURE__ */ new Set([
@@ -9722,6 +10292,8 @@ function createFeedMapper(bootstrap) {
9722
10292
  ts: runtimeEvent.timestamp,
9723
10293
  session_id: runtimeEvent.sessionId,
9724
10294
  run_id: runId,
10295
+ prompt_id: runtimeEvent.promptId,
10296
+ effort_level: runtimeEvent.effortLevel,
9725
10297
  kind,
9726
10298
  level,
9727
10299
  actor_id: actorId,
@@ -9876,8 +10448,34 @@ function createFeedMapper(bootstrap) {
9876
10448
  };
9877
10449
  }
9878
10450
 
10451
+ // src/core/feed/syntheticEvents.ts
10452
+ var HOOK_NAME_BY_KIND = {
10453
+ "task.created": "TaskCreated",
10454
+ "task.completed": "TaskCompleted"
10455
+ };
10456
+ function buildSyntheticTaskEvent(kind, sessionId, data) {
10457
+ const hookName = HOOK_NAME_BY_KIND[kind];
10458
+ return {
10459
+ id: `${kind}-${generateId()}`,
10460
+ timestamp: Date.now(),
10461
+ kind,
10462
+ data,
10463
+ hookName,
10464
+ sessionId,
10465
+ context: { cwd: "", transcriptPath: "" },
10466
+ interaction: { expectsDecision: false },
10467
+ payload: {
10468
+ hook_event_name: hookName,
10469
+ session_id: sessionId,
10470
+ transcript_path: "",
10471
+ cwd: "",
10472
+ ...data
10473
+ }
10474
+ };
10475
+ }
10476
+
9879
10477
  // src/infra/sessions/schema.ts
9880
- var SCHEMA_VERSION = 6;
10478
+ var SCHEMA_VERSION = 9;
9881
10479
  var applySessionSchema = (db, fromVersion) => {
9882
10480
  db.exec(`
9883
10481
  CREATE TABLE IF NOT EXISTS session (
@@ -9907,6 +10505,10 @@ var applySessionSchema = (db, fromVersion) => {
9907
10505
  actor_id TEXT NOT NULL,
9908
10506
  timestamp INTEGER NOT NULL,
9909
10507
  data JSON NOT NULL,
10508
+ -- Harness-native Prompt identity (Claude prompt_id): a nullable
10509
+ -- correlation key, NOT the Run identity (that stays run_id). Unset for
10510
+ -- pre-prompt bootstrap + harnesses/versions without it (ADR 0009).
10511
+ prompt_id TEXT,
9910
10512
  FOREIGN KEY (runtime_event_id) REFERENCES runtime_events(id)
9911
10513
  );
9912
10514
 
@@ -9936,6 +10538,18 @@ var applySessionSchema = (db, fromVersion) => {
9936
10538
  status TEXT NOT NULL DEFAULT 'running',
9937
10539
  stop_reason TEXT,
9938
10540
  tracker_path TEXT,
10541
+ -- Vendor session id (Claude Code session / Codex thread) of the Run's
10542
+ -- most recent Turn's Agent Session. Every resume- and fork-based
10543
+ -- transition (Nudge, Retry, human-resume, Handover) depends on it
10544
+ -- surviving a process restart (ADR 0014).
10545
+ adapter_session_id TEXT,
10546
+ -- Opaque JSON snapshot of the run-loop reducer's RunMemory (nudge/
10547
+ -- retry streaks, last tracker hash, in-flight stop prompt/
10548
+ -- continuation), so a resumed or woken process rehydrates the
10549
+ -- reducer instead of restarting its counters (ADR 0016). Written
10550
+ -- and read only by runMachine.ts's serializeRunMemory/
10551
+ -- deserializeRunMemory \u2014 this layer stores it opaquely.
10552
+ run_memory_json TEXT,
9939
10553
  FOREIGN KEY (session_id) REFERENCES session(id)
9940
10554
  );
9941
10555
 
@@ -9960,6 +10574,11 @@ var applySessionSchema = (db, fromVersion) => {
9960
10574
  CREATE INDEX IF NOT EXISTS idx_workflow_runs_session ON workflow_runs(session_id);
9961
10575
  CREATE INDEX IF NOT EXISTS idx_outbox_due ON channel_outbox(next_attempt_at);
9962
10576
  `);
10577
+ if (fromVersion === void 0 || fromVersion >= 7) {
10578
+ db.exec(
10579
+ "CREATE INDEX IF NOT EXISTS idx_feed_prompt ON feed_events(prompt_id);"
10580
+ );
10581
+ }
9963
10582
  if (fromVersion === void 0) return;
9964
10583
  if (fromVersion >= SCHEMA_VERSION) return;
9965
10584
  if (fromVersion < 2) {
@@ -10022,6 +10641,34 @@ var applySessionSchema = (db, fromVersion) => {
10022
10641
  UPDATE schema_version SET version = 6;
10023
10642
  `);
10024
10643
  }
10644
+ const versionAfterV6 = db.prepare("SELECT version FROM schema_version").get().version;
10645
+ if (versionAfterV6 === 6) {
10646
+ db.exec(`
10647
+ ALTER TABLE feed_events ADD COLUMN prompt_id TEXT;
10648
+ CREATE INDEX IF NOT EXISTS idx_feed_prompt ON feed_events(prompt_id);
10649
+ UPDATE schema_version SET version = 7;
10650
+ `);
10651
+ }
10652
+ const versionAfterV7 = db.prepare("SELECT version FROM schema_version").get().version;
10653
+ if (versionAfterV7 === 7) {
10654
+ const hasColumn = db.prepare(
10655
+ `SELECT COUNT(*) AS n FROM pragma_table_info('workflow_runs') WHERE name = 'adapter_session_id'`
10656
+ ).get().n > 0;
10657
+ if (!hasColumn) {
10658
+ db.exec("ALTER TABLE workflow_runs ADD COLUMN adapter_session_id TEXT;");
10659
+ }
10660
+ db.exec("UPDATE schema_version SET version = 8;");
10661
+ }
10662
+ const versionAfterV8 = db.prepare("SELECT version FROM schema_version").get().version;
10663
+ if (versionAfterV8 === 8) {
10664
+ const hasRunMemoryColumn = db.prepare(
10665
+ `SELECT COUNT(*) AS n FROM pragma_table_info('workflow_runs') WHERE name = 'run_memory_json'`
10666
+ ).get().n > 0;
10667
+ if (!hasRunMemoryColumn) {
10668
+ db.exec("ALTER TABLE workflow_runs ADD COLUMN run_memory_json TEXT;");
10669
+ }
10670
+ db.exec("UPDATE schema_version SET version = 9;");
10671
+ }
10025
10672
  };
10026
10673
  var SESSION_SCHEMA = {
10027
10674
  version: SCHEMA_VERSION,
@@ -10078,8 +10725,8 @@ function createSessionStore(opts) {
10078
10725
  VALUES (?, ?, ?, ?, ?, ?)`
10079
10726
  );
10080
10727
  const insertFeedEvent = db.prepare(
10081
- `INSERT OR IGNORE INTO feed_events (event_id, runtime_event_id, seq, kind, run_id, actor_id, timestamp, data)
10082
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
10728
+ `INSERT OR IGNORE INTO feed_events (event_id, runtime_event_id, seq, kind, run_id, actor_id, timestamp, data, prompt_id)
10729
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
10083
10730
  );
10084
10731
  const insertAdapterSession = db.prepare(
10085
10732
  `INSERT OR IGNORE INTO adapter_sessions (session_id, started_at)
@@ -10092,13 +10739,15 @@ function createSessionStore(opts) {
10092
10739
  "UPDATE session SET event_count = event_count + ? WHERE id = ?"
10093
10740
  );
10094
10741
  const upsertRun = db.prepare(
10095
- `INSERT INTO workflow_runs (id, session_id, workflow_name, started_at, iteration, max_iterations, status, stop_reason, tracker_path)
10096
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
10742
+ `INSERT INTO workflow_runs (id, session_id, workflow_name, started_at, iteration, max_iterations, status, stop_reason, tracker_path, adapter_session_id, run_memory_json)
10743
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
10097
10744
  ON CONFLICT(id) DO UPDATE SET
10098
10745
  iteration = excluded.iteration,
10099
10746
  status = excluded.status,
10100
10747
  stop_reason = excluded.stop_reason,
10101
- ended_at = CASE WHEN excluded.status != 'running' THEN ? ELSE ended_at END`
10748
+ adapter_session_id = COALESCE(excluded.adapter_session_id, adapter_session_id),
10749
+ run_memory_json = COALESCE(excluded.run_memory_json, run_memory_json),
10750
+ ended_at = CASE WHEN excluded.status NOT IN ('running', 'awaiting_attention') THEN ? ELSE ended_at END`
10102
10751
  );
10103
10752
  const selectLatestRun = db.prepare(
10104
10753
  `SELECT * FROM workflow_runs WHERE session_id = ? ORDER BY started_at DESC, rowid DESC LIMIT 1`
@@ -10134,7 +10783,8 @@ function createSessionStore(opts) {
10134
10783
  fe.run_id,
10135
10784
  fe.actor_id,
10136
10785
  fe.ts,
10137
- JSON.stringify(fe)
10786
+ JSON.stringify(fe),
10787
+ fe.prompt_id ?? null
10138
10788
  );
10139
10789
  }
10140
10790
  updateEventCount.run(feedEvents.length, opts.sessionId);
@@ -10150,7 +10800,8 @@ function createSessionStore(opts) {
10150
10800
  fe.run_id,
10151
10801
  fe.actor_id,
10152
10802
  fe.ts,
10153
- JSON.stringify(fe)
10803
+ JSON.stringify(fe),
10804
+ fe.prompt_id ?? null
10154
10805
  );
10155
10806
  }
10156
10807
  updateEventCount.run(feedEvents.length, opts.sessionId);
@@ -10262,7 +10913,7 @@ function createSessionStore(opts) {
10262
10913
  }
10263
10914
  function persistRun(snapshot) {
10264
10915
  const now2 = Date.now();
10265
- const endedAt = snapshot.status !== "running" ? now2 : null;
10916
+ const endedAt = snapshot.status !== "running" && snapshot.status !== "awaiting_attention" ? now2 : null;
10266
10917
  upsertRun.run(
10267
10918
  snapshot.runId,
10268
10919
  snapshot.sessionId,
@@ -10273,6 +10924,8 @@ function createSessionStore(opts) {
10273
10924
  snapshot.status,
10274
10925
  snapshot.stopReason ?? null,
10275
10926
  snapshot.trackerPath ?? null,
10927
+ snapshot.adapterSessionId ?? null,
10928
+ snapshot.runMemoryJson ?? null,
10276
10929
  endedAt
10277
10930
  );
10278
10931
  }
@@ -10289,7 +10942,9 @@ function createSessionStore(opts) {
10289
10942
  maxIterations: row.max_iterations,
10290
10943
  status: row.status,
10291
10944
  stopReason: row.stop_reason ?? void 0,
10292
- trackerPath: row.tracker_path ?? void 0
10945
+ trackerPath: row.tracker_path ?? void 0,
10946
+ adapterSessionId: row.adapter_session_id ?? void 0,
10947
+ runMemoryJson: row.run_memory_json ?? void 0
10293
10948
  };
10294
10949
  }
10295
10950
  function linkAdapterSession(adapterSessionId, runId) {
@@ -10997,8 +11652,8 @@ var PRIMARY_INPUT_EXTRACTORS = {
10997
11652
  return compactText(colonIdx >= 0 ? name.slice(colonIdx + 1) : name, 80);
10998
11653
  },
10999
11654
  NotebookEdit: (input) => {
11000
- const path21 = String(input.notebook_path ?? "");
11001
- return path21 ? shortenPath(path21) : "";
11655
+ const path22 = String(input.notebook_path ?? "");
11656
+ return path22 ? shortenPath(path22) : "";
11002
11657
  },
11003
11658
  AskUserQuestion: (input) => {
11004
11659
  const questions = input.questions;
@@ -11984,6 +12639,13 @@ var stopFailure = defaultRenderer(
11984
12639
  var permissionDenied = defaultRenderer(
11985
12640
  (event) => `${event.data.tool_name}${event.data.reason ? `: ${event.data.reason}` : ""}`
11986
12641
  );
12642
+ var promptExpansion = defaultRenderer(
12643
+ (event) => event.data.command_name ? `/${event.data.command_name}${event.data.command_args ? ` ${event.data.command_args}` : ""}` : event.data.expansion_type ?? ""
12644
+ );
12645
+ var toolBatch = defaultRenderer((event) => {
12646
+ const names = event.data.tool_calls.map((call) => call.tool_name).filter((name) => Boolean(name));
12647
+ return names.length > 0 ? names.join(", ") : "";
12648
+ });
11987
12649
  var elicitationRequest = defaultRenderer((event) => `elicitation from ${event.data.mcp_server}`);
11988
12650
  var elicitationResult = defaultRenderer(
11989
12651
  (event) => `${event.data.mcp_server} \u2192 ${event.data.action}`
@@ -12032,6 +12694,7 @@ var RENDERERS = {
12032
12694
  "tool.delta": toolDelta,
12033
12695
  "tool.pre": toolPre,
12034
12696
  "tool.post": toolPost,
12697
+ "tool.batch": toolBatch,
12035
12698
  "tool.failure": toolFailure,
12036
12699
  "permission.request": permissionRequest,
12037
12700
  "permission.decision": permissionDecision,
@@ -12071,6 +12734,7 @@ var RENDERERS = {
12071
12734
  "worktree.remove": worktreeRemove,
12072
12735
  "stop.failure": stopFailure,
12073
12736
  "permission.denied": permissionDenied,
12737
+ "prompt.expansion": promptExpansion,
12074
12738
  "elicitation.request": elicitationRequest,
12075
12739
  "elicitation.result": elicitationResult,
12076
12740
  "channel.permission.relayed": channelPermissionRelayed,
@@ -12269,16 +12933,16 @@ function extractDomain(url) {
12269
12933
  }
12270
12934
  }
12271
12935
  function filePathSegments(input) {
12272
- const path21 = prop2(input, "file_path") ?? prop2(input, "notebook_path") ?? "";
12273
- if (typeof path21 !== "string" || !path21) return [];
12274
- const { prefix, filename } = shortenPathStructured(path21);
12936
+ const path22 = prop2(input, "file_path") ?? prop2(input, "notebook_path") ?? "";
12937
+ if (typeof path22 !== "string" || !path22) return [];
12938
+ const { prefix, filename } = shortenPathStructured(path22);
12275
12939
  if (prefix && filename) {
12276
12940
  return [
12277
12941
  { text: prefix, role: "target" },
12278
12942
  { text: filename, role: "filename" }
12279
12943
  ];
12280
12944
  }
12281
- return [{ text: filename || path21, role: "filename" }];
12945
+ return [{ text: filename || path22, role: "filename" }];
12282
12946
  }
12283
12947
  function grepSegments(input) {
12284
12948
  const pattern = String(prop2(input, "pattern") ?? "");
@@ -12912,6 +13576,7 @@ function resolveEventToolColumn(event) {
12912
13576
  case "tool.delta":
12913
13577
  case "tool.pre":
12914
13578
  case "tool.post":
13579
+ case "tool.batch":
12915
13580
  case "tool.failure":
12916
13581
  case "permission.decision":
12917
13582
  case "permission.denied":
@@ -12939,6 +13604,7 @@ function resolveEventToolColumn(event) {
12939
13604
  case "todo.done":
12940
13605
  case "agent.message":
12941
13606
  case "teammate.idle":
13607
+ case "prompt.expansion":
12942
13608
  case "task.created":
12943
13609
  case "task.completed":
12944
13610
  case "config.change":
@@ -13444,6 +14110,30 @@ function openSessionDbReadonly(dbPath, options = {}) {
13444
14110
  for (const row of rows) counts[row.hook_name] = row.count;
13445
14111
  return counts;
13446
14112
  },
14113
+ latestWorkflowRun() {
14114
+ try {
14115
+ const row = db.prepare(
14116
+ "SELECT * FROM workflow_runs ORDER BY started_at DESC, rowid DESC LIMIT 1"
14117
+ ).get();
14118
+ if (!row) return void 0;
14119
+ return {
14120
+ id: row["id"],
14121
+ sessionId: row["session_id"],
14122
+ workflowName: row["workflow_name"] ?? void 0,
14123
+ startedAt: row["started_at"],
14124
+ endedAt: row["ended_at"] ?? void 0,
14125
+ iteration: row["iteration"],
14126
+ maxIterations: row["max_iterations"],
14127
+ status: row["status"],
14128
+ stopReason: row["stop_reason"] ?? void 0,
14129
+ trackerPath: row["tracker_path"] ?? void 0,
14130
+ adapterSessionId: row["adapter_session_id"] ?? void 0,
14131
+ runMemoryJson: row["run_memory_json"] ?? void 0
14132
+ };
14133
+ } catch {
14134
+ return void 0;
14135
+ }
14136
+ },
13447
14137
  feedEvents() {
13448
14138
  const rows = db.prepare("SELECT data FROM feed_events ORDER BY seq").all();
13449
14139
  return rows.map((r) => JSON.parse(r.data));
@@ -13455,17 +14145,17 @@ function openSessionDbReadonly(dbPath, options = {}) {
13455
14145
  }
13456
14146
 
13457
14147
  // src/infra/sessions/registry.ts
13458
- import fs17 from "fs";
13459
- import os11 from "os";
13460
- import path14 from "path";
14148
+ import fs18 from "fs";
14149
+ import os12 from "os";
14150
+ import path15 from "path";
13461
14151
  function sessionsDir() {
13462
- return path14.join(os11.homedir(), ".config", "athena", "sessions");
14152
+ return path15.join(os12.homedir(), ".config", "athena", "sessions");
13463
14153
  }
13464
14154
  function sessionDbPath(sessionId) {
13465
- return path14.join(sessionsDir(), sessionId, "session.db");
14155
+ return path15.join(sessionsDir(), sessionId, "session.db");
13466
14156
  }
13467
14157
  function readSessionFromDb(dbPath) {
13468
- if (!fs17.existsSync(dbPath)) return null;
14158
+ if (!fs18.existsSync(dbPath)) return null;
13469
14159
  let reader;
13470
14160
  try {
13471
14161
  reader = openSessionDbReadonly(dbPath);
@@ -13492,12 +14182,12 @@ function readSessionFromDb(dbPath) {
13492
14182
  }
13493
14183
  function listSessions(projectDir) {
13494
14184
  const dir = sessionsDir();
13495
- if (!fs17.existsSync(dir)) return [];
13496
- const entries = fs17.readdirSync(dir, { withFileTypes: true });
14185
+ if (!fs18.existsSync(dir)) return [];
14186
+ const entries = fs18.readdirSync(dir, { withFileTypes: true });
13497
14187
  const sessions = [];
13498
14188
  for (const entry of entries) {
13499
14189
  if (!entry.isDirectory()) continue;
13500
- const dbPath = path14.join(dir, entry.name, "session.db");
14190
+ const dbPath = path15.join(dir, entry.name, "session.db");
13501
14191
  const session = readSessionFromDb(dbPath);
13502
14192
  if (session && (session.eventCount ?? 0) > 0) {
13503
14193
  if (!projectDir || session.projectDir === projectDir) {
@@ -13514,6 +14204,44 @@ function getMostRecentAthenaSession(projectDir) {
13514
14204
  const sessions = listSessions(projectDir);
13515
14205
  return sessions[0] ?? null;
13516
14206
  }
14207
+ function getLatestRunForSession(sessionId, baseDir) {
14208
+ const dbPath = path15.join(baseDir ?? sessionsDir(), sessionId, "session.db");
14209
+ if (!fs18.existsSync(dbPath)) return null;
14210
+ let reader;
14211
+ try {
14212
+ reader = openSessionDbReadonly(dbPath);
14213
+ return reader.latestWorkflowRun() ?? null;
14214
+ } catch {
14215
+ return null;
14216
+ } finally {
14217
+ reader?.close();
14218
+ }
14219
+ }
14220
+ function listAwaitingAttentionRuns(projectDir, baseDir) {
14221
+ const dir = baseDir ?? sessionsDir();
14222
+ if (!fs18.existsSync(dir)) return [];
14223
+ const results = [];
14224
+ for (const entry of fs18.readdirSync(dir, { withFileTypes: true })) {
14225
+ if (!entry.isDirectory()) continue;
14226
+ const dbPath = path15.join(dir, entry.name, "session.db");
14227
+ const session = readSessionFromDb(dbPath);
14228
+ if (!session) continue;
14229
+ if (projectDir && session.projectDir !== projectDir) continue;
14230
+ const run = getLatestRunForSession(entry.name, dir);
14231
+ if (!run || run.status !== "awaiting_attention") continue;
14232
+ results.push({
14233
+ athenaSessionId: session.id,
14234
+ projectDir: session.projectDir,
14235
+ runId: run.id,
14236
+ workflowName: run.workflowName,
14237
+ stopReason: run.stopReason,
14238
+ adapterSessionId: run.adapterSessionId,
14239
+ startedAt: run.startedAt,
14240
+ sessionUpdatedAt: session.updatedAt
14241
+ });
14242
+ }
14243
+ return results.sort((a, b) => b.sessionUpdatedAt - a.sessionUpdatedAt);
14244
+ }
13517
14245
 
13518
14246
  // src/app/channels/relayAdapter.ts
13519
14247
  function createRelayPermissionCallback(bridge, runtime) {
@@ -13642,7 +14370,7 @@ function extractRelayQuestions(event) {
13642
14370
  }
13643
14371
 
13644
14372
  // src/app/channels/gatewayControlClient.ts
13645
- import { readFileSync as readFileSync3 } from "fs";
14373
+ import { readFileSync as readFileSync2 } from "fs";
13646
14374
 
13647
14375
  // src/gateway/control/client.ts
13648
14376
  import crypto from "crypto";
@@ -13750,6 +14478,7 @@ async function connect(opts) {
13750
14478
  preHelloResolve = (frame) => {
13751
14479
  clearTimeout(timer);
13752
14480
  preHelloAbort = null;
14481
+ helloAcked = true;
13753
14482
  resolve(frame);
13754
14483
  };
13755
14484
  preHelloAbort = (err) => {
@@ -13772,7 +14501,6 @@ async function connect(opts) {
13772
14501
  }
13773
14502
  throw new GatewayProtocolError(String(msg));
13774
14503
  }
13775
- helloAcked = true;
13776
14504
  const request = async (kind, payload, reqOpts) => {
13777
14505
  const requestId = crypto.randomUUID();
13778
14506
  const envelope = {
@@ -13793,9 +14521,6 @@ async function connect(opts) {
13793
14521
  );
13794
14522
  connection.send(envelope);
13795
14523
  const res = await responsePromise;
13796
- if (res.request_id !== requestId) {
13797
- throw new GatewayProtocolError("response request_id mismatch");
13798
- }
13799
14524
  if (!res.ok) {
13800
14525
  throw new GatewayProtocolError(
13801
14526
  `${res.error.code}: ${res.error.message}`,
@@ -13832,93 +14557,6 @@ function isStringRecord(v) {
13832
14557
  return typeof v === "object" && v !== null && !Array.isArray(v);
13833
14558
  }
13834
14559
 
13835
- // src/gateway/transport/wsClient.ts
13836
- import { WebSocket } from "ws";
13837
- import { readFileSync as readFileSync2 } from "fs";
13838
- function createWsClientTransport(opts) {
13839
- return {
13840
- kind: "ws",
13841
- connect: () => connectWs(opts)
13842
- };
13843
- }
13844
- function wsClientOptionsForEndpoint(input) {
13845
- return {
13846
- url: input.url,
13847
- ...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {},
13848
- ...input.tlsCaPath !== void 0 ? { tlsCaPath: input.tlsCaPath } : {}
13849
- };
13850
- }
13851
- async function connectWs(opts) {
13852
- const timeoutMs = opts.timeoutMs ?? 5e3;
13853
- const wsOpts = opts.tlsCaPath ? { ca: readFileSync2(opts.tlsCaPath) } : {};
13854
- const ws = new WebSocket(opts.url, wsOpts);
13855
- await new Promise((resolve, reject) => {
13856
- const timer = setTimeout(() => {
13857
- ws.close();
13858
- reject(
13859
- new TransportUnreachableError(`connect timed out after ${timeoutMs}ms`)
13860
- );
13861
- }, timeoutMs);
13862
- ws.once("open", () => {
13863
- clearTimeout(timer);
13864
- resolve();
13865
- });
13866
- ws.once("error", (err) => {
13867
- clearTimeout(timer);
13868
- reject(
13869
- new TransportUnreachableError(
13870
- `gateway not reachable at ${opts.url}: ${err.message}`
13871
- )
13872
- );
13873
- });
13874
- });
13875
- return createWsConnection(ws, opts.url);
13876
- }
13877
- function createWsConnection(ws, peer) {
13878
- const frameHandlers = /* @__PURE__ */ new Set();
13879
- const closeHandlers = /* @__PURE__ */ new Set();
13880
- const errorHandlers = /* @__PURE__ */ new Set();
13881
- ws.on("message", (data) => {
13882
- let parsed;
13883
- try {
13884
- parsed = JSON.parse(data.toString());
13885
- } catch {
13886
- ws.close();
13887
- return;
13888
- }
13889
- traceGatewayFrame("ws-client", peer, "in", parsed);
13890
- for (const handler of frameHandlers) handler(parsed);
13891
- });
13892
- ws.on("error", (err) => {
13893
- for (const handler of errorHandlers) handler(err);
13894
- });
13895
- ws.on("close", () => {
13896
- for (const handler of closeHandlers) handler();
13897
- });
13898
- return {
13899
- kind: "ws",
13900
- peer,
13901
- send: (frame) => {
13902
- if (ws.readyState !== ws.OPEN) return;
13903
- traceGatewayFrame("ws-client", peer, "out", frame);
13904
- ws.send(JSON.stringify(frame));
13905
- },
13906
- close: () => ws.close(),
13907
- onFrame: (cb) => {
13908
- frameHandlers.add(cb);
13909
- return () => frameHandlers.delete(cb);
13910
- },
13911
- onClose: (cb) => {
13912
- closeHandlers.add(cb);
13913
- return () => closeHandlers.delete(cb);
13914
- },
13915
- onError: (cb) => {
13916
- errorHandlers.add(cb);
13917
- return () => errorHandlers.delete(cb);
13918
- }
13919
- };
13920
- }
13921
-
13922
14560
  // src/app/channels/gatewayControlClient.ts
13923
14561
  async function connectGatewayControlClient(opts) {
13924
14562
  const loadToken = opts.loadToken ?? defaultLoadToken;
@@ -13940,13 +14578,13 @@ async function connectGatewayControlClient(opts) {
13940
14578
  });
13941
14579
  }
13942
14580
  function defaultLoadToken(tokenPath) {
13943
- return readFileSync3(tokenPath, "utf8").trim();
14581
+ return readFileSync2(tokenPath, "utf8").trim();
13944
14582
  }
13945
14583
 
13946
14584
  // src/infra/config/gatewayClient.ts
13947
- import fs18 from "fs";
13948
- import os12 from "os";
13949
- import path15 from "path";
14585
+ import fs19 from "fs";
14586
+ import os13 from "os";
14587
+ import path16 from "path";
13950
14588
 
13951
14589
  // src/shared/gateway-protocol/endpoint.ts
13952
14590
  function parseRuntimeEndpoint(value) {
@@ -14001,17 +14639,17 @@ function isRecord2(value) {
14001
14639
 
14002
14640
  // src/infra/config/gatewayClient.ts
14003
14641
  function resolveGatewayClientConfigPath(env = process.env) {
14004
- const home = env["HOME"] ?? os12.homedir();
14005
- return path15.join(home, ".config", "athena", "gateway.json");
14642
+ const home = env["HOME"] ?? os13.homedir();
14643
+ return path16.join(home, ".config", "athena", "gateway.json");
14006
14644
  }
14007
14645
  function readGatewayClientConfig(env = process.env) {
14008
14646
  const configPath = resolveGatewayClientConfigPath(env);
14009
- if (!fs18.existsSync(configPath)) {
14647
+ if (!fs19.existsSync(configPath)) {
14010
14648
  return { mode: "local" };
14011
14649
  }
14012
14650
  let parsed;
14013
14651
  try {
14014
- parsed = JSON.parse(fs18.readFileSync(configPath, "utf-8"));
14652
+ parsed = JSON.parse(fs19.readFileSync(configPath, "utf-8"));
14015
14653
  } catch (err) {
14016
14654
  throw new Error(
14017
14655
  `gateway client config ${configPath} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`
@@ -14028,16 +14666,16 @@ function readGatewayClientConfig(env = process.env) {
14028
14666
  function writeGatewayClientConfig(config, env = process.env) {
14029
14667
  const parsed = parseRuntimeEndpoint(config);
14030
14668
  const configPath = resolveGatewayClientConfigPath(env);
14031
- const dir = path15.dirname(configPath);
14032
- fs18.mkdirSync(dir, { recursive: true, mode: 448 });
14033
- fs18.writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n", {
14669
+ const dir = path16.dirname(configPath);
14670
+ fs19.mkdirSync(dir, { recursive: true, mode: 448 });
14671
+ fs19.writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n", {
14034
14672
  encoding: "utf-8",
14035
14673
  mode: 384
14036
14674
  });
14037
14675
  if (process.platform !== "win32") {
14038
14676
  try {
14039
- fs18.chmodSync(dir, 448);
14040
- fs18.chmodSync(configPath, 384);
14677
+ fs19.chmodSync(dir, 448);
14678
+ fs19.chmodSync(configPath, 384);
14041
14679
  } catch {
14042
14680
  }
14043
14681
  }
@@ -14825,6 +15463,20 @@ function handleEvent(event, cb) {
14825
15463
  }
14826
15464
  };
14827
15465
  }
15466
+ if (eventKind2 === "compact.pre" && cb.interceptCompaction) {
15467
+ const reason = cb.interceptCompaction(event);
15468
+ if (reason !== null) {
15469
+ return {
15470
+ handled: true,
15471
+ decision: {
15472
+ type: "json",
15473
+ source: "rule",
15474
+ intent: { kind: "compact_block", reason }
15475
+ }
15476
+ };
15477
+ }
15478
+ return { handled: false };
15479
+ }
14828
15480
  return { handled: false };
14829
15481
  }
14830
15482
 
@@ -15005,8 +15657,8 @@ function exitCodeFromFailure(failure) {
15005
15657
  }
15006
15658
 
15007
15659
  // src/app/exec/output.ts
15008
- import fs19 from "fs/promises";
15009
- import path16 from "path";
15660
+ import fs20 from "fs/promises";
15661
+ import path17 from "path";
15010
15662
 
15011
15663
  // src/app/exec/jsonl.ts
15012
15664
  function createExecJsonlEvent(type, data, ts = Date.now()) {
@@ -15034,6 +15686,10 @@ function createExecOutputWriter(options) {
15034
15686
  if (!options.verbose) return;
15035
15687
  writeLine(options.stderr, `[athena exec] ${message}`);
15036
15688
  },
15689
+ notice(message) {
15690
+ if (options.json) return;
15691
+ writeLine(options.stderr, `[athena exec] ${message}`);
15692
+ },
15037
15693
  warn(message) {
15038
15694
  writeLine(options.stderr, `[athena exec] warning: ${message}`);
15039
15695
  },
@@ -15045,9 +15701,9 @@ function createExecOutputWriter(options) {
15045
15701
  writeLine(options.stdout, message);
15046
15702
  },
15047
15703
  async writeLastMessage(filePath, message) {
15048
- const absPath = path16.resolve(filePath);
15049
- await fs19.mkdir(path16.dirname(absPath), { recursive: true });
15050
- await fs19.writeFile(absPath, message, "utf-8");
15704
+ const absPath = path17.resolve(filePath);
15705
+ await fs20.mkdir(path17.dirname(absPath), { recursive: true });
15706
+ await fs20.writeFile(absPath, message, "utf-8");
15051
15707
  }
15052
15708
  };
15053
15709
  }
@@ -15062,12 +15718,57 @@ var NULL_TOKENS3 = {
15062
15718
  contextSize: null,
15063
15719
  contextWindowSize: null
15064
15720
  };
15065
- function workflowFailure(state, message) {
15066
- return {
15067
- kind: "workflow",
15068
- state,
15069
- message
15070
- };
15721
+ function formatPersonalCapabilityNotice(summary) {
15722
+ const parts = [];
15723
+ if (summary.mcpServers.length > 0) {
15724
+ const list = summary.mcpServers.map((server) => `${server.name} [${server.sourceLayer}]`).join(", ");
15725
+ parts.push(`mcp servers: ${list}`);
15726
+ }
15727
+ if (summary.skills.length > 0) {
15728
+ const list = summary.skills.map((skill) => `${skill.name} [${skill.sourceLayer}]`).join(", ");
15729
+ parts.push(`skills: ${list}`);
15730
+ }
15731
+ if (parts.length === 0) return null;
15732
+ return `personal capabilities active \u2014 ${parts.join("; ")}`;
15733
+ }
15734
+ function formatCapabilityConflictNotice(summary) {
15735
+ const parts = [];
15736
+ if (summary.mcpServers.length > 0) {
15737
+ const list = summary.mcpServers.map((server) => `${server.name} [${server.sourceLayer}]`).join(", ");
15738
+ parts.push(`mcp servers: ${list}`);
15739
+ }
15740
+ if (summary.skills.length > 0) {
15741
+ const list = summary.skills.map((skill) => `${skill.name} [${skill.sourceLayer}]`).join(", ");
15742
+ parts.push(`skills: ${list}`);
15743
+ }
15744
+ if (parts.length === 0) return null;
15745
+ return `personal capability conflicts \u2014 workflow plugin wins; shadowed: ${parts.join(
15746
+ "; "
15747
+ )}`;
15748
+ }
15749
+ function describeAttentionRequest(event) {
15750
+ const data = event.data;
15751
+ const toolInput = data["tool_input"];
15752
+ const questions = typeof toolInput === "object" && toolInput !== null ? toolInput["questions"] : void 0;
15753
+ if (Array.isArray(questions)) {
15754
+ const texts = questions.map(
15755
+ (q) => typeof q === "object" && q !== null ? q["question"] : void 0
15756
+ ).filter((q) => typeof q === "string");
15757
+ if (texts.length > 0) {
15758
+ return `agent asked a question with no human attached to answer: ${texts.join(" | ")}`;
15759
+ }
15760
+ }
15761
+ if (event.kind === "permission.request" && event.toolName !== "user_input") {
15762
+ return `agent requested sandbox approval (${event.toolName ?? "unknown tool"}) with no human attached to answer \u2014 rerun with a more permissive --isolation, or wake with guidance`;
15763
+ }
15764
+ return "agent asked a question with no human attached to answer";
15765
+ }
15766
+ function isQuestionEvent(event) {
15767
+ return event.kind === "tool.pre" && event.toolName === "AskUserQuestion" || // ANY permission request, not just Codex's 'user_input': a sandbox
15768
+ // approval (e.g. item/fileChange/requestApproval under a read-only
15769
+ // sandbox) is equally unanswerable unattended — observed live hanging a
15770
+ // headless codex workflow run forever on the null-timeout decision.
15771
+ event.kind === "permission.request" || event.kind === "elicitation.request";
15071
15772
  }
15072
15773
  function buildEarlyFailureResult(input) {
15073
15774
  return {
@@ -15121,12 +15822,13 @@ async function runExec(options) {
15121
15822
  let adapterSessionId = null;
15122
15823
  let activeRunId = null;
15123
15824
  let beforeTerminalCompletionRan = false;
15825
+ let attentionRequest = null;
15124
15826
  let store;
15125
15827
  try {
15126
15828
  store = sessionStoreFactory({
15127
15829
  sessionId: athenaSessionId,
15128
15830
  projectDir: options.projectDir,
15129
- dbPath: options.ephemeral ? ":memory:" : path17.join(sessionsDir(), athenaSessionId, "session.db")
15831
+ dbPath: options.ephemeral ? ":memory:" : path18.join(sessionsDir(), athenaSessionId, "session.db")
15130
15832
  });
15131
15833
  } catch (error) {
15132
15834
  const message = `Failed to initialize session store: ${error instanceof Error ? error.message : String(error)}`;
@@ -15202,6 +15904,26 @@ async function runExec(options) {
15202
15904
  defaultAgentId: "main",
15203
15905
  ...options.signal ? { signal: options.signal } : {}
15204
15906
  }) : null;
15907
+ let handoverRequest = null;
15908
+ let handoverForkInProgress = false;
15909
+ const handoverDegradedSessions = /* @__PURE__ */ new Set();
15910
+ const interceptCompaction = (event) => {
15911
+ const handle = event.sessionId;
15912
+ if (!handle) return null;
15913
+ if (handoverDegradedSessions.has(handle)) return null;
15914
+ if (handoverForkInProgress) {
15915
+ return "Handover fork in progress \u2014 compaction stays blocked while the Handoff file is written.";
15916
+ }
15917
+ if (handoverRequest === null) {
15918
+ handoverRequest = { handle };
15919
+ output.notice(
15920
+ `handover: context bound reached \u2014 forking session ${handle} to write a Handoff file`
15921
+ );
15922
+ output.emitJsonEvent("run.handover", { adapterSessionId: handle });
15923
+ void sessionController.kill();
15924
+ }
15925
+ return "Handover in progress \u2014 Athena forks the conversation instead of compacting.";
15926
+ };
15205
15927
  const controllerCallbacks = {
15206
15928
  getRules: () => rules,
15207
15929
  // No UI queue in exec; with no bridge attached, the runtime never
@@ -15214,6 +15936,10 @@ async function runExec(options) {
15214
15936
  relayPermission: createRelayPermissionCallback(bridge, runtime),
15215
15937
  relayQuestion: createRelayQuestionCallback(bridge, runtime)
15216
15938
  } : {},
15939
+ // Handover interception is Claude-only for now: the fork transition
15940
+ // rides --fork-session, which Codex has no equivalent for. Non-workflow
15941
+ // sessions never intercept — vendor compaction proceeds unchanged.
15942
+ ...options.harness === "claude-code" && options.workflow?.loop?.enabled ? { interceptCompaction } : {},
15217
15943
  ...options.signal ? { signal: options.signal } : {}
15218
15944
  };
15219
15945
  const dashboardDecisionInbox = options.dashboardDecisionInbox;
@@ -15287,6 +16013,10 @@ async function runExec(options) {
15287
16013
  },
15288
16014
  onEventReceived: (runtimeEvent) => {
15289
16015
  adapterSessionId = runtimeEvent.sessionId;
16016
+ if (options.workflow?.loop?.enabled && !bridge && attentionRequest === null && isQuestionEvent(runtimeEvent)) {
16017
+ attentionRequest = describeAttentionRequest(runtimeEvent);
16018
+ void sessionController.kill();
16019
+ }
15290
16020
  if (runtimeEvent.sessionId && activeRunId && !linkedAdapterSessions.has(runtimeEvent.sessionId)) {
15291
16021
  linkedAdapterSessions.add(runtimeEvent.sessionId);
15292
16022
  safePersist(
@@ -15336,11 +16066,29 @@ async function runExec(options) {
15336
16066
  });
15337
16067
  }, options.timeoutMs);
15338
16068
  }
16069
+ const personalCapabilities = options.personalCapabilities ?? {
16070
+ mcpServers: [],
16071
+ skills: []
16072
+ };
16073
+ const capabilityConflicts = options.capabilityConflicts ?? {
16074
+ mcpServers: [],
16075
+ skills: []
16076
+ };
15339
16077
  output.emitJsonEvent("exec.started", {
15340
16078
  projectDir: options.projectDir,
15341
16079
  harness: options.harness,
15342
- athenaSessionId: options.ephemeral ? null : athenaSessionId
16080
+ athenaSessionId: options.ephemeral ? null : athenaSessionId,
16081
+ personalCapabilities,
16082
+ capabilityConflicts
15343
16083
  });
16084
+ const personalCapabilityNotice = formatPersonalCapabilityNotice(personalCapabilities);
16085
+ if (personalCapabilityNotice) {
16086
+ output.notice(personalCapabilityNotice);
16087
+ }
16088
+ const capabilityConflictNotice = formatCapabilityConflictNotice(capabilityConflicts);
16089
+ if (capabilityConflictNotice) {
16090
+ output.notice(capabilityConflictNotice);
16091
+ }
15344
16092
  try {
15345
16093
  await runtime.start();
15346
16094
  runtimeStarted = true;
@@ -15365,6 +16113,15 @@ async function runExec(options) {
15365
16113
  loopEnabled: workflow?.loop?.enabled ?? false
15366
16114
  });
15367
16115
  const nextContinuation = options.adapterResumeSessionId ? { mode: "resume", handle: options.adapterResumeSessionId } : { mode: "fresh" };
16116
+ let resumedRunMemory;
16117
+ let resumedStopReason;
16118
+ if (options.resumeRunId) {
16119
+ const resumedRun = store.getLatestRun();
16120
+ if (resumedRun?.id === options.resumeRunId) {
16121
+ resumedRunMemory = deserializeRunMemory(resumedRun.runMemoryJson) ?? void 0;
16122
+ resumedStopReason = resumedRun.stopReason;
16123
+ }
16124
+ }
15368
16125
  const handle = createWorkflowRunner({
15369
16126
  sessionId: athenaSessionId,
15370
16127
  projectDir: options.projectDir,
@@ -15372,6 +16129,9 @@ async function runExec(options) {
15372
16129
  workflow,
15373
16130
  prompt: options.prompt,
15374
16131
  initialContinuation: nextContinuation,
16132
+ resumeRunId: options.resumeRunId,
16133
+ resumedRunMemory,
16134
+ resumedStopReason,
15375
16135
  startTurn: async (turnInput) => {
15376
16136
  const turnResult = await sessionController.startTurn({
15377
16137
  prompt: turnInput.prompt,
@@ -15401,32 +16161,88 @@ async function runExec(options) {
15401
16161
  "persistRun failed"
15402
16162
  );
15403
16163
  },
16164
+ checkSuspension: () => attentionRequest !== null ? { reason: attentionRequest } : null,
16165
+ currentAdapterSessionId,
16166
+ handover: {
16167
+ takeRequest: () => {
16168
+ const request = handoverRequest;
16169
+ handoverRequest = null;
16170
+ return request;
16171
+ },
16172
+ onForkStateChange: (forking) => {
16173
+ handoverForkInProgress = forking;
16174
+ },
16175
+ onDegraded: (handle2) => {
16176
+ handoverDegradedSessions.add(handle2);
16177
+ output.warn(
16178
+ `handover failed for session ${handle2} \u2014 falling back to normal vendor compaction`
16179
+ );
16180
+ output.emitJsonEvent("run.handover.degraded", {
16181
+ adapterSessionId: handle2
16182
+ });
16183
+ }
16184
+ },
15404
16185
  abortCurrentTurn: () => void sessionController.kill(),
15405
16186
  onIterationComplete: (runSnapshot) => {
15406
16187
  output.emitJsonEvent("iteration.complete", {
15407
16188
  iteration: runSnapshot.iteration,
15408
16189
  status: runSnapshot.status
15409
16190
  });
16191
+ },
16192
+ // Task-tool projection (ADR 0015 §7): the Tracker's `## Units` table +
16193
+ // unit-record frontmatter, diffed against what the Feed already knows
16194
+ // and reconciled through the same `task.created`/`task.completed`
16195
+ // path a live TodoWrite/TaskCreate/TaskUpdate call would take.
16196
+ projectTasks: (tasks) => {
16197
+ const known = new Map(
16198
+ mapper.getTasks().filter((task) => task.taskId).map((task) => [task.taskId, task])
16199
+ );
16200
+ const newEvents = [];
16201
+ for (const task of tasks) {
16202
+ const existing = known.get(task.taskId);
16203
+ if (!existing) {
16204
+ newEvents.push(
16205
+ ...mapper.mapEvent(
16206
+ buildSyntheticTaskEvent("task.created", athenaSessionId, {
16207
+ task_id: task.taskId,
16208
+ task_subject: task.content
16209
+ })
16210
+ )
16211
+ );
16212
+ }
16213
+ if (task.status === "completed" && existing?.status !== "completed") {
16214
+ newEvents.push(
16215
+ ...mapper.mapEvent(
16216
+ buildSyntheticTaskEvent("task.completed", athenaSessionId, {
16217
+ task_id: task.taskId,
16218
+ task_subject: task.content
16219
+ })
16220
+ )
16221
+ );
16222
+ }
16223
+ }
16224
+ if (newEvents.length > 0) {
16225
+ safePersist(
16226
+ store,
16227
+ () => store.recordFeedEvents(newEvents),
16228
+ (message) => output.warn(message),
16229
+ "recordFeedEvents failed"
16230
+ );
16231
+ }
15410
16232
  }
15411
16233
  });
15412
16234
  activeRunId = handle.runId;
15413
16235
  const runResult = await handle.result;
15414
16236
  cumulativeTokens = runResult.tokens;
15415
16237
  if (!latch.hasFailure()) {
15416
- if (runResult.status === "blocked") {
15417
- latch.register(
15418
- workflowFailure(
15419
- "blocked",
15420
- runResult.stopReason ? `Workflow blocked: ${runResult.stopReason}` : "Workflow blocked."
15421
- )
15422
- );
15423
- } else if (runResult.status === "exhausted") {
15424
- latch.register(
15425
- workflowFailure(
15426
- "exhausted",
15427
- `Workflow reached the maximum of ${workflow?.loop?.maxIterations ?? 0} iterations.`
15428
- )
15429
- );
16238
+ if (runResult.status === "awaiting_attention") {
16239
+ const reason = runResult.stopReason ?? "awaiting attention";
16240
+ output.notice(`workflow run suspended \u2014 ${reason}`);
16241
+ output.emitJsonEvent("run.suspended", {
16242
+ runId: runResult.runId,
16243
+ status: "awaiting_attention",
16244
+ stopReason: runResult.stopReason ?? null
16245
+ });
15430
16246
  } else if (runResult.status === "failed") {
15431
16247
  latch.register({
15432
16248
  kind: "process",
@@ -15497,7 +16313,7 @@ async function runExec(options) {
15497
16313
  }
15498
16314
 
15499
16315
  // src/app/dashboard/instanceSocketClient.ts
15500
- import { WebSocket as WebSocket2 } from "ws";
16316
+ import { WebSocket } from "ws";
15501
16317
  var DEFAULT_HEARTBEAT_MS = 3e4;
15502
16318
  var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
15503
16319
  function instanceSocketUrl(dashboardUrl, instanceId) {
@@ -15514,7 +16330,7 @@ function createInstanceSocketClient(opts) {
15514
16330
  const log = opts.log ?? (() => {
15515
16331
  });
15516
16332
  const now = opts.now ?? (() => Date.now());
15517
- const makeWebSocket = opts.makeWebSocket ?? ((url, accessToken) => new WebSocket2(url, [accessToken]));
16333
+ const makeWebSocket = opts.makeWebSocket ?? ((url, accessToken) => new WebSocket(url, [accessToken]));
15518
16334
  const frameHandlers = /* @__PURE__ */ new Set();
15519
16335
  const closeHandlers = /* @__PURE__ */ new Set();
15520
16336
  let ws = null;
@@ -15714,7 +16530,7 @@ function normalizeHarnessOverride(value) {
15714
16530
  }
15715
16531
 
15716
16532
  // src/app/dashboard/runStreamClient.ts
15717
- import { WebSocket as WebSocket3 } from "ws";
16533
+ import { WebSocket as WebSocket2 } from "ws";
15718
16534
  var DEFAULT_RECONNECT_DELAYS_MS = [250, 1e3, 2e3, 5e3, 15e3];
15719
16535
  var DEFAULT_HEARTBEAT_MS2 = 3e4;
15720
16536
  var DEFAULT_WATCHDOG_MS = 9e4;
@@ -15727,7 +16543,7 @@ function createRunStreamClient(opts) {
15727
16543
  const heartbeatMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS2;
15728
16544
  const watchdogMs = opts.watchdogTimeoutMs ?? DEFAULT_WATCHDOG_MS;
15729
16545
  const maxQueueSize = opts.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
15730
- const makeWebSocket = opts.makeWebSocket ?? ((url) => new WebSocket3(url));
16546
+ const makeWebSocket = opts.makeWebSocket ?? ((url) => new WebSocket2(url));
15731
16547
  const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
15732
16548
  const clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
15733
16549
  let nextSeq = 1;
@@ -16067,8 +16883,8 @@ async function createRemoteRunEventPublisher({
16067
16883
  // src/app/dashboard/artifactCapture.ts
16068
16884
  import { execFile } from "child_process";
16069
16885
  import crypto3 from "crypto";
16070
- import fs20 from "fs/promises";
16071
- import path18 from "path";
16886
+ import fs21 from "fs/promises";
16887
+ import path19 from "path";
16072
16888
  import { promisify } from "util";
16073
16889
  var execFileAsync = promisify(execFile);
16074
16890
  function parseArtifactUploadSpec(value) {
@@ -16251,21 +17067,21 @@ async function collectArtifactPayloads(input) {
16251
17067
  return payloads;
16252
17068
  }
16253
17069
  async function readWorkspaceFile(projectDir, rel) {
16254
- const absolute = path18.resolve(projectDir, rel);
16255
- const workspaceRoot = await fs20.realpath(projectDir);
17070
+ const absolute = path19.resolve(projectDir, rel);
17071
+ const workspaceRoot = await fs21.realpath(projectDir);
16256
17072
  let stat;
16257
17073
  try {
16258
- stat = await fs20.lstat(absolute);
17074
+ stat = await fs21.lstat(absolute);
16259
17075
  } catch {
16260
17076
  return null;
16261
17077
  }
16262
17078
  if (!stat.isFile() || stat.isSymbolicLink()) return null;
16263
- const real = await fs20.realpath(absolute);
16264
- const relativeToRoot = path18.relative(workspaceRoot, real);
16265
- if (relativeToRoot === ".." || relativeToRoot.startsWith(`..${path18.sep}`) || path18.isAbsolute(relativeToRoot)) {
17079
+ const real = await fs21.realpath(absolute);
17080
+ const relativeToRoot = path19.relative(workspaceRoot, real);
17081
+ if (relativeToRoot === ".." || relativeToRoot.startsWith(`..${path19.sep}`) || path19.isAbsolute(relativeToRoot)) {
16266
17082
  return null;
16267
17083
  }
16268
- return fs20.readFile(absolute);
17084
+ return fs21.readFile(absolute);
16269
17085
  }
16270
17086
  async function gitDiffForAllowedPaths(input) {
16271
17087
  const paths = splitNul(await git(input.projectDir, input.nameArgs)).filter(
@@ -16356,13 +17172,13 @@ var DEFAULT_HARD_DENY = [
16356
17172
  ".ssh/**"
16357
17173
  ];
16358
17174
  function isAllowedRelativePath(rel, hardDeny) {
16359
- if (path18.isAbsolute(rel) || rel.includes("\0")) return false;
16360
- const normalized = path18.posix.normalize(rel.replaceAll(path18.sep, "/"));
17175
+ if (path19.isAbsolute(rel) || rel.includes("\0")) return false;
17176
+ const normalized = path19.posix.normalize(rel.replaceAll(path19.sep, "/"));
16361
17177
  if (normalized === ".." || normalized.startsWith("../")) return false;
16362
17178
  return !hardDeny.some((pattern) => matchesDenyPattern(normalized, pattern));
16363
17179
  }
16364
17180
  function matchesDenyPattern(rel, pattern) {
16365
- const normalized = pattern.replaceAll(path18.sep, "/");
17181
+ const normalized = pattern.replaceAll(path19.sep, "/");
16366
17182
  if (normalized.includes("*") || normalized.includes("?")) {
16367
17183
  return globToRegExp(normalized).test(rel);
16368
17184
  }
@@ -16759,18 +17575,18 @@ async function executeRemoteAssignment({
16759
17575
 
16760
17576
  // src/infra/config/attachmentMirror.ts
16761
17577
  import crypto4 from "crypto";
16762
- import fs21 from "fs";
16763
- import os13 from "os";
16764
- import path19 from "path";
17578
+ import fs22 from "fs";
17579
+ import os14 from "os";
17580
+ import path20 from "path";
16765
17581
  function attachmentMirrorPath(env = process.env) {
16766
- const home = env["HOME"] ?? os13.homedir();
16767
- return path19.join(home, ".config", "athena", "attachments.json");
17582
+ const home = env["HOME"] ?? os14.homedir();
17583
+ return path20.join(home, ".config", "athena", "attachments.json");
16768
17584
  }
16769
17585
  function readAttachmentMirror(env = process.env) {
16770
17586
  const file = attachmentMirrorPath(env);
16771
17587
  let raw;
16772
17588
  try {
16773
- raw = fs21.readFileSync(file, "utf-8");
17589
+ raw = fs22.readFileSync(file, "utf-8");
16774
17590
  } catch (err) {
16775
17591
  if (err.code === "ENOENT") return null;
16776
17592
  throw err;
@@ -16794,29 +17610,29 @@ function readAttachmentMirror(env = process.env) {
16794
17610
  function writeAttachmentMirror(mirror, env = process.env) {
16795
17611
  const validated = parseAttachmentMirror(mirror);
16796
17612
  const file = attachmentMirrorPath(env);
16797
- const dir = path19.dirname(file);
16798
- fs21.mkdirSync(dir, { recursive: true, mode: 448 });
17613
+ const dir = path20.dirname(file);
17614
+ fs22.mkdirSync(dir, { recursive: true, mode: 448 });
16799
17615
  const tmp = `${file}.${process.pid}.${crypto4.randomBytes(4).toString("hex")}.tmp`;
16800
- const fd = fs21.openSync(tmp, "w", 384);
17616
+ const fd = fs22.openSync(tmp, "w", 384);
16801
17617
  try {
16802
- fs21.writeSync(fd, JSON.stringify(validated, null, 2) + "\n");
16803
- fs21.fsyncSync(fd);
17618
+ fs22.writeSync(fd, JSON.stringify(validated, null, 2) + "\n");
17619
+ fs22.fsyncSync(fd);
16804
17620
  } finally {
16805
- fs21.closeSync(fd);
17621
+ fs22.closeSync(fd);
16806
17622
  }
16807
17623
  try {
16808
- fs21.renameSync(tmp, file);
17624
+ fs22.renameSync(tmp, file);
16809
17625
  } catch (err) {
16810
17626
  try {
16811
- fs21.unlinkSync(tmp);
17627
+ fs22.unlinkSync(tmp);
16812
17628
  } catch {
16813
17629
  }
16814
17630
  throw err;
16815
17631
  }
16816
17632
  if (process.platform !== "win32") {
16817
17633
  try {
16818
- fs21.chmodSync(dir, 448);
16819
- fs21.chmodSync(file, 384);
17634
+ fs22.chmodSync(dir, 448);
17635
+ fs22.chmodSync(file, 384);
16820
17636
  } catch {
16821
17637
  }
16822
17638
  }
@@ -16824,7 +17640,7 @@ function writeAttachmentMirror(mirror, env = process.env) {
16824
17640
  function removeAttachmentMirror(env = process.env) {
16825
17641
  const file = attachmentMirrorPath(env);
16826
17642
  try {
16827
- fs21.unlinkSync(file);
17643
+ fs22.unlinkSync(file);
16828
17644
  } catch (err) {
16829
17645
  if (err.code !== "ENOENT") throw err;
16830
17646
  }
@@ -17228,9 +18044,9 @@ function createDashboardPairedExecution(options) {
17228
18044
  }
17229
18045
 
17230
18046
  // src/app/dashboard/remoteWorkspaceResolver.ts
17231
- import fs22 from "fs";
17232
- import os14 from "os";
17233
- import path20 from "path";
18047
+ import fs23 from "fs";
18048
+ import os15 from "os";
18049
+ import path21 from "path";
17234
18050
  function resolveRemoteWorkspace(assignment, options = {}) {
17235
18051
  const { spec, runId, runnerId } = assignment;
17236
18052
  if (spec.projectDir) {
@@ -17239,14 +18055,14 @@ function resolveRemoteWorkspace(assignment, options = {}) {
17239
18055
  const sessionId = spec.athenaSessionId ?? spec.sessionId;
17240
18056
  const deploymentSlug = deploymentSlugFromUrl(options.dashboardUrl);
17241
18057
  const stateDir = daemonStatePaths(options.env).dir;
17242
- const projectDir = sessionId ? path20.join(
18058
+ const projectDir = sessionId ? path21.join(
17243
18059
  stateDir,
17244
18060
  "remote-workspaces",
17245
18061
  deploymentSlug,
17246
18062
  sanitizePathSegment(runnerId),
17247
18063
  "sessions",
17248
18064
  sanitizePathSegment(sessionId)
17249
- ) : path20.join(
18065
+ ) : path21.join(
17250
18066
  stateDir,
17251
18067
  "remote-workspaces",
17252
18068
  deploymentSlug,
@@ -17255,10 +18071,10 @@ function resolveRemoteWorkspace(assignment, options = {}) {
17255
18071
  sanitizePathSegment(runId)
17256
18072
  );
17257
18073
  try {
17258
- fs22.mkdirSync(projectDir, { recursive: true, mode: 448 });
18074
+ fs23.mkdirSync(projectDir, { recursive: true, mode: 448 });
17259
18075
  if (process.platform !== "win32") {
17260
18076
  try {
17261
- fs22.chmodSync(projectDir, 448);
18077
+ fs23.chmodSync(projectDir, 448);
17262
18078
  } catch {
17263
18079
  }
17264
18080
  }
@@ -17274,8 +18090,8 @@ function resolveRemoteWorkspace(assignment, options = {}) {
17274
18090
  return validateProjectDir(projectDir, options.env);
17275
18091
  }
17276
18092
  function validateProjectDir(projectDir, env = process.env) {
17277
- const resolved = path20.resolve(projectDir);
17278
- if (!path20.isAbsolute(projectDir)) {
18093
+ const resolved = path21.resolve(projectDir);
18094
+ if (!path21.isAbsolute(projectDir)) {
17279
18095
  return {
17280
18096
  kind: "rejected",
17281
18097
  rejection: {
@@ -17284,7 +18100,7 @@ function validateProjectDir(projectDir, env = process.env) {
17284
18100
  }
17285
18101
  };
17286
18102
  }
17287
- const home = path20.resolve(env["HOME"] ?? os14.homedir());
18103
+ const home = path21.resolve(env["HOME"] ?? os15.homedir());
17288
18104
  if (resolved === home) {
17289
18105
  return {
17290
18106
  kind: "rejected",
@@ -17296,7 +18112,7 @@ function validateProjectDir(projectDir, env = process.env) {
17296
18112
  }
17297
18113
  let stat;
17298
18114
  try {
17299
- stat = fs22.statSync(resolved);
18115
+ stat = fs23.statSync(resolved);
17300
18116
  } catch {
17301
18117
  return {
17302
18118
  kind: "rejected",
@@ -17741,18 +18557,18 @@ async function runDashboardRuntimeDaemon(options = {}) {
17741
18557
  }
17742
18558
 
17743
18559
  // src/infra/daemon/pidLock.ts
17744
- import fs23 from "fs";
18560
+ import fs24 from "fs";
17745
18561
  function acquirePidLock(pidPath) {
17746
18562
  const ownPid = process.pid;
17747
18563
  for (let attempt = 0; attempt < 2; attempt += 1) {
17748
18564
  try {
17749
- const fd = fs23.openSync(pidPath, "wx", 384);
18565
+ const fd = fs24.openSync(pidPath, "wx", 384);
17750
18566
  try {
17751
- fs23.writeSync(fd, `${ownPid}
18567
+ fs24.writeSync(fd, `${ownPid}
17752
18568
  `);
17753
- fs23.fsyncSync(fd);
18569
+ fs24.fsyncSync(fd);
17754
18570
  } finally {
17755
- fs23.closeSync(fd);
18571
+ fs24.closeSync(fd);
17756
18572
  }
17757
18573
  return makeHandle(pidPath, ownPid);
17758
18574
  } catch (err) {
@@ -17766,7 +18582,7 @@ function acquirePidLock(pidPath) {
17766
18582
  }
17767
18583
  if (existing.state === "stale") {
17768
18584
  try {
17769
- fs23.unlinkSync(pidPath);
18585
+ fs24.unlinkSync(pidPath);
17770
18586
  } catch (err) {
17771
18587
  if (err.code !== "ENOENT") throw err;
17772
18588
  }
@@ -17780,7 +18596,7 @@ function acquirePidLock(pidPath) {
17780
18596
  function readPidLock(pidPath) {
17781
18597
  let raw;
17782
18598
  try {
17783
- raw = fs23.readFileSync(pidPath, "utf-8");
18599
+ raw = fs24.readFileSync(pidPath, "utf-8");
17784
18600
  } catch (err) {
17785
18601
  if (err.code === "ENOENT") {
17786
18602
  return { state: "absent" };
@@ -17804,9 +18620,9 @@ function makeHandle(pidPath, pid) {
17804
18620
  if (released) return;
17805
18621
  released = true;
17806
18622
  try {
17807
- const raw = fs23.readFileSync(pidPath, "utf-8").trim();
18623
+ const raw = fs24.readFileSync(pidPath, "utf-8").trim();
17808
18624
  if (raw === String(pid)) {
17809
- fs23.unlinkSync(pidPath);
18625
+ fs24.unlinkSync(pidPath);
17810
18626
  }
17811
18627
  } catch (err) {
17812
18628
  if (err.code !== "ENOENT") {
@@ -17832,7 +18648,7 @@ function isProcessAlive(pid) {
17832
18648
  }
17833
18649
 
17834
18650
  // src/infra/daemon/udsIpc.ts
17835
- import fs24 from "fs";
18651
+ import fs25 from "fs";
17836
18652
  import net2 from "net";
17837
18653
 
17838
18654
  // src/infra/daemon/udsFrameCodec.ts
@@ -17877,7 +18693,7 @@ async function startUdsServer(socketPath, handler, log) {
17877
18693
  });
17878
18694
  if (process.platform !== "win32") {
17879
18695
  try {
17880
- fs24.chmodSync(socketPath, 384);
18696
+ fs25.chmodSync(socketPath, 384);
17881
18697
  } catch {
17882
18698
  }
17883
18699
  }
@@ -17887,7 +18703,7 @@ async function startUdsServer(socketPath, handler, log) {
17887
18703
  server.close(() => resolve());
17888
18704
  });
17889
18705
  try {
17890
- fs24.unlinkSync(socketPath);
18706
+ fs25.unlinkSync(socketPath);
17891
18707
  } catch (err) {
17892
18708
  if (err.code !== "ENOENT") {
17893
18709
  }
@@ -18005,7 +18821,7 @@ async function sendUdsRequest(socketPath, request, options = {}) {
18005
18821
  async function unlinkStaleSocket(socketPath) {
18006
18822
  let stat;
18007
18823
  try {
18008
- stat = fs24.statSync(socketPath);
18824
+ stat = fs25.statSync(socketPath);
18009
18825
  } catch (err) {
18010
18826
  if (err.code === "ENOENT") return;
18011
18827
  throw err;
@@ -18032,7 +18848,7 @@ async function unlinkStaleSocket(socketPath) {
18032
18848
  `uds path ${socketPath} is in use by another process; aborting`
18033
18849
  );
18034
18850
  }
18035
- fs24.unlinkSync(socketPath);
18851
+ fs25.unlinkSync(socketPath);
18036
18852
  }
18037
18853
 
18038
18854
  export {
@@ -18062,6 +18878,8 @@ export {
18062
18878
  listSessions,
18063
18879
  getSessionMeta,
18064
18880
  getMostRecentAthenaSession,
18881
+ getLatestRunForSession,
18882
+ listAwaitingAttentionRuns,
18065
18883
  resolveClaudeBinary,
18066
18884
  detectClaudeVersion,
18067
18885
  resolveHookForwarderCommand,
@@ -18092,8 +18910,6 @@ export {
18092
18910
  GatewayUnreachableError,
18093
18911
  GatewayUnauthorizedError,
18094
18912
  connect,
18095
- createWsClientTransport,
18096
- wsClientOptionsForEndpoint,
18097
18913
  isSupportedGatewayUrl,
18098
18914
  readGatewayClientConfig,
18099
18915
  writeGatewayClientConfig,
@@ -18121,6 +18937,8 @@ export {
18121
18937
  isBashToolResponse,
18122
18938
  resolveToolColumn,
18123
18939
  IndexedTimeline,
18940
+ parseFrontmatter,
18941
+ resolveEffectiveCapabilities,
18124
18942
  bootstrapRuntimeConfig,
18125
18943
  EXEC_EXIT_CODE,
18126
18944
  runExec,
@@ -18133,4 +18951,4 @@ export {
18133
18951
  startUdsServer,
18134
18952
  sendUdsRequest
18135
18953
  };
18136
- //# sourceMappingURL=chunk-QBMYQJFX.js.map
18954
+ //# sourceMappingURL=chunk-4HWBXCVY.js.map