@drisp/cli 0.5.22 → 0.5.24

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.
@@ -17,13 +17,14 @@ import {
17
17
  TransportUnreachableError,
18
18
  createUdsClientTransport,
19
19
  generateChannelRequestId,
20
+ openVersionedDb,
20
21
  readDashboardClientConfig,
21
22
  refreshDashboardAccessToken,
22
23
  resolveGatewayPaths,
23
24
  traceGatewayFrame,
24
25
  trackGatewayTransportReconnect,
25
26
  writeGatewayTrace
26
- } from "./chunk-BTY7MYYT.js";
27
+ } from "./chunk-YU2WMPRC.js";
27
28
  import {
28
29
  compileWorkflowPlan,
29
30
  createWorkflowRunner,
@@ -34,7 +35,7 @@ import {
34
35
  resolveWorkflow,
35
36
  resolveWorkflowInstall,
36
37
  resolveWorkflowPlugins
37
- } from "./chunk-DSWRVUOO.js";
38
+ } from "./chunk-7UUPLAP4.js";
38
39
 
39
40
  // src/infra/daemon/stateDir.ts
40
41
  import fs from "fs";
@@ -3374,7 +3375,7 @@ function createAssistantMessageAccumulator() {
3374
3375
  };
3375
3376
  }
3376
3377
 
