@drisp/cli 0.5.23 → 0.5.26
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-CCCUR6KA.js → WorkflowInstallWizard-5FENJHBW.js} +2 -2
- package/dist/athena-gateway.js +81 -275
- package/dist/{chunk-BTY7MYYT.js → chunk-3U37HT4T.js} +379 -66
- package/dist/{chunk-MRAM6EYI.js → chunk-4NX4WFPB.js} +2 -2
- package/dist/{chunk-JHSADKDJ.js → chunk-73V7GXV6.js} +162 -210
- package/dist/{chunk-BTKQ67RE.js → chunk-QYB6N2OT.js} +1 -1
- package/dist/{chunk-EC67PEFT.js → chunk-YZ7RCBKO.js} +1377 -1054
- package/dist/cli.js +886 -484
- package/dist/dashboard-daemon.js +4 -4
- package/dist/hook-forwarder.js +1 -1
- package/package.json +1 -1
|
@@ -12,29 +12,33 @@ import {
|
|
|
12
12
|
isToolEvent,
|
|
13
13
|
isValidHookEventEnvelope,
|
|
14
14
|
resolveHookSocketPath
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-QYB6N2OT.js";
|
|
16
16
|
import {
|
|
17
17
|
TransportUnreachableError,
|
|
18
18
|
createUdsClientTransport,
|
|
19
|
+
createWsClientTransport,
|
|
19
20
|
generateChannelRequestId,
|
|
21
|
+
openVersionedDb,
|
|
20
22
|
readDashboardClientConfig,
|
|
21
23
|
refreshDashboardAccessToken,
|
|
22
24
|
resolveGatewayPaths,
|
|
23
|
-
traceGatewayFrame,
|
|
24
25
|
trackGatewayTransportReconnect,
|
|
25
|
-
writeGatewayTrace
|
|
26
|
-
|
|
26
|
+
writeGatewayTrace,
|
|
27
|
+
wsClientOptionsForEndpoint
|
|
28
|
+
} from "./chunk-3U37HT4T.js";
|
|
27
29
|
import {
|
|
28
30
|
compileWorkflowPlan,
|
|
29
31
|
createWorkflowRunner,
|
|
30
32
|
installWorkflowFromSource,
|
|
33
|
+
isMarketplaceRef,
|
|
31
34
|
readConfig,
|
|
32
35
|
readGlobalConfig,
|
|
33
36
|
resolveActiveWorkflow,
|
|
37
|
+
resolveMarketplacePlugin,
|
|
34
38
|
resolveWorkflow,
|
|
35
39
|
resolveWorkflowInstall,
|
|
36
40
|
resolveWorkflowPlugins
|
|
37
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-73V7GXV6.js";
|
|
38
42
|
|
|
39
43
|
// src/infra/daemon/stateDir.ts
|
|
40
44
|
import fs from "fs";
|
|
@@ -237,6 +241,16 @@ var RULES = {
|
|
|
237
241
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
238
242
|
canBlock: false
|
|
239
243
|
},
|
|
244
|
+
// Observation-only: the batch fires after every call in it has already
|
|
245
|
+
// resolved, so nothing downstream produces a decision or block for it. The
|
|
246
|
+
// claim is not cosmetic — hook dispatch keeps every decision-capable event
|
|
247
|
+
// on Claude's critical path, so a false `canBlock` would make every
|
|
248
|
+
// parallel tool batch wait on the forwarder for a reply never sent.
|
|
249
|
+
"tool.batch": {
|
|
250
|
+
expectsDecision: false,
|
|
251
|
+
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
252
|
+
canBlock: false
|
|
253
|
+
},
|
|
240
254
|
"tool.delta": {
|
|
241
255
|
expectsDecision: false,
|
|
242
256
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
@@ -287,6 +301,16 @@ var RULES = {
|
|
|
287
301
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
288
302
|
canBlock: true
|
|
289
303
|
},
|
|
304
|
+
// Observation-only: nothing in the pipeline produces a decision or a block
|
|
305
|
+
// for an expansion, so it must not claim `canBlock`. The claim is not
|
|
306
|
+
// cosmetic — hook dispatch keeps every decision-capable event on Claude's
|
|
307
|
+
// critical path, so a false `canBlock` would make every slash command wait
|
|
308
|
+
// on the forwarder for a reply that is never sent.
|
|
309
|
+
"prompt.expansion": {
|
|
310
|
+
expectsDecision: false,
|
|
311
|
+
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
312
|
+
canBlock: false
|
|
313
|
+
},
|
|
290
314
|
"turn.start": {
|
|
291
315
|
expectsDecision: false,
|
|
292
316
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
@@ -419,6 +443,15 @@ function asRecord(value) {
|
|
|
419
443
|
}
|
|
420
444
|
return {};
|
|
421
445
|
}
|
|
446
|
+
function readToolBatchCalls(value) {
|
|
447
|
+
if (!Array.isArray(value)) return [];
|
|
448
|
+
return value.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => asRecord(entry)).map((call) => ({
|
|
449
|
+
tool_name: call["tool_name"],
|
|
450
|
+
tool_input: call["tool_input"],
|
|
451
|
+
tool_use_id: call["tool_use_id"],
|
|
452
|
+
tool_response: call["tool_response"]
|
|
453
|
+
}));
|
|
454
|
+
}
|
|
422
455
|
function translateClaudeEnvelope(envelope) {
|
|
423
456
|
const payload = asRecord(envelope.payload);
|
|
424
457
|
let toolName;
|
|
@@ -443,7 +476,9 @@ function translateClaudeEnvelope(envelope) {
|
|
|
443
476
|
kind: "session.start",
|
|
444
477
|
data: {
|
|
445
478
|
source: payload["source"],
|
|
446
|
-
|
|
479
|
+
model: payload["model"],
|
|
480
|
+
agent_type: payload["agent_type"],
|
|
481
|
+
session_title: payload["session_title"]
|
|
447
482
|
}
|
|
448
483
|
};
|
|
449
484
|
case "SessionEnd":
|
|
@@ -457,6 +492,22 @@ function translateClaudeEnvelope(envelope) {
|
|
|
457
492
|
return {
|
|
458
493
|
kind: "user.prompt",
|
|
459
494
|
data: {
|
|
495
|
+
prompt: payload["prompt"],
|
|
496
|
+
cwd: payload["cwd"],
|
|
497
|
+
permission_mode: payload["permission_mode"]
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
// Fires immediately before UserPromptSubmit, sharing its prompt_id,
|
|
501
|
+
// when a typed command expands into a prompt. Field names are pinned
|
|
502
|
+
// to the payload captured in the #116 spike.
|
|
503
|
+
case "UserPromptExpansion":
|
|
504
|
+
return {
|
|
505
|
+
kind: "prompt.expansion",
|
|
506
|
+
data: {
|
|
507
|
+
expansion_type: payload["expansion_type"],
|
|
508
|
+
command_name: payload["command_name"],
|
|
509
|
+
command_args: payload["command_args"],
|
|
510
|
+
command_source: payload["command_source"],
|
|
460
511
|
prompt: payload["prompt"],
|
|
461
512
|
permission_mode: payload["permission_mode"]
|
|
462
513
|
}
|
|
@@ -484,6 +535,18 @@ function translateClaudeEnvelope(envelope) {
|
|
|
484
535
|
tool_response: payload["tool_response"]
|
|
485
536
|
}
|
|
486
537
|
};
|
|
538
|
+
// Fires once per assistant tool batch, after every PostToolUse in it.
|
|
539
|
+
// Field names are pinned to the payload captured in the #116 spike;
|
|
540
|
+
// note that `tool_calls[].tool_response` is a STRING there, unlike
|
|
541
|
+
// PostToolUse's structured `tool_response`.
|
|
542
|
+
case "PostToolBatch":
|
|
543
|
+
return {
|
|
544
|
+
kind: "tool.batch",
|
|
545
|
+
data: {
|
|
546
|
+
tool_calls: readToolBatchCalls(payload["tool_calls"]),
|
|
547
|
+
permission_mode: payload["permission_mode"]
|
|
548
|
+
}
|
|
549
|
+
};
|
|
487
550
|
case "PostToolUseFailure":
|
|
488
551
|
return {
|
|
489
552
|
kind: "tool.failure",
|
|
@@ -494,7 +557,10 @@ function translateClaudeEnvelope(envelope) {
|
|
|
494
557
|
tool_input: payload["tool_input"] ?? {},
|
|
495
558
|
tool_use_id: toolUseId,
|
|
496
559
|
error: payload["error"],
|
|
497
|
-
is_interrupt: payload["is_interrupt"]
|
|
560
|
+
is_interrupt: payload["is_interrupt"],
|
|
561
|
+
exit_code: payload["exit_code"],
|
|
562
|
+
output: payload["output"],
|
|
563
|
+
error_code: payload["error_code"]
|
|
498
564
|
}
|
|
499
565
|
};
|
|
500
566
|
case "PermissionRequest":
|
|
@@ -720,6 +786,12 @@ function buildClaudeDisplay(kind, data) {
|
|
|
720
786
|
}
|
|
721
787
|
|
|
722
788
|
// src/harnesses/claude/runtime/mapper.ts
|
|
789
|
+
function readEffortLevel(payload) {
|
|
790
|
+
const effort = payload["effort"];
|
|
791
|
+
if (typeof effort !== "object" || effort === null) return void 0;
|
|
792
|
+
const level = effort["level"];
|
|
793
|
+
return typeof level === "string" ? level : void 0;
|
|
794
|
+
}
|
|
723
795
|
function mapEnvelopeToRuntimeEvent(envelope) {
|
|
724
796
|
const payload = envelope.payload;
|
|
725
797
|
const safePayload = typeof payload === "object" && payload !== null ? payload : { value: payload };
|
|
@@ -737,6 +809,13 @@ function mapEnvelopeToRuntimeEvent(envelope) {
|
|
|
737
809
|
data: translated.data,
|
|
738
810
|
hookName: envelope.hook_event_name,
|
|
739
811
|
sessionId: envelope.session_id,
|
|
812
|
+
// prompt_id is a Claude common input field (v2.1.196+) on every hook
|
|
813
|
+
// payload; carried harness-neutrally as the Prompt identity (ADR 0009).
|
|
814
|
+
promptId: safePayloadRecord["prompt_id"],
|
|
815
|
+
// `effort` is likewise a common input field ({level}), observed on
|
|
816
|
+
// PostToolUse-class payloads and never on SessionStart, so it is read here
|
|
817
|
+
// rather than in any one hook's translation.
|
|
818
|
+
effortLevel: readEffortLevel(safePayloadRecord),
|
|
740
819
|
toolName: translated.toolName,
|
|
741
820
|
toolUseId: translated.toolUseId,
|
|
742
821
|
agentId: translated.agentId,
|
|
@@ -854,9 +933,23 @@ function extractResultText(content) {
|
|
|
854
933
|
}
|
|
855
934
|
return parts.join("\n");
|
|
856
935
|
}
|
|
857
|
-
function
|
|
936
|
+
function extractAssistantText(content) {
|
|
937
|
+
if (!Array.isArray(content)) return "";
|
|
938
|
+
const parts = [];
|
|
939
|
+
for (const block of content) {
|
|
940
|
+
if (typeof block === "object" && block !== null) {
|
|
941
|
+
const rec = block;
|
|
942
|
+
if (rec["type"] === "text" && typeof rec["text"] === "string") {
|
|
943
|
+
parts.push(rec["text"]);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return parts.join("");
|
|
948
|
+
}
|
|
949
|
+
function createStreamJsonToolParser(onToolResult, onToolUse, onMessageDelta, onMessageComplete) {
|
|
858
950
|
let buffer = "";
|
|
859
951
|
const toolNameById = /* @__PURE__ */ new Map();
|
|
952
|
+
let currentMessageId;
|
|
860
953
|
function processLine(line) {
|
|
861
954
|
const trimmed = line.trim();
|
|
862
955
|
if (!trimmed) return;
|
|
@@ -867,6 +960,19 @@ function createStreamJsonToolParser(onToolResult, onToolUse) {
|
|
|
867
960
|
return;
|
|
868
961
|
}
|
|
869
962
|
const record = parsed.type === "stream_event" && parsed.event ? parsed.event : parsed;
|
|
963
|
+
if (record.type === "message_start") {
|
|
964
|
+
const id = record.message?.["id"];
|
|
965
|
+
if (typeof id === "string") currentMessageId = id;
|
|
966
|
+
}
|
|
967
|
+
if (record.type === "content_block_delta") {
|
|
968
|
+
const delta = record["delta"];
|
|
969
|
+
if (typeof delta === "object" && delta !== null) {
|
|
970
|
+
const d = delta;
|
|
971
|
+
if (d["type"] === "text_delta" && typeof d["text"] === "string") {
|
|
972
|
+
onMessageDelta?.({ item_id: currentMessageId, delta: d["text"] });
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
870
976
|
const contentBlocks = (record.type === "assistant" ? record.message?.content : record.type === "message" && record.role === "assistant" ? record.content : void 0) ?? [];
|
|
871
977
|
for (const block of contentBlocks) {
|
|
872
978
|
if (typeof block !== "object" || block === null) continue;
|
|
@@ -876,6 +982,14 @@ function createStreamJsonToolParser(onToolResult, onToolUse) {
|
|
|
876
982
|
onToolUse?.({ tool_use_id: rec["id"], tool_name: rec["name"] });
|
|
877
983
|
}
|
|
878
984
|
}
|
|
985
|
+
if (record.type === "assistant" || record.type === "message" && record.role === "assistant") {
|
|
986
|
+
const messageText = extractAssistantText(contentBlocks);
|
|
987
|
+
if (messageText) {
|
|
988
|
+
const idValue = record.type === "assistant" ? record.message?.["id"] : record["id"];
|
|
989
|
+
const itemId = typeof idValue === "string" ? idValue : currentMessageId;
|
|
990
|
+
onMessageComplete?.({ item_id: itemId, message: messageText });
|
|
991
|
+
}
|
|
992
|
+
}
|
|
879
993
|
if (record.type === "tool_result") {
|
|
880
994
|
const toolUseId = typeof record.tool_use_id === "string" ? record.tool_use_id : void 0;
|
|
881
995
|
const text = extractResultText(record.content);
|
|
@@ -974,6 +1088,38 @@ function createServer2(opts) {
|
|
|
974
1088
|
},
|
|
975
1089
|
() => {
|
|
976
1090
|
transportStats.streamToolUses++;
|
|
1091
|
+
},
|
|
1092
|
+
(messageDelta) => {
|
|
1093
|
+
emit({
|
|
1094
|
+
id: `stream-msg-delta-${messageDelta.item_id ?? "unknown"}-${Date.now()}`,
|
|
1095
|
+
timestamp: Date.now(),
|
|
1096
|
+
kind: "message.delta",
|
|
1097
|
+
data: {
|
|
1098
|
+
item_id: messageDelta.item_id,
|
|
1099
|
+
delta: messageDelta.delta
|
|
1100
|
+
},
|
|
1101
|
+
hookName: "stream-json",
|
|
1102
|
+
sessionId,
|
|
1103
|
+
context: { cwd: projectDir, transcriptPath: "" },
|
|
1104
|
+
interaction: getInteractionHints("message.delta"),
|
|
1105
|
+
payload: null
|
|
1106
|
+
});
|
|
1107
|
+
},
|
|
1108
|
+
(messageComplete) => {
|
|
1109
|
+
emit({
|
|
1110
|
+
id: `stream-msg-complete-${messageComplete.item_id ?? "unknown"}-${Date.now()}`,
|
|
1111
|
+
timestamp: Date.now(),
|
|
1112
|
+
kind: "message.complete",
|
|
1113
|
+
data: {
|
|
1114
|
+
item_id: messageComplete.item_id,
|
|
1115
|
+
message: messageComplete.message
|
|
1116
|
+
},
|
|
1117
|
+
hookName: "stream-json",
|
|
1118
|
+
sessionId,
|
|
1119
|
+
context: { cwd: projectDir, transcriptPath: "" },
|
|
1120
|
+
interaction: getInteractionHints("message.complete"),
|
|
1121
|
+
payload: null
|
|
1122
|
+
});
|
|
977
1123
|
}
|
|
978
1124
|
);
|
|
979
1125
|
function emit(event) {
|
|
@@ -1374,6 +1520,10 @@ var TOOL_HOOK_EVENTS = [
|
|
|
1374
1520
|
"PreToolUse",
|
|
1375
1521
|
"PostToolUse",
|
|
1376
1522
|
"PostToolUseFailure",
|
|
1523
|
+
// Batch-level, not per-tool, but Claude accepts (and requires) a matcher
|
|
1524
|
+
// here: the #116 capture registered it with `matcher: '*'` and it fired in
|
|
1525
|
+
// every run. Matcher-less registration is unverified.
|
|
1526
|
+
"PostToolBatch",
|
|
1377
1527
|
"PermissionRequest",
|
|
1378
1528
|
"PermissionDenied",
|
|
1379
1529
|
"Elicitation",
|
|
@@ -1388,6 +1538,7 @@ var NON_TOOL_HOOK_EVENTS = [
|
|
|
1388
1538
|
"SubagentStart",
|
|
1389
1539
|
"SubagentStop",
|
|
1390
1540
|
"UserPromptSubmit",
|
|
1541
|
+
"UserPromptExpansion",
|
|
1391
1542
|
"PreCompact",
|
|
1392
1543
|
"PostCompact",
|
|
1393
1544
|
"Setup",
|
|
@@ -1400,6 +1551,27 @@ var NON_TOOL_HOOK_EVENTS = [
|
|
|
1400
1551
|
"WorktreeCreate",
|
|
1401
1552
|
"WorktreeRemove"
|
|
1402
1553
|
];
|
|
1554
|
+
var SYNC_HOOK_EVENTS = [
|
|
1555
|
+
// Decision/blocking — kept in step with `runtime/interactionRules.ts`.
|
|
1556
|
+
"PreToolUse",
|
|
1557
|
+
"PermissionRequest",
|
|
1558
|
+
"UserPromptSubmit",
|
|
1559
|
+
"Stop",
|
|
1560
|
+
"StopFailure",
|
|
1561
|
+
"SubagentStop",
|
|
1562
|
+
"Elicitation",
|
|
1563
|
+
"ElicitationResult",
|
|
1564
|
+
"TeammateIdle",
|
|
1565
|
+
"TaskCreated",
|
|
1566
|
+
"TaskCompleted",
|
|
1567
|
+
"ConfigChange",
|
|
1568
|
+
// Teardown — see (2) above.
|
|
1569
|
+
"SessionEnd"
|
|
1570
|
+
];
|
|
1571
|
+
var SYNC_HOOK_EVENT_SET = new Set(SYNC_HOOK_EVENTS);
|
|
1572
|
+
function isAsyncHookEvent(event) {
|
|
1573
|
+
return !SYNC_HOOK_EVENT_SET.has(event);
|
|
1574
|
+
}
|
|
1403
1575
|
function quoteShellArg(value) {
|
|
1404
1576
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
1405
1577
|
}
|
|
@@ -1447,23 +1619,20 @@ function generateHookSettings(tempDir, authOverlay) {
|
|
|
1447
1619
|
if (process.env["ATHENA_DEBUG"]) {
|
|
1448
1620
|
console.error("[athena-debug] Hook forwarder path:", hookForwarder.command);
|
|
1449
1621
|
}
|
|
1450
|
-
const
|
|
1451
|
-
type: "command",
|
|
1452
|
-
command: hookForwarder.command
|
|
1453
|
-
};
|
|
1622
|
+
const hookCommandFor = (event) => isAsyncHookEvent(event) ? { type: "command", command: hookForwarder.command, async: true } : { type: "command", command: hookForwarder.command };
|
|
1454
1623
|
const hooks = {};
|
|
1455
1624
|
for (const event of TOOL_HOOK_EVENTS) {
|
|
1456
1625
|
hooks[event] = [
|
|
1457
1626
|
{
|
|
1458
1627
|
matcher: "*",
|
|
1459
|
-
hooks: [
|
|
1628
|
+
hooks: [hookCommandFor(event)]
|
|
1460
1629
|
}
|
|
1461
1630
|
];
|
|
1462
1631
|
}
|
|
1463
1632
|
for (const event of NON_TOOL_HOOK_EVENTS) {
|
|
1464
1633
|
hooks[event] = [
|
|
1465
1634
|
{
|
|
1466
|
-
hooks: [
|
|
1635
|
+
hooks: [hookCommandFor(event)]
|
|
1467
1636
|
}
|
|
1468
1637
|
];
|
|
1469
1638
|
}
|
|
@@ -2823,6 +2992,17 @@ import os8 from "os";
|
|
|
2823
2992
|
import path10 from "path";
|
|
2824
2993
|
|
|
2825
2994
|
// src/harnesses/claude/config/isolation.ts
|
|
2995
|
+
var CLAUDE_EFFORT_LEVELS = [
|
|
2996
|
+
"low",
|
|
2997
|
+
"medium",
|
|
2998
|
+
"high",
|
|
2999
|
+
"xhigh",
|
|
3000
|
+
"max"
|
|
3001
|
+
];
|
|
3002
|
+
function normalizeEffort(value) {
|
|
3003
|
+
if (value === void 0) return void 0;
|
|
3004
|
+
return CLAUDE_EFFORT_LEVELS.includes(value) ? value : void 0;
|
|
3005
|
+
}
|
|
2826
3006
|
var DISALLOWED_FIRST_PARTY_MCPS = [
|
|
2827
3007
|
"mcp__claude_ai_Gmail__*",
|
|
2828
3008
|
"mcp__claude_ai_Google_Calendar__*",
|
|
@@ -2933,6 +3113,7 @@ var FLAG_REGISTRY = [
|
|
|
2933
3113
|
{ field: "additionalDirectories", flag: "--add-dir", kind: "array" },
|
|
2934
3114
|
// === Model & Agent ===
|
|
2935
3115
|
{ field: "model", flag: "--model", kind: "value" },
|
|
3116
|
+
{ field: "effort", flag: "--effort", kind: "value" },
|
|
2936
3117
|
{ field: "fallbackModel", flag: "--fallback-model", kind: "value" },
|
|
2937
3118
|
{ field: "agent", flag: "--agent", kind: "value" },
|
|
2938
3119
|
{ field: "agents", flag: "--agents", kind: "json" },
|
|
@@ -3374,7 +3555,7 @@ function createAssistantMessageAccumulator() {
|
|
|
3374
3555
|
};
|
|
3375
3556
|
}
|
|
3376
3557
|
|
|
3377
|
-
// src/harnesses/claude/
|
|
3558
|
+
// src/harnesses/claude/session/turnConfig.ts
|
|
3378
3559
|
function mergeIsolation(base, pluginMcpConfig, perCommand) {
|
|
3379
3560
|
if (!pluginMcpConfig && !perCommand) return base;
|
|
3380
3561
|
return {
|
|
@@ -3383,6 +3564,17 @@ function mergeIsolation(base, pluginMcpConfig, perCommand) {
|
|
|
3383
3564
|
...pluginMcpConfig ? { mcpConfig: pluginMcpConfig } : {}
|
|
3384
3565
|
};
|
|
3385
3566
|
}
|
|
3567
|
+
function resolveClaudeSessionId(continuation) {
|
|
3568
|
+
if (!continuation || continuation.mode === "fresh") {
|
|
3569
|
+
return void 0;
|
|
3570
|
+
}
|
|
3571
|
+
if (continuation.mode === "resume") {
|
|
3572
|
+
return continuation.handle;
|
|
3573
|
+
}
|
|
3574
|
+
throw new Error("Claude harness does not support reuse-current continuation");
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
// src/harnesses/claude/process/useProcess.ts
|
|
3386
3578
|
var MAX_OUTPUT = 1e3;
|
|
3387
3579
|
var KILL_TIMEOUT_MS = 3e3;
|
|
3388
3580
|
var NULL_TOKENS = {
|
|
@@ -3433,17 +3625,6 @@ function tokenUsageEquals(a, b) {
|
|
|
3433
3625
|
return a.input === b.input && a.output === b.output && a.cacheRead === b.cacheRead && a.cacheWrite === b.cacheWrite && a.total === b.total && a.contextSize === b.contextSize && a.contextWindowSize === b.contextWindowSize;
|
|
3434
3626
|
}
|
|
3435
3627
|
var JQ_ASSISTANT_TEXT_FILTER = 'select(.type == "message" and .role == "assistant") | .content[] | select(.type == "text") | .text';
|
|
3436
|
-
function resolveClaudeSessionId(continuation) {
|
|
3437
|
-
if (!continuation || continuation.mode === "fresh") {
|
|
3438
|
-
return void 0;
|
|
3439
|
-
}
|
|
3440
|
-
if (continuation.mode === "resume") {
|
|
3441
|
-
return continuation.handle;
|
|
3442
|
-
}
|
|
3443
|
-
throw new Error(
|
|
3444
|
-
"Claude process hook does not support reuse-current continuation"
|
|
3445
|
-
);
|
|
3446
|
-
}
|
|
3447
3628
|
function useClaudeProcess(projectDir, instanceId, isolation, pluginMcpConfig, verbose, workflow, options) {
|
|
3448
3629
|
const processRef = useRef(null);
|
|
3449
3630
|
const abortRef = useRef(new AbortController());
|
|
@@ -3728,25 +3909,6 @@ function useClaudeProcess(projectDir, instanceId, isolation, pluginMcpConfig, ve
|
|
|
3728
3909
|
}
|
|
3729
3910
|
|
|
3730
3911
|
// src/harnesses/claude/session/controller.ts
|
|
3731
|
-
function mergeIsolation2(base, pluginMcpConfig, overrides) {
|
|
3732
|
-
if (!pluginMcpConfig && !overrides) return base;
|
|
3733
|
-
return {
|
|
3734
|
-
...resolveIsolationConfig(base),
|
|
3735
|
-
...overrides ?? {},
|
|
3736
|
-
...pluginMcpConfig ? { mcpConfig: pluginMcpConfig } : {}
|
|
3737
|
-
};
|
|
3738
|
-
}
|
|
3739
|
-
function resolveClaudeSessionId2(continuation) {
|
|
3740
|
-
if (!continuation || continuation.mode === "fresh") {
|
|
3741
|
-
return void 0;
|
|
3742
|
-
}
|
|
3743
|
-
if (continuation.mode === "resume") {
|
|
3744
|
-
return continuation.handle;
|
|
3745
|
-
}
|
|
3746
|
-
throw new Error(
|
|
3747
|
-
"Claude session controller does not support reuse-current continuation"
|
|
3748
|
-
);
|
|
3749
|
-
}
|
|
3750
3912
|
function createClaudeSessionController(input) {
|
|
3751
3913
|
const spawnProcess = input.spawnProcess ?? spawnClaude;
|
|
3752
3914
|
const processConfig = input.processConfig;
|
|
@@ -3791,8 +3953,8 @@ function createClaudeSessionController(input) {
|
|
|
3791
3953
|
prompt,
|
|
3792
3954
|
projectDir: input.projectDir,
|
|
3793
3955
|
instanceId: input.instanceId,
|
|
3794
|
-
sessionId:
|
|
3795
|
-
isolation:
|
|
3956
|
+
sessionId: resolveClaudeSessionId(continuation),
|
|
3957
|
+
isolation: mergeIsolation(
|
|
3796
3958
|
processConfig,
|
|
3797
3959
|
input.pluginMcpConfig,
|
|
3798
3960
|
configOverride
|
|
@@ -3849,20 +4011,49 @@ function buildClaudeCompatibleIsolationConfig({
|
|
|
3849
4011
|
additionalDirectories,
|
|
3850
4012
|
pluginDirs,
|
|
3851
4013
|
verbose,
|
|
3852
|
-
configuredModel
|
|
4014
|
+
configuredModel,
|
|
4015
|
+
configuredEffort
|
|
3853
4016
|
}) {
|
|
3854
4017
|
return {
|
|
3855
4018
|
preset: isolationPreset,
|
|
3856
4019
|
additionalDirectories,
|
|
3857
4020
|
pluginDirs: pluginDirs.length > 0 ? pluginDirs : void 0,
|
|
3858
4021
|
debug: verbose,
|
|
3859
|
-
model: configuredModel
|
|
4022
|
+
model: configuredModel,
|
|
4023
|
+
effort: normalizeEffort(configuredEffort)
|
|
3860
4024
|
};
|
|
3861
4025
|
}
|
|
4026
|
+
var CLAUDE_MODEL_OPTIONS = [
|
|
4027
|
+
{
|
|
4028
|
+
value: "sonnet",
|
|
4029
|
+
label: "Sonnet",
|
|
4030
|
+
description: "Balanced default for day-to-day coding work."
|
|
4031
|
+
},
|
|
4032
|
+
{
|
|
4033
|
+
value: "opus",
|
|
4034
|
+
label: "Opus",
|
|
4035
|
+
description: "Stronger reasoning for complex architecture and debugging."
|
|
4036
|
+
},
|
|
4037
|
+
{
|
|
4038
|
+
value: "haiku",
|
|
4039
|
+
label: "Haiku",
|
|
4040
|
+
description: "Fastest option for lighter tasks."
|
|
4041
|
+
},
|
|
4042
|
+
{
|
|
4043
|
+
value: "opusplan",
|
|
4044
|
+
label: "OpusPlan",
|
|
4045
|
+
description: "Uses Opus for planning and Sonnet for execution."
|
|
4046
|
+
}
|
|
4047
|
+
];
|
|
3862
4048
|
var CLAUDE_CONFIG_PROFILE = {
|
|
3863
4049
|
harness: "claude-code",
|
|
3864
4050
|
buildIsolationConfig: buildClaudeCompatibleIsolationConfig,
|
|
3865
|
-
resolveModelName: ({ projectDir, configuredModel }) => resolveClaudeModel({ projectDir, configuredModel })
|
|
4051
|
+
resolveModelName: ({ projectDir, configuredModel }) => resolveClaudeModel({ projectDir, configuredModel }),
|
|
4052
|
+
pluginDelivery: {
|
|
4053
|
+
mergeWorkflowPluginDirs: true,
|
|
4054
|
+
registrationBuildsMcpConfig: true,
|
|
4055
|
+
workflowPluginsVia: "registration"
|
|
4056
|
+
}
|
|
3866
4057
|
};
|
|
3867
4058
|
var claudeHarnessAdapter = {
|
|
3868
4059
|
id: "claude-code",
|
|
@@ -3872,7 +4063,9 @@ var claudeHarnessAdapter = {
|
|
|
3872
4063
|
conversationModel: "fresh_per_turn",
|
|
3873
4064
|
killWaitsForTurnSettlement: true,
|
|
3874
4065
|
supportsEphemeralSessions: false,
|
|
3875
|
-
supportsConfigurableIsolation: true
|
|
4066
|
+
supportsConfigurableIsolation: true,
|
|
4067
|
+
emitsStartupDiagnostics: true,
|
|
4068
|
+
extraAllowedTools: []
|
|
3876
4069
|
},
|
|
3877
4070
|
verify: () => verifyClaudeHarness(),
|
|
3878
4071
|
createRuntime: (input) => createClaudeHookRuntime({
|
|
@@ -3908,7 +4101,8 @@ var claudeHarnessAdapter = {
|
|
|
3908
4101
|
};
|
|
3909
4102
|
return controller;
|
|
3910
4103
|
},
|
|
3911
|
-
resolveConfigProfile: () => CLAUDE_CONFIG_PROFILE
|
|
4104
|
+
resolveConfigProfile: () => CLAUDE_CONFIG_PROFILE,
|
|
4105
|
+
listModels: async () => CLAUDE_MODEL_OPTIONS
|
|
3912
4106
|
};
|
|
3913
4107
|
|
|
3914
4108
|
// src/harnesses/codex/runtime/appServerManager.ts
|
|
@@ -6751,6 +6945,74 @@ function buildCodexPromptOptions(input) {
|
|
|
6751
6945
|
};
|
|
6752
6946
|
}
|
|
6753
6947
|
|
|
6948
|
+
// src/harnesses/codex/session/turnRunner.ts
|
|
6949
|
+
function createCodexTurnEventCollector() {
|
|
6950
|
+
let message = "";
|
|
6951
|
+
let tokenDelta = { ...NULL_TOKENS2 };
|
|
6952
|
+
let turnStatus;
|
|
6953
|
+
let turnErrorMessage;
|
|
6954
|
+
return {
|
|
6955
|
+
handle(event) {
|
|
6956
|
+
const data = typeof event.data === "object" ? event.data : {};
|
|
6957
|
+
if (event.kind === "message.delta") {
|
|
6958
|
+
const delta = typeof data["delta"] === "string" ? data["delta"] : "";
|
|
6959
|
+
message += delta;
|
|
6960
|
+
}
|
|
6961
|
+
if (event.kind === "usage.update") {
|
|
6962
|
+
tokenDelta = readTokenUsage(data["delta"]);
|
|
6963
|
+
}
|
|
6964
|
+
if (event.kind === "turn.complete") {
|
|
6965
|
+
turnStatus = typeof data["status"] === "string" ? data["status"] : turnStatus;
|
|
6966
|
+
}
|
|
6967
|
+
if (event.kind === "unknown" && event.hookName === "error") {
|
|
6968
|
+
const payload = typeof data["payload"] === "object" && data["payload"] !== null ? data["payload"] : null;
|
|
6969
|
+
const errorValue = typeof payload?.["error"] === "object" && payload["error"] !== null ? payload["error"] : null;
|
|
6970
|
+
if (typeof errorValue?.["message"] === "string") {
|
|
6971
|
+
turnErrorMessage = errorValue["message"];
|
|
6972
|
+
}
|
|
6973
|
+
}
|
|
6974
|
+
},
|
|
6975
|
+
result() {
|
|
6976
|
+
if (turnStatus === "failed") {
|
|
6977
|
+
return {
|
|
6978
|
+
exitCode: 1,
|
|
6979
|
+
error: new Error(turnErrorMessage ?? "Codex turn failed"),
|
|
6980
|
+
tokens: tokenDelta,
|
|
6981
|
+
streamMessage: message || null
|
|
6982
|
+
};
|
|
6983
|
+
}
|
|
6984
|
+
return {
|
|
6985
|
+
exitCode: 0,
|
|
6986
|
+
error: null,
|
|
6987
|
+
tokens: tokenDelta,
|
|
6988
|
+
streamMessage: message || null
|
|
6989
|
+
};
|
|
6990
|
+
},
|
|
6991
|
+
errorResult(error) {
|
|
6992
|
+
return {
|
|
6993
|
+
exitCode: null,
|
|
6994
|
+
error,
|
|
6995
|
+
tokens: tokenDelta,
|
|
6996
|
+
streamMessage: message || null
|
|
6997
|
+
};
|
|
6998
|
+
}
|
|
6999
|
+
};
|
|
7000
|
+
}
|
|
7001
|
+
async function runCodexTurn(runtime, prompt, optionsInput, hooks) {
|
|
7002
|
+
const collector = createCodexTurnEventCollector();
|
|
7003
|
+
const unsubscribe = runtime.onEvent(collector.handle);
|
|
7004
|
+
try {
|
|
7005
|
+
await runtime.sendPrompt(prompt, buildCodexPromptOptions(optionsInput));
|
|
7006
|
+
return collector.result();
|
|
7007
|
+
} catch (error) {
|
|
7008
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
7009
|
+
hooks?.onError?.(normalized);
|
|
7010
|
+
return collector.errorResult(normalized);
|
|
7011
|
+
} finally {
|
|
7012
|
+
unsubscribe();
|
|
7013
|
+
}
|
|
7014
|
+
}
|
|
7015
|
+
|
|
6754
7016
|
// src/harnesses/codex/session/controller.ts
|
|
6755
7017
|
function createCodexSessionController(input) {
|
|
6756
7018
|
const runtime = input.runtime;
|
|
@@ -6770,73 +7032,19 @@ function createCodexSessionController(input) {
|
|
|
6770
7032
|
streamMessage: null
|
|
6771
7033
|
};
|
|
6772
7034
|
}
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
const delta = typeof data["delta"] === "string" ? data["delta"] : "";
|
|
6781
|
-
message += delta;
|
|
6782
|
-
}
|
|
6783
|
-
if (event.kind === "usage.update") {
|
|
6784
|
-
tokenDelta = readTokenUsage(data["delta"]);
|
|
6785
|
-
}
|
|
6786
|
-
if (event.kind === "turn.complete") {
|
|
6787
|
-
turnStatus = typeof data["status"] === "string" ? data["status"] : turnStatus;
|
|
6788
|
-
}
|
|
6789
|
-
if (event.kind === "unknown" && event.hookName === "error") {
|
|
6790
|
-
const payload = typeof data["payload"] === "object" && data["payload"] !== null ? data["payload"] : null;
|
|
6791
|
-
const errorValue = typeof payload?.["error"] === "object" && payload["error"] !== null ? payload["error"] : null;
|
|
6792
|
-
if (typeof errorValue?.["message"] === "string") {
|
|
6793
|
-
turnErrorMessage = errorValue["message"];
|
|
6794
|
-
}
|
|
6795
|
-
}
|
|
7035
|
+
const turnPromise = runCodexTurn(runtime, prompt, {
|
|
7036
|
+
processConfig,
|
|
7037
|
+
continuation,
|
|
7038
|
+
configOverride,
|
|
7039
|
+
workflowPlan: input.workflowPlan,
|
|
7040
|
+
pluginMcpConfig: input.pluginMcpConfig,
|
|
7041
|
+
ephemeral: input.ephemeral
|
|
6796
7042
|
});
|
|
6797
|
-
const turnPromise = (async () => {
|
|
6798
|
-
try {
|
|
6799
|
-
await runtime.sendPrompt(
|
|
6800
|
-
prompt,
|
|
6801
|
-
buildCodexPromptOptions({
|
|
6802
|
-
processConfig,
|
|
6803
|
-
continuation,
|
|
6804
|
-
configOverride,
|
|
6805
|
-
workflowPlan: input.workflowPlan,
|
|
6806
|
-
pluginMcpConfig: input.pluginMcpConfig,
|
|
6807
|
-
ephemeral: input.ephemeral
|
|
6808
|
-
})
|
|
6809
|
-
);
|
|
6810
|
-
if (turnStatus === "failed") {
|
|
6811
|
-
return {
|
|
6812
|
-
exitCode: 1,
|
|
6813
|
-
error: new Error(turnErrorMessage ?? "Codex turn failed"),
|
|
6814
|
-
tokens: tokenDelta,
|
|
6815
|
-
streamMessage: message || null
|
|
6816
|
-
};
|
|
6817
|
-
}
|
|
6818
|
-
return {
|
|
6819
|
-
exitCode: 0,
|
|
6820
|
-
error: null,
|
|
6821
|
-
tokens: tokenDelta,
|
|
6822
|
-
streamMessage: message || null
|
|
6823
|
-
};
|
|
6824
|
-
} catch (error) {
|
|
6825
|
-
return {
|
|
6826
|
-
exitCode: null,
|
|
6827
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
6828
|
-
tokens: tokenDelta,
|
|
6829
|
-
streamMessage: message || null
|
|
6830
|
-
};
|
|
6831
|
-
} finally {
|
|
6832
|
-
activeTurnPromise = null;
|
|
6833
|
-
}
|
|
6834
|
-
})();
|
|
6835
7043
|
activeTurnPromise = turnPromise;
|
|
6836
7044
|
try {
|
|
6837
7045
|
return await turnPromise;
|
|
6838
7046
|
} finally {
|
|
6839
|
-
|
|
7047
|
+
activeTurnPromise = null;
|
|
6840
7048
|
}
|
|
6841
7049
|
},
|
|
6842
7050
|
interrupt() {
|
|
@@ -6893,81 +7101,38 @@ function useCodexSessionController(runtime, processConfig, workflowPlan, ephemer
|
|
|
6893
7101
|
};
|
|
6894
7102
|
}
|
|
6895
7103
|
setIsRunning(true);
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
turnErrorMessage = errorValue["message"];
|
|
6917
|
-
}
|
|
6918
|
-
}
|
|
6919
|
-
});
|
|
6920
|
-
const turnPromise = (async () => {
|
|
6921
|
-
try {
|
|
6922
|
-
await codexRuntime.sendPrompt(
|
|
6923
|
-
prompt,
|
|
6924
|
-
buildCodexPromptOptions({
|
|
6925
|
-
processConfig,
|
|
6926
|
-
continuation,
|
|
6927
|
-
configOverride: _configOverride,
|
|
6928
|
-
workflowPlan,
|
|
6929
|
-
pluginMcpConfig,
|
|
6930
|
-
ephemeral
|
|
6931
|
-
})
|
|
6932
|
-
);
|
|
6933
|
-
if (turnStatus === "failed") {
|
|
6934
|
-
return {
|
|
6935
|
-
exitCode: 1,
|
|
6936
|
-
error: new Error(turnErrorMessage ?? "Codex turn failed"),
|
|
6937
|
-
tokens: turnTokens,
|
|
6938
|
-
streamMessage: streamedMessage || null
|
|
6939
|
-
};
|
|
6940
|
-
}
|
|
6941
|
-
return {
|
|
6942
|
-
exitCode: 0,
|
|
6943
|
-
error: null,
|
|
6944
|
-
tokens: turnTokens,
|
|
6945
|
-
streamMessage: streamedMessage || null
|
|
6946
|
-
};
|
|
6947
|
-
} catch (error) {
|
|
6948
|
-
if (!abortRef.current.signal.aborted) {
|
|
6949
|
-
onLifecycleEventRef.current?.({
|
|
6950
|
-
type: "spawn_error",
|
|
6951
|
-
message: error instanceof Error ? error.message : "Unknown Codex error",
|
|
6952
|
-
failureCode: "spawn_error"
|
|
6953
|
-
});
|
|
6954
|
-
}
|
|
6955
|
-
return {
|
|
6956
|
-
exitCode: null,
|
|
6957
|
-
error: error instanceof Error ? error : new Error("Unknown Codex error"),
|
|
6958
|
-
tokens: turnTokens,
|
|
6959
|
-
streamMessage: streamedMessage || null
|
|
6960
|
-
};
|
|
6961
|
-
} finally {
|
|
6962
|
-
activeTurnPromiseRef.current = null;
|
|
6963
|
-
unsubscribe();
|
|
6964
|
-
if (!abortRef.current.signal.aborted) {
|
|
6965
|
-
setIsRunning(false);
|
|
7104
|
+
const turnPromise = runCodexTurn(
|
|
7105
|
+
codexRuntime,
|
|
7106
|
+
prompt,
|
|
7107
|
+
{
|
|
7108
|
+
processConfig,
|
|
7109
|
+
continuation,
|
|
7110
|
+
configOverride: _configOverride,
|
|
7111
|
+
workflowPlan,
|
|
7112
|
+
pluginMcpConfig,
|
|
7113
|
+
ephemeral
|
|
7114
|
+
},
|
|
7115
|
+
{
|
|
7116
|
+
onError: (error) => {
|
|
7117
|
+
if (!abortRef.current.signal.aborted) {
|
|
7118
|
+
onLifecycleEventRef.current?.({
|
|
7119
|
+
type: "spawn_error",
|
|
7120
|
+
message: error.message,
|
|
7121
|
+
failureCode: "spawn_error"
|
|
7122
|
+
});
|
|
7123
|
+
}
|
|
6966
7124
|
}
|
|
6967
7125
|
}
|
|
6968
|
-
|
|
7126
|
+
);
|
|
6969
7127
|
activeTurnPromiseRef.current = turnPromise;
|
|
6970
|
-
|
|
7128
|
+
try {
|
|
7129
|
+
return await turnPromise;
|
|
7130
|
+
} finally {
|
|
7131
|
+
activeTurnPromiseRef.current = null;
|
|
7132
|
+
if (!abortRef.current.signal.aborted) {
|
|
7133
|
+
setIsRunning(false);
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
6971
7136
|
},
|
|
6972
7137
|
[codexRuntime, processConfig, workflowPlan, pluginMcpConfig, ephemeral]
|
|
6973
7138
|
);
|
|
@@ -7076,7 +7241,12 @@ var CODEX_CONFIG_PROFILE = {
|
|
|
7076
7241
|
preset: isolationPreset,
|
|
7077
7242
|
model: configuredModel
|
|
7078
7243
|
}),
|
|
7079
|
-
resolveModelName: ({ configuredModel }) => configuredModel ?? null
|
|
7244
|
+
resolveModelName: ({ configuredModel }) => configuredModel ?? null,
|
|
7245
|
+
pluginDelivery: {
|
|
7246
|
+
mergeWorkflowPluginDirs: false,
|
|
7247
|
+
registrationBuildsMcpConfig: false,
|
|
7248
|
+
workflowPluginsVia: "generated-mcp"
|
|
7249
|
+
}
|
|
7080
7250
|
};
|
|
7081
7251
|
var codexHarnessAdapter = {
|
|
7082
7252
|
id: "openai-codex",
|
|
@@ -7086,7 +7256,12 @@ var codexHarnessAdapter = {
|
|
|
7086
7256
|
conversationModel: "persistent_thread",
|
|
7087
7257
|
killWaitsForTurnSettlement: true,
|
|
7088
7258
|
supportsEphemeralSessions: true,
|
|
7089
|
-
supportsConfigurableIsolation: true
|
|
7259
|
+
supportsConfigurableIsolation: true,
|
|
7260
|
+
emitsStartupDiagnostics: false,
|
|
7261
|
+
// Codex auto-approves Bash/Edit (outside-sandbox commandExecution /
|
|
7262
|
+
// fileChange) and the scoped 'Permissions' capability, on top of the
|
|
7263
|
+
// shared 'mcp__*' baseline seeded via allowedTools (see buildInitialRules).
|
|
7264
|
+
extraAllowedTools: ["Permissions", "Bash", "Edit"]
|
|
7090
7265
|
},
|
|
7091
7266
|
verify: () => verifyCodexHarness(),
|
|
7092
7267
|
createRuntime: (input) => createCodexRuntime({
|
|
@@ -7113,8 +7288,23 @@ var codexHarnessAdapter = {
|
|
|
7113
7288
|
};
|
|
7114
7289
|
return controller;
|
|
7115
7290
|
},
|
|
7116
|
-
resolveConfigProfile: () => CODEX_CONFIG_PROFILE
|
|
7291
|
+
resolveConfigProfile: () => CODEX_CONFIG_PROFILE,
|
|
7292
|
+
listModels: async (runtime) => {
|
|
7293
|
+
if (!isCodexRuntimeWithModels(runtime)) {
|
|
7294
|
+
throw new Error("Codex runtime is not available");
|
|
7295
|
+
}
|
|
7296
|
+
const models = await runtime.listModels();
|
|
7297
|
+
return models.map((model) => ({
|
|
7298
|
+
value: model.model,
|
|
7299
|
+
label: model.displayName,
|
|
7300
|
+
description: model.description,
|
|
7301
|
+
isDefault: model.isDefault
|
|
7302
|
+
}));
|
|
7303
|
+
}
|
|
7117
7304
|
};
|
|
7305
|
+
function isCodexRuntimeWithModels(runtime) {
|
|
7306
|
+
return Boolean(runtime && "listModels" in runtime);
|
|
7307
|
+
}
|
|
7118
7308
|
|
|
7119
7309
|
// src/harnesses/registry.ts
|
|
7120
7310
|
function buildClaudeCompatibleIsolationConfig2({
|
|
@@ -7140,7 +7330,9 @@ var opencodeHarnessAdapter = {
|
|
|
7140
7330
|
conversationModel: "fresh_per_turn",
|
|
7141
7331
|
killWaitsForTurnSettlement: true,
|
|
7142
7332
|
supportsEphemeralSessions: false,
|
|
7143
|
-
supportsConfigurableIsolation: false
|
|
7333
|
+
supportsConfigurableIsolation: false,
|
|
7334
|
+
emitsStartupDiagnostics: false,
|
|
7335
|
+
extraAllowedTools: []
|
|
7144
7336
|
},
|
|
7145
7337
|
createRuntime: (input) => claudeHarnessAdapter.createRuntime(input),
|
|
7146
7338
|
createSessionController: (input) => claudeHarnessAdapter.createSessionController(input),
|
|
@@ -7148,8 +7340,14 @@ var opencodeHarnessAdapter = {
|
|
|
7148
7340
|
resolveConfigProfile: () => ({
|
|
7149
7341
|
harness: "opencode",
|
|
7150
7342
|
buildIsolationConfig: (input) => buildClaudeCompatibleIsolationConfig2(input),
|
|
7151
|
-
resolveModelName: ({ configuredModel }) => configuredModel ?? null
|
|
7152
|
-
|
|
7343
|
+
resolveModelName: ({ configuredModel }) => configuredModel ?? null,
|
|
7344
|
+
pluginDelivery: {
|
|
7345
|
+
mergeWorkflowPluginDirs: true,
|
|
7346
|
+
registrationBuildsMcpConfig: true,
|
|
7347
|
+
workflowPluginsVia: "registration"
|
|
7348
|
+
}
|
|
7349
|
+
}),
|
|
7350
|
+
listModels: async () => []
|
|
7153
7351
|
};
|
|
7154
7352
|
var HARNESS_ADAPTERS = [
|
|
7155
7353
|
claudeHarnessAdapter,
|
|
@@ -7265,6 +7463,28 @@ function loadPlugin(pluginDir) {
|
|
|
7265
7463
|
}
|
|
7266
7464
|
return commands2;
|
|
7267
7465
|
}
|
|
7466
|
+
function loadPersonalSkills(skills) {
|
|
7467
|
+
const commands2 = [];
|
|
7468
|
+
for (const skill of skills) {
|
|
7469
|
+
const skillPath = path12.join(skill.path, "SKILL.md");
|
|
7470
|
+
if (!fs14.existsSync(skillPath)) {
|
|
7471
|
+
console.warn(
|
|
7472
|
+
`Skipping personal skill '${skill.name}' [${skill.sourceLayer}]: SKILL.md not found at ${skill.path}`
|
|
7473
|
+
);
|
|
7474
|
+
continue;
|
|
7475
|
+
}
|
|
7476
|
+
try {
|
|
7477
|
+
const parsed = parseFrontmatter(fs14.readFileSync(skillPath, "utf-8"));
|
|
7478
|
+
commands2.push(skillToCommand(parsed.frontmatter, parsed.body));
|
|
7479
|
+
} catch (error) {
|
|
7480
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7481
|
+
console.warn(
|
|
7482
|
+
`Skipping personal skill '${skill.name}' [${skill.sourceLayer}]: ${message}`
|
|
7483
|
+
);
|
|
7484
|
+
}
|
|
7485
|
+
}
|
|
7486
|
+
return commands2;
|
|
7487
|
+
}
|
|
7268
7488
|
function skillToCommand(frontmatter, body, mcpConfigPath) {
|
|
7269
7489
|
const args = frontmatter["argument-hint"] ? [
|
|
7270
7490
|
{
|
|
@@ -7288,8 +7508,9 @@ function skillToCommand(frontmatter, body, mcpConfigPath) {
|
|
|
7288
7508
|
}
|
|
7289
7509
|
|
|
7290
7510
|
// src/infra/plugins/register.ts
|
|
7291
|
-
function buildPluginMcpConfig(pluginDirs, mcpServerOptions) {
|
|
7511
|
+
function buildPluginMcpConfig(pluginDirs, mcpServerOptions, personalMcpServers = []) {
|
|
7292
7512
|
const mergedServers = {};
|
|
7513
|
+
const conflicts = [];
|
|
7293
7514
|
for (const dir of pluginDirs) {
|
|
7294
7515
|
const mcpPath = path13.join(dir, ".mcp.json");
|
|
7295
7516
|
if (!fs15.existsSync(mcpPath)) {
|
|
@@ -7315,22 +7536,99 @@ function buildPluginMcpConfig(pluginDirs, mcpServerOptions) {
|
|
|
7315
7536
|
mergedServers[serverName] = rest;
|
|
7316
7537
|
}
|
|
7317
7538
|
}
|
|
7539
|
+
for (const personal of personalMcpServers) {
|
|
7540
|
+
const { name, sourceLayer: _sourceLayer, ...server } = personal;
|
|
7541
|
+
if (name in mergedServers) {
|
|
7542
|
+
conflicts.push(personal);
|
|
7543
|
+
continue;
|
|
7544
|
+
}
|
|
7545
|
+
mergedServers[name] = server;
|
|
7546
|
+
}
|
|
7318
7547
|
if (Object.keys(mergedServers).length === 0) {
|
|
7319
|
-
return void 0;
|
|
7548
|
+
return { mcpConfig: void 0, conflicts };
|
|
7320
7549
|
}
|
|
7321
7550
|
const mcpConfig = path13.join(os10.tmpdir(), `athena-mcp-${process.pid}.json`);
|
|
7322
7551
|
fs15.writeFileSync(mcpConfig, JSON.stringify({ mcpServers: mergedServers }));
|
|
7323
|
-
return mcpConfig;
|
|
7552
|
+
return { mcpConfig, conflicts };
|
|
7324
7553
|
}
|
|
7325
|
-
function registerPlugins(pluginDirs, mcpServerOptions, includeMcpConfig = true) {
|
|
7554
|
+
function registerPlugins(pluginDirs, mcpServerOptions, includeMcpConfig = true, personalMcpServers = [], personalSkills = []) {
|
|
7326
7555
|
for (const dir of pluginDirs) {
|
|
7327
7556
|
const commands2 = loadPlugin(dir);
|
|
7328
7557
|
for (const command of commands2) {
|
|
7329
7558
|
register(command);
|
|
7330
7559
|
}
|
|
7331
7560
|
}
|
|
7332
|
-
const
|
|
7333
|
-
|
|
7561
|
+
const skillByName = new Map(personalSkills.map((skill) => [skill.name, skill]));
|
|
7562
|
+
const skillConflicts = [];
|
|
7563
|
+
for (const command of loadPersonalSkills(personalSkills)) {
|
|
7564
|
+
if (get(command.name)) {
|
|
7565
|
+
const shadowed = skillByName.get(command.name);
|
|
7566
|
+
if (shadowed) {
|
|
7567
|
+
skillConflicts.push(shadowed);
|
|
7568
|
+
}
|
|
7569
|
+
continue;
|
|
7570
|
+
}
|
|
7571
|
+
register(command);
|
|
7572
|
+
}
|
|
7573
|
+
const mcpResult = includeMcpConfig ? buildPluginMcpConfig(pluginDirs, mcpServerOptions, personalMcpServers) : { mcpConfig: void 0, conflicts: [] };
|
|
7574
|
+
return {
|
|
7575
|
+
mcpConfig: mcpResult.mcpConfig,
|
|
7576
|
+
conflicts: { mcpServers: mcpResult.conflicts, skills: skillConflicts }
|
|
7577
|
+
};
|
|
7578
|
+
}
|
|
7579
|
+
|
|
7580
|
+
// src/infra/plugins/pluginRefResolution.ts
|
|
7581
|
+
function resolvePluginDirs(entries) {
|
|
7582
|
+
const dirs = [];
|
|
7583
|
+
const warnings = [];
|
|
7584
|
+
for (const entry of entries) {
|
|
7585
|
+
if (!isMarketplaceRef(entry)) {
|
|
7586
|
+
dirs.push(entry);
|
|
7587
|
+
continue;
|
|
7588
|
+
}
|
|
7589
|
+
try {
|
|
7590
|
+
dirs.push(resolveMarketplacePlugin(entry));
|
|
7591
|
+
} catch (error) {
|
|
7592
|
+
warnings.push(`Skipping plugin "${entry}": ${error.message}`);
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
7595
|
+
return { dirs, warnings };
|
|
7596
|
+
}
|
|
7597
|
+
|
|
7598
|
+
// src/infra/capabilities/effective.ts
|
|
7599
|
+
function resolveEffectiveCapabilities(input) {
|
|
7600
|
+
const { globalConfig, projectConfig } = input;
|
|
7601
|
+
return {
|
|
7602
|
+
mcpServers: mergeMcpServers(
|
|
7603
|
+
globalConfig.mcpServers,
|
|
7604
|
+
projectConfig.mcpServers
|
|
7605
|
+
),
|
|
7606
|
+
skills: mergeSkills(globalConfig.skills, projectConfig.skills)
|
|
7607
|
+
};
|
|
7608
|
+
}
|
|
7609
|
+
function mergeMcpServers(global, project) {
|
|
7610
|
+
const projectNames = new Set(Object.keys(project ?? {}));
|
|
7611
|
+
const result = [];
|
|
7612
|
+
for (const [name, server] of Object.entries(global ?? {})) {
|
|
7613
|
+
if (projectNames.has(name)) continue;
|
|
7614
|
+
result.push({ name, ...server, sourceLayer: "global" });
|
|
7615
|
+
}
|
|
7616
|
+
for (const [name, server] of Object.entries(project ?? {})) {
|
|
7617
|
+
result.push({ name, ...server, sourceLayer: "project" });
|
|
7618
|
+
}
|
|
7619
|
+
return result;
|
|
7620
|
+
}
|
|
7621
|
+
function mergeSkills(global, project) {
|
|
7622
|
+
const projectNames = new Set((project ?? []).map((s) => s.name));
|
|
7623
|
+
const result = [];
|
|
7624
|
+
for (const skill of global ?? []) {
|
|
7625
|
+
if (projectNames.has(skill.name)) continue;
|
|
7626
|
+
result.push({ ...skill, sourceLayer: "global" });
|
|
7627
|
+
}
|
|
7628
|
+
for (const skill of project ?? []) {
|
|
7629
|
+
result.push({ ...skill, sourceLayer: "project" });
|
|
7630
|
+
}
|
|
7631
|
+
return result;
|
|
7334
7632
|
}
|
|
7335
7633
|
|
|
7336
7634
|
// src/setup/shouldResolveWorkflow.ts
|
|
@@ -7386,6 +7684,9 @@ function bootstrapRuntimeConfig({
|
|
|
7386
7684
|
const projectConfig = providedProjectConfig ?? readConfig(projectDir);
|
|
7387
7685
|
const warnings = [];
|
|
7388
7686
|
const harness = harnessOverride ?? projectConfig.harness ?? globalConfig.harness ?? DEFAULT_HARNESS;
|
|
7687
|
+
const harnessConfigProfile = resolveHarnessConfigProfile(harness);
|
|
7688
|
+
const pluginDelivery = harnessConfigProfile.pluginDelivery;
|
|
7689
|
+
const workflowPluginsAsGeneratedMcp = pluginDelivery.workflowPluginsVia === "generated-mcp";
|
|
7389
7690
|
const activeWorkflowSelection = resolveActiveWorkflow({
|
|
7390
7691
|
globalConfig,
|
|
7391
7692
|
projectConfig,
|
|
@@ -7408,34 +7709,45 @@ function bootstrapRuntimeConfig({
|
|
|
7408
7709
|
(plugin) => plugin.claudeArtifactDir
|
|
7409
7710
|
);
|
|
7410
7711
|
}
|
|
7712
|
+
const globalPluginResolution = resolvePluginDirs(globalConfig.plugins);
|
|
7713
|
+
const projectPluginResolution = resolvePluginDirs(projectConfig.plugins);
|
|
7714
|
+
warnings.push(
|
|
7715
|
+
...globalPluginResolution.warnings,
|
|
7716
|
+
...projectPluginResolution.warnings
|
|
7717
|
+
);
|
|
7411
7718
|
const pluginDirs = mergePluginDirs({
|
|
7412
|
-
workflowPluginDirs:
|
|
7413
|
-
globalPlugins:
|
|
7414
|
-
projectPlugins:
|
|
7719
|
+
workflowPluginDirs: pluginDelivery.mergeWorkflowPluginDirs ? workflowPluginDirs : [],
|
|
7720
|
+
globalPlugins: globalPluginResolution.dirs,
|
|
7721
|
+
projectPlugins: projectPluginResolution.dirs,
|
|
7415
7722
|
pluginFlags
|
|
7416
7723
|
});
|
|
7417
|
-
const
|
|
7724
|
+
const effectiveCapabilities = harness === "openai-codex" ? { mcpServers: [], skills: [] } : resolveEffectiveCapabilities({ globalConfig, projectConfig });
|
|
7725
|
+
const personalMcpServers = effectiveCapabilities.mcpServers;
|
|
7726
|
+
const personalSkills = effectiveCapabilities.skills;
|
|
7727
|
+
const pluginResult = pluginDirs.length > 0 || personalMcpServers.length > 0 || personalSkills.length > 0 ? registerPlugins(
|
|
7418
7728
|
pluginDirs,
|
|
7419
7729
|
workflowToResolve ? activeWorkflowConfig.workflowSelections?.[workflowToResolve]?.mcpServerOptions : void 0,
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7730
|
+
pluginDelivery.registrationBuildsMcpConfig,
|
|
7731
|
+
personalMcpServers,
|
|
7732
|
+
personalSkills
|
|
7733
|
+
) : { mcpConfig: void 0, conflicts: { mcpServers: [], skills: [] } };
|
|
7734
|
+
const workflowPluginMcpConfig = workflowPluginsAsGeneratedMcp ? buildPluginMcpConfig(
|
|
7423
7735
|
workflowPluginDirs,
|
|
7424
7736
|
workflowToResolve ? activeWorkflowConfig.workflowSelections?.[workflowToResolve]?.mcpServerOptions : void 0
|
|
7425
|
-
) : void 0;
|
|
7426
|
-
const pluginMcpConfig =
|
|
7737
|
+
).mcpConfig : void 0;
|
|
7738
|
+
const pluginMcpConfig = workflowPluginsAsGeneratedMcp ? void 0 : pluginResult.mcpConfig;
|
|
7427
7739
|
const activeWorkflow = resolvedWorkflow;
|
|
7428
7740
|
const additionalDirectories = [
|
|
7429
7741
|
...globalConfig.additionalDirectories,
|
|
7430
7742
|
...projectConfig.additionalDirectories
|
|
7431
7743
|
];
|
|
7432
|
-
const harnessConfigProfile = resolveHarnessConfigProfile(harness);
|
|
7433
7744
|
const workflowPlan = compileWorkflowPlan({
|
|
7434
7745
|
workflow: activeWorkflow,
|
|
7435
7746
|
resolvedPlugins: activeWorkflow && resolvedWorkflow?.name === activeWorkflow.name ? workflowResolvedPlugins : void 0,
|
|
7436
|
-
pluginMcpConfig:
|
|
7747
|
+
pluginMcpConfig: workflowPluginsAsGeneratedMcp && activeWorkflow && resolvedWorkflow?.name === activeWorkflow.name ? workflowPluginMcpConfig : pluginResult.mcpConfig
|
|
7437
7748
|
});
|
|
7438
7749
|
const configModel = projectConfig.model || globalConfig.model || activeWorkflow?.model;
|
|
7750
|
+
const configEffort = activeWorkflow?.effort;
|
|
7439
7751
|
let isolationPreset = initialIsolationPreset;
|
|
7440
7752
|
if (activeWorkflow?.isolation) {
|
|
7441
7753
|
const presetOrder = ["strict", "minimal", "permissive"];
|
|
@@ -7454,7 +7766,8 @@ function bootstrapRuntimeConfig({
|
|
|
7454
7766
|
additionalDirectories,
|
|
7455
7767
|
pluginDirs,
|
|
7456
7768
|
verbose,
|
|
7457
|
-
configuredModel: configModel
|
|
7769
|
+
configuredModel: configModel,
|
|
7770
|
+
configuredEffort: configEffort
|
|
7458
7771
|
});
|
|
7459
7772
|
const modelName = harnessConfigProfile.resolveModelName({
|
|
7460
7773
|
projectDir,
|
|
@@ -7470,6 +7783,9 @@ function bootstrapRuntimeConfig({
|
|
|
7470
7783
|
workflow: activeWorkflow,
|
|
7471
7784
|
workflowPlan,
|
|
7472
7785
|
modelName,
|
|
7786
|
+
personalMcpServers,
|
|
7787
|
+
personalSkills,
|
|
7788
|
+
capabilityConflicts: pluginResult.conflicts,
|
|
7473
7789
|
warnings
|
|
7474
7790
|
};
|
|
7475
7791
|
}
|
|
@@ -7491,7 +7807,7 @@ var EXEC_EXIT_CODE = {
|
|
|
7491
7807
|
|
|
7492
7808
|
// src/app/exec/runner.ts
|
|
7493
7809
|
import crypto2 from "crypto";
|
|
7494
|
-
import
|
|
7810
|
+
import path17 from "path";
|
|
7495
7811
|
|
|
7496
7812
|
// src/core/feed/entities.ts
|
|
7497
7813
|
var ActorRegistry = class {
|
|
@@ -7682,8 +7998,10 @@ function generateTitle(event, ascii = false) {
|
|
|
7682
7998
|
}
|
|
7683
7999
|
function generateNeutralTitle(event, g) {
|
|
7684
8000
|
switch (event.kind) {
|
|
7685
|
-
case "session.start":
|
|
7686
|
-
|
|
8001
|
+
case "session.start": {
|
|
8002
|
+
const sessionTitle = event.data.session_title?.trim();
|
|
8003
|
+
return sessionTitle ? truncate(sessionTitle) : `Session started (${event.data.source})`;
|
|
8004
|
+
}
|
|
7687
8005
|
case "session.end":
|
|
7688
8006
|
return `Session ended (${event.data.reason})`;
|
|
7689
8007
|
case "run.start":
|
|
@@ -7692,6 +8010,8 @@ function generateNeutralTitle(event, g) {
|
|
|
7692
8010
|
return `Run ${event.data.status}`;
|
|
7693
8011
|
case "user.prompt":
|
|
7694
8012
|
return truncate(event.data.prompt);
|
|
8013
|
+
case "prompt.expansion":
|
|
8014
|
+
return event.data.command_name ? truncate(`Expanded /${event.data.command_name}`) : "Prompt expanded";
|
|
7695
8015
|
case "plan.update":
|
|
7696
8016
|
return truncate(
|
|
7697
8017
|
event.data.explanation || event.data.delta || "Plan updated"
|
|
@@ -7706,6 +8026,10 @@ function generateNeutralTitle(event, g) {
|
|
|
7706
8026
|
return `${g["tool.bullet"]} ${event.data.tool_name}`;
|
|
7707
8027
|
case "tool.post":
|
|
7708
8028
|
return `${g["tool.gutter"]} ${event.data.tool_name} result`;
|
|
8029
|
+
case "tool.batch": {
|
|
8030
|
+
const n = event.data.tool_calls.length;
|
|
8031
|
+
return `Batch of ${n} tool call${n === 1 ? "" : "s"}`;
|
|
8032
|
+
}
|
|
7709
8033
|
case "tool.failure":
|
|
7710
8034
|
return truncate(
|
|
7711
8035
|
`${g["status.blocked"]} ${event.data.tool_name} failed: ${event.data.error}`
|
|
@@ -7936,6 +8260,7 @@ function createRunLifecycle(boundary) {
|
|
|
7936
8260
|
let currentRun = null;
|
|
7937
8261
|
let seq = 0;
|
|
7938
8262
|
let runSeq = 0;
|
|
8263
|
+
let currentPromptId;
|
|
7939
8264
|
function getRunId() {
|
|
7940
8265
|
const sessId = currentSession?.session_id ?? "unknown";
|
|
7941
8266
|
return `${sessId}:R${runSeq}`;
|
|
@@ -7984,7 +8309,13 @@ function createRunLifecycle(boundary) {
|
|
|
7984
8309
|
);
|
|
7985
8310
|
}
|
|
7986
8311
|
function beginRun(runtimeEvent, triggerType = "other", promptPreview) {
|
|
7987
|
-
|
|
8312
|
+
const promptId = runtimeEvent.promptId;
|
|
8313
|
+
const isExplicitContextTrigger = triggerType === "resume" || triggerType === "clear" || triggerType === "compact";
|
|
8314
|
+
if (promptId !== void 0 && !isExplicitContextTrigger) {
|
|
8315
|
+
if (currentRun && promptId === currentPromptId) return [];
|
|
8316
|
+
} else if (promptId === void 0 && currentRun && triggerType === "other") {
|
|
8317
|
+
return [];
|
|
8318
|
+
}
|
|
7988
8319
|
const results = [];
|
|
7989
8320
|
const closeEvt = closeRunIntoEvent(runtimeEvent, "completed");
|
|
7990
8321
|
if (closeEvt) results.push(closeEvt);
|
|
@@ -7995,6 +8326,7 @@ function createRunLifecycle(boundary) {
|
|
|
7995
8326
|
triggerType,
|
|
7996
8327
|
promptPreview
|
|
7997
8328
|
);
|
|
8329
|
+
currentPromptId = promptId;
|
|
7998
8330
|
results.push(
|
|
7999
8331
|
makeEvent(
|
|
8000
8332
|
"run.start",
|
|
@@ -8074,6 +8406,7 @@ function createRunLifecycle(boundary) {
|
|
|
8074
8406
|
if (e.kind === "tool.failure") currentRun.counters.tool_failures++;
|
|
8075
8407
|
if (e.kind === "permission.request")
|
|
8076
8408
|
currentRun.counters.permission_requests++;
|
|
8409
|
+
if (e.prompt_id !== void 0) currentPromptId = e.prompt_id;
|
|
8077
8410
|
}
|
|
8078
8411
|
}
|
|
8079
8412
|
}
|
|
@@ -8336,28 +8669,11 @@ function createAgentMessageStream(eventBuilder, transcriptReader) {
|
|
|
8336
8669
|
};
|
|
8337
8670
|
}
|
|
8338
8671
|
|
|
8339
|
-
// src/core/feed/internals/
|
|
8340
|
-
function
|
|
8341
|
-
|
|
8342
|
-
return
|
|
8343
|
-
set(next) {
|
|
8344
|
-
items = next;
|
|
8345
|
-
},
|
|
8346
|
-
current() {
|
|
8347
|
-
return items;
|
|
8348
|
-
},
|
|
8349
|
-
differs(next) {
|
|
8350
|
-
if (next.length !== items.length) return true;
|
|
8351
|
-
for (let i = 0; i < next.length; i++) {
|
|
8352
|
-
if (next[i]?.content !== items[i]?.content) return true;
|
|
8353
|
-
if (next[i]?.status !== items[i]?.status) return true;
|
|
8354
|
-
}
|
|
8355
|
-
return false;
|
|
8356
|
-
}
|
|
8357
|
-
};
|
|
8672
|
+
// src/core/feed/internals/taskStateTracker.ts
|
|
8673
|
+
function extractTodoItems(toolInput) {
|
|
8674
|
+
const input = toolInput;
|
|
8675
|
+
return Array.isArray(input?.todos) ? input.todos : [];
|
|
8358
8676
|
}
|
|
8359
|
-
|
|
8360
|
-
// src/core/feed/internals/taskLifecycleTracker.ts
|
|
8361
8677
|
function coerceTaskStatus(status) {
|
|
8362
8678
|
switch (status) {
|
|
8363
8679
|
case "pending":
|
|
@@ -8369,57 +8685,6 @@ function coerceTaskStatus(status) {
|
|
|
8369
8685
|
return null;
|
|
8370
8686
|
}
|
|
8371
8687
|
}
|
|
8372
|
-
function createTaskLifecycleTracker() {
|
|
8373
|
-
let items = [];
|
|
8374
|
-
return {
|
|
8375
|
-
current() {
|
|
8376
|
-
return items;
|
|
8377
|
-
},
|
|
8378
|
-
upsertCreated({ taskId, subject, description, activeForm }) {
|
|
8379
|
-
const task = {
|
|
8380
|
-
taskId,
|
|
8381
|
-
content: subject,
|
|
8382
|
-
status: "pending",
|
|
8383
|
-
activeForm: activeForm ?? description
|
|
8384
|
-
};
|
|
8385
|
-
const existingIndex = items.findIndex((item) => item.taskId === taskId);
|
|
8386
|
-
if (existingIndex === -1) {
|
|
8387
|
-
items = [...items, task];
|
|
8388
|
-
return;
|
|
8389
|
-
}
|
|
8390
|
-
items = items.map(
|
|
8391
|
-
(item, index) => index === existingIndex ? {
|
|
8392
|
-
...item,
|
|
8393
|
-
taskId,
|
|
8394
|
-
content: subject,
|
|
8395
|
-
activeForm: task.activeForm ?? item.activeForm
|
|
8396
|
-
} : item
|
|
8397
|
-
);
|
|
8398
|
-
},
|
|
8399
|
-
markCompleted({ taskId, subject }) {
|
|
8400
|
-
const existingIndex = items.findIndex((item) => item.taskId === taskId);
|
|
8401
|
-
if (existingIndex === -1) {
|
|
8402
|
-
if (!subject) return;
|
|
8403
|
-
items = [...items, { taskId, content: subject, status: "completed" }];
|
|
8404
|
-
return;
|
|
8405
|
-
}
|
|
8406
|
-
items = items.map(
|
|
8407
|
-
(item, index) => index === existingIndex ? { ...item, status: "completed" } : item
|
|
8408
|
-
);
|
|
8409
|
-
},
|
|
8410
|
-
updateStatus({ taskId, status }) {
|
|
8411
|
-
items = items.map(
|
|
8412
|
-
(item) => item.taskId === taskId ? { ...item, status } : item
|
|
8413
|
-
);
|
|
8414
|
-
}
|
|
8415
|
-
};
|
|
8416
|
-
}
|
|
8417
|
-
|
|
8418
|
-
// src/core/feed/internals/taskStateTracker.ts
|
|
8419
|
-
function extractTodoItems(toolInput) {
|
|
8420
|
-
const input = toolInput;
|
|
8421
|
-
return Array.isArray(input?.todos) ? input.todos : [];
|
|
8422
|
-
}
|
|
8423
8688
|
function mapPlanStepStatus(status) {
|
|
8424
8689
|
switch (status) {
|
|
8425
8690
|
case "inProgress":
|
|
@@ -8432,18 +8697,69 @@ function mapPlanStepStatus(status) {
|
|
|
8432
8697
|
}
|
|
8433
8698
|
}
|
|
8434
8699
|
function createTaskStateTracker() {
|
|
8435
|
-
|
|
8436
|
-
|
|
8700
|
+
let rootPlanItems = [];
|
|
8701
|
+
let taskItems = [];
|
|
8702
|
+
function rootPlanDiffers(next) {
|
|
8703
|
+
if (next.length !== rootPlanItems.length) return true;
|
|
8704
|
+
for (let i = 0; i < next.length; i++) {
|
|
8705
|
+
if (next[i]?.content !== rootPlanItems[i]?.content) return true;
|
|
8706
|
+
if (next[i]?.status !== rootPlanItems[i]?.status) return true;
|
|
8707
|
+
}
|
|
8708
|
+
return false;
|
|
8709
|
+
}
|
|
8710
|
+
function upsertCreatedTask(input) {
|
|
8711
|
+
const { taskId, subject, description, activeForm } = input;
|
|
8712
|
+
const task = {
|
|
8713
|
+
taskId,
|
|
8714
|
+
content: subject,
|
|
8715
|
+
status: "pending",
|
|
8716
|
+
activeForm: activeForm ?? description
|
|
8717
|
+
};
|
|
8718
|
+
const existingIndex = taskItems.findIndex((item) => item.taskId === taskId);
|
|
8719
|
+
if (existingIndex === -1) {
|
|
8720
|
+
taskItems = [...taskItems, task];
|
|
8721
|
+
return;
|
|
8722
|
+
}
|
|
8723
|
+
taskItems = taskItems.map(
|
|
8724
|
+
(item, index) => index === existingIndex ? {
|
|
8725
|
+
...item,
|
|
8726
|
+
taskId,
|
|
8727
|
+
content: subject,
|
|
8728
|
+
activeForm: task.activeForm ?? item.activeForm
|
|
8729
|
+
} : item
|
|
8730
|
+
);
|
|
8731
|
+
}
|
|
8732
|
+
function markTaskCompleted(input) {
|
|
8733
|
+
const { taskId, subject } = input;
|
|
8734
|
+
const existingIndex = taskItems.findIndex((item) => item.taskId === taskId);
|
|
8735
|
+
if (existingIndex === -1) {
|
|
8736
|
+
if (!subject) return;
|
|
8737
|
+
taskItems = [
|
|
8738
|
+
...taskItems,
|
|
8739
|
+
{ taskId, content: subject, status: "completed" }
|
|
8740
|
+
];
|
|
8741
|
+
return;
|
|
8742
|
+
}
|
|
8743
|
+
taskItems = taskItems.map(
|
|
8744
|
+
(item, index) => index === existingIndex ? { ...item, status: "completed" } : item
|
|
8745
|
+
);
|
|
8746
|
+
}
|
|
8747
|
+
function updateTaskStatus(input) {
|
|
8748
|
+
const { taskId, status } = input;
|
|
8749
|
+
taskItems = taskItems.map(
|
|
8750
|
+
(item) => item.taskId === taskId ? { ...item, status } : item
|
|
8751
|
+
);
|
|
8752
|
+
}
|
|
8437
8753
|
function applyToolPre(input) {
|
|
8438
8754
|
const { toolName, toolInput, actorId } = input;
|
|
8439
8755
|
if (toolName === "TodoWrite" && actorId === "agent:root") {
|
|
8440
|
-
|
|
8756
|
+
rootPlanItems = extractTodoItems(toolInput);
|
|
8441
8757
|
}
|
|
8442
8758
|
if (toolName === "TaskUpdate") {
|
|
8443
8759
|
const taskId = readString(toolInput["taskId"], toolInput["task_id"]);
|
|
8444
8760
|
const status = coerceTaskStatus(toolInput["status"]);
|
|
8445
8761
|
if (taskId && status) {
|
|
8446
|
-
|
|
8762
|
+
updateTaskStatus({ taskId, status });
|
|
8447
8763
|
}
|
|
8448
8764
|
}
|
|
8449
8765
|
}
|
|
@@ -8455,7 +8771,7 @@ function createTaskStateTracker() {
|
|
|
8455
8771
|
const taskId = readString(task["id"], task["task_id"]);
|
|
8456
8772
|
const subject = readString(task["subject"], toolInput["subject"]);
|
|
8457
8773
|
if (taskId && subject) {
|
|
8458
|
-
|
|
8774
|
+
upsertCreatedTask({
|
|
8459
8775
|
taskId,
|
|
8460
8776
|
subject,
|
|
8461
8777
|
description: readString(toolInput["description"]),
|
|
@@ -8475,7 +8791,7 @@ function createTaskStateTracker() {
|
|
|
8475
8791
|
readObject(response["statusChange"])["to"] ?? toolInput["status"]
|
|
8476
8792
|
);
|
|
8477
8793
|
if (taskId && status) {
|
|
8478
|
-
|
|
8794
|
+
updateTaskStatus({ taskId, status });
|
|
8479
8795
|
}
|
|
8480
8796
|
}
|
|
8481
8797
|
}
|
|
@@ -8484,19 +8800,19 @@ function createTaskStateTracker() {
|
|
|
8484
8800
|
const subject = readString(data["task_subject"]);
|
|
8485
8801
|
const description = readString(data["task_description"]);
|
|
8486
8802
|
if (taskId && subject) {
|
|
8487
|
-
|
|
8803
|
+
upsertCreatedTask({ taskId, subject, description });
|
|
8488
8804
|
}
|
|
8489
8805
|
}
|
|
8490
8806
|
function applyTaskCompletedEvent(data) {
|
|
8491
8807
|
const taskId = readString(data["task_id"]);
|
|
8492
8808
|
const subject = readString(data["task_subject"]);
|
|
8493
8809
|
if (taskId) {
|
|
8494
|
-
|
|
8810
|
+
markTaskCompleted({ taskId, subject });
|
|
8495
8811
|
}
|
|
8496
8812
|
}
|
|
8497
8813
|
return {
|
|
8498
8814
|
current() {
|
|
8499
|
-
return [...
|
|
8815
|
+
return [...rootPlanItems, ...taskItems];
|
|
8500
8816
|
},
|
|
8501
8817
|
applyToolPre,
|
|
8502
8818
|
applyToolPost,
|
|
@@ -8506,8 +8822,8 @@ function createTaskStateTracker() {
|
|
|
8506
8822
|
content: typeof step.step === "string" ? step.step : "",
|
|
8507
8823
|
status: mapPlanStepStatus(step.status)
|
|
8508
8824
|
}));
|
|
8509
|
-
if (!
|
|
8510
|
-
|
|
8825
|
+
if (!rootPlanDiffers(next)) return false;
|
|
8826
|
+
rootPlanItems = next;
|
|
8511
8827
|
return true;
|
|
8512
8828
|
},
|
|
8513
8829
|
applyTaskCreatedEvent,
|
|
@@ -8538,48 +8854,6 @@ function createTaskStateTracker() {
|
|
|
8538
8854
|
};
|
|
8539
8855
|
}
|
|
8540
8856
|
|
|
8541
|
-
// src/core/feed/internals/subagentTracker.ts
|
|
8542
|
-
function createSubagentTracker() {
|
|
8543
|
-
const stack = [];
|
|
8544
|
-
const descriptions = /* @__PURE__ */ new Map();
|
|
8545
|
-
let pendingDescription;
|
|
8546
|
-
return {
|
|
8547
|
-
pushActor(actorId) {
|
|
8548
|
-
stack.push(actorId);
|
|
8549
|
-
},
|
|
8550
|
-
popActor(actorId) {
|
|
8551
|
-
const idx = stack.lastIndexOf(actorId);
|
|
8552
|
-
if (idx !== -1) stack.splice(idx, 1);
|
|
8553
|
-
},
|
|
8554
|
-
peek() {
|
|
8555
|
-
return stack.length > 0 ? stack[stack.length - 1] : void 0;
|
|
8556
|
-
},
|
|
8557
|
-
clear() {
|
|
8558
|
-
stack.length = 0;
|
|
8559
|
-
},
|
|
8560
|
-
currentScope() {
|
|
8561
|
-
return stack.length > 0 ? "subagent" : "root";
|
|
8562
|
-
},
|
|
8563
|
-
recordPendingDescription(description) {
|
|
8564
|
-
pendingDescription = description;
|
|
8565
|
-
},
|
|
8566
|
-
clearPendingDescription() {
|
|
8567
|
-
pendingDescription = void 0;
|
|
8568
|
-
},
|
|
8569
|
-
consumePendingDescription() {
|
|
8570
|
-
const value = pendingDescription;
|
|
8571
|
-
pendingDescription = void 0;
|
|
8572
|
-
return value;
|
|
8573
|
-
},
|
|
8574
|
-
setDescription(agentId, description) {
|
|
8575
|
-
descriptions.set(agentId, description);
|
|
8576
|
-
},
|
|
8577
|
-
description(agentId) {
|
|
8578
|
-
return descriptions.get(agentId);
|
|
8579
|
-
}
|
|
8580
|
-
};
|
|
8581
|
-
}
|
|
8582
|
-
|
|
8583
8857
|
// src/core/feed/todo.ts
|
|
8584
8858
|
function isSubagentTool(toolName) {
|
|
8585
8859
|
return toolName === "Task" || toolName === "Agent";
|
|
@@ -8588,44 +8862,48 @@ function isSubagentTool(toolName) {
|
|
|
8588
8862
|
// src/core/feed/internals/subagentLifecycle.ts
|
|
8589
8863
|
function createSubagentLifecycle(args) {
|
|
8590
8864
|
const { actors, runLifecycle } = args;
|
|
8591
|
-
const
|
|
8865
|
+
const stack = [];
|
|
8866
|
+
const descriptions = /* @__PURE__ */ new Map();
|
|
8867
|
+
let pendingDescription;
|
|
8592
8868
|
const actorIdFor = (agentId) => `subagent:${agentId}`;
|
|
8593
8869
|
return {
|
|
8594
8870
|
observeToolInput(toolName, toolInput) {
|
|
8595
8871
|
if (!isSubagentTool(toolName)) return;
|
|
8596
|
-
|
|
8597
|
-
tracker.recordPendingDescription(toolInput["description"]);
|
|
8598
|
-
} else {
|
|
8599
|
-
tracker.clearPendingDescription();
|
|
8600
|
-
}
|
|
8872
|
+
pendingDescription = typeof toolInput["description"] === "string" ? toolInput["description"] : void 0;
|
|
8601
8873
|
},
|
|
8602
8874
|
startSubagent({ agentId, agentType, fallbackDescription }) {
|
|
8603
|
-
const
|
|
8875
|
+
const consumed = pendingDescription;
|
|
8876
|
+
pendingDescription = void 0;
|
|
8877
|
+
const description = consumed ?? fallbackDescription;
|
|
8604
8878
|
if (agentId) {
|
|
8605
8879
|
actors.ensureSubagent(agentId, agentType ?? "unknown");
|
|
8606
8880
|
const currentRun = runLifecycle.getCurrentRun();
|
|
8607
8881
|
if (currentRun) currentRun.actors.subagent_ids.push(agentId);
|
|
8608
|
-
|
|
8609
|
-
if (description)
|
|
8882
|
+
stack.push(actorIdFor(agentId));
|
|
8883
|
+
if (description) descriptions.set(agentId, description);
|
|
8610
8884
|
}
|
|
8611
8885
|
return { actorId: "agent:root", description: description ?? void 0 };
|
|
8612
8886
|
},
|
|
8613
8887
|
stopSubagent(agentId) {
|
|
8614
|
-
if (agentId)
|
|
8888
|
+
if (agentId) {
|
|
8889
|
+
const actorId = actorIdFor(agentId);
|
|
8890
|
+
const idx = stack.lastIndexOf(actorId);
|
|
8891
|
+
if (idx !== -1) stack.splice(idx, 1);
|
|
8892
|
+
}
|
|
8615
8893
|
return {
|
|
8616
8894
|
actorId: actorIdFor(agentId ?? "unknown"),
|
|
8617
|
-
description:
|
|
8895
|
+
description: descriptions.get(agentId ?? "")
|
|
8618
8896
|
};
|
|
8619
8897
|
},
|
|
8620
8898
|
currentActor() {
|
|
8621
|
-
return
|
|
8899
|
+
return stack.at(-1) ?? "agent:root";
|
|
8622
8900
|
},
|
|
8623
8901
|
currentScope() {
|
|
8624
|
-
return
|
|
8902
|
+
return stack.length > 0 ? "subagent" : "root";
|
|
8625
8903
|
},
|
|
8626
8904
|
actorIdFor,
|
|
8627
8905
|
clear() {
|
|
8628
|
-
|
|
8906
|
+
stack.length = 0;
|
|
8629
8907
|
}
|
|
8630
8908
|
};
|
|
8631
8909
|
}
|
|
@@ -8640,6 +8918,15 @@ function toolUseCause(toolUseId, parentId) {
|
|
|
8640
8918
|
...parentId ? { parent_event_id: parentId } : {}
|
|
8641
8919
|
};
|
|
8642
8920
|
}
|
|
8921
|
+
function readToolBatchCalls2(value) {
|
|
8922
|
+
if (!Array.isArray(value)) return [];
|
|
8923
|
+
return value.filter((entry) => typeof entry === "object" && entry !== null).map((entry) => readObject(entry)).map((call) => ({
|
|
8924
|
+
tool_name: readString(call["tool_name"]),
|
|
8925
|
+
tool_input: readObject(call["tool_input"]),
|
|
8926
|
+
tool_use_id: readString(call["tool_use_id"]),
|
|
8927
|
+
tool_response: readString(call["tool_response"])
|
|
8928
|
+
}));
|
|
8929
|
+
}
|
|
8643
8930
|
function createToolProjection(args) {
|
|
8644
8931
|
const {
|
|
8645
8932
|
ensureRunArray,
|
|
@@ -8696,6 +8983,21 @@ function createToolProjection(args) {
|
|
|
8696
8983
|
return {
|
|
8697
8984
|
mapToolEvent(event, data) {
|
|
8698
8985
|
const results = ensureRunArray(event);
|
|
8986
|
+
if (event.kind === "tool.batch") {
|
|
8987
|
+
results.push(
|
|
8988
|
+
makeEvent(
|
|
8989
|
+
"tool.batch",
|
|
8990
|
+
"info",
|
|
8991
|
+
resolveToolActor(),
|
|
8992
|
+
{
|
|
8993
|
+
tool_calls: readToolBatchCalls2(data["tool_calls"]),
|
|
8994
|
+
permission_mode: readString(data["permission_mode"])
|
|
8995
|
+
},
|
|
8996
|
+
event
|
|
8997
|
+
)
|
|
8998
|
+
);
|
|
8999
|
+
return results;
|
|
9000
|
+
}
|
|
8699
9001
|
const toolUseId = resolveToolUseId(event, data);
|
|
8700
9002
|
const toolName = event.toolName ?? readString(data["tool_name"]) ?? "Unknown";
|
|
8701
9003
|
const toolInput = readObject(data["tool_input"]);
|
|
@@ -9474,7 +9776,8 @@ function createRunSessionProjection(args) {
|
|
|
9474
9776
|
{
|
|
9475
9777
|
source,
|
|
9476
9778
|
agent_type: readString(data["agent_type"]),
|
|
9477
|
-
model: readString(data["model"])
|
|
9779
|
+
model: readString(data["model"]),
|
|
9780
|
+
session_title: readString(data["session_title"])
|
|
9478
9781
|
},
|
|
9479
9782
|
event
|
|
9480
9783
|
)
|
|
@@ -9519,6 +9822,34 @@ function createRunSessionProjection(args) {
|
|
|
9519
9822
|
);
|
|
9520
9823
|
return results;
|
|
9521
9824
|
}
|
|
9825
|
+
if (event.kind === "prompt.expansion") {
|
|
9826
|
+
if (event.promptId !== void 0) {
|
|
9827
|
+
results.push(
|
|
9828
|
+
...ensureRunArray(
|
|
9829
|
+
event,
|
|
9830
|
+
"user_prompt_submit",
|
|
9831
|
+
readString(data["prompt"])?.slice(0, 80)
|
|
9832
|
+
)
|
|
9833
|
+
);
|
|
9834
|
+
}
|
|
9835
|
+
results.push(
|
|
9836
|
+
makeEvent(
|
|
9837
|
+
"prompt.expansion",
|
|
9838
|
+
"info",
|
|
9839
|
+
"user",
|
|
9840
|
+
{
|
|
9841
|
+
expansion_type: readString(data["expansion_type"]),
|
|
9842
|
+
command_name: readString(data["command_name"]),
|
|
9843
|
+
command_args: readString(data["command_args"]),
|
|
9844
|
+
command_source: readString(data["command_source"]),
|
|
9845
|
+
prompt: readString(data["prompt"]),
|
|
9846
|
+
permission_mode: event.context.permissionMode ?? readString(data["permission_mode"])
|
|
9847
|
+
},
|
|
9848
|
+
event
|
|
9849
|
+
)
|
|
9850
|
+
);
|
|
9851
|
+
return results;
|
|
9852
|
+
}
|
|
9522
9853
|
if (event.kind === "turn.start") {
|
|
9523
9854
|
agentMessageStream.clearPending();
|
|
9524
9855
|
const prompt = readString(data["prompt"]);
|
|
@@ -9751,6 +10082,7 @@ var RUN_SESSION_EVENT_KINDS = /* @__PURE__ */ new Set([
|
|
|
9751
10082
|
"session.start",
|
|
9752
10083
|
"session.end",
|
|
9753
10084
|
"user.prompt",
|
|
10085
|
+
"prompt.expansion",
|
|
9754
10086
|
"turn.start",
|
|
9755
10087
|
"message.delta",
|
|
9756
10088
|
"message.complete",
|
|
@@ -9763,6 +10095,7 @@ var TOOL_EVENT_KINDS = /* @__PURE__ */ new Set([
|
|
|
9763
10095
|
"tool.delta",
|
|
9764
10096
|
"tool.pre",
|
|
9765
10097
|
"tool.post",
|
|
10098
|
+
"tool.batch",
|
|
9766
10099
|
"tool.failure"
|
|
9767
10100
|
]);
|
|
9768
10101
|
var DECISION_EVENT_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -9824,6 +10157,8 @@ function createFeedMapper(bootstrap) {
|
|
|
9824
10157
|
ts: runtimeEvent.timestamp,
|
|
9825
10158
|
session_id: runtimeEvent.sessionId,
|
|
9826
10159
|
run_id: runId,
|
|
10160
|
+
prompt_id: runtimeEvent.promptId,
|
|
10161
|
+
effort_level: runtimeEvent.effortLevel,
|
|
9827
10162
|
kind,
|
|
9828
10163
|
level,
|
|
9829
10164
|
actor_id: actorId,
|
|
@@ -9978,185 +10313,10 @@ function createFeedMapper(bootstrap) {
|
|
|
9978
10313
|
};
|
|
9979
10314
|
}
|
|
9980
10315
|
|
|
9981
|
-
// src/core/controller/rules.ts
|
|
9982
|
-
function ruleMatches(ruleToolName, toolName) {
|
|
9983
|
-
if (ruleToolName === "*") return true;
|
|
9984
|
-
if (ruleToolName === toolName) return true;
|
|
9985
|
-
if (ruleToolName.endsWith("__*")) {
|
|
9986
|
-
const prefix = ruleToolName.slice(0, -1);
|
|
9987
|
-
return toolName.startsWith(prefix);
|
|
9988
|
-
}
|
|
9989
|
-
return false;
|
|
9990
|
-
}
|
|
9991
|
-
function matchRule(rules, toolName) {
|
|
9992
|
-
const denyMatch = rules.find(
|
|
9993
|
-
(r) => r.action === "deny" && ruleMatches(r.toolName, toolName)
|
|
9994
|
-
);
|
|
9995
|
-
if (denyMatch) return denyMatch;
|
|
9996
|
-
return rules.find(
|
|
9997
|
-
(r) => r.action === "approve" && ruleMatches(r.toolName, toolName)
|
|
9998
|
-
);
|
|
9999
|
-
}
|
|
10000
|
-
|
|
10001
|
-
// src/core/controller/permission.ts
|
|
10002
|
-
var SESSION_APPROVAL_REQUEST_HOOKS = /* @__PURE__ */ new Set([
|
|
10003
|
-
"item/commandExecution/requestApproval",
|
|
10004
|
-
"item/fileChange/requestApproval",
|
|
10005
|
-
"item/permissions/requestApproval"
|
|
10006
|
-
]);
|
|
10007
|
-
function isScopedPermissionsRequest(hookName) {
|
|
10008
|
-
return hookName === "item/permissions/requestApproval";
|
|
10009
|
-
}
|
|
10010
|
-
function supportsSessionApproval(hookName) {
|
|
10011
|
-
return hookName !== void 0 && SESSION_APPROVAL_REQUEST_HOOKS.has(hookName);
|
|
10012
|
-
}
|
|
10013
|
-
function extractPermissionSnapshot(event) {
|
|
10014
|
-
const data = event.data;
|
|
10015
|
-
const toolNameFromData = typeof data["tool_name"] === "string" ? data["tool_name"] : void 0;
|
|
10016
|
-
const toolInputFromData = typeof data["tool_input"] === "object" && data["tool_input"] !== null ? data["tool_input"] : void 0;
|
|
10017
|
-
const toolUseIdFromData = typeof data["tool_use_id"] === "string" ? data["tool_use_id"] : void 0;
|
|
10018
|
-
return {
|
|
10019
|
-
request_id: event.id,
|
|
10020
|
-
ts: event.timestamp,
|
|
10021
|
-
kind: event.kind,
|
|
10022
|
-
hookName: event.hookName,
|
|
10023
|
-
tool_name: event.toolName ?? toolNameFromData ?? "Unknown",
|
|
10024
|
-
tool_input: toolInputFromData ?? {},
|
|
10025
|
-
tool_use_id: event.toolUseId ?? toolUseIdFromData,
|
|
10026
|
-
suggestions: data.permission_suggestions
|
|
10027
|
-
};
|
|
10028
|
-
}
|
|
10029
|
-
|
|
10030
|
-
// src/core/controller/runtimeController.ts
|
|
10031
|
-
function handleEvent(event, cb) {
|
|
10032
|
-
const eventKind2 = event.kind;
|
|
10033
|
-
const isScoped = isScopedPermissionsRequest(event.hookName);
|
|
10034
|
-
const eventData = event.data;
|
|
10035
|
-
const toolName = event.toolName ?? (typeof eventData["tool_name"] === "string" ? eventData["tool_name"] : void 0);
|
|
10036
|
-
if (eventKind2 === "permission.request" && toolName === "user_input") {
|
|
10037
|
-
cb.enqueueQuestion(event.id);
|
|
10038
|
-
cb.relayQuestion?.(event);
|
|
10039
|
-
return { handled: true };
|
|
10040
|
-
}
|
|
10041
|
-
if (eventKind2 === "permission.request" && toolName) {
|
|
10042
|
-
const rule = matchRule(cb.getRules(), toolName);
|
|
10043
|
-
if (rule?.action === "deny") {
|
|
10044
|
-
return {
|
|
10045
|
-
handled: true,
|
|
10046
|
-
decision: {
|
|
10047
|
-
type: "json",
|
|
10048
|
-
source: "rule",
|
|
10049
|
-
intent: {
|
|
10050
|
-
kind: "permission_deny",
|
|
10051
|
-
reason: `Blocked by rule: ${rule.addedBy}`
|
|
10052
|
-
}
|
|
10053
|
-
}
|
|
10054
|
-
};
|
|
10055
|
-
}
|
|
10056
|
-
if (rule?.action === "approve") {
|
|
10057
|
-
return {
|
|
10058
|
-
handled: true,
|
|
10059
|
-
decision: {
|
|
10060
|
-
type: "json",
|
|
10061
|
-
source: "rule",
|
|
10062
|
-
intent: { kind: "permission_allow" },
|
|
10063
|
-
// Scoped Codex permission grants are session-scoped under
|
|
10064
|
-
// auto rules so the same capability isn't re-prompted.
|
|
10065
|
-
...isScoped ? { data: { scope: "session" } } : {}
|
|
10066
|
-
}
|
|
10067
|
-
};
|
|
10068
|
-
}
|
|
10069
|
-
cb.enqueuePermission(event);
|
|
10070
|
-
cb.relayPermission?.(event);
|
|
10071
|
-
return { handled: true };
|
|
10072
|
-
}
|
|
10073
|
-
if (eventKind2 === "tool.pre" && toolName === "AskUserQuestion") {
|
|
10074
|
-
cb.enqueueQuestion(event.id);
|
|
10075
|
-
cb.relayQuestion?.(event);
|
|
10076
|
-
return { handled: true };
|
|
10077
|
-
}
|
|
10078
|
-
if (eventKind2 === "tool.pre" && toolName) {
|
|
10079
|
-
const rule = matchRule(cb.getRules(), toolName);
|
|
10080
|
-
if (rule?.action === "deny") {
|
|
10081
|
-
return {
|
|
10082
|
-
handled: true,
|
|
10083
|
-
decision: {
|
|
10084
|
-
type: "json",
|
|
10085
|
-
source: "rule",
|
|
10086
|
-
intent: {
|
|
10087
|
-
kind: "pre_tool_deny",
|
|
10088
|
-
reason: `Blocked by rule: ${rule.addedBy}`
|
|
10089
|
-
}
|
|
10090
|
-
}
|
|
10091
|
-
};
|
|
10092
|
-
}
|
|
10093
|
-
return {
|
|
10094
|
-
handled: true,
|
|
10095
|
-
decision: {
|
|
10096
|
-
type: "json",
|
|
10097
|
-
source: "rule",
|
|
10098
|
-
intent: { kind: "pre_tool_allow" }
|
|
10099
|
-
}
|
|
10100
|
-
};
|
|
10101
|
-
}
|
|
10102
|
-
return { handled: false };
|
|
10103
|
-
}
|
|
10104
|
-
|
|
10105
|
-
// src/core/feed/ingest.ts
|
|
10106
|
-
function ingestRuntimeEvent(event, ctx) {
|
|
10107
|
-
const controllerResult = handleEvent(event, ctx.controllerCallbacks);
|
|
10108
|
-
const feedEvents = ctx.mapper.mapEvent(event);
|
|
10109
|
-
if (ctx.store) {
|
|
10110
|
-
persistOrDegrade(
|
|
10111
|
-
ctx.store,
|
|
10112
|
-
() => ctx.store.recordEvent(event, feedEvents),
|
|
10113
|
-
"recordEvent failed",
|
|
10114
|
-
ctx.onPersistFailure
|
|
10115
|
-
);
|
|
10116
|
-
}
|
|
10117
|
-
return {
|
|
10118
|
-
feedEvents,
|
|
10119
|
-
decision: controllerResult.handled && controllerResult.decision ? controllerResult.decision : null
|
|
10120
|
-
};
|
|
10121
|
-
}
|
|
10122
|
-
function ingestRuntimeDecision(eventId, decision, ctx) {
|
|
10123
|
-
const feedEvent = ctx.mapper.mapDecision(eventId, decision);
|
|
10124
|
-
if (feedEvent && ctx.store) {
|
|
10125
|
-
persistOrDegrade(
|
|
10126
|
-
ctx.store,
|
|
10127
|
-
() => ctx.store.recordFeedEvents([feedEvent]),
|
|
10128
|
-
"recordFeedEvents failed",
|
|
10129
|
-
ctx.onPersistFailure
|
|
10130
|
-
);
|
|
10131
|
-
}
|
|
10132
|
-
return feedEvent;
|
|
10133
|
-
}
|
|
10134
|
-
function persistOrDegrade(store, action, label, onFailure) {
|
|
10135
|
-
try {
|
|
10136
|
-
action();
|
|
10137
|
-
} catch (err) {
|
|
10138
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
10139
|
-
const message = `${label}: ${reason}`;
|
|
10140
|
-
store.markDegraded(message);
|
|
10141
|
-
onFailure?.(message);
|
|
10142
|
-
}
|
|
10143
|
-
}
|
|
10144
|
-
|
|
10145
|
-
// src/infra/sessions/store.ts
|
|
10146
|
-
import fs17 from "fs";
|
|
10147
|
-
import path14 from "path";
|
|
10148
|
-
import Database from "better-sqlite3";
|
|
10149
|
-
|
|
10150
10316
|
// src/infra/sessions/schema.ts
|
|
10151
|
-
var SCHEMA_VERSION =
|
|
10152
|
-
|
|
10153
|
-
db.exec("PRAGMA journal_mode = WAL");
|
|
10154
|
-
db.exec("PRAGMA foreign_keys = ON");
|
|
10317
|
+
var SCHEMA_VERSION = 7;
|
|
10318
|
+
var applySessionSchema = (db, fromVersion) => {
|
|
10155
10319
|
db.exec(`
|
|
10156
|
-
CREATE TABLE IF NOT EXISTS schema_version (
|
|
10157
|
-
version INTEGER NOT NULL
|
|
10158
|
-
);
|
|
10159
|
-
|
|
10160
10320
|
CREATE TABLE IF NOT EXISTS session (
|
|
10161
10321
|
id TEXT PRIMARY KEY,
|
|
10162
10322
|
project_dir TEXT NOT NULL,
|
|
@@ -10184,6 +10344,10 @@ function initSchema(db) {
|
|
|
10184
10344
|
actor_id TEXT NOT NULL,
|
|
10185
10345
|
timestamp INTEGER NOT NULL,
|
|
10186
10346
|
data JSON NOT NULL,
|
|
10347
|
+
-- Harness-native Prompt identity (Claude prompt_id): a nullable
|
|
10348
|
+
-- correlation key, NOT the Run identity (that stays run_id). Unset for
|
|
10349
|
+
-- pre-prompt bootstrap + harnesses/versions without it (ADR 0009).
|
|
10350
|
+
prompt_id TEXT,
|
|
10187
10351
|
FOREIGN KEY (runtime_event_id) REFERENCES runtime_events(id)
|
|
10188
10352
|
);
|
|
10189
10353
|
|
|
@@ -10216,45 +10380,6 @@ function initSchema(db) {
|
|
|
10216
10380
|
FOREIGN KEY (session_id) REFERENCES session(id)
|
|
10217
10381
|
);
|
|
10218
10382
|
|
|
10219
|
-
-- Channel I/O ledger: every inbound chat normalized by an adapter and every
|
|
10220
|
-
-- outbound chat dispatched back to a provider gets one row. Idempotency
|
|
10221
|
-
-- key prevents double-delivery on retry/restart; session_id ties chat to a
|
|
10222
|
-
-- particular Athena interactive runtime when known.
|
|
10223
|
-
CREATE TABLE IF NOT EXISTS channel_messages (
|
|
10224
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10225
|
-
channel_id TEXT NOT NULL,
|
|
10226
|
-
account_id TEXT NOT NULL,
|
|
10227
|
-
peer_id TEXT,
|
|
10228
|
-
room_id TEXT,
|
|
10229
|
-
thread_id TEXT,
|
|
10230
|
-
provider_message_id TEXT NOT NULL,
|
|
10231
|
-
direction TEXT NOT NULL CHECK(direction IN ('in','out')),
|
|
10232
|
-
session_id TEXT REFERENCES adapter_sessions(session_id),
|
|
10233
|
-
agent_id TEXT,
|
|
10234
|
-
idempotency_key TEXT,
|
|
10235
|
-
feed_event_id TEXT,
|
|
10236
|
-
created_at INTEGER NOT NULL
|
|
10237
|
-
);
|
|
10238
|
-
|
|
10239
|
-
-- Audit log for cloud function invocations brokered by the gateway. One
|
|
10240
|
-
-- row per invocation across all callers (agent tool, /run channel cmd,
|
|
10241
|
-
-- hook helper). Idempotency cache is in-memory + write-through here.
|
|
10242
|
-
CREATE TABLE IF NOT EXISTS gateway_function_invocations (
|
|
10243
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10244
|
-
name TEXT NOT NULL,
|
|
10245
|
-
caller_kind TEXT NOT NULL CHECK(caller_kind IN ('agent','channel','hook')),
|
|
10246
|
-
session_id TEXT REFERENCES adapter_sessions(session_id),
|
|
10247
|
-
agent_id TEXT,
|
|
10248
|
-
idempotency_key TEXT,
|
|
10249
|
-
request_hash TEXT NOT NULL,
|
|
10250
|
-
status TEXT NOT NULL CHECK(status IN ('pending','ok','error','timeout')),
|
|
10251
|
-
http_status INTEGER,
|
|
10252
|
-
duration_ms INTEGER,
|
|
10253
|
-
error TEXT,
|
|
10254
|
-
started_at INTEGER NOT NULL,
|
|
10255
|
-
completed_at INTEGER
|
|
10256
|
-
);
|
|
10257
|
-
|
|
10258
10383
|
-- Durable retry queue for outbound channel sends. Drained by the gateway
|
|
10259
10384
|
-- daemon on startup and after transient send failures.
|
|
10260
10385
|
CREATE TABLE IF NOT EXISTS channel_outbox (
|
|
@@ -10274,127 +10399,91 @@ function initSchema(db) {
|
|
|
10274
10399
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_feed_seq ON feed_events(seq);
|
|
10275
10400
|
CREATE INDEX IF NOT EXISTS idx_runtime_seq ON runtime_events(seq);
|
|
10276
10401
|
CREATE INDEX IF NOT EXISTS idx_workflow_runs_session ON workflow_runs(session_id);
|
|
10277
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_channel_messages_idem
|
|
10278
|
-
ON channel_messages(channel_id, account_id, idempotency_key)
|
|
10279
|
-
WHERE idempotency_key IS NOT NULL;
|
|
10280
|
-
CREATE INDEX IF NOT EXISTS idx_channel_messages_session_key
|
|
10281
|
-
ON channel_messages(channel_id, account_id, peer_id, room_id, thread_id, created_at);
|
|
10282
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_fn_idem
|
|
10283
|
-
ON gateway_function_invocations(name, idempotency_key)
|
|
10284
|
-
WHERE idempotency_key IS NOT NULL;
|
|
10285
10402
|
CREATE INDEX IF NOT EXISTS idx_outbox_due ON channel_outbox(next_attempt_at);
|
|
10286
10403
|
`);
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
`Database has newer schema version ${existing.version} (expected <= ${SCHEMA_VERSION}). Update athena-cli to open this session.`
|
|
10404
|
+
if (fromVersion === void 0 || fromVersion >= 7) {
|
|
10405
|
+
db.exec(
|
|
10406
|
+
"CREATE INDEX IF NOT EXISTS idx_feed_prompt ON feed_events(prompt_id);"
|
|
10291
10407
|
);
|
|
10292
10408
|
}
|
|
10293
|
-
if (
|
|
10294
|
-
|
|
10295
|
-
|
|
10409
|
+
if (fromVersion === void 0) return;
|
|
10410
|
+
if (fromVersion >= SCHEMA_VERSION) return;
|
|
10411
|
+
if (fromVersion < 2) {
|
|
10412
|
+
throw new Error(
|
|
10413
|
+
`Session database is at schema version ${fromVersion} which predates the first release. Delete the session database and start fresh.`
|
|
10296
10414
|
);
|
|
10297
|
-
} else if (existing.version < SCHEMA_VERSION) {
|
|
10298
|
-
if (existing.version < 2) {
|
|
10299
|
-
throw new Error(
|
|
10300
|
-
`Session database is at schema version ${existing.version} which predates the first release. Delete the session database and start fresh.`
|
|
10301
|
-
);
|
|
10302
|
-
}
|
|
10303
|
-
if (existing.version === 2) {
|
|
10304
|
-
db.exec(`
|
|
10305
|
-
ALTER TABLE adapter_sessions ADD COLUMN tokens_input INTEGER;
|
|
10306
|
-
ALTER TABLE adapter_sessions ADD COLUMN tokens_output INTEGER;
|
|
10307
|
-
ALTER TABLE adapter_sessions ADD COLUMN tokens_cache_read INTEGER;
|
|
10308
|
-
ALTER TABLE adapter_sessions ADD COLUMN tokens_cache_write INTEGER;
|
|
10309
|
-
ALTER TABLE adapter_sessions ADD COLUMN tokens_context_size INTEGER;
|
|
10310
|
-
ALTER TABLE adapter_sessions ADD COLUMN tokens_context_window_size INTEGER;
|
|
10311
|
-
UPDATE schema_version SET version = 4;
|
|
10312
|
-
`);
|
|
10313
|
-
}
|
|
10314
|
-
if (existing.version === 3) {
|
|
10315
|
-
db.exec(`
|
|
10316
|
-
ALTER TABLE adapter_sessions ADD COLUMN tokens_context_window_size INTEGER;
|
|
10317
|
-
UPDATE schema_version SET version = 4;
|
|
10318
|
-
`);
|
|
10319
|
-
}
|
|
10320
|
-
const currentVersion = db.prepare("SELECT version FROM schema_version").get().version;
|
|
10321
|
-
if (currentVersion === 4) {
|
|
10322
|
-
db.exec(`
|
|
10323
|
-
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
10324
|
-
id TEXT PRIMARY KEY,
|
|
10325
|
-
session_id TEXT NOT NULL,
|
|
10326
|
-
workflow_name TEXT,
|
|
10327
|
-
started_at INTEGER NOT NULL,
|
|
10328
|
-
ended_at INTEGER,
|
|
10329
|
-
iteration INTEGER NOT NULL DEFAULT 0,
|
|
10330
|
-
max_iterations INTEGER NOT NULL DEFAULT 1,
|
|
10331
|
-
status TEXT NOT NULL DEFAULT 'running',
|
|
10332
|
-
stop_reason TEXT,
|
|
10333
|
-
tracker_path TEXT,
|
|
10334
|
-
FOREIGN KEY (session_id) REFERENCES session(id)
|
|
10335
|
-
);
|
|
10336
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_runs_session ON workflow_runs(session_id);
|
|
10337
|
-
ALTER TABLE adapter_sessions ADD COLUMN run_id TEXT REFERENCES workflow_runs(id);
|
|
10338
|
-
UPDATE schema_version SET version = 5;
|
|
10339
|
-
`);
|
|
10340
|
-
}
|
|
10341
|
-
const versionAfterV5 = db.prepare("SELECT version FROM schema_version").get().version;
|
|
10342
|
-
if (versionAfterV5 === 5) {
|
|
10343
|
-
db.exec(`
|
|
10344
|
-
CREATE TABLE IF NOT EXISTS channel_messages (
|
|
10345
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10346
|
-
channel_id TEXT NOT NULL,
|
|
10347
|
-
account_id TEXT NOT NULL,
|
|
10348
|
-
peer_id TEXT,
|
|
10349
|
-
room_id TEXT,
|
|
10350
|
-
thread_id TEXT,
|
|
10351
|
-
provider_message_id TEXT NOT NULL,
|
|
10352
|
-
direction TEXT NOT NULL CHECK(direction IN ('in','out')),
|
|
10353
|
-
session_id TEXT REFERENCES adapter_sessions(session_id),
|
|
10354
|
-
agent_id TEXT,
|
|
10355
|
-
idempotency_key TEXT,
|
|
10356
|
-
feed_event_id TEXT,
|
|
10357
|
-
created_at INTEGER NOT NULL
|
|
10358
|
-
);
|
|
10359
|
-
CREATE TABLE IF NOT EXISTS gateway_function_invocations (
|
|
10360
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10361
|
-
name TEXT NOT NULL,
|
|
10362
|
-
caller_kind TEXT NOT NULL CHECK(caller_kind IN ('agent','channel','hook')),
|
|
10363
|
-
session_id TEXT REFERENCES adapter_sessions(session_id),
|
|
10364
|
-
agent_id TEXT,
|
|
10365
|
-
idempotency_key TEXT,
|
|
10366
|
-
request_hash TEXT NOT NULL,
|
|
10367
|
-
status TEXT NOT NULL CHECK(status IN ('pending','ok','error','timeout')),
|
|
10368
|
-
http_status INTEGER,
|
|
10369
|
-
duration_ms INTEGER,
|
|
10370
|
-
error TEXT,
|
|
10371
|
-
started_at INTEGER NOT NULL,
|
|
10372
|
-
completed_at INTEGER
|
|
10373
|
-
);
|
|
10374
|
-
CREATE TABLE IF NOT EXISTS channel_outbox (
|
|
10375
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10376
|
-
channel_id TEXT NOT NULL,
|
|
10377
|
-
account_id TEXT NOT NULL,
|
|
10378
|
-
payload_json TEXT NOT NULL,
|
|
10379
|
-
attempt INTEGER NOT NULL DEFAULT 0,
|
|
10380
|
-
next_attempt_at INTEGER NOT NULL,
|
|
10381
|
-
last_error TEXT,
|
|
10382
|
-
created_at INTEGER NOT NULL
|
|
10383
|
-
);
|
|
10384
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_channel_messages_idem
|
|
10385
|
-
ON channel_messages(channel_id, account_id, idempotency_key)
|
|
10386
|
-
WHERE idempotency_key IS NOT NULL;
|
|
10387
|
-
CREATE INDEX IF NOT EXISTS idx_channel_messages_session_key
|
|
10388
|
-
ON channel_messages(channel_id, account_id, peer_id, room_id, thread_id, created_at);
|
|
10389
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_fn_idem
|
|
10390
|
-
ON gateway_function_invocations(name, idempotency_key)
|
|
10391
|
-
WHERE idempotency_key IS NOT NULL;
|
|
10392
|
-
CREATE INDEX IF NOT EXISTS idx_outbox_due ON channel_outbox(next_attempt_at);
|
|
10393
|
-
UPDATE schema_version SET version = 6;
|
|
10394
|
-
`);
|
|
10395
|
-
}
|
|
10396
10415
|
}
|
|
10397
|
-
|
|
10416
|
+
if (fromVersion === 2) {
|
|
10417
|
+
db.exec(`
|
|
10418
|
+
ALTER TABLE adapter_sessions ADD COLUMN tokens_input INTEGER;
|
|
10419
|
+
ALTER TABLE adapter_sessions ADD COLUMN tokens_output INTEGER;
|
|
10420
|
+
ALTER TABLE adapter_sessions ADD COLUMN tokens_cache_read INTEGER;
|
|
10421
|
+
ALTER TABLE adapter_sessions ADD COLUMN tokens_cache_write INTEGER;
|
|
10422
|
+
ALTER TABLE adapter_sessions ADD COLUMN tokens_context_size INTEGER;
|
|
10423
|
+
ALTER TABLE adapter_sessions ADD COLUMN tokens_context_window_size INTEGER;
|
|
10424
|
+
UPDATE schema_version SET version = 4;
|
|
10425
|
+
`);
|
|
10426
|
+
}
|
|
10427
|
+
if (fromVersion === 3) {
|
|
10428
|
+
db.exec(`
|
|
10429
|
+
ALTER TABLE adapter_sessions ADD COLUMN tokens_context_window_size INTEGER;
|
|
10430
|
+
UPDATE schema_version SET version = 4;
|
|
10431
|
+
`);
|
|
10432
|
+
}
|
|
10433
|
+
const currentVersion = db.prepare("SELECT version FROM schema_version").get().version;
|
|
10434
|
+
if (currentVersion === 4) {
|
|
10435
|
+
db.exec(`
|
|
10436
|
+
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
10437
|
+
id TEXT PRIMARY KEY,
|
|
10438
|
+
session_id TEXT NOT NULL,
|
|
10439
|
+
workflow_name TEXT,
|
|
10440
|
+
started_at INTEGER NOT NULL,
|
|
10441
|
+
ended_at INTEGER,
|
|
10442
|
+
iteration INTEGER NOT NULL DEFAULT 0,
|
|
10443
|
+
max_iterations INTEGER NOT NULL DEFAULT 1,
|
|
10444
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
10445
|
+
stop_reason TEXT,
|
|
10446
|
+
tracker_path TEXT,
|
|
10447
|
+
FOREIGN KEY (session_id) REFERENCES session(id)
|
|
10448
|
+
);
|
|
10449
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_runs_session ON workflow_runs(session_id);
|
|
10450
|
+
ALTER TABLE adapter_sessions ADD COLUMN run_id TEXT REFERENCES workflow_runs(id);
|
|
10451
|
+
UPDATE schema_version SET version = 5;
|
|
10452
|
+
`);
|
|
10453
|
+
}
|
|
10454
|
+
const versionAfterV5 = db.prepare("SELECT version FROM schema_version").get().version;
|
|
10455
|
+
if (versionAfterV5 === 5) {
|
|
10456
|
+
db.exec(`
|
|
10457
|
+
CREATE TABLE IF NOT EXISTS channel_outbox (
|
|
10458
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10459
|
+
channel_id TEXT NOT NULL,
|
|
10460
|
+
account_id TEXT NOT NULL,
|
|
10461
|
+
payload_json TEXT NOT NULL,
|
|
10462
|
+
attempt INTEGER NOT NULL DEFAULT 0,
|
|
10463
|
+
next_attempt_at INTEGER NOT NULL,
|
|
10464
|
+
last_error TEXT,
|
|
10465
|
+
created_at INTEGER NOT NULL
|
|
10466
|
+
);
|
|
10467
|
+
CREATE INDEX IF NOT EXISTS idx_outbox_due ON channel_outbox(next_attempt_at);
|
|
10468
|
+
UPDATE schema_version SET version = 6;
|
|
10469
|
+
`);
|
|
10470
|
+
}
|
|
10471
|
+
const versionAfterV6 = db.prepare("SELECT version FROM schema_version").get().version;
|
|
10472
|
+
if (versionAfterV6 === 6) {
|
|
10473
|
+
db.exec(`
|
|
10474
|
+
ALTER TABLE feed_events ADD COLUMN prompt_id TEXT;
|
|
10475
|
+
CREATE INDEX IF NOT EXISTS idx_feed_prompt ON feed_events(prompt_id);
|
|
10476
|
+
UPDATE schema_version SET version = 7;
|
|
10477
|
+
`);
|
|
10478
|
+
}
|
|
10479
|
+
};
|
|
10480
|
+
var SESSION_SCHEMA = {
|
|
10481
|
+
version: SCHEMA_VERSION,
|
|
10482
|
+
migrate: applySessionSchema,
|
|
10483
|
+
onNewerVersion: (found, expected) => new Error(
|
|
10484
|
+
`Database has newer schema version ${found} (expected <= ${expected}). Update athena-cli to open this session.`
|
|
10485
|
+
)
|
|
10486
|
+
};
|
|
10398
10487
|
|
|
10399
10488
|
// src/infra/sessions/types.ts
|
|
10400
10489
|
function rowToAthenaSession(row, adapterSessionIds, firstPrompt) {
|
|
@@ -10412,11 +10501,11 @@ function rowToAthenaSession(row, adapterSessionIds, firstPrompt) {
|
|
|
10412
10501
|
|
|
10413
10502
|
// src/infra/sessions/store.ts
|
|
10414
10503
|
function createSessionStore(opts) {
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
|
|
10418
|
-
|
|
10419
|
-
|
|
10504
|
+
const db = openVersionedDb(opts.dbPath, {
|
|
10505
|
+
...SESSION_SCHEMA,
|
|
10506
|
+
foreignKeys: true,
|
|
10507
|
+
ensureDir: true
|
|
10508
|
+
});
|
|
10420
10509
|
if (opts.dbPath !== ":memory:") {
|
|
10421
10510
|
db.pragma("locking_mode = EXCLUSIVE");
|
|
10422
10511
|
db.exec("BEGIN IMMEDIATE; COMMIT");
|
|
@@ -10443,8 +10532,8 @@ function createSessionStore(opts) {
|
|
|
10443
10532
|
VALUES (?, ?, ?, ?, ?, ?)`
|
|
10444
10533
|
);
|
|
10445
10534
|
const insertFeedEvent = db.prepare(
|
|
10446
|
-
`INSERT OR IGNORE INTO feed_events (event_id, runtime_event_id, seq, kind, run_id, actor_id, timestamp, data)
|
|
10447
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
10535
|
+
`INSERT OR IGNORE INTO feed_events (event_id, runtime_event_id, seq, kind, run_id, actor_id, timestamp, data, prompt_id)
|
|
10536
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
10448
10537
|
);
|
|
10449
10538
|
const insertAdapterSession = db.prepare(
|
|
10450
10539
|
`INSERT OR IGNORE INTO adapter_sessions (session_id, started_at)
|
|
@@ -10499,7 +10588,8 @@ function createSessionStore(opts) {
|
|
|
10499
10588
|
fe.run_id,
|
|
10500
10589
|
fe.actor_id,
|
|
10501
10590
|
fe.ts,
|
|
10502
|
-
JSON.stringify(fe)
|
|
10591
|
+
JSON.stringify(fe),
|
|
10592
|
+
fe.prompt_id ?? null
|
|
10503
10593
|
);
|
|
10504
10594
|
}
|
|
10505
10595
|
updateEventCount.run(feedEvents.length, opts.sessionId);
|
|
@@ -10515,7 +10605,8 @@ function createSessionStore(opts) {
|
|
|
10515
10605
|
fe.run_id,
|
|
10516
10606
|
fe.actor_id,
|
|
10517
10607
|
fe.ts,
|
|
10518
|
-
JSON.stringify(fe)
|
|
10608
|
+
JSON.stringify(fe),
|
|
10609
|
+
fe.prompt_id ?? null
|
|
10519
10610
|
);
|
|
10520
10611
|
}
|
|
10521
10612
|
updateEventCount.run(feedEvents.length, opts.sessionId);
|
|
@@ -10687,9 +10778,6 @@ function createSessionStore(opts) {
|
|
|
10687
10778
|
};
|
|
10688
10779
|
}
|
|
10689
10780
|
|
|
10690
|
-
// src/infra/sessions/hookAudit.ts
|
|
10691
|
-
import Database2 from "better-sqlite3";
|
|
10692
|
-
|
|
10693
10781
|
// src/core/feed/filter.ts
|
|
10694
10782
|
var TASK_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
10695
10783
|
"TodoWrite",
|
|
@@ -11365,8 +11453,8 @@ var PRIMARY_INPUT_EXTRACTORS = {
|
|
|
11365
11453
|
return compactText(colonIdx >= 0 ? name.slice(colonIdx + 1) : name, 80);
|
|
11366
11454
|
},
|
|
11367
11455
|
NotebookEdit: (input) => {
|
|
11368
|
-
const
|
|
11369
|
-
return
|
|
11456
|
+
const path21 = String(input.notebook_path ?? "");
|
|
11457
|
+
return path21 ? shortenPath(path21) : "";
|
|
11370
11458
|
},
|
|
11371
11459
|
AskUserQuestion: (input) => {
|
|
11372
11460
|
const questions = input.questions;
|
|
@@ -12352,6 +12440,13 @@ var stopFailure = defaultRenderer(
|
|
|
12352
12440
|
var permissionDenied = defaultRenderer(
|
|
12353
12441
|
(event) => `${event.data.tool_name}${event.data.reason ? `: ${event.data.reason}` : ""}`
|
|
12354
12442
|
);
|
|
12443
|
+
var promptExpansion = defaultRenderer(
|
|
12444
|
+
(event) => event.data.command_name ? `/${event.data.command_name}${event.data.command_args ? ` ${event.data.command_args}` : ""}` : event.data.expansion_type ?? ""
|
|
12445
|
+
);
|
|
12446
|
+
var toolBatch = defaultRenderer((event) => {
|
|
12447
|
+
const names = event.data.tool_calls.map((call) => call.tool_name).filter((name) => Boolean(name));
|
|
12448
|
+
return names.length > 0 ? names.join(", ") : "";
|
|
12449
|
+
});
|
|
12355
12450
|
var elicitationRequest = defaultRenderer((event) => `elicitation from ${event.data.mcp_server}`);
|
|
12356
12451
|
var elicitationResult = defaultRenderer(
|
|
12357
12452
|
(event) => `${event.data.mcp_server} \u2192 ${event.data.action}`
|
|
@@ -12400,6 +12495,7 @@ var RENDERERS = {
|
|
|
12400
12495
|
"tool.delta": toolDelta,
|
|
12401
12496
|
"tool.pre": toolPre,
|
|
12402
12497
|
"tool.post": toolPost,
|
|
12498
|
+
"tool.batch": toolBatch,
|
|
12403
12499
|
"tool.failure": toolFailure,
|
|
12404
12500
|
"permission.request": permissionRequest,
|
|
12405
12501
|
"permission.decision": permissionDecision,
|
|
@@ -12439,6 +12535,7 @@ var RENDERERS = {
|
|
|
12439
12535
|
"worktree.remove": worktreeRemove,
|
|
12440
12536
|
"stop.failure": stopFailure,
|
|
12441
12537
|
"permission.denied": permissionDenied,
|
|
12538
|
+
"prompt.expansion": promptExpansion,
|
|
12442
12539
|
"elicitation.request": elicitationRequest,
|
|
12443
12540
|
"elicitation.result": elicitationResult,
|
|
12444
12541
|
"channel.permission.relayed": channelPermissionRelayed,
|
|
@@ -12637,16 +12734,16 @@ function extractDomain(url) {
|
|
|
12637
12734
|
}
|
|
12638
12735
|
}
|
|
12639
12736
|
function filePathSegments(input) {
|
|
12640
|
-
const
|
|
12641
|
-
if (typeof
|
|
12642
|
-
const { prefix, filename } = shortenPathStructured(
|
|
12737
|
+
const path21 = prop2(input, "file_path") ?? prop2(input, "notebook_path") ?? "";
|
|
12738
|
+
if (typeof path21 !== "string" || !path21) return [];
|
|
12739
|
+
const { prefix, filename } = shortenPathStructured(path21);
|
|
12643
12740
|
if (prefix && filename) {
|
|
12644
12741
|
return [
|
|
12645
12742
|
{ text: prefix, role: "target" },
|
|
12646
12743
|
{ text: filename, role: "filename" }
|
|
12647
12744
|
];
|
|
12648
12745
|
}
|
|
12649
|
-
return [{ text: filename ||
|
|
12746
|
+
return [{ text: filename || path21, role: "filename" }];
|
|
12650
12747
|
}
|
|
12651
12748
|
function grepSegments(input) {
|
|
12652
12749
|
const pattern = String(prop2(input, "pattern") ?? "");
|
|
@@ -13280,6 +13377,7 @@ function resolveEventToolColumn(event) {
|
|
|
13280
13377
|
case "tool.delta":
|
|
13281
13378
|
case "tool.pre":
|
|
13282
13379
|
case "tool.post":
|
|
13380
|
+
case "tool.batch":
|
|
13283
13381
|
case "tool.failure":
|
|
13284
13382
|
case "permission.decision":
|
|
13285
13383
|
case "permission.denied":
|
|
@@ -13307,6 +13405,7 @@ function resolveEventToolColumn(event) {
|
|
|
13307
13405
|
case "todo.done":
|
|
13308
13406
|
case "agent.message":
|
|
13309
13407
|
case "teammate.idle":
|
|
13408
|
+
case "prompt.expansion":
|
|
13310
13409
|
case "task.created":
|
|
13311
13410
|
case "task.completed":
|
|
13312
13411
|
case "config.change":
|
|
@@ -13779,57 +13878,93 @@ ${details}` : entry.summary;
|
|
|
13779
13878
|
}
|
|
13780
13879
|
};
|
|
13781
13880
|
|
|
13881
|
+
// src/infra/sessions/sessionDbReader.ts
|
|
13882
|
+
import Database from "better-sqlite3";
|
|
13883
|
+
function openSessionDbReadonly(dbPath, options = {}) {
|
|
13884
|
+
const db = new Database(dbPath, {
|
|
13885
|
+
readonly: true,
|
|
13886
|
+
...options.fileMustExist ? { fileMustExist: true } : {}
|
|
13887
|
+
});
|
|
13888
|
+
return {
|
|
13889
|
+
schemaVersion() {
|
|
13890
|
+
const row = db.prepare("SELECT version FROM schema_version").get();
|
|
13891
|
+
return row?.version;
|
|
13892
|
+
},
|
|
13893
|
+
sessionRow() {
|
|
13894
|
+
return db.prepare("SELECT * FROM session LIMIT 1").get();
|
|
13895
|
+
},
|
|
13896
|
+
adapterSessionIds() {
|
|
13897
|
+
const rows = db.prepare("SELECT session_id FROM adapter_sessions ORDER BY started_at").all();
|
|
13898
|
+
return rows.map((r) => r.session_id);
|
|
13899
|
+
},
|
|
13900
|
+
firstUserPrompt() {
|
|
13901
|
+
const row = db.prepare(
|
|
13902
|
+
`SELECT json_extract(payload, '$.data.prompt') as prompt FROM runtime_events WHERE hook_name = 'UserPromptSubmit' ORDER BY seq ASC LIMIT 1`
|
|
13903
|
+
).get();
|
|
13904
|
+
return row?.prompt ?? void 0;
|
|
13905
|
+
},
|
|
13906
|
+
runtimeHookCounts() {
|
|
13907
|
+
const rows = db.prepare(
|
|
13908
|
+
"SELECT hook_name, COUNT(*) AS count FROM runtime_events GROUP BY hook_name"
|
|
13909
|
+
).all();
|
|
13910
|
+
const counts = {};
|
|
13911
|
+
for (const row of rows) counts[row.hook_name] = row.count;
|
|
13912
|
+
return counts;
|
|
13913
|
+
},
|
|
13914
|
+
feedEvents() {
|
|
13915
|
+
const rows = db.prepare("SELECT data FROM feed_events ORDER BY seq").all();
|
|
13916
|
+
return rows.map((r) => JSON.parse(r.data));
|
|
13917
|
+
},
|
|
13918
|
+
close() {
|
|
13919
|
+
db.close();
|
|
13920
|
+
}
|
|
13921
|
+
};
|
|
13922
|
+
}
|
|
13923
|
+
|
|
13782
13924
|
// src/infra/sessions/registry.ts
|
|
13783
|
-
import
|
|
13925
|
+
import fs17 from "fs";
|
|
13784
13926
|
import os11 from "os";
|
|
13785
|
-
import
|
|
13786
|
-
import Database3 from "better-sqlite3";
|
|
13927
|
+
import path14 from "path";
|
|
13787
13928
|
function sessionsDir() {
|
|
13788
|
-
return
|
|
13929
|
+
return path14.join(os11.homedir(), ".config", "athena", "sessions");
|
|
13789
13930
|
}
|
|
13790
13931
|
function sessionDbPath(sessionId) {
|
|
13791
|
-
return
|
|
13932
|
+
return path14.join(sessionsDir(), sessionId, "session.db");
|
|
13792
13933
|
}
|
|
13793
13934
|
function readSessionFromDb(dbPath) {
|
|
13794
|
-
if (!
|
|
13795
|
-
let
|
|
13935
|
+
if (!fs17.existsSync(dbPath)) return null;
|
|
13936
|
+
let reader;
|
|
13796
13937
|
try {
|
|
13797
|
-
|
|
13798
|
-
const
|
|
13799
|
-
if (
|
|
13938
|
+
reader = openSessionDbReadonly(dbPath);
|
|
13939
|
+
const version = reader.schemaVersion();
|
|
13940
|
+
if (version !== void 0 && version > SCHEMA_VERSION) {
|
|
13800
13941
|
return null;
|
|
13801
13942
|
}
|
|
13802
|
-
const row =
|
|
13943
|
+
const row = reader.sessionRow();
|
|
13803
13944
|
if (!row) return null;
|
|
13804
|
-
const
|
|
13945
|
+
const adapterSessionIds = reader.adapterSessionIds();
|
|
13805
13946
|
let firstPrompt;
|
|
13806
13947
|
if (!row.label && (row.event_count ?? 0) > 0) {
|
|
13807
|
-
const
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
if (promptRow?.prompt) {
|
|
13811
|
-
firstPrompt = promptRow.prompt;
|
|
13948
|
+
const prompt = reader.firstUserPrompt();
|
|
13949
|
+
if (prompt) {
|
|
13950
|
+
firstPrompt = prompt;
|
|
13812
13951
|
}
|
|
13813
13952
|
}
|
|
13814
|
-
return rowToAthenaSession(
|
|
13815
|
-
row,
|
|
13816
|
-
adapters.map((a) => a.session_id),
|
|
13817
|
-
firstPrompt
|
|
13818
|
-
);
|
|
13953
|
+
return rowToAthenaSession(row, adapterSessionIds, firstPrompt);
|
|
13819
13954
|
} catch {
|
|
13820
13955
|
return null;
|
|
13821
13956
|
} finally {
|
|
13822
|
-
|
|
13957
|
+
reader?.close();
|
|
13823
13958
|
}
|
|
13824
13959
|
}
|
|
13825
13960
|
function listSessions(projectDir) {
|
|
13826
13961
|
const dir = sessionsDir();
|
|
13827
|
-
if (!
|
|
13828
|
-
const entries =
|
|
13962
|
+
if (!fs17.existsSync(dir)) return [];
|
|
13963
|
+
const entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
13829
13964
|
const sessions = [];
|
|
13830
13965
|
for (const entry of entries) {
|
|
13831
13966
|
if (!entry.isDirectory()) continue;
|
|
13832
|
-
const dbPath =
|
|
13967
|
+
const dbPath = path14.join(dir, entry.name, "session.db");
|
|
13833
13968
|
const session = readSessionFromDb(dbPath);
|
|
13834
13969
|
if (session && (session.eventCount ?? 0) > 0) {
|
|
13835
13970
|
if (!projectDir || session.projectDir === projectDir) {
|
|
@@ -13974,7 +14109,7 @@ function extractRelayQuestions(event) {
|
|
|
13974
14109
|
}
|
|
13975
14110
|
|
|
13976
14111
|
// src/app/channels/gatewayControlClient.ts
|
|
13977
|
-
import { readFileSync as
|
|
14112
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
13978
14113
|
|
|
13979
14114
|
// src/gateway/control/client.ts
|
|
13980
14115
|
import crypto from "crypto";
|
|
@@ -14082,6 +14217,7 @@ async function connect(opts) {
|
|
|
14082
14217
|
preHelloResolve = (frame) => {
|
|
14083
14218
|
clearTimeout(timer);
|
|
14084
14219
|
preHelloAbort = null;
|
|
14220
|
+
helloAcked = true;
|
|
14085
14221
|
resolve(frame);
|
|
14086
14222
|
};
|
|
14087
14223
|
preHelloAbort = (err) => {
|
|
@@ -14104,7 +14240,6 @@ async function connect(opts) {
|
|
|
14104
14240
|
}
|
|
14105
14241
|
throw new GatewayProtocolError(String(msg));
|
|
14106
14242
|
}
|
|
14107
|
-
helloAcked = true;
|
|
14108
14243
|
const request = async (kind, payload, reqOpts) => {
|
|
14109
14244
|
const requestId = crypto.randomUUID();
|
|
14110
14245
|
const envelope = {
|
|
@@ -14125,9 +14260,6 @@ async function connect(opts) {
|
|
|
14125
14260
|
);
|
|
14126
14261
|
connection.send(envelope);
|
|
14127
14262
|
const res = await responsePromise;
|
|
14128
|
-
if (res.request_id !== requestId) {
|
|
14129
|
-
throw new GatewayProtocolError("response request_id mismatch");
|
|
14130
|
-
}
|
|
14131
14263
|
if (!res.ok) {
|
|
14132
14264
|
throw new GatewayProtocolError(
|
|
14133
14265
|
`${res.error.code}: ${res.error.message}`,
|
|
@@ -14164,93 +14296,6 @@ function isStringRecord(v) {
|
|
|
14164
14296
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
14165
14297
|
}
|
|
14166
14298
|
|
|
14167
|
-
// src/gateway/transport/wsClient.ts
|
|
14168
|
-
import { WebSocket } from "ws";
|
|
14169
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
14170
|
-
function createWsClientTransport(opts) {
|
|
14171
|
-
return {
|
|
14172
|
-
kind: "ws",
|
|
14173
|
-
connect: () => connectWs(opts)
|
|
14174
|
-
};
|
|
14175
|
-
}
|
|
14176
|
-
function wsClientOptionsForEndpoint(input) {
|
|
14177
|
-
return {
|
|
14178
|
-
url: input.url,
|
|
14179
|
-
...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {},
|
|
14180
|
-
...input.tlsCaPath !== void 0 ? { tlsCaPath: input.tlsCaPath } : {}
|
|
14181
|
-
};
|
|
14182
|
-
}
|
|
14183
|
-
async function connectWs(opts) {
|
|
14184
|
-
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
14185
|
-
const wsOpts = opts.tlsCaPath ? { ca: readFileSync2(opts.tlsCaPath) } : {};
|
|
14186
|
-
const ws = new WebSocket(opts.url, wsOpts);
|
|
14187
|
-
await new Promise((resolve, reject) => {
|
|
14188
|
-
const timer = setTimeout(() => {
|
|
14189
|
-
ws.close();
|
|
14190
|
-
reject(
|
|
14191
|
-
new TransportUnreachableError(`connect timed out after ${timeoutMs}ms`)
|
|
14192
|
-
);
|
|
14193
|
-
}, timeoutMs);
|
|
14194
|
-
ws.once("open", () => {
|
|
14195
|
-
clearTimeout(timer);
|
|
14196
|
-
resolve();
|
|
14197
|
-
});
|
|
14198
|
-
ws.once("error", (err) => {
|
|
14199
|
-
clearTimeout(timer);
|
|
14200
|
-
reject(
|
|
14201
|
-
new TransportUnreachableError(
|
|
14202
|
-
`gateway not reachable at ${opts.url}: ${err.message}`
|
|
14203
|
-
)
|
|
14204
|
-
);
|
|
14205
|
-
});
|
|
14206
|
-
});
|
|
14207
|
-
return createWsConnection(ws, opts.url);
|
|
14208
|
-
}
|
|
14209
|
-
function createWsConnection(ws, peer) {
|
|
14210
|
-
const frameHandlers = /* @__PURE__ */ new Set();
|
|
14211
|
-
const closeHandlers = /* @__PURE__ */ new Set();
|
|
14212
|
-
const errorHandlers = /* @__PURE__ */ new Set();
|
|
14213
|
-
ws.on("message", (data) => {
|
|
14214
|
-
let parsed;
|
|
14215
|
-
try {
|
|
14216
|
-
parsed = JSON.parse(data.toString());
|
|
14217
|
-
} catch {
|
|
14218
|
-
ws.close();
|
|
14219
|
-
return;
|
|
14220
|
-
}
|
|
14221
|
-
traceGatewayFrame("ws-client", peer, "in", parsed);
|
|
14222
|
-
for (const handler of frameHandlers) handler(parsed);
|
|
14223
|
-
});
|
|
14224
|
-
ws.on("error", (err) => {
|
|
14225
|
-
for (const handler of errorHandlers) handler(err);
|
|
14226
|
-
});
|
|
14227
|
-
ws.on("close", () => {
|
|
14228
|
-
for (const handler of closeHandlers) handler();
|
|
14229
|
-
});
|
|
14230
|
-
return {
|
|
14231
|
-
kind: "ws",
|
|
14232
|
-
peer,
|
|
14233
|
-
send: (frame) => {
|
|
14234
|
-
if (ws.readyState !== ws.OPEN) return;
|
|
14235
|
-
traceGatewayFrame("ws-client", peer, "out", frame);
|
|
14236
|
-
ws.send(JSON.stringify(frame));
|
|
14237
|
-
},
|
|
14238
|
-
close: () => ws.close(),
|
|
14239
|
-
onFrame: (cb) => {
|
|
14240
|
-
frameHandlers.add(cb);
|
|
14241
|
-
return () => frameHandlers.delete(cb);
|
|
14242
|
-
},
|
|
14243
|
-
onClose: (cb) => {
|
|
14244
|
-
closeHandlers.add(cb);
|
|
14245
|
-
return () => closeHandlers.delete(cb);
|
|
14246
|
-
},
|
|
14247
|
-
onError: (cb) => {
|
|
14248
|
-
errorHandlers.add(cb);
|
|
14249
|
-
return () => errorHandlers.delete(cb);
|
|
14250
|
-
}
|
|
14251
|
-
};
|
|
14252
|
-
}
|
|
14253
|
-
|
|
14254
14299
|
// src/app/channels/gatewayControlClient.ts
|
|
14255
14300
|
async function connectGatewayControlClient(opts) {
|
|
14256
14301
|
const loadToken = opts.loadToken ?? defaultLoadToken;
|
|
@@ -14272,13 +14317,13 @@ async function connectGatewayControlClient(opts) {
|
|
|
14272
14317
|
});
|
|
14273
14318
|
}
|
|
14274
14319
|
function defaultLoadToken(tokenPath) {
|
|
14275
|
-
return
|
|
14320
|
+
return readFileSync2(tokenPath, "utf8").trim();
|
|
14276
14321
|
}
|
|
14277
14322
|
|
|
14278
14323
|
// src/infra/config/gatewayClient.ts
|
|
14279
|
-
import
|
|
14324
|
+
import fs18 from "fs";
|
|
14280
14325
|
import os12 from "os";
|
|
14281
|
-
import
|
|
14326
|
+
import path15 from "path";
|
|
14282
14327
|
|
|
14283
14328
|
// src/shared/gateway-protocol/endpoint.ts
|
|
14284
14329
|
function parseRuntimeEndpoint(value) {
|
|
@@ -14334,16 +14379,16 @@ function isRecord2(value) {
|
|
|
14334
14379
|
// src/infra/config/gatewayClient.ts
|
|
14335
14380
|
function resolveGatewayClientConfigPath(env = process.env) {
|
|
14336
14381
|
const home = env["HOME"] ?? os12.homedir();
|
|
14337
|
-
return
|
|
14382
|
+
return path15.join(home, ".config", "athena", "gateway.json");
|
|
14338
14383
|
}
|
|
14339
14384
|
function readGatewayClientConfig(env = process.env) {
|
|
14340
14385
|
const configPath = resolveGatewayClientConfigPath(env);
|
|
14341
|
-
if (!
|
|
14386
|
+
if (!fs18.existsSync(configPath)) {
|
|
14342
14387
|
return { mode: "local" };
|
|
14343
14388
|
}
|
|
14344
14389
|
let parsed;
|
|
14345
14390
|
try {
|
|
14346
|
-
parsed = JSON.parse(
|
|
14391
|
+
parsed = JSON.parse(fs18.readFileSync(configPath, "utf-8"));
|
|
14347
14392
|
} catch (err) {
|
|
14348
14393
|
throw new Error(
|
|
14349
14394
|
`gateway client config ${configPath} is invalid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -14360,16 +14405,16 @@ function readGatewayClientConfig(env = process.env) {
|
|
|
14360
14405
|
function writeGatewayClientConfig(config, env = process.env) {
|
|
14361
14406
|
const parsed = parseRuntimeEndpoint(config);
|
|
14362
14407
|
const configPath = resolveGatewayClientConfigPath(env);
|
|
14363
|
-
const dir =
|
|
14364
|
-
|
|
14365
|
-
|
|
14408
|
+
const dir = path15.dirname(configPath);
|
|
14409
|
+
fs18.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
14410
|
+
fs18.writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n", {
|
|
14366
14411
|
encoding: "utf-8",
|
|
14367
14412
|
mode: 384
|
|
14368
14413
|
});
|
|
14369
14414
|
if (process.platform !== "win32") {
|
|
14370
14415
|
try {
|
|
14371
|
-
|
|
14372
|
-
|
|
14416
|
+
fs18.chmodSync(dir, 448);
|
|
14417
|
+
fs18.chmodSync(configPath, 384);
|
|
14373
14418
|
} catch {
|
|
14374
14419
|
}
|
|
14375
14420
|
}
|
|
@@ -14797,14 +14842,11 @@ function sleepOrAbort(ms, signal) {
|
|
|
14797
14842
|
}
|
|
14798
14843
|
|
|
14799
14844
|
// src/app/dashboard/dashboardFeedPublisher.ts
|
|
14800
|
-
import Database4 from "better-sqlite3";
|
|
14801
14845
|
function dashboardFeedOutboxPath() {
|
|
14802
14846
|
return `${ensureDaemonStateDir().dir}/dashboard-feed-outbox.db`;
|
|
14803
14847
|
}
|
|
14804
14848
|
function initOutboxSchema(db) {
|
|
14805
14849
|
db.exec(`
|
|
14806
|
-
PRAGMA journal_mode = WAL;
|
|
14807
|
-
|
|
14808
14850
|
CREATE TABLE IF NOT EXISTS dashboard_feed_outbox (
|
|
14809
14851
|
delivery_seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
14810
14852
|
instance_id TEXT NOT NULL,
|
|
@@ -14838,8 +14880,9 @@ function makeEnvelope(input) {
|
|
|
14838
14880
|
};
|
|
14839
14881
|
}
|
|
14840
14882
|
function createDashboardFeedOutbox(options = {}) {
|
|
14841
|
-
const db =
|
|
14842
|
-
|
|
14883
|
+
const db = openVersionedDb(options.dbPath ?? dashboardFeedOutboxPath(), {
|
|
14884
|
+
migrate: initOutboxSchema
|
|
14885
|
+
});
|
|
14843
14886
|
const insert = db.prepare(`
|
|
14844
14887
|
INSERT OR IGNORE INTO dashboard_feed_outbox (
|
|
14845
14888
|
instance_id,
|
|
@@ -14949,91 +14992,342 @@ function createDashboardFeedOutbox(options = {}) {
|
|
|
14949
14992
|
close() {
|
|
14950
14993
|
db.close();
|
|
14951
14994
|
}
|
|
14952
|
-
};
|
|
14953
|
-
}
|
|
14954
|
-
|
|
14955
|
-
// src/app/dashboard/pairedFeedPublisher.ts
|
|
14956
|
-
var DEFAULT_DRAIN_INTERVAL_MS = 1e3;
|
|
14957
|
-
function createPairedFeedPublisher(options = {}) {
|
|
14958
|
-
const readConfig2 = options.readConfig ?? (() => readDashboardClientConfig());
|
|
14959
|
-
const now = options.now ?? (() => Date.now());
|
|
14960
|
-
const onError = options.onError ?? (() => {
|
|
14961
|
-
});
|
|
14962
|
-
const drainIntervalMs = options.drainIntervalMs ?? DEFAULT_DRAIN_INTERVAL_MS;
|
|
14963
|
-
let ownedOutbox = null;
|
|
14964
|
-
let transport = null;
|
|
14965
|
-
let drainTimer = null;
|
|
14966
|
-
function getOutbox() {
|
|
14967
|
-
if (options.outbox) return options.outbox;
|
|
14968
|
-
ownedOutbox ??= createDashboardFeedOutbox();
|
|
14969
|
-
return ownedOutbox;
|
|
14995
|
+
};
|
|
14996
|
+
}
|
|
14997
|
+
|
|
14998
|
+
// src/app/dashboard/pairedFeedPublisher.ts
|
|
14999
|
+
var DEFAULT_DRAIN_INTERVAL_MS = 1e3;
|
|
15000
|
+
function createPairedFeedPublisher(options = {}) {
|
|
15001
|
+
const readConfig2 = options.readConfig ?? (() => readDashboardClientConfig());
|
|
15002
|
+
const now = options.now ?? (() => Date.now());
|
|
15003
|
+
const onError = options.onError ?? (() => {
|
|
15004
|
+
});
|
|
15005
|
+
const drainIntervalMs = options.drainIntervalMs ?? DEFAULT_DRAIN_INTERVAL_MS;
|
|
15006
|
+
let ownedOutbox = null;
|
|
15007
|
+
let transport = null;
|
|
15008
|
+
let drainTimer = null;
|
|
15009
|
+
function getOutbox() {
|
|
15010
|
+
if (options.outbox) return options.outbox;
|
|
15011
|
+
ownedOutbox ??= createDashboardFeedOutbox();
|
|
15012
|
+
return ownedOutbox;
|
|
15013
|
+
}
|
|
15014
|
+
function clearDrainTimer() {
|
|
15015
|
+
if (!drainTimer) return;
|
|
15016
|
+
clearInterval(drainTimer);
|
|
15017
|
+
drainTimer = null;
|
|
15018
|
+
}
|
|
15019
|
+
function drain(force = false) {
|
|
15020
|
+
if (!transport) return;
|
|
15021
|
+
const rows = getOutbox().pendingBatch({
|
|
15022
|
+
limit: 100,
|
|
15023
|
+
now: force ? Number.POSITIVE_INFINITY : now()
|
|
15024
|
+
});
|
|
15025
|
+
for (const row of rows) {
|
|
15026
|
+
transport.sendFeedEvent({
|
|
15027
|
+
deliverySeq: row.deliverySeq,
|
|
15028
|
+
envelope: row.envelope
|
|
15029
|
+
});
|
|
15030
|
+
getOutbox().markAttempted({
|
|
15031
|
+
deliverySeq: row.deliverySeq,
|
|
15032
|
+
nextAttemptAt: now() + Math.min(3e4, (row.attempt + 1) * 1e3)
|
|
15033
|
+
});
|
|
15034
|
+
}
|
|
15035
|
+
}
|
|
15036
|
+
function startDrainTimer() {
|
|
15037
|
+
clearDrainTimer();
|
|
15038
|
+
const timer = setInterval(drain, drainIntervalMs);
|
|
15039
|
+
timer.unref();
|
|
15040
|
+
drainTimer = timer;
|
|
15041
|
+
drain(true);
|
|
15042
|
+
}
|
|
15043
|
+
return {
|
|
15044
|
+
publish(input) {
|
|
15045
|
+
if (input.feedEvents.length === 0) return;
|
|
15046
|
+
try {
|
|
15047
|
+
const config = readConfig2();
|
|
15048
|
+
if (!config) return;
|
|
15049
|
+
getOutbox().enqueue({
|
|
15050
|
+
instanceId: config.instanceId,
|
|
15051
|
+
athenaSessionId: input.athenaSessionId,
|
|
15052
|
+
origin: input.origin,
|
|
15053
|
+
feedEvents: input.feedEvents,
|
|
15054
|
+
emittedAt: now()
|
|
15055
|
+
});
|
|
15056
|
+
drain();
|
|
15057
|
+
} catch (err) {
|
|
15058
|
+
onError(
|
|
15059
|
+
`paired feed publish failed: ${err instanceof Error ? err.message : String(err)}`
|
|
15060
|
+
);
|
|
15061
|
+
}
|
|
15062
|
+
},
|
|
15063
|
+
attachTransport(nextTransport) {
|
|
15064
|
+
transport = nextTransport;
|
|
15065
|
+
startDrainTimer();
|
|
15066
|
+
},
|
|
15067
|
+
detachTransport() {
|
|
15068
|
+
transport = null;
|
|
15069
|
+
clearDrainTimer();
|
|
15070
|
+
},
|
|
15071
|
+
handleAck(frame) {
|
|
15072
|
+
getOutbox().markAcked({
|
|
15073
|
+
...typeof frame.deliverySeq === "number" ? { deliverySeq: frame.deliverySeq } : {},
|
|
15074
|
+
...typeof frame.eventId === "string" ? { eventId: frame.eventId } : {}
|
|
15075
|
+
});
|
|
15076
|
+
},
|
|
15077
|
+
close() {
|
|
15078
|
+
this.detachTransport();
|
|
15079
|
+
ownedOutbox?.close();
|
|
15080
|
+
}
|
|
15081
|
+
};
|
|
15082
|
+
}
|
|
15083
|
+
|
|
15084
|
+
// src/core/controller/rules.ts
|
|
15085
|
+
function ruleMatches(ruleToolName, toolName) {
|
|
15086
|
+
if (ruleToolName === "*") return true;
|
|
15087
|
+
if (ruleToolName === toolName) return true;
|
|
15088
|
+
if (ruleToolName.endsWith("__*")) {
|
|
15089
|
+
const prefix = ruleToolName.slice(0, -1);
|
|
15090
|
+
return toolName.startsWith(prefix);
|
|
15091
|
+
}
|
|
15092
|
+
return false;
|
|
15093
|
+
}
|
|
15094
|
+
function matchRule(rules, toolName) {
|
|
15095
|
+
const denyMatch = rules.find(
|
|
15096
|
+
(r) => r.action === "deny" && ruleMatches(r.toolName, toolName)
|
|
15097
|
+
);
|
|
15098
|
+
if (denyMatch) return denyMatch;
|
|
15099
|
+
return rules.find(
|
|
15100
|
+
(r) => r.action === "approve" && ruleMatches(r.toolName, toolName)
|
|
15101
|
+
);
|
|
15102
|
+
}
|
|
15103
|
+
|
|
15104
|
+
// src/core/controller/permission.ts
|
|
15105
|
+
var SESSION_APPROVAL_REQUEST_HOOKS = /* @__PURE__ */ new Set([
|
|
15106
|
+
"item/commandExecution/requestApproval",
|
|
15107
|
+
"item/fileChange/requestApproval",
|
|
15108
|
+
"item/permissions/requestApproval"
|
|
15109
|
+
]);
|
|
15110
|
+
function isScopedPermissionsRequest(hookName) {
|
|
15111
|
+
return hookName === "item/permissions/requestApproval";
|
|
15112
|
+
}
|
|
15113
|
+
function supportsSessionApproval(hookName) {
|
|
15114
|
+
return hookName !== void 0 && SESSION_APPROVAL_REQUEST_HOOKS.has(hookName);
|
|
15115
|
+
}
|
|
15116
|
+
function extractPermissionSnapshot(event) {
|
|
15117
|
+
const data = event.data;
|
|
15118
|
+
const toolNameFromData = typeof data["tool_name"] === "string" ? data["tool_name"] : void 0;
|
|
15119
|
+
const toolInputFromData = typeof data["tool_input"] === "object" && data["tool_input"] !== null ? data["tool_input"] : void 0;
|
|
15120
|
+
const toolUseIdFromData = typeof data["tool_use_id"] === "string" ? data["tool_use_id"] : void 0;
|
|
15121
|
+
return {
|
|
15122
|
+
request_id: event.id,
|
|
15123
|
+
ts: event.timestamp,
|
|
15124
|
+
kind: event.kind,
|
|
15125
|
+
hookName: event.hookName,
|
|
15126
|
+
tool_name: event.toolName ?? toolNameFromData ?? "Unknown",
|
|
15127
|
+
tool_input: toolInputFromData ?? {},
|
|
15128
|
+
tool_use_id: event.toolUseId ?? toolUseIdFromData,
|
|
15129
|
+
suggestions: data.permission_suggestions
|
|
15130
|
+
};
|
|
15131
|
+
}
|
|
15132
|
+
|
|
15133
|
+
// src/core/controller/runtimeController.ts
|
|
15134
|
+
function handleEvent(event, cb) {
|
|
15135
|
+
const eventKind2 = event.kind;
|
|
15136
|
+
const isScoped = isScopedPermissionsRequest(event.hookName);
|
|
15137
|
+
const eventData = event.data;
|
|
15138
|
+
const toolName = event.toolName ?? (typeof eventData["tool_name"] === "string" ? eventData["tool_name"] : void 0);
|
|
15139
|
+
if (eventKind2 === "permission.request" && toolName === "user_input") {
|
|
15140
|
+
cb.enqueueQuestion(event.id);
|
|
15141
|
+
cb.relayQuestion?.(event);
|
|
15142
|
+
return { handled: true };
|
|
15143
|
+
}
|
|
15144
|
+
if (eventKind2 === "permission.request" && toolName) {
|
|
15145
|
+
const rule = matchRule(cb.getRules(), toolName);
|
|
15146
|
+
if (rule?.action === "deny") {
|
|
15147
|
+
return {
|
|
15148
|
+
handled: true,
|
|
15149
|
+
decision: {
|
|
15150
|
+
type: "json",
|
|
15151
|
+
source: "rule",
|
|
15152
|
+
intent: {
|
|
15153
|
+
kind: "permission_deny",
|
|
15154
|
+
reason: `Blocked by rule: ${rule.addedBy}`
|
|
15155
|
+
}
|
|
15156
|
+
}
|
|
15157
|
+
};
|
|
15158
|
+
}
|
|
15159
|
+
if (rule?.action === "approve") {
|
|
15160
|
+
return {
|
|
15161
|
+
handled: true,
|
|
15162
|
+
decision: {
|
|
15163
|
+
type: "json",
|
|
15164
|
+
source: "rule",
|
|
15165
|
+
intent: { kind: "permission_allow" },
|
|
15166
|
+
// Scoped Codex permission grants are session-scoped under
|
|
15167
|
+
// auto rules so the same capability isn't re-prompted.
|
|
15168
|
+
...isScoped ? { data: { scope: "session" } } : {}
|
|
15169
|
+
}
|
|
15170
|
+
};
|
|
15171
|
+
}
|
|
15172
|
+
cb.enqueuePermission(event);
|
|
15173
|
+
cb.relayPermission?.(event);
|
|
15174
|
+
return { handled: true };
|
|
14970
15175
|
}
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
14974
|
-
|
|
15176
|
+
if (eventKind2 === "tool.pre" && toolName === "AskUserQuestion") {
|
|
15177
|
+
cb.enqueueQuestion(event.id);
|
|
15178
|
+
cb.relayQuestion?.(event);
|
|
15179
|
+
return { handled: true };
|
|
14975
15180
|
}
|
|
14976
|
-
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
|
|
14980
|
-
|
|
14981
|
-
|
|
14982
|
-
|
|
14983
|
-
|
|
14984
|
-
|
|
14985
|
-
|
|
14986
|
-
|
|
14987
|
-
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
});
|
|
15181
|
+
if (eventKind2 === "tool.pre" && toolName) {
|
|
15182
|
+
const rule = matchRule(cb.getRules(), toolName);
|
|
15183
|
+
if (rule?.action === "deny") {
|
|
15184
|
+
return {
|
|
15185
|
+
handled: true,
|
|
15186
|
+
decision: {
|
|
15187
|
+
type: "json",
|
|
15188
|
+
source: "rule",
|
|
15189
|
+
intent: {
|
|
15190
|
+
kind: "pre_tool_deny",
|
|
15191
|
+
reason: `Blocked by rule: ${rule.addedBy}`
|
|
15192
|
+
}
|
|
15193
|
+
}
|
|
15194
|
+
};
|
|
14991
15195
|
}
|
|
15196
|
+
return {
|
|
15197
|
+
handled: true,
|
|
15198
|
+
decision: {
|
|
15199
|
+
type: "json",
|
|
15200
|
+
source: "rule",
|
|
15201
|
+
intent: { kind: "pre_tool_allow" }
|
|
15202
|
+
}
|
|
15203
|
+
};
|
|
14992
15204
|
}
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
|
|
14996
|
-
|
|
14997
|
-
|
|
14998
|
-
|
|
15205
|
+
return { handled: false };
|
|
15206
|
+
}
|
|
15207
|
+
|
|
15208
|
+
// src/core/feed/ingest.ts
|
|
15209
|
+
function ingestRuntimeEvent(event, ctx) {
|
|
15210
|
+
const controllerResult = handleEvent(event, ctx.controllerCallbacks);
|
|
15211
|
+
const feedEvents = ctx.mapper.mapEvent(event);
|
|
15212
|
+
if (ctx.store) {
|
|
15213
|
+
persistOrDegrade(
|
|
15214
|
+
ctx.store,
|
|
15215
|
+
() => ctx.store.recordEvent(event, feedEvents),
|
|
15216
|
+
"recordEvent failed",
|
|
15217
|
+
ctx.onPersistFailure
|
|
15218
|
+
);
|
|
14999
15219
|
}
|
|
15000
15220
|
return {
|
|
15001
|
-
|
|
15002
|
-
|
|
15221
|
+
feedEvents,
|
|
15222
|
+
decision: controllerResult.handled && controllerResult.decision ? controllerResult.decision : null
|
|
15223
|
+
};
|
|
15224
|
+
}
|
|
15225
|
+
function ingestRuntimeDecision(eventId, decision, ctx) {
|
|
15226
|
+
const feedEvent = ctx.mapper.mapDecision(eventId, decision);
|
|
15227
|
+
if (feedEvent && ctx.store) {
|
|
15228
|
+
persistOrDegrade(
|
|
15229
|
+
ctx.store,
|
|
15230
|
+
() => ctx.store.recordFeedEvents([feedEvent]),
|
|
15231
|
+
"recordFeedEvents failed",
|
|
15232
|
+
ctx.onPersistFailure
|
|
15233
|
+
);
|
|
15234
|
+
}
|
|
15235
|
+
return feedEvent;
|
|
15236
|
+
}
|
|
15237
|
+
function persistOrDegrade(store, action, label, onFailure) {
|
|
15238
|
+
try {
|
|
15239
|
+
action();
|
|
15240
|
+
} catch (err) {
|
|
15241
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
15242
|
+
const message = `${label}: ${reason}`;
|
|
15243
|
+
store.markDegraded(message);
|
|
15244
|
+
onFailure?.(message);
|
|
15245
|
+
}
|
|
15246
|
+
}
|
|
15247
|
+
|
|
15248
|
+
// src/app/runtime/runtimeEventLoop.ts
|
|
15249
|
+
var DASHBOARD_DECISION_POLL_LIMIT = 25;
|
|
15250
|
+
var DASHBOARD_DECISION_POLL_INTERVAL_MS = 1e3;
|
|
15251
|
+
function resolveIngest(source) {
|
|
15252
|
+
return typeof source === "function" ? source() : source;
|
|
15253
|
+
}
|
|
15254
|
+
function attachRuntimeEventLoop(options) {
|
|
15255
|
+
const { runtime } = options;
|
|
15256
|
+
const runEvent = (event) => {
|
|
15257
|
+
options.onEventReceived?.(event);
|
|
15258
|
+
if (options.skipEvent?.(event)) return;
|
|
15259
|
+
const { feedEvents, decision } = ingestRuntimeEvent(
|
|
15260
|
+
event,
|
|
15261
|
+
resolveIngest(options.ingest)
|
|
15262
|
+
);
|
|
15263
|
+
if (decision) {
|
|
15264
|
+
runtime.sendDecision(event.id, decision);
|
|
15265
|
+
}
|
|
15266
|
+
options.emitEventFeed(feedEvents, event);
|
|
15267
|
+
};
|
|
15268
|
+
const runDecision = (eventId, decision) => {
|
|
15269
|
+
options.onDecisionReceived?.(eventId, decision);
|
|
15270
|
+
if (options.skipDecision?.(eventId, decision)) return;
|
|
15271
|
+
options.beforeDecisionIngest?.(eventId, decision);
|
|
15272
|
+
const feedEvent = ingestRuntimeDecision(
|
|
15273
|
+
eventId,
|
|
15274
|
+
decision,
|
|
15275
|
+
resolveIngest(options.ingest)
|
|
15276
|
+
);
|
|
15277
|
+
options.emitDecisionFeed(feedEvent, eventId, decision);
|
|
15278
|
+
};
|
|
15279
|
+
const unsubscribeEvent = runtime.onEvent((event) => {
|
|
15280
|
+
if (options.wrapEvent) {
|
|
15281
|
+
options.wrapEvent(event, () => runEvent(event));
|
|
15282
|
+
} else {
|
|
15283
|
+
runEvent(event);
|
|
15284
|
+
}
|
|
15285
|
+
});
|
|
15286
|
+
const unsubscribeDecision = runtime.onDecision((eventId, decision) => {
|
|
15287
|
+
if (options.wrapDecision) {
|
|
15288
|
+
options.wrapDecision(
|
|
15289
|
+
eventId,
|
|
15290
|
+
decision,
|
|
15291
|
+
() => runDecision(eventId, decision)
|
|
15292
|
+
);
|
|
15293
|
+
} else {
|
|
15294
|
+
runDecision(eventId, decision);
|
|
15295
|
+
}
|
|
15296
|
+
});
|
|
15297
|
+
return {
|
|
15298
|
+
stop() {
|
|
15299
|
+
unsubscribeEvent();
|
|
15300
|
+
unsubscribeDecision();
|
|
15301
|
+
}
|
|
15302
|
+
};
|
|
15303
|
+
}
|
|
15304
|
+
function startDashboardDecisionDrain(options) {
|
|
15305
|
+
const { runtime, inbox, athenaSessionId } = options;
|
|
15306
|
+
const drainOnce = () => {
|
|
15307
|
+
const rows = inbox.pendingForSession({
|
|
15308
|
+
athenaSessionId,
|
|
15309
|
+
limit: DASHBOARD_DECISION_POLL_LIMIT
|
|
15310
|
+
});
|
|
15311
|
+
for (const row of rows) {
|
|
15003
15312
|
try {
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
origin: input.origin,
|
|
15010
|
-
feedEvents: input.feedEvents,
|
|
15011
|
-
emittedAt: now()
|
|
15012
|
-
});
|
|
15013
|
-
drain();
|
|
15014
|
-
} catch (err) {
|
|
15015
|
-
onError(
|
|
15016
|
-
`paired feed publish failed: ${err instanceof Error ? err.message : String(err)}`
|
|
15017
|
-
);
|
|
15313
|
+
runtime.sendDecision(row.requestId, row.decision);
|
|
15314
|
+
inbox.markConsumed({ id: row.id });
|
|
15315
|
+
} catch (error) {
|
|
15316
|
+
if (!options.onError) throw error;
|
|
15317
|
+
options.onError(error);
|
|
15018
15318
|
}
|
|
15019
|
-
}
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
...typeof frame.eventId === "string" ? { eventId: frame.eventId } : {}
|
|
15032
|
-
});
|
|
15033
|
-
},
|
|
15034
|
-
close() {
|
|
15035
|
-
this.detachTransport();
|
|
15036
|
-
ownedOutbox?.close();
|
|
15319
|
+
}
|
|
15320
|
+
};
|
|
15321
|
+
drainOnce();
|
|
15322
|
+
const timer = setInterval(
|
|
15323
|
+
drainOnce,
|
|
15324
|
+
options.pollIntervalMs ?? DASHBOARD_DECISION_POLL_INTERVAL_MS
|
|
15325
|
+
);
|
|
15326
|
+
options.configureTimer?.(timer);
|
|
15327
|
+
return {
|
|
15328
|
+
drainOnce,
|
|
15329
|
+
stop() {
|
|
15330
|
+
clearInterval(timer);
|
|
15037
15331
|
}
|
|
15038
15332
|
};
|
|
15039
15333
|
}
|
|
@@ -15088,8 +15382,8 @@ function exitCodeFromFailure(failure) {
|
|
|
15088
15382
|
}
|
|
15089
15383
|
|
|
15090
15384
|
// src/app/exec/output.ts
|
|
15091
|
-
import
|
|
15092
|
-
import
|
|
15385
|
+
import fs19 from "fs/promises";
|
|
15386
|
+
import path16 from "path";
|
|
15093
15387
|
|
|
15094
15388
|
// src/app/exec/jsonl.ts
|
|
15095
15389
|
function createExecJsonlEvent(type, data, ts = Date.now()) {
|
|
@@ -15117,6 +15411,10 @@ function createExecOutputWriter(options) {
|
|
|
15117
15411
|
if (!options.verbose) return;
|
|
15118
15412
|
writeLine(options.stderr, `[athena exec] ${message}`);
|
|
15119
15413
|
},
|
|
15414
|
+
notice(message) {
|
|
15415
|
+
if (options.json) return;
|
|
15416
|
+
writeLine(options.stderr, `[athena exec] ${message}`);
|
|
15417
|
+
},
|
|
15120
15418
|
warn(message) {
|
|
15121
15419
|
writeLine(options.stderr, `[athena exec] warning: ${message}`);
|
|
15122
15420
|
},
|
|
@@ -15128,9 +15426,9 @@ function createExecOutputWriter(options) {
|
|
|
15128
15426
|
writeLine(options.stdout, message);
|
|
15129
15427
|
},
|
|
15130
15428
|
async writeLastMessage(filePath, message) {
|
|
15131
|
-
const absPath =
|
|
15132
|
-
await
|
|
15133
|
-
await
|
|
15429
|
+
const absPath = path16.resolve(filePath);
|
|
15430
|
+
await fs19.mkdir(path16.dirname(absPath), { recursive: true });
|
|
15431
|
+
await fs19.writeFile(absPath, message, "utf-8");
|
|
15134
15432
|
}
|
|
15135
15433
|
};
|
|
15136
15434
|
}
|
|
@@ -15145,6 +15443,34 @@ var NULL_TOKENS3 = {
|
|
|
15145
15443
|
contextSize: null,
|
|
15146
15444
|
contextWindowSize: null
|
|
15147
15445
|
};
|
|
15446
|
+
function formatPersonalCapabilityNotice(summary) {
|
|
15447
|
+
const parts = [];
|
|
15448
|
+
if (summary.mcpServers.length > 0) {
|
|
15449
|
+
const list = summary.mcpServers.map((server) => `${server.name} [${server.sourceLayer}]`).join(", ");
|
|
15450
|
+
parts.push(`mcp servers: ${list}`);
|
|
15451
|
+
}
|
|
15452
|
+
if (summary.skills.length > 0) {
|
|
15453
|
+
const list = summary.skills.map((skill) => `${skill.name} [${skill.sourceLayer}]`).join(", ");
|
|
15454
|
+
parts.push(`skills: ${list}`);
|
|
15455
|
+
}
|
|
15456
|
+
if (parts.length === 0) return null;
|
|
15457
|
+
return `personal capabilities active \u2014 ${parts.join("; ")}`;
|
|
15458
|
+
}
|
|
15459
|
+
function formatCapabilityConflictNotice(summary) {
|
|
15460
|
+
const parts = [];
|
|
15461
|
+
if (summary.mcpServers.length > 0) {
|
|
15462
|
+
const list = summary.mcpServers.map((server) => `${server.name} [${server.sourceLayer}]`).join(", ");
|
|
15463
|
+
parts.push(`mcp servers: ${list}`);
|
|
15464
|
+
}
|
|
15465
|
+
if (summary.skills.length > 0) {
|
|
15466
|
+
const list = summary.skills.map((skill) => `${skill.name} [${skill.sourceLayer}]`).join(", ");
|
|
15467
|
+
parts.push(`skills: ${list}`);
|
|
15468
|
+
}
|
|
15469
|
+
if (parts.length === 0) return null;
|
|
15470
|
+
return `personal capability conflicts \u2014 workflow plugin wins; shadowed: ${parts.join(
|
|
15471
|
+
"; "
|
|
15472
|
+
)}`;
|
|
15473
|
+
}
|
|
15148
15474
|
function workflowFailure(state, message) {
|
|
15149
15475
|
return {
|
|
15150
15476
|
kind: "workflow",
|
|
@@ -15209,7 +15535,7 @@ async function runExec(options) {
|
|
|
15209
15535
|
store = sessionStoreFactory({
|
|
15210
15536
|
sessionId: athenaSessionId,
|
|
15211
15537
|
projectDir: options.projectDir,
|
|
15212
|
-
dbPath: options.ephemeral ? ":memory:" :
|
|
15538
|
+
dbPath: options.ephemeral ? ":memory:" : path17.join(sessionsDir(), athenaSessionId, "session.db")
|
|
15213
15539
|
});
|
|
15214
15540
|
} catch (error) {
|
|
15215
15541
|
const message = `Failed to initialize session store: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -15300,23 +15626,6 @@ async function runExec(options) {
|
|
|
15300
15626
|
...options.signal ? { signal: options.signal } : {}
|
|
15301
15627
|
};
|
|
15302
15628
|
const dashboardDecisionInbox = options.dashboardDecisionInbox;
|
|
15303
|
-
const applyPendingDashboardDecisions = () => {
|
|
15304
|
-
if (!dashboardDecisionInbox) return;
|
|
15305
|
-
const rows = dashboardDecisionInbox.pendingForSession({
|
|
15306
|
-
athenaSessionId,
|
|
15307
|
-
limit: 25
|
|
15308
|
-
});
|
|
15309
|
-
for (const row of rows) {
|
|
15310
|
-
try {
|
|
15311
|
-
runtime.sendDecision(row.requestId, row.decision);
|
|
15312
|
-
dashboardDecisionInbox.markConsumed({ id: row.id });
|
|
15313
|
-
} catch (error) {
|
|
15314
|
-
output.warn(
|
|
15315
|
-
`dashboard decision failed: ${error instanceof Error ? error.message : String(error)}`
|
|
15316
|
-
);
|
|
15317
|
-
}
|
|
15318
|
-
}
|
|
15319
|
-
};
|
|
15320
15629
|
const linkedAdapterSessions = /* @__PURE__ */ new Set();
|
|
15321
15630
|
function publishFeedEvents(feedEvents) {
|
|
15322
15631
|
if (feedEvents.length === 0) return;
|
|
@@ -15377,60 +15686,57 @@ async function runExec(options) {
|
|
|
15377
15686
|
});
|
|
15378
15687
|
}
|
|
15379
15688
|
};
|
|
15380
|
-
const
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
linkedAdapterSessions.add(runtimeEvent.sessionId);
|
|
15384
|
-
safePersist(
|
|
15385
|
-
store,
|
|
15386
|
-
() => store.linkAdapterSession(runtimeEvent.sessionId, activeRunId),
|
|
15387
|
-
(message) => output.warn(message),
|
|
15388
|
-
"linkAdapterSession failed"
|
|
15389
|
-
);
|
|
15390
|
-
}
|
|
15391
|
-
output.emitJsonEvent("runtime.event", {
|
|
15392
|
-
id: runtimeEvent.id,
|
|
15393
|
-
kind: runtimeEvent.kind,
|
|
15394
|
-
hookName: runtimeEvent.hookName,
|
|
15395
|
-
sessionId: runtimeEvent.sessionId,
|
|
15396
|
-
toolName: runtimeEvent.toolName ?? null,
|
|
15397
|
-
data: runtimeEvent.data
|
|
15398
|
-
});
|
|
15399
|
-
if (latch.hasFailure()) return;
|
|
15400
|
-
const { feedEvents, decision } = ingestRuntimeEvent(runtimeEvent, {
|
|
15689
|
+
const runtimeEventLoop = attachRuntimeEventLoop({
|
|
15690
|
+
runtime,
|
|
15691
|
+
ingest: {
|
|
15401
15692
|
mapper,
|
|
15402
15693
|
store,
|
|
15403
15694
|
controllerCallbacks,
|
|
15404
15695
|
onPersistFailure: (message) => output.warn(message)
|
|
15405
|
-
}
|
|
15406
|
-
|
|
15407
|
-
|
|
15408
|
-
|
|
15409
|
-
|
|
15410
|
-
|
|
15411
|
-
|
|
15696
|
+
},
|
|
15697
|
+
onEventReceived: (runtimeEvent) => {
|
|
15698
|
+
adapterSessionId = runtimeEvent.sessionId;
|
|
15699
|
+
if (runtimeEvent.sessionId && activeRunId && !linkedAdapterSessions.has(runtimeEvent.sessionId)) {
|
|
15700
|
+
linkedAdapterSessions.add(runtimeEvent.sessionId);
|
|
15701
|
+
safePersist(
|
|
15702
|
+
store,
|
|
15703
|
+
() => store.linkAdapterSession(runtimeEvent.sessionId, activeRunId),
|
|
15704
|
+
(message) => output.warn(message),
|
|
15705
|
+
"linkAdapterSession failed"
|
|
15706
|
+
);
|
|
15412
15707
|
}
|
|
15413
|
-
|
|
15414
|
-
|
|
15415
|
-
|
|
15416
|
-
|
|
15417
|
-
|
|
15708
|
+
output.emitJsonEvent("runtime.event", {
|
|
15709
|
+
id: runtimeEvent.id,
|
|
15710
|
+
kind: runtimeEvent.kind,
|
|
15711
|
+
hookName: runtimeEvent.hookName,
|
|
15712
|
+
sessionId: runtimeEvent.sessionId,
|
|
15713
|
+
toolName: runtimeEvent.toolName ?? null,
|
|
15714
|
+
data: runtimeEvent.data
|
|
15715
|
+
});
|
|
15716
|
+
},
|
|
15717
|
+
skipEvent: () => latch.hasFailure(),
|
|
15718
|
+
emitEventFeed: (feedEvents) => {
|
|
15719
|
+
for (const event of feedEvents) {
|
|
15720
|
+
if (event.kind === "agent.message") {
|
|
15721
|
+
mappedFinalMessage = event.data.message;
|
|
15722
|
+
}
|
|
15723
|
+
}
|
|
15724
|
+
publishFeedEvents(feedEvents);
|
|
15725
|
+
},
|
|
15726
|
+
onDecisionReceived: (eventId, decision) => {
|
|
15418
15727
|
output.emitJsonEvent("runtime.decision", {
|
|
15419
15728
|
eventId,
|
|
15420
15729
|
decision
|
|
15421
15730
|
});
|
|
15422
|
-
|
|
15423
|
-
|
|
15424
|
-
store,
|
|
15425
|
-
onPersistFailure: (message) => output.warn(message)
|
|
15426
|
-
});
|
|
15731
|
+
},
|
|
15732
|
+
emitDecisionFeed: (feedEvent) => {
|
|
15427
15733
|
if (feedEvent) {
|
|
15428
15734
|
publishFeedEvents([feedEvent]);
|
|
15429
15735
|
}
|
|
15430
15736
|
}
|
|
15431
|
-
);
|
|
15737
|
+
});
|
|
15432
15738
|
let timeoutTimer;
|
|
15433
|
-
let
|
|
15739
|
+
let dashboardDecisionDrain;
|
|
15434
15740
|
if (typeof options.timeoutMs === "number" && options.timeoutMs > 0) {
|
|
15435
15741
|
timeoutTimer = setTimeout(() => {
|
|
15436
15742
|
latch.register({
|
|
@@ -15439,11 +15745,29 @@ async function runExec(options) {
|
|
|
15439
15745
|
});
|
|
15440
15746
|
}, options.timeoutMs);
|
|
15441
15747
|
}
|
|
15748
|
+
const personalCapabilities = options.personalCapabilities ?? {
|
|
15749
|
+
mcpServers: [],
|
|
15750
|
+
skills: []
|
|
15751
|
+
};
|
|
15752
|
+
const capabilityConflicts = options.capabilityConflicts ?? {
|
|
15753
|
+
mcpServers: [],
|
|
15754
|
+
skills: []
|
|
15755
|
+
};
|
|
15442
15756
|
output.emitJsonEvent("exec.started", {
|
|
15443
15757
|
projectDir: options.projectDir,
|
|
15444
15758
|
harness: options.harness,
|
|
15445
|
-
athenaSessionId: options.ephemeral ? null : athenaSessionId
|
|
15759
|
+
athenaSessionId: options.ephemeral ? null : athenaSessionId,
|
|
15760
|
+
personalCapabilities,
|
|
15761
|
+
capabilityConflicts
|
|
15446
15762
|
});
|
|
15763
|
+
const personalCapabilityNotice = formatPersonalCapabilityNotice(personalCapabilities);
|
|
15764
|
+
if (personalCapabilityNotice) {
|
|
15765
|
+
output.notice(personalCapabilityNotice);
|
|
15766
|
+
}
|
|
15767
|
+
const capabilityConflictNotice = formatCapabilityConflictNotice(capabilityConflicts);
|
|
15768
|
+
if (capabilityConflictNotice) {
|
|
15769
|
+
output.notice(capabilityConflictNotice);
|
|
15770
|
+
}
|
|
15447
15771
|
try {
|
|
15448
15772
|
await runtime.start();
|
|
15449
15773
|
runtimeStarted = true;
|
|
@@ -15451,12 +15775,16 @@ async function runExec(options) {
|
|
|
15451
15775
|
status: runtime.getStatus()
|
|
15452
15776
|
});
|
|
15453
15777
|
if (dashboardDecisionInbox) {
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15778
|
+
dashboardDecisionDrain = startDashboardDecisionDrain({
|
|
15779
|
+
runtime,
|
|
15780
|
+
inbox: dashboardDecisionInbox,
|
|
15781
|
+
athenaSessionId,
|
|
15782
|
+
...options.dashboardDecisionPollIntervalMs !== void 0 ? { pollIntervalMs: options.dashboardDecisionPollIntervalMs } : {},
|
|
15783
|
+
onError: (error) => output.warn(
|
|
15784
|
+
`dashboard decision failed: ${error instanceof Error ? error.message : String(error)}`
|
|
15785
|
+
),
|
|
15786
|
+
configureTimer: (timer) => timer.unref()
|
|
15787
|
+
});
|
|
15460
15788
|
}
|
|
15461
15789
|
const workflow = options.workflow;
|
|
15462
15790
|
output.emitJsonEvent("run.started", {
|
|
@@ -15543,14 +15871,11 @@ async function runExec(options) {
|
|
|
15543
15871
|
if (timeoutTimer) {
|
|
15544
15872
|
clearTimeout(timeoutTimer);
|
|
15545
15873
|
}
|
|
15546
|
-
|
|
15547
|
-
clearInterval(dashboardDecisionTimer);
|
|
15548
|
-
}
|
|
15874
|
+
dashboardDecisionDrain?.stop();
|
|
15549
15875
|
await writeLastMessageBeforeTerminalCompletion();
|
|
15550
15876
|
await runBeforeTerminalCompletion();
|
|
15551
15877
|
await sessionController.kill();
|
|
15552
|
-
|
|
15553
|
-
unsubscribeDecision();
|
|
15878
|
+
runtimeEventLoop.stop();
|
|
15554
15879
|
if (runtimeStarted) {
|
|
15555
15880
|
runtime.stop();
|
|
15556
15881
|
}
|
|
@@ -15599,7 +15924,7 @@ async function runExec(options) {
|
|
|
15599
15924
|
}
|
|
15600
15925
|
|
|
15601
15926
|
// src/app/dashboard/instanceSocketClient.ts
|
|
15602
|
-
import { WebSocket
|
|
15927
|
+
import { WebSocket } from "ws";
|
|
15603
15928
|
var DEFAULT_HEARTBEAT_MS = 3e4;
|
|
15604
15929
|
var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
15605
15930
|
function instanceSocketUrl(dashboardUrl, instanceId) {
|
|
@@ -15616,7 +15941,7 @@ function createInstanceSocketClient(opts) {
|
|
|
15616
15941
|
const log = opts.log ?? (() => {
|
|
15617
15942
|
});
|
|
15618
15943
|
const now = opts.now ?? (() => Date.now());
|
|
15619
|
-
const makeWebSocket = opts.makeWebSocket ?? ((url, accessToken) => new
|
|
15944
|
+
const makeWebSocket = opts.makeWebSocket ?? ((url, accessToken) => new WebSocket(url, [accessToken]));
|
|
15620
15945
|
const frameHandlers = /* @__PURE__ */ new Set();
|
|
15621
15946
|
const closeHandlers = /* @__PURE__ */ new Set();
|
|
15622
15947
|
let ws = null;
|
|
@@ -15816,7 +16141,7 @@ function normalizeHarnessOverride(value) {
|
|
|
15816
16141
|
}
|
|
15817
16142
|
|
|
15818
16143
|
// src/app/dashboard/runStreamClient.ts
|
|
15819
|
-
import { WebSocket as
|
|
16144
|
+
import { WebSocket as WebSocket2 } from "ws";
|
|
15820
16145
|
var DEFAULT_RECONNECT_DELAYS_MS = [250, 1e3, 2e3, 5e3, 15e3];
|
|
15821
16146
|
var DEFAULT_HEARTBEAT_MS2 = 3e4;
|
|
15822
16147
|
var DEFAULT_WATCHDOG_MS = 9e4;
|
|
@@ -15829,7 +16154,7 @@ function createRunStreamClient(opts) {
|
|
|
15829
16154
|
const heartbeatMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS2;
|
|
15830
16155
|
const watchdogMs = opts.watchdogTimeoutMs ?? DEFAULT_WATCHDOG_MS;
|
|
15831
16156
|
const maxQueueSize = opts.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
15832
|
-
const makeWebSocket = opts.makeWebSocket ?? ((url) => new
|
|
16157
|
+
const makeWebSocket = opts.makeWebSocket ?? ((url) => new WebSocket2(url));
|
|
15833
16158
|
const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
15834
16159
|
const clearTimer = opts.clearTimer ?? ((t) => clearTimeout(t));
|
|
15835
16160
|
let nextSeq = 1;
|
|
@@ -16169,8 +16494,8 @@ async function createRemoteRunEventPublisher({
|
|
|
16169
16494
|
// src/app/dashboard/artifactCapture.ts
|
|
16170
16495
|
import { execFile } from "child_process";
|
|
16171
16496
|
import crypto3 from "crypto";
|
|
16172
|
-
import
|
|
16173
|
-
import
|
|
16497
|
+
import fs20 from "fs/promises";
|
|
16498
|
+
import path18 from "path";
|
|
16174
16499
|
import { promisify } from "util";
|
|
16175
16500
|
var execFileAsync = promisify(execFile);
|
|
16176
16501
|
function parseArtifactUploadSpec(value) {
|
|
@@ -16353,21 +16678,21 @@ async function collectArtifactPayloads(input) {
|
|
|
16353
16678
|
return payloads;
|
|
16354
16679
|
}
|
|
16355
16680
|
async function readWorkspaceFile(projectDir, rel) {
|
|
16356
|
-
const absolute =
|
|
16357
|
-
const workspaceRoot = await
|
|
16681
|
+
const absolute = path18.resolve(projectDir, rel);
|
|
16682
|
+
const workspaceRoot = await fs20.realpath(projectDir);
|
|
16358
16683
|
let stat;
|
|
16359
16684
|
try {
|
|
16360
|
-
stat = await
|
|
16685
|
+
stat = await fs20.lstat(absolute);
|
|
16361
16686
|
} catch {
|
|
16362
16687
|
return null;
|
|
16363
16688
|
}
|
|
16364
16689
|
if (!stat.isFile() || stat.isSymbolicLink()) return null;
|
|
16365
|
-
const real = await
|
|
16366
|
-
const relativeToRoot =
|
|
16367
|
-
if (relativeToRoot === ".." || relativeToRoot.startsWith(`..${
|
|
16690
|
+
const real = await fs20.realpath(absolute);
|
|
16691
|
+
const relativeToRoot = path18.relative(workspaceRoot, real);
|
|
16692
|
+
if (relativeToRoot === ".." || relativeToRoot.startsWith(`..${path18.sep}`) || path18.isAbsolute(relativeToRoot)) {
|
|
16368
16693
|
return null;
|
|
16369
16694
|
}
|
|
16370
|
-
return
|
|
16695
|
+
return fs20.readFile(absolute);
|
|
16371
16696
|
}
|
|
16372
16697
|
async function gitDiffForAllowedPaths(input) {
|
|
16373
16698
|
const paths = splitNul(await git(input.projectDir, input.nameArgs)).filter(
|
|
@@ -16458,13 +16783,13 @@ var DEFAULT_HARD_DENY = [
|
|
|
16458
16783
|
".ssh/**"
|
|
16459
16784
|
];
|
|
16460
16785
|
function isAllowedRelativePath(rel, hardDeny) {
|
|
16461
|
-
if (
|
|
16462
|
-
const normalized =
|
|
16786
|
+
if (path18.isAbsolute(rel) || rel.includes("\0")) return false;
|
|
16787
|
+
const normalized = path18.posix.normalize(rel.replaceAll(path18.sep, "/"));
|
|
16463
16788
|
if (normalized === ".." || normalized.startsWith("../")) return false;
|
|
16464
16789
|
return !hardDeny.some((pattern) => matchesDenyPattern(normalized, pattern));
|
|
16465
16790
|
}
|
|
16466
16791
|
function matchesDenyPattern(rel, pattern) {
|
|
16467
|
-
const normalized = pattern.replaceAll(
|
|
16792
|
+
const normalized = pattern.replaceAll(path18.sep, "/");
|
|
16468
16793
|
if (normalized.includes("*") || normalized.includes("?")) {
|
|
16469
16794
|
return globToRegExp(normalized).test(rel);
|
|
16470
16795
|
}
|
|
@@ -16861,18 +17186,18 @@ async function executeRemoteAssignment({
|
|
|
16861
17186
|
|
|
16862
17187
|
// src/infra/config/attachmentMirror.ts
|
|
16863
17188
|
import crypto4 from "crypto";
|
|
16864
|
-
import
|
|
17189
|
+
import fs21 from "fs";
|
|
16865
17190
|
import os13 from "os";
|
|
16866
|
-
import
|
|
17191
|
+
import path19 from "path";
|
|
16867
17192
|
function attachmentMirrorPath(env = process.env) {
|
|
16868
17193
|
const home = env["HOME"] ?? os13.homedir();
|
|
16869
|
-
return
|
|
17194
|
+
return path19.join(home, ".config", "athena", "attachments.json");
|
|
16870
17195
|
}
|
|
16871
17196
|
function readAttachmentMirror(env = process.env) {
|
|
16872
17197
|
const file = attachmentMirrorPath(env);
|
|
16873
17198
|
let raw;
|
|
16874
17199
|
try {
|
|
16875
|
-
raw =
|
|
17200
|
+
raw = fs21.readFileSync(file, "utf-8");
|
|
16876
17201
|
} catch (err) {
|
|
16877
17202
|
if (err.code === "ENOENT") return null;
|
|
16878
17203
|
throw err;
|
|
@@ -16896,29 +17221,29 @@ function readAttachmentMirror(env = process.env) {
|
|
|
16896
17221
|
function writeAttachmentMirror(mirror, env = process.env) {
|
|
16897
17222
|
const validated = parseAttachmentMirror(mirror);
|
|
16898
17223
|
const file = attachmentMirrorPath(env);
|
|
16899
|
-
const dir =
|
|
16900
|
-
|
|
17224
|
+
const dir = path19.dirname(file);
|
|
17225
|
+
fs21.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
16901
17226
|
const tmp = `${file}.${process.pid}.${crypto4.randomBytes(4).toString("hex")}.tmp`;
|
|
16902
|
-
const fd =
|
|
17227
|
+
const fd = fs21.openSync(tmp, "w", 384);
|
|
16903
17228
|
try {
|
|
16904
|
-
|
|
16905
|
-
|
|
17229
|
+
fs21.writeSync(fd, JSON.stringify(validated, null, 2) + "\n");
|
|
17230
|
+
fs21.fsyncSync(fd);
|
|
16906
17231
|
} finally {
|
|
16907
|
-
|
|
17232
|
+
fs21.closeSync(fd);
|
|
16908
17233
|
}
|
|
16909
17234
|
try {
|
|
16910
|
-
|
|
17235
|
+
fs21.renameSync(tmp, file);
|
|
16911
17236
|
} catch (err) {
|
|
16912
17237
|
try {
|
|
16913
|
-
|
|
17238
|
+
fs21.unlinkSync(tmp);
|
|
16914
17239
|
} catch {
|
|
16915
17240
|
}
|
|
16916
17241
|
throw err;
|
|
16917
17242
|
}
|
|
16918
17243
|
if (process.platform !== "win32") {
|
|
16919
17244
|
try {
|
|
16920
|
-
|
|
16921
|
-
|
|
17245
|
+
fs21.chmodSync(dir, 448);
|
|
17246
|
+
fs21.chmodSync(file, 384);
|
|
16922
17247
|
} catch {
|
|
16923
17248
|
}
|
|
16924
17249
|
}
|
|
@@ -16926,7 +17251,7 @@ function writeAttachmentMirror(mirror, env = process.env) {
|
|
|
16926
17251
|
function removeAttachmentMirror(env = process.env) {
|
|
16927
17252
|
const file = attachmentMirrorPath(env);
|
|
16928
17253
|
try {
|
|
16929
|
-
|
|
17254
|
+
fs21.unlinkSync(file);
|
|
16930
17255
|
} catch (err) {
|
|
16931
17256
|
if (err.code !== "ENOENT") throw err;
|
|
16932
17257
|
}
|
|
@@ -17054,7 +17379,6 @@ function createAttachmentReconciler(options) {
|
|
|
17054
17379
|
}
|
|
17055
17380
|
|
|
17056
17381
|
// src/app/dashboard/dashboardDecisionInbox.ts
|
|
17057
|
-
import Database5 from "better-sqlite3";
|
|
17058
17382
|
function dashboardDecisionInboxPath() {
|
|
17059
17383
|
return `${ensureDaemonStateDir().dir}/dashboard-decision-inbox.db`;
|
|
17060
17384
|
}
|
|
@@ -17099,8 +17423,6 @@ function migrateLegacyUniqueConstraint(db) {
|
|
|
17099
17423
|
}
|
|
17100
17424
|
function initInboxSchema(db) {
|
|
17101
17425
|
db.exec(`
|
|
17102
|
-
PRAGMA journal_mode = WAL;
|
|
17103
|
-
|
|
17104
17426
|
CREATE TABLE IF NOT EXISTS dashboard_decision_inbox (
|
|
17105
17427
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
17106
17428
|
athena_session_id TEXT NOT NULL,
|
|
@@ -17122,8 +17444,9 @@ function initInboxSchema(db) {
|
|
|
17122
17444
|
`);
|
|
17123
17445
|
}
|
|
17124
17446
|
function createDashboardDecisionInbox(options = {}) {
|
|
17125
|
-
const db =
|
|
17126
|
-
|
|
17447
|
+
const db = openVersionedDb(options.dbPath ?? dashboardDecisionInboxPath(), {
|
|
17448
|
+
migrate: initInboxSchema
|
|
17449
|
+
});
|
|
17127
17450
|
const upsertUnconsumed = db.prepare(`
|
|
17128
17451
|
INSERT INTO dashboard_decision_inbox (
|
|
17129
17452
|
athena_session_id,
|
|
@@ -17332,9 +17655,9 @@ function createDashboardPairedExecution(options) {
|
|
|
17332
17655
|
}
|
|
17333
17656
|
|
|
17334
17657
|
// src/app/dashboard/remoteWorkspaceResolver.ts
|
|
17335
|
-
import
|
|
17658
|
+
import fs22 from "fs";
|
|
17336
17659
|
import os14 from "os";
|
|
17337
|
-
import
|
|
17660
|
+
import path20 from "path";
|
|
17338
17661
|
function resolveRemoteWorkspace(assignment, options = {}) {
|
|
17339
17662
|
const { spec, runId, runnerId } = assignment;
|
|
17340
17663
|
if (spec.projectDir) {
|
|
@@ -17343,14 +17666,14 @@ function resolveRemoteWorkspace(assignment, options = {}) {
|
|
|
17343
17666
|
const sessionId = spec.athenaSessionId ?? spec.sessionId;
|
|
17344
17667
|
const deploymentSlug = deploymentSlugFromUrl(options.dashboardUrl);
|
|
17345
17668
|
const stateDir = daemonStatePaths(options.env).dir;
|
|
17346
|
-
const projectDir = sessionId ?
|
|
17669
|
+
const projectDir = sessionId ? path20.join(
|
|
17347
17670
|
stateDir,
|
|
17348
17671
|
"remote-workspaces",
|
|
17349
17672
|
deploymentSlug,
|
|
17350
17673
|
sanitizePathSegment(runnerId),
|
|
17351
17674
|
"sessions",
|
|
17352
17675
|
sanitizePathSegment(sessionId)
|
|
17353
|
-
) :
|
|
17676
|
+
) : path20.join(
|
|
17354
17677
|
stateDir,
|
|
17355
17678
|
"remote-workspaces",
|
|
17356
17679
|
deploymentSlug,
|
|
@@ -17359,10 +17682,10 @@ function resolveRemoteWorkspace(assignment, options = {}) {
|
|
|
17359
17682
|
sanitizePathSegment(runId)
|
|
17360
17683
|
);
|
|
17361
17684
|
try {
|
|
17362
|
-
|
|
17685
|
+
fs22.mkdirSync(projectDir, { recursive: true, mode: 448 });
|
|
17363
17686
|
if (process.platform !== "win32") {
|
|
17364
17687
|
try {
|
|
17365
|
-
|
|
17688
|
+
fs22.chmodSync(projectDir, 448);
|
|
17366
17689
|
} catch {
|
|
17367
17690
|
}
|
|
17368
17691
|
}
|
|
@@ -17378,8 +17701,8 @@ function resolveRemoteWorkspace(assignment, options = {}) {
|
|
|
17378
17701
|
return validateProjectDir(projectDir, options.env);
|
|
17379
17702
|
}
|
|
17380
17703
|
function validateProjectDir(projectDir, env = process.env) {
|
|
17381
|
-
const resolved =
|
|
17382
|
-
if (!
|
|
17704
|
+
const resolved = path20.resolve(projectDir);
|
|
17705
|
+
if (!path20.isAbsolute(projectDir)) {
|
|
17383
17706
|
return {
|
|
17384
17707
|
kind: "rejected",
|
|
17385
17708
|
rejection: {
|
|
@@ -17388,7 +17711,7 @@ function validateProjectDir(projectDir, env = process.env) {
|
|
|
17388
17711
|
}
|
|
17389
17712
|
};
|
|
17390
17713
|
}
|
|
17391
|
-
const home =
|
|
17714
|
+
const home = path20.resolve(env["HOME"] ?? os14.homedir());
|
|
17392
17715
|
if (resolved === home) {
|
|
17393
17716
|
return {
|
|
17394
17717
|
kind: "rejected",
|
|
@@ -17400,7 +17723,7 @@ function validateProjectDir(projectDir, env = process.env) {
|
|
|
17400
17723
|
}
|
|
17401
17724
|
let stat;
|
|
17402
17725
|
try {
|
|
17403
|
-
stat =
|
|
17726
|
+
stat = fs22.statSync(resolved);
|
|
17404
17727
|
} catch {
|
|
17405
17728
|
return {
|
|
17406
17729
|
kind: "rejected",
|
|
@@ -17845,18 +18168,18 @@ async function runDashboardRuntimeDaemon(options = {}) {
|
|
|
17845
18168
|
}
|
|
17846
18169
|
|
|
17847
18170
|
// src/infra/daemon/pidLock.ts
|
|
17848
|
-
import
|
|
18171
|
+
import fs23 from "fs";
|
|
17849
18172
|
function acquirePidLock(pidPath) {
|
|
17850
18173
|
const ownPid = process.pid;
|
|
17851
18174
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
17852
18175
|
try {
|
|
17853
|
-
const fd =
|
|
18176
|
+
const fd = fs23.openSync(pidPath, "wx", 384);
|
|
17854
18177
|
try {
|
|
17855
|
-
|
|
18178
|
+
fs23.writeSync(fd, `${ownPid}
|
|
17856
18179
|
`);
|
|
17857
|
-
|
|
18180
|
+
fs23.fsyncSync(fd);
|
|
17858
18181
|
} finally {
|
|
17859
|
-
|
|
18182
|
+
fs23.closeSync(fd);
|
|
17860
18183
|
}
|
|
17861
18184
|
return makeHandle(pidPath, ownPid);
|
|
17862
18185
|
} catch (err) {
|
|
@@ -17870,7 +18193,7 @@ function acquirePidLock(pidPath) {
|
|
|
17870
18193
|
}
|
|
17871
18194
|
if (existing.state === "stale") {
|
|
17872
18195
|
try {
|
|
17873
|
-
|
|
18196
|
+
fs23.unlinkSync(pidPath);
|
|
17874
18197
|
} catch (err) {
|
|
17875
18198
|
if (err.code !== "ENOENT") throw err;
|
|
17876
18199
|
}
|
|
@@ -17884,7 +18207,7 @@ function acquirePidLock(pidPath) {
|
|
|
17884
18207
|
function readPidLock(pidPath) {
|
|
17885
18208
|
let raw;
|
|
17886
18209
|
try {
|
|
17887
|
-
raw =
|
|
18210
|
+
raw = fs23.readFileSync(pidPath, "utf-8");
|
|
17888
18211
|
} catch (err) {
|
|
17889
18212
|
if (err.code === "ENOENT") {
|
|
17890
18213
|
return { state: "absent" };
|
|
@@ -17908,9 +18231,9 @@ function makeHandle(pidPath, pid) {
|
|
|
17908
18231
|
if (released) return;
|
|
17909
18232
|
released = true;
|
|
17910
18233
|
try {
|
|
17911
|
-
const raw =
|
|
18234
|
+
const raw = fs23.readFileSync(pidPath, "utf-8").trim();
|
|
17912
18235
|
if (raw === String(pid)) {
|
|
17913
|
-
|
|
18236
|
+
fs23.unlinkSync(pidPath);
|
|
17914
18237
|
}
|
|
17915
18238
|
} catch (err) {
|
|
17916
18239
|
if (err.code !== "ENOENT") {
|
|
@@ -17936,7 +18259,7 @@ function isProcessAlive(pid) {
|
|
|
17936
18259
|
}
|
|
17937
18260
|
|
|
17938
18261
|
// src/infra/daemon/udsIpc.ts
|
|
17939
|
-
import
|
|
18262
|
+
import fs24 from "fs";
|
|
17940
18263
|
import net2 from "net";
|
|
17941
18264
|
|
|
17942
18265
|
// src/infra/daemon/udsFrameCodec.ts
|
|
@@ -17981,7 +18304,7 @@ async function startUdsServer(socketPath, handler, log) {
|
|
|
17981
18304
|
});
|
|
17982
18305
|
if (process.platform !== "win32") {
|
|
17983
18306
|
try {
|
|
17984
|
-
|
|
18307
|
+
fs24.chmodSync(socketPath, 384);
|
|
17985
18308
|
} catch {
|
|
17986
18309
|
}
|
|
17987
18310
|
}
|
|
@@ -17991,7 +18314,7 @@ async function startUdsServer(socketPath, handler, log) {
|
|
|
17991
18314
|
server.close(() => resolve());
|
|
17992
18315
|
});
|
|
17993
18316
|
try {
|
|
17994
|
-
|
|
18317
|
+
fs24.unlinkSync(socketPath);
|
|
17995
18318
|
} catch (err) {
|
|
17996
18319
|
if (err.code !== "ENOENT") {
|
|
17997
18320
|
}
|
|
@@ -18109,7 +18432,7 @@ async function sendUdsRequest(socketPath, request, options = {}) {
|
|
|
18109
18432
|
async function unlinkStaleSocket(socketPath) {
|
|
18110
18433
|
let stat;
|
|
18111
18434
|
try {
|
|
18112
|
-
stat =
|
|
18435
|
+
stat = fs24.statSync(socketPath);
|
|
18113
18436
|
} catch (err) {
|
|
18114
18437
|
if (err.code === "ENOENT") return;
|
|
18115
18438
|
throw err;
|
|
@@ -18136,7 +18459,7 @@ async function unlinkStaleSocket(socketPath) {
|
|
|
18136
18459
|
`uds path ${socketPath} is in use by another process; aborting`
|
|
18137
18460
|
);
|
|
18138
18461
|
}
|
|
18139
|
-
|
|
18462
|
+
fs24.unlinkSync(socketPath);
|
|
18140
18463
|
}
|
|
18141
18464
|
|
|
18142
18465
|
export {
|
|
@@ -18154,8 +18477,8 @@ export {
|
|
|
18154
18477
|
mergeFeedItems,
|
|
18155
18478
|
buildPostByToolUseId,
|
|
18156
18479
|
createFeedMapper,
|
|
18157
|
-
|
|
18158
|
-
|
|
18480
|
+
attachRuntimeEventLoop,
|
|
18481
|
+
startDashboardDecisionDrain,
|
|
18159
18482
|
generateId,
|
|
18160
18483
|
daemonStatePaths,
|
|
18161
18484
|
ensureDaemonStateDir,
|
|
@@ -18196,8 +18519,6 @@ export {
|
|
|
18196
18519
|
GatewayUnreachableError,
|
|
18197
18520
|
GatewayUnauthorizedError,
|
|
18198
18521
|
connect,
|
|
18199
|
-
createWsClientTransport,
|
|
18200
|
-
wsClientOptionsForEndpoint,
|
|
18201
18522
|
isSupportedGatewayUrl,
|
|
18202
18523
|
readGatewayClientConfig,
|
|
18203
18524
|
writeGatewayClientConfig,
|
|
@@ -18225,6 +18546,8 @@ export {
|
|
|
18225
18546
|
isBashToolResponse,
|
|
18226
18547
|
resolveToolColumn,
|
|
18227
18548
|
IndexedTimeline,
|
|
18549
|
+
parseFrontmatter,
|
|
18550
|
+
resolveEffectiveCapabilities,
|
|
18228
18551
|
bootstrapRuntimeConfig,
|
|
18229
18552
|
EXEC_EXIT_CODE,
|
|
18230
18553
|
runExec,
|
|
@@ -18237,4 +18560,4 @@ export {
|
|
|
18237
18560
|
startUdsServer,
|
|
18238
18561
|
sendUdsRequest
|
|
18239
18562
|
};
|
|
18240
|
-
//# sourceMappingURL=chunk-
|
|
18563
|
+
//# sourceMappingURL=chunk-YZ7RCBKO.js.map
|