@drisp/cli 0.4.2 → 0.4.5

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.
@@ -32,7 +32,7 @@ import {
32
32
  resolveActiveWorkflow,
33
33
  resolveWorkflow,
34
34
  resolveWorkflowPlugins
35
- } from "./chunk-GE6PPB6Z.js";
35
+ } from "./chunk-5VK2ZMVV.js";
36
36
 
37
37
  // src/shared/utils/processRegistry.ts
38
38
  var ProcessRegistry = class {
@@ -3079,8 +3079,8 @@ function spawnClaude(options) {
3079
3079
  cwd: projectDir,
3080
3080
  stdio: ["ignore", "pipe", "pipe"],
3081
3081
  env: {
3082
- // Trigger auto-compact at ~95% of 130k (~123.5k tokens used).
3083
- CLAUDE_CODE_AUTO_COMPACT_WINDOW: "130000",
3082
+ // Claude compacts around 95% of this window; 185k keeps the trigger above 175k tokens used.
3083
+ CLAUDE_CODE_AUTO_COMPACT_WINDOW: "185000",
3084
3084
  ...process.env,
3085
3085
  ...extraEnv ?? {},
3086
3086
  ATHENA_INSTANCE_ID: String(instanceId),
@@ -6555,7 +6555,7 @@ function buildCodexPromptOptions(input) {
6555
6555
  agentRoots: input.workflowPlan?.agentRoots && input.workflowPlan.agentRoots.length > 0 ? input.workflowPlan.agentRoots : void 0,
6556
6556
  plugins: resolveCodexWorkflowPlugins(input.workflowPlan),
6557
6557
  config: {
6558
- model_auto_compact_token_limit: 13e4,
6558
+ model_auto_compact_token_limit: 175e3,
6559
6559
  ...resolveCodexMcpConfig(input.pluginMcpConfig, input.workflowPlan) ?? {}
6560
6560
  },
6561
6561
  ephemeral: input.ephemeral,
@@ -7297,130 +7297,6 @@ var EXEC_EXIT_CODE = {
7297
7297
  import crypto2 from "crypto";
7298
7298
  import path17 from "path";
7299
7299
 
7300
- // src/core/controller/rules.ts
7301
- function ruleMatches(ruleToolName, toolName) {
7302
- if (ruleToolName === "*") return true;
7303
- if (ruleToolName === toolName) return true;
7304
- if (ruleToolName.endsWith("__*")) {
7305
- const prefix = ruleToolName.slice(0, -1);
7306
- return toolName.startsWith(prefix);
7307
- }
7308
- return false;
7309
- }
7310
- function matchRule(rules, toolName) {
7311
- const denyMatch = rules.find(
7312
- (r) => r.action === "deny" && ruleMatches(r.toolName, toolName)
7313
- );
7314
- if (denyMatch) return denyMatch;
7315
- return rules.find(
7316
- (r) => r.action === "approve" && ruleMatches(r.toolName, toolName)
7317
- );
7318
- }
7319
-
7320
- // src/core/controller/permission.ts
7321
- var SESSION_APPROVAL_REQUEST_HOOKS = /* @__PURE__ */ new Set([
7322
- "item/commandExecution/requestApproval",
7323
- "item/fileChange/requestApproval",
7324
- "item/permissions/requestApproval"
7325
- ]);
7326
- function isScopedPermissionsRequest(hookName) {
7327
- return hookName === "item/permissions/requestApproval";
7328
- }
7329
- function supportsSessionApproval(hookName) {
7330
- return hookName !== void 0 && SESSION_APPROVAL_REQUEST_HOOKS.has(hookName);
7331
- }
7332
- function extractPermissionSnapshot(event) {
7333
- const data = event.data;
7334
- const toolNameFromData = typeof data["tool_name"] === "string" ? data["tool_name"] : void 0;
7335
- const toolInputFromData = typeof data["tool_input"] === "object" && data["tool_input"] !== null ? data["tool_input"] : void 0;
7336
- const toolUseIdFromData = typeof data["tool_use_id"] === "string" ? data["tool_use_id"] : void 0;
7337
- return {
7338
- request_id: event.id,
7339
- ts: event.timestamp,
7340
- kind: event.kind,
7341
- hookName: event.hookName,
7342
- tool_name: event.toolName ?? toolNameFromData ?? "Unknown",
7343
- tool_input: toolInputFromData ?? {},
7344
- tool_use_id: event.toolUseId ?? toolUseIdFromData,
7345
- suggestions: data.permission_suggestions
7346
- };
7347
- }
7348
-
7349
- // src/core/controller/runtimeController.ts
7350
- function handleEvent(event, cb) {
7351
- const eventKind2 = event.kind;
7352
- const isScoped = isScopedPermissionsRequest(event.hookName);
7353
- const eventData = event.data;
7354
- const toolName = event.toolName ?? (typeof eventData["tool_name"] === "string" ? eventData["tool_name"] : void 0);
7355
- if (eventKind2 === "permission.request" && toolName === "user_input") {
7356
- cb.enqueueQuestion(event.id);
7357
- cb.relayQuestion?.(event);
7358
- return { handled: true };
7359
- }
7360
- if (eventKind2 === "permission.request" && toolName) {
7361
- const rule = matchRule(cb.getRules(), toolName);
7362
- if (rule?.action === "deny") {
7363
- return {
7364
- handled: true,
7365
- decision: {
7366
- type: "json",
7367
- source: "rule",
7368
- intent: {
7369
- kind: "permission_deny",
7370
- reason: `Blocked by rule: ${rule.addedBy}`
7371
- }
7372
- }
7373
- };
7374
- }
7375
- if (rule?.action === "approve") {
7376
- return {
7377
- handled: true,
7378
- decision: {
7379
- type: "json",
7380
- source: "rule",
7381
- intent: { kind: "permission_allow" },
7382
- // Scoped Codex permission grants are session-scoped under
7383
- // auto rules so the same capability isn't re-prompted.
7384
- ...isScoped ? { data: { scope: "session" } } : {}
7385
- }
7386
- };
7387
- }
7388
- cb.enqueuePermission(event);
7389
- cb.relayPermission?.(event);
7390
- return { handled: true };
7391
- }
7392
- if (eventKind2 === "tool.pre" && toolName === "AskUserQuestion") {
7393
- cb.enqueueQuestion(event.id);
7394
- cb.relayQuestion?.(event);
7395
- return { handled: true };
7396
- }
7397
- if (eventKind2 === "tool.pre" && toolName) {
7398
- const rule = matchRule(cb.getRules(), toolName);
7399
- if (rule?.action === "deny") {
7400
- return {
7401
- handled: true,
7402
- decision: {
7403
- type: "json",
7404
- source: "rule",
7405
- intent: {
7406
- kind: "pre_tool_deny",
7407
- reason: `Blocked by rule: ${rule.addedBy}`
7408
- }
7409
- }
7410
- };
7411
- }
7412
- return {
7413
- handled: true,
7414
- decision: {
7415
- type: "json",
7416
- source: "rule",
7417
- intent: { kind: "pre_tool_allow" }
7418
- }
7419
- };
7420
- }
7421
- return { handled: false };
7422
- }
7423
-
7424
7300
  // src/core/feed/todo.ts
7425
7301
  function isSubagentTool(toolName) {
7426
7302
  return toolName === "Task" || toolName === "Agent";
@@ -7802,8 +7678,16 @@ function createTranscriptReader() {
7802
7678
  try {
7803
7679
  const buf = Buffer.alloc(fileSize - offset);
7804
7680
  const bytesRead = fs15.readSync(fd, buf, 0, buf.length, offset);
7805
- offsets.set(transcriptPath, offset + bytesRead);
7806
- const chunk = buf.toString("utf8", 0, bytesRead);
7681
+ let lastNewline = -1;
7682
+ for (let i = bytesRead - 1; i >= 0; i--) {
7683
+ if (buf[i] === 10) {
7684
+ lastNewline = i;
7685
+ break;
7686
+ }
7687
+ }
7688
+ if (lastNewline === -1) return [];
7689
+ offsets.set(transcriptPath, offset + lastNewline + 1);
7690
+ const chunk = buf.toString("utf8", 0, lastNewline + 1);
7807
7691
  const lines = chunk.split("\n").filter((l) => l.trim().length > 0);
7808
7692
  const messages = [];
7809
7693
  for (const line of lines) {
@@ -7846,55 +7730,54 @@ function createTranscriptReader() {
7846
7730
  return { readNewAssistantMessages, getOffset };
7847
7731
  }
7848
7732
 
7849
- // src/core/feed/mapper.ts
7850
- function createFeedMapper(bootstrap) {
7851
- const MAX_STREAMED_TOOL_OUTPUT_CHARS = 64e3;
7852
- const STREAMED_TOOL_OUTPUT_TRUNCATED_NOTICE = "[streaming output truncated to recent content]\n";
7733
+ // src/core/feed/internals/runLifecycle.ts
7734
+ function createRunLifecycle() {
7853
7735
  let currentSession = null;
7854
7736
  let currentRun = null;
7855
- let lastRootTasks = [];
7856
- const actors = new ActorRegistry();
7857
7737
  let seq = 0;
7858
7738
  let runSeq = 0;
7859
- const toolPreIndex = /* @__PURE__ */ new Map();
7860
- const toolDeltaTextByUseId = /* @__PURE__ */ new Map();
7861
- const truncatedToolDeltaUseIds = /* @__PURE__ */ new Set();
7862
- const eventIdByRequestId = /* @__PURE__ */ new Map();
7863
- const eventKindByRequestId = /* @__PURE__ */ new Map();
7864
- const resolvedRequestById = /* @__PURE__ */ new Map();
7865
- if (bootstrap) {
7866
- for (const e of bootstrap.feedEvents) {
7867
- if (e.seq > seq) seq = e.seq;
7868
- const m = e.run_id.match(/:R(\d+)$/);
7869
- if (m) {
7870
- const n = parseInt(m[1], 10);
7871
- if (n > runSeq) runSeq = n;
7872
- }
7873
- if (e.kind === "tool.pre" && e.actor_id === "agent:root" && e.data.tool_name === "TodoWrite") {
7874
- lastRootTasks = extractTodoItems(e.data.tool_input);
7875
- }
7876
- }
7877
- const lastAdapterId = bootstrap.adapterSessionIds.at(-1);
7878
- if (lastAdapterId) {
7879
- currentSession = {
7880
- session_id: lastAdapterId,
7881
- started_at: bootstrap.createdAt,
7882
- source: "resume"
7883
- };
7884
- }
7885
- let lastRunStart;
7886
- let lastRunEnd;
7887
- for (const e of bootstrap.feedEvents) {
7888
- if (e.kind === "run.start") lastRunStart = e;
7889
- if (e.kind === "run.end") lastRunEnd = e;
7890
- }
7891
- if (lastRunStart && (!lastRunEnd || lastRunEnd.seq < lastRunStart.seq)) {
7892
- const triggerData = lastRunStart.data;
7739
+ function getRunId() {
7740
+ const sessId = currentSession?.session_id ?? "unknown";
7741
+ return `${sessId}:R${runSeq}`;
7742
+ }
7743
+ return {
7744
+ allocateSeq() {
7745
+ return ++seq;
7746
+ },
7747
+ getRunId,
7748
+ getSession() {
7749
+ return currentSession;
7750
+ },
7751
+ getCurrentRun() {
7752
+ return currentRun;
7753
+ },
7754
+ setSession(session) {
7755
+ currentSession = session;
7756
+ },
7757
+ endSession(ts) {
7758
+ if (currentSession) currentSession.ended_at = ts;
7759
+ },
7760
+ clearSession() {
7761
+ currentSession = null;
7762
+ },
7763
+ incrementCounter(name) {
7764
+ if (currentRun) currentRun.counters[name]++;
7765
+ },
7766
+ closeRun(ts, status) {
7767
+ if (!currentRun) return null;
7768
+ currentRun.status = status;
7769
+ currentRun.ended_at = ts;
7770
+ const closed = currentRun;
7771
+ currentRun = null;
7772
+ return closed;
7773
+ },
7774
+ openNewRun(ts, sessionId, triggerType, promptPreview) {
7775
+ runSeq++;
7893
7776
  currentRun = {
7894
- run_id: lastRunStart.run_id,
7895
- session_id: lastRunStart.session_id,
7896
- started_at: lastRunStart.ts,
7897
- trigger: triggerData.trigger,
7777
+ run_id: getRunId(),
7778
+ session_id: sessionId,
7779
+ started_at: ts,
7780
+ trigger: { type: triggerType, prompt_preview: promptPreview },
7898
7781
  status: "running",
7899
7782
  actors: { root_agent_id: "agent:root", subagent_ids: [] },
7900
7783
  counters: {
@@ -7904,54 +7787,344 @@ function createFeedMapper(bootstrap) {
7904
7787
  blocks: 0
7905
7788
  }
7906
7789
  };
7790
+ return currentRun;
7791
+ },
7792
+ restoreFrom(bootstrap) {
7907
7793
  for (const e of bootstrap.feedEvents) {
7908
- if (e.run_id !== currentRun.run_id) continue;
7909
- if (e.kind === "tool.pre") currentRun.counters.tool_uses++;
7910
- if (e.kind === "tool.failure") currentRun.counters.tool_failures++;
7911
- if (e.kind === "permission.request")
7912
- currentRun.counters.permission_requests++;
7794
+ if (e.seq > seq) seq = e.seq;
7795
+ const m = e.run_id.match(/:R(\d+)$/);
7796
+ if (m) {
7797
+ const n = parseInt(m[1], 10);
7798
+ if (n > runSeq) runSeq = n;
7799
+ }
7800
+ }
7801
+ const lastAdapterId = bootstrap.adapterSessionIds.at(-1);
7802
+ if (lastAdapterId) {
7803
+ currentSession = {
7804
+ session_id: lastAdapterId,
7805
+ started_at: bootstrap.createdAt,
7806
+ source: "resume"
7807
+ };
7808
+ }
7809
+ let lastRunStart;
7810
+ let lastRunEnd;
7811
+ for (const e of bootstrap.feedEvents) {
7812
+ if (e.kind === "run.start") lastRunStart = e;
7813
+ if (e.kind === "run.end") lastRunEnd = e;
7814
+ }
7815
+ if (lastRunStart && (!lastRunEnd || lastRunEnd.seq < lastRunStart.seq)) {
7816
+ const triggerData = lastRunStart.data;
7817
+ currentRun = {
7818
+ run_id: lastRunStart.run_id,
7819
+ session_id: lastRunStart.session_id,
7820
+ started_at: lastRunStart.ts,
7821
+ trigger: triggerData.trigger,
7822
+ status: "running",
7823
+ actors: { root_agent_id: "agent:root", subagent_ids: [] },
7824
+ counters: {
7825
+ tool_uses: 0,
7826
+ tool_failures: 0,
7827
+ permission_requests: 0,
7828
+ blocks: 0
7829
+ }
7830
+ };
7831
+ for (const e of bootstrap.feedEvents) {
7832
+ if (e.run_id !== currentRun.run_id) continue;
7833
+ if (e.kind === "tool.pre") currentRun.counters.tool_uses++;
7834
+ if (e.kind === "tool.failure") currentRun.counters.tool_failures++;
7835
+ if (e.kind === "permission.request")
7836
+ currentRun.counters.permission_requests++;
7837
+ }
7913
7838
  }
7914
7839
  }
7915
- }
7916
- function extractTodoItems(toolInput) {
7917
- const input = toolInput;
7918
- return Array.isArray(input?.todos) ? input.todos : [];
7919
- }
7920
- function mapPlanStepStatus(status) {
7921
- switch (status) {
7922
- case "inProgress":
7923
- return "in_progress";
7924
- case "completed":
7925
- return "completed";
7926
- case void 0:
7927
- default:
7928
- return "pending";
7840
+ };
7841
+ }
7842
+
7843
+ // src/core/feed/internals/decisionCorrelation.ts
7844
+ function createDecisionCorrelation() {
7845
+ const eventIdByRequestId = /* @__PURE__ */ new Map();
7846
+ const eventKindByRequestId = /* @__PURE__ */ new Map();
7847
+ const resolvedRequestById = /* @__PURE__ */ new Map();
7848
+ return {
7849
+ recordRequest(requestId, eventId, kind) {
7850
+ eventIdByRequestId.set(requestId, eventId);
7851
+ eventKindByRequestId.set(requestId, kind);
7852
+ resolvedRequestById.set(requestId, { event_id: eventId, kind });
7853
+ },
7854
+ lookupResolved(requestId) {
7855
+ return resolvedRequestById.get(requestId) ?? null;
7856
+ },
7857
+ consumeForDecision(requestId) {
7858
+ const parentEventId = eventIdByRequestId.get(requestId);
7859
+ if (!parentEventId) return null;
7860
+ const originalKind = eventKindByRequestId.get(requestId);
7861
+ eventIdByRequestId.delete(requestId);
7862
+ eventKindByRequestId.delete(requestId);
7863
+ return { parentEventId, originalKind };
7864
+ },
7865
+ resetForNewRun() {
7866
+ eventIdByRequestId.clear();
7867
+ eventKindByRequestId.clear();
7868
+ resolvedRequestById.clear();
7929
7869
  }
7870
+ };
7871
+ }
7872
+
7873
+ // src/core/feed/internals/toolCorrelation.ts
7874
+ var MAX_STREAMED_TOOL_OUTPUT_CHARS = 64e3;
7875
+ var STREAMED_TOOL_OUTPUT_TRUNCATED_NOTICE = "[streaming output truncated to recent content]\n";
7876
+ function createToolCorrelation() {
7877
+ const toolPreIndex = /* @__PURE__ */ new Map();
7878
+ const toolDeltaTextByUseId = /* @__PURE__ */ new Map();
7879
+ const truncatedToolDeltaUseIds = /* @__PURE__ */ new Set();
7880
+ return {
7881
+ recordPre(toolUseId, eventId) {
7882
+ toolPreIndex.set(toolUseId, eventId);
7883
+ },
7884
+ lookupParent(toolUseId) {
7885
+ return toolUseId ? toolPreIndex.get(toolUseId) : void 0;
7886
+ },
7887
+ forgetTool(toolUseId) {
7888
+ toolDeltaTextByUseId.delete(toolUseId);
7889
+ truncatedToolDeltaUseIds.delete(toolUseId);
7890
+ },
7891
+ appendDelta(toolUseId, chunk) {
7892
+ if (!toolUseId) return chunk;
7893
+ const cumulative = `${toolDeltaTextByUseId.get(toolUseId) ?? ""}${chunk}`;
7894
+ if (cumulative.length <= MAX_STREAMED_TOOL_OUTPUT_CHARS) {
7895
+ toolDeltaTextByUseId.set(toolUseId, cumulative);
7896
+ return truncatedToolDeltaUseIds.has(toolUseId) ? `${STREAMED_TOOL_OUTPUT_TRUNCATED_NOTICE}${cumulative}` : cumulative;
7897
+ }
7898
+ const tail2 = cumulative.slice(-MAX_STREAMED_TOOL_OUTPUT_CHARS);
7899
+ toolDeltaTextByUseId.set(toolUseId, tail2);
7900
+ truncatedToolDeltaUseIds.add(toolUseId);
7901
+ return `${STREAMED_TOOL_OUTPUT_TRUNCATED_NOTICE}${tail2}`;
7902
+ },
7903
+ resetForNewRun() {
7904
+ toolPreIndex.clear();
7905
+ toolDeltaTextByUseId.clear();
7906
+ truncatedToolDeltaUseIds.clear();
7907
+ }
7908
+ };
7909
+ }
7910
+
7911
+ // src/core/feed/internals/agentMessageStream.ts
7912
+ function normalizeAgentMessage(message) {
7913
+ return message.replace(/\r\n/g, "\n").trimEnd();
7914
+ }
7915
+ function agentMessageKey(actorId, scope) {
7916
+ return `${actorId}\0${scope}`;
7917
+ }
7918
+ function createAgentMessageStream(eventBuilder, transcriptReader) {
7919
+ const pendingMessages = /* @__PURE__ */ new Map();
7920
+ const lastAgentMessageByActorScope = /* @__PURE__ */ new Map();
7921
+ const reasoningSummaryByKey = /* @__PURE__ */ new Map();
7922
+ function emit(args) {
7923
+ const normalized = normalizeAgentMessage(args.message);
7924
+ if (!normalized) return null;
7925
+ const key = agentMessageKey(args.actorId, args.scope);
7926
+ if (lastAgentMessageByActorScope.get(key) === normalized) return null;
7927
+ const data = {
7928
+ message: normalized,
7929
+ source: args.source,
7930
+ scope: args.scope,
7931
+ ...args.model ? { model: args.model } : {}
7932
+ };
7933
+ const event = eventBuilder(
7934
+ "agent.message",
7935
+ "info",
7936
+ args.actorId,
7937
+ data,
7938
+ args.runtimeEvent,
7939
+ args.cause
7940
+ );
7941
+ lastAgentMessageByActorScope.set(key, normalized);
7942
+ return event;
7930
7943
  }
7931
- function nextSeq() {
7932
- return ++seq;
7933
- }
7934
- function appendToolDelta(toolUseId, chunk) {
7935
- if (!toolUseId) {
7936
- return chunk;
7944
+ return {
7945
+ emit,
7946
+ appendPendingDelta(itemId, delta, defaultActorId, defaultScope) {
7947
+ if (!delta) return;
7948
+ const key = itemId ?? "__legacy_root__";
7949
+ const existing = pendingMessages.get(key);
7950
+ if (existing) {
7951
+ existing.message += delta;
7952
+ return;
7953
+ }
7954
+ pendingMessages.set(key, {
7955
+ message: delta,
7956
+ actorId: defaultActorId,
7957
+ scope: defaultScope
7958
+ });
7959
+ },
7960
+ emitCompleted({
7961
+ itemId,
7962
+ messageText,
7963
+ fallbackActorId,
7964
+ fallbackScope,
7965
+ runtimeEvent
7966
+ }) {
7967
+ const key = itemId ?? "__legacy_root__";
7968
+ const pending = pendingMessages.get(key);
7969
+ const message = messageText ?? pending?.message ?? "";
7970
+ pendingMessages.delete(key);
7971
+ if (!message) return null;
7972
+ const actorId = pending?.actorId ?? fallbackActorId;
7973
+ const scope = pending?.scope ?? fallbackScope;
7974
+ return emit({
7975
+ runtimeEvent,
7976
+ actorId,
7977
+ scope,
7978
+ message,
7979
+ source: "hook"
7980
+ });
7981
+ },
7982
+ flushPending(runtimeEvent) {
7983
+ const out = [];
7984
+ for (const [key, pending] of pendingMessages) {
7985
+ if (pending.message) {
7986
+ const ev = emit({
7987
+ runtimeEvent,
7988
+ actorId: pending.actorId,
7989
+ scope: pending.scope,
7990
+ message: pending.message,
7991
+ source: "hook"
7992
+ });
7993
+ if (ev) out.push(ev);
7994
+ }
7995
+ pendingMessages.delete(key);
7996
+ }
7997
+ return out;
7998
+ },
7999
+ emitTranscriptMessages(transcriptPath, runtimeEvent, actorId, scope) {
8000
+ const msgs = transcriptReader.readNewAssistantMessages(transcriptPath);
8001
+ const out = [];
8002
+ for (const msg of msgs) {
8003
+ const ev = emit({
8004
+ runtimeEvent,
8005
+ actorId,
8006
+ scope,
8007
+ message: msg.text,
8008
+ source: "transcript",
8009
+ model: msg.model
8010
+ });
8011
+ if (ev) out.push(ev);
8012
+ }
8013
+ return out;
8014
+ },
8015
+ drainTranscript(transcriptPath) {
8016
+ transcriptReader.readNewAssistantMessages(transcriptPath);
8017
+ },
8018
+ appendReasoningSummary(itemId, index, chunk) {
8019
+ const key = `${itemId ?? ""}:${index ?? 0}`;
8020
+ const next = `${reasoningSummaryByKey.get(key) ?? ""}${chunk}`;
8021
+ reasoningSummaryByKey.set(key, next);
8022
+ return next;
8023
+ },
8024
+ clearPending() {
8025
+ pendingMessages.clear();
8026
+ },
8027
+ resetDeduper() {
8028
+ lastAgentMessageByActorScope.clear();
8029
+ },
8030
+ resetForNewRun() {
8031
+ lastAgentMessageByActorScope.clear();
8032
+ reasoningSummaryByKey.clear();
7937
8033
  }
7938
- const cumulative = `${toolDeltaTextByUseId.get(toolUseId) ?? ""}${chunk}`;
7939
- if (cumulative.length <= MAX_STREAMED_TOOL_OUTPUT_CHARS) {
7940
- toolDeltaTextByUseId.set(toolUseId, cumulative);
7941
- return truncatedToolDeltaUseIds.has(toolUseId) ? `${STREAMED_TOOL_OUTPUT_TRUNCATED_NOTICE}${cumulative}` : cumulative;
8034
+ };
8035
+ }
8036
+
8037
+ // src/core/feed/internals/rootPlanTracker.ts
8038
+ function createRootPlanTracker() {
8039
+ let items = [];
8040
+ return {
8041
+ set(next) {
8042
+ items = next;
8043
+ },
8044
+ current() {
8045
+ return items;
8046
+ },
8047
+ differs(next) {
8048
+ if (next.length !== items.length) return true;
8049
+ for (let i = 0; i < next.length; i++) {
8050
+ if (next[i]?.content !== items[i]?.content) return true;
8051
+ if (next[i]?.status !== items[i]?.status) return true;
8052
+ }
8053
+ return false;
7942
8054
  }
7943
- const tail2 = cumulative.slice(-MAX_STREAMED_TOOL_OUTPUT_CHARS);
7944
- toolDeltaTextByUseId.set(toolUseId, tail2);
7945
- truncatedToolDeltaUseIds.add(toolUseId);
7946
- return `${STREAMED_TOOL_OUTPUT_TRUNCATED_NOTICE}${tail2}`;
7947
- }
7948
- function getRunId() {
7949
- const sessId = currentSession?.session_id ?? "unknown";
7950
- return `${sessId}:R${runSeq}`;
8055
+ };
8056
+ }
8057
+
8058
+ // src/core/feed/internals/subagentTracker.ts
8059
+ function createSubagentTracker() {
8060
+ const stack = [];
8061
+ const descriptions = /* @__PURE__ */ new Map();
8062
+ let pendingDescription;
8063
+ return {
8064
+ pushActor(actorId) {
8065
+ stack.push(actorId);
8066
+ },
8067
+ popActor(actorId) {
8068
+ const idx = stack.lastIndexOf(actorId);
8069
+ if (idx !== -1) stack.splice(idx, 1);
8070
+ },
8071
+ peek() {
8072
+ return stack.length > 0 ? stack[stack.length - 1] : void 0;
8073
+ },
8074
+ clear() {
8075
+ stack.length = 0;
8076
+ },
8077
+ currentScope() {
8078
+ return stack.length > 0 ? "subagent" : "root";
8079
+ },
8080
+ recordPendingDescription(description) {
8081
+ pendingDescription = description;
8082
+ },
8083
+ clearPendingDescription() {
8084
+ pendingDescription = void 0;
8085
+ },
8086
+ consumePendingDescription() {
8087
+ const value = pendingDescription;
8088
+ pendingDescription = void 0;
8089
+ return value;
8090
+ },
8091
+ setDescription(agentId, description) {
8092
+ descriptions.set(agentId, description);
8093
+ },
8094
+ description(agentId) {
8095
+ return descriptions.get(agentId);
8096
+ }
8097
+ };
8098
+ }
8099
+
8100
+ // src/core/feed/mapper.ts
8101
+ function extractTodoItems(toolInput) {
8102
+ const input = toolInput;
8103
+ return Array.isArray(input?.todos) ? input.todos : [];
8104
+ }
8105
+ function mapPlanStepStatus(status) {
8106
+ switch (status) {
8107
+ case "inProgress":
8108
+ return "in_progress";
8109
+ case "completed":
8110
+ return "completed";
8111
+ case void 0:
8112
+ default:
8113
+ return "pending";
7951
8114
  }
8115
+ }
8116
+ function createFeedMapper(bootstrap) {
8117
+ const runLifecycle = createRunLifecycle();
8118
+ const decisionCorrelation = createDecisionCorrelation();
8119
+ const toolCorrelation = createToolCorrelation();
8120
+ const transcriptReader = createTranscriptReader();
8121
+ const actors = new ActorRegistry();
8122
+ const rootPlan = createRootPlanTracker();
8123
+ const subagents = createSubagentTracker();
7952
8124
  function makeEvent(kind, level, actorId, data, runtimeEvent, cause) {
7953
- const s = nextSeq();
7954
- const eventId = `${getRunId()}:E${s}`;
8125
+ const s = runLifecycle.allocateSeq();
8126
+ const runId = runLifecycle.getRunId();
8127
+ const eventId = `${runId}:E${s}`;
7955
8128
  const baseCause = {
7956
8129
  hook_request_id: runtimeEvent.id,
7957
8130
  transcript_path: runtimeEvent.context.transcriptPath,
@@ -7962,7 +8135,7 @@ function createFeedMapper(bootstrap) {
7962
8135
  seq: s,
7963
8136
  ts: runtimeEvent.timestamp,
7964
8137
  session_id: runtimeEvent.sessionId,
7965
- run_id: getRunId(),
8138
+ run_id: runId,
7966
8139
  kind,
7967
8140
  level,
7968
8141
  actor_id: actorId,
@@ -7974,57 +8147,50 @@ function createFeedMapper(bootstrap) {
7974
8147
  };
7975
8148
  fe.title = composeTitle(fe, runtimeEvent);
7976
8149
  if (runtimeEvent.interaction.expectsDecision || kind === "permission.request" || kind === "stop.request") {
7977
- eventIdByRequestId.set(runtimeEvent.id, eventId);
7978
- eventKindByRequestId.set(runtimeEvent.id, kind);
7979
- resolvedRequestById.set(runtimeEvent.id, { event_id: eventId, kind });
8150
+ decisionCorrelation.recordRequest(runtimeEvent.id, eventId, kind);
7980
8151
  }
7981
8152
  return fe;
7982
8153
  }
7983
- function closeRun(runtimeEvent, status) {
7984
- if (!currentRun) return null;
7985
- currentRun.status = status;
7986
- currentRun.ended_at = runtimeEvent.timestamp;
7987
- const evt = makeEvent(
8154
+ const agentMessageStream = createAgentMessageStream(
8155
+ makeEvent,
8156
+ transcriptReader
8157
+ );
8158
+ if (bootstrap) {
8159
+ runLifecycle.restoreFrom(bootstrap);
8160
+ for (const e of bootstrap.feedEvents) {
8161
+ if (e.kind === "tool.pre" && e.actor_id === "agent:root" && e.data.tool_name === "TodoWrite") {
8162
+ rootPlan.set(
8163
+ extractTodoItems(e.data.tool_input)
8164
+ );
8165
+ }
8166
+ }
8167
+ }
8168
+ function closeRunIntoEvent(runtimeEvent, status) {
8169
+ const closed = runLifecycle.closeRun(runtimeEvent.timestamp, status);
8170
+ if (!closed) return null;
8171
+ return makeEvent(
7988
8172
  "run.end",
7989
8173
  "info",
7990
8174
  "system",
7991
- { status, counters: { ...currentRun.counters } },
8175
+ { status, counters: { ...closed.counters } },
7992
8176
  runtimeEvent
7993
8177
  );
7994
- currentRun = null;
7995
- return evt;
7996
8178
  }
7997
8179
  function ensureRunArray(runtimeEvent, triggerType = "other", promptPreview) {
7998
- if (currentRun && triggerType === "other") return [];
8180
+ if (runLifecycle.getCurrentRun() && triggerType === "other") return [];
7999
8181
  const results = [];
8000
- if (currentRun) {
8001
- const closeEvt = closeRun(runtimeEvent, "completed");
8002
- if (closeEvt) results.push(closeEvt);
8003
- }
8004
- runSeq++;
8005
- toolPreIndex.clear();
8006
- toolDeltaTextByUseId.clear();
8007
- truncatedToolDeltaUseIds.clear();
8008
- reasoningSummaryByKey.clear();
8009
- eventIdByRequestId.clear();
8010
- eventKindByRequestId.clear();
8011
- resolvedRequestById.clear();
8012
- resetAgentMessageDeduper();
8013
- activeSubagentStack.length = 0;
8014
- currentRun = {
8015
- run_id: getRunId(),
8016
- session_id: runtimeEvent.sessionId,
8017
- started_at: runtimeEvent.timestamp,
8018
- trigger: { type: triggerType, prompt_preview: promptPreview },
8019
- status: "running",
8020
- actors: { root_agent_id: "agent:root", subagent_ids: [] },
8021
- counters: {
8022
- tool_uses: 0,
8023
- tool_failures: 0,
8024
- permission_requests: 0,
8025
- blocks: 0
8026
- }
8027
- };
8182
+ const closeEvt = closeRunIntoEvent(runtimeEvent, "completed");
8183
+ if (closeEvt) results.push(closeEvt);
8184
+ toolCorrelation.resetForNewRun();
8185
+ decisionCorrelation.resetForNewRun();
8186
+ agentMessageStream.resetForNewRun();
8187
+ subagents.clear();
8188
+ runLifecycle.openNewRun(
8189
+ runtimeEvent.timestamp,
8190
+ runtimeEvent.sessionId,
8191
+ triggerType,
8192
+ promptPreview
8193
+ );
8028
8194
  results.push(
8029
8195
  makeEvent(
8030
8196
  "run.start",
@@ -8036,73 +8202,8 @@ function createFeedMapper(bootstrap) {
8036
8202
  );
8037
8203
  return results;
8038
8204
  }
8039
- const activeSubagentStack = [];
8040
- let lastTaskDescription;
8041
- const subagentDescriptions = /* @__PURE__ */ new Map();
8042
- const pendingMessages = /* @__PURE__ */ new Map();
8043
- const lastAgentMessageByActorScope = /* @__PURE__ */ new Map();
8044
- const reasoningSummaryByKey = /* @__PURE__ */ new Map();
8045
- const transcriptReader = createTranscriptReader();
8046
- function emitTranscriptMessages(transcriptPath, runtimeEvent, actorId, scope) {
8047
- const msgs = transcriptReader.readNewAssistantMessages(transcriptPath);
8048
- const results = [];
8049
- for (const msg of msgs) {
8050
- const agentMsg = emitAgentMessage(
8051
- runtimeEvent,
8052
- actorId,
8053
- scope,
8054
- msg.text,
8055
- "transcript",
8056
- void 0,
8057
- msg.model
8058
- );
8059
- if (agentMsg) {
8060
- results.push(agentMsg);
8061
- }
8062
- }
8063
- return results;
8064
- }
8065
- function agentMessageKey(actorId, scope) {
8066
- return `${actorId}\0${scope}`;
8067
- }
8068
- function normalizeAgentMessage(message) {
8069
- return message.replace(/\r\n/g, "\n").trimEnd();
8070
- }
8071
- function resetAgentMessageDeduper() {
8072
- lastAgentMessageByActorScope.clear();
8073
- }
8074
- function appendReasoningSummary(itemId, index, chunk) {
8075
- const key = `${itemId ?? ""}:${index ?? 0}`;
8076
- const next = `${reasoningSummaryByKey.get(key) ?? ""}${chunk}`;
8077
- reasoningSummaryByKey.set(key, next);
8078
- return next;
8079
- }
8080
- function emitAgentMessage(runtimeEvent, actorId, scope, message, source, cause, model) {
8081
- const normalized = normalizeAgentMessage(message);
8082
- if (!normalized) return null;
8083
- const key = agentMessageKey(actorId, scope);
8084
- const previous = lastAgentMessageByActorScope.get(key);
8085
- if (previous === normalized) {
8086
- return null;
8087
- }
8088
- const event = makeEvent(
8089
- "agent.message",
8090
- "info",
8091
- actorId,
8092
- {
8093
- message: normalized,
8094
- source,
8095
- scope,
8096
- ...model ? { model } : {}
8097
- },
8098
- runtimeEvent,
8099
- cause
8100
- );
8101
- lastAgentMessageByActorScope.set(key, normalized);
8102
- return event;
8103
- }
8104
8205
  function resolveToolActor() {
8105
- return activeSubagentStack.length > 0 ? activeSubagentStack[activeSubagentStack.length - 1] : "agent:root";
8206
+ return subagents.peek() ?? "agent:root";
8106
8207
  }
8107
8208
  function resolveToolUseId(event, record) {
8108
8209
  return event.toolUseId ?? record["tool_use_id"];
@@ -8145,100 +8246,44 @@ function createFeedMapper(bootstrap) {
8145
8246
  };
8146
8247
  const eventKind2 = event.kind;
8147
8248
  const results = [];
8148
- const resolveMessageScope = () => {
8149
- const actorId = resolveToolActor();
8150
- return {
8151
- actorId,
8152
- scope: activeSubagentStack.length > 0 ? "subagent" : "root"
8153
- };
8154
- };
8155
- const appendPendingMessageDelta = (itemId, delta) => {
8156
- if (!delta) return;
8157
- const key = itemId ?? "__legacy_root__";
8158
- const existing = pendingMessages.get(key);
8159
- if (existing) {
8160
- existing.message += delta;
8161
- return;
8162
- }
8163
- const scope = resolveMessageScope();
8164
- pendingMessages.set(key, {
8165
- message: delta,
8166
- actorId: scope.actorId,
8167
- scope: scope.scope
8168
- });
8169
- };
8170
- const emitCompletedMessage = (itemId, messageText) => {
8171
- const key = itemId ?? "__legacy_root__";
8172
- const pending = pendingMessages.get(key);
8173
- const message = messageText ?? pending?.message ?? "";
8174
- if (!message) return;
8175
- const scope = pending ?? resolveMessageScope();
8176
- const agentMsg = emitAgentMessage(
8177
- event,
8178
- scope.actorId,
8179
- scope.scope,
8180
- message,
8181
- "hook"
8182
- );
8183
- if (agentMsg) {
8184
- results.push(agentMsg);
8185
- }
8186
- pendingMessages.delete(key);
8187
- };
8188
- const flushPendingMessages = () => {
8189
- for (const [itemId, pending] of pendingMessages) {
8190
- if (!pending.message) continue;
8191
- const agentMsg = emitAgentMessage(
8192
- event,
8193
- pending.actorId,
8194
- pending.scope,
8195
- pending.message,
8196
- "hook"
8197
- );
8198
- if (agentMsg) {
8199
- results.push(agentMsg);
8200
- }
8201
- pendingMessages.delete(itemId);
8202
- }
8203
- };
8249
+ const currentScope = () => subagents.currentScope();
8204
8250
  function emitFallbackMessage(parentKind, actorId, scope) {
8205
8251
  if (results.some((r) => r.kind === "agent.message")) return;
8206
8252
  const msg = readString(d["last_assistant_message"]);
8207
8253
  if (!msg) return;
8208
8254
  const parentEvt = results.find((r) => r.kind === parentKind);
8209
- const agentMsg = emitAgentMessage(
8210
- event,
8255
+ const ev = agentMessageStream.emit({
8256
+ runtimeEvent: event,
8211
8257
  actorId,
8212
8258
  scope,
8213
- msg,
8214
- "hook",
8215
- parentEvt ? { parent_event_id: parentEvt.event_id } : void 0
8216
- );
8217
- if (agentMsg) {
8218
- results.push(agentMsg);
8219
- }
8259
+ message: msg,
8260
+ source: "hook",
8261
+ cause: parentEvt ? { parent_event_id: parentEvt.event_id } : void 0
8262
+ });
8263
+ if (ev) results.push(ev);
8220
8264
  }
8221
8265
  const transcriptPath = event.context.transcriptPath;
8222
8266
  const isStopEvent = eventKind2 === "stop.request" || eventKind2 === "subagent.stop";
8223
8267
  if (transcriptPath && !isStopEvent) {
8224
- const transcriptMsgs = emitTranscriptMessages(
8225
- transcriptPath,
8226
- event,
8227
- resolveToolActor(),
8228
- activeSubagentStack.length > 0 ? "subagent" : "root"
8268
+ results.push(
8269
+ ...agentMessageStream.emitTranscriptMessages(
8270
+ transcriptPath,
8271
+ event,
8272
+ resolveToolActor(),
8273
+ currentScope()
8274
+ )
8229
8275
  );
8230
- results.push(...transcriptMsgs);
8231
8276
  }
8232
8277
  switch (eventKind2) {
8233
8278
  case "session.start": {
8234
- pendingMessages.clear();
8279
+ agentMessageStream.clearPending();
8235
8280
  const source = readString(d["source"]) ?? "startup";
8236
- currentSession = {
8281
+ runLifecycle.setSession({
8237
8282
  session_id: event.sessionId,
8238
8283
  started_at: event.timestamp,
8239
8284
  source,
8240
8285
  agent_type: readString(d["agent_type"])
8241
- };
8286
+ });
8242
8287
  if (source === "resume" || source === "clear" || source === "compact") {
8243
8288
  results.push(
8244
8289
  ...ensureRunArray(event, source)
@@ -8260,11 +8305,9 @@ function createFeedMapper(bootstrap) {
8260
8305
  break;
8261
8306
  }
8262
8307
  case "session.end": {
8263
- pendingMessages.clear();
8264
- if (currentRun) {
8265
- const closeEvt = closeRun(event, "completed");
8266
- if (closeEvt) results.push(closeEvt);
8267
- }
8308
+ agentMessageStream.clearPending();
8309
+ const closeEvt = closeRunIntoEvent(event, "completed");
8310
+ if (closeEvt) results.push(closeEvt);
8268
8311
  results.push(
8269
8312
  makeEvent(
8270
8313
  "session.end",
@@ -8276,9 +8319,7 @@ function createFeedMapper(bootstrap) {
8276
8319
  event
8277
8320
  )
8278
8321
  );
8279
- if (currentSession) {
8280
- currentSession.ended_at = event.timestamp;
8281
- }
8322
+ runLifecycle.endSession(event.timestamp);
8282
8323
  break;
8283
8324
  }
8284
8325
  case "user.prompt": {
@@ -8302,7 +8343,7 @@ function createFeedMapper(bootstrap) {
8302
8343
  break;
8303
8344
  }
8304
8345
  case "turn.start": {
8305
- pendingMessages.clear();
8346
+ agentMessageStream.clearPending();
8306
8347
  const prompt = readString(d["prompt"]);
8307
8348
  results.push(
8308
8349
  ...ensureRunArray(
@@ -8329,23 +8370,29 @@ function createFeedMapper(bootstrap) {
8329
8370
  break;
8330
8371
  }
8331
8372
  case "message.delta": {
8332
- appendPendingMessageDelta(
8373
+ agentMessageStream.appendPendingDelta(
8333
8374
  readString(d["item_id"]),
8334
- readString(d["delta"]) ?? ""
8375
+ readString(d["delta"]) ?? "",
8376
+ resolveToolActor(),
8377
+ currentScope()
8335
8378
  );
8336
8379
  break;
8337
8380
  }
8338
8381
  case "message.complete": {
8339
8382
  results.push(...ensureRunArray(event));
8340
- emitCompletedMessage(
8341
- readString(d["item_id"]),
8342
- readString(d["message"])
8343
- );
8383
+ const ev = agentMessageStream.emitCompleted({
8384
+ itemId: readString(d["item_id"]),
8385
+ messageText: readString(d["message"]),
8386
+ fallbackActorId: resolveToolActor(),
8387
+ fallbackScope: currentScope(),
8388
+ runtimeEvent: event
8389
+ });
8390
+ if (ev) results.push(ev);
8344
8391
  break;
8345
8392
  }
8346
8393
  case "turn.complete": {
8347
- if (!currentRun) {
8348
- pendingMessages.clear();
8394
+ if (!runLifecycle.getCurrentRun()) {
8395
+ agentMessageStream.clearPending();
8349
8396
  break;
8350
8397
  }
8351
8398
  const stopEvt = makeEvent(
@@ -8358,39 +8405,29 @@ function createFeedMapper(bootstrap) {
8358
8405
  event
8359
8406
  );
8360
8407
  results.push(stopEvt);
8361
- const messageCountBeforeFlush = results.length;
8362
- flushPendingMessages();
8363
- if (results.length > messageCountBeforeFlush) {
8364
- for (let i = messageCountBeforeFlush; i < results.length; i++) {
8365
- results[i].cause = {
8366
- ...results[i].cause ?? {},
8367
- parent_event_id: stopEvt.event_id
8368
- };
8369
- }
8370
- }
8371
- const closeEvt = closeRun(event, "completed");
8372
- if (closeEvt) {
8373
- results.push(closeEvt);
8408
+ const flushed = agentMessageStream.flushPending(event);
8409
+ for (const f of flushed) {
8410
+ f.cause = {
8411
+ ...f.cause ?? {},
8412
+ parent_event_id: stopEvt.event_id
8413
+ };
8414
+ results.push(f);
8374
8415
  }
8416
+ const closeEvt = closeRunIntoEvent(event, "completed");
8417
+ if (closeEvt) results.push(closeEvt);
8375
8418
  break;
8376
8419
  }
8377
8420
  case "plan.delta": {
8378
8421
  const planSteps = d["plan"];
8379
8422
  if (Array.isArray(planSteps) && planSteps.length > 0) {
8380
- const changed = planSteps.length !== lastRootTasks.length || planSteps.some(
8381
- (step, i) => {
8382
- const content = typeof step.step === "string" ? step.step : "";
8383
- const status = mapPlanStepStatus(step.status);
8384
- return content !== lastRootTasks[i]?.content || status !== lastRootTasks[i]?.status;
8385
- }
8423
+ const next = planSteps.map(
8424
+ (step) => ({
8425
+ content: typeof step.step === "string" ? step.step : "",
8426
+ status: mapPlanStepStatus(step.status)
8427
+ })
8386
8428
  );
8387
- if (changed) {
8388
- lastRootTasks = planSteps.map(
8389
- (step) => ({
8390
- content: typeof step.step === "string" ? step.step : "",
8391
- status: mapPlanStepStatus(step.status)
8392
- })
8393
- );
8429
+ if (rootPlan.differs(next)) {
8430
+ rootPlan.set(next);
8394
8431
  results.push(
8395
8432
  makeEvent(
8396
8433
  "todo.update",
@@ -8435,7 +8472,7 @@ function createFeedMapper(bootstrap) {
8435
8472
  "info",
8436
8473
  "agent:root",
8437
8474
  {
8438
- message: appendReasoningSummary(
8475
+ message: agentMessageStream.appendReasoningSummary(
8439
8476
  readString(d["item_id"]),
8440
8477
  summaryIndex,
8441
8478
  readString(d["delta"]) ?? ""
@@ -8472,9 +8509,9 @@ function createFeedMapper(bootstrap) {
8472
8509
  results.push(...ensureRunArray(event));
8473
8510
  const toolUseId = resolveToolUseId(event, d);
8474
8511
  const toolName = event.toolName ?? readString(d["tool_name"]) ?? "Unknown";
8475
- const parentId = toolUseId ? toolPreIndex.get(toolUseId) : void 0;
8512
+ const parentId = toolCorrelation.lookupParent(toolUseId);
8476
8513
  const chunk = readString(d["delta"]) ?? "";
8477
- const cumulative = appendToolDelta(toolUseId, chunk);
8514
+ const cumulative = toolCorrelation.appendDelta(toolUseId, chunk);
8478
8515
  results.push(
8479
8516
  makeEvent(
8480
8517
  "tool.delta",
@@ -8494,7 +8531,7 @@ function createFeedMapper(bootstrap) {
8494
8531
  }
8495
8532
  case "tool.pre": {
8496
8533
  results.push(...ensureRunArray(event));
8497
- if (currentRun) currentRun.counters.tool_uses++;
8534
+ runLifecycle.incrementCounter("tool_uses");
8498
8535
  const toolUseId = resolveToolUseId(event, d);
8499
8536
  const toolName = event.toolName ?? readString(d["tool_name"]) ?? "Unknown";
8500
8537
  const fe = makeEvent(
@@ -8510,7 +8547,7 @@ function createFeedMapper(bootstrap) {
8510
8547
  toolUseId ? { tool_use_id: toolUseId } : void 0
8511
8548
  );
8512
8549
  if (toolUseId) {
8513
- toolPreIndex.set(toolUseId, fe.event_id);
8550
+ toolCorrelation.recordPre(toolUseId, fe.event_id);
8514
8551
  }
8515
8552
  results.push(fe);
8516
8553
  if (toolName === "WebSearch") {
@@ -8536,22 +8573,23 @@ function createFeedMapper(bootstrap) {
8536
8573
  );
8537
8574
  }
8538
8575
  if (toolName === "TodoWrite" && fe.actor_id === "agent:root") {
8539
- lastRootTasks = extractTodoItems(readObject(d["tool_input"]));
8576
+ rootPlan.set(extractTodoItems(readObject(d["tool_input"])));
8540
8577
  }
8541
8578
  if (isSubagentTool(toolName)) {
8542
8579
  const input = readObject(d["tool_input"]);
8543
- lastTaskDescription = typeof input["description"] === "string" ? input["description"] : void 0;
8580
+ if (typeof input["description"] === "string") {
8581
+ subagents.recordPendingDescription(input["description"]);
8582
+ } else {
8583
+ subagents.clearPendingDescription();
8584
+ }
8544
8585
  }
8545
8586
  break;
8546
8587
  }
8547
8588
  case "tool.post": {
8548
8589
  results.push(...ensureRunArray(event));
8549
8590
  const toolUseId = resolveToolUseId(event, d);
8550
- if (toolUseId) {
8551
- toolDeltaTextByUseId.delete(toolUseId);
8552
- truncatedToolDeltaUseIds.delete(toolUseId);
8553
- }
8554
- const parentId = toolUseId ? toolPreIndex.get(toolUseId) : void 0;
8591
+ if (toolUseId) toolCorrelation.forgetTool(toolUseId);
8592
+ const parentId = toolCorrelation.lookupParent(toolUseId);
8555
8593
  const toolName = event.toolName ?? readString(d["tool_name"]) ?? "Unknown";
8556
8594
  const postEvent = makeEvent(
8557
8595
  "tool.post",
@@ -8599,13 +8637,10 @@ function createFeedMapper(bootstrap) {
8599
8637
  }
8600
8638
  case "tool.failure": {
8601
8639
  results.push(...ensureRunArray(event));
8602
- if (currentRun) currentRun.counters.tool_failures++;
8640
+ runLifecycle.incrementCounter("tool_failures");
8603
8641
  const toolUseId = resolveToolUseId(event, d);
8604
- if (toolUseId) {
8605
- toolDeltaTextByUseId.delete(toolUseId);
8606
- truncatedToolDeltaUseIds.delete(toolUseId);
8607
- }
8608
- const parentId = toolUseId ? toolPreIndex.get(toolUseId) : void 0;
8642
+ if (toolUseId) toolCorrelation.forgetTool(toolUseId);
8643
+ const parentId = toolCorrelation.lookupParent(toolUseId);
8609
8644
  const toolName = event.toolName ?? readString(d["tool_name"]) ?? "Unknown";
8610
8645
  results.push(
8611
8646
  makeEvent(
@@ -8627,7 +8662,7 @@ function createFeedMapper(bootstrap) {
8627
8662
  }
8628
8663
  case "permission.request": {
8629
8664
  results.push(...ensureRunArray(event));
8630
- if (currentRun) currentRun.counters.permission_requests++;
8665
+ runLifecycle.incrementCounter("permission_requests");
8631
8666
  const toolName = event.toolName ?? readString(d["tool_name"]) ?? "Unknown";
8632
8667
  results.push(
8633
8668
  makeEvent(
@@ -8676,9 +8711,11 @@ function createFeedMapper(bootstrap) {
8676
8711
  const agentType = event.agentType ?? readString(d["agent_type"]);
8677
8712
  if (agentId) {
8678
8713
  actors.ensureSubagent(agentId, agentType ?? "unknown");
8714
+ const currentRun = runLifecycle.getCurrentRun();
8679
8715
  if (currentRun) currentRun.actors.subagent_ids.push(agentId);
8680
- activeSubagentStack.push(`subagent:${agentId}`);
8716
+ subagents.pushActor(`subagent:${agentId}`);
8681
8717
  }
8718
+ const description = subagents.consumePendingDescription() ?? readString(d["prompt"]);
8682
8719
  results.push(
8683
8720
  makeEvent(
8684
8721
  "subagent.start",
@@ -8687,7 +8724,7 @@ function createFeedMapper(bootstrap) {
8687
8724
  {
8688
8725
  agent_id: agentId ?? "",
8689
8726
  agent_type: agentType ?? "",
8690
- description: lastTaskDescription ?? readString(d["prompt"]) ?? void 0,
8727
+ description: description ?? void 0,
8691
8728
  tool: readString(d["tool"]),
8692
8729
  sender_thread_id: readString(d["sender_thread_id"]),
8693
8730
  receiver_thread_id: readString(d["receiver_thread_id"]),
@@ -8697,22 +8734,16 @@ function createFeedMapper(bootstrap) {
8697
8734
  event
8698
8735
  )
8699
8736
  );
8700
- if (agentId && (lastTaskDescription || readString(d["prompt"]))) {
8701
- subagentDescriptions.set(
8702
- agentId,
8703
- lastTaskDescription ?? readString(d["prompt"]) ?? ""
8704
- );
8737
+ if (agentId && description) {
8738
+ subagents.setDescription(agentId, description);
8705
8739
  }
8706
- lastTaskDescription = void 0;
8707
8740
  break;
8708
8741
  }
8709
8742
  case "subagent.stop": {
8710
8743
  results.push(...ensureRunArray(event));
8711
8744
  const agentId = event.agentId ?? readString(d["agent_id"]);
8712
8745
  if (agentId) {
8713
- const actorId = `subagent:${agentId}`;
8714
- const idx = activeSubagentStack.lastIndexOf(actorId);
8715
- if (idx !== -1) activeSubagentStack.splice(idx, 1);
8746
+ subagents.popActor(`subagent:${agentId}`);
8716
8747
  }
8717
8748
  const subStopActorId = `subagent:${agentId ?? "unknown"}`;
8718
8749
  const subStopEvt = makeEvent(
@@ -8725,7 +8756,7 @@ function createFeedMapper(bootstrap) {
8725
8756
  stop_hook_active: readBoolean(d["stop_hook_active"]) ?? false,
8726
8757
  agent_transcript_path: readString(d["agent_transcript_path"]),
8727
8758
  last_assistant_message: readString(d["last_assistant_message"]),
8728
- description: subagentDescriptions.get(agentId ?? ""),
8759
+ description: subagents.description(agentId ?? ""),
8729
8760
  tool: readString(d["tool"]),
8730
8761
  status: readString(d["status"]),
8731
8762
  sender_thread_id: readString(d["sender_thread_id"]),
@@ -8836,7 +8867,7 @@ function createFeedMapper(bootstrap) {
8836
8867
  ],
8837
8868
  "server_request.resolved": (data, runtimeEvent, ctx) => {
8838
8869
  const requestId = data["request_id"] !== void 0 ? String(data["request_id"]) : void 0;
8839
- const resolved = requestId ? resolvedRequestById.get(requestId) : void 0;
8870
+ const resolved = requestId ? decisionCorrelation.lookupResolved(requestId) : null;
8840
8871
  return [
8841
8872
  makeEvent(
8842
8873
  "server.request.resolved",
@@ -9174,100 +9205,260 @@ function createFeedMapper(bootstrap) {
9174
9205
  }
9175
9206
  }
9176
9207
  if (eventKind2 === "stop.request") {
9177
- if (transcriptPath) {
9178
- transcriptReader.readNewAssistantMessages(transcriptPath);
9179
- }
9208
+ if (transcriptPath) agentMessageStream.drainTranscript(transcriptPath);
9180
9209
  emitFallbackMessage("stop.request", "agent:root", "root");
9181
9210
  }
9182
9211
  if (eventKind2 === "subagent.stop") {
9183
9212
  const agentId = readString(d["agent_id"]) ?? "unknown";
9184
- if (transcriptPath) {
9185
- transcriptReader.readNewAssistantMessages(transcriptPath);
9186
- }
9213
+ if (transcriptPath) agentMessageStream.drainTranscript(transcriptPath);
9187
9214
  emitFallbackMessage("subagent.stop", `subagent:${agentId}`, "subagent");
9188
9215
  }
9189
- return results;
9216
+ return results;
9217
+ }
9218
+ function mapDecision(requestId, decision) {
9219
+ const consumed = decisionCorrelation.consumeForDecision(requestId);
9220
+ if (!consumed) return null;
9221
+ const { parentEventId, originalKind } = consumed;
9222
+ function makeDecisionEvent(kind, data) {
9223
+ const s = runLifecycle.allocateSeq();
9224
+ const runId = runLifecycle.getRunId();
9225
+ const session = runLifecycle.getSession();
9226
+ const fe = {
9227
+ event_id: `${runId}:E${s}`,
9228
+ seq: s,
9229
+ ts: Date.now(),
9230
+ session_id: session?.session_id ?? "unknown",
9231
+ run_id: runId,
9232
+ kind,
9233
+ level: "info",
9234
+ actor_id: decision.source === "user" ? "user" : "system",
9235
+ cause: {
9236
+ parent_event_id: parentEventId,
9237
+ hook_request_id: requestId
9238
+ },
9239
+ title: "",
9240
+ data
9241
+ };
9242
+ fe.title = generateTitle(fe);
9243
+ return fe;
9244
+ }
9245
+ if (originalKind === "permission.request") {
9246
+ let data;
9247
+ if (decision.source === "timeout") {
9248
+ data = { decision_type: "no_opinion", reason: "timeout" };
9249
+ } else if (decision.type === "passthrough") {
9250
+ data = { decision_type: "no_opinion", reason: decision.source };
9251
+ } else if (decision.intent?.kind === "permission_allow") {
9252
+ data = { decision_type: "allow" };
9253
+ } else if (decision.intent?.kind === "permission_deny") {
9254
+ data = {
9255
+ decision_type: "deny",
9256
+ message: decision.intent.reason
9257
+ };
9258
+ } else {
9259
+ data = { decision_type: "no_opinion", reason: "unknown" };
9260
+ }
9261
+ return makeDecisionEvent("permission.decision", data);
9262
+ }
9263
+ if (originalKind === "stop.request") {
9264
+ let data;
9265
+ const d = decision.data;
9266
+ const decisionReason = typeof d?.reason === "string" ? d.reason : void 0;
9267
+ if (decision.source === "timeout") {
9268
+ data = { decision_type: "no_opinion", reason: "timeout" };
9269
+ } else if (decision.type === "passthrough") {
9270
+ data = { decision_type: "no_opinion", reason: decision.source };
9271
+ } else if (d?.decision === "block") {
9272
+ data = {
9273
+ decision_type: "block",
9274
+ reason: decisionReason ?? decision.reason ?? "Blocked"
9275
+ };
9276
+ } else if (d?.ok === false) {
9277
+ data = {
9278
+ decision_type: "block",
9279
+ reason: decisionReason ?? "Blocked by hook"
9280
+ };
9281
+ } else {
9282
+ data = { decision_type: "allow" };
9283
+ }
9284
+ return makeDecisionEvent("stop.decision", data);
9285
+ }
9286
+ return null;
9287
+ }
9288
+ return {
9289
+ mapEvent,
9290
+ mapDecision,
9291
+ getSession: () => runLifecycle.getSession(),
9292
+ getCurrentRun: () => runLifecycle.getCurrentRun(),
9293
+ getActors: () => actors.all(),
9294
+ getTasks: () => rootPlan.current(),
9295
+ allocateSeq: () => runLifecycle.allocateSeq()
9296
+ };
9297
+ }
9298
+
9299
+ // src/core/controller/rules.ts
9300
+ function ruleMatches(ruleToolName, toolName) {
9301
+ if (ruleToolName === "*") return true;
9302
+ if (ruleToolName === toolName) return true;
9303
+ if (ruleToolName.endsWith("__*")) {
9304
+ const prefix = ruleToolName.slice(0, -1);
9305
+ return toolName.startsWith(prefix);
9306
+ }
9307
+ return false;
9308
+ }
9309
+ function matchRule(rules, toolName) {
9310
+ const denyMatch = rules.find(
9311
+ (r) => r.action === "deny" && ruleMatches(r.toolName, toolName)
9312
+ );
9313
+ if (denyMatch) return denyMatch;
9314
+ return rules.find(
9315
+ (r) => r.action === "approve" && ruleMatches(r.toolName, toolName)
9316
+ );
9317
+ }
9318
+
9319
+ // src/core/controller/permission.ts
9320
+ var SESSION_APPROVAL_REQUEST_HOOKS = /* @__PURE__ */ new Set([
9321
+ "item/commandExecution/requestApproval",
9322
+ "item/fileChange/requestApproval",
9323
+ "item/permissions/requestApproval"
9324
+ ]);
9325
+ function isScopedPermissionsRequest(hookName) {
9326
+ return hookName === "item/permissions/requestApproval";
9327
+ }
9328
+ function supportsSessionApproval(hookName) {
9329
+ return hookName !== void 0 && SESSION_APPROVAL_REQUEST_HOOKS.has(hookName);
9330
+ }
9331
+ function extractPermissionSnapshot(event) {
9332
+ const data = event.data;
9333
+ const toolNameFromData = typeof data["tool_name"] === "string" ? data["tool_name"] : void 0;
9334
+ const toolInputFromData = typeof data["tool_input"] === "object" && data["tool_input"] !== null ? data["tool_input"] : void 0;
9335
+ const toolUseIdFromData = typeof data["tool_use_id"] === "string" ? data["tool_use_id"] : void 0;
9336
+ return {
9337
+ request_id: event.id,
9338
+ ts: event.timestamp,
9339
+ kind: event.kind,
9340
+ hookName: event.hookName,
9341
+ tool_name: event.toolName ?? toolNameFromData ?? "Unknown",
9342
+ tool_input: toolInputFromData ?? {},
9343
+ tool_use_id: event.toolUseId ?? toolUseIdFromData,
9344
+ suggestions: data.permission_suggestions
9345
+ };
9346
+ }
9347
+
9348
+ // src/core/controller/runtimeController.ts
9349
+ function handleEvent(event, cb) {
9350
+ const eventKind2 = event.kind;
9351
+ const isScoped = isScopedPermissionsRequest(event.hookName);
9352
+ const eventData = event.data;
9353
+ const toolName = event.toolName ?? (typeof eventData["tool_name"] === "string" ? eventData["tool_name"] : void 0);
9354
+ if (eventKind2 === "permission.request" && toolName === "user_input") {
9355
+ cb.enqueueQuestion(event.id);
9356
+ cb.relayQuestion?.(event);
9357
+ return { handled: true };
9358
+ }
9359
+ if (eventKind2 === "permission.request" && toolName) {
9360
+ const rule = matchRule(cb.getRules(), toolName);
9361
+ if (rule?.action === "deny") {
9362
+ return {
9363
+ handled: true,
9364
+ decision: {
9365
+ type: "json",
9366
+ source: "rule",
9367
+ intent: {
9368
+ kind: "permission_deny",
9369
+ reason: `Blocked by rule: ${rule.addedBy}`
9370
+ }
9371
+ }
9372
+ };
9373
+ }
9374
+ if (rule?.action === "approve") {
9375
+ return {
9376
+ handled: true,
9377
+ decision: {
9378
+ type: "json",
9379
+ source: "rule",
9380
+ intent: { kind: "permission_allow" },
9381
+ // Scoped Codex permission grants are session-scoped under
9382
+ // auto rules so the same capability isn't re-prompted.
9383
+ ...isScoped ? { data: { scope: "session" } } : {}
9384
+ }
9385
+ };
9386
+ }
9387
+ cb.enqueuePermission(event);
9388
+ cb.relayPermission?.(event);
9389
+ return { handled: true };
9190
9390
  }
9191
- function mapDecision(requestId, decision) {
9192
- const parentEventId = eventIdByRequestId.get(requestId);
9193
- if (!parentEventId) return null;
9194
- const originalKind = eventKindByRequestId.get(requestId);
9195
- eventIdByRequestId.delete(requestId);
9196
- eventKindByRequestId.delete(requestId);
9197
- function makeDecisionEvent(kind, data) {
9198
- const s = nextSeq();
9199
- const fe = {
9200
- event_id: `${getRunId()}:E${s}`,
9201
- seq: s,
9202
- ts: Date.now(),
9203
- session_id: currentSession?.session_id ?? "unknown",
9204
- run_id: getRunId(),
9205
- kind,
9206
- level: "info",
9207
- actor_id: decision.source === "user" ? "user" : "system",
9208
- cause: {
9209
- parent_event_id: parentEventId,
9210
- hook_request_id: requestId
9211
- },
9212
- title: "",
9213
- data
9391
+ if (eventKind2 === "tool.pre" && toolName === "AskUserQuestion") {
9392
+ cb.enqueueQuestion(event.id);
9393
+ cb.relayQuestion?.(event);
9394
+ return { handled: true };
9395
+ }
9396
+ if (eventKind2 === "tool.pre" && toolName) {
9397
+ const rule = matchRule(cb.getRules(), toolName);
9398
+ if (rule?.action === "deny") {
9399
+ return {
9400
+ handled: true,
9401
+ decision: {
9402
+ type: "json",
9403
+ source: "rule",
9404
+ intent: {
9405
+ kind: "pre_tool_deny",
9406
+ reason: `Blocked by rule: ${rule.addedBy}`
9407
+ }
9408
+ }
9214
9409
  };
9215
- fe.title = generateTitle(fe);
9216
- return fe;
9217
- }
9218
- if (originalKind === "permission.request") {
9219
- let data;
9220
- if (decision.source === "timeout") {
9221
- data = { decision_type: "no_opinion", reason: "timeout" };
9222
- } else if (decision.type === "passthrough") {
9223
- data = { decision_type: "no_opinion", reason: decision.source };
9224
- } else if (decision.intent?.kind === "permission_allow") {
9225
- data = { decision_type: "allow" };
9226
- } else if (decision.intent?.kind === "permission_deny") {
9227
- data = {
9228
- decision_type: "deny",
9229
- message: decision.intent.reason
9230
- };
9231
- } else {
9232
- data = { decision_type: "no_opinion", reason: "unknown" };
9233
- }
9234
- return makeDecisionEvent("permission.decision", data);
9235
9410
  }
9236
- if (originalKind === "stop.request") {
9237
- let data;
9238
- const d = decision.data;
9239
- const decisionReason = typeof d?.reason === "string" ? d.reason : void 0;
9240
- if (decision.source === "timeout") {
9241
- data = { decision_type: "no_opinion", reason: "timeout" };
9242
- } else if (decision.type === "passthrough") {
9243
- data = { decision_type: "no_opinion", reason: decision.source };
9244
- } else if (d?.decision === "block") {
9245
- data = {
9246
- decision_type: "block",
9247
- reason: decisionReason ?? decision.reason ?? "Blocked"
9248
- };
9249
- } else if (d?.ok === false) {
9250
- data = {
9251
- decision_type: "block",
9252
- reason: decisionReason ?? "Blocked by hook"
9253
- };
9254
- } else {
9255
- data = { decision_type: "allow" };
9411
+ return {
9412
+ handled: true,
9413
+ decision: {
9414
+ type: "json",
9415
+ source: "rule",
9416
+ intent: { kind: "pre_tool_allow" }
9256
9417
  }
9257
- return makeDecisionEvent("stop.decision", data);
9258
- }
9259
- return null;
9418
+ };
9419
+ }
9420
+ return { handled: false };
9421
+ }
9422
+
9423
+ // src/core/feed/ingest.ts
9424
+ function ingestRuntimeEvent(event, ctx) {
9425
+ const controllerResult = handleEvent(event, ctx.controllerCallbacks);
9426
+ const feedEvents = ctx.mapper.mapEvent(event);
9427
+ if (ctx.store) {
9428
+ persistOrDegrade(
9429
+ ctx.store,
9430
+ () => ctx.store.recordEvent(event, feedEvents),
9431
+ "recordEvent failed",
9432
+ ctx.onPersistFailure
9433
+ );
9260
9434
  }
9261
9435
  return {
9262
- mapEvent,
9263
- mapDecision,
9264
- getSession: () => currentSession,
9265
- getCurrentRun: () => currentRun,
9266
- getActors: () => actors.all(),
9267
- getTasks: () => lastRootTasks,
9268
- allocateSeq: nextSeq
9436
+ feedEvents,
9437
+ decision: controllerResult.handled && controllerResult.decision ? controllerResult.decision : null
9269
9438
  };
9270
9439
  }
9440
+ function ingestRuntimeDecision(eventId, decision, ctx) {
9441
+ const feedEvent = ctx.mapper.mapDecision(eventId, decision);
9442
+ if (feedEvent && ctx.store) {
9443
+ persistOrDegrade(
9444
+ ctx.store,
9445
+ () => ctx.store.recordFeedEvents([feedEvent]),
9446
+ "recordFeedEvents failed",
9447
+ ctx.onPersistFailure
9448
+ );
9449
+ }
9450
+ return feedEvent;
9451
+ }
9452
+ function persistOrDegrade(store, action, label, onFailure) {
9453
+ try {
9454
+ action();
9455
+ } catch (err) {
9456
+ const reason = err instanceof Error ? err.message : String(err);
9457
+ const message = `${label}: ${reason}`;
9458
+ store.markDegraded(message);
9459
+ onFailure?.(message);
9460
+ }
9461
+ }
9271
9462
 
9272
9463
  // src/infra/sessions/store.ts
9273
9464
  import fs16 from "fs";
@@ -9523,6 +9714,20 @@ function initSchema(db) {
9523
9714
  }
9524
9715
  }
9525
9716
 
9717
+ // src/infra/sessions/types.ts
9718
+ function rowToAthenaSession(row, adapterSessionIds, firstPrompt) {
9719
+ return {
9720
+ id: row.id,
9721
+ projectDir: row.project_dir,
9722
+ createdAt: row.created_at,
9723
+ updatedAt: row.updated_at,
9724
+ label: row.label ?? void 0,
9725
+ eventCount: row.event_count ?? 0,
9726
+ firstPrompt,
9727
+ adapterSessionIds
9728
+ };
9729
+ }
9730
+
9526
9731
  // src/infra/sessions/store.ts
9527
9732
  function createSessionStore(opts) {
9528
9733
  if (opts.dbPath !== ":memory:") {
@@ -9639,15 +9844,7 @@ function createSessionStore(opts) {
9639
9844
  const adapterRows = db.prepare("SELECT * FROM adapter_sessions ORDER BY started_at").all();
9640
9845
  const feedRows = db.prepare("SELECT data FROM feed_events ORDER BY seq").all();
9641
9846
  const adapterSessionIds = adapterRows.map((r) => r.session_id);
9642
- const session = sessionRow ? {
9643
- id: sessionRow.id,
9644
- projectDir: sessionRow.project_dir,
9645
- createdAt: sessionRow.created_at,
9646
- updatedAt: sessionRow.updated_at,
9647
- label: sessionRow.label ?? void 0,
9648
- eventCount: sessionRow.event_count ?? 0,
9649
- adapterSessionIds
9650
- } : {
9847
+ const session = sessionRow ? rowToAthenaSession(sessionRow, adapterSessionIds) : {
9651
9848
  id: opts.sessionId,
9652
9849
  projectDir: opts.projectDir,
9653
9850
  createdAt: now,
@@ -9677,24 +9874,17 @@ function createSessionStore(opts) {
9677
9874
  function getAthenaSession() {
9678
9875
  const sessionRow = db.prepare("SELECT * FROM session WHERE id = ?").get(opts.sessionId);
9679
9876
  const adapterRows = db.prepare("SELECT session_id FROM adapter_sessions ORDER BY started_at").all();
9877
+ const adapterSessionIds = adapterRows.map((r) => r.session_id);
9680
9878
  if (!sessionRow) {
9681
9879
  return {
9682
9880
  id: opts.sessionId,
9683
9881
  projectDir: opts.projectDir,
9684
9882
  createdAt: now,
9685
9883
  updatedAt: now,
9686
- adapterSessionIds: adapterRows.map((r) => r.session_id)
9884
+ adapterSessionIds
9687
9885
  };
9688
9886
  }
9689
- return {
9690
- id: sessionRow.id,
9691
- projectDir: sessionRow.project_dir,
9692
- createdAt: sessionRow.created_at,
9693
- updatedAt: sessionRow.updated_at,
9694
- label: sessionRow.label ?? void 0,
9695
- eventCount: sessionRow.event_count ?? 0,
9696
- adapterSessionIds: adapterRows.map((r) => r.session_id)
9697
- };
9887
+ return rowToAthenaSession(sessionRow, adapterSessionIds);
9698
9888
  }
9699
9889
  const updateTokens = db.prepare(
9700
9890
  `UPDATE adapter_sessions SET
@@ -9847,16 +10037,11 @@ function readSessionFromDb(dbPath) {
9847
10037
  firstPrompt = promptRow.prompt;
9848
10038
  }
9849
10039
  }
9850
- return {
9851
- id: row.id,
9852
- projectDir: row.project_dir,
9853
- createdAt: row.created_at,
9854
- updatedAt: row.updated_at,
9855
- label: row.label ?? void 0,
9856
- eventCount: row.event_count ?? 0,
9857
- firstPrompt,
9858
- adapterSessionIds: adapters.map((a) => a.session_id)
9859
- };
10040
+ return rowToAthenaSession(
10041
+ row,
10042
+ adapters.map((a) => a.session_id),
10043
+ firstPrompt
10044
+ );
9860
10045
  } catch {
9861
10046
  return null;
9862
10047
  } finally {
@@ -10844,6 +11029,33 @@ function resolveFinalMessage(input) {
10844
11029
  return { message: "", source: "empty" };
10845
11030
  }
10846
11031
 
11032
+ // src/app/exec/failureLatch.ts
11033
+ function createFailureLatch(onRegister) {
11034
+ let failure;
11035
+ return {
11036
+ register(next) {
11037
+ if (failure) return;
11038
+ failure = next;
11039
+ onRegister(failure);
11040
+ },
11041
+ current() {
11042
+ return failure;
11043
+ },
11044
+ hasFailure() {
11045
+ return failure !== void 0;
11046
+ }
11047
+ };
11048
+ }
11049
+ function exitCodeFromFailure(failure) {
11050
+ if (!failure) return EXEC_EXIT_CODE.SUCCESS;
11051
+ if (failure.kind === "timeout") return EXEC_EXIT_CODE.TIMEOUT;
11052
+ if (failure.kind === "output") return EXEC_EXIT_CODE.OUTPUT;
11053
+ if (failure.kind === "workflow") {
11054
+ return failure.state === "exhausted" ? EXEC_EXIT_CODE.WORKFLOW_EXHAUSTED : EXEC_EXIT_CODE.WORKFLOW_BLOCKED;
11055
+ }
11056
+ return EXEC_EXIT_CODE.RUNTIME;
11057
+ }
11058
+
10847
11059
  // src/app/exec/output.ts
10848
11060
  import fs19 from "fs/promises";
10849
11061
  import path16 from "path";
@@ -10951,7 +11163,6 @@ async function runExec(options) {
10951
11163
  now
10952
11164
  });
10953
11165
  const rules = [];
10954
- let failure;
10955
11166
  let runtimeStarted = false;
10956
11167
  let cumulativeTokens = { ...NULL_TOKENS3 };
10957
11168
  let streamFinalMessage = null;
@@ -11016,28 +11227,22 @@ async function runExec(options) {
11016
11227
  runtime,
11017
11228
  spawnProcess: options.spawnProcess
11018
11229
  });
11019
- function registerFailure(next) {
11020
- if (failure) return;
11021
- failure = next;
11230
+ const latch = createFailureLatch((next) => {
11022
11231
  output.error(next.message);
11023
11232
  output.emitJsonEvent("exec.error", {
11024
11233
  kind: next.kind,
11025
11234
  message: next.message
11026
11235
  });
11027
11236
  void sessionController.kill();
11028
- }
11237
+ });
11029
11238
  const abortListener = () => {
11030
- registerFailure({
11031
- kind: "process",
11032
- message: "Execution cancelled."
11033
- });
11239
+ latch.register({ kind: "process", message: "Execution cancelled." });
11034
11240
  };
11035
11241
  if (options.signal?.aborted) {
11036
11242
  abortListener();
11037
11243
  } else {
11038
11244
  options.signal?.addEventListener("abort", abortListener, { once: true });
11039
11245
  }
11040
- const hasFailure = () => failure !== void 0;
11041
11246
  const currentAdapterSessionId = () => adapterSessionId;
11042
11247
  const bridgeFactory = options.bridgeFactory ?? startSessionBridge;
11043
11248
  const bridge = options.channels && options.channels.length > 0 ? await bridgeFactory({
@@ -11079,23 +11284,21 @@ async function runExec(options) {
11079
11284
  toolName: runtimeEvent.toolName ?? null,
11080
11285
  data: runtimeEvent.data
11081
11286
  });
11082
- if (hasFailure()) return;
11083
- const controllerResult = handleEvent(runtimeEvent, controllerCallbacks);
11084
- if (controllerResult.handled && controllerResult.decision) {
11085
- runtime.sendDecision(runtimeEvent.id, controllerResult.decision);
11287
+ if (latch.hasFailure()) return;
11288
+ const { feedEvents, decision } = ingestRuntimeEvent(runtimeEvent, {
11289
+ mapper,
11290
+ store,
11291
+ controllerCallbacks,
11292
+ onPersistFailure: (message) => output.warn(message)
11293
+ });
11294
+ if (decision) {
11295
+ runtime.sendDecision(runtimeEvent.id, decision);
11086
11296
  }
11087
- const mapped = mapper.mapEvent(runtimeEvent);
11088
- for (const event of mapped) {
11297
+ for (const event of feedEvents) {
11089
11298
  if (event.kind === "agent.message") {
11090
11299
  mappedFinalMessage = event.data.message;
11091
11300
  }
11092
11301
  }
11093
- safePersist(
11094
- store,
11095
- () => store.recordEvent(runtimeEvent, mapped),
11096
- (message) => output.warn(message),
11097
- "recordEvent failed"
11098
- );
11099
11302
  });
11100
11303
  const unsubscribeDecision = runtime.onDecision(
11101
11304
  (eventId, decision) => {
@@ -11103,20 +11306,17 @@ async function runExec(options) {
11103
11306
  eventId,
11104
11307
  decision
11105
11308
  });
11106
- const mapped = mapper.mapDecision(eventId, decision);
11107
- if (!mapped) return;
11108
- safePersist(
11309
+ ingestRuntimeDecision(eventId, decision, {
11310
+ mapper,
11109
11311
  store,
11110
- () => store.recordFeedEvents([mapped]),
11111
- (message) => output.warn(message),
11112
- "recordFeedEvents failed"
11113
- );
11312
+ onPersistFailure: (message) => output.warn(message)
11313
+ });
11114
11314
  }
11115
11315
  );
11116
11316
  let timeoutTimer;
11117
11317
  if (typeof options.timeoutMs === "number" && options.timeoutMs > 0) {
11118
11318
  timeoutTimer = setTimeout(() => {
11119
- registerFailure({
11319
+ latch.register({
11120
11320
  kind: "timeout",
11121
11321
  message: `Execution timed out after ${options.timeoutMs}ms.`
11122
11322
  });
@@ -11186,30 +11386,30 @@ async function runExec(options) {
11186
11386
  activeRunId = handle.runId;
11187
11387
  const runResult = await handle.result;
11188
11388
  cumulativeTokens = runResult.tokens;
11189
- if (!failure) {
11389
+ if (!latch.hasFailure()) {
11190
11390
  if (runResult.status === "blocked") {
11191
- registerFailure(
11391
+ latch.register(
11192
11392
  workflowFailure(
11193
11393
  "blocked",
11194
11394
  runResult.stopReason ? `Workflow blocked: ${runResult.stopReason}` : "Workflow blocked."
11195
11395
  )
11196
11396
  );
11197
11397
  } else if (runResult.status === "exhausted") {
11198
- registerFailure(
11398
+ latch.register(
11199
11399
  workflowFailure(
11200
11400
  "exhausted",
11201
11401
  `Workflow reached the maximum of ${workflow?.loop?.maxIterations ?? 0} iterations.`
11202
11402
  )
11203
11403
  );
11204
11404
  } else if (runResult.status === "failed") {
11205
- registerFailure({
11405
+ latch.register({
11206
11406
  kind: "process",
11207
11407
  message: runResult.stopReason ?? "Workflow run failed."
11208
11408
  });
11209
11409
  }
11210
11410
  }
11211
11411
  } catch (error) {
11212
- registerFailure({
11412
+ latch.register({
11213
11413
  kind: "process",
11214
11414
  message: error instanceof Error ? error.message : String(error)
11215
11415
  });
@@ -11231,39 +11431,26 @@ async function runExec(options) {
11231
11431
  streamMessage: streamFinalMessage,
11232
11432
  mappedMessage: mappedFinalMessage
11233
11433
  });
11234
- if (resolvedFinalMessage.source === "empty" && !failure) {
11434
+ if (resolvedFinalMessage.source === "empty" && !latch.hasFailure()) {
11235
11435
  const warning = "No assistant message found in stream or hook events; writing empty output.";
11236
11436
  output.warn(warning);
11237
11437
  output.emitJsonEvent("exec.warning", { message: warning });
11238
11438
  }
11239
- if (!failure && options.outputLastMessagePath) {
11439
+ if (!latch.hasFailure() && options.outputLastMessagePath) {
11240
11440
  try {
11241
11441
  await output.writeLastMessage(
11242
11442
  options.outputLastMessagePath,
11243
11443
  resolvedFinalMessage.message
11244
11444
  );
11245
11445
  } catch (error) {
11246
- failure = {
11446
+ latch.register({
11247
11447
  kind: "output",
11248
11448
  message: `Failed writing --output-last-message: ${error instanceof Error ? error.message : String(error)}`
11249
- };
11250
- output.error(failure.message);
11251
- output.emitJsonEvent("exec.error", {
11252
- kind: failure.kind,
11253
- message: failure.message
11254
11449
  });
11255
11450
  }
11256
11451
  }
11257
- let exitCode = EXEC_EXIT_CODE.SUCCESS;
11258
- if (failure?.kind === "timeout") {
11259
- exitCode = EXEC_EXIT_CODE.TIMEOUT;
11260
- } else if (failure?.kind === "output") {
11261
- exitCode = EXEC_EXIT_CODE.OUTPUT;
11262
- } else if (failure?.kind === "workflow") {
11263
- exitCode = failure.state === "exhausted" ? EXEC_EXIT_CODE.WORKFLOW_EXHAUSTED : EXEC_EXIT_CODE.WORKFLOW_BLOCKED;
11264
- } else if (failure) {
11265
- exitCode = EXEC_EXIT_CODE.RUNTIME;
11266
- }
11452
+ const failure = latch.current();
11453
+ const exitCode = exitCodeFromFailure(failure);
11267
11454
  const success = exitCode === EXEC_EXIT_CODE.SUCCESS;
11268
11455
  const finalMessage = success ? resolvedFinalMessage.message : null;
11269
11456
  if (success && finalMessage !== null) {
@@ -11421,54 +11608,335 @@ function createInstanceSocketClient(opts) {
11421
11608
  next.terminate();
11422
11609
  } catch {
11423
11610
  }
11424
- throw err;
11611
+ throw err;
11612
+ }
11613
+ ws = next;
11614
+ startHeartbeat();
11615
+ next.on("message", (data) => {
11616
+ let parsed;
11617
+ try {
11618
+ parsed = JSON.parse(String(data));
11619
+ } catch (err) {
11620
+ log(
11621
+ "warn",
11622
+ `instance socket frame parse failed: ${err instanceof Error ? err.message : String(err)}`
11623
+ );
11624
+ return;
11625
+ }
11626
+ handleFrame(parsed);
11627
+ });
11628
+ next.on("close", (_code, reasonBuf) => {
11629
+ if (next !== ws) return;
11630
+ ws = null;
11631
+ const reason = reasonBuf.toString() || "closed";
11632
+ emitClose(reason);
11633
+ });
11634
+ next.on("error", (err) => {
11635
+ log("warn", `instance socket error: ${err.message}`);
11636
+ });
11637
+ }
11638
+ function close(reason) {
11639
+ stopHeartbeat();
11640
+ if (ws) {
11641
+ try {
11642
+ ws.close(1e3, reason ?? "client closed");
11643
+ } catch {
11644
+ ws.terminate();
11645
+ }
11646
+ }
11647
+ ws = null;
11648
+ }
11649
+ function onFrame(handler) {
11650
+ frameHandlers.add(handler);
11651
+ }
11652
+ function onClose(handler) {
11653
+ closeHandlers.add(handler);
11654
+ }
11655
+ function sendRunEvent(event) {
11656
+ send({ type: "run_event", ...event });
11657
+ }
11658
+ return { connect: connect2, close, onFrame, onClose, sendRunEvent };
11659
+ }
11660
+
11661
+ // src/app/dashboard/runStreamClient.ts
11662
+ import { WebSocket as WebSocket3 } from "ws";
11663
+ var DEFAULT_RECONNECT_DELAYS_MS = [250, 1e3, 2e3, 5e3, 15e3];
11664
+ var DEFAULT_HEARTBEAT_MS2 = 3e4;
11665
+ var DEFAULT_WATCHDOG_MS = 9e4;
11666
+ var DEFAULT_MAX_QUEUE_SIZE = 5e3;
11667
+ function createRunStreamClient(opts) {
11668
+ const log = opts.log ?? (() => {
11669
+ });
11670
+ const now = opts.now ?? (() => Date.now());
11671
+ const reconnectDelays = opts.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS;
11672
+ const heartbeatMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS2;
11673
+ const watchdogMs = opts.watchdogTimeoutMs ?? DEFAULT_WATCHDOG_MS;
11674
+ const maxQueueSize = opts.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
11675
+ const makeWebSocket = opts.makeWebSocket ?? ((url) => new WebSocket3(url));
11676
+ const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
11677
+ const clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
11678
+ let nextSeq = 1;
11679
+ const queue = [];
11680
+ let ws = null;
11681
+ let connectAttempt = 0;
11682
+ let stopped = false;
11683
+ let serverTerminated = false;
11684
+ let heartbeatTimer = null;
11685
+ let watchdogTimer = null;
11686
+ let reconnectTimer = null;
11687
+ let resumeResolved = false;
11688
+ let firstConnect = null;
11689
+ const terminationWaiters = [];
11690
+ function trimQueueUpTo(lastAckedSeq) {
11691
+ while (queue.length > 0 && queue[0].seq <= lastAckedSeq) {
11692
+ queue.shift();
11693
+ }
11694
+ }
11695
+ function trimQueueUpToExclusive(expectedSeq) {
11696
+ while (queue.length > 0 && queue[0].seq < expectedSeq) {
11697
+ queue.shift();
11698
+ }
11699
+ }
11700
+ function clearTimers() {
11701
+ if (heartbeatTimer) {
11702
+ clearTimer(heartbeatTimer);
11703
+ heartbeatTimer = null;
11704
+ }
11705
+ if (watchdogTimer) {
11706
+ clearTimer(watchdogTimer);
11707
+ watchdogTimer = null;
11708
+ }
11709
+ if (reconnectTimer) {
11710
+ clearTimer(reconnectTimer);
11711
+ reconnectTimer = null;
11712
+ }
11713
+ }
11714
+ function startHeartbeat() {
11715
+ if (heartbeatMs <= 0) return;
11716
+ const tick = () => {
11717
+ if (!ws || ws.readyState !== ws.OPEN) return;
11718
+ try {
11719
+ ws.send(JSON.stringify({ type: "ping", ts: now() }));
11720
+ } catch (err) {
11721
+ log(
11722
+ "warn",
11723
+ `run-stream ping failed: ${err instanceof Error ? err.message : String(err)}`
11724
+ );
11725
+ }
11726
+ heartbeatTimer = setTimer(tick, heartbeatMs);
11727
+ heartbeatTimer.unref?.();
11728
+ };
11729
+ heartbeatTimer = setTimer(tick, heartbeatMs);
11730
+ heartbeatTimer.unref?.();
11731
+ }
11732
+ function bumpWatchdog() {
11733
+ if (watchdogMs <= 0) return;
11734
+ if (watchdogTimer) clearTimer(watchdogTimer);
11735
+ watchdogTimer = setTimer(() => {
11736
+ log(
11737
+ "warn",
11738
+ `run-stream watchdog: no server frames for ${watchdogMs}ms \u2014 recycling socket`
11739
+ );
11740
+ try {
11741
+ ws?.terminate();
11742
+ } catch {
11743
+ }
11744
+ }, watchdogMs);
11745
+ watchdogTimer.unref?.();
11746
+ }
11747
+ function flushQueue() {
11748
+ if (!ws || ws.readyState !== ws.OPEN || !resumeResolved) return;
11749
+ for (const frame of queue) {
11750
+ try {
11751
+ ws.send(JSON.stringify(frame));
11752
+ } catch (err) {
11753
+ log(
11754
+ "warn",
11755
+ `run-stream flush stopped: ${err instanceof Error ? err.message : String(err)}`
11756
+ );
11757
+ return;
11758
+ }
11759
+ }
11760
+ }
11761
+ function notifyTerminated() {
11762
+ serverTerminated = true;
11763
+ const waiters = terminationWaiters.splice(0, terminationWaiters.length);
11764
+ for (const w of waiters) {
11765
+ try {
11766
+ w();
11767
+ } catch {
11768
+ }
11769
+ }
11770
+ }
11771
+ function handleServerFrame(parsed) {
11772
+ bumpWatchdog();
11773
+ switch (parsed.type) {
11774
+ case "resume": {
11775
+ resumeResolved = true;
11776
+ trimQueueUpTo(parsed.lastAckedSeq);
11777
+ if (firstConnect) {
11778
+ const f = firstConnect;
11779
+ firstConnect = null;
11780
+ f.resolve();
11781
+ }
11782
+ if (parsed.terminated) {
11783
+ notifyTerminated();
11784
+ try {
11785
+ ws?.close(1e3, "already_terminated");
11786
+ } catch {
11787
+ }
11788
+ return;
11789
+ }
11790
+ flushQueue();
11791
+ return;
11792
+ }
11793
+ case "ack": {
11794
+ trimQueueUpTo(parsed.seq);
11795
+ return;
11796
+ }
11797
+ case "pong":
11798
+ return;
11799
+ case "error": {
11800
+ if (parsed.code === "sequence_gap" && typeof parsed.expected === "number") {
11801
+ trimQueueUpToExclusive(parsed.expected);
11802
+ flushQueue();
11803
+ log(
11804
+ "warn",
11805
+ `run-stream sequence_gap: server expected seq=${parsed.expected}; replayed ${queue.length} frames`
11806
+ );
11807
+ return;
11808
+ }
11809
+ log(
11810
+ "warn",
11811
+ `run-stream server error: ${parsed.code}${parsed.message ? ` ${parsed.message}` : ""}`
11812
+ );
11813
+ return;
11814
+ }
11425
11815
  }
11816
+ }
11817
+ function nextReconnectDelay() {
11818
+ if (reconnectDelays.length === 0) return 0;
11819
+ const idx = Math.min(connectAttempt, reconnectDelays.length - 1);
11820
+ const ms = reconnectDelays[idx] ?? 0;
11821
+ connectAttempt += 1;
11822
+ return ms;
11823
+ }
11824
+ function scheduleReconnect() {
11825
+ if (stopped || serverTerminated) return;
11826
+ const delayMs = nextReconnectDelay();
11827
+ log("info", `run-stream reconnect scheduled in ${delayMs}ms`);
11828
+ reconnectTimer = setTimer(() => {
11829
+ reconnectTimer = null;
11830
+ void openSocket();
11831
+ }, delayMs);
11832
+ reconnectTimer.unref?.();
11833
+ }
11834
+ async function openSocket() {
11835
+ if (stopped || serverTerminated) return;
11836
+ resumeResolved = false;
11837
+ const next = makeWebSocket(opts.wsUrl);
11426
11838
  ws = next;
11427
- startHeartbeat();
11839
+ next.on("open", () => {
11840
+ connectAttempt = 0;
11841
+ startHeartbeat();
11842
+ bumpWatchdog();
11843
+ });
11428
11844
  next.on("message", (data) => {
11429
11845
  let parsed;
11430
11846
  try {
11431
11847
  parsed = JSON.parse(String(data));
11432
- } catch (err) {
11433
- log(
11434
- "warn",
11435
- `instance socket frame parse failed: ${err instanceof Error ? err.message : String(err)}`
11436
- );
11848
+ } catch {
11849
+ log("warn", "run-stream received non-JSON frame");
11437
11850
  return;
11438
11851
  }
11439
- handleFrame(parsed);
11852
+ handleServerFrame(parsed);
11440
11853
  });
11441
- next.on("close", (_code, reasonBuf) => {
11854
+ next.on("close", (code, reasonBuf) => {
11442
11855
  if (next !== ws) return;
11443
11856
  ws = null;
11444
- const reason = reasonBuf.toString() || "closed";
11445
- emitClose(reason);
11857
+ clearTimers();
11858
+ const reason = reasonBuf?.toString?.() || "closed";
11859
+ if (code === 1e3 && reason === "run_terminated") {
11860
+ notifyTerminated();
11861
+ return;
11862
+ }
11863
+ if (firstConnect) {
11864
+ const f = firstConnect;
11865
+ firstConnect = null;
11866
+ f.reject(
11867
+ new Error(`run-stream initial connect failed: ${reason} (${code})`)
11868
+ );
11869
+ return;
11870
+ }
11871
+ scheduleReconnect();
11446
11872
  });
11447
11873
  next.on("error", (err) => {
11448
- log("warn", `instance socket error: ${err.message}`);
11874
+ log(
11875
+ "warn",
11876
+ `run-stream socket error: ${err instanceof Error ? err.message : String(err)}`
11877
+ );
11449
11878
  });
11450
11879
  }
11451
- function close(reason) {
11452
- stopHeartbeat();
11453
- if (ws) {
11454
- try {
11455
- ws.close(1e3, reason ?? "client closed");
11456
- } catch {
11457
- ws.terminate();
11880
+ return {
11881
+ async connect() {
11882
+ if (firstConnect) {
11883
+ throw new Error("run-stream client already connecting");
11884
+ }
11885
+ await new Promise((resolve, reject) => {
11886
+ firstConnect = { resolve, reject };
11887
+ void openSocket();
11888
+ });
11889
+ },
11890
+ sendEvent(input) {
11891
+ const frame = {
11892
+ seq: nextSeq++,
11893
+ ts: input.ts,
11894
+ kind: input.kind,
11895
+ payload: input.payload ?? null
11896
+ };
11897
+ if (queue.length >= maxQueueSize) {
11898
+ log(
11899
+ "warn",
11900
+ `run-stream queue at cap (${maxQueueSize}); dropping oldest unacked frame seq=${queue[0].seq}`
11901
+ );
11902
+ queue.shift();
11903
+ }
11904
+ queue.push(frame);
11905
+ if (ws && ws.readyState === ws.OPEN && resumeResolved) {
11906
+ try {
11907
+ ws.send(JSON.stringify(frame));
11908
+ } catch (err) {
11909
+ log(
11910
+ "warn",
11911
+ `run-stream send failed (will replay): ${err instanceof Error ? err.message : String(err)}`
11912
+ );
11913
+ }
11914
+ }
11915
+ },
11916
+ whenTerminated() {
11917
+ if (serverTerminated) return Promise.resolve();
11918
+ return new Promise((resolve) => {
11919
+ terminationWaiters.push(resolve);
11920
+ });
11921
+ },
11922
+ async close(reason = "client_close") {
11923
+ stopped = true;
11924
+ clearTimers();
11925
+ const current = ws;
11926
+ ws = null;
11927
+ if (current) {
11928
+ try {
11929
+ current.close(1e3, reason);
11930
+ } catch {
11931
+ try {
11932
+ current.terminate();
11933
+ } catch {
11934
+ }
11935
+ }
11458
11936
  }
11937
+ notifyTerminated();
11459
11938
  }
11460
- ws = null;
11461
- }
11462
- function onFrame(handler) {
11463
- frameHandlers.add(handler);
11464
- }
11465
- function onClose(handler) {
11466
- closeHandlers.add(handler);
11467
- }
11468
- function sendRunEvent(event) {
11469
- send({ type: "run_event", ...event });
11470
- }
11471
- return { connect: connect2, close, onFrame, onClose, sendRunEvent };
11939
+ };
11472
11940
  }
11473
11941
 
11474
11942
  // src/app/dashboard/remoteRunExecutor.ts
@@ -11479,6 +11947,8 @@ function parseRunSpec(value) {
11479
11947
  if (typeof prompt !== "string" || prompt.trim().length === 0) return null;
11480
11948
  const env = obj["env"];
11481
11949
  const workflow = obj["workflow"];
11950
+ const callbackWsUrl = obj["callbackWsUrl"];
11951
+ const callbackToken = obj["callbackToken"];
11482
11952
  return {
11483
11953
  prompt,
11484
11954
  sessionId: typeof obj["sessionId"] === "string" && obj["sessionId"].length > 0 ? obj["sessionId"] : void 0,
@@ -11489,7 +11959,9 @@ function parseRunSpec(value) {
11489
11959
  (entry) => typeof entry[1] === "string"
11490
11960
  )
11491
11961
  ) : void 0,
11492
- timeoutSec: typeof obj["timeoutSec"] === "number" && Number.isFinite(obj["timeoutSec"]) ? obj["timeoutSec"] : void 0
11962
+ timeoutSec: typeof obj["timeoutSec"] === "number" && Number.isFinite(obj["timeoutSec"]) ? obj["timeoutSec"] : void 0,
11963
+ callbackWsUrl: typeof callbackWsUrl === "string" && callbackWsUrl.length > 0 ? callbackWsUrl : void 0,
11964
+ callbackToken: typeof callbackToken === "string" && callbackToken.length > 0 ? callbackToken : void 0
11493
11965
  };
11494
11966
  }
11495
11967
  function workflowNameFromRef(ref) {
@@ -11542,112 +12014,165 @@ async function executeRemoteAssignment({
11542
12014
  runExecFn = runExec,
11543
12015
  bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
11544
12016
  now = Date.now,
11545
- abortSignal
12017
+ abortSignal,
12018
+ createRunStreamClientFn = createRunStreamClient,
12019
+ runStreamConnectTimeoutMs = 5e3
11546
12020
  }) {
11547
- let seq = 0;
11548
12021
  let lastTerminalFailureMessage = null;
11549
12022
  let deferredFailedCompletion = null;
12023
+ const spec = parseRunSpec(frame.runSpec);
12024
+ let runStream = null;
12025
+ if (spec?.callbackWsUrl && spec.callbackToken) {
12026
+ const connect2 = createRunStreamClientFn({
12027
+ wsUrl: spec.callbackWsUrl,
12028
+ token: spec.callbackToken,
12029
+ log: (level, message) => log(level, `run-stream[${frame.runId}]: ${message}`),
12030
+ now
12031
+ });
12032
+ const timeoutPromise = new Promise((resolve) => {
12033
+ const t = setTimeout(() => resolve("timeout"), runStreamConnectTimeoutMs);
12034
+ t.unref?.();
12035
+ });
12036
+ try {
12037
+ const result = await Promise.race([
12038
+ connect2.connect().then(() => "connected"),
12039
+ timeoutPromise
12040
+ ]);
12041
+ if (result === "connected") {
12042
+ runStream = connect2;
12043
+ } else {
12044
+ log(
12045
+ "warn",
12046
+ `run-stream[${frame.runId}]: connect timed out after ${runStreamConnectTimeoutMs}ms; falling back to instance-socket relay`
12047
+ );
12048
+ void connect2.close("connect_timeout");
12049
+ }
12050
+ } catch (err) {
12051
+ log(
12052
+ "warn",
12053
+ `run-stream[${frame.runId}]: connect failed (${err instanceof Error ? err.message : String(err)}); falling back to instance-socket relay`
12054
+ );
12055
+ }
12056
+ }
12057
+ let legacySeq = 0;
11550
12058
  const send = (kind, payload, ts = now()) => {
11551
- seq += 1;
11552
12059
  if (kind === "error" && typeof payload === "object" && payload !== null && typeof payload.message === "string") {
11553
12060
  lastTerminalFailureMessage = payload.message;
11554
12061
  }
12062
+ if (runStream) {
12063
+ runStream.sendEvent({ ts, kind, payload });
12064
+ return;
12065
+ }
12066
+ legacySeq += 1;
11555
12067
  client.sendRunEvent({
11556
12068
  runId: frame.runId,
11557
- seq,
12069
+ seq: legacySeq,
11558
12070
  ts,
11559
12071
  kind,
11560
12072
  payload
11561
12073
  });
11562
12074
  };
11563
12075
  send("progress", { message: "assignment received" });
11564
- const spec = parseRunSpec(frame.runSpec);
11565
- if (!spec) {
11566
- send("error", {
11567
- message: "remote assignment missing prompt"
11568
- });
11569
- return;
11570
- }
11571
- const projectDir = spec.projectDir ?? fallbackProjectDir;
11572
- let runtimeConfig;
11573
12076
  try {
11574
- runtimeConfig = bootstrapRuntimeConfigFn({
11575
- projectDir,
11576
- showSetup: false,
11577
- isolationPreset: "minimal",
11578
- workflowOverride: workflowNameFromRef(spec.workflow?.ref)
11579
- });
11580
- } catch (err) {
11581
- send("error", {
11582
- message: err instanceof Error ? err.message : String(err)
11583
- });
11584
- return;
11585
- }
11586
- for (const warning of runtimeConfig.warnings) {
11587
- send("warning", { message: warning });
11588
- }
11589
- let buffered = "";
11590
- const stdout = {
11591
- write(chunk) {
11592
- buffered += chunk;
11593
- let newline = buffered.indexOf("\n");
11594
- while (newline >= 0) {
11595
- const line = buffered.slice(0, newline).trim();
11596
- buffered = buffered.slice(newline + 1);
11597
- if (line.length > 0) {
11598
- try {
11599
- const event = JSON.parse(line);
11600
- const data = event.data;
11601
- if (event.type === "exec.completed" && data?.success === false) {
11602
- deferredFailedCompletion = event;
11603
- continue;
12077
+ if (!spec) {
12078
+ send("error", { message: "remote assignment missing prompt" });
12079
+ return;
12080
+ }
12081
+ const projectDir = spec.projectDir ?? fallbackProjectDir;
12082
+ let runtimeConfig;
12083
+ try {
12084
+ runtimeConfig = bootstrapRuntimeConfigFn({
12085
+ projectDir,
12086
+ showSetup: false,
12087
+ isolationPreset: "minimal",
12088
+ workflowOverride: workflowNameFromRef(spec.workflow?.ref)
12089
+ });
12090
+ } catch (err) {
12091
+ send("error", {
12092
+ message: err instanceof Error ? err.message : String(err)
12093
+ });
12094
+ return;
12095
+ }
12096
+ for (const warning of runtimeConfig.warnings) {
12097
+ send("warning", { message: warning });
12098
+ }
12099
+ let buffered = "";
12100
+ const stdout = {
12101
+ write(chunk) {
12102
+ buffered += chunk;
12103
+ let newline = buffered.indexOf("\n");
12104
+ while (newline >= 0) {
12105
+ const line = buffered.slice(0, newline).trim();
12106
+ buffered = buffered.slice(newline + 1);
12107
+ if (line.length > 0) {
12108
+ try {
12109
+ const event = JSON.parse(line);
12110
+ const data = event.data;
12111
+ if (event.type === "exec.completed" && data?.success === false) {
12112
+ deferredFailedCompletion = event;
12113
+ continue;
12114
+ }
12115
+ send(eventKind(event), eventPayload(event), now());
12116
+ } catch (err) {
12117
+ send("progress", { line });
12118
+ log(
12119
+ "warn",
12120
+ `remote run emitted malformed JSONL: ${err instanceof Error ? err.message : String(err)}`
12121
+ );
11604
12122
  }
11605
- send(eventKind(event), eventPayload(event), now());
11606
- } catch (err) {
11607
- send("progress", { line });
11608
- log(
11609
- "warn",
11610
- `remote run emitted malformed JSONL: ${err instanceof Error ? err.message : String(err)}`
11611
- );
11612
12123
  }
12124
+ newline = buffered.indexOf("\n");
11613
12125
  }
11614
- newline = buffered.indexOf("\n");
12126
+ return true;
11615
12127
  }
11616
- return true;
11617
- }
11618
- };
11619
- const stderr = {
11620
- write(chunk) {
11621
- const text = chunk.trim();
11622
- if (text.length > 0) send("stderr", { text });
11623
- return true;
11624
- }
11625
- };
11626
- try {
11627
- await withEnv(spec.env, async () => {
11628
- const result = await runExecFn({
11629
- prompt: spec.prompt,
11630
- projectDir,
11631
- harness: runtimeConfig.harness,
11632
- athenaSessionId: spec.sessionId ?? `athena-${frame.runId}`,
11633
- isolationConfig: runtimeConfig.isolationConfig,
11634
- pluginMcpConfig: runtimeConfig.pluginMcpConfig,
11635
- workflow: runtimeConfig.workflow,
11636
- workflowPlan: runtimeConfig.workflowPlan,
11637
- json: true,
11638
- verbose: false,
11639
- ephemeral: false,
11640
- timeoutMs: spec.timeoutSec ? spec.timeoutSec * 1e3 : void 0,
11641
- signal: abortSignal,
11642
- stdout,
11643
- stderr
11644
- });
11645
- if (deferredFailedCompletion) {
11646
- const data = typeof deferredFailedCompletion.data === "object" && deferredFailedCompletion.data !== null ? deferredFailedCompletion.data : {};
11647
- send(
11648
- "error",
11649
- {
11650
- ...data,
12128
+ };
12129
+ const stderr = {
12130
+ write(chunk) {
12131
+ const text = chunk.trim();
12132
+ if (text.length > 0) send("stderr", { text });
12133
+ return true;
12134
+ }
12135
+ };
12136
+ try {
12137
+ await withEnv(spec.env, async () => {
12138
+ const result = await runExecFn({
12139
+ prompt: spec.prompt,
12140
+ projectDir,
12141
+ harness: runtimeConfig.harness,
12142
+ athenaSessionId: spec.sessionId ?? `athena-${frame.runId}`,
12143
+ isolationConfig: runtimeConfig.isolationConfig,
12144
+ pluginMcpConfig: runtimeConfig.pluginMcpConfig,
12145
+ workflow: runtimeConfig.workflow,
12146
+ workflowPlan: runtimeConfig.workflowPlan,
12147
+ json: true,
12148
+ verbose: false,
12149
+ ephemeral: false,
12150
+ timeoutMs: spec.timeoutSec ? spec.timeoutSec * 1e3 : void 0,
12151
+ signal: abortSignal,
12152
+ stdout,
12153
+ stderr
12154
+ });
12155
+ if (deferredFailedCompletion) {
12156
+ const data = typeof deferredFailedCompletion.data === "object" && deferredFailedCompletion.data !== null ? deferredFailedCompletion.data : {};
12157
+ send(
12158
+ "error",
12159
+ {
12160
+ ...data,
12161
+ success: result.success,
12162
+ exitCode: result.exitCode,
12163
+ athenaSessionId: result.athenaSessionId,
12164
+ adapterSessionId: result.adapterSessionId,
12165
+ finalMessage: result.finalMessage,
12166
+ tokens: result.tokens,
12167
+ durationMs: result.durationMs,
12168
+ message: result.failure?.message ?? eventPayload(deferredFailedCompletion).message ?? "remote execution failed"
12169
+ },
12170
+ typeof deferredFailedCompletion.ts === "number" ? deferredFailedCompletion.ts : now()
12171
+ );
12172
+ return;
12173
+ }
12174
+ if (result.failure && result.failure.message !== lastTerminalFailureMessage) {
12175
+ send("error", {
11651
12176
  success: result.success,
11652
12177
  exitCode: result.exitCode,
11653
12178
  athenaSessionId: result.athenaSessionId,
@@ -11655,34 +12180,142 @@ async function executeRemoteAssignment({
11655
12180
  finalMessage: result.finalMessage,
11656
12181
  tokens: result.tokens,
11657
12182
  durationMs: result.durationMs,
11658
- message: result.failure?.message ?? eventPayload(deferredFailedCompletion).message ?? "remote execution failed"
11659
- },
11660
- typeof deferredFailedCompletion.ts === "number" ? deferredFailedCompletion.ts : now()
11661
- );
11662
- return;
11663
- }
11664
- if (result.failure && result.failure.message !== lastTerminalFailureMessage) {
11665
- send("error", {
11666
- success: result.success,
11667
- exitCode: result.exitCode,
11668
- athenaSessionId: result.athenaSessionId,
11669
- adapterSessionId: result.adapterSessionId,
11670
- finalMessage: result.finalMessage,
11671
- tokens: result.tokens,
11672
- durationMs: result.durationMs,
11673
- message: result.failure.message
11674
- });
11675
- }
11676
- });
12183
+ message: result.failure.message
12184
+ });
12185
+ }
12186
+ });
12187
+ } catch (err) {
12188
+ send("error", {
12189
+ message: err instanceof Error ? err.message : String(err)
12190
+ });
12191
+ }
12192
+ } finally {
12193
+ if (runStream) {
12194
+ const drainTimeout = new Promise((resolve) => {
12195
+ const t = setTimeout(() => resolve(), 1e4);
12196
+ t.unref?.();
12197
+ });
12198
+ await Promise.race([runStream.whenTerminated(), drainTimeout]);
12199
+ await runStream.close("done");
12200
+ }
12201
+ }
12202
+ }
12203
+
12204
+ // src/infra/config/attachmentMirror.ts
12205
+ import crypto3 from "crypto";
12206
+ import fs20 from "fs";
12207
+ import os11 from "os";
12208
+ import path18 from "path";
12209
+ function attachmentMirrorPath(env = process.env) {
12210
+ const home = env["HOME"] ?? os11.homedir();
12211
+ return path18.join(home, ".config", "athena", "attachments.json");
12212
+ }
12213
+ function readAttachmentMirror(env = process.env) {
12214
+ const file = attachmentMirrorPath(env);
12215
+ let raw;
12216
+ try {
12217
+ raw = fs20.readFileSync(file, "utf-8");
11677
12218
  } catch (err) {
11678
- send("error", {
11679
- message: err instanceof Error ? err.message : String(err)
11680
- });
12219
+ if (err.code === "ENOENT") return null;
12220
+ throw err;
12221
+ }
12222
+ let parsed;
12223
+ try {
12224
+ parsed = JSON.parse(raw);
12225
+ } catch (err) {
12226
+ throw new Error(
12227
+ `attachment mirror ${file} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`
12228
+ );
12229
+ }
12230
+ try {
12231
+ return parseAttachmentMirror(parsed);
12232
+ } catch (err) {
12233
+ throw new Error(
12234
+ `attachment mirror ${file} is invalid: ${err instanceof Error ? err.message : String(err)}`
12235
+ );
12236
+ }
12237
+ }
12238
+ function writeAttachmentMirror(mirror, env = process.env) {
12239
+ const validated = parseAttachmentMirror(mirror);
12240
+ const file = attachmentMirrorPath(env);
12241
+ const dir = path18.dirname(file);
12242
+ fs20.mkdirSync(dir, { recursive: true, mode: 448 });
12243
+ const tmp = `${file}.${process.pid}.${crypto3.randomBytes(4).toString("hex")}.tmp`;
12244
+ const fd = fs20.openSync(tmp, "w", 384);
12245
+ try {
12246
+ fs20.writeSync(fd, JSON.stringify(validated, null, 2) + "\n");
12247
+ fs20.fsyncSync(fd);
12248
+ } finally {
12249
+ fs20.closeSync(fd);
12250
+ }
12251
+ try {
12252
+ fs20.renameSync(tmp, file);
12253
+ } catch (err) {
12254
+ try {
12255
+ fs20.unlinkSync(tmp);
12256
+ } catch {
12257
+ }
12258
+ throw err;
12259
+ }
12260
+ if (process.platform !== "win32") {
12261
+ try {
12262
+ fs20.chmodSync(dir, 448);
12263
+ fs20.chmodSync(file, 384);
12264
+ } catch {
12265
+ }
12266
+ }
12267
+ }
12268
+ function removeAttachmentMirror(env = process.env) {
12269
+ const file = attachmentMirrorPath(env);
12270
+ try {
12271
+ fs20.unlinkSync(file);
12272
+ } catch (err) {
12273
+ if (err.code !== "ENOENT") throw err;
12274
+ }
12275
+ }
12276
+ function parseAttachmentMirror(raw) {
12277
+ if (typeof raw !== "object" || raw === null) {
12278
+ throw new Error("root must be an object");
12279
+ }
12280
+ const obj = raw;
12281
+ if (typeof obj["instanceId"] !== "string" || obj["instanceId"].length === 0) {
12282
+ throw new Error("instanceId must be a non-empty string");
12283
+ }
12284
+ if (typeof obj["fetchedAt"] !== "number") {
12285
+ throw new Error("fetchedAt must be a number");
11681
12286
  }
12287
+ if (!Array.isArray(obj["attachments"])) {
12288
+ throw new Error("attachments must be an array");
12289
+ }
12290
+ const attachments = obj["attachments"].map((entry, idx) => {
12291
+ if (typeof entry !== "object" || entry === null) {
12292
+ throw new Error(`attachments[${idx}] must be an object`);
12293
+ }
12294
+ const e = entry;
12295
+ if (typeof e["runnerId"] !== "string" || e["runnerId"].length === 0) {
12296
+ throw new Error(
12297
+ `attachments[${idx}].runnerId must be a non-empty string`
12298
+ );
12299
+ }
12300
+ const out = { runnerId: e["runnerId"] };
12301
+ if (typeof e["name"] === "string") out.name = e["name"];
12302
+ if (typeof e["executionTarget"] === "string") {
12303
+ out.executionTarget = e["executionTarget"];
12304
+ }
12305
+ if (typeof e["remoteInstanceId"] === "string") {
12306
+ out.remoteInstanceId = e["remoteInstanceId"];
12307
+ }
12308
+ return out;
12309
+ });
12310
+ return {
12311
+ instanceId: obj["instanceId"],
12312
+ fetchedAt: obj["fetchedAt"],
12313
+ attachments
12314
+ };
11682
12315
  }
11683
12316
 
11684
12317
  // src/app/dashboard/runtimeDaemon.ts
11685
- var DEFAULT_RECONNECT_DELAYS_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
12318
+ var DEFAULT_RECONNECT_DELAYS_MS2 = [1e3, 2e3, 5e3, 1e4, 3e4];
11686
12319
  var DEFAULT_MAX_CONCURRENT_RUNS = 1;
11687
12320
  var DEFAULT_REFRESH_LEAD_SEC = 60;
11688
12321
  var DEFAULT_REFRESH_FAILURE_LIMIT = 5;
@@ -11708,7 +12341,7 @@ async function runDashboardRuntimeDaemon(options = {}) {
11708
12341
  const projectDir = options.projectDir ?? process.cwd();
11709
12342
  const log = options.log ?? (() => {
11710
12343
  });
11711
- const reconnectDelays = options.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS;
12344
+ const reconnectDelays = options.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS2;
11712
12345
  const maxConcurrentRuns = options.maxConcurrentRuns ?? DEFAULT_MAX_CONCURRENT_RUNS;
11713
12346
  const refreshLeadSec = options.refreshLeadSec ?? DEFAULT_REFRESH_LEAD_SEC;
11714
12347
  const refreshFailureLimit = options.refreshFailureLimit ?? DEFAULT_REFRESH_FAILURE_LIMIT;
@@ -11716,6 +12349,7 @@ async function runDashboardRuntimeDaemon(options = {}) {
11716
12349
  const refreshCooldownMs = options.refreshCooldownMs ?? DEFAULT_REFRESH_COOLDOWN_MS;
11717
12350
  const runHistoryLimit = options.runHistoryLimit ?? DEFAULT_RUN_HISTORY_LIMIT;
11718
12351
  const now = options.now ?? (() => Date.now());
12352
+ const writeMirror = options.writeMirror ?? writeAttachmentMirror;
11719
12353
  const startedAt = now();
11720
12354
  let stopped = false;
11721
12355
  let reconnectAttempt = 0;
@@ -11849,6 +12483,26 @@ async function runDashboardRuntimeDaemon(options = {}) {
11849
12483
  });
11850
12484
  next.onFrame((frame) => {
11851
12485
  lastFrameAt = now();
12486
+ if (frame.type === "attachments.changed") {
12487
+ try {
12488
+ writeMirror({
12489
+ instanceId: token.instanceId,
12490
+ fetchedAt: now(),
12491
+ attachments: frame.attachments.map((a) => ({
12492
+ runnerId: a.runnerId,
12493
+ ...a.name !== void 0 ? { name: a.name } : {},
12494
+ ...a.executionTarget !== void 0 ? { executionTarget: a.executionTarget } : {},
12495
+ ...a.remoteInstanceId !== void 0 ? { remoteInstanceId: a.remoteInstanceId } : {}
12496
+ }))
12497
+ });
12498
+ } catch (err) {
12499
+ log(
12500
+ "warn",
12501
+ `runtime daemon: failed to write attachment mirror: ${err instanceof Error ? err.message : String(err)}`
12502
+ );
12503
+ }
12504
+ return;
12505
+ }
11852
12506
  if (frame.type === "cancel") {
11853
12507
  const entry = active.get(frame.runId);
11854
12508
  if (entry) {
@@ -11973,27 +12627,27 @@ async function runDashboardRuntimeDaemon(options = {}) {
11973
12627
  }
11974
12628
 
11975
12629
  // src/infra/daemon/stateDir.ts
11976
- import fs20 from "fs";
11977
- import os11 from "os";
11978
- import path18 from "path";
12630
+ import fs21 from "fs";
12631
+ import os12 from "os";
12632
+ import path19 from "path";
11979
12633
  function daemonStatePaths(env = process.env) {
11980
12634
  const xdg = env["XDG_STATE_HOME"];
11981
- const home = env["HOME"] ?? os11.homedir();
11982
- const base = xdg && xdg.length > 0 ? xdg : path18.join(home, ".local", "state");
11983
- const dir = path18.join(base, "drisp");
12635
+ const home = env["HOME"] ?? os12.homedir();
12636
+ const base = xdg && xdg.length > 0 ? xdg : path19.join(home, ".local", "state");
12637
+ const dir = path19.join(base, "drisp");
11984
12638
  return {
11985
12639
  dir,
11986
- pidPath: path18.join(dir, "dashboard-daemon.pid"),
11987
- logPath: path18.join(dir, "dashboard-daemon.log"),
11988
- socketPath: path18.join(dir, "dashboard-daemon.sock")
12640
+ pidPath: path19.join(dir, "dashboard-daemon.pid"),
12641
+ logPath: path19.join(dir, "dashboard-daemon.log"),
12642
+ socketPath: path19.join(dir, "dashboard-daemon.sock")
11989
12643
  };
11990
12644
  }
11991
12645
  function ensureDaemonStateDir(env = process.env) {
11992
12646
  const paths = daemonStatePaths(env);
11993
- fs20.mkdirSync(paths.dir, { recursive: true, mode: 448 });
12647
+ fs21.mkdirSync(paths.dir, { recursive: true, mode: 448 });
11994
12648
  if (process.platform !== "win32") {
11995
12649
  try {
11996
- fs20.chmodSync(paths.dir, 448);
12650
+ fs21.chmodSync(paths.dir, 448);
11997
12651
  } catch {
11998
12652
  }
11999
12653
  }
@@ -12001,18 +12655,18 @@ function ensureDaemonStateDir(env = process.env) {
12001
12655
  }
12002
12656
 
12003
12657
  // src/infra/daemon/pidLock.ts
12004
- import fs21 from "fs";
12658
+ import fs22 from "fs";
12005
12659
  function acquirePidLock(pidPath) {
12006
12660
  const ownPid = process.pid;
12007
12661
  for (let attempt = 0; attempt < 2; attempt += 1) {
12008
12662
  try {
12009
- const fd = fs21.openSync(pidPath, "wx", 384);
12663
+ const fd = fs22.openSync(pidPath, "wx", 384);
12010
12664
  try {
12011
- fs21.writeSync(fd, `${ownPid}
12665
+ fs22.writeSync(fd, `${ownPid}
12012
12666
  `);
12013
- fs21.fsyncSync(fd);
12667
+ fs22.fsyncSync(fd);
12014
12668
  } finally {
12015
- fs21.closeSync(fd);
12669
+ fs22.closeSync(fd);
12016
12670
  }
12017
12671
  return makeHandle(pidPath, ownPid);
12018
12672
  } catch (err) {
@@ -12026,7 +12680,7 @@ function acquirePidLock(pidPath) {
12026
12680
  }
12027
12681
  if (existing.state === "stale") {
12028
12682
  try {
12029
- fs21.unlinkSync(pidPath);
12683
+ fs22.unlinkSync(pidPath);
12030
12684
  } catch (err) {
12031
12685
  if (err.code !== "ENOENT") throw err;
12032
12686
  }
@@ -12040,7 +12694,7 @@ function acquirePidLock(pidPath) {
12040
12694
  function readPidLock(pidPath) {
12041
12695
  let raw;
12042
12696
  try {
12043
- raw = fs21.readFileSync(pidPath, "utf-8");
12697
+ raw = fs22.readFileSync(pidPath, "utf-8");
12044
12698
  } catch (err) {
12045
12699
  if (err.code === "ENOENT") {
12046
12700
  return { state: "absent" };
@@ -12064,9 +12718,9 @@ function makeHandle(pidPath, pid) {
12064
12718
  if (released) return;
12065
12719
  released = true;
12066
12720
  try {
12067
- const raw = fs21.readFileSync(pidPath, "utf-8").trim();
12721
+ const raw = fs22.readFileSync(pidPath, "utf-8").trim();
12068
12722
  if (raw === String(pid)) {
12069
- fs21.unlinkSync(pidPath);
12723
+ fs22.unlinkSync(pidPath);
12070
12724
  }
12071
12725
  } catch (err) {
12072
12726
  if (err.code !== "ENOENT") {
@@ -12092,8 +12746,37 @@ function isProcessAlive(pid) {
12092
12746
  }
12093
12747
 
12094
12748
  // src/infra/daemon/udsIpc.ts
12095
- import fs22 from "fs";
12749
+ import fs23 from "fs";
12096
12750
  import net2 from "net";
12751
+
12752
+ // src/infra/daemon/udsFrameCodec.ts
12753
+ var DEFAULT_MAX_FRAME_BYTES = 1e6;
12754
+ function tryReadFrame(buffer, maxBodyBytes = DEFAULT_MAX_FRAME_BYTES) {
12755
+ const newlineIdx = buffer.indexOf(10);
12756
+ if (newlineIdx < 0) return null;
12757
+ const lenStr = buffer.subarray(0, newlineIdx).toString("utf-8");
12758
+ const len = Number.parseInt(lenStr, 10);
12759
+ if (!Number.isFinite(len) || len < 0 || len > maxBodyBytes) {
12760
+ return {
12761
+ ok: false,
12762
+ error: `uds bad framing header: ${JSON.stringify(lenStr.slice(0, 32))}`,
12763
+ rest: buffer.subarray(newlineIdx + 1)
12764
+ };
12765
+ }
12766
+ if (buffer.length < newlineIdx + 1 + len) return null;
12767
+ const body = buffer.subarray(newlineIdx + 1, newlineIdx + 1 + len);
12768
+ const rest = buffer.subarray(newlineIdx + 1 + len);
12769
+ return { ok: true, body, rest };
12770
+ }
12771
+ function writeFrame(socket, value) {
12772
+ const body = JSON.stringify(value);
12773
+ const buf = Buffer.from(body, "utf-8");
12774
+ socket.write(`${buf.length}
12775
+ `);
12776
+ socket.write(buf);
12777
+ }
12778
+
12779
+ // src/infra/daemon/udsIpc.ts
12097
12780
  async function startUdsServer(socketPath, handler, log) {
12098
12781
  await unlinkStaleSocket(socketPath);
12099
12782
  const server = net2.createServer((socket) => {
@@ -12108,7 +12791,7 @@ async function startUdsServer(socketPath, handler, log) {
12108
12791
  });
12109
12792
  if (process.platform !== "win32") {
12110
12793
  try {
12111
- fs22.chmodSync(socketPath, 384);
12794
+ fs23.chmodSync(socketPath, 384);
12112
12795
  } catch {
12113
12796
  }
12114
12797
  }
@@ -12118,7 +12801,7 @@ async function startUdsServer(socketPath, handler, log) {
12118
12801
  server.close(() => resolve());
12119
12802
  });
12120
12803
  try {
12121
- fs22.unlinkSync(socketPath);
12804
+ fs23.unlinkSync(socketPath);
12122
12805
  } catch (err) {
12123
12806
  if (err.code !== "ENOENT") {
12124
12807
  }
@@ -12142,21 +12825,15 @@ async function handleConnection(socket, handler, log) {
12142
12825
  });
12143
12826
  async function drain() {
12144
12827
  for (; ; ) {
12145
- const newlineIdx = buffer.indexOf(10);
12146
- if (newlineIdx < 0) return;
12147
- const lenStr = buffer.subarray(0, newlineIdx).toString("utf-8");
12148
- const len = Number.parseInt(lenStr, 10);
12149
- if (!Number.isFinite(len) || len < 0 || len > 1e6) {
12150
- log?.(
12151
- "warn",
12152
- `uds bad framing header: ${JSON.stringify(lenStr.slice(0, 32))}`
12153
- );
12828
+ const frame = tryReadFrame(buffer);
12829
+ if (frame === null) return;
12830
+ if (!frame.ok) {
12831
+ log?.("warn", frame.error);
12154
12832
  socket.destroy();
12155
12833
  return;
12156
12834
  }
12157
- if (buffer.length < newlineIdx + 1 + len) return;
12158
- const body = buffer.subarray(newlineIdx + 1, newlineIdx + 1 + len);
12159
- buffer = buffer.subarray(newlineIdx + 1 + len);
12835
+ const { body } = frame;
12836
+ buffer = frame.rest;
12160
12837
  let parsed;
12161
12838
  try {
12162
12839
  parsed = JSON.parse(body.toString("utf-8"));
@@ -12178,13 +12855,6 @@ async function handleConnection(socket, handler, log) {
12178
12855
  }
12179
12856
  }
12180
12857
  }
12181
- function writeFrame(socket, response) {
12182
- const body = JSON.stringify(response);
12183
- const buf = Buffer.from(body, "utf-8");
12184
- socket.write(`${buf.length}
12185
- `);
12186
- socket.write(buf);
12187
- }
12188
12858
  async function sendUdsRequest(socketPath, request, options = {}) {
12189
12859
  const timeoutMs = options.timeoutMs ?? 5e3;
12190
12860
  return await new Promise((resolve, reject) => {
@@ -12209,30 +12879,20 @@ async function sendUdsRequest(socketPath, request, options = {}) {
12209
12879
  action();
12210
12880
  };
12211
12881
  socket.on("connect", () => {
12212
- const body = JSON.stringify(request);
12213
- const buf = Buffer.from(body, "utf-8");
12214
- socket.write(`${buf.length}
12215
- `);
12216
- socket.write(buf);
12882
+ writeFrame(socket, request);
12217
12883
  });
12218
12884
  socket.on("data", (chunk) => {
12219
12885
  const chunkBuf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
12220
12886
  buffer = Buffer.concat([buffer, chunkBuf]);
12221
- const newlineIdx = buffer.indexOf(10);
12222
- if (newlineIdx < 0) return;
12223
- const len = Number.parseInt(
12224
- buffer.subarray(0, newlineIdx).toString("utf-8"),
12225
- 10
12226
- );
12227
- if (!Number.isFinite(len)) {
12887
+ const frame = tryReadFrame(buffer);
12888
+ if (frame === null) return;
12889
+ if (!frame.ok) {
12228
12890
  finish(() => reject(new Error("uds reply malformed")));
12229
12891
  return;
12230
12892
  }
12231
- if (buffer.length < newlineIdx + 1 + len) return;
12232
- const body = buffer.subarray(newlineIdx + 1, newlineIdx + 1 + len).toString("utf-8");
12233
12893
  let parsed;
12234
12894
  try {
12235
- parsed = JSON.parse(body);
12895
+ parsed = JSON.parse(frame.body.toString("utf-8"));
12236
12896
  } catch (err) {
12237
12897
  finish(
12238
12898
  () => reject(
@@ -12259,7 +12919,7 @@ async function sendUdsRequest(socketPath, request, options = {}) {
12259
12919
  async function unlinkStaleSocket(socketPath) {
12260
12920
  let stat;
12261
12921
  try {
12262
- stat = fs22.statSync(socketPath);
12922
+ stat = fs23.statSync(socketPath);
12263
12923
  } catch (err) {
12264
12924
  if (err.code === "ENOENT") return;
12265
12925
  throw err;
@@ -12286,7 +12946,7 @@ async function unlinkStaleSocket(socketPath) {
12286
12946
  `uds path ${socketPath} is in use by another process; aborting`
12287
12947
  );
12288
12948
  }
12289
- fs22.unlinkSync(socketPath);
12949
+ fs23.unlinkSync(socketPath);
12290
12950
  }
12291
12951
 
12292
12952
  export {
@@ -12301,7 +12961,8 @@ export {
12301
12961
  extractPermissionSnapshot,
12302
12962
  isSubagentTool,
12303
12963
  createFeedMapper,
12304
- handleEvent,
12964
+ ingestRuntimeEvent,
12965
+ ingestRuntimeDecision,
12305
12966
  generateId,
12306
12967
  createSessionStore,
12307
12968
  sessionsDir,
@@ -12350,6 +13011,9 @@ export {
12350
13011
  bootstrapRuntimeConfig,
12351
13012
  EXEC_EXIT_CODE,
12352
13013
  runExec,
13014
+ readAttachmentMirror,
13015
+ writeAttachmentMirror,
13016
+ removeAttachmentMirror,
12353
13017
  runDashboardRuntimeDaemon,
12354
13018
  daemonStatePaths,
12355
13019
  ensureDaemonStateDir,
@@ -12358,4 +13022,4 @@ export {
12358
13022
  startUdsServer,
12359
13023
  sendUdsRequest
12360
13024
  };
12361
- //# sourceMappingURL=chunk-WHELLVBL.js.map
13025
+ //# sourceMappingURL=chunk-PJUDHH4R.js.map