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