3377
- // src/harnesses/claude/process/useProcess.ts
3378
+ // src/harnesses/claude/session/turnConfig.ts
3378
3379
  function mergeIsolation(base, pluginMcpConfig, perCommand) {
3379
3380
  if (!pluginMcpConfig && !perCommand) return base;
3380
3381
  return {
@@ -3383,6 +3384,17 @@ function mergeIsolation(base, pluginMcpConfig, perCommand) {
3383
3384
  ...pluginMcpConfig ? { mcpConfig: pluginMcpConfig } : {}
3384
3385
  };
3385
3386
  }
3387
+ function resolveClaudeSessionId(continuation) {
3388
+ if (!continuation || continuation.mode === "fresh") {
3389
+ return void 0;
3390
+ }
3391
+ if (continuation.mode === "resume") {
3392
+ return continuation.handle;
3393
+ }
3394
+ throw new Error("Claude harness does not support reuse-current continuation");
3395
+ }
3396
+
3397
+ // src/harnesses/claude/process/useProcess.ts
3386
3398
  var MAX_OUTPUT = 1e3;
3387
3399
  var KILL_TIMEOUT_MS = 3e3;
3388
3400
  var NULL_TOKENS = {
@@ -3433,17 +3445,6 @@ function tokenUsageEquals(a, b) {
3433
3445
  return a.input === b.input && a.output === b.output && a.cacheRead === b.cacheRead && a.cacheWrite === b.cacheWrite && a.total === b.total && a.contextSize === b.contextSize && a.contextWindowSize === b.contextWindowSize;
3434
3446
  }
3435
3447
  var JQ_ASSISTANT_TEXT_FILTER = 'select(.type == "message" and .role == "assistant") | .content[] | select(.type == "text") | .text';
3436
- function resolveClaudeSessionId(continuation) {
3437
- if (!continuation || continuation.mode === "fresh") {
3438
- return void 0;
3439
- }
3440
- if (continuation.mode === "resume") {
3441
- return continuation.handle;
3442
- }
3443
- throw new Error(
3444
- "Claude process hook does not support reuse-current continuation"
3445
- );
3446
- }
3447
3448
  function useClaudeProcess(projectDir, instanceId, isolation, pluginMcpConfig, verbose, workflow, options) {
3448
3449
  const processRef = useRef(null);
3449
3450
  const abortRef = useRef(new AbortController());
@@ -3728,25 +3729,6 @@ function useClaudeProcess(projectDir, instanceId, isolation, pluginMcpConfig, ve
3728
3729
  }
3729
3730
 
3730
3731
  // src/harnesses/claude/session/controller.ts
3731
- function mergeIsolation2(base, pluginMcpConfig, overrides) {
3732
- if (!pluginMcpConfig && !overrides) return base;
3733
- return {
3734
- ...resolveIsolationConfig(base),
3735
- ...overrides ?? {},
3736
- ...pluginMcpConfig ? { mcpConfig: pluginMcpConfig } : {}
3737
- };
3738
- }
3739
- function resolveClaudeSessionId2(continuation) {
3740
- if (!continuation || continuation.mode === "fresh") {
3741
- return void 0;
3742
- }
3743
- if (continuation.mode === "resume") {
3744
- return continuation.handle;
3745
- }
3746
- throw new Error(
3747
- "Claude session controller does not support reuse-current continuation"
3748
- );
3749
- }
3750
3732
  function createClaudeSessionController(input) {
3751
3733
  const spawnProcess = input.spawnProcess ?? spawnClaude;
3752
3734
  const processConfig = input.processConfig;
@@ -3791,8 +3773,8 @@ function createClaudeSessionController(input) {
3791
3773
  prompt,
3792
3774
  projectDir: input.projectDir,
3793
3775
  instanceId: input.instanceId,
3794
- sessionId: resolveClaudeSessionId2(continuation),
3795
- isolation: mergeIsolation2(
3776
+ sessionId: resolveClaudeSessionId(continuation),
3777
+ isolation: mergeIsolation(
3796
3778
  processConfig,
3797
3779
  input.pluginMcpConfig,
3798
3780
  configOverride
@@ -6751,6 +6733,74 @@ function buildCodexPromptOptions(input) {
6751
6733
  };
6752
6734
  }
6753
6735
 
6736
+ // src/harnesses/codex/session/turnRunner.ts
6737
+ function createCodexTurnEventCollector() {
6738
+ let message = "";
6739
+ let tokenDelta = { ...NULL_TOKENS2 };
6740
+ let turnStatus;
6741
+ let turnErrorMessage;
6742
+ return {
6743
+ handle(event) {
6744
+ const data = typeof event.data === "object" ? event.data : {};
6745
+ if (event.kind === "message.delta") {
6746
+ const delta = typeof data["delta"] === "string" ? data["delta"] : "";
6747
+ message += delta;
6748
+ }
6749
+ if (event.kind === "usage.update") {
6750
+ tokenDelta = readTokenUsage(data["delta"]);
6751
+ }
6752
+ if (event.kind === "turn.complete") {
6753
+ turnStatus = typeof data["status"] === "string" ? data["status"] : turnStatus;
6754
+ }
6755
+ if (event.kind === "unknown" && event.hookName === "error") {
6756
+ const payload = typeof data["payload"] === "object" && data["payload"] !== null ? data["payload"] : null;
6757
+ const errorValue = typeof payload?.["error"] === "object" && payload["error"] !== null ? payload["error"] : null;
6758
+ if (typeof errorValue?.["message"] === "string") {
6759
+ turnErrorMessage = errorValue["message"];
6760
+ }
6761
+ }
6762
+ },
6763
+ result() {
6764
+ if (turnStatus === "failed") {
6765
+ return {
6766
+ exitCode: 1,
6767
+ error: new Error(turnErrorMessage ?? "Codex turn failed"),
6768
+ tokens: tokenDelta,
6769
+ streamMessage: message || null
6770
+ };
6771
+ }
6772
+ return {
6773
+ exitCode: 0,
6774
+ error: null,
6775
+ tokens: tokenDelta,
6776
+ streamMessage: message || null
6777
+ };
6778
+ },
6779
+ errorResult(error) {
6780
+ return {
6781
+ exitCode: null,
6782
+ error,
6783
+ tokens: tokenDelta,
6784
+ streamMessage: message || null
6785
+ };
6786
+ }
6787
+ };
6788
+ }
6789
+ async function runCodexTurn(runtime, prompt, optionsInput, hooks) {
6790
+ const collector = createCodexTurnEventCollector();
6791
+ const unsubscribe = runtime.onEvent(collector.handle);
6792
+ try {
6793
+ await runtime.sendPrompt(prompt, buildCodexPromptOptions(optionsInput));
6794
+ return collector.result();
6795
+ } catch (error) {
6796
+ const normalized = error instanceof Error ? error : new Error(String(error));
6797
+ hooks?.onError?.(normalized);
6798
+ return collector.errorResult(normalized);
6799
+ } finally {
6800
+ unsubscribe();
6801
+ }
6802
+ }
6803
+
6754
6804
  // src/harnesses/codex/session/controller.ts
6755
6805
  function createCodexSessionController(input) {
6756
6806
  const runtime = input.runtime;
@@ -6770,73 +6820,19 @@ function createCodexSessionController(input) {
6770
6820
  streamMessage: null
6771
6821
  };
6772
6822
  }
6773
- let message = "";
6774
- let tokenDelta = { ...NULL_TOKENS2 };
6775
- let turnStatus;
6776
- let turnErrorMessage;
6777
- const unsubscribe = runtime.onEvent((event) => {
6778
- const data = typeof event.data === "object" ? event.data : {};
6779
- if (event.kind === "message.delta") {
6780
- const delta = typeof data["delta"] === "string" ? data["delta"] : "";
6781
- message += delta;
6782
- }
6783
- if (event.kind === "usage.update") {
6784
- tokenDelta = readTokenUsage(data["delta"]);
6785
- }
6786
- if (event.kind === "turn.complete") {
6787
- turnStatus = typeof data["status"] === "string" ? data["status"] : turnStatus;
6788
- }
6789
- if (event.kind === "unknown" && event.hookName === "error") {
6790
- const payload = typeof data["payload"] === "object" && data["payload"] !== null ? data["payload"] : null;
6791
- const errorValue = typeof payload?.["error"] === "object" && payload["error"] !== null ? payload["error"] : null;
6792
- if (typeof errorValue?.["message"] === "string") {
6793
- turnErrorMessage = errorValue["message"];
6794
- }
6795
- }
6823
+ const turnPromise = runCodexTurn(runtime, prompt, {
6824
+ processConfig,
6825
+ continuation,
6826
+ configOverride,
6827
+ workflowPlan: input.workflowPlan,
6828
+ pluginMcpConfig: input.pluginMcpConfig,
6829
+ ephemeral: input.ephemeral
6796
6830
  });
6797
- const turnPromise = (async () => {
6798
- try {
6799
- await runtime.sendPrompt(
6800
- prompt,
6801
- buildCodexPromptOptions({
6802
- processConfig,
6803
- continuation,
6804
- configOverride,
6805
- workflowPlan: input.workflowPlan,
6806
- pluginMcpConfig: input.pluginMcpConfig,
6807
- ephemeral: input.ephemeral
6808
- })
6809
- );
6810
- if (turnStatus === "failed") {
6811
- return {
6812
- exitCode: 1,
6813
- error: new Error(turnErrorMessage ?? "Codex turn failed"),
6814
- tokens: tokenDelta,
6815
- streamMessage: message || null
6816
- };
6817
- }
6818
- return {
6819
- exitCode: 0,
6820
- error: null,
6821
- tokens: tokenDelta,
6822
- streamMessage: message || null
6823
- };
6824
- } catch (error) {
6825
- return {
6826
- exitCode: null,
6827
- error: error instanceof Error ? error : new Error(String(error)),
6828
- tokens: tokenDelta,
6829
- streamMessage: message || null
6830
- };
6831
- } finally {
6832
- activeTurnPromise = null;
6833
- }
6834
- })();
6835
6831
  activeTurnPromise = turnPromise;
6836
6832
  try {
6837
6833
  return await turnPromise;
6838
6834
  } finally {
6839
- unsubscribe();
6835
+ activeTurnPromise = null;
6840
6836
  }
6841
6837
  },
6842
6838
  interrupt() {
@@ -6893,81 +6889,38 @@ function useCodexSessionController(runtime, processConfig, workflowPlan, ephemer
6893
6889
  };
6894
6890
  }
6895
6891
  setIsRunning(true);
6896
- let streamedMessage = "";
6897
- let turnTokens = { ...NULL_TOKENS2 };
6898
- let turnStatus;
6899
- let turnErrorMessage;
6900
- const unsubscribe = codexRuntime.onEvent((event) => {
6901
- const data = typeof event.data === "object" ? event.data : {};
6902
- if (event.kind === "message.delta") {
6903
- const delta = typeof data["delta"] === "string" ? data["delta"] : "";
6904
- streamedMessage += delta;
6905
- }
6906
- if (event.kind === "usage.update") {
6907
- turnTokens = readTokenUsage(data["delta"]);
6908
- }
6909
- if (event.kind === "turn.complete") {
6910
- turnStatus = typeof data["status"] === "string" ? data["status"] : turnStatus;
6911
- }
6912
- if (event.kind === "unknown" && event.hookName === "error") {
6913
- const payload = typeof data["payload"] === "object" && data["payload"] !== null ? data["payload"] : null;
6914
- const errorValue = typeof payload?.["error"] === "object" && payload["error"] !== null ? payload["error"] : null;
6915
- if (typeof errorValue?.["message"] === "string") {
6916
- turnErrorMessage = errorValue["message"];
6917
- }
6918
- }
6919
- });
6920
- const turnPromise = (async () => {
6921
- try {
6922
- await codexRuntime.sendPrompt(
6923
- prompt,
6924
- buildCodexPromptOptions({
6925
- processConfig,
6926
- continuation,
6927
- configOverride: _configOverride,
6928
- workflowPlan,
6929
- pluginMcpConfig,
6930
- ephemeral
6931
- })
6932
- );
6933
- if (turnStatus === "failed") {
6934
- return {
6935
- exitCode: 1,
6936
- error: new Error(turnErrorMessage ?? "Codex turn failed"),
6937
- tokens: turnTokens,
6938
- streamMessage: streamedMessage || null
6939
- };
6940
- }
6941
- return {
6942
- exitCode: 0,
6943
- error: null,
6944
- tokens: turnTokens,
6945
- streamMessage: streamedMessage || null
6946
- };
6947
- } catch (error) {
6948
- if (!abortRef.current.signal.aborted) {
6949
- onLifecycleEventRef.current?.({
6950
- type: "spawn_error",
6951
- message: error instanceof Error ? error.message : "Unknown Codex error",
6952
- failureCode: "spawn_error"
6953
- });
6954
- }
6955
- return {
6956
- exitCode: null,
6957
- error: error instanceof Error ? error : new Error("Unknown Codex error"),
6958
- tokens: turnTokens,
6959
- streamMessage: streamedMessage || null
6960
- };
6961
- } finally {
6962
- activeTurnPromiseRef.current = null;
6963
- unsubscribe();
6964
- if (!abortRef.current.signal.aborted) {
6965
- setIsRunning(false);
6892
+ const turnPromise = runCodexTurn(
6893
+ codexRuntime,
6894
+ prompt,
6895
+ {
6896
+ processConfig,
6897
+ continuation,
6898
+ configOverride: _configOverride,
6899
+ workflowPlan,
6900
+ pluginMcpConfig,
6901
+ ephemeral
6902
+ },
6903
+ {
6904
+ onError: (error) => {
6905
+ if (!abortRef.current.signal.aborted) {
6906
+ onLifecycleEventRef.current?.({
6907
+ type: "spawn_error",
6908
+ message: error.message,
6909
+ failureCode: "spawn_error"
6910
+ });
6911
+ }
6966
6912
  }
6967
6913
  }
6968
- })();
6914
+ );
6969
6915
  activeTurnPromiseRef.current = turnPromise;
6970
- return await turnPromise;
6916
+ try {
6917
+ return await turnPromise;
6918
+ } finally {
6919
+ activeTurnPromiseRef.current = null;
6920
+ if (!abortRef.current.signal.aborted) {
6921
+ setIsRunning(false);
6922
+ }
6923
+ }
6971
6924
  },
6972
6925
  [codexRuntime, processConfig, workflowPlan, pluginMcpConfig, ephemeral]
6973
6926
  );
@@ -7491,7 +7444,7 @@ var EXEC_EXIT_CODE = {
7491
7444
 
7492
7445
  // src/app/exec/runner.ts
7493
7446
  import crypto2 from "crypto";
7494
- import path18 from "path";
7447
+ import path17 from "path";
7495
7448
 
7496
7449
  // src/core/feed/entities.ts
7497
7450
  var ActorRegistry = class {
@@ -8336,28 +8289,11 @@ function createAgentMessageStream(eventBuilder, transcriptReader) {
8336
8289
  };
8337
8290
  }
8338
8291
 
8339
- // src/core/feed/internals/rootPlanTracker.ts
8340
- function createRootPlanTracker() {
8341
- let items = [];
8342
- return {
8343
- set(next) {
8344
- items = next;
8345
- },
8346
- current() {
8347
- return items;
8348
- },
8349
- differs(next) {
8350
- if (next.length !== items.length) return true;
8351
- for (let i = 0; i < next.length; i++) {
8352
- if (next[i]?.content !== items[i]?.content) return true;
8353
- if (next[i]?.status !== items[i]?.status) return true;
8354
- }
8355
- return false;
8356
- }
8357
- };
8292
+ // src/core/feed/internals/taskStateTracker.ts
8293
+ function extractTodoItems(toolInput) {
8294
+ const input = toolInput;
8295
+ return Array.isArray(input?.todos) ? input.todos : [];
8358
8296
  }
8359
-
8360
- // src/core/feed/internals/taskLifecycleTracker.ts
8361
8297
  function coerceTaskStatus(status) {
8362
8298
  switch (status) {
8363
8299
  case "pending":
@@ -8369,57 +8305,6 @@ function coerceTaskStatus(status) {
8369
8305
  return null;
8370
8306
  }
8371
8307
  }
8372
- function createTaskLifecycleTracker() {
8373
- let items = [];
8374
- return {
8375
- current() {
8376
- return items;
8377
- },
8378
- upsertCreated({ taskId, subject, description, activeForm }) {
8379
- const task = {
8380
- taskId,
8381
- content: subject,
8382
- status: "pending",
8383
- activeForm: activeForm ?? description
8384
- };
8385
- const existingIndex = items.findIndex((item) => item.taskId === taskId);
8386
- if (existingIndex === -1) {
8387
- items = [...items, task];
8388
- return;
8389
- }
8390
- items = items.map(
8391
- (item, index) => index === existingIndex ? {
8392
- ...item,
8393
- taskId,
8394
- content: subject,
8395
- activeForm: task.activeForm ?? item.activeForm
8396
- } : item
8397
- );
8398
- },
8399
- markCompleted({ taskId, subject }) {
8400
- const existingIndex = items.findIndex((item) => item.taskId === taskId);
8401
- if (existingIndex === -1) {
8402
- if (!subject) return;
8403
- items = [...items, { taskId, content: subject, status: "completed" }];
8404
- return;
8405
- }
8406
- items = items.map(
8407
- (item, index) => index === existingIndex ? { ...item, status: "completed" } : item
8408
- );
8409
- },
8410
- updateStatus({ taskId, status }) {
8411
- items = items.map(
8412
- (item) => item.taskId === taskId ? { ...item, status } : item
8413
- );
8414
- }
8415
- };
8416
- }
8417
-
8418
- // src/core/feed/internals/taskStateTracker.ts
8419
- function extractTodoItems(toolInput) {
8420
- const input = toolInput;
8421
- return Array.isArray(input?.todos) ? input.todos : [];
8422
- }
8423
8308
  function mapPlanStepStatus(status) {
8424
8309
  switch (status) {
8425
8310
  case "inProgress":
@@ -8432,18 +8317,69 @@ function mapPlanStepStatus(status) {
8432
8317
  }
8433
8318
  }
8434
8319
  function createTaskStateTracker() {
8435
- const rootPlan = createRootPlanTracker();
8436
- const taskLifecycle = createTaskLifecycleTracker();
8320
+ let rootPlanItems = [];
8321
+ let taskItems = [];
8322
+ function rootPlanDiffers(next) {
8323
+ if (next.length !== rootPlanItems.length) return true;
8324
+ for (let i = 0; i < next.length; i++) {
8325
+ if (next[i]?.content !== rootPlanItems[i]?.content) return true;
8326
+ if (next[i]?.status !== rootPlanItems[i]?.status) return true;
8327
+ }
8328
+ return false;
8329
+ }
8330
+ function upsertCreatedTask(input) {
8331
+ const { taskId, subject, description, activeForm } = input;
8332
+ const task = {
8333
+ taskId,
8334
+ content: subject,
8335
+ status: "pending",
8336
+ activeForm: activeForm ?? description
8337
+ };
8338
+ const existingIndex = taskItems.findIndex((item) => item.taskId === taskId);
8339
+ if (existingIndex === -1) {
8340
+ taskItems = [...taskItems, task];
8341
+ return;
8342
+ }
8343
+ taskItems = taskItems.map(
8344
+ (item, index) => index === existingIndex ? {
8345
+ ...item,
8346
+ taskId,
8347
+ content: subject,
8348
+ activeForm: task.activeForm ?? item.activeForm
8349
+ } : item
8350
+ );
8351
+ }
8352
+ function markTaskCompleted(input) {
8353
+ const { taskId, subject } = input;
8354
+ const existingIndex = taskItems.findIndex((item) => item.taskId === taskId);
8355
+ if (existingIndex === -1) {
8356
+ if (!subject) return;
8357
+ taskItems = [
8358
+ ...taskItems,
8359
+ { taskId, content: subject, status: "completed" }
8360
+ ];
8361
+ return;
8362
+ }
8363
+ taskItems = taskItems.map(
8364
+ (item, index) => index === existingIndex ? { ...item, status: "completed" } : item
8365
+ );
8366
+ }
8367
+ function updateTaskStatus(input) {
8368
+ const { taskId, status } = input;
8369
+ taskItems = taskItems.map(
8370
+ (item) => item.taskId === taskId ? { ...item, status } : item
8371
+ );
8372
+ }
8437
8373
  function applyToolPre(input) {
8438
8374
  const { toolName, toolInput, actorId } = input;
8439
8375
  if (toolName === "TodoWrite" && actorId === "agent:root") {
8440
- rootPlan.set(extractTodoItems(toolInput));
8376
+ rootPlanItems = extractTodoItems(toolInput);
8441
8377
  }
8442
8378
  if (toolName === "TaskUpdate") {
8443
8379
  const taskId = readString(toolInput["taskId"], toolInput["task_id"]);
8444
8380
  const status = coerceTaskStatus(toolInput["status"]);
8445
8381
  if (taskId && status) {
8446
- taskLifecycle.updateStatus({ taskId, status });
8382
+ updateTaskStatus({ taskId, status });
8447
8383
  }
8448
8384
  }
8449
8385
  }
@@ -8455,7 +8391,7 @@ function createTaskStateTracker() {
8455
8391
  const taskId = readString(task["id"], task["task_id"]);
8456
8392
  const subject = readString(task["subject"], toolInput["subject"]);
8457
8393
  if (taskId && subject) {
8458
- taskLifecycle.upsertCreated({
8394
+ upsertCreatedTask({
8459
8395
  taskId,
8460
8396
  subject,
8461
8397
  description: readString(toolInput["description"]),
@@ -8475,7 +8411,7 @@ function createTaskStateTracker() {
8475
8411
  readObject(response["statusChange"])["to"] ?? toolInput["status"]
8476
8412
  );
8477
8413
  if (taskId && status) {
8478
- taskLifecycle.updateStatus({ taskId, status });
8414
+ updateTaskStatus({ taskId, status });
8479
8415
  }
8480
8416
  }
8481
8417
  }
@@ -8484,19 +8420,19 @@ function createTaskStateTracker() {
8484
8420
  const subject = readString(data["task_subject"]);
8485
8421
  const description = readString(data["task_description"]);
8486
8422
  if (taskId && subject) {
8487
- taskLifecycle.upsertCreated({ taskId, subject, description });
8423
+ upsertCreatedTask({ taskId, subject, description });
8488
8424
  }
8489
8425
  }
8490
8426
  function applyTaskCompletedEvent(data) {
8491
8427
  const taskId = readString(data["task_id"]);
8492
8428
  const subject = readString(data["task_subject"]);
8493
8429
  if (taskId) {
8494
- taskLifecycle.markCompleted({ taskId, subject });
8430
+ markTaskCompleted({ taskId, subject });
8495
8431
  }
8496
8432
  }
8497
8433
  return {
8498
8434
  current() {
8499
- return [...rootPlan.current(), ...taskLifecycle.current()];
8435
+ return [...rootPlanItems, ...taskItems];
8500
8436
  },
8501
8437
  applyToolPre,
8502
8438
  applyToolPost,
@@ -8506,8 +8442,8 @@ function createTaskStateTracker() {
8506
8442
  content: typeof step.step === "string" ? step.step : "",
8507
8443
  status: mapPlanStepStatus(step.status)
8508
8444
  }));
8509
- if (!rootPlan.differs(next)) return false;
8510
- rootPlan.set(next);
8445
+ if (!rootPlanDiffers(next)) return false;
8446
+ rootPlanItems = next;
8511
8447
  return true;
8512
8448
  },
8513
8449
  applyTaskCreatedEvent,
@@ -8538,48 +8474,6 @@ function createTaskStateTracker() {
8538
8474
  };
8539
8475
  }
8540
8476
 
8541
- // src/core/feed/internals/subagentTracker.ts
8542
- function createSubagentTracker() {
8543
- const stack = [];
8544
- const descriptions = /* @__PURE__ */ new Map();
8545
- let pendingDescription;
8546
- return {
8547
- pushActor(actorId) {
8548
- stack.push(actorId);
8549
- },
8550
- popActor(actorId) {
8551
- const idx = stack.lastIndexOf(actorId);
8552
- if (idx !== -1) stack.splice(idx, 1);
8553
- },
8554
- peek() {
8555
- return stack.length > 0 ? stack[stack.length - 1] : void 0;
8556
- },
8557
- clear() {
8558
- stack.length = 0;
8559
- },
8560
- currentScope() {
8561
- return stack.length > 0 ? "subagent" : "root";
8562
- },
8563
- recordPendingDescription(description) {
8564
- pendingDescription = description;
8565
- },
8566
- clearPendingDescription() {
8567
- pendingDescription = void 0;
8568
- },
8569
- consumePendingDescription() {
8570
- const value = pendingDescription;
8571
- pendingDescription = void 0;
8572
- return value;
8573
- },
8574
- setDescription(agentId, description) {
8575
- descriptions.set(agentId, description);
8576
- },
8577
- description(agentId) {
8578
- return descriptions.get(agentId);
8579
- }
8580
- };
8581
- }
8582
-
8583
8477
  // src/core/feed/todo.ts
8584
8478
  function isSubagentTool(toolName) {
8585
8479
  return toolName === "Task" || toolName === "Agent";
@@ -8588,44 +8482,48 @@ function isSubagentTool(toolName) {
8588
8482
  // src/core/feed/internals/subagentLifecycle.ts
8589
8483
  function createSubagentLifecycle(args) {
8590
8484
  const { actors, runLifecycle } = args;
8591
- const tracker = createSubagentTracker();
8485
+ const stack = [];
8486
+ const descriptions = /* @__PURE__ */ new Map();
8487
+ let pendingDescription;
8592
8488
  const actorIdFor = (agentId) => `subagent:${agentId}`;
8593
8489
  return {
8594
8490
  observeToolInput(toolName, toolInput) {
8595
8491
  if (!isSubagentTool(toolName)) return;
8596
- if (typeof toolInput["description"] === "string") {
8597
- tracker.recordPendingDescription(toolInput["description"]);
8598
- } else {
8599
- tracker.clearPendingDescription();
8600
- }
8492
+ pendingDescription = typeof toolInput["description"] === "string" ? toolInput["description"] : void 0;
8601
8493
  },
8602
8494
  startSubagent({ agentId, agentType, fallbackDescription }) {
8603
- const description = tracker.consumePendingDescription() ?? fallbackDescription;
8495
+ const consumed = pendingDescription;
8496
+ pendingDescription = void 0;
8497
+ const description = consumed ?? fallbackDescription;
8604
8498
  if (agentId) {
8605
8499
  actors.ensureSubagent(agentId, agentType ?? "unknown");
8606
8500
  const currentRun = runLifecycle.getCurrentRun();
8607
8501
  if (currentRun) currentRun.actors.subagent_ids.push(agentId);
8608
- tracker.pushActor(actorIdFor(agentId));
8609
- if (description) tracker.setDescription(agentId, description);
8502
+ stack.push(actorIdFor(agentId));
8503
+ if (description) descriptions.set(agentId, description);
8610
8504
  }
8611
8505
  return { actorId: "agent:root", description: description ?? void 0 };
8612
8506
  },
8613
8507
  stopSubagent(agentId) {
8614
- if (agentId) tracker.popActor(actorIdFor(agentId));
8508
+ if (agentId) {
8509
+ const actorId = actorIdFor(agentId);
8510
+ const idx = stack.lastIndexOf(actorId);
8511
+ if (idx !== -1) stack.splice(idx, 1);
8512
+ }
8615
8513
  return {
8616
8514
  actorId: actorIdFor(agentId ?? "unknown"),
8617
- description: tracker.description(agentId ?? "")
8515
+ description: descriptions.get(agentId ?? "")
8618
8516
  };
8619
8517
  },
8620
8518
  currentActor() {
8621
- return tracker.peek() ?? "agent:root";
8519
+ return stack.at(-1) ?? "agent:root";
8622
8520
  },
8623
8521
  currentScope() {
8624
- return tracker.currentScope();
8522
+ return stack.length > 0 ? "subagent" : "root";
8625
8523
  },
8626
8524
  actorIdFor,
8627
8525
  clear() {
8628
- tracker.clear();
8526
+ stack.length = 0;
8629
8527
  }
8630
8528
  };
8631
8529
  }
@@ -9978,193 +9876,18 @@ function createFeedMapper(bootstrap) {
9978
9876
  };
9979
9877
  }
9980
9878
 
9981
- // src/core/controller/rules.ts
9982
- function ruleMatches(ruleToolName, toolName) {
9983
- if (ruleToolName === "*") return true;
9984
- if (ruleToolName === toolName) return true;
9985
- if (ruleToolName.endsWith("__*")) {
9986
- const prefix = ruleToolName.slice(0, -1);
9987
- return toolName.startsWith(prefix);
9988
- }
9989
- return false;
9990
- }
9991
- function matchRule(rules, toolName) {
9992
- const denyMatch = rules.find(
9993
- (r) => r.action === "deny" && ruleMatches(r.toolName, toolName)
9994
- );
9995
- if (denyMatch) return denyMatch;
9996
- return rules.find(
9997
- (r) => r.action === "approve" && ruleMatches(r.toolName, toolName)
9998
- );
9999
- }
10000
-
10001
- // src/core/controller/permission.ts
10002
- var SESSION_APPROVAL_REQUEST_HOOKS = /* @__PURE__ */ new Set([
10003
- "item/commandExecution/requestApproval",
10004
- "item/fileChange/requestApproval",
10005
- "item/permissions/requestApproval"
10006
- ]);
10007
- function isScopedPermissionsRequest(hookName) {
10008
- return hookName === "item/permissions/requestApproval";
10009
- }
10010
- function supportsSessionApproval(hookName) {
10011
- return hookName !== void 0 && SESSION_APPROVAL_REQUEST_HOOKS.has(hookName);
10012
- }
10013
- function extractPermissionSnapshot(event) {
10014
- const data = event.data;
10015
- const toolNameFromData = typeof data["tool_name"] === "string" ? data["tool_name"] : void 0;
10016
- const toolInputFromData = typeof data["tool_input"] === "object" && data["tool_input"] !== null ? data["tool_input"] : void 0;
10017
- const toolUseIdFromData = typeof data["tool_use_id"] === "string" ? data["tool_use_id"] : void 0;
10018
- return {
10019
- request_id: event.id,
10020
- ts: event.timestamp,
10021
- kind: event.kind,
10022
- hookName: event.hookName,
10023
- tool_name: event.toolName ?? toolNameFromData ?? "Unknown",
10024
- tool_input: toolInputFromData ?? {},
10025
- tool_use_id: event.toolUseId ?? toolUseIdFromData,
10026
- suggestions: data.permission_suggestions
10027
- };
10028
- }
10029
-
10030
- // src/core/controller/runtimeController.ts
10031
- function handleEvent(event, cb) {
10032
- const eventKind2 = event.kind;
10033
- const isScoped = isScopedPermissionsRequest(event.hookName);
10034
- const eventData = event.data;
10035
- const toolName = event.toolName ?? (typeof eventData["tool_name"] === "string" ? eventData["tool_name"] : void 0);
10036
- if (eventKind2 === "permission.request" && toolName === "user_input") {
10037
- cb.enqueueQuestion(event.id);
10038
- cb.relayQuestion?.(event);
10039
- return { handled: true };
10040
- }
10041
- if (eventKind2 === "permission.request" && toolName) {
10042
- const rule = matchRule(cb.getRules(), toolName);
10043
- if (rule?.action === "deny") {
10044
- return {
10045
- handled: true,
10046
- decision: {
10047
- type: "json",
10048
- source: "rule",
10049
- intent: {
10050
- kind: "permission_deny",
10051
- reason: `Blocked by rule: ${rule.addedBy}`
10052
- }
10053
- }
10054
- };
10055
- }
10056
- if (rule?.action === "approve") {
10057
- return {
10058
- handled: true,
10059
- decision: {
10060
- type: "json",
10061
- source: "rule",
10062
- intent: { kind: "permission_allow" },
10063
- // Scoped Codex permission grants are session-scoped under
10064
- // auto rules so the same capability isn't re-prompted.
10065
- ...isScoped ? { data: { scope: "session" } } : {}
10066
- }
10067
- };
10068
- }
10069
- cb.enqueuePermission(event);
10070
- cb.relayPermission?.(event);
10071
- return { handled: true };
10072
- }
10073
- if (eventKind2 === "tool.pre" && toolName === "AskUserQuestion") {
10074
- cb.enqueueQuestion(event.id);
10075
- cb.relayQuestion?.(event);
10076
- return { handled: true };
10077
- }
10078
- if (eventKind2 === "tool.pre" && toolName) {
10079
- const rule = matchRule(cb.getRules(), toolName);
10080
- if (rule?.action === "deny") {
10081
- return {
10082
- handled: true,
10083
- decision: {
10084
- type: "json",
10085
- source: "rule",
10086
- intent: {
10087
- kind: "pre_tool_deny",
10088
- reason: `Blocked by rule: ${rule.addedBy}`
10089
- }
10090
- }
10091
- };
10092
- }
10093
- return {
10094
- handled: true,
10095
- decision: {
10096
- type: "json",
10097
- source: "rule",
10098
- intent: { kind: "pre_tool_allow" }
10099
- }
10100
- };
10101
- }
10102
- return { handled: false };
10103
- }
10104
-
10105
- // src/core/feed/ingest.ts
10106
- function ingestRuntimeEvent(event, ctx) {
10107
- const controllerResult = handleEvent(event, ctx.controllerCallbacks);
10108
- const feedEvents = ctx.mapper.mapEvent(event);
10109
- if (ctx.store) {
10110
- persistOrDegrade(
10111
- ctx.store,
10112
- () => ctx.store.recordEvent(event, feedEvents),
10113
- "recordEvent failed",
10114
- ctx.onPersistFailure
10115
- );
10116
- }
10117
- return {
10118
- feedEvents,
10119
- decision: controllerResult.handled && controllerResult.decision ? controllerResult.decision : null
10120
- };
10121
- }
10122
- function ingestRuntimeDecision(eventId, decision, ctx) {
10123
- const feedEvent = ctx.mapper.mapDecision(eventId, decision);
10124
- if (feedEvent && ctx.store) {
10125
- persistOrDegrade(
10126
- ctx.store,
10127
- () => ctx.store.recordFeedEvents([feedEvent]),
10128
- "recordFeedEvents failed",
10129
- ctx.onPersistFailure
10130
- );
10131
- }
10132
- return feedEvent;
10133
- }
10134
- function persistOrDegrade(store, action, label, onFailure) {
10135
- try {
10136
- action();
10137
- } catch (err) {
10138
- const reason = err instanceof Error ? err.message : String(err);
10139
- const message = `${label}: ${reason}`;
10140
- store.markDegraded(message);
10141
- onFailure?.(message);
10142
- }
10143
- }
10144
-
10145
- // src/infra/sessions/store.ts
10146
- import fs17 from "fs";
10147
- import path14 from "path";
10148
- import Database from "better-sqlite3";
10149
-
10150
- // src/infra/sessions/schema.ts
10151
- var SCHEMA_VERSION = 6;
10152
- function initSchema(db) {
10153
- db.exec("PRAGMA journal_mode = WAL");
10154
- db.exec("PRAGMA foreign_keys = ON");
10155
- db.exec(`
10156
- CREATE TABLE IF NOT EXISTS schema_version (
10157
- version INTEGER NOT NULL
10158
- );
10159
-
10160
- CREATE TABLE IF NOT EXISTS session (
10161
- id TEXT PRIMARY KEY,
10162
- project_dir TEXT NOT NULL,
10163
- created_at INTEGER NOT NULL,
10164
- updated_at INTEGER NOT NULL,
10165
- label TEXT,
10166
- event_count INTEGER DEFAULT 0
10167
- );
9879
+ // src/infra/sessions/schema.ts
9880
+ var SCHEMA_VERSION = 6;
9881
+ var applySessionSchema = (db, fromVersion) => {
9882
+ db.exec(`
9883
+ CREATE TABLE IF NOT EXISTS session (
9884
+ id TEXT PRIMARY KEY,
9885
+ project_dir TEXT NOT NULL,
9886
+ created_at INTEGER NOT NULL,
9887
+ updated_at INTEGER NOT NULL,
9888
+ label TEXT,
9889
+ event_count INTEGER DEFAULT 0
9890
+ );
10168
9891
 
10169
9892
  CREATE TABLE IF NOT EXISTS runtime_events (
10170
9893
  id TEXT PRIMARY KEY,
@@ -10216,45 +9939,6 @@ function initSchema(db) {
10216
9939
  FOREIGN KEY (session_id) REFERENCES session(id)
10217
9940
  );
10218
9941
 
10219
- -- Channel I/O ledger: every inbound chat normalized by an adapter and every
10220
- -- outbound chat dispatched back to a provider gets one row. Idempotency
10221
- -- key prevents double-delivery on retry/restart; session_id ties chat to a
10222
- -- particular Athena interactive runtime when known.
10223
- CREATE TABLE IF NOT EXISTS channel_messages (
10224
- id INTEGER PRIMARY KEY AUTOINCREMENT,
10225
- channel_id TEXT NOT NULL,
10226
- account_id TEXT NOT NULL,
10227
- peer_id TEXT,
10228
- room_id TEXT,
10229
- thread_id TEXT,
10230
- provider_message_id TEXT NOT NULL,
10231
- direction TEXT NOT NULL CHECK(direction IN ('in','out')),
10232
- session_id TEXT REFERENCES adapter_sessions(session_id),
10233
- agent_id TEXT,
10234
- idempotency_key TEXT,
10235
- feed_event_id TEXT,
10236
- created_at INTEGER NOT NULL
10237
- );
10238
-
10239
- -- Audit log for cloud function invocations brokered by the gateway. One
10240
- -- row per invocation across all callers (agent tool, /run channel cmd,
10241
- -- hook helper). Idempotency cache is in-memory + write-through here.
10242
- CREATE TABLE IF NOT EXISTS gateway_function_invocations (
10243
- id INTEGER PRIMARY KEY AUTOINCREMENT,
10244
- name TEXT NOT NULL,
10245
- caller_kind TEXT NOT NULL CHECK(caller_kind IN ('agent','channel','hook')),
10246
- session_id TEXT REFERENCES adapter_sessions(session_id),
10247
- agent_id TEXT,
10248
- idempotency_key TEXT,
10249
- request_hash TEXT NOT NULL,
10250
- status TEXT NOT NULL CHECK(status IN ('pending','ok','error','timeout')),
10251
- http_status INTEGER,
10252
- duration_ms INTEGER,
10253
- error TEXT,
10254
- started_at INTEGER NOT NULL,
10255
- completed_at INTEGER
10256
- );
10257
-
10258
9942
  -- Durable retry queue for outbound channel sends. Drained by the gateway
10259
9943
  -- daemon on startup and after transient send failures.
10260
9944
  CREATE TABLE IF NOT EXISTS channel_outbox (
@@ -10274,127 +9958,78 @@ function initSchema(db) {
10274
9958
  CREATE UNIQUE INDEX IF NOT EXISTS idx_feed_seq ON feed_events(seq);
10275
9959
  CREATE INDEX IF NOT EXISTS idx_runtime_seq ON runtime_events(seq);
10276
9960
  CREATE INDEX IF NOT EXISTS idx_workflow_runs_session ON workflow_runs(session_id);
10277
- CREATE UNIQUE INDEX IF NOT EXISTS idx_channel_messages_idem
10278
- ON channel_messages(channel_id, account_id, idempotency_key)
10279
- WHERE idempotency_key IS NOT NULL;
10280
- CREATE INDEX IF NOT EXISTS idx_channel_messages_session_key
10281
- ON channel_messages(channel_id, account_id, peer_id, room_id, thread_id, created_at);
10282
- CREATE UNIQUE INDEX IF NOT EXISTS idx_fn_idem
10283
- ON gateway_function_invocations(name, idempotency_key)
10284
- WHERE idempotency_key IS NOT NULL;
10285
9961
  CREATE INDEX IF NOT EXISTS idx_outbox_due ON channel_outbox(next_attempt_at);
10286
9962
  `);
10287
- const existing = db.prepare("SELECT version FROM schema_version").get();
10288
- if (existing && existing.version > SCHEMA_VERSION) {
9963
+ if (fromVersion === void 0) return;
9964
+ if (fromVersion >= SCHEMA_VERSION) return;
9965
+ if (fromVersion < 2) {
10289
9966
  throw new Error(
10290
- `Database has newer schema version ${existing.version} (expected <= ${SCHEMA_VERSION}). Update athena-cli to open this session.`
9967
+ `Session database is at schema version ${fromVersion} which predates the first release. Delete the session database and start fresh.`
10291
9968
  );
10292
9969
  }
10293
- if (!existing) {
10294
- db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(
10295
- SCHEMA_VERSION
10296
- );
10297
- } else if (existing.version < SCHEMA_VERSION) {
10298
- if (existing.version < 2) {
10299
- throw new Error(
10300
- `Session database is at schema version ${existing.version} which predates the first release. Delete the session database and start fresh.`
10301
- );
10302
- }
10303
- if (existing.version === 2) {
10304
- db.exec(`
10305
- ALTER TABLE adapter_sessions ADD COLUMN tokens_input INTEGER;
10306
- ALTER TABLE adapter_sessions ADD COLUMN tokens_output INTEGER;
10307
- ALTER TABLE adapter_sessions ADD COLUMN tokens_cache_read INTEGER;
10308
- ALTER TABLE adapter_sessions ADD COLUMN tokens_cache_write INTEGER;
10309
- ALTER TABLE adapter_sessions ADD COLUMN tokens_context_size INTEGER;
10310
- ALTER TABLE adapter_sessions ADD COLUMN tokens_context_window_size INTEGER;
10311
- UPDATE schema_version SET version = 4;
10312
- `);
10313
- }
10314
- if (existing.version === 3) {
10315
- db.exec(`
10316
- ALTER TABLE adapter_sessions ADD COLUMN tokens_context_window_size INTEGER;
10317
- UPDATE schema_version SET version = 4;
10318
- `);
10319
- }
10320
- const currentVersion = db.prepare("SELECT version FROM schema_version").get().version;
10321
- if (currentVersion === 4) {
10322
- db.exec(`
10323
- CREATE TABLE IF NOT EXISTS workflow_runs (
10324
- id TEXT PRIMARY KEY,
10325
- session_id TEXT NOT NULL,
10326
- workflow_name TEXT,
10327
- started_at INTEGER NOT NULL,
10328
- ended_at INTEGER,
10329
- iteration INTEGER NOT NULL DEFAULT 0,
10330
- max_iterations INTEGER NOT NULL DEFAULT 1,
10331
- status TEXT NOT NULL DEFAULT 'running',
10332
- stop_reason TEXT,
10333
- tracker_path TEXT,
10334
- FOREIGN KEY (session_id) REFERENCES session(id)
10335
- );
10336
- CREATE INDEX IF NOT EXISTS idx_workflow_runs_session ON workflow_runs(session_id);
10337
- ALTER TABLE adapter_sessions ADD COLUMN run_id TEXT REFERENCES workflow_runs(id);
10338
- UPDATE schema_version SET version = 5;
10339
- `);
10340
- }
10341
- const versionAfterV5 = db.prepare("SELECT version FROM schema_version").get().version;
10342
- if (versionAfterV5 === 5) {
10343
- db.exec(`
10344
- CREATE TABLE IF NOT EXISTS channel_messages (
10345
- id INTEGER PRIMARY KEY AUTOINCREMENT,
10346
- channel_id TEXT NOT NULL,
10347
- account_id TEXT NOT NULL,
10348
- peer_id TEXT,
10349
- room_id TEXT,
10350
- thread_id TEXT,
10351
- provider_message_id TEXT NOT NULL,
10352
- direction TEXT NOT NULL CHECK(direction IN ('in','out')),
10353
- session_id TEXT REFERENCES adapter_sessions(session_id),
10354
- agent_id TEXT,
10355
- idempotency_key TEXT,
10356
- feed_event_id TEXT,
10357
- created_at INTEGER NOT NULL
10358
- );
10359
- CREATE TABLE IF NOT EXISTS gateway_function_invocations (
10360
- id INTEGER PRIMARY KEY AUTOINCREMENT,
10361
- name TEXT NOT NULL,
10362
- caller_kind TEXT NOT NULL CHECK(caller_kind IN ('agent','channel','hook')),
10363
- session_id TEXT REFERENCES adapter_sessions(session_id),
10364
- agent_id TEXT,
10365
- idempotency_key TEXT,
10366
- request_hash TEXT NOT NULL,
10367
- status TEXT NOT NULL CHECK(status IN ('pending','ok','error','timeout')),
10368
- http_status INTEGER,
10369
- duration_ms INTEGER,
10370
- error TEXT,
10371
- started_at INTEGER NOT NULL,
10372
- completed_at INTEGER
10373
- );
10374
- CREATE TABLE IF NOT EXISTS channel_outbox (
10375
- id INTEGER PRIMARY KEY AUTOINCREMENT,
10376
- channel_id TEXT NOT NULL,
10377
- account_id TEXT NOT NULL,
10378
- payload_json TEXT NOT NULL,
10379
- attempt INTEGER NOT NULL DEFAULT 0,
10380
- next_attempt_at INTEGER NOT NULL,
10381
- last_error TEXT,
10382
- created_at INTEGER NOT NULL
10383
- );
10384
- CREATE UNIQUE INDEX IF NOT EXISTS idx_channel_messages_idem
10385
- ON channel_messages(channel_id, account_id, idempotency_key)
10386
- WHERE idempotency_key IS NOT NULL;
10387
- CREATE INDEX IF NOT EXISTS idx_channel_messages_session_key
10388
- ON channel_messages(channel_id, account_id, peer_id, room_id, thread_id, created_at);
10389
- CREATE UNIQUE INDEX IF NOT EXISTS idx_fn_idem
10390
- ON gateway_function_invocations(name, idempotency_key)
10391
- WHERE idempotency_key IS NOT NULL;
10392
- CREATE INDEX IF NOT EXISTS idx_outbox_due ON channel_outbox(next_attempt_at);
10393
- UPDATE schema_version SET version = 6;
10394
- `);
10395
- }
9970
+ if (fromVersion === 2) {
9971
+ db.exec(`
9972
+ ALTER TABLE adapter_sessions ADD COLUMN tokens_input INTEGER;
9973
+ ALTER TABLE adapter_sessions ADD COLUMN tokens_output INTEGER;
9974
+ ALTER TABLE adapter_sessions ADD COLUMN tokens_cache_read INTEGER;
9975
+ ALTER TABLE adapter_sessions ADD COLUMN tokens_cache_write INTEGER;
9976
+ ALTER TABLE adapter_sessions ADD COLUMN tokens_context_size INTEGER;
9977
+ ALTER TABLE adapter_sessions ADD COLUMN tokens_context_window_size INTEGER;
9978
+ UPDATE schema_version SET version = 4;
9979
+ `);
9980
+ }
9981
+ if (fromVersion === 3) {
9982
+ db.exec(`
9983
+ ALTER TABLE adapter_sessions ADD COLUMN tokens_context_window_size INTEGER;
9984
+ UPDATE schema_version SET version = 4;
9985
+ `);
9986
+ }
9987
+ const currentVersion = db.prepare("SELECT version FROM schema_version").get().version;
9988
+ if (currentVersion === 4) {
9989
+ db.exec(`
9990
+ CREATE TABLE IF NOT EXISTS workflow_runs (
9991
+ id TEXT PRIMARY KEY,
9992
+ session_id TEXT NOT NULL,
9993
+ workflow_name TEXT,
9994
+ started_at INTEGER NOT NULL,
9995
+ ended_at INTEGER,
9996
+ iteration INTEGER NOT NULL DEFAULT 0,
9997
+ max_iterations INTEGER NOT NULL DEFAULT 1,
9998
+ status TEXT NOT NULL DEFAULT 'running',
9999
+ stop_reason TEXT,
10000
+ tracker_path TEXT,
10001
+ FOREIGN KEY (session_id) REFERENCES session(id)
10002
+ );
10003
+ CREATE INDEX IF NOT EXISTS idx_workflow_runs_session ON workflow_runs(session_id);
10004
+ ALTER TABLE adapter_sessions ADD COLUMN run_id TEXT REFERENCES workflow_runs(id);
10005
+ UPDATE schema_version SET version = 5;
10006
+ `);
10007
+ }
10008
+ const versionAfterV5 = db.prepare("SELECT version FROM schema_version").get().version;
10009
+ if (versionAfterV5 === 5) {
10010
+ db.exec(`
10011
+ CREATE TABLE IF NOT EXISTS channel_outbox (
10012
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
10013
+ channel_id TEXT NOT NULL,
10014
+ account_id TEXT NOT NULL,
10015
+ payload_json TEXT NOT NULL,
10016
+ attempt INTEGER NOT NULL DEFAULT 0,
10017
+ next_attempt_at INTEGER NOT NULL,
10018
+ last_error TEXT,
10019
+ created_at INTEGER NOT NULL
10020
+ );
10021
+ CREATE INDEX IF NOT EXISTS idx_outbox_due ON channel_outbox(next_attempt_at);
10022
+ UPDATE schema_version SET version = 6;
10023
+ `);
10396
10024
  }
10397
- }
10025
+ };
10026
+ var SESSION_SCHEMA = {
10027
+ version: SCHEMA_VERSION,
10028
+ migrate: applySessionSchema,
10029
+ onNewerVersion: (found, expected) => new Error(
10030
+ `Database has newer schema version ${found} (expected <= ${expected}). Update athena-cli to open this session.`
10031
+ )
10032
+ };
10398
10033
 
10399
10034
  // src/infra/sessions/types.ts
10400
10035
  function rowToAthenaSession(row, adapterSessionIds, firstPrompt) {
@@ -10412,11 +10047,11 @@ function rowToAthenaSession(row, adapterSessionIds, firstPrompt) {
10412
10047
 
10413
10048
  // src/infra/sessions/store.ts
10414
10049
  function createSessionStore(opts) {
10415
- if (opts.dbPath !== ":memory:") {
10416
- fs17.mkdirSync(path14.dirname(opts.dbPath), { recursive: true });
10417
- }
10418
- const db = new Database(opts.dbPath);
10419
- initSchema(db);
10050
+ const db = openVersionedDb(opts.dbPath, {
10051
+ ...SESSION_SCHEMA,
10052
+ foreignKeys: true,
10053
+ ensureDir: true
10054
+ });
10420
10055
  if (opts.dbPath !== ":memory:") {
10421
10056
  db.pragma("locking_mode = EXCLUSIVE");
10422
10057
  db.exec("BEGIN IMMEDIATE; COMMIT");
@@ -10687,9 +10322,6 @@ function createSessionStore(opts) {
10687
10322
  };
10688
10323
  }
10689
10324
 
10690
- // src/infra/sessions/hookAudit.ts
10691
- import Database2 from "better-sqlite3";
10692
-
10693
10325
  // src/core/feed/filter.ts
10694
10326
  var TASK_TOOL_NAMES = /* @__PURE__ */ new Set([
10695
10327
  "TodoWrite",
@@ -11365,8 +10997,8 @@ var PRIMARY_INPUT_EXTRACTORS = {
11365
10997
  return compactText(colonIdx >= 0 ? name.slice(colonIdx + 1) : name, 80);
11366
10998
  },
11367
10999
  NotebookEdit: (input) => {
11368
- const path22 = String(input.notebook_path ?? "");
11369
- return path22 ? shortenPath(path22) : "";
11000
+ const path21 = String(input.notebook_path ?? "");
11001
+ return path21 ? shortenPath(path21) : "";
11370
11002
  },
11371
11003
  AskUserQuestion: (input) => {
11372
11004
  const questions = input.questions;
@@ -12637,16 +12269,16 @@ function extractDomain(url) {
12637
12269
  }
12638
12270
  }
12639
12271
  function filePathSegments(input) {
12640
- const path22 = prop2(input, "file_path") ?? prop2(input, "notebook_path") ?? "";
12641
- if (typeof path22 !== "string" || !path22) return [];
12642
- const { prefix, filename } = shortenPathStructured(path22);
12272
+ const path21 = prop2(input, "file_path") ?? prop2(input, "notebook_path") ?? "";
12273
+ if (typeof path21 !== "string" || !path21) return [];
12274
+ const { prefix, filename } = shortenPathStructured(path21);
12643
12275
  if (prefix && filename) {
12644
12276
  return [
12645
12277
  { text: prefix, role: "target" },
12646
12278
  { text: filename, role: "filename" }
12647
12279
  ];
12648
12280
  }
12649
- return [{ text: filename || path22, role: "filename" }];
12281
+ return [{ text: filename || path21, role: "filename" }];
12650
12282
  }
12651
12283
  function grepSegments(input) {
12652
12284
  const pattern = String(prop2(input, "pattern") ?? "");
@@ -13779,57 +13411,93 @@ ${details}` : entry.summary;
13779
13411
  }
13780
13412
  };
13781
13413
 
13414
+ // src/infra/sessions/sessionDbReader.ts
13415
+ import Database from "better-sqlite3";
13416
+ function openSessionDbReadonly(dbPath, options = {}) {
13417
+ const db = new Database(dbPath, {
13418
+ readonly: true,
13419
+ ...options.fileMustExist ? { fileMustExist: true } : {}
13420
+ });
13421
+ return {
13422
+ schemaVersion() {
13423
+ const row = db.prepare("SELECT version FROM schema_version").get();
13424
+ return row?.version;
13425
+ },
13426
+ sessionRow() {
13427
+ return db.prepare("SELECT * FROM session LIMIT 1").get();
13428
+ },
13429
+ adapterSessionIds() {
13430
+ const rows = db.prepare("SELECT session_id FROM adapter_sessions ORDER BY started_at").all();
13431
+ return rows.map((r) => r.session_id);
13432
+ },
13433
+ firstUserPrompt() {
13434
+ const row = db.prepare(
13435
+ `SELECT json_extract(payload, '$.data.prompt') as prompt FROM runtime_events WHERE hook_name = 'UserPromptSubmit' ORDER BY seq ASC LIMIT 1`
13436
+ ).get();
13437
+ return row?.prompt ?? void 0;
13438
+ },
13439
+ runtimeHookCounts() {
13440
+ const rows = db.prepare(
13441
+ "SELECT hook_name, COUNT(*) AS count FROM runtime_events GROUP BY hook_name"
13442
+ ).all();
13443
+ const counts = {};
13444
+ for (const row of rows) counts[row.hook_name] = row.count;
13445
+ return counts;
13446
+ },
13447
+ feedEvents() {
13448
+ const rows = db.prepare("SELECT data FROM feed_events ORDER BY seq").all();
13449
+ return rows.map((r) => JSON.parse(r.data));
13450
+ },
13451
+ close() {
13452
+ db.close();
13453
+ }
13454
+ };
13455
+ }
13456
+
13782
13457
  // src/infra/sessions/registry.ts
13783
- import fs18 from "fs";
13458
+ import fs17 from "fs";
13784
13459
  import os11 from "os";
13785
- import path15 from "path";
13786
- import Database3 from "better-sqlite3";
13460
+ import path14 from "path";
13787
13461
  function sessionsDir() {
13788
- return path15.join(os11.homedir(), ".config", "athena", "sessions");
13462
+ return path14.join(os11.homedir(), ".config", "athena", "sessions");
13789
13463
  }
13790
13464
  function sessionDbPath(sessionId) {
13791
- return path15.join(sessionsDir(), sessionId, "session.db");
13465
+ return path14.join(sessionsDir(), sessionId, "session.db");
13792
13466
  }
13793
13467
  function readSessionFromDb(dbPath) {
13794
- if (!fs18.existsSync(dbPath)) return null;
13795
- let db;
13468
+ if (!fs17.existsSync(dbPath)) return null;
13469
+ let reader;
13796
13470
  try {
13797
- db = new Database3(dbPath, { readonly: true });
13798
- const versionRow = db.prepare("SELECT version FROM schema_version").get();
13799
- if (versionRow && versionRow.version > SCHEMA_VERSION) {
13471
+ reader = openSessionDbReadonly(dbPath);
13472
+ const version = reader.schemaVersion();
13473
+ if (version !== void 0 && version > SCHEMA_VERSION) {
13800
13474
  return null;
13801
13475
  }
13802
- const row = db.prepare("SELECT * FROM session LIMIT 1").get();
13476
+ const row = reader.sessionRow();
13803
13477
  if (!row) return null;
13804
- const adapters = db.prepare("SELECT session_id FROM adapter_sessions ORDER BY started_at").all();
13478
+ const adapterSessionIds = reader.adapterSessionIds();
13805
13479
  let firstPrompt;
13806
13480
  if (!row.label && (row.event_count ?? 0) > 0) {
13807
- const promptRow = db.prepare(
13808
- `SELECT json_extract(payload, '$.data.prompt') as prompt FROM runtime_events WHERE hook_name = 'UserPromptSubmit' ORDER BY seq ASC LIMIT 1`
13809
- ).get();
13810
- if (promptRow?.prompt) {
13811
- firstPrompt = promptRow.prompt;
13481
+ const prompt = reader.firstUserPrompt();
13482
+ if (prompt) {
13483
+ firstPrompt = prompt;
13812
13484
  }
13813
13485
  }
13814
- return rowToAthenaSession(
13815
- row,
13816
- adapters.map((a) => a.session_id),
13817
- firstPrompt
13818
- );
13486
+ return rowToAthenaSession(row, adapterSessionIds, firstPrompt);
13819
13487
  } catch {
13820
13488
  return null;
13821
13489
  } finally {
13822
- db?.close();
13490
+ reader?.close();
13823
13491
  }
13824
13492
  }
13825
13493
  function listSessions(projectDir) {
13826
13494
  const dir = sessionsDir();
13827
- if (!fs18.existsSync(dir)) return [];
13828
- const entries = fs18.readdirSync(dir, { withFileTypes: true });
13495
+ if (!fs17.existsSync(dir)) return [];
13496
+ const entries = fs17.readdirSync(dir, { withFileTypes: true });
13829
13497
  const sessions = [];
13830
13498
  for (const entry of entries) {
13831
13499
  if (!entry.isDirectory()) continue;
13832
- const dbPath = path15.join(dir, entry.name, "session.db");
13500
+ const dbPath = path14.join(dir, entry.name, "session.db");
13833
13501
  const session = readSessionFromDb(dbPath);
13834
13502
  if (session && (session.eventCount ?? 0) > 0) {
13835
13503
  if (!projectDir || session.projectDir === projectDir) {
@@ -14276,9 +13944,9 @@ function defaultLoadToken(tokenPath) {
14276
13944
  }
14277
13945
 
14278
13946
  // src/infra/config/gatewayClient.ts
14279
- import fs19 from "fs";
13947
+ import fs18 from "fs";
14280
13948
  import os12 from "os";
14281
- import path16 from "path";
13949
+ import path15 from "path";
14282
13950
 
14283
13951
  // src/shared/gateway-protocol/endpoint.ts
14284
13952
  function parseRuntimeEndpoint(value) {
@@ -14334,16 +14002,16 @@ function isRecord2(value) {
14334
14002
  // src/infra/config/gatewayClient.ts
14335
14003
  function resolveGatewayClientConfigPath(env = process.env) {
14336
14004
  const home = env["HOME"] ?? os12.homedir();
14337
- return path16.join(home, ".config", "athena", "gateway.json");
14005
+ return path15.join(home, ".config", "athena", "gateway.json");
14338
14006
  }
14339
14007
  function readGatewayClientConfig(env = process.env) {
14340
14008
  const configPath = resolveGatewayClientConfigPath(env);
14341
- if (!fs19.existsSync(configPath)) {
14009
+ if (!fs18.existsSync(configPath)) {
14342
14010
  return { mode: "local" };
14343
14011
  }
14344
14012
  let parsed;
14345
14013
  try {
14346
- parsed = JSON.parse(fs19.readFileSync(configPath, "utf-8"));
14014
+ parsed = JSON.parse(fs18.readFileSync(configPath, "utf-8"));
14347
14015
  } catch (err) {
14348
14016
  throw new Error(
14349
14017
  `gateway client config ${configPath} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`
@@ -14360,16 +14028,16 @@ function readGatewayClientConfig(env = process.env) {
14360
14028
  function writeGatewayClientConfig(config, env = process.env) {
14361
14029
  const parsed = parseRuntimeEndpoint(config);
14362
14030
  const configPath = resolveGatewayClientConfigPath(env);
14363
- const dir = path16.dirname(configPath);
14364
- fs19.mkdirSync(dir, { recursive: true, mode: 448 });
14365
- fs19.writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n", {
14031
+ const dir = path15.dirname(configPath);
14032
+ fs18.mkdirSync(dir, { recursive: true, mode: 448 });
14033
+ fs18.writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n", {
14366
14034
  encoding: "utf-8",
14367
14035
  mode: 384
14368
14036
  });
14369
14037
  if (process.platform !== "win32") {
14370
14038
  try {
14371
- fs19.chmodSync(dir, 448);
14372
- fs19.chmodSync(configPath, 384);
14039
+ fs18.chmodSync(dir, 448);
14040
+ fs18.chmodSync(configPath, 384);
14373
14041
  } catch {
14374
14042
  }
14375
14043
  }
@@ -14797,14 +14465,11 @@ function sleepOrAbort(ms, signal) {
14797
14465
  }
14798
14466
 
14799
14467
  // src/app/dashboard/dashboardFeedPublisher.ts
14800
- import Database4 from "better-sqlite3";
14801
14468
  function dashboardFeedOutboxPath() {
14802
14469
  return `${ensureDaemonStateDir().dir}/dashboard-feed-outbox.db`;
14803
14470
  }
14804
14471
  function initOutboxSchema(db) {
14805
14472
  db.exec(`
14806
- PRAGMA journal_mode = WAL;
14807
-
14808
14473
  CREATE TABLE IF NOT EXISTS dashboard_feed_outbox (
14809
14474
  delivery_seq INTEGER PRIMARY KEY AUTOINCREMENT,
14810
14475
  instance_id TEXT NOT NULL,
@@ -14838,8 +14503,9 @@ function makeEnvelope(input) {
14838
14503
  };
14839
14504
  }
14840
14505
  function createDashboardFeedOutbox(options = {}) {
14841
- const db = new Database4(options.dbPath ?? dashboardFeedOutboxPath());
14842
- initOutboxSchema(db);
14506
+ const db = openVersionedDb(options.dbPath ?? dashboardFeedOutboxPath(), {
14507
+ migrate: initOutboxSchema
14508
+ });
14843
14509
  const insert = db.prepare(`
14844
14510
  INSERT OR IGNORE INTO dashboard_feed_outbox (
14845
14511
  instance_id,
@@ -15038,6 +14704,257 @@ function createPairedFeedPublisher(options = {}) {
15038
14704
  };
15039
14705
  }
15040
14706
 
14707
+ // src/core/controller/rules.ts
14708
+ function ruleMatches(ruleToolName, toolName) {
14709
+ if (ruleToolName === "*") return true;
14710
+ if (ruleToolName === toolName) return true;
14711
+ if (ruleToolName.endsWith("__*")) {
14712
+ const prefix = ruleToolName.slice(0, -1);
14713
+ return toolName.startsWith(prefix);
14714
+ }
14715
+ return false;
14716
+ }
14717
+ function matchRule(rules, toolName) {
14718
+ const denyMatch = rules.find(
14719
+ (r) => r.action === "deny" && ruleMatches(r.toolName, toolName)
14720
+ );
14721
+ if (denyMatch) return denyMatch;
14722
+ return rules.find(
14723
+ (r) => r.action === "approve" && ruleMatches(r.toolName, toolName)
14724
+ );
14725
+ }
14726
+
14727
+ // src/core/controller/permission.ts
14728
+ var SESSION_APPROVAL_REQUEST_HOOKS = /* @__PURE__ */ new Set([
14729
+ "item/commandExecution/requestApproval",
14730
+ "item/fileChange/requestApproval",
14731
+ "item/permissions/requestApproval"
14732
+ ]);
14733
+ function isScopedPermissionsRequest(hookName) {
14734
+ return hookName === "item/permissions/requestApproval";
14735
+ }
14736
+ function supportsSessionApproval(hookName) {
14737
+ return hookName !== void 0 && SESSION_APPROVAL_REQUEST_HOOKS.has(hookName);
14738
+ }
14739
+ function extractPermissionSnapshot(event) {
14740
+ const data = event.data;
14741
+ const toolNameFromData = typeof data["tool_name"] === "string" ? data["tool_name"] : void 0;
14742
+ const toolInputFromData = typeof data["tool_input"] === "object" && data["tool_input"] !== null ? data["tool_input"] : void 0;
14743
+ const toolUseIdFromData = typeof data["tool_use_id"] === "string" ? data["tool_use_id"] : void 0;
14744
+ return {
14745
+ request_id: event.id,
14746
+ ts: event.timestamp,
14747
+ kind: event.kind,
14748
+ hookName: event.hookName,
14749
+ tool_name: event.toolName ?? toolNameFromData ?? "Unknown",
14750
+ tool_input: toolInputFromData ?? {},
14751
+ tool_use_id: event.toolUseId ?? toolUseIdFromData,
14752
+ suggestions: data.permission_suggestions
14753
+ };
14754
+ }
14755
+
14756
+ // src/core/controller/runtimeController.ts
14757
+ function handleEvent(event, cb) {
14758
+ const eventKind2 = event.kind;
14759
+ const isScoped = isScopedPermissionsRequest(event.hookName);
14760
+ const eventData = event.data;
14761
+ const toolName = event.toolName ?? (typeof eventData["tool_name"] === "string" ? eventData["tool_name"] : void 0);
14762
+ if (eventKind2 === "permission.request" && toolName === "user_input") {
14763
+ cb.enqueueQuestion(event.id);
14764
+ cb.relayQuestion?.(event);
14765
+ return { handled: true };
14766
+ }
14767
+ if (eventKind2 === "permission.request" && toolName) {
14768
+ const rule = matchRule(cb.getRules(), toolName);
14769
+ if (rule?.action === "deny") {
14770
+ return {
14771
+ handled: true,
14772
+ decision: {
14773
+ type: "json",
14774
+ source: "rule",
14775
+ intent: {
14776
+ kind: "permission_deny",
14777
+ reason: `Blocked by rule: ${rule.addedBy}`
14778
+ }
14779
+ }
14780
+ };
14781
+ }
14782
+ if (rule?.action === "approve") {
14783
+ return {
14784
+ handled: true,
14785
+ decision: {
14786
+ type: "json",
14787
+ source: "rule",
14788
+ intent: { kind: "permission_allow" },
14789
+ // Scoped Codex permission grants are session-scoped under
14790
+ // auto rules so the same capability isn't re-prompted.
14791
+ ...isScoped ? { data: { scope: "session" } } : {}
14792
+ }
14793
+ };
14794
+ }
14795
+ cb.enqueuePermission(event);
14796
+ cb.relayPermission?.(event);
14797
+ return { handled: true };
14798
+ }
14799
+ if (eventKind2 === "tool.pre" && toolName === "AskUserQuestion") {
14800
+ cb.enqueueQuestion(event.id);
14801
+ cb.relayQuestion?.(event);
14802
+ return { handled: true };
14803
+ }
14804
+ if (eventKind2 === "tool.pre" && toolName) {
14805
+ const rule = matchRule(cb.getRules(), toolName);
14806
+ if (rule?.action === "deny") {
14807
+ return {
14808
+ handled: true,
14809
+ decision: {
14810
+ type: "json",
14811
+ source: "rule",
14812
+ intent: {
14813
+ kind: "pre_tool_deny",
14814
+ reason: `Blocked by rule: ${rule.addedBy}`
14815
+ }
14816
+ }
14817
+ };
14818
+ }
14819
+ return {
14820
+ handled: true,
14821
+ decision: {
14822
+ type: "json",
14823
+ source: "rule",
14824
+ intent: { kind: "pre_tool_allow" }
14825
+ }
14826
+ };
14827
+ }
14828
+ return { handled: false };
14829
+ }
14830
+
14831
+ // src/core/feed/ingest.ts
14832
+ function ingestRuntimeEvent(event, ctx) {
14833
+ const controllerResult = handleEvent(event, ctx.controllerCallbacks);
14834
+ const feedEvents = ctx.mapper.mapEvent(event);
14835
+ if (ctx.store) {
14836
+ persistOrDegrade(
14837
+ ctx.store,
14838
+ () => ctx.store.recordEvent(event, feedEvents),
14839
+ "recordEvent failed",
14840
+ ctx.onPersistFailure
14841
+ );
14842
+ }
14843
+ return {
14844
+ feedEvents,
14845
+ decision: controllerResult.handled && controllerResult.decision ? controllerResult.decision : null
14846
+ };
14847
+ }
14848
+ function ingestRuntimeDecision(eventId, decision, ctx) {
14849
+ const feedEvent = ctx.mapper.mapDecision(eventId, decision);
14850
+ if (feedEvent && ctx.store) {
14851
+ persistOrDegrade(
14852
+ ctx.store,
14853
+ () => ctx.store.recordFeedEvents([feedEvent]),
14854
+ "recordFeedEvents failed",
14855
+ ctx.onPersistFailure
14856
+ );
14857
+ }
14858
+ return feedEvent;
14859
+ }
14860
+ function persistOrDegrade(store, action, label, onFailure) {
14861
+ try {
14862
+ action();
14863
+ } catch (err) {
14864
+ const reason = err instanceof Error ? err.message : String(err);
14865
+ const message = `${label}: ${reason}`;
14866
+ store.markDegraded(message);
14867
+ onFailure?.(message);
14868
+ }
14869
+ }
14870
+
14871
+ // src/app/runtime/runtimeEventLoop.ts
14872
+ var DASHBOARD_DECISION_POLL_LIMIT = 25;
14873
+ var DASHBOARD_DECISION_POLL_INTERVAL_MS = 1e3;
14874
+ function resolveIngest(source) {
14875
+ return typeof source === "function" ? source() : source;
14876
+ }
14877
+ function attachRuntimeEventLoop(options) {
14878
+ const { runtime } = options;
14879
+ const runEvent = (event) => {
14880
+ options.onEventReceived?.(event);
14881
+ if (options.skipEvent?.(event)) return;
14882
+ const { feedEvents, decision } = ingestRuntimeEvent(
14883
+ event,
14884
+ resolveIngest(options.ingest)
14885
+ );
14886
+ if (decision) {
14887
+ runtime.sendDecision(event.id, decision);
14888
+ }
14889
+ options.emitEventFeed(feedEvents, event);
14890
+ };
14891
+ const runDecision = (eventId, decision) => {
14892
+ options.onDecisionReceived?.(eventId, decision);
14893
+ if (options.skipDecision?.(eventId, decision)) return;
14894
+ options.beforeDecisionIngest?.(eventId, decision);
14895
+ const feedEvent = ingestRuntimeDecision(
14896
+ eventId,
14897
+ decision,
14898
+ resolveIngest(options.ingest)
14899
+ );
14900
+ options.emitDecisionFeed(feedEvent, eventId, decision);
14901
+ };
14902
+ const unsubscribeEvent = runtime.onEvent((event) => {
14903
+ if (options.wrapEvent) {
14904
+ options.wrapEvent(event, () => runEvent(event));
14905
+ } else {
14906
+ runEvent(event);
14907
+ }
14908
+ });
14909
+ const unsubscribeDecision = runtime.onDecision((eventId, decision) => {
14910
+ if (options.wrapDecision) {
14911
+ options.wrapDecision(
14912
+ eventId,
14913
+ decision,
14914
+ () => runDecision(eventId, decision)
14915
+ );
14916
+ } else {
14917
+ runDecision(eventId, decision);
14918
+ }
14919
+ });
14920
+ return {
14921
+ stop() {
14922
+ unsubscribeEvent();
14923
+ unsubscribeDecision();
14924
+ }
14925
+ };
14926
+ }
14927
+ function startDashboardDecisionDrain(options) {
14928
+ const { runtime, inbox, athenaSessionId } = options;
14929
+ const drainOnce = () => {
14930
+ const rows = inbox.pendingForSession({
14931
+ athenaSessionId,
14932
+ limit: DASHBOARD_DECISION_POLL_LIMIT
14933
+ });
14934
+ for (const row of rows) {
14935
+ try {
14936
+ runtime.sendDecision(row.requestId, row.decision);
14937
+ inbox.markConsumed({ id: row.id });
14938
+ } catch (error) {
14939
+ if (!options.onError) throw error;
14940
+ options.onError(error);
14941
+ }
14942
+ }
14943
+ };
14944
+ drainOnce();
14945
+ const timer = setInterval(
14946
+ drainOnce,
14947
+ options.pollIntervalMs ?? DASHBOARD_DECISION_POLL_INTERVAL_MS
14948
+ );
14949
+ options.configureTimer?.(timer);
14950
+ return {
14951
+ drainOnce,
14952
+ stop() {
14953
+ clearInterval(timer);
14954
+ }
14955
+ };
14956
+ }
14957
+
15041
14958
  // src/app/exec/finalMessage.ts
15042
14959
  function findLastMappedAgentMessage(feedEvents) {
15043
14960
  for (let i = feedEvents.length - 1; i >= 0; i--) {
@@ -15088,8 +15005,8 @@ function exitCodeFromFailure(failure) {
15088
15005
  }
15089
15006
 
15090
15007
  // src/app/exec/output.ts
15091
- import fs20 from "fs/promises";
15092
- import path17 from "path";
15008
+ import fs19 from "fs/promises";
15009
+ import path16 from "path";
15093
15010
 
15094
15011
  // src/app/exec/jsonl.ts
15095
15012
  function createExecJsonlEvent(type, data, ts = Date.now()) {
@@ -15128,9 +15045,9 @@ function createExecOutputWriter(options) {
15128
15045
  writeLine(options.stdout, message);
15129
15046
  },
15130
15047
  async writeLastMessage(filePath, message) {
15131
- const absPath = path17.resolve(filePath);
15132
- await fs20.mkdir(path17.dirname(absPath), { recursive: true });
15133
- await fs20.writeFile(absPath, message, "utf-8");
15048
+ const absPath = path16.resolve(filePath);
15049
+ await fs19.mkdir(path16.dirname(absPath), { recursive: true });
15050
+ await fs19.writeFile(absPath, message, "utf-8");
15134
15051
  }
15135
15052
  };
15136
15053
  }
@@ -15209,7 +15126,7 @@ async function runExec(options) {
15209
15126
  store = sessionStoreFactory({
15210
15127
  sessionId: athenaSessionId,
15211
15128
  projectDir: options.projectDir,
15212
- dbPath: options.ephemeral ? ":memory:" : path18.join(sessionsDir(), athenaSessionId, "session.db")
15129
+ dbPath: options.ephemeral ? ":memory:" : path17.join(sessionsDir(), athenaSessionId, "session.db")
15213
15130
  });
15214
15131
  } catch (error) {
15215
15132
  const message = `Failed to initialize session store: ${error instanceof Error ? error.message : String(error)}`;
@@ -15300,23 +15217,6 @@ async function runExec(options) {
15300
15217
  ...options.signal ? { signal: options.signal } : {}
15301
15218
  };
15302
15219
  const dashboardDecisionInbox = options.dashboardDecisionInbox;
15303
- const applyPendingDashboardDecisions = () => {
15304
- if (!dashboardDecisionInbox) return;
15305
- const rows = dashboardDecisionInbox.pendingForSession({
15306
- athenaSessionId,
15307
- limit: 25
15308
- });
15309
- for (const row of rows) {
15310
- try {
15311
- runtime.sendDecision(row.requestId, row.decision);
15312
- dashboardDecisionInbox.markConsumed({ id: row.id });
15313
- } catch (error) {
15314
- output.warn(
15315
- `dashboard decision failed: ${error instanceof Error ? error.message : String(error)}`
15316
- );
15317
- }
15318
- }
15319
- };
15320
15220
  const linkedAdapterSessions = /* @__PURE__ */ new Set();
15321
15221
  function publishFeedEvents(feedEvents) {
15322
15222
  if (feedEvents.length === 0) return;
@@ -15377,60 +15277,57 @@ async function runExec(options) {
15377
15277
  });
15378
15278
  }
15379
15279
  };
15380
- const unsubscribeEvent = runtime.onEvent((runtimeEvent) => {
15381
- adapterSessionId = runtimeEvent.sessionId;
15382
- if (runtimeEvent.sessionId && activeRunId && !linkedAdapterSessions.has(runtimeEvent.sessionId)) {
15383
- linkedAdapterSessions.add(runtimeEvent.sessionId);
15384
- safePersist(
15385
- store,
15386
- () => store.linkAdapterSession(runtimeEvent.sessionId, activeRunId),
15387
- (message) => output.warn(message),
15388
- "linkAdapterSession failed"
15389
- );
15390
- }
15391
- output.emitJsonEvent("runtime.event", {
15392
- id: runtimeEvent.id,
15393
- kind: runtimeEvent.kind,
15394
- hookName: runtimeEvent.hookName,
15395
- sessionId: runtimeEvent.sessionId,
15396
- toolName: runtimeEvent.toolName ?? null,
15397
- data: runtimeEvent.data
15398
- });
15399
- if (latch.hasFailure()) return;
15400
- const { feedEvents, decision } = ingestRuntimeEvent(runtimeEvent, {
15280
+ const runtimeEventLoop = attachRuntimeEventLoop({
15281
+ runtime,
15282
+ ingest: {
15401
15283
  mapper,
15402
15284
  store,
15403
15285
  controllerCallbacks,
15404
15286
  onPersistFailure: (message) => output.warn(message)
15405
- });
15406
- if (decision) {
15407
- runtime.sendDecision(runtimeEvent.id, decision);
15408
- }
15409
- for (const event of feedEvents) {
15410
- if (event.kind === "agent.message") {
15411
- mappedFinalMessage = event.data.message;
15287
+ },
15288
+ onEventReceived: (runtimeEvent) => {
15289
+ adapterSessionId = runtimeEvent.sessionId;
15290
+ if (runtimeEvent.sessionId && activeRunId && !linkedAdapterSessions.has(runtimeEvent.sessionId)) {
15291
+ linkedAdapterSessions.add(runtimeEvent.sessionId);
15292
+ safePersist(
15293
+ store,
15294
+ () => store.linkAdapterSession(runtimeEvent.sessionId, activeRunId),
15295
+ (message) => output.warn(message),
15296
+ "linkAdapterSession failed"
15297
+ );
15412
15298
  }
15413
- }
15414
- publishFeedEvents(feedEvents);
15415
- });
15416
- const unsubscribeDecision = runtime.onDecision(
15417
- (eventId, decision) => {
15299
+ output.emitJsonEvent("runtime.event", {
15300
+ id: runtimeEvent.id,
15301
+ kind: runtimeEvent.kind,
15302
+ hookName: runtimeEvent.hookName,
15303
+ sessionId: runtimeEvent.sessionId,
15304
+ toolName: runtimeEvent.toolName ?? null,
15305
+ data: runtimeEvent.data
15306
+ });
15307
+ },
15308
+ skipEvent: () => latch.hasFailure(),
15309
+ emitEventFeed: (feedEvents) => {
15310
+ for (const event of feedEvents) {
15311
+ if (event.kind === "agent.message") {
15312
+ mappedFinalMessage = event.data.message;
15313
+ }
15314
+ }
15315
+ publishFeedEvents(feedEvents);
15316
+ },
15317
+ onDecisionReceived: (eventId, decision) => {
15418
15318
  output.emitJsonEvent("runtime.decision", {
15419
15319
  eventId,
15420
15320
  decision
15421
15321
  });
15422
- const feedEvent = ingestRuntimeDecision(eventId, decision, {
15423
- mapper,
15424
- store,
15425
- onPersistFailure: (message) => output.warn(message)
15426
- });
15322
+ },
15323
+ emitDecisionFeed: (feedEvent) => {
15427
15324
  if (feedEvent) {
15428
15325
  publishFeedEvents([feedEvent]);
15429
15326
  }
15430
15327
  }
15431
- );
15328
+ });
15432
15329
  let timeoutTimer;
15433
- let dashboardDecisionTimer;
15330
+ let dashboardDecisionDrain;
15434
15331
  if (typeof options.timeoutMs === "number" && options.timeoutMs > 0) {
15435
15332
  timeoutTimer = setTimeout(() => {
15436
15333
  latch.register({
@@ -15451,12 +15348,16 @@ async function runExec(options) {
15451
15348
  status: runtime.getStatus()
15452
15349
  });
15453
15350
  if (dashboardDecisionInbox) {
15454
- applyPendingDashboardDecisions();
15455
- dashboardDecisionTimer = setInterval(
15456
- applyPendingDashboardDecisions,
15457
- options.dashboardDecisionPollIntervalMs ?? 1e3
15458
- );
15459
- dashboardDecisionTimer.unref();
15351
+ dashboardDecisionDrain = startDashboardDecisionDrain({
15352
+ runtime,
15353
+ inbox: dashboardDecisionInbox,
15354
+ athenaSessionId,
15355
+ ...options.dashboardDecisionPollIntervalMs !== void 0 ? { pollIntervalMs: options.dashboardDecisionPollIntervalMs } : {},
15356
+ onError: (error) => output.warn(
15357
+ `dashboard decision failed: ${error instanceof Error ? error.message : String(error)}`
15358
+ ),
15359
+ configureTimer: (timer) => timer.unref()
15360
+ });
15460
15361
  }
15461
15362
  const workflow = options.workflow;
15462
15363
  output.emitJsonEvent("run.started", {
@@ -15543,14 +15444,11 @@ async function runExec(options) {
15543
15444
  if (timeoutTimer) {
15544
15445
  clearTimeout(timeoutTimer);
15545
15446
  }
15546
- if (dashboardDecisionTimer) {
15547
- clearInterval(dashboardDecisionTimer);
15548
- }
15447
+ dashboardDecisionDrain?.stop();
15549
15448
  await writeLastMessageBeforeTerminalCompletion();
15550
15449
  await runBeforeTerminalCompletion();
15551
15450
  await sessionController.kill();
15552
- unsubscribeEvent();
15553
- unsubscribeDecision();
15451
+ runtimeEventLoop.stop();
15554
15452
  if (runtimeStarted) {
15555
15453
  runtime.stop();
15556
15454
  }
@@ -16169,8 +16067,8 @@ async function createRemoteRunEventPublisher({
16169
16067
  // src/app/dashboard/artifactCapture.ts
16170
16068
  import { execFile } from "child_process";
16171
16069
  import crypto3 from "crypto";
16172
- import fs21 from "fs/promises";
16173
- import path19 from "path";
16070
+ import fs20 from "fs/promises";
16071
+ import path18 from "path";
16174
16072
  import { promisify } from "util";
16175
16073
  var execFileAsync = promisify(execFile);
16176
16074
  function parseArtifactUploadSpec(value) {
@@ -16353,21 +16251,21 @@ async function collectArtifactPayloads(input) {
16353
16251
  return payloads;
16354
16252
  }
16355
16253
  async function readWorkspaceFile(projectDir, rel) {
16356
- const absolute = path19.resolve(projectDir, rel);
16357
- const workspaceRoot = await fs21.realpath(projectDir);
16254
+ const absolute = path18.resolve(projectDir, rel);
16255
+ const workspaceRoot = await fs20.realpath(projectDir);
16358
16256
  let stat;
16359
16257
  try {
16360
- stat = await fs21.lstat(absolute);
16258
+ stat = await fs20.lstat(absolute);
16361
16259
  } catch {
16362
16260
  return null;
16363
16261
  }
16364
16262
  if (!stat.isFile() || stat.isSymbolicLink()) return null;
16365
- const real = await fs21.realpath(absolute);
16366
- const relativeToRoot = path19.relative(workspaceRoot, real);
16367
- if (relativeToRoot === ".." || relativeToRoot.startsWith(`..${path19.sep}`) || path19.isAbsolute(relativeToRoot)) {
16263
+ const real = await fs20.realpath(absolute);
16264
+ const relativeToRoot = path18.relative(workspaceRoot, real);
16265
+ if (relativeToRoot === ".." || relativeToRoot.startsWith(`..${path18.sep}`) || path18.isAbsolute(relativeToRoot)) {
16368
16266
  return null;
16369
16267
  }
16370
- return fs21.readFile(absolute);
16268
+ return fs20.readFile(absolute);
16371
16269
  }
16372
16270
  async function gitDiffForAllowedPaths(input) {
16373
16271
  const paths = splitNul(await git(input.projectDir, input.nameArgs)).filter(
@@ -16458,13 +16356,13 @@ var DEFAULT_HARD_DENY = [
16458
16356
  ".ssh/**"
16459
16357
  ];
16460
16358
  function isAllowedRelativePath(rel, hardDeny) {
16461
- if (path19.isAbsolute(rel) || rel.includes("\0")) return false;
16462
- const normalized = path19.posix.normalize(rel.replaceAll(path19.sep, "/"));
16359
+ if (path18.isAbsolute(rel) || rel.includes("\0")) return false;
16360
+ const normalized = path18.posix.normalize(rel.replaceAll(path18.sep, "/"));
16463
16361
  if (normalized === ".." || normalized.startsWith("../")) return false;
16464
16362
  return !hardDeny.some((pattern) => matchesDenyPattern(normalized, pattern));
16465
16363
  }
16466
16364
  function matchesDenyPattern(rel, pattern) {
16467
- const normalized = pattern.replaceAll(path19.sep, "/");
16365
+ const normalized = pattern.replaceAll(path18.sep, "/");
16468
16366
  if (normalized.includes("*") || normalized.includes("?")) {
16469
16367
  return globToRegExp(normalized).test(rel);
16470
16368
  }
@@ -16861,18 +16759,18 @@ async function executeRemoteAssignment({
16861
16759
 
16862
16760
  // src/infra/config/attachmentMirror.ts
16863
16761
  import crypto4 from "crypto";
16864
- import fs22 from "fs";
16762
+ import fs21 from "fs";
16865
16763
  import os13 from "os";
16866
- import path20 from "path";
16764
+ import path19 from "path";
16867
16765
  function attachmentMirrorPath(env = process.env) {
16868
16766
  const home = env["HOME"] ?? os13.homedir();
16869
- return path20.join(home, ".config", "athena", "attachments.json");
16767
+ return path19.join(home, ".config", "athena", "attachments.json");
16870
16768
  }
16871
16769
  function readAttachmentMirror(env = process.env) {
16872
16770
  const file = attachmentMirrorPath(env);
16873
16771
  let raw;
16874
16772
  try {
16875
- raw = fs22.readFileSync(file, "utf-8");
16773
+ raw = fs21.readFileSync(file, "utf-8");
16876
16774
  } catch (err) {
16877
16775
  if (err.code === "ENOENT") return null;
16878
16776
  throw err;
@@ -16896,29 +16794,29 @@ function readAttachmentMirror(env = process.env) {
16896
16794
  function writeAttachmentMirror(mirror, env = process.env) {
16897
16795
  const validated = parseAttachmentMirror(mirror);
16898
16796
  const file = attachmentMirrorPath(env);
16899
- const dir = path20.dirname(file);
16900
- fs22.mkdirSync(dir, { recursive: true, mode: 448 });
16797
+ const dir = path19.dirname(file);
16798
+ fs21.mkdirSync(dir, { recursive: true, mode: 448 });
16901
16799
  const tmp = `${file}.${process.pid}.${crypto4.randomBytes(4).toString("hex")}.tmp`;
16902
- const fd = fs22.openSync(tmp, "w", 384);
16800
+ const fd = fs21.openSync(tmp, "w", 384);
16903
16801
  try {
16904
- fs22.writeSync(fd, JSON.stringify(validated, null, 2) + "\n");
16905
- fs22.fsyncSync(fd);
16802
+ fs21.writeSync(fd, JSON.stringify(validated, null, 2) + "\n");
16803
+ fs21.fsyncSync(fd);
16906
16804
  } finally {
16907
- fs22.closeSync(fd);
16805
+ fs21.closeSync(fd);
16908
16806
  }
16909
16807
  try {
16910
- fs22.renameSync(tmp, file);
16808
+ fs21.renameSync(tmp, file);
16911
16809
  } catch (err) {
16912
16810
  try {
16913
- fs22.unlinkSync(tmp);
16811
+ fs21.unlinkSync(tmp);
16914
16812
  } catch {
16915
16813
  }
16916
16814
  throw err;
16917
16815
  }
16918
16816
  if (process.platform !== "win32") {
16919
16817
  try {
16920
- fs22.chmodSync(dir, 448);
16921
- fs22.chmodSync(file, 384);
16818
+ fs21.chmodSync(dir, 448);
16819
+ fs21.chmodSync(file, 384);
16922
16820
  } catch {
16923
16821
  }
16924
16822
  }
@@ -16926,7 +16824,7 @@ function writeAttachmentMirror(mirror, env = process.env) {
16926
16824
  function removeAttachmentMirror(env = process.env) {
16927
16825
  const file = attachmentMirrorPath(env);
16928
16826
  try {
16929
- fs22.unlinkSync(file);
16827
+ fs21.unlinkSync(file);
16930
16828
  } catch (err) {
16931
16829
  if (err.code !== "ENOENT") throw err;
16932
16830
  }
@@ -17054,7 +16952,6 @@ function createAttachmentReconciler(options) {
17054
16952
  }
17055
16953
 
17056
16954
  // src/app/dashboard/dashboardDecisionInbox.ts
17057
- import Database5 from "better-sqlite3";
17058
16955
  function dashboardDecisionInboxPath() {
17059
16956
  return `${ensureDaemonStateDir().dir}/dashboard-decision-inbox.db`;
17060
16957
  }
@@ -17099,8 +16996,6 @@ function migrateLegacyUniqueConstraint(db) {
17099
16996
  }
17100
16997
  function initInboxSchema(db) {
17101
16998
  db.exec(`
17102
- PRAGMA journal_mode = WAL;
17103
-
17104
16999
  CREATE TABLE IF NOT EXISTS dashboard_decision_inbox (
17105
17000
  id INTEGER PRIMARY KEY AUTOINCREMENT,
17106
17001
  athena_session_id TEXT NOT NULL,
@@ -17122,8 +17017,9 @@ function initInboxSchema(db) {
17122
17017
  `);
17123
17018
  }
17124
17019
  function createDashboardDecisionInbox(options = {}) {
17125
- const db = new Database5(options.dbPath ?? dashboardDecisionInboxPath());
17126
- initInboxSchema(db);
17020
+ const db = openVersionedDb(options.dbPath ?? dashboardDecisionInboxPath(), {
17021
+ migrate: initInboxSchema
17022
+ });
17127
17023
  const upsertUnconsumed = db.prepare(`
17128
17024
  INSERT INTO dashboard_decision_inbox (
17129
17025
  athena_session_id,
@@ -17332,9 +17228,9 @@ function createDashboardPairedExecution(options) {
17332
17228
  }
17333
17229
 
17334
17230
  // src/app/dashboard/remoteWorkspaceResolver.ts
17335
- import fs23 from "fs";
17231
+ import fs22 from "fs";
17336
17232
  import os14 from "os";
17337
- import path21 from "path";
17233
+ import path20 from "path";
17338
17234
  function resolveRemoteWorkspace(assignment, options = {}) {
17339
17235
  const { spec, runId, runnerId } = assignment;
17340
17236
  if (spec.projectDir) {
@@ -17343,14 +17239,14 @@ function resolveRemoteWorkspace(assignment, options = {}) {
17343
17239
  const sessionId = spec.athenaSessionId ?? spec.sessionId;
17344
17240
  const deploymentSlug = deploymentSlugFromUrl(options.dashboardUrl);
17345
17241
  const stateDir = daemonStatePaths(options.env).dir;
17346
- const projectDir = sessionId ? path21.join(
17242
+ const projectDir = sessionId ? path20.join(
17347
17243
  stateDir,
17348
17244
  "remote-workspaces",
17349
17245
  deploymentSlug,
17350
17246
  sanitizePathSegment(runnerId),
17351
17247
  "sessions",
17352
17248
  sanitizePathSegment(sessionId)
17353
- ) : path21.join(
17249
+ ) : path20.join(
17354
17250
  stateDir,
17355
17251
  "remote-workspaces",
17356
17252
  deploymentSlug,
@@ -17359,10 +17255,10 @@ function resolveRemoteWorkspace(assignment, options = {}) {
17359
17255
  sanitizePathSegment(runId)
17360
17256
  );
17361
17257
  try {
17362
- fs23.mkdirSync(projectDir, { recursive: true, mode: 448 });
17258
+ fs22.mkdirSync(projectDir, { recursive: true, mode: 448 });
17363
17259
  if (process.platform !== "win32") {
17364
17260
  try {
17365
- fs23.chmodSync(projectDir, 448);
17261
+ fs22.chmodSync(projectDir, 448);
17366
17262
  } catch {
17367
17263
  }
17368
17264
  }
@@ -17378,8 +17274,8 @@ function resolveRemoteWorkspace(assignment, options = {}) {
17378
17274
  return validateProjectDir(projectDir, options.env);
17379
17275
  }
17380
17276
  function validateProjectDir(projectDir, env = process.env) {
17381
- const resolved = path21.resolve(projectDir);
17382
- if (!path21.isAbsolute(projectDir)) {
17277
+ const resolved = path20.resolve(projectDir);
17278
+ if (!path20.isAbsolute(projectDir)) {
17383
17279
  return {
17384
17280
  kind: "rejected",
17385
17281
  rejection: {
@@ -17388,7 +17284,7 @@ function validateProjectDir(projectDir, env = process.env) {
17388
17284
  }
17389
17285
  };
17390
17286
  }
17391
- const home = path21.resolve(env["HOME"] ?? os14.homedir());
17287
+ const home = path20.resolve(env["HOME"] ?? os14.homedir());
17392
17288
  if (resolved === home) {
17393
17289
  return {
17394
17290
  kind: "rejected",
@@ -17400,7 +17296,7 @@ function validateProjectDir(projectDir, env = process.env) {
17400
17296
  }
17401
17297
  let stat;
17402
17298
  try {
17403
- stat = fs23.statSync(resolved);
17299
+ stat = fs22.statSync(resolved);
17404
17300
  } catch {
17405
17301
  return {
17406
17302
  kind: "rejected",
@@ -17845,18 +17741,18 @@ async function runDashboardRuntimeDaemon(options = {}) {
17845
17741
  }
17846
17742
 
17847
17743
  // src/infra/daemon/pidLock.ts
17848
- import fs24 from "fs";
17744
+ import fs23 from "fs";
17849
17745
  function acquirePidLock(pidPath) {
17850
17746
  const ownPid = process.pid;
17851
17747
  for (let attempt = 0; attempt < 2; attempt += 1) {
17852
17748
  try {
17853
- const fd = fs24.openSync(pidPath, "wx", 384);
17749
+ const fd = fs23.openSync(pidPath, "wx", 384);
17854
17750
  try {
17855
- fs24.writeSync(fd, `${ownPid}
17751
+ fs23.writeSync(fd, `${ownPid}
17856
17752
  `);
17857
- fs24.fsyncSync(fd);
17753
+ fs23.fsyncSync(fd);
17858
17754
  } finally {
17859
- fs24.closeSync(fd);
17755
+ fs23.closeSync(fd);
17860
17756
  }
17861
17757
  return makeHandle(pidPath, ownPid);
17862
17758
  } catch (err) {
@@ -17870,7 +17766,7 @@ function acquirePidLock(pidPath) {
17870
17766
  }
17871
17767
  if (existing.state === "stale") {
17872
17768
  try {
17873
- fs24.unlinkSync(pidPath);
17769
+ fs23.unlinkSync(pidPath);
17874
17770
  } catch (err) {
17875
17771
  if (err.code !== "ENOENT") throw err;
17876
17772
  }
@@ -17884,7 +17780,7 @@ function acquirePidLock(pidPath) {
17884
17780
  function readPidLock(pidPath) {
17885
17781
  let raw;
17886
17782
  try {
17887
- raw = fs24.readFileSync(pidPath, "utf-8");
17783
+ raw = fs23.readFileSync(pidPath, "utf-8");
17888
17784
  } catch (err) {
17889
17785
  if (err.code === "ENOENT") {
17890
17786
  return { state: "absent" };
@@ -17908,9 +17804,9 @@ function makeHandle(pidPath, pid) {
17908
17804
  if (released) return;
17909
17805
  released = true;
17910
17806
  try {
17911
- const raw = fs24.readFileSync(pidPath, "utf-8").trim();
17807
+ const raw = fs23.readFileSync(pidPath, "utf-8").trim();
17912
17808
  if (raw === String(pid)) {
17913
- fs24.unlinkSync(pidPath);
17809
+ fs23.unlinkSync(pidPath);
17914
17810
  }
17915
17811
  } catch (err) {
17916
17812
  if (err.code !== "ENOENT") {
@@ -17936,7 +17832,7 @@ function isProcessAlive(pid) {
17936
17832
  }
17937
17833
 
17938
17834
  // src/infra/daemon/udsIpc.ts
17939
- import fs25 from "fs";
17835
+ import fs24 from "fs";
17940
17836
  import net2 from "net";
17941
17837
 
17942
17838
  // src/infra/daemon/udsFrameCodec.ts
@@ -17981,7 +17877,7 @@ async function startUdsServer(socketPath, handler, log) {
17981
17877
  });
17982
17878
  if (process.platform !== "win32") {
17983
17879
  try {
17984
- fs25.chmodSync(socketPath, 384);
17880
+ fs24.chmodSync(socketPath, 384);
17985
17881
  } catch {
17986
17882
  }
17987
17883
  }
@@ -17991,7 +17887,7 @@ async function startUdsServer(socketPath, handler, log) {
17991
17887
  server.close(() => resolve());
17992
17888
  });
17993
17889
  try {
17994
- fs25.unlinkSync(socketPath);
17890
+ fs24.unlinkSync(socketPath);
17995
17891
  } catch (err) {
17996
17892
  if (err.code !== "ENOENT") {
17997
17893
  }
@@ -18109,7 +18005,7 @@ async function sendUdsRequest(socketPath, request, options = {}) {
18109
18005
  async function unlinkStaleSocket(socketPath) {
18110
18006
  let stat;
18111
18007
  try {
18112
- stat = fs25.statSync(socketPath);
18008
+ stat = fs24.statSync(socketPath);
18113
18009
  } catch (err) {
18114
18010
  if (err.code === "ENOENT") return;
18115
18011
  throw err;
@@ -18136,7 +18032,7 @@ async function unlinkStaleSocket(socketPath) {
18136
18032
  `uds path ${socketPath} is in use by another process; aborting`
18137
18033
  );
18138
18034
  }
18139
- fs25.unlinkSync(socketPath);
18035
+ fs24.unlinkSync(socketPath);
18140
18036
  }
18141
18037
 
18142
18038
  export {
@@ -18154,8 +18050,8 @@ export {
18154
18050
  mergeFeedItems,
18155
18051
  buildPostByToolUseId,
18156
18052
  createFeedMapper,
18157
- ingestRuntimeEvent,
18158
- ingestRuntimeDecision,
18053
+ attachRuntimeEventLoop,
18054
+ startDashboardDecisionDrain,
18159
18055
  generateId,
18160
18056
  daemonStatePaths,
18161
18057
  ensureDaemonStateDir,
@@ -18237,4 +18133,4 @@ export {
18237
18133
  startUdsServer,
18238
18134
  sendUdsRequest
18239
18135
  };
18240
- //# sourceMappingURL=chunk-HXW5N4GE.js.map
18136
+ //# sourceMappingURL=chunk-QBMYQJFX.js.map