@drisp/cli 0.4.2 → 0.4.4

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;
8054
+ }
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);
7942
8096
  }
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}`;
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,34 +9205,30 @@ 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
9216
  return results;
9190
9217
  }
9191
9218
  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);
9219
+ const consumed = decisionCorrelation.consumeForDecision(requestId);
9220
+ if (!consumed) return null;
9221
+ const { parentEventId, originalKind } = consumed;
9197
9222
  function makeDecisionEvent(kind, data) {
9198
- const s = nextSeq();
9223
+ const s = runLifecycle.allocateSeq();
9224
+ const runId = runLifecycle.getRunId();
9225
+ const session = runLifecycle.getSession();
9199
9226
  const fe = {
9200
- event_id: `${getRunId()}:E${s}`,
9227
+ event_id: `${runId}:E${s}`,
9201
9228
  seq: s,
9202
9229
  ts: Date.now(),
9203
- session_id: currentSession?.session_id ?? "unknown",
9204
- run_id: getRunId(),
9230
+ session_id: session?.session_id ?? "unknown",
9231
+ run_id: runId,
9205
9232
  kind,
9206
9233
  level: "info",
9207
9234
  actor_id: decision.source === "user" ? "user" : "system",
@@ -9233,41 +9260,205 @@ function createFeedMapper(bootstrap) {
9233
9260
  }
9234
9261
  return makeDecisionEvent("permission.decision", data);
9235
9262
  }
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" };
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 };
9390
+ }
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
+ }
9409
+ };
9410
+ }
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) {
@@ -11471,6 +11658,292 @@ function createInstanceSocketClient(opts) {
11471
11658
  return { connect: connect2, close, onFrame, onClose, sendRunEvent };
11472
11659
  }
11473
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
+ var TERMINAL_KINDS = /* @__PURE__ */ new Set(["completion", "error"]);
11668
+ function createRunStreamClient(opts) {
11669
+ const log = opts.log ?? (() => {
11670
+ });
11671
+ const now = opts.now ?? (() => Date.now());
11672
+ const reconnectDelays = opts.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS;
11673
+ const heartbeatMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS2;
11674
+ const watchdogMs = opts.watchdogTimeoutMs ?? DEFAULT_WATCHDOG_MS;
11675
+ const maxQueueSize = opts.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
11676
+ const makeWebSocket = opts.makeWebSocket ?? ((url) => new WebSocket3(url));
11677
+ const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
11678
+ const clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
11679
+ let nextSeq = 1;
11680
+ const queue = [];
11681
+ let ws = null;
11682
+ let connectAttempt = 0;
11683
+ let stopped = false;
11684
+ let serverTerminated = false;
11685
+ let heartbeatTimer = null;
11686
+ let watchdogTimer = null;
11687
+ let reconnectTimer = null;
11688
+ let resumeResolved = false;
11689
+ let firstConnect = null;
11690
+ const terminationWaiters = [];
11691
+ function trimQueueUpTo(lastAckedSeq) {
11692
+ while (queue.length > 0 && queue[0].seq <= lastAckedSeq) {
11693
+ queue.shift();
11694
+ }
11695
+ }
11696
+ function trimQueueUpToExclusive(expectedSeq) {
11697
+ while (queue.length > 0 && queue[0].seq < expectedSeq) {
11698
+ queue.shift();
11699
+ }
11700
+ }
11701
+ function clearTimers() {
11702
+ if (heartbeatTimer) {
11703
+ clearTimer(heartbeatTimer);
11704
+ heartbeatTimer = null;
11705
+ }
11706
+ if (watchdogTimer) {
11707
+ clearTimer(watchdogTimer);
11708
+ watchdogTimer = null;
11709
+ }
11710
+ if (reconnectTimer) {
11711
+ clearTimer(reconnectTimer);
11712
+ reconnectTimer = null;
11713
+ }
11714
+ }
11715
+ function startHeartbeat() {
11716
+ if (heartbeatMs <= 0) return;
11717
+ const tick = () => {
11718
+ if (!ws || ws.readyState !== ws.OPEN) return;
11719
+ try {
11720
+ ws.send(JSON.stringify({ type: "ping", ts: now() }));
11721
+ } catch (err) {
11722
+ log(
11723
+ "warn",
11724
+ `run-stream ping failed: ${err instanceof Error ? err.message : String(err)}`
11725
+ );
11726
+ }
11727
+ heartbeatTimer = setTimer(tick, heartbeatMs);
11728
+ heartbeatTimer.unref?.();
11729
+ };
11730
+ heartbeatTimer = setTimer(tick, heartbeatMs);
11731
+ heartbeatTimer.unref?.();
11732
+ }
11733
+ function bumpWatchdog() {
11734
+ if (watchdogMs <= 0) return;
11735
+ if (watchdogTimer) clearTimer(watchdogTimer);
11736
+ watchdogTimer = setTimer(() => {
11737
+ log(
11738
+ "warn",
11739
+ `run-stream watchdog: no server frames for ${watchdogMs}ms \u2014 recycling socket`
11740
+ );
11741
+ try {
11742
+ ws?.terminate();
11743
+ } catch {
11744
+ }
11745
+ }, watchdogMs);
11746
+ watchdogTimer.unref?.();
11747
+ }
11748
+ function flushQueue() {
11749
+ if (!ws || ws.readyState !== ws.OPEN || !resumeResolved) return;
11750
+ for (const frame of queue) {
11751
+ try {
11752
+ ws.send(JSON.stringify(frame));
11753
+ } catch (err) {
11754
+ log(
11755
+ "warn",
11756
+ `run-stream flush stopped: ${err instanceof Error ? err.message : String(err)}`
11757
+ );
11758
+ return;
11759
+ }
11760
+ }
11761
+ }
11762
+ function notifyTerminated() {
11763
+ serverTerminated = true;
11764
+ const waiters = terminationWaiters.splice(0, terminationWaiters.length);
11765
+ for (const w of waiters) {
11766
+ try {
11767
+ w();
11768
+ } catch {
11769
+ }
11770
+ }
11771
+ }
11772
+ function handleServerFrame(parsed) {
11773
+ bumpWatchdog();
11774
+ switch (parsed.type) {
11775
+ case "resume": {
11776
+ resumeResolved = true;
11777
+ trimQueueUpTo(parsed.lastAckedSeq);
11778
+ if (firstConnect) {
11779
+ const f = firstConnect;
11780
+ firstConnect = null;
11781
+ f.resolve();
11782
+ }
11783
+ if (parsed.terminated) {
11784
+ notifyTerminated();
11785
+ try {
11786
+ ws?.close(1e3, "already_terminated");
11787
+ } catch {
11788
+ }
11789
+ return;
11790
+ }
11791
+ flushQueue();
11792
+ return;
11793
+ }
11794
+ case "ack": {
11795
+ trimQueueUpTo(parsed.seq);
11796
+ return;
11797
+ }
11798
+ case "pong":
11799
+ return;
11800
+ case "error": {
11801
+ if (parsed.code === "sequence_gap" && typeof parsed.expected === "number") {
11802
+ trimQueueUpToExclusive(parsed.expected);
11803
+ flushQueue();
11804
+ log(
11805
+ "warn",
11806
+ `run-stream sequence_gap: server expected seq=${parsed.expected}; replayed ${queue.length} frames`
11807
+ );
11808
+ return;
11809
+ }
11810
+ log(
11811
+ "warn",
11812
+ `run-stream server error: ${parsed.code}${parsed.message ? ` ${parsed.message}` : ""}`
11813
+ );
11814
+ return;
11815
+ }
11816
+ }
11817
+ }
11818
+ function nextReconnectDelay() {
11819
+ if (reconnectDelays.length === 0) return 0;
11820
+ const idx = Math.min(connectAttempt, reconnectDelays.length - 1);
11821
+ const ms = reconnectDelays[idx] ?? 0;
11822
+ connectAttempt += 1;
11823
+ return ms;
11824
+ }
11825
+ function scheduleReconnect() {
11826
+ if (stopped || serverTerminated) return;
11827
+ const delayMs = nextReconnectDelay();
11828
+ log("info", `run-stream reconnect scheduled in ${delayMs}ms`);
11829
+ reconnectTimer = setTimer(() => {
11830
+ reconnectTimer = null;
11831
+ void openSocket();
11832
+ }, delayMs);
11833
+ reconnectTimer.unref?.();
11834
+ }
11835
+ async function openSocket() {
11836
+ if (stopped || serverTerminated) return;
11837
+ resumeResolved = false;
11838
+ const next = makeWebSocket(opts.wsUrl);
11839
+ ws = next;
11840
+ next.on("open", () => {
11841
+ connectAttempt = 0;
11842
+ startHeartbeat();
11843
+ bumpWatchdog();
11844
+ });
11845
+ next.on("message", (data) => {
11846
+ let parsed;
11847
+ try {
11848
+ parsed = JSON.parse(String(data));
11849
+ } catch {
11850
+ log("warn", "run-stream received non-JSON frame");
11851
+ return;
11852
+ }
11853
+ handleServerFrame(parsed);
11854
+ });
11855
+ next.on("close", (code, reasonBuf) => {
11856
+ if (next !== ws) return;
11857
+ ws = null;
11858
+ clearTimers();
11859
+ const reason = reasonBuf?.toString?.() || "closed";
11860
+ if (code === 1e3 && reason === "run_terminated") {
11861
+ notifyTerminated();
11862
+ return;
11863
+ }
11864
+ if (firstConnect) {
11865
+ const f = firstConnect;
11866
+ firstConnect = null;
11867
+ f.reject(
11868
+ new Error(`run-stream initial connect failed: ${reason} (${code})`)
11869
+ );
11870
+ return;
11871
+ }
11872
+ scheduleReconnect();
11873
+ });
11874
+ next.on("error", (err) => {
11875
+ log(
11876
+ "warn",
11877
+ `run-stream socket error: ${err instanceof Error ? err.message : String(err)}`
11878
+ );
11879
+ });
11880
+ }
11881
+ return {
11882
+ async connect() {
11883
+ if (firstConnect) {
11884
+ throw new Error("run-stream client already connecting");
11885
+ }
11886
+ await new Promise((resolve, reject) => {
11887
+ firstConnect = { resolve, reject };
11888
+ void openSocket();
11889
+ });
11890
+ },
11891
+ sendEvent(input) {
11892
+ const seq = nextSeq++;
11893
+ const frame = {
11894
+ seq,
11895
+ ts: input.ts,
11896
+ kind: input.kind,
11897
+ payload: input.payload ?? null
11898
+ };
11899
+ if (queue.length >= maxQueueSize) {
11900
+ log(
11901
+ "warn",
11902
+ `run-stream queue at cap (${maxQueueSize}); dropping oldest unacked frame seq=${queue[0].seq}`
11903
+ );
11904
+ queue.shift();
11905
+ }
11906
+ queue.push(frame);
11907
+ if (ws && ws.readyState === ws.OPEN && resumeResolved) {
11908
+ try {
11909
+ ws.send(JSON.stringify(frame));
11910
+ } catch (err) {
11911
+ log(
11912
+ "warn",
11913
+ `run-stream send failed (will replay): ${err instanceof Error ? err.message : String(err)}`
11914
+ );
11915
+ }
11916
+ }
11917
+ if (TERMINAL_KINDS.has(frame.kind)) {
11918
+ }
11919
+ return seq;
11920
+ },
11921
+ whenTerminated() {
11922
+ if (serverTerminated) return Promise.resolve();
11923
+ return new Promise((resolve) => {
11924
+ terminationWaiters.push(resolve);
11925
+ });
11926
+ },
11927
+ async close(reason = "client_close") {
11928
+ stopped = true;
11929
+ clearTimers();
11930
+ const current = ws;
11931
+ ws = null;
11932
+ if (current) {
11933
+ try {
11934
+ current.close(1e3, reason);
11935
+ } catch {
11936
+ try {
11937
+ current.terminate();
11938
+ } catch {
11939
+ }
11940
+ }
11941
+ }
11942
+ notifyTerminated();
11943
+ }
11944
+ };
11945
+ }
11946
+
11474
11947
  // src/app/dashboard/remoteRunExecutor.ts
11475
11948
  function parseRunSpec(value) {
11476
11949
  if (typeof value !== "object" || value === null) return null;
@@ -11479,6 +11952,8 @@ function parseRunSpec(value) {
11479
11952
  if (typeof prompt !== "string" || prompt.trim().length === 0) return null;
11480
11953
  const env = obj["env"];
11481
11954
  const workflow = obj["workflow"];
11955
+ const callbackWsUrl = obj["callbackWsUrl"];
11956
+ const callbackToken = obj["callbackToken"];
11482
11957
  return {
11483
11958
  prompt,
11484
11959
  sessionId: typeof obj["sessionId"] === "string" && obj["sessionId"].length > 0 ? obj["sessionId"] : void 0,
@@ -11489,7 +11964,9 @@ function parseRunSpec(value) {
11489
11964
  (entry) => typeof entry[1] === "string"
11490
11965
  )
11491
11966
  ) : void 0,
11492
- timeoutSec: typeof obj["timeoutSec"] === "number" && Number.isFinite(obj["timeoutSec"]) ? obj["timeoutSec"] : void 0
11967
+ timeoutSec: typeof obj["timeoutSec"] === "number" && Number.isFinite(obj["timeoutSec"]) ? obj["timeoutSec"] : void 0,
11968
+ callbackWsUrl: typeof callbackWsUrl === "string" && callbackWsUrl.length > 0 ? callbackWsUrl : void 0,
11969
+ callbackToken: typeof callbackToken === "string" && callbackToken.length > 0 ? callbackToken : void 0
11493
11970
  };
11494
11971
  }
11495
11972
  function workflowNameFromRef(ref) {
@@ -11542,30 +12019,80 @@ async function executeRemoteAssignment({
11542
12019
  runExecFn = runExec,
11543
12020
  bootstrapRuntimeConfigFn = bootstrapRuntimeConfig,
11544
12021
  now = Date.now,
11545
- abortSignal
12022
+ abortSignal,
12023
+ createRunStreamClientFn = createRunStreamClient,
12024
+ runStreamConnectTimeoutMs = 5e3
11546
12025
  }) {
11547
- let seq = 0;
11548
12026
  let lastTerminalFailureMessage = null;
11549
12027
  let deferredFailedCompletion = null;
12028
+ const spec = parseRunSpec(frame.runSpec);
12029
+ let runStream = null;
12030
+ if (spec?.callbackWsUrl && spec.callbackToken) {
12031
+ const connect2 = createRunStreamClientFn({
12032
+ wsUrl: spec.callbackWsUrl,
12033
+ token: spec.callbackToken,
12034
+ log: (level, message) => log(level, `run-stream[${frame.runId}]: ${message}`),
12035
+ now
12036
+ });
12037
+ const timeoutPromise = new Promise((resolve) => {
12038
+ const t = setTimeout(() => resolve("timeout"), runStreamConnectTimeoutMs);
12039
+ t.unref?.();
12040
+ });
12041
+ try {
12042
+ const result = await Promise.race([
12043
+ connect2.connect().then(() => "connected"),
12044
+ timeoutPromise
12045
+ ]);
12046
+ if (result === "connected") {
12047
+ runStream = connect2;
12048
+ } else {
12049
+ log(
12050
+ "warn",
12051
+ `run-stream[${frame.runId}]: connect timed out after ${runStreamConnectTimeoutMs}ms; falling back to instance-socket relay`
12052
+ );
12053
+ void connect2.close("connect_timeout");
12054
+ }
12055
+ } catch (err) {
12056
+ log(
12057
+ "warn",
12058
+ `run-stream[${frame.runId}]: connect failed (${err instanceof Error ? err.message : String(err)}); falling back to instance-socket relay`
12059
+ );
12060
+ }
12061
+ }
12062
+ let legacySeq = 0;
11550
12063
  const send = (kind, payload, ts = now()) => {
11551
- seq += 1;
11552
12064
  if (kind === "error" && typeof payload === "object" && payload !== null && typeof payload.message === "string") {
11553
12065
  lastTerminalFailureMessage = payload.message;
11554
12066
  }
12067
+ if (runStream) {
12068
+ runStream.sendEvent({ ts, kind, payload });
12069
+ return;
12070
+ }
12071
+ legacySeq += 1;
11555
12072
  client.sendRunEvent({
11556
12073
  runId: frame.runId,
11557
- seq,
12074
+ seq: legacySeq,
11558
12075
  ts,
11559
12076
  kind,
11560
12077
  payload
11561
12078
  });
11562
12079
  };
11563
12080
  send("progress", { message: "assignment received" });
11564
- const spec = parseRunSpec(frame.runSpec);
12081
+ const closeRunStream = async (reason) => {
12082
+ if (!runStream) return;
12083
+ const drainTimeout = new Promise((resolve) => {
12084
+ const t = setTimeout(() => resolve(), 1e4);
12085
+ t.unref?.();
12086
+ });
12087
+ await Promise.race([runStream.whenTerminated(), drainTimeout]);
12088
+ await runStream.close(reason);
12089
+ runStream = null;
12090
+ };
11565
12091
  if (!spec) {
11566
12092
  send("error", {
11567
12093
  message: "remote assignment missing prompt"
11568
12094
  });
12095
+ await closeRunStream("done");
11569
12096
  return;
11570
12097
  }
11571
12098
  const projectDir = spec.projectDir ?? fallbackProjectDir;
@@ -11581,6 +12108,7 @@ async function executeRemoteAssignment({
11581
12108
  send("error", {
11582
12109
  message: err instanceof Error ? err.message : String(err)
11583
12110
  });
12111
+ await closeRunStream("done");
11584
12112
  return;
11585
12113
  }
11586
12114
  for (const warning of runtimeConfig.warnings) {
@@ -11678,11 +12206,13 @@ async function executeRemoteAssignment({
11678
12206
  send("error", {
11679
12207
  message: err instanceof Error ? err.message : String(err)
11680
12208
  });
12209
+ } finally {
12210
+ await closeRunStream("done");
11681
12211
  }
11682
12212
  }
11683
12213
 
11684
12214
  // src/app/dashboard/runtimeDaemon.ts
11685
- var DEFAULT_RECONNECT_DELAYS_MS = [1e3, 2e3, 5e3, 1e4, 3e4];
12215
+ var DEFAULT_RECONNECT_DELAYS_MS2 = [1e3, 2e3, 5e3, 1e4, 3e4];
11686
12216
  var DEFAULT_MAX_CONCURRENT_RUNS = 1;
11687
12217
  var DEFAULT_REFRESH_LEAD_SEC = 60;
11688
12218
  var DEFAULT_REFRESH_FAILURE_LIMIT = 5;
@@ -11708,7 +12238,7 @@ async function runDashboardRuntimeDaemon(options = {}) {
11708
12238
  const projectDir = options.projectDir ?? process.cwd();
11709
12239
  const log = options.log ?? (() => {
11710
12240
  });
11711
- const reconnectDelays = options.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS;
12241
+ const reconnectDelays = options.reconnectDelaysMs ?? DEFAULT_RECONNECT_DELAYS_MS2;
11712
12242
  const maxConcurrentRuns = options.maxConcurrentRuns ?? DEFAULT_MAX_CONCURRENT_RUNS;
11713
12243
  const refreshLeadSec = options.refreshLeadSec ?? DEFAULT_REFRESH_LEAD_SEC;
11714
12244
  const refreshFailureLimit = options.refreshFailureLimit ?? DEFAULT_REFRESH_FAILURE_LIMIT;
@@ -12094,6 +12624,35 @@ function isProcessAlive(pid) {
12094
12624
  // src/infra/daemon/udsIpc.ts
12095
12625
  import fs22 from "fs";
12096
12626
  import net2 from "net";
12627
+
12628
+ // src/infra/daemon/udsFrameCodec.ts
12629
+ var DEFAULT_MAX_FRAME_BYTES = 1e6;
12630
+ function tryReadFrame(buffer, maxBodyBytes = DEFAULT_MAX_FRAME_BYTES) {
12631
+ const newlineIdx = buffer.indexOf(10);
12632
+ if (newlineIdx < 0) return null;
12633
+ const lenStr = buffer.subarray(0, newlineIdx).toString("utf-8");
12634
+ const len = Number.parseInt(lenStr, 10);
12635
+ if (!Number.isFinite(len) || len < 0 || len > maxBodyBytes) {
12636
+ return {
12637
+ ok: false,
12638
+ error: `uds bad framing header: ${JSON.stringify(lenStr.slice(0, 32))}`,
12639
+ rest: buffer.subarray(newlineIdx + 1)
12640
+ };
12641
+ }
12642
+ if (buffer.length < newlineIdx + 1 + len) return null;
12643
+ const body = buffer.subarray(newlineIdx + 1, newlineIdx + 1 + len);
12644
+ const rest = buffer.subarray(newlineIdx + 1 + len);
12645
+ return { ok: true, body, rest };
12646
+ }
12647
+ function writeFrame(socket, value) {
12648
+ const body = JSON.stringify(value);
12649
+ const buf = Buffer.from(body, "utf-8");
12650
+ socket.write(`${buf.length}
12651
+ `);
12652
+ socket.write(buf);
12653
+ }
12654
+
12655
+ // src/infra/daemon/udsIpc.ts
12097
12656
  async function startUdsServer(socketPath, handler, log) {
12098
12657
  await unlinkStaleSocket(socketPath);
12099
12658
  const server = net2.createServer((socket) => {
@@ -12142,21 +12701,15 @@ async function handleConnection(socket, handler, log) {
12142
12701
  });
12143
12702
  async function drain() {
12144
12703
  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
- );
12704
+ const frame = tryReadFrame(buffer);
12705
+ if (frame === null) return;
12706
+ if (!frame.ok) {
12707
+ log?.("warn", frame.error);
12154
12708
  socket.destroy();
12155
12709
  return;
12156
12710
  }
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);
12711
+ const { body } = frame;
12712
+ buffer = frame.rest;
12160
12713
  let parsed;
12161
12714
  try {
12162
12715
  parsed = JSON.parse(body.toString("utf-8"));
@@ -12178,13 +12731,6 @@ async function handleConnection(socket, handler, log) {
12178
12731
  }
12179
12732
  }
12180
12733
  }
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
12734
  async function sendUdsRequest(socketPath, request, options = {}) {
12189
12735
  const timeoutMs = options.timeoutMs ?? 5e3;
12190
12736
  return await new Promise((resolve, reject) => {
@@ -12209,30 +12755,20 @@ async function sendUdsRequest(socketPath, request, options = {}) {
12209
12755
  action();
12210
12756
  };
12211
12757
  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);
12758
+ writeFrame(socket, request);
12217
12759
  });
12218
12760
  socket.on("data", (chunk) => {
12219
12761
  const chunkBuf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
12220
12762
  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)) {
12763
+ const frame = tryReadFrame(buffer);
12764
+ if (frame === null) return;
12765
+ if (!frame.ok) {
12228
12766
  finish(() => reject(new Error("uds reply malformed")));
12229
12767
  return;
12230
12768
  }
12231
- if (buffer.length < newlineIdx + 1 + len) return;
12232
- const body = buffer.subarray(newlineIdx + 1, newlineIdx + 1 + len).toString("utf-8");
12233
12769
  let parsed;
12234
12770
  try {
12235
- parsed = JSON.parse(body);
12771
+ parsed = JSON.parse(frame.body.toString("utf-8"));
12236
12772
  } catch (err) {
12237
12773
  finish(
12238
12774
  () => reject(
@@ -12301,7 +12837,8 @@ export {
12301
12837
  extractPermissionSnapshot,
12302
12838
  isSubagentTool,
12303
12839
  createFeedMapper,
12304
- handleEvent,
12840
+ ingestRuntimeEvent,
12841
+ ingestRuntimeDecision,
12305
12842
  generateId,
12306
12843
  createSessionStore,
12307
12844
  sessionsDir,
@@ -12358,4 +12895,4 @@ export {
12358
12895
  startUdsServer,
12359
12896
  sendUdsRequest
12360
12897
  };
12361
- //# sourceMappingURL=chunk-WHELLVBL.js.map
12898
+ //# sourceMappingURL=chunk-JAPBSL7D.js.map