@drisp/cli 0.5.26 → 0.5.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{WorkflowInstallWizard-5FENJHBW.js → WorkflowInstallWizard-7OSOOVS2.js} +2 -2
- package/dist/athena-gateway.js +3 -3
- package/dist/{chunk-73V7GXV6.js → chunk-2QV7YSX6.js} +1130 -108
- package/dist/{chunk-YZ7RCBKO.js → chunk-4HWBXCVY.js} +522 -131
- package/dist/{chunk-4NX4WFPB.js → chunk-MSPZOJMJ.js} +2 -2
- package/dist/{chunk-QYB6N2OT.js → chunk-USK5VSQ7.js} +12 -2
- package/dist/{chunk-3U37HT4T.js → chunk-VAFS446Y.js} +2 -2
- package/dist/cli.js +86 -15
- package/dist/dashboard-daemon.js +4 -4
- package/dist/hook-forwarder.js +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ATHENA_HOOK_SOCKET_ENV,
|
|
3
3
|
createAskUserQuestionResult,
|
|
4
|
+
createCompactBlockResult,
|
|
4
5
|
createPermissionRequestAllowResult,
|
|
5
6
|
createPermissionRequestDenyResult,
|
|
6
7
|
createPreToolUseAllowResult,
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
isToolEvent,
|
|
13
14
|
isValidHookEventEnvelope,
|
|
14
15
|
resolveHookSocketPath
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-USK5VSQ7.js";
|
|
16
17
|
import {
|
|
17
18
|
TransportUnreachableError,
|
|
18
19
|
createUdsClientTransport,
|
|
@@ -25,10 +26,12 @@ import {
|
|
|
25
26
|
trackGatewayTransportReconnect,
|
|
26
27
|
writeGatewayTrace,
|
|
27
28
|
wsClientOptionsForEndpoint
|
|
28
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-VAFS446Y.js";
|
|
29
30
|
import {
|
|
31
|
+
DEFAULT_MAX_TURN_TOKEN_COUNT,
|
|
30
32
|
compileWorkflowPlan,
|
|
31
33
|
createWorkflowRunner,
|
|
34
|
+
deserializeRunMemory,
|
|
32
35
|
installWorkflowFromSource,
|
|
33
36
|
isMarketplaceRef,
|
|
34
37
|
readConfig,
|
|
@@ -38,7 +41,7 @@ import {
|
|
|
38
41
|
resolveWorkflow,
|
|
39
42
|
resolveWorkflowInstall,
|
|
40
43
|
resolveWorkflowPlugins
|
|
41
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-2QV7YSX6.js";
|
|
42
45
|
|
|
43
46
|
// src/infra/daemon/stateDir.ts
|
|
44
47
|
import fs from "fs";
|
|
@@ -291,10 +294,17 @@ var RULES = {
|
|
|
291
294
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
292
295
|
canBlock: false
|
|
293
296
|
},
|
|
297
|
+
// Decision-bearing for Handover (ADR 0014): a Workflow Run's orchestrator
|
|
298
|
+
// blocks the compaction and forks the conversation instead. Both claims are
|
|
299
|
+
// required — `canBlock` alone leaves the event off the decision-waiting
|
|
300
|
+
// path and the block reply is never sent. The timeout is deliberately a
|
|
301
|
+
// number, not null: with no decision (non-workflow session, orchestration
|
|
302
|
+
// failure) the adapter fires a passthrough and Claude proceeds with normal
|
|
303
|
+
// vendor compaction — Handover degrades, it never hangs.
|
|
294
304
|
"compact.pre": {
|
|
295
|
-
expectsDecision:
|
|
305
|
+
expectsDecision: true,
|
|
296
306
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
297
|
-
canBlock:
|
|
307
|
+
canBlock: true
|
|
298
308
|
},
|
|
299
309
|
"user.prompt": {
|
|
300
310
|
expectsDecision: false,
|
|
@@ -858,6 +868,8 @@ function mapDecisionToResult(_event, decision) {
|
|
|
858
868
|
return createPreToolUseDenyResult(intent.reason);
|
|
859
869
|
case "stop_block":
|
|
860
870
|
return createStopBlockResult(intent.reason);
|
|
871
|
+
case "compact_block":
|
|
872
|
+
return createCompactBlockResult(intent.reason);
|
|
861
873
|
default:
|
|
862
874
|
return { action: "passthrough" };
|
|
863
875
|
}
|
|
@@ -1565,6 +1577,10 @@ var SYNC_HOOK_EVENTS = [
|
|
|
1565
1577
|
"TaskCreated",
|
|
1566
1578
|
"TaskCompleted",
|
|
1567
1579
|
"ConfigChange",
|
|
1580
|
+
// Handover interception (ADR 0014): the forwarder may reply with a
|
|
1581
|
+
// compaction block. Dispatched async, Claude would ignore the stdout and
|
|
1582
|
+
// silently drop the block.
|
|
1583
|
+
"PreCompact",
|
|
1568
1584
|
// Teardown — see (2) above.
|
|
1569
1585
|
"SessionEnd"
|
|
1570
1586
|
];
|
|
@@ -3417,8 +3433,14 @@ function spawnClaude(options) {
|
|
|
3417
3433
|
cwd: projectDir,
|
|
3418
3434
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3419
3435
|
env: {
|
|
3420
|
-
// Claude compacts around 95% of this window
|
|
3421
|
-
|
|
3436
|
+
// Claude compacts around 95% of this window. The default sits well
|
|
3437
|
+
// under the model window (ADR 0014 §5): Handover needs headroom to
|
|
3438
|
+
// hold the conversation AND emit a Handoff file in the fork — the old
|
|
3439
|
+
// 185k default sat at the window and defeated that. Precedence:
|
|
3440
|
+
// default < process.env < extraEnv, so a workflow's explicit
|
|
3441
|
+
// maxTurnTokenCount (delivered via extraEnv) beats a user env var,
|
|
3442
|
+
// while the bare default stays env-overridable.
|
|
3443
|
+
CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(DEFAULT_MAX_TURN_TOKEN_COUNT),
|
|
3422
3444
|
...process.env,
|
|
3423
3445
|
...extraEnv ?? {},
|
|
3424
3446
|
ATHENA_INSTANCE_ID: String(instanceId),
|
|
@@ -3564,6 +3586,16 @@ function mergeIsolation(base, pluginMcpConfig, perCommand) {
|
|
|
3564
3586
|
...pluginMcpConfig ? { mcpConfig: pluginMcpConfig } : {}
|
|
3565
3587
|
};
|
|
3566
3588
|
}
|
|
3589
|
+
function resolveWorkflowSpawnEnv(workflow) {
|
|
3590
|
+
const maxTurnTokenCount = workflow?.loop?.maxTurnTokenCount;
|
|
3591
|
+
if (maxTurnTokenCount === void 0) {
|
|
3592
|
+
return workflow?.env;
|
|
3593
|
+
}
|
|
3594
|
+
return {
|
|
3595
|
+
...workflow?.env,
|
|
3596
|
+
CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(maxTurnTokenCount)
|
|
3597
|
+
};
|
|
3598
|
+
}
|
|
3567
3599
|
function resolveClaudeSessionId(continuation) {
|
|
3568
3600
|
if (!continuation || continuation.mode === "fresh") {
|
|
3569
3601
|
return void 0;
|
|
@@ -3788,7 +3820,7 @@ function useClaudeProcess(projectDir, instanceId, isolation, pluginMcpConfig, ve
|
|
|
3788
3820
|
pluginMcpConfig,
|
|
3789
3821
|
perCallIsolation
|
|
3790
3822
|
),
|
|
3791
|
-
env: workflow
|
|
3823
|
+
env: resolveWorkflowSpawnEnv(workflow),
|
|
3792
3824
|
...verbose ? {
|
|
3793
3825
|
jqFilter: JQ_ASSISTANT_TEXT_FILTER,
|
|
3794
3826
|
onFilteredStdout: (data) => {
|
|
@@ -3927,6 +3959,7 @@ function createClaudeSessionController(input) {
|
|
|
3927
3959
|
const tokenAccumulator = createTokenAccumulator();
|
|
3928
3960
|
const messageAccumulator = createAssistantMessageAccumulator();
|
|
3929
3961
|
let lastStderr = "";
|
|
3962
|
+
let stderrTail = "";
|
|
3930
3963
|
if (supportsTransportDiagnostics) {
|
|
3931
3964
|
runtime.beginTurn();
|
|
3932
3965
|
}
|
|
@@ -3945,6 +3978,7 @@ function createClaudeSessionController(input) {
|
|
|
3945
3978
|
tokens: tokenAccumulator.getUsage(),
|
|
3946
3979
|
streamMessage: messageAccumulator.getLastMessage(),
|
|
3947
3980
|
lastStderr,
|
|
3981
|
+
stderrTail,
|
|
3948
3982
|
diagnostics: supportsTransportDiagnostics ? { transport: runtime.getTransportStats() } : void 0
|
|
3949
3983
|
});
|
|
3950
3984
|
};
|
|
@@ -3959,7 +3993,7 @@ function createClaudeSessionController(input) {
|
|
|
3959
3993
|
input.pluginMcpConfig,
|
|
3960
3994
|
configOverride
|
|
3961
3995
|
),
|
|
3962
|
-
env: input.workflow
|
|
3996
|
+
env: resolveWorkflowSpawnEnv(input.workflow),
|
|
3963
3997
|
onStdout: (data) => {
|
|
3964
3998
|
tokenAccumulator.feed(data);
|
|
3965
3999
|
messageAccumulator.feed(data);
|
|
@@ -3972,6 +4006,7 @@ function createClaudeSessionController(input) {
|
|
|
3972
4006
|
if (!lastStderr) {
|
|
3973
4007
|
lastStderr = trimmed;
|
|
3974
4008
|
}
|
|
4009
|
+
stderrTail = (stderrTail + "\n" + trimmed).slice(-2048).trim();
|
|
3975
4010
|
if (!input.verbose) return;
|
|
3976
4011
|
onStderrLine?.(trimmed);
|
|
3977
4012
|
},
|
|
@@ -5796,6 +5831,10 @@ function mapToV2Decision(event, decision) {
|
|
|
5796
5831
|
return { decision: wantsSessionApproval ? "acceptForSession" : "accept" };
|
|
5797
5832
|
case "stop_block":
|
|
5798
5833
|
return { decision: "cancel" };
|
|
5834
|
+
// Claude-specific Handover interception; Codex has no PreCompact
|
|
5835
|
+
// approval, so an (unexpected) compact_block on an approval request
|
|
5836
|
+
// falls through to accept like any other unmappable intent.
|
|
5837
|
+
case "compact_block":
|
|
5799
5838
|
default:
|
|
5800
5839
|
return { decision: wantsSessionApproval ? "acceptForSession" : "accept" };
|
|
5801
5840
|
}
|
|
@@ -6914,7 +6953,7 @@ function resolveIsolation(preset) {
|
|
|
6914
6953
|
case "strict":
|
|
6915
6954
|
return { approvalPolicy: "on-request", sandbox: "read-only" };
|
|
6916
6955
|
case "permissive":
|
|
6917
|
-
return { approvalPolicy: "
|
|
6956
|
+
return { approvalPolicy: "never", sandbox: "danger-full-access" };
|
|
6918
6957
|
case "minimal":
|
|
6919
6958
|
case void 0:
|
|
6920
6959
|
return { approvalPolicy: "on-request", sandbox: "workspace-write" };
|
|
@@ -6933,7 +6972,11 @@ function buildCodexPromptOptions(input) {
|
|
|
6933
6972
|
agentRoots: input.workflowPlan?.agentRoots && input.workflowPlan.agentRoots.length > 0 ? input.workflowPlan.agentRoots : void 0,
|
|
6934
6973
|
plugins: resolveCodexWorkflowPlugins(input.workflowPlan),
|
|
6935
6974
|
config: {
|
|
6936
|
-
|
|
6975
|
+
// The harness-neutral maxTurnTokenCount (ADR 0014 §5): the bound sits
|
|
6976
|
+
// well under the model window so a Handover has headroom to hold the
|
|
6977
|
+
// conversation and emit a Handoff file — the old 175k default sat at
|
|
6978
|
+
// the window and defeated that.
|
|
6979
|
+
model_auto_compact_token_limit: input.workflowPlan?.workflow.loop?.maxTurnTokenCount ?? DEFAULT_MAX_TURN_TOKEN_COUNT,
|
|
6937
6980
|
// Steer Codex's history compaction toward a handoff-style summary.
|
|
6938
6981
|
// `compact_prompt` replaces the default summarization prompt.
|
|
6939
6982
|
compact_prompt: HANDOFF_COMPACT_PROMPT,
|
|
@@ -7638,6 +7681,89 @@ function shouldResolveWorkflow({
|
|
|
7638
7681
|
return Boolean(workflowName);
|
|
7639
7682
|
}
|
|
7640
7683
|
|
|
7684
|
+
// src/core/workflows/builtins/handoffSkill.ts
|
|
7685
|
+
import fs16 from "fs";
|
|
7686
|
+
import os11 from "os";
|
|
7687
|
+
import path14 from "path";
|
|
7688
|
+
var DEFAULT_HANDOFF_FILE_PATH = ".athena/handoff.md";
|
|
7689
|
+
var PLUGIN_MANIFEST = `{
|
|
7690
|
+
"name": "athena-handoff",
|
|
7691
|
+
"description": "Athena's first-party handoff skill: distill the current conversation into a Handoff file",
|
|
7692
|
+
"version": "1.0.0"
|
|
7693
|
+
}
|
|
7694
|
+
`;
|
|
7695
|
+
var SKILL_CONTENT = `---
|
|
7696
|
+
name: handoff
|
|
7697
|
+
description: Distill the current conversation into a Handoff file on disk so a fresh agent session can continue the work without re-reading the history. Use when asked to hand off, or before context is reset.
|
|
7698
|
+
argument-hint: '[output-path] \u2014 where to write the Handoff file (default: ${DEFAULT_HANDOFF_FILE_PATH})'
|
|
7699
|
+
user-invocable: false
|
|
7700
|
+
---
|
|
7701
|
+
|
|
7702
|
+
# Handoff
|
|
7703
|
+
|
|
7704
|
+
Write a **Handoff file**: a distillation of this conversation that lets a fresh
|
|
7705
|
+
agent session \u2014 with none of your context \u2014 continue the work seamlessly.
|
|
7706
|
+
|
|
7707
|
+
## Output path
|
|
7708
|
+
|
|
7709
|
+
Write the file to: \`$ARGUMENTS\`
|
|
7710
|
+
|
|
7711
|
+
If no path was provided above (it reads "(none provided)"), write to
|
|
7712
|
+
\`${DEFAULT_HANDOFF_FILE_PATH}\` relative to the current working directory.
|
|
7713
|
+
Create parent directories as needed. Overwrite an existing file at that path.
|
|
7714
|
+
|
|
7715
|
+
## What the file must contain
|
|
7716
|
+
|
|
7717
|
+
Structure the Handoff file with these sections, in this order:
|
|
7718
|
+
|
|
7719
|
+
1. **Task and status** \u2014 what we are trying to accomplish, and exactly where it
|
|
7720
|
+
stands right now: what is done and verified, what is in flight, what has not
|
|
7721
|
+
been started. Include the concrete state of any in-flight step (e.g. "test X
|
|
7722
|
+
written but failing on Y").
|
|
7723
|
+
2. **Decisions and rationale** \u2014 every decision already made, each with the
|
|
7724
|
+
*why*. A fresh session that doesn't know why a decision was made will
|
|
7725
|
+
re-litigate it and may silently reverse it.
|
|
7726
|
+
3. **Open questions** \u2014 anything unresolved, ambiguous, or awaiting an answer,
|
|
7727
|
+
including from whom or where the answer should come.
|
|
7728
|
+
4. **Files touched** \u2014 every file read, written, or relevant, referenced **by
|
|
7729
|
+
path** (repo-relative where possible). Never paste file contents \u2014 the next
|
|
7730
|
+
session can read the files itself.
|
|
7731
|
+
5. **Suggested next steps / skills** \u2014 what the next session should do first,
|
|
7732
|
+
and which skills, commands, or workflows to invoke for it.
|
|
7733
|
+
|
|
7734
|
+
## Rules
|
|
7735
|
+
|
|
7736
|
+
- **Reference, don't duplicate.** Point to existing artifacts \u2014 PRDs, plans,
|
|
7737
|
+
ADRs, issues, PRs, commits, diffs, trackers \u2014 by path or URL instead of
|
|
7738
|
+
copying their content into the Handoff file.
|
|
7739
|
+
- **Redact secrets.** Never write API keys, passwords, tokens, credentials, or
|
|
7740
|
+
personally identifiable information into the file. Replace any such value
|
|
7741
|
+
with \`[REDACTED]\` and, if needed, name where the real value lives (e.g. an
|
|
7742
|
+
env var name).
|
|
7743
|
+
- **Be selective, not exhaustive.** The file is a briefing, not a transcript.
|
|
7744
|
+
Everything the next session needs; nothing it can rediscover cheaply.
|
|
7745
|
+
- After writing the file, reply with only the absolute path of the file you
|
|
7746
|
+
wrote \u2014 no other prose.
|
|
7747
|
+
`;
|
|
7748
|
+
function writeIfChanged(filePath, content) {
|
|
7749
|
+
if (!fs16.existsSync(filePath) || fs16.readFileSync(filePath, "utf-8") !== content) {
|
|
7750
|
+
fs16.mkdirSync(path14.dirname(filePath), { recursive: true });
|
|
7751
|
+
fs16.writeFileSync(filePath, content, "utf-8");
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7754
|
+
function ensureHandoffSkillPlugin(baseDir = path14.join(os11.homedir(), ".config", "athena", "builtins")) {
|
|
7755
|
+
const pluginDir = path14.join(baseDir, "handoff");
|
|
7756
|
+
writeIfChanged(
|
|
7757
|
+
path14.join(pluginDir, ".claude-plugin", "plugin.json"),
|
|
7758
|
+
PLUGIN_MANIFEST
|
|
7759
|
+
);
|
|
7760
|
+
writeIfChanged(
|
|
7761
|
+
path14.join(pluginDir, "skills", "handoff", "SKILL.md"),
|
|
7762
|
+
SKILL_CONTENT
|
|
7763
|
+
);
|
|
7764
|
+
return pluginDir;
|
|
7765
|
+
}
|
|
7766
|
+
|
|
7641
7767
|
// src/app/runtime/createRuntime.ts
|
|
7642
7768
|
var DEFAULT_HARNESS = "claude-code";
|
|
7643
7769
|
function createRuntime(input) {
|
|
@@ -7708,6 +7834,15 @@ function bootstrapRuntimeConfig({
|
|
|
7708
7834
|
workflowPluginDirs = workflowResolvedPlugins.map(
|
|
7709
7835
|
(plugin) => plugin.claudeArtifactDir
|
|
7710
7836
|
);
|
|
7837
|
+
if (pluginDelivery.mergeWorkflowPluginDirs) {
|
|
7838
|
+
try {
|
|
7839
|
+
workflowPluginDirs.push(ensureHandoffSkillPlugin());
|
|
7840
|
+
} catch (error) {
|
|
7841
|
+
warnings.push(
|
|
7842
|
+
`Failed to materialize the handoff skill plugin: ${error instanceof Error ? error.message : String(error)}`
|
|
7843
|
+
);
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7711
7846
|
}
|
|
7712
7847
|
const globalPluginResolution = resolvePluginDirs(globalConfig.plugins);
|
|
7713
7848
|
const projectPluginResolution = resolvePluginDirs(projectConfig.plugins);
|
|
@@ -7807,7 +7942,7 @@ var EXEC_EXIT_CODE = {
|
|
|
7807
7942
|
|
|
7808
7943
|
// src/app/exec/runner.ts
|
|
7809
7944
|
import crypto2 from "crypto";
|
|
7810
|
-
import
|
|
7945
|
+
import path18 from "path";
|
|
7811
7946
|
|
|
7812
7947
|
// src/core/feed/entities.ts
|
|
7813
7948
|
var ActorRegistry = class {
|
|
@@ -8180,27 +8315,27 @@ function generateNeutralTitle(event, g) {
|
|
|
8180
8315
|
}
|
|
8181
8316
|
|
|
8182
8317
|
// src/core/feed/transcript.ts
|
|
8183
|
-
import * as
|
|
8318
|
+
import * as fs17 from "fs";
|
|
8184
8319
|
function createTranscriptReader() {
|
|
8185
8320
|
const offsets = /* @__PURE__ */ new Map();
|
|
8186
8321
|
function readNewAssistantMessages(transcriptPath) {
|
|
8187
8322
|
const offset = offsets.get(transcriptPath) ?? 0;
|
|
8188
8323
|
let fileSize;
|
|
8189
8324
|
try {
|
|
8190
|
-
fileSize =
|
|
8325
|
+
fileSize = fs17.statSync(transcriptPath).size;
|
|
8191
8326
|
if (fileSize <= offset) return [];
|
|
8192
8327
|
} catch {
|
|
8193
8328
|
return [];
|
|
8194
8329
|
}
|
|
8195
8330
|
let fd;
|
|
8196
8331
|
try {
|
|
8197
|
-
fd =
|
|
8332
|
+
fd = fs17.openSync(transcriptPath, "r");
|
|
8198
8333
|
} catch {
|
|
8199
8334
|
return [];
|
|
8200
8335
|
}
|
|
8201
8336
|
try {
|
|
8202
8337
|
const buf = Buffer.alloc(fileSize - offset);
|
|
8203
|
-
const bytesRead =
|
|
8338
|
+
const bytesRead = fs17.readSync(fd, buf, 0, buf.length, offset);
|
|
8204
8339
|
let lastNewline = -1;
|
|
8205
8340
|
for (let i = bytesRead - 1; i >= 0; i--) {
|
|
8206
8341
|
if (buf[i] === 10) {
|
|
@@ -8244,7 +8379,7 @@ function createTranscriptReader() {
|
|
|
8244
8379
|
}
|
|
8245
8380
|
return messages;
|
|
8246
8381
|
} finally {
|
|
8247
|
-
|
|
8382
|
+
fs17.closeSync(fd);
|
|
8248
8383
|
}
|
|
8249
8384
|
}
|
|
8250
8385
|
function getOffset(transcriptPath) {
|
|
@@ -10313,8 +10448,34 @@ function createFeedMapper(bootstrap) {
|
|
|
10313
10448
|
};
|
|
10314
10449
|
}
|
|
10315
10450
|
|
|
10451
|
+
// src/core/feed/syntheticEvents.ts
|
|
10452
|
+
var HOOK_NAME_BY_KIND = {
|
|
10453
|
+
"task.created": "TaskCreated",
|
|
10454
|
+
"task.completed": "TaskCompleted"
|
|
10455
|
+
};
|
|
10456
|
+
function buildSyntheticTaskEvent(kind, sessionId, data) {
|
|
10457
|
+
const hookName = HOOK_NAME_BY_KIND[kind];
|
|
10458
|
+
return {
|
|
10459
|
+
id: `${kind}-${generateId()}`,
|
|
10460
|
+
timestamp: Date.now(),
|
|
10461
|
+
kind,
|
|
10462
|
+
data,
|
|
10463
|
+
hookName,
|
|
10464
|
+
sessionId,
|
|
10465
|
+
context: { cwd: "", transcriptPath: "" },
|
|
10466
|
+
interaction: { expectsDecision: false },
|
|
10467
|
+
payload: {
|
|
10468
|
+
hook_event_name: hookName,
|
|
10469
|
+
session_id: sessionId,
|
|
10470
|
+
transcript_path: "",
|
|
10471
|
+
cwd: "",
|
|
10472
|
+
...data
|
|
10473
|
+
}
|
|
10474
|
+
};
|
|
10475
|
+
}
|
|
10476
|
+
|
|
10316
10477
|
// src/infra/sessions/schema.ts
|
|
10317
|
-
var SCHEMA_VERSION =
|
|
10478
|
+
var SCHEMA_VERSION = 9;
|
|
10318
10479
|
var applySessionSchema = (db, fromVersion) => {
|
|
10319
10480
|
db.exec(`
|
|
10320
10481
|
CREATE TABLE IF NOT EXISTS session (
|
|
@@ -10377,6 +10538,18 @@ var applySessionSchema = (db, fromVersion) => {
|
|
|
10377
10538
|
status TEXT NOT NULL DEFAULT 'running',
|
|
10378
10539
|
stop_reason TEXT,
|
|
10379
10540
|
tracker_path TEXT,
|
|
10541
|
+
-- Vendor session id (Claude Code session / Codex thread) of the Run's
|
|
10542
|
+
-- most recent Turn's Agent Session. Every resume- and fork-based
|
|
10543
|
+
-- transition (Nudge, Retry, human-resume, Handover) depends on it
|
|
10544
|
+
-- surviving a process restart (ADR 0014).
|
|
10545
|
+
adapter_session_id TEXT,
|
|
10546
|
+
-- Opaque JSON snapshot of the run-loop reducer's RunMemory (nudge/
|
|
10547
|
+
-- retry streaks, last tracker hash, in-flight stop prompt/
|
|
10548
|
+
-- continuation), so a resumed or woken process rehydrates the
|
|
10549
|
+
-- reducer instead of restarting its counters (ADR 0016). Written
|
|
10550
|
+
-- and read only by runMachine.ts's serializeRunMemory/
|
|
10551
|
+
-- deserializeRunMemory \u2014 this layer stores it opaquely.
|
|
10552
|
+
run_memory_json TEXT,
|
|
10380
10553
|
FOREIGN KEY (session_id) REFERENCES session(id)
|
|
10381
10554
|
);
|
|
10382
10555
|
|
|
@@ -10476,6 +10649,26 @@ var applySessionSchema = (db, fromVersion) => {
|
|
|
10476
10649
|
UPDATE schema_version SET version = 7;
|
|
10477
10650
|
`);
|
|
10478
10651
|
}
|
|
10652
|
+
const versionAfterV7 = db.prepare("SELECT version FROM schema_version").get().version;
|
|
10653
|
+
if (versionAfterV7 === 7) {
|
|
10654
|
+
const hasColumn = db.prepare(
|
|
10655
|
+
`SELECT COUNT(*) AS n FROM pragma_table_info('workflow_runs') WHERE name = 'adapter_session_id'`
|
|
10656
|
+
).get().n > 0;
|
|
10657
|
+
if (!hasColumn) {
|
|
10658
|
+
db.exec("ALTER TABLE workflow_runs ADD COLUMN adapter_session_id TEXT;");
|
|
10659
|
+
}
|
|
10660
|
+
db.exec("UPDATE schema_version SET version = 8;");
|
|
10661
|
+
}
|
|
10662
|
+
const versionAfterV8 = db.prepare("SELECT version FROM schema_version").get().version;
|
|
10663
|
+
if (versionAfterV8 === 8) {
|
|
10664
|
+
const hasRunMemoryColumn = db.prepare(
|
|
10665
|
+
`SELECT COUNT(*) AS n FROM pragma_table_info('workflow_runs') WHERE name = 'run_memory_json'`
|
|
10666
|
+
).get().n > 0;
|
|
10667
|
+
if (!hasRunMemoryColumn) {
|
|
10668
|
+
db.exec("ALTER TABLE workflow_runs ADD COLUMN run_memory_json TEXT;");
|
|
10669
|
+
}
|
|
10670
|
+
db.exec("UPDATE schema_version SET version = 9;");
|
|
10671
|
+
}
|
|
10479
10672
|
};
|
|
10480
10673
|
var SESSION_SCHEMA = {
|
|
10481
10674
|
version: SCHEMA_VERSION,
|
|
@@ -10546,13 +10739,15 @@ function createSessionStore(opts) {
|
|
|
10546
10739
|
"UPDATE session SET event_count = event_count + ? WHERE id = ?"
|
|
10547
10740
|
);
|
|
10548
10741
|
const upsertRun = db.prepare(
|
|
10549
|
-
`INSERT INTO workflow_runs (id, session_id, workflow_name, started_at, iteration, max_iterations, status, stop_reason, tracker_path)
|
|
10550
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
10742
|
+
`INSERT INTO workflow_runs (id, session_id, workflow_name, started_at, iteration, max_iterations, status, stop_reason, tracker_path, adapter_session_id, run_memory_json)
|
|
10743
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
10551
10744
|
ON CONFLICT(id) DO UPDATE SET
|
|
10552
10745
|
iteration = excluded.iteration,
|
|
10553
10746
|
status = excluded.status,
|
|
10554
10747
|
stop_reason = excluded.stop_reason,
|
|
10555
|
-
|
|
10748
|
+
adapter_session_id = COALESCE(excluded.adapter_session_id, adapter_session_id),
|
|
10749
|
+
run_memory_json = COALESCE(excluded.run_memory_json, run_memory_json),
|
|
10750
|
+
ended_at = CASE WHEN excluded.status NOT IN ('running', 'awaiting_attention') THEN ? ELSE ended_at END`
|
|
10556
10751
|
);
|
|
10557
10752
|
const selectLatestRun = db.prepare(
|
|
10558
10753
|
`SELECT * FROM workflow_runs WHERE session_id = ? ORDER BY started_at DESC, rowid DESC LIMIT 1`
|
|
@@ -10718,7 +10913,7 @@ function createSessionStore(opts) {
|
|
|
10718
10913
|
}
|
|
10719
10914
|
function persistRun(snapshot) {
|
|
10720
10915
|
const now2 = Date.now();
|
|
10721
|
-
const endedAt = snapshot.status !== "running" ? now2 : null;
|
|
10916
|
+
const endedAt = snapshot.status !== "running" && snapshot.status !== "awaiting_attention" ? now2 : null;
|
|
10722
10917
|
upsertRun.run(
|
|
10723
10918
|
snapshot.runId,
|
|
10724
10919
|
snapshot.sessionId,
|
|
@@ -10729,6 +10924,8 @@ function createSessionStore(opts) {
|
|
|
10729
10924
|
snapshot.status,
|
|
10730
10925
|
snapshot.stopReason ?? null,
|
|
10731
10926
|
snapshot.trackerPath ?? null,
|
|
10927
|
+
snapshot.adapterSessionId ?? null,
|
|
10928
|
+
snapshot.runMemoryJson ?? null,
|
|
10732
10929
|
endedAt
|
|
10733
10930
|
);
|
|
10734
10931
|
}
|
|
@@ -10745,7 +10942,9 @@ function createSessionStore(opts) {
|
|
|
10745
10942
|
maxIterations: row.max_iterations,
|
|
10746
10943
|
status: row.status,
|
|
10747
10944
|
stopReason: row.stop_reason ?? void 0,
|
|
10748
|
-
trackerPath: row.tracker_path ?? void 0
|
|
10945
|
+
trackerPath: row.tracker_path ?? void 0,
|
|
10946
|
+
adapterSessionId: row.adapter_session_id ?? void 0,
|
|
10947
|
+
runMemoryJson: row.run_memory_json ?? void 0
|
|
10749
10948
|
};
|
|
10750
10949
|
}
|
|
10751
10950
|
function linkAdapterSession(adapterSessionId, runId) {
|
|
@@ -11453,8 +11652,8 @@ var PRIMARY_INPUT_EXTRACTORS = {
|
|
|
11453
11652
|
return compactText(colonIdx >= 0 ? name.slice(colonIdx + 1) : name, 80);
|
|
11454
11653
|
},
|
|
11455
11654
|
NotebookEdit: (input) => {
|
|
11456
|
-
const
|
|
11457
|
-
return
|
|
11655
|
+
const path22 = String(input.notebook_path ?? "");
|
|
11656
|
+
return path22 ? shortenPath(path22) : "";
|
|
11458
11657
|
},
|
|
11459
11658
|
AskUserQuestion: (input) => {
|
|
11460
11659
|
const questions = input.questions;
|
|
@@ -12734,16 +12933,16 @@ function extractDomain(url) {
|
|
|
12734
12933
|
}
|
|
12735
12934
|
}
|
|
12736
12935
|
function filePathSegments(input) {
|
|
12737
|
-
const
|
|
12738
|
-
if (typeof
|
|
12739
|
-
const { prefix, filename } = shortenPathStructured(
|
|
12936
|
+
const path22 = prop2(input, "file_path") ?? prop2(input, "notebook_path") ?? "";
|
|
12937
|
+
if (typeof path22 !== "string" || !path22) return [];
|
|
12938
|
+
const { prefix, filename } = shortenPathStructured(path22);
|
|
12740
12939
|
if (prefix && filename) {
|
|
12741
12940
|
return [
|
|
12742
12941
|
{ text: prefix, role: "target" },
|
|
12743
12942
|
{ text: filename, role: "filename" }
|
|
12744
12943
|
];
|
|
12745
12944
|
}
|
|
12746
|
-
return [{ text: filename ||
|
|
12945
|
+
return [{ text: filename || path22, role: "filename" }];
|
|
12747
12946
|
}
|
|
12748
12947
|
function grepSegments(input) {
|
|
12749
12948
|
const pattern = String(prop2(input, "pattern") ?? "");
|
|
@@ -13911,6 +14110,30 @@ function openSessionDbReadonly(dbPath, options = {}) {
|
|
|
13911
14110
|
for (const row of rows) counts[row.hook_name] = row.count;
|
|
13912
14111
|
return counts;
|
|
13913
14112
|
},
|
|
14113
|
+
latestWorkflowRun() {
|
|
14114
|
+
try {
|
|
14115
|
+
const row = db.prepare(
|
|
14116
|
+
"SELECT * FROM workflow_runs ORDER BY started_at DESC, rowid DESC LIMIT 1"
|
|
14117
|
+
).get();
|
|
14118
|
+
if (!row) return void 0;
|
|
14119
|
+
return {
|
|
14120
|
+
id: row["id"],
|
|
14121
|
+
sessionId: row["session_id"],
|
|
14122
|
+
workflowName: row["workflow_name"] ?? void 0,
|
|
14123
|
+
startedAt: row["started_at"],
|
|
14124
|
+
endedAt: row["ended_at"] ?? void 0,
|
|
14125
|
+
iteration: row["iteration"],
|
|
14126
|
+
maxIterations: row["max_iterations"],
|
|
14127
|
+
status: row["status"],
|
|
14128
|
+
stopReason: row["stop_reason"] ?? void 0,
|
|
14129
|
+
trackerPath: row["tracker_path"] ?? void 0,
|
|
14130
|
+
adapterSessionId: row["adapter_session_id"] ?? void 0,
|
|
14131
|
+
runMemoryJson: row["run_memory_json"] ?? void 0
|
|
14132
|
+
};
|
|
14133
|
+
} catch {
|
|
14134
|
+
return void 0;
|
|
14135
|
+
}
|
|
14136
|
+
},
|
|
13914
14137
|
feedEvents() {
|
|
13915
14138
|
const rows = db.prepare("SELECT data FROM feed_events ORDER BY seq").all();
|
|
13916
14139
|
return rows.map((r) => JSON.parse(r.data));
|
|
@@ -13922,17 +14145,17 @@ function openSessionDbReadonly(dbPath, options = {}) {
|
|
|
13922
14145
|
}
|
|
13923
14146
|
|
|
13924
14147
|
// src/infra/sessions/registry.ts
|
|
13925
|
-
import
|
|
13926
|
-
import
|
|
13927
|
-
import
|
|
14148
|
+
import fs18 from "fs";
|
|
14149
|
+
import os12 from "os";
|
|
14150
|
+
import path15 from "path";
|
|
13928
14151
|
function sessionsDir() {
|
|
13929
|
-
return
|
|
14152
|
+
return path15.join(os12.homedir(), ".config", "athena", "sessions");
|
|
13930
14153
|
}
|
|
13931
14154
|
function sessionDbPath(sessionId) {
|
|
13932
|
-
return
|
|
14155
|
+
return path15.join(sessionsDir(), sessionId, "session.db");
|
|
13933
14156
|
}
|
|
13934
14157
|
function readSessionFromDb(dbPath) {
|
|
13935
|
-
if (!
|
|
14158
|
+
if (!fs18.existsSync(dbPath)) return null;
|
|
13936
14159
|
let reader;
|
|
13937
14160
|
try {
|
|
13938
14161
|
reader = openSessionDbReadonly(dbPath);
|
|
@@ -13959,12 +14182,12 @@ function readSessionFromDb(dbPath) {
|
|
|
13959
14182
|
}
|
|
13960
14183
|
function listSessions(projectDir) {
|
|
13961
14184
|
const dir = sessionsDir();
|
|
13962
|
-
if (!
|
|
13963
|
-
const entries =
|
|
14185
|
+
if (!fs18.existsSync(dir)) return [];
|
|
14186
|
+
const entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
13964
14187
|
const sessions = [];
|
|
13965
14188
|
for (const entry of entries) {
|
|
13966
14189
|
if (!entry.isDirectory()) continue;
|
|
13967
|
-
const dbPath =
|
|
14190
|
+
const dbPath = path15.join(dir, entry.name, "session.db");
|
|
13968
14191
|
const session = readSessionFromDb(dbPath);
|
|
13969
14192
|
if (session && (session.eventCount ?? 0) > 0) {
|
|
13970
14193
|
if (!projectDir || session.projectDir === projectDir) {
|
|
@@ -13981,6 +14204,44 @@ function getMostRecentAthenaSession(projectDir) {
|
|
|
13981
14204
|
const sessions = listSessions(projectDir);
|
|
13982
14205
|
return sessions[0] ?? null;
|
|
13983
14206
|
}
|
|
14207
|
+
function getLatestRunForSession(sessionId, baseDir) {
|
|
14208
|
+
const dbPath = path15.join(baseDir ?? sessionsDir(), sessionId, "session.db");
|
|
14209
|
+
if (!fs18.existsSync(dbPath)) return null;
|
|
14210
|
+
let reader;
|
|
14211
|
+
try {
|
|
14212
|
+
reader = openSessionDbReadonly(dbPath);
|
|
14213
|
+
return reader.latestWorkflowRun() ?? null;
|
|
14214
|
+
} catch {
|
|
14215
|
+
return null;
|
|
14216
|
+
} finally {
|
|
14217
|
+
reader?.close();
|
|
14218
|
+
}
|
|
14219
|
+
}
|
|
14220
|
+
function listAwaitingAttentionRuns(projectDir, baseDir) {
|
|
14221
|
+
const dir = baseDir ?? sessionsDir();
|
|
14222
|
+
if (!fs18.existsSync(dir)) return [];
|
|
14223
|
+
const results = [];
|
|
14224
|
+
for (const entry of fs18.readdirSync(dir, { withFileTypes: true })) {
|
|
14225
|
+
if (!entry.isDirectory()) continue;
|
|
14226
|
+
const dbPath = path15.join(dir, entry.name, "session.db");
|
|
14227
|
+
const session = readSessionFromDb(dbPath);
|
|
14228
|
+
if (!session) continue;
|
|
14229
|
+
if (projectDir && session.projectDir !== projectDir) continue;
|
|
14230
|
+
const run = getLatestRunForSession(entry.name, dir);
|
|
14231
|
+
if (!run || run.status !== "awaiting_attention") continue;
|
|
14232
|
+
results.push({
|
|
14233
|
+
athenaSessionId: session.id,
|
|
14234
|
+
projectDir: session.projectDir,
|
|
14235
|
+
runId: run.id,
|
|
14236
|
+
workflowName: run.workflowName,
|
|
14237
|
+
stopReason: run.stopReason,
|
|
14238
|
+
adapterSessionId: run.adapterSessionId,
|
|
14239
|
+
startedAt: run.startedAt,
|
|
14240
|
+
sessionUpdatedAt: session.updatedAt
|
|
14241
|
+
});
|
|
14242
|
+
}
|
|
14243
|
+
return results.sort((a, b) => b.sessionUpdatedAt - a.sessionUpdatedAt);
|
|
14244
|
+
}
|
|
13984
14245
|
|
|
13985
14246
|
// src/app/channels/relayAdapter.ts
|
|
13986
14247
|
function createRelayPermissionCallback(bridge, runtime) {
|
|
@@ -14321,9 +14582,9 @@ function defaultLoadToken(tokenPath) {
|
|
|
14321
14582
|
}
|
|
14322
14583
|
|
|
14323
14584
|
// src/infra/config/gatewayClient.ts
|
|
14324
|
-
import
|
|
14325
|
-
import
|
|
14326
|
-
import
|
|
14585
|
+
import fs19 from "fs";
|
|
14586
|
+
import os13 from "os";
|
|
14587
|
+
import path16 from "path";
|
|
14327
14588
|
|
|
14328
14589
|
// src/shared/gateway-protocol/endpoint.ts
|
|
14329
14590
|
function parseRuntimeEndpoint(value) {
|
|
@@ -14378,17 +14639,17 @@ function isRecord2(value) {
|
|
|
14378
14639
|
|
|
14379
14640
|
// src/infra/config/gatewayClient.ts
|
|
14380
14641
|
function resolveGatewayClientConfigPath(env = process.env) {
|
|
14381
|
-
const home = env["HOME"] ??
|
|
14382
|
-
return
|
|
14642
|
+
const home = env["HOME"] ?? os13.homedir();
|
|
14643
|
+
return path16.join(home, ".config", "athena", "gateway.json");
|
|
14383
14644
|
}
|
|
14384
14645
|
function readGatewayClientConfig(env = process.env) {
|
|
14385
14646
|
const configPath = resolveGatewayClientConfigPath(env);
|
|
14386
|
-
if (!
|
|
14647
|
+
if (!fs19.existsSync(configPath)) {
|
|
14387
14648
|
return { mode: "local" };
|
|
14388
14649
|
}
|
|
14389
14650
|
let parsed;
|
|
14390
14651
|
try {
|
|
14391
|
-
parsed = JSON.parse(
|
|
14652
|
+
parsed = JSON.parse(fs19.readFileSync(configPath, "utf-8"));
|
|
14392
14653
|
} catch (err) {
|
|
14393
14654
|
throw new Error(
|
|
14394
14655
|
`gateway client config ${configPath} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -14405,16 +14666,16 @@ function readGatewayClientConfig(env = process.env) {
|
|
|
14405
14666
|
function writeGatewayClientConfig(config, env = process.env) {
|
|
14406
14667
|
const parsed = parseRuntimeEndpoint(config);
|
|
14407
14668
|
const configPath = resolveGatewayClientConfigPath(env);
|
|
14408
|
-
const dir =
|
|
14409
|
-
|
|
14410
|
-
|
|
14669
|
+
const dir = path16.dirname(configPath);
|
|
14670
|
+
fs19.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
14671
|
+
fs19.writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n", {
|
|
14411
14672
|
encoding: "utf-8",
|
|
14412
14673
|
mode: 384
|
|
14413
14674
|
});
|
|
14414
14675
|
if (process.platform !== "win32") {
|
|
14415
14676
|
try {
|
|
14416
|
-
|
|
14417
|
-
|
|
14677
|
+
fs19.chmodSync(dir, 448);
|
|
14678
|
+
fs19.chmodSync(configPath, 384);
|
|
14418
14679
|
} catch {
|
|
14419
14680
|
}
|
|
14420
14681
|
}
|
|
@@ -15202,6 +15463,20 @@ function handleEvent(event, cb) {
|
|
|
15202
15463
|
}
|
|
15203
15464
|
};
|
|
15204
15465
|
}
|
|
15466
|
+
if (eventKind2 === "compact.pre" && cb.interceptCompaction) {
|
|
15467
|
+
const reason = cb.interceptCompaction(event);
|
|
15468
|
+
if (reason !== null) {
|
|
15469
|
+
return {
|
|
15470
|
+
handled: true,
|
|
15471
|
+
decision: {
|
|
15472
|
+
type: "json",
|
|
15473
|
+
source: "rule",
|
|
15474
|
+
intent: { kind: "compact_block", reason }
|
|
15475
|
+
}
|
|
15476
|
+
};
|
|
15477
|
+
}
|
|
15478
|
+
return { handled: false };
|
|
15479
|
+
}
|
|
15205
15480
|
return { handled: false };
|
|
15206
15481
|
}
|
|
15207
15482
|
|
|
@@ -15382,8 +15657,8 @@ function exitCodeFromFailure(failure) {
|
|
|
15382
15657
|
}
|
|
15383
15658
|
|
|
15384
15659
|
// src/app/exec/output.ts
|
|
15385
|
-
import
|
|
15386
|
-
import
|
|
15660
|
+
import fs20 from "fs/promises";
|
|
15661
|
+
import path17 from "path";
|
|
15387
15662
|
|
|
15388
15663
|
// src/app/exec/jsonl.ts
|
|
15389
15664
|
function createExecJsonlEvent(type, data, ts = Date.now()) {
|
|
@@ -15426,9 +15701,9 @@ function createExecOutputWriter(options) {
|
|
|
15426
15701
|
writeLine(options.stdout, message);
|
|
15427
15702
|
},
|
|
15428
15703
|
async writeLastMessage(filePath, message) {
|
|
15429
|
-
const absPath =
|
|
15430
|
-
await
|
|
15431
|
-
await
|
|
15704
|
+
const absPath = path17.resolve(filePath);
|
|
15705
|
+
await fs20.mkdir(path17.dirname(absPath), { recursive: true });
|
|
15706
|
+
await fs20.writeFile(absPath, message, "utf-8");
|
|
15432
15707
|
}
|
|
15433
15708
|
};
|
|
15434
15709
|
}
|
|
@@ -15471,12 +15746,29 @@ function formatCapabilityConflictNotice(summary) {
|
|
|
15471
15746
|
"; "
|
|
15472
15747
|
)}`;
|
|
15473
15748
|
}
|
|
15474
|
-
function
|
|
15475
|
-
|
|
15476
|
-
|
|
15477
|
-
|
|
15478
|
-
|
|
15479
|
-
|
|
15749
|
+
function describeAttentionRequest(event) {
|
|
15750
|
+
const data = event.data;
|
|
15751
|
+
const toolInput = data["tool_input"];
|
|
15752
|
+
const questions = typeof toolInput === "object" && toolInput !== null ? toolInput["questions"] : void 0;
|
|
15753
|
+
if (Array.isArray(questions)) {
|
|
15754
|
+
const texts = questions.map(
|
|
15755
|
+
(q) => typeof q === "object" && q !== null ? q["question"] : void 0
|
|
15756
|
+
).filter((q) => typeof q === "string");
|
|
15757
|
+
if (texts.length > 0) {
|
|
15758
|
+
return `agent asked a question with no human attached to answer: ${texts.join(" | ")}`;
|
|
15759
|
+
}
|
|
15760
|
+
}
|
|
15761
|
+
if (event.kind === "permission.request" && event.toolName !== "user_input") {
|
|
15762
|
+
return `agent requested sandbox approval (${event.toolName ?? "unknown tool"}) with no human attached to answer \u2014 rerun with a more permissive --isolation, or wake with guidance`;
|
|
15763
|
+
}
|
|
15764
|
+
return "agent asked a question with no human attached to answer";
|
|
15765
|
+
}
|
|
15766
|
+
function isQuestionEvent(event) {
|
|
15767
|
+
return event.kind === "tool.pre" && event.toolName === "AskUserQuestion" || // ANY permission request, not just Codex's 'user_input': a sandbox
|
|
15768
|
+
// approval (e.g. item/fileChange/requestApproval under a read-only
|
|
15769
|
+
// sandbox) is equally unanswerable unattended — observed live hanging a
|
|
15770
|
+
// headless codex workflow run forever on the null-timeout decision.
|
|
15771
|
+
event.kind === "permission.request" || event.kind === "elicitation.request";
|
|
15480
15772
|
}
|
|
15481
15773
|
function buildEarlyFailureResult(input) {
|
|
15482
15774
|
return {
|
|
@@ -15530,12 +15822,13 @@ async function runExec(options) {
|
|
|
15530
15822
|
let adapterSessionId = null;
|
|
15531
15823
|
let activeRunId = null;
|
|
15532
15824
|
let beforeTerminalCompletionRan = false;
|
|
15825
|
+
let attentionRequest = null;
|
|
15533
15826
|
let store;
|
|
15534
15827
|
try {
|
|
15535
15828
|
store = sessionStoreFactory({
|
|
15536
15829
|
sessionId: athenaSessionId,
|
|
15537
15830
|
projectDir: options.projectDir,
|
|
15538
|
-
dbPath: options.ephemeral ? ":memory:" :
|
|
15831
|
+
dbPath: options.ephemeral ? ":memory:" : path18.join(sessionsDir(), athenaSessionId, "session.db")
|
|
15539
15832
|
});
|
|
15540
15833
|
} catch (error) {
|
|
15541
15834
|
const message = `Failed to initialize session store: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -15611,6 +15904,26 @@ async function runExec(options) {
|
|
|
15611
15904
|
defaultAgentId: "main",
|
|
15612
15905
|
...options.signal ? { signal: options.signal } : {}
|
|
15613
15906
|
}) : null;
|
|
15907
|
+
let handoverRequest = null;
|
|
15908
|
+
let handoverForkInProgress = false;
|
|
15909
|
+
const handoverDegradedSessions = /* @__PURE__ */ new Set();
|
|
15910
|
+
const interceptCompaction = (event) => {
|
|
15911
|
+
const handle = event.sessionId;
|
|
15912
|
+
if (!handle) return null;
|
|
15913
|
+
if (handoverDegradedSessions.has(handle)) return null;
|
|
15914
|
+
if (handoverForkInProgress) {
|
|
15915
|
+
return "Handover fork in progress \u2014 compaction stays blocked while the Handoff file is written.";
|
|
15916
|
+
}
|
|
15917
|
+
if (handoverRequest === null) {
|
|
15918
|
+
handoverRequest = { handle };
|
|
15919
|
+
output.notice(
|
|
15920
|
+
`handover: context bound reached \u2014 forking session ${handle} to write a Handoff file`
|
|
15921
|
+
);
|
|
15922
|
+
output.emitJsonEvent("run.handover", { adapterSessionId: handle });
|
|
15923
|
+
void sessionController.kill();
|
|
15924
|
+
}
|
|
15925
|
+
return "Handover in progress \u2014 Athena forks the conversation instead of compacting.";
|
|
15926
|
+
};
|
|
15614
15927
|
const controllerCallbacks = {
|
|
15615
15928
|
getRules: () => rules,
|
|
15616
15929
|
// No UI queue in exec; with no bridge attached, the runtime never
|
|
@@ -15623,6 +15936,10 @@ async function runExec(options) {
|
|
|
15623
15936
|
relayPermission: createRelayPermissionCallback(bridge, runtime),
|
|
15624
15937
|
relayQuestion: createRelayQuestionCallback(bridge, runtime)
|
|
15625
15938
|
} : {},
|
|
15939
|
+
// Handover interception is Claude-only for now: the fork transition
|
|
15940
|
+
// rides --fork-session, which Codex has no equivalent for. Non-workflow
|
|
15941
|
+
// sessions never intercept — vendor compaction proceeds unchanged.
|
|
15942
|
+
...options.harness === "claude-code" && options.workflow?.loop?.enabled ? { interceptCompaction } : {},
|
|
15626
15943
|
...options.signal ? { signal: options.signal } : {}
|
|
15627
15944
|
};
|
|
15628
15945
|
const dashboardDecisionInbox = options.dashboardDecisionInbox;
|
|
@@ -15696,6 +16013,10 @@ async function runExec(options) {
|
|
|
15696
16013
|
},
|
|
15697
16014
|
onEventReceived: (runtimeEvent) => {
|
|
15698
16015
|
adapterSessionId = runtimeEvent.sessionId;
|
|
16016
|
+
if (options.workflow?.loop?.enabled && !bridge && attentionRequest === null && isQuestionEvent(runtimeEvent)) {
|
|
16017
|
+
attentionRequest = describeAttentionRequest(runtimeEvent);
|
|
16018
|
+
void sessionController.kill();
|
|
16019
|
+
}
|
|
15699
16020
|
if (runtimeEvent.sessionId && activeRunId && !linkedAdapterSessions.has(runtimeEvent.sessionId)) {
|
|
15700
16021
|
linkedAdapterSessions.add(runtimeEvent.sessionId);
|
|
15701
16022
|
safePersist(
|
|
@@ -15792,6 +16113,15 @@ async function runExec(options) {
|
|
|
15792
16113
|
loopEnabled: workflow?.loop?.enabled ?? false
|
|
15793
16114
|
});
|
|
15794
16115
|
const nextContinuation = options.adapterResumeSessionId ? { mode: "resume", handle: options.adapterResumeSessionId } : { mode: "fresh" };
|
|
16116
|
+
let resumedRunMemory;
|
|
16117
|
+
let resumedStopReason;
|
|
16118
|
+
if (options.resumeRunId) {
|
|
16119
|
+
const resumedRun = store.getLatestRun();
|
|
16120
|
+
if (resumedRun?.id === options.resumeRunId) {
|
|
16121
|
+
resumedRunMemory = deserializeRunMemory(resumedRun.runMemoryJson) ?? void 0;
|
|
16122
|
+
resumedStopReason = resumedRun.stopReason;
|
|
16123
|
+
}
|
|
16124
|
+
}
|
|
15795
16125
|
const handle = createWorkflowRunner({
|
|
15796
16126
|
sessionId: athenaSessionId,
|
|
15797
16127
|
projectDir: options.projectDir,
|
|
@@ -15799,6 +16129,9 @@ async function runExec(options) {
|
|
|
15799
16129
|
workflow,
|
|
15800
16130
|
prompt: options.prompt,
|
|
15801
16131
|
initialContinuation: nextContinuation,
|
|
16132
|
+
resumeRunId: options.resumeRunId,
|
|
16133
|
+
resumedRunMemory,
|
|
16134
|
+
resumedStopReason,
|
|
15802
16135
|
startTurn: async (turnInput) => {
|
|
15803
16136
|
const turnResult = await sessionController.startTurn({
|
|
15804
16137
|
prompt: turnInput.prompt,
|
|
@@ -15828,32 +16161,88 @@ async function runExec(options) {
|
|
|
15828
16161
|
"persistRun failed"
|
|
15829
16162
|
);
|
|
15830
16163
|
},
|
|
16164
|
+
checkSuspension: () => attentionRequest !== null ? { reason: attentionRequest } : null,
|
|
16165
|
+
currentAdapterSessionId,
|
|
16166
|
+
handover: {
|
|
16167
|
+
takeRequest: () => {
|
|
16168
|
+
const request = handoverRequest;
|
|
16169
|
+
handoverRequest = null;
|
|
16170
|
+
return request;
|
|
16171
|
+
},
|
|
16172
|
+
onForkStateChange: (forking) => {
|
|
16173
|
+
handoverForkInProgress = forking;
|
|
16174
|
+
},
|
|
16175
|
+
onDegraded: (handle2) => {
|
|
16176
|
+
handoverDegradedSessions.add(handle2);
|
|
16177
|
+
output.warn(
|
|
16178
|
+
`handover failed for session ${handle2} \u2014 falling back to normal vendor compaction`
|
|
16179
|
+
);
|
|
16180
|
+
output.emitJsonEvent("run.handover.degraded", {
|
|
16181
|
+
adapterSessionId: handle2
|
|
16182
|
+
});
|
|
16183
|
+
}
|
|
16184
|
+
},
|
|
15831
16185
|
abortCurrentTurn: () => void sessionController.kill(),
|
|
15832
16186
|
onIterationComplete: (runSnapshot) => {
|
|
15833
16187
|
output.emitJsonEvent("iteration.complete", {
|
|
15834
16188
|
iteration: runSnapshot.iteration,
|
|
15835
16189
|
status: runSnapshot.status
|
|
15836
16190
|
});
|
|
16191
|
+
},
|
|
16192
|
+
// Task-tool projection (ADR 0015 §7): the Tracker's `## Units` table +
|
|
16193
|
+
// unit-record frontmatter, diffed against what the Feed already knows
|
|
16194
|
+
// and reconciled through the same `task.created`/`task.completed`
|
|
16195
|
+
// path a live TodoWrite/TaskCreate/TaskUpdate call would take.
|
|
16196
|
+
projectTasks: (tasks) => {
|
|
16197
|
+
const known = new Map(
|
|
16198
|
+
mapper.getTasks().filter((task) => task.taskId).map((task) => [task.taskId, task])
|
|
16199
|
+
);
|
|
16200
|
+
const newEvents = [];
|
|
16201
|
+
for (const task of tasks) {
|
|
16202
|
+
const existing = known.get(task.taskId);
|
|
16203
|
+
if (!existing) {
|
|
16204
|
+
newEvents.push(
|
|
16205
|
+
...mapper.mapEvent(
|
|
16206
|
+
buildSyntheticTaskEvent("task.created", athenaSessionId, {
|
|
16207
|
+
task_id: task.taskId,
|
|
16208
|
+
task_subject: task.content
|
|
16209
|
+
})
|
|
16210
|
+
)
|
|
16211
|
+
);
|
|
16212
|
+
}
|
|
16213
|
+
if (task.status === "completed" && existing?.status !== "completed") {
|
|
16214
|
+
newEvents.push(
|
|
16215
|
+
...mapper.mapEvent(
|
|
16216
|
+
buildSyntheticTaskEvent("task.completed", athenaSessionId, {
|
|
16217
|
+
task_id: task.taskId,
|
|
16218
|
+
task_subject: task.content
|
|
16219
|
+
})
|
|
16220
|
+
)
|
|
16221
|
+
);
|
|
16222
|
+
}
|
|
16223
|
+
}
|
|
16224
|
+
if (newEvents.length > 0) {
|
|
16225
|
+
safePersist(
|
|
16226
|
+
store,
|
|
16227
|
+
() => store.recordFeedEvents(newEvents),
|
|
16228
|
+
(message) => output.warn(message),
|
|
16229
|
+
"recordFeedEvents failed"
|
|
16230
|
+
);
|
|
16231
|
+
}
|
|
15837
16232
|
}
|
|
15838
16233
|
});
|
|
15839
16234
|
activeRunId = handle.runId;
|
|
15840
16235
|
const runResult = await handle.result;
|
|
15841
16236
|
cumulativeTokens = runResult.tokens;
|
|
15842
16237
|
if (!latch.hasFailure()) {
|
|
15843
|
-
if (runResult.status === "
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
|
|
15850
|
-
|
|
15851
|
-
latch.register(
|
|
15852
|
-
workflowFailure(
|
|
15853
|
-
"exhausted",
|
|
15854
|
-
`Workflow reached the maximum of ${workflow?.loop?.maxIterations ?? 0} iterations.`
|
|
15855
|
-
)
|
|
15856
|
-
);
|
|
16238
|
+
if (runResult.status === "awaiting_attention") {
|
|
16239
|
+
const reason = runResult.stopReason ?? "awaiting attention";
|
|
16240
|
+
output.notice(`workflow run suspended \u2014 ${reason}`);
|
|
16241
|
+
output.emitJsonEvent("run.suspended", {
|
|
16242
|
+
runId: runResult.runId,
|
|
16243
|
+
status: "awaiting_attention",
|
|
16244
|
+
stopReason: runResult.stopReason ?? null
|
|
16245
|
+
});
|
|
15857
16246
|
} else if (runResult.status === "failed") {
|
|
15858
16247
|
latch.register({
|
|
15859
16248
|
kind: "process",
|
|
@@ -16494,8 +16883,8 @@ async function createRemoteRunEventPublisher({
|
|
|
16494
16883
|
// src/app/dashboard/artifactCapture.ts
|
|
16495
16884
|
import { execFile } from "child_process";
|
|
16496
16885
|
import crypto3 from "crypto";
|
|
16497
|
-
import
|
|
16498
|
-
import
|
|
16886
|
+
import fs21 from "fs/promises";
|
|
16887
|
+
import path19 from "path";
|
|
16499
16888
|
import { promisify } from "util";
|
|
16500
16889
|
var execFileAsync = promisify(execFile);
|
|
16501
16890
|
function parseArtifactUploadSpec(value) {
|
|
@@ -16678,21 +17067,21 @@ async function collectArtifactPayloads(input) {
|
|
|
16678
17067
|
return payloads;
|
|
16679
17068
|
}
|
|
16680
17069
|
async function readWorkspaceFile(projectDir, rel) {
|
|
16681
|
-
const absolute =
|
|
16682
|
-
const workspaceRoot = await
|
|
17070
|
+
const absolute = path19.resolve(projectDir, rel);
|
|
17071
|
+
const workspaceRoot = await fs21.realpath(projectDir);
|
|
16683
17072
|
let stat;
|
|
16684
17073
|
try {
|
|
16685
|
-
stat = await
|
|
17074
|
+
stat = await fs21.lstat(absolute);
|
|
16686
17075
|
} catch {
|
|
16687
17076
|
return null;
|
|
16688
17077
|
}
|
|
16689
17078
|
if (!stat.isFile() || stat.isSymbolicLink()) return null;
|
|
16690
|
-
const real = await
|
|
16691
|
-
const relativeToRoot =
|
|
16692
|
-
if (relativeToRoot === ".." || relativeToRoot.startsWith(`..${
|
|
17079
|
+
const real = await fs21.realpath(absolute);
|
|
17080
|
+
const relativeToRoot = path19.relative(workspaceRoot, real);
|
|
17081
|
+
if (relativeToRoot === ".." || relativeToRoot.startsWith(`..${path19.sep}`) || path19.isAbsolute(relativeToRoot)) {
|
|
16693
17082
|
return null;
|
|
16694
17083
|
}
|
|
16695
|
-
return
|
|
17084
|
+
return fs21.readFile(absolute);
|
|
16696
17085
|
}
|
|
16697
17086
|
async function gitDiffForAllowedPaths(input) {
|
|
16698
17087
|
const paths = splitNul(await git(input.projectDir, input.nameArgs)).filter(
|
|
@@ -16783,13 +17172,13 @@ var DEFAULT_HARD_DENY = [
|
|
|
16783
17172
|
".ssh/**"
|
|
16784
17173
|
];
|
|
16785
17174
|
function isAllowedRelativePath(rel, hardDeny) {
|
|
16786
|
-
if (
|
|
16787
|
-
const normalized =
|
|
17175
|
+
if (path19.isAbsolute(rel) || rel.includes("\0")) return false;
|
|
17176
|
+
const normalized = path19.posix.normalize(rel.replaceAll(path19.sep, "/"));
|
|
16788
17177
|
if (normalized === ".." || normalized.startsWith("../")) return false;
|
|
16789
17178
|
return !hardDeny.some((pattern) => matchesDenyPattern(normalized, pattern));
|
|
16790
17179
|
}
|
|
16791
17180
|
function matchesDenyPattern(rel, pattern) {
|
|
16792
|
-
const normalized = pattern.replaceAll(
|
|
17181
|
+
const normalized = pattern.replaceAll(path19.sep, "/");
|
|
16793
17182
|
if (normalized.includes("*") || normalized.includes("?")) {
|
|
16794
17183
|
return globToRegExp(normalized).test(rel);
|
|
16795
17184
|
}
|
|
@@ -17186,18 +17575,18 @@ async function executeRemoteAssignment({
|
|
|
17186
17575
|
|
|
17187
17576
|
// src/infra/config/attachmentMirror.ts
|
|
17188
17577
|
import crypto4 from "crypto";
|
|
17189
|
-
import
|
|
17190
|
-
import
|
|
17191
|
-
import
|
|
17578
|
+
import fs22 from "fs";
|
|
17579
|
+
import os14 from "os";
|
|
17580
|
+
import path20 from "path";
|
|
17192
17581
|
function attachmentMirrorPath(env = process.env) {
|
|
17193
|
-
const home = env["HOME"] ??
|
|
17194
|
-
return
|
|
17582
|
+
const home = env["HOME"] ?? os14.homedir();
|
|
17583
|
+
return path20.join(home, ".config", "athena", "attachments.json");
|
|
17195
17584
|
}
|
|
17196
17585
|
function readAttachmentMirror(env = process.env) {
|
|
17197
17586
|
const file = attachmentMirrorPath(env);
|
|
17198
17587
|
let raw;
|
|
17199
17588
|
try {
|
|
17200
|
-
raw =
|
|
17589
|
+
raw = fs22.readFileSync(file, "utf-8");
|
|
17201
17590
|
} catch (err) {
|
|
17202
17591
|
if (err.code === "ENOENT") return null;
|
|
17203
17592
|
throw err;
|
|
@@ -17221,29 +17610,29 @@ function readAttachmentMirror(env = process.env) {
|
|
|
17221
17610
|
function writeAttachmentMirror(mirror, env = process.env) {
|
|
17222
17611
|
const validated = parseAttachmentMirror(mirror);
|
|
17223
17612
|
const file = attachmentMirrorPath(env);
|
|
17224
|
-
const dir =
|
|
17225
|
-
|
|
17613
|
+
const dir = path20.dirname(file);
|
|
17614
|
+
fs22.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
17226
17615
|
const tmp = `${file}.${process.pid}.${crypto4.randomBytes(4).toString("hex")}.tmp`;
|
|
17227
|
-
const fd =
|
|
17616
|
+
const fd = fs22.openSync(tmp, "w", 384);
|
|
17228
17617
|
try {
|
|
17229
|
-
|
|
17230
|
-
|
|
17618
|
+
fs22.writeSync(fd, JSON.stringify(validated, null, 2) + "\n");
|
|
17619
|
+
fs22.fsyncSync(fd);
|
|
17231
17620
|
} finally {
|
|
17232
|
-
|
|
17621
|
+
fs22.closeSync(fd);
|
|
17233
17622
|
}
|
|
17234
17623
|
try {
|
|
17235
|
-
|
|
17624
|
+
fs22.renameSync(tmp, file);
|
|
17236
17625
|
} catch (err) {
|
|
17237
17626
|
try {
|
|
17238
|
-
|
|
17627
|
+
fs22.unlinkSync(tmp);
|
|
17239
17628
|
} catch {
|
|
17240
17629
|
}
|
|
17241
17630
|
throw err;
|
|
17242
17631
|
}
|
|
17243
17632
|
if (process.platform !== "win32") {
|
|
17244
17633
|
try {
|
|
17245
|
-
|
|
17246
|
-
|
|
17634
|
+
fs22.chmodSync(dir, 448);
|
|
17635
|
+
fs22.chmodSync(file, 384);
|
|
17247
17636
|
} catch {
|
|
17248
17637
|
}
|
|
17249
17638
|
}
|
|
@@ -17251,7 +17640,7 @@ function writeAttachmentMirror(mirror, env = process.env) {
|
|
|
17251
17640
|
function removeAttachmentMirror(env = process.env) {
|
|
17252
17641
|
const file = attachmentMirrorPath(env);
|
|
17253
17642
|
try {
|
|
17254
|
-
|
|
17643
|
+
fs22.unlinkSync(file);
|
|
17255
17644
|
} catch (err) {
|
|
17256
17645
|
if (err.code !== "ENOENT") throw err;
|
|
17257
17646
|
}
|
|
@@ -17655,9 +18044,9 @@ function createDashboardPairedExecution(options) {
|
|
|
17655
18044
|
}
|
|
17656
18045
|
|
|
17657
18046
|
// src/app/dashboard/remoteWorkspaceResolver.ts
|
|
17658
|
-
import
|
|
17659
|
-
import
|
|
17660
|
-
import
|
|
18047
|
+
import fs23 from "fs";
|
|
18048
|
+
import os15 from "os";
|
|
18049
|
+
import path21 from "path";
|
|
17661
18050
|
function resolveRemoteWorkspace(assignment, options = {}) {
|
|
17662
18051
|
const { spec, runId, runnerId } = assignment;
|
|
17663
18052
|
if (spec.projectDir) {
|
|
@@ -17666,14 +18055,14 @@ function resolveRemoteWorkspace(assignment, options = {}) {
|
|
|
17666
18055
|
const sessionId = spec.athenaSessionId ?? spec.sessionId;
|
|
17667
18056
|
const deploymentSlug = deploymentSlugFromUrl(options.dashboardUrl);
|
|
17668
18057
|
const stateDir = daemonStatePaths(options.env).dir;
|
|
17669
|
-
const projectDir = sessionId ?
|
|
18058
|
+
const projectDir = sessionId ? path21.join(
|
|
17670
18059
|
stateDir,
|
|
17671
18060
|
"remote-workspaces",
|
|
17672
18061
|
deploymentSlug,
|
|
17673
18062
|
sanitizePathSegment(runnerId),
|
|
17674
18063
|
"sessions",
|
|
17675
18064
|
sanitizePathSegment(sessionId)
|
|
17676
|
-
) :
|
|
18065
|
+
) : path21.join(
|
|
17677
18066
|
stateDir,
|
|
17678
18067
|
"remote-workspaces",
|
|
17679
18068
|
deploymentSlug,
|
|
@@ -17682,10 +18071,10 @@ function resolveRemoteWorkspace(assignment, options = {}) {
|
|
|
17682
18071
|
sanitizePathSegment(runId)
|
|
17683
18072
|
);
|
|
17684
18073
|
try {
|
|
17685
|
-
|
|
18074
|
+
fs23.mkdirSync(projectDir, { recursive: true, mode: 448 });
|
|
17686
18075
|
if (process.platform !== "win32") {
|
|
17687
18076
|
try {
|
|
17688
|
-
|
|
18077
|
+
fs23.chmodSync(projectDir, 448);
|
|
17689
18078
|
} catch {
|
|
17690
18079
|
}
|
|
17691
18080
|
}
|
|
@@ -17701,8 +18090,8 @@ function resolveRemoteWorkspace(assignment, options = {}) {
|
|
|
17701
18090
|
return validateProjectDir(projectDir, options.env);
|
|
17702
18091
|
}
|
|
17703
18092
|
function validateProjectDir(projectDir, env = process.env) {
|
|
17704
|
-
const resolved =
|
|
17705
|
-
if (!
|
|
18093
|
+
const resolved = path21.resolve(projectDir);
|
|
18094
|
+
if (!path21.isAbsolute(projectDir)) {
|
|
17706
18095
|
return {
|
|
17707
18096
|
kind: "rejected",
|
|
17708
18097
|
rejection: {
|
|
@@ -17711,7 +18100,7 @@ function validateProjectDir(projectDir, env = process.env) {
|
|
|
17711
18100
|
}
|
|
17712
18101
|
};
|
|
17713
18102
|
}
|
|
17714
|
-
const home =
|
|
18103
|
+
const home = path21.resolve(env["HOME"] ?? os15.homedir());
|
|
17715
18104
|
if (resolved === home) {
|
|
17716
18105
|
return {
|
|
17717
18106
|
kind: "rejected",
|
|
@@ -17723,7 +18112,7 @@ function validateProjectDir(projectDir, env = process.env) {
|
|
|
17723
18112
|
}
|
|
17724
18113
|
let stat;
|
|
17725
18114
|
try {
|
|
17726
|
-
stat =
|
|
18115
|
+
stat = fs23.statSync(resolved);
|
|
17727
18116
|
} catch {
|
|
17728
18117
|
return {
|
|
17729
18118
|
kind: "rejected",
|
|
@@ -18168,18 +18557,18 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
18168
18557
|
}
|
|
18169
18558
|
|
|
18170
18559
|
// src/infra/daemon/pidLock.ts
|
|
18171
|
-
import
|
|
18560
|
+
import fs24 from "fs";
|
|
18172
18561
|
function acquirePidLock(pidPath) {
|
|
18173
18562
|
const ownPid = process.pid;
|
|
18174
18563
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
18175
18564
|
try {
|
|
18176
|
-
const fd =
|
|
18565
|
+
const fd = fs24.openSync(pidPath, "wx", 384);
|
|
18177
18566
|
try {
|
|
18178
|
-
|
|
18567
|
+
fs24.writeSync(fd, `${ownPid}
|
|
18179
18568
|
`);
|
|
18180
|
-
|
|
18569
|
+
fs24.fsyncSync(fd);
|
|
18181
18570
|
} finally {
|
|
18182
|
-
|
|
18571
|
+
fs24.closeSync(fd);
|
|
18183
18572
|
}
|
|
18184
18573
|
return makeHandle(pidPath, ownPid);
|
|
18185
18574
|
} catch (err) {
|
|
@@ -18193,7 +18582,7 @@ function acquirePidLock(pidPath) {
|
|
|
18193
18582
|
}
|
|
18194
18583
|
if (existing.state === "stale") {
|
|
18195
18584
|
try {
|
|
18196
|
-
|
|
18585
|
+
fs24.unlinkSync(pidPath);
|
|
18197
18586
|
} catch (err) {
|
|
18198
18587
|
if (err.code !== "ENOENT") throw err;
|
|
18199
18588
|
}
|
|
@@ -18207,7 +18596,7 @@ function acquirePidLock(pidPath) {
|
|
|
18207
18596
|
function readPidLock(pidPath) {
|
|
18208
18597
|
let raw;
|
|
18209
18598
|
try {
|
|
18210
|
-
raw =
|
|
18599
|
+
raw = fs24.readFileSync(pidPath, "utf-8");
|
|
18211
18600
|
} catch (err) {
|
|
18212
18601
|
if (err.code === "ENOENT") {
|
|
18213
18602
|
return { state: "absent" };
|
|
@@ -18231,9 +18620,9 @@ function makeHandle(pidPath, pid) {
|
|
|
18231
18620
|
if (released) return;
|
|
18232
18621
|
released = true;
|
|
18233
18622
|
try {
|
|
18234
|
-
const raw =
|
|
18623
|
+
const raw = fs24.readFileSync(pidPath, "utf-8").trim();
|
|
18235
18624
|
if (raw === String(pid)) {
|
|
18236
|
-
|
|
18625
|
+
fs24.unlinkSync(pidPath);
|
|
18237
18626
|
}
|
|
18238
18627
|
} catch (err) {
|
|
18239
18628
|
if (err.code !== "ENOENT") {
|
|
@@ -18259,7 +18648,7 @@ function isProcessAlive(pid) {
|
|
|
18259
18648
|
}
|
|
18260
18649
|
|
|
18261
18650
|
// src/infra/daemon/udsIpc.ts
|
|
18262
|
-
import
|
|
18651
|
+
import fs25 from "fs";
|
|
18263
18652
|
import net2 from "net";
|
|
18264
18653
|
|
|
18265
18654
|
// src/infra/daemon/udsFrameCodec.ts
|
|
@@ -18304,7 +18693,7 @@ async function startUdsServer(socketPath, handler, log) {
|
|
|
18304
18693
|
});
|
|
18305
18694
|
if (process.platform !== "win32") {
|
|
18306
18695
|
try {
|
|
18307
|
-
|
|
18696
|
+
fs25.chmodSync(socketPath, 384);
|
|
18308
18697
|
} catch {
|
|
18309
18698
|
}
|
|
18310
18699
|
}
|
|
@@ -18314,7 +18703,7 @@ async function startUdsServer(socketPath, handler, log) {
|
|
|
18314
18703
|
server.close(() => resolve());
|
|
18315
18704
|
});
|
|
18316
18705
|
try {
|
|
18317
|
-
|
|
18706
|
+
fs25.unlinkSync(socketPath);
|
|
18318
18707
|
} catch (err) {
|
|
18319
18708
|
if (err.code !== "ENOENT") {
|
|
18320
18709
|
}
|
|
@@ -18432,7 +18821,7 @@ async function sendUdsRequest(socketPath, request, options = {}) {
|
|
|
18432
18821
|
async function unlinkStaleSocket(socketPath) {
|
|
18433
18822
|
let stat;
|
|
18434
18823
|
try {
|
|
18435
|
-
stat =
|
|
18824
|
+
stat = fs25.statSync(socketPath);
|
|
18436
18825
|
} catch (err) {
|
|
18437
18826
|
if (err.code === "ENOENT") return;
|
|
18438
18827
|
throw err;
|
|
@@ -18459,7 +18848,7 @@ async function unlinkStaleSocket(socketPath) {
|
|
|
18459
18848
|
`uds path ${socketPath} is in use by another process; aborting`
|
|
18460
18849
|
);
|
|
18461
18850
|
}
|
|
18462
|
-
|
|
18851
|
+
fs25.unlinkSync(socketPath);
|
|
18463
18852
|
}
|
|
18464
18853
|
|
|
18465
18854
|
export {
|
|
@@ -18489,6 +18878,8 @@ export {
|
|
|
18489
18878
|
listSessions,
|
|
18490
18879
|
getSessionMeta,
|
|
18491
18880
|
getMostRecentAthenaSession,
|
|
18881
|
+
getLatestRunForSession,
|
|
18882
|
+
listAwaitingAttentionRuns,
|
|
18492
18883
|
resolveClaudeBinary,
|
|
18493
18884
|
detectClaudeVersion,
|
|
18494
18885
|
resolveHookForwarderCommand,
|
|
@@ -18560,4 +18951,4 @@ export {
|
|
|
18560
18951
|
startUdsServer,
|
|
18561
18952
|
sendUdsRequest
|
|
18562
18953
|
};
|
|
18563
|
-
//# sourceMappingURL=chunk-
|
|
18954
|
+
//# sourceMappingURL=chunk-4HWBXCVY.js.map
|