@slock-ai/daemon 0.50.0-play.20260518104853 → 0.51.0
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/{chunk-SY3CKIJO.js → chunk-NAMH7BB6.js} +990 -572
- package/dist/cli/index.js +167 -165
- package/dist/core.js +2 -6
- package/dist/index.js +3 -5
- package/package.json +1 -2
- package/dist/drivers/piSdkRunner.js +0 -96
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
} from "./chunk-KNMCE6WB.js";
|
|
8
8
|
|
|
9
9
|
// src/core.ts
|
|
10
|
-
import
|
|
10
|
+
import path16 from "path";
|
|
11
11
|
import os8 from "os";
|
|
12
12
|
import { createRequire } from "module";
|
|
13
13
|
import { accessSync } from "fs";
|
|
14
|
-
import { fileURLToPath
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
15
|
|
|
16
16
|
// ../shared/src/tracing/index.ts
|
|
17
17
|
var DEFAULT_TRACE_FLAGS = "00";
|
|
@@ -723,7 +723,6 @@ var SERVER_CAPABILITY_MATRIX = {
|
|
|
723
723
|
var RUNTIMES = [
|
|
724
724
|
{ id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
|
|
725
725
|
{ id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
|
|
726
|
-
{ id: "pi", displayName: "Pi", binary: "pi", supported: true },
|
|
727
726
|
{ id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
|
|
728
727
|
{ id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
|
|
729
728
|
{ id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
|
|
@@ -765,10 +764,10 @@ var DISPLAY_PLAN_CONFIG = {
|
|
|
765
764
|
};
|
|
766
765
|
|
|
767
766
|
// src/agentProcessManager.ts
|
|
768
|
-
import { mkdirSync as
|
|
767
|
+
import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
769
768
|
import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
|
|
770
769
|
import { createHash as createHash2 } from "crypto";
|
|
771
|
-
import
|
|
770
|
+
import path12 from "path";
|
|
772
771
|
import os6 from "os";
|
|
773
772
|
|
|
774
773
|
// src/drivers/claude.ts
|
|
@@ -778,7 +777,7 @@ import os2 from "os";
|
|
|
778
777
|
import path4 from "path";
|
|
779
778
|
|
|
780
779
|
// src/drivers/cliTransport.ts
|
|
781
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
780
|
+
import { mkdirSync, rmSync, writeFileSync } from "fs";
|
|
782
781
|
import path2 from "path";
|
|
783
782
|
|
|
784
783
|
// src/drivers/systemPrompt.ts
|
|
@@ -871,16 +870,14 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
|
|
|
871
870
|
16. **\`slock attachment upload\`** \u2014 Upload a file to attach to a message. Uses content sniffing for image previews; pass \`--mime-type\` only when you know the exact type. Returns an attachment ID to pass to \`slock message send\`.
|
|
872
871
|
17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
873
872
|
18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
874
|
-
19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--display-name <name>\`, and \`--description <text>\`. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
|
|
875
|
-
20. **\`slock
|
|
876
|
-
21. **\`slock
|
|
877
|
-
22. **\`slock reminder
|
|
878
|
-
23. **\`slock reminder
|
|
879
|
-
24. **\`slock reminder
|
|
880
|
-
25. **\`slock reminder
|
|
881
|
-
26. **\`slock
|
|
882
|
-
27. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
883
|
-
28. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
|
|
873
|
+
19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--avatar-url pixel:random:<seed>\`, \`--display-name <name>\`, and \`--description <text>\`. Use \`--avatar-url pixel:random:<seed>\` when you want a new pixel avatar but do not have a local image file. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
|
|
874
|
+
20. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
875
|
+
21. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
|
|
876
|
+
22. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
|
|
877
|
+
23. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
878
|
+
24. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
879
|
+
25. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
880
|
+
26. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
|
|
884
881
|
|
|
885
882
|
The CLI prints human-readable canonical text on success (matching the format you see in received messages and history). On failure it prints JSON to stderr:
|
|
886
883
|
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
@@ -981,11 +978,6 @@ Each channel has a **name** and optionally a **description** that define its pur
|
|
|
981
978
|
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
982
979
|
- **Stay on topic** \u2014 when proactively sharing results or updates, post in the channel most relevant to the work. Don't scatter messages across unrelated channels.
|
|
983
980
|
- If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
|
|
984
|
-
const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
|
|
985
|
-
|
|
986
|
-
If a registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first run \`slock integration list\` and match the app to a registered service before browsing the app. Use \`slock integration login --service <service>\` to provision or reuse your agent login for that service. When the command returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes an app URL, open that URL as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`slock profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.` : `### Third-party integrations
|
|
987
|
-
|
|
988
|
-
If a registered third-party service requires login, use Slock Agent Login through the available registered-service interface instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first inspect the registered-service interface and match the app to a registered service before browsing the app. Once the registered-service interface reports the agent login is ready, the agent-side login is ready. If that interface provides an app URL, use it as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow or treat internal request IDs as OAuth callback codes unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use your Slock profile view. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.`;
|
|
989
981
|
const readingHistorySection = isCli ? `### Reading history
|
|
990
982
|
|
|
991
983
|
\`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
|
|
@@ -1157,8 +1149,6 @@ ${discoverySection}
|
|
|
1157
1149
|
|
|
1158
1150
|
${channelAwarenessSection}
|
|
1159
1151
|
|
|
1160
|
-
${thirdPartyIntegrationsSection}
|
|
1161
|
-
|
|
1162
1152
|
${readingHistorySection}
|
|
1163
1153
|
|
|
1164
1154
|
${historicalReferenceSection}
|
|
@@ -1190,17 +1180,6 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
1190
1180
|
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
1191
1181
|
- When done, summarize the result.
|
|
1192
1182
|
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.
|
|
1193
|
-
- For long answers where users need the conclusion first but details still matter, put the conclusion and next action outside any collapse, then use sanitized HTML details blocks for optional depth:
|
|
1194
|
-
|
|
1195
|
-
\`\`\`html
|
|
1196
|
-
<details>
|
|
1197
|
-
<summary>Evidence, logs, or edge cases</summary>
|
|
1198
|
-
|
|
1199
|
-
Detailed notes go here.
|
|
1200
|
-
</details>
|
|
1201
|
-
\`\`\`
|
|
1202
|
-
|
|
1203
|
-
Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
|
|
1204
1183
|
|
|
1205
1184
|
### Conversation etiquette
|
|
1206
1185
|
|
|
@@ -1370,21 +1349,304 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
|
|
|
1370
1349
|
return candidates.filter((candidate) => existsSync(candidate.path));
|
|
1371
1350
|
}
|
|
1372
1351
|
|
|
1373
|
-
// src/
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1352
|
+
// src/agentCredentialProxy.ts
|
|
1353
|
+
import { randomBytes } from "crypto";
|
|
1354
|
+
import http from "http";
|
|
1355
|
+
import { URL as URL2 } from "url";
|
|
1356
|
+
var registrations = /* @__PURE__ */ new Map();
|
|
1357
|
+
var servers = /* @__PURE__ */ new Map();
|
|
1358
|
+
function allocatePort() {
|
|
1359
|
+
return 43e3 + randomBytes(2).readUInt16BE(0) % 1e4;
|
|
1360
|
+
}
|
|
1361
|
+
function ensureServer(port) {
|
|
1362
|
+
if (servers.has(port)) return;
|
|
1363
|
+
const server = http.createServer((req, res) => {
|
|
1364
|
+
void handleProxyRequest(req, res);
|
|
1365
|
+
});
|
|
1366
|
+
server.on("error", (err) => {
|
|
1367
|
+
logger.warn(`[Agent Credential Proxy] local proxy on port ${port} failed: ${err.message}`);
|
|
1368
|
+
});
|
|
1369
|
+
server.listen(port, "127.0.0.1");
|
|
1370
|
+
server.unref();
|
|
1371
|
+
servers.set(port, server);
|
|
1372
|
+
}
|
|
1373
|
+
async function handleProxyRequest(req, res) {
|
|
1374
|
+
const auth = req.headers.authorization;
|
|
1375
|
+
const token = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice("Bearer ".length) : "";
|
|
1376
|
+
const registration = registrations.get(token);
|
|
1377
|
+
if (!registration) {
|
|
1378
|
+
res.writeHead(401, { "content-type": "application/json" });
|
|
1379
|
+
res.end(JSON.stringify({ error: "invalid local agent proxy token", code: "invalid_agent_proxy_token" }));
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
try {
|
|
1383
|
+
const target = new URL2(req.url ?? "/", registration.serverUrl);
|
|
1384
|
+
const headers = new Headers();
|
|
1385
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
1386
|
+
if (value === void 0) continue;
|
|
1387
|
+
if (name.toLowerCase() === "host") continue;
|
|
1388
|
+
if (name.toLowerCase() === "authorization") continue;
|
|
1389
|
+
if (Array.isArray(value)) {
|
|
1390
|
+
for (const item of value) headers.append(name, item);
|
|
1391
|
+
} else {
|
|
1392
|
+
headers.set(name, value);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
headers.set("Authorization", `Bearer ${registration.apiKey}`);
|
|
1396
|
+
headers.set("X-Agent-Id", registration.agentId);
|
|
1397
|
+
headers.set("X-Slock-Client", "cli");
|
|
1398
|
+
headers.set("X-Slock-Agent-Active-Capabilities", registration.activeCapabilities);
|
|
1399
|
+
const method = req.method ?? "GET";
|
|
1400
|
+
let body = method === "GET" || method === "HEAD" ? void 0 : req;
|
|
1401
|
+
let sendTarget;
|
|
1402
|
+
if (method === "POST" && target.pathname === "/internal/agent-api/send") {
|
|
1403
|
+
const rawBody = await readRequestBody(req);
|
|
1404
|
+
const prepared = await prepareAgentApiSendForward(registration, headers, rawBody);
|
|
1405
|
+
if (prepared.localResponse) {
|
|
1406
|
+
const responseText = JSON.stringify(prepared.localResponse);
|
|
1407
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
1408
|
+
res.end(responseText);
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
body = prepared.bodyText;
|
|
1412
|
+
sendTarget = prepared.target;
|
|
1413
|
+
headers.set("content-type", "application/json");
|
|
1414
|
+
headers.set("content-length", String(Buffer.byteLength(prepared.bodyText)));
|
|
1415
|
+
}
|
|
1416
|
+
const upstream = await fetch(target, {
|
|
1417
|
+
method,
|
|
1418
|
+
headers,
|
|
1419
|
+
body,
|
|
1420
|
+
// Required by undici when forwarding a Node stream.
|
|
1421
|
+
duplex: body ? "half" : void 0
|
|
1422
|
+
});
|
|
1423
|
+
if (shouldBufferJsonResponse(upstream, target.pathname, registration)) {
|
|
1424
|
+
const responseText = await upstream.text();
|
|
1425
|
+
consumeVisibleResponse(registration, target, sendTarget, responseText);
|
|
1426
|
+
res.writeHead(upstream.status, Object.fromEntries(upstream.headers.entries()));
|
|
1427
|
+
res.end(responseText);
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
res.writeHead(upstream.status, Object.fromEntries(upstream.headers.entries()));
|
|
1431
|
+
if (upstream.body) {
|
|
1432
|
+
const reader = upstream.body.getReader();
|
|
1433
|
+
try {
|
|
1434
|
+
while (true) {
|
|
1435
|
+
const { done, value } = await reader.read();
|
|
1436
|
+
if (done) break;
|
|
1437
|
+
res.write(Buffer.from(value));
|
|
1438
|
+
}
|
|
1439
|
+
} finally {
|
|
1440
|
+
res.end();
|
|
1441
|
+
}
|
|
1442
|
+
} else {
|
|
1443
|
+
res.end();
|
|
1444
|
+
}
|
|
1445
|
+
} catch (err) {
|
|
1446
|
+
res.writeHead(502, { "content-type": "application/json" });
|
|
1447
|
+
res.end(JSON.stringify({
|
|
1448
|
+
error: "failed to proxy local agent request",
|
|
1449
|
+
code: "agent_proxy_failed",
|
|
1450
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
1451
|
+
}));
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
async function readRequestBody(req) {
|
|
1455
|
+
let body = "";
|
|
1456
|
+
req.setEncoding("utf8");
|
|
1457
|
+
for await (const chunk of req) {
|
|
1458
|
+
body += String(chunk);
|
|
1459
|
+
}
|
|
1460
|
+
return body;
|
|
1461
|
+
}
|
|
1462
|
+
function messageSeq(message) {
|
|
1463
|
+
return Number(message.seq ?? 0);
|
|
1464
|
+
}
|
|
1465
|
+
function boundaryBefore(messages) {
|
|
1466
|
+
let minSeq = Number.POSITIVE_INFINITY;
|
|
1467
|
+
for (const message of messages) {
|
|
1468
|
+
const seq = Math.floor(messageSeq(message));
|
|
1469
|
+
if (Number.isFinite(seq) && seq > 0) minSeq = Math.min(minSeq, seq);
|
|
1470
|
+
}
|
|
1471
|
+
return Number.isFinite(minSeq) ? Math.max(0, minSeq - 1) : void 0;
|
|
1472
|
+
}
|
|
1473
|
+
function maxMessageSeq(messages) {
|
|
1474
|
+
let maxSeq = 0;
|
|
1475
|
+
for (const message of messages) {
|
|
1476
|
+
const seq = Math.floor(messageSeq(message));
|
|
1477
|
+
if (Number.isFinite(seq) && seq > 0) maxSeq = Math.max(maxSeq, seq);
|
|
1478
|
+
}
|
|
1479
|
+
return maxSeq > 0 ? maxSeq : void 0;
|
|
1480
|
+
}
|
|
1481
|
+
function parseTargetFields(target) {
|
|
1482
|
+
if (target.startsWith("dm:@")) {
|
|
1483
|
+
const rest = target.slice("dm:@".length);
|
|
1484
|
+
const [peer, threadId] = rest.split(":", 2);
|
|
1485
|
+
if (threadId) {
|
|
1486
|
+
return {
|
|
1487
|
+
channel_type: "thread",
|
|
1488
|
+
channel_name: threadId,
|
|
1489
|
+
parent_channel_type: "dm",
|
|
1490
|
+
parent_channel_name: peer
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
return { channel_type: "dm", channel_name: peer };
|
|
1494
|
+
}
|
|
1495
|
+
if (target.startsWith("#")) {
|
|
1496
|
+
const rest = target.slice(1);
|
|
1497
|
+
const [channel, threadId] = rest.split(":", 2);
|
|
1498
|
+
if (threadId) {
|
|
1499
|
+
return {
|
|
1500
|
+
channel_type: "thread",
|
|
1501
|
+
channel_name: threadId,
|
|
1502
|
+
parent_channel_type: "channel",
|
|
1503
|
+
parent_channel_name: channel
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
return { channel_type: "channel", channel_name: channel };
|
|
1507
|
+
}
|
|
1508
|
+
return {};
|
|
1509
|
+
}
|
|
1510
|
+
function normalizeVisibleMessage(message, target) {
|
|
1511
|
+
const targetFields = target ? parseTargetFields(target) : {};
|
|
1512
|
+
return {
|
|
1513
|
+
...targetFields,
|
|
1514
|
+
...message,
|
|
1515
|
+
message_id: message.message_id ?? message.id,
|
|
1516
|
+
timestamp: message.timestamp ?? message.createdAt,
|
|
1517
|
+
sender_type: message.sender_type ?? message.senderType,
|
|
1518
|
+
sender_name: message.sender_name ?? message.senderName,
|
|
1519
|
+
sender_description: message.sender_description ?? message.senderDescription ?? null
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
function normalizeVisibleMessages(messages, target) {
|
|
1523
|
+
return messages.map((message) => normalizeVisibleMessage(message, target));
|
|
1524
|
+
}
|
|
1525
|
+
async function loadRecentTargetMessages(registration, headers, target) {
|
|
1526
|
+
const historyUrl = new URL2("/internal/agent-api/history", registration.serverUrl);
|
|
1527
|
+
historyUrl.searchParams.set("channel", target);
|
|
1528
|
+
historyUrl.searchParams.set("limit", "3");
|
|
1529
|
+
const historyHeaders = new Headers(headers);
|
|
1530
|
+
historyHeaders.delete("content-length");
|
|
1531
|
+
historyHeaders.delete("content-type");
|
|
1532
|
+
const res = await fetch(historyUrl, { method: "GET", headers: historyHeaders });
|
|
1533
|
+
if (!res.ok) return [];
|
|
1534
|
+
const parsed = await res.json().catch(() => null);
|
|
1535
|
+
return Array.isArray(parsed?.messages) ? normalizeVisibleMessages(parsed.messages, target) : [];
|
|
1536
|
+
}
|
|
1537
|
+
async function prepareAgentApiSendForward(registration, headers, rawBody) {
|
|
1538
|
+
let body;
|
|
1539
|
+
try {
|
|
1540
|
+
body = rawBody ? JSON.parse(rawBody) : {};
|
|
1541
|
+
} catch {
|
|
1542
|
+
return { bodyText: rawBody };
|
|
1543
|
+
}
|
|
1544
|
+
const target = typeof body.target === "string" ? body.target : void 0;
|
|
1545
|
+
const coordinator = registration.inboxCoordinator;
|
|
1546
|
+
if (!target || !coordinator || body.continueAnyway === true) {
|
|
1547
|
+
return { bodyText: JSON.stringify(body), target };
|
|
1548
|
+
}
|
|
1549
|
+
const pending = coordinator.getPendingMessages(target);
|
|
1550
|
+
if (pending.length > 0) {
|
|
1551
|
+
const staleBoundary = boundaryBefore(pending);
|
|
1552
|
+
if (staleBoundary !== void 0) {
|
|
1553
|
+
body.seenUpToSeq = staleBoundary;
|
|
1554
|
+
}
|
|
1555
|
+
return { bodyText: JSON.stringify(body), target };
|
|
1556
|
+
}
|
|
1557
|
+
const existingBoundary = typeof body.seenUpToSeq === "number" && Number.isFinite(body.seenUpToSeq) ? Math.max(0, Math.floor(body.seenUpToSeq)) : void 0;
|
|
1558
|
+
const loadedBoundary = coordinator.getBoundary(target);
|
|
1559
|
+
const boundary = Math.max(existingBoundary ?? 0, loadedBoundary ?? 0);
|
|
1560
|
+
if (boundary > 0) {
|
|
1561
|
+
body.seenUpToSeq = boundary;
|
|
1562
|
+
return { bodyText: JSON.stringify(body), target };
|
|
1563
|
+
}
|
|
1564
|
+
const recent = await loadRecentTargetMessages(registration, headers, target);
|
|
1565
|
+
if (recent.length > 0) {
|
|
1566
|
+
const seenUpToSeq = maxMessageSeq(recent);
|
|
1567
|
+
coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "send_preflight_context" });
|
|
1568
|
+
return {
|
|
1569
|
+
bodyText: JSON.stringify(body),
|
|
1570
|
+
target,
|
|
1571
|
+
localResponse: {
|
|
1572
|
+
state: "held",
|
|
1573
|
+
seenUpToSeq,
|
|
1574
|
+
heldMessages: recent,
|
|
1575
|
+
newMessageCount: recent.length,
|
|
1576
|
+
shownMessageCount: recent.length,
|
|
1577
|
+
omittedMessageCount: 0
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
return { bodyText: JSON.stringify(body), target };
|
|
1582
|
+
}
|
|
1583
|
+
function shouldBufferJsonResponse(upstream, pathname, registration) {
|
|
1584
|
+
if (!registration.inboxCoordinator) return false;
|
|
1585
|
+
const contentType = upstream.headers.get("content-type") ?? "";
|
|
1586
|
+
if (!contentType.includes("application/json")) return false;
|
|
1587
|
+
return pathname === "/internal/agent-api/send" || pathname === "/internal/agent-api/events" || pathname === "/internal/agent-api/history";
|
|
1588
|
+
}
|
|
1589
|
+
function consumeVisibleResponse(registration, targetUrl, sendTarget, responseText) {
|
|
1590
|
+
const coordinator = registration.inboxCoordinator;
|
|
1591
|
+
if (!coordinator) return;
|
|
1592
|
+
let parsed;
|
|
1593
|
+
try {
|
|
1594
|
+
parsed = JSON.parse(responseText);
|
|
1595
|
+
} catch {
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
if (targetUrl.pathname === "/internal/agent-api/send" && parsed.state === "held" && Array.isArray(parsed.heldMessages)) {
|
|
1599
|
+
coordinator.consumeVisibleMessages({
|
|
1600
|
+
target: sendTarget,
|
|
1601
|
+
messages: normalizeVisibleMessages(parsed.heldMessages, sendTarget),
|
|
1602
|
+
boundarySeq: typeof parsed.seenUpToSeq === "number" ? parsed.seenUpToSeq : void 0,
|
|
1603
|
+
source: "server_held_context"
|
|
1604
|
+
});
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
if (targetUrl.pathname === "/internal/agent-api/events" && Array.isArray(parsed.events)) {
|
|
1608
|
+
coordinator.consumeVisibleMessages({ messages: normalizeVisibleMessages(parsed.events), source: "agent_api_events" });
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
if (targetUrl.pathname === "/internal/agent-api/history" && Array.isArray(parsed.messages)) {
|
|
1612
|
+
const target = targetUrl.searchParams.get("channel") ?? void 0;
|
|
1613
|
+
const messages = normalizeVisibleMessages(parsed.messages, target);
|
|
1614
|
+
coordinator.consumeVisibleMessages({ target, messages, boundarySeq: maxMessageSeq(messages), source: "agent_api_history" });
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
function registerAgentCredentialProxy(input) {
|
|
1618
|
+
const port = allocatePort();
|
|
1619
|
+
ensureServer(port);
|
|
1620
|
+
const proxyToken = `sap_${randomBytes(32).toString("base64url")}`;
|
|
1621
|
+
registrations.set(proxyToken, {
|
|
1622
|
+
serverUrl: input.serverUrl,
|
|
1623
|
+
apiKey: input.apiKey,
|
|
1624
|
+
agentId: input.agentId,
|
|
1625
|
+
launchId: input.launchId ?? null,
|
|
1626
|
+
activeCapabilities: input.activeCapabilities,
|
|
1627
|
+
inboxCoordinator: input.inboxCoordinator
|
|
1628
|
+
});
|
|
1629
|
+
return {
|
|
1630
|
+
proxyUrl: `http://127.0.0.1:${port}`,
|
|
1631
|
+
proxyToken
|
|
1632
|
+
};
|
|
1379
1633
|
}
|
|
1380
|
-
function
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1634
|
+
function unregisterAgentCredentialProxyForLaunch(input) {
|
|
1635
|
+
let removed = 0;
|
|
1636
|
+
const launchId = input.launchId ?? null;
|
|
1637
|
+
for (const [token, registration] of registrations) {
|
|
1638
|
+
if (registration.agentId === input.agentId && registration.launchId === launchId) {
|
|
1639
|
+
registrations.delete(token);
|
|
1640
|
+
removed += 1;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
return removed;
|
|
1384
1644
|
}
|
|
1385
1645
|
|
|
1386
1646
|
// src/drivers/cliTransport.ts
|
|
1387
1647
|
var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1648
|
+
var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
|
|
1649
|
+
var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
1388
1650
|
function runtimeContextEnv(config) {
|
|
1389
1651
|
const ctx = config.runtimeContext;
|
|
1390
1652
|
if (!ctx) return {};
|
|
@@ -1408,17 +1670,43 @@ function prepareCliTransport(ctx, extraEnv = {}, platform = process.platform) {
|
|
|
1408
1670
|
}
|
|
1409
1671
|
const slockDir = path2.join(ctx.workingDirectory, ".slock");
|
|
1410
1672
|
mkdirSync(slockDir, { recursive: true });
|
|
1673
|
+
const slockHome = resolveSlockHome();
|
|
1411
1674
|
const tokenFile = path2.join(slockDir, "agent-token");
|
|
1412
|
-
|
|
1675
|
+
const agentCredentialKey = ctx.config.agentCredentialKey;
|
|
1676
|
+
let agentCredentialProxy = null;
|
|
1677
|
+
let agentCredentialProxyTokenFile = null;
|
|
1678
|
+
if (typeof agentCredentialKey === "string" && agentCredentialKey.length > 0) {
|
|
1679
|
+
rmSync(tokenFile, { force: true });
|
|
1680
|
+
agentCredentialProxy = registerAgentCredentialProxy({
|
|
1681
|
+
agentId: ctx.agentId,
|
|
1682
|
+
launchId: ctx.launchId,
|
|
1683
|
+
serverUrl: ctx.config.serverUrl,
|
|
1684
|
+
apiKey: agentCredentialKey,
|
|
1685
|
+
activeCapabilities: DEFAULT_ACTIVE_CAPABILITIES,
|
|
1686
|
+
inboxCoordinator: ctx.agentCredentialProxyInboxCoordinator
|
|
1687
|
+
});
|
|
1688
|
+
const launchPart = safePathPart(ctx.launchId || `pid-${process.pid}`);
|
|
1689
|
+
const proxyTokenDir = path2.join(slockHome, "agent-proxy-tokens", safePathPart(ctx.agentId));
|
|
1690
|
+
mkdirSync(proxyTokenDir, { recursive: true, mode: 448 });
|
|
1691
|
+
agentCredentialProxyTokenFile = path2.join(proxyTokenDir, `${launchPart}.token`);
|
|
1692
|
+
writeFileSync(agentCredentialProxyTokenFile, agentCredentialProxy.proxyToken, { mode: 384 });
|
|
1693
|
+
} else {
|
|
1694
|
+
writeFileSync(tokenFile, ctx.config.authToken || ctx.daemonApiKey, { mode: 384 });
|
|
1695
|
+
}
|
|
1413
1696
|
const posixWrapper = path2.join(slockDir, "slock");
|
|
1697
|
+
const posixCredentialPrefix = agentCredentialProxy ? `SLOCK_AGENT_PROXY_URL=${shellSingleQuote(agentCredentialProxy.proxyUrl)} SLOCK_AGENT_PROXY_TOKEN_FILE=${shellSingleQuote(agentCredentialProxyTokenFile)} SLOCK_AGENT_ACTIVE_CAPABILITIES=${shellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)} ` : "";
|
|
1414
1698
|
const posixBody = `#!/usr/bin/env bash
|
|
1415
|
-
exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
|
|
1699
|
+
${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
|
|
1416
1700
|
`;
|
|
1417
1701
|
writeFileSync(posixWrapper, posixBody, { mode: 493 });
|
|
1418
1702
|
if (platform === "win32") {
|
|
1419
1703
|
const cmdWrapper = path2.join(slockDir, "slock.cmd");
|
|
1704
|
+
const cmdCredentialLine = agentCredentialProxy ? `set "SLOCK_AGENT_PROXY_URL=${agentCredentialProxy.proxyUrl}"\r
|
|
1705
|
+
set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
|
|
1706
|
+
set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
|
|
1707
|
+
` : "";
|
|
1420
1708
|
const cmdBody = `@echo off\r
|
|
1421
|
-
"${process.execPath}" "${ctx.slockCliPath}" %*\r
|
|
1709
|
+
${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
|
|
1422
1710
|
`;
|
|
1423
1711
|
writeFileSync(cmdWrapper, cmdBody);
|
|
1424
1712
|
}
|
|
@@ -1429,17 +1717,27 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
|
|
|
1429
1717
|
...ctx.config.envVars || {},
|
|
1430
1718
|
...extraEnv,
|
|
1431
1719
|
...runtimeContextEnv(ctx.config),
|
|
1432
|
-
[SLOCK_HOME_ENV]:
|
|
1720
|
+
[SLOCK_HOME_ENV]: slockHome,
|
|
1433
1721
|
SLOCK_AGENT_ID: ctx.agentId,
|
|
1434
1722
|
...ctx.launchId ? { SLOCK_AGENT_LAUNCH_ID: ctx.launchId } : {},
|
|
1435
1723
|
SLOCK_SERVER_URL: ctx.config.serverUrl,
|
|
1436
|
-
SLOCK_AGENT_TOKEN_FILE: tokenFile,
|
|
1724
|
+
...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
|
|
1437
1725
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
1438
1726
|
};
|
|
1439
|
-
|
|
1727
|
+
delete spawnEnv.SLOCK_AGENT_TOKEN;
|
|
1728
|
+
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
|
|
1729
|
+
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
|
|
1730
|
+
delete spawnEnv.SLOCK_AGENT_PROXY_URL;
|
|
1731
|
+
delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN;
|
|
1732
|
+
delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN_FILE;
|
|
1733
|
+
delete spawnEnv.SLOCK_AGENT_ACTIVE_CAPABILITIES;
|
|
1734
|
+
if (agentCredentialProxy) {
|
|
1735
|
+
delete spawnEnv.SLOCK_AGENT_TOKEN_FILE;
|
|
1736
|
+
}
|
|
1440
1737
|
return {
|
|
1441
1738
|
slockDir,
|
|
1442
1739
|
tokenFile,
|
|
1740
|
+
agentCredentialProxyUrl: agentCredentialProxy?.proxyUrl ?? null,
|
|
1443
1741
|
wrapperPath,
|
|
1444
1742
|
spawnEnv
|
|
1445
1743
|
};
|
|
@@ -1473,7 +1771,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
|
|
|
1473
1771
|
}
|
|
1474
1772
|
function resolveCommandOnPath(command, deps = {}) {
|
|
1475
1773
|
const platform = deps.platform ?? process.platform;
|
|
1476
|
-
const env =
|
|
1774
|
+
const env = deps.env ?? process.env;
|
|
1477
1775
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
1478
1776
|
if (platform === "win32") {
|
|
1479
1777
|
return resolveCommandOnWindows(command, env, execFileSyncFn);
|
|
@@ -1498,7 +1796,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
1498
1796
|
return null;
|
|
1499
1797
|
}
|
|
1500
1798
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
1501
|
-
const env =
|
|
1799
|
+
const env = deps.env ?? process.env;
|
|
1502
1800
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
1503
1801
|
try {
|
|
1504
1802
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -1607,7 +1905,7 @@ function collectClaudeMcpConfigFiles(ctx, home) {
|
|
|
1607
1905
|
return files;
|
|
1608
1906
|
}
|
|
1609
1907
|
function buildClaudeUserMcpServers(ctx, home) {
|
|
1610
|
-
const
|
|
1908
|
+
const servers2 = /* @__PURE__ */ Object.create(null);
|
|
1611
1909
|
for (const configFile of collectClaudeMcpConfigFiles(ctx, home)) {
|
|
1612
1910
|
const mcpServers = readClaudeMcpServers(configFile.path, configFile.vars);
|
|
1613
1911
|
if (!mcpServers) continue;
|
|
@@ -1616,10 +1914,10 @@ function buildClaudeUserMcpServers(ctx, home) {
|
|
|
1616
1914
|
logger.warn(`[Claude] ignoring invalid MCP server "${name}" in ${configFile.path}`);
|
|
1617
1915
|
continue;
|
|
1618
1916
|
}
|
|
1619
|
-
|
|
1917
|
+
servers2[name] = server;
|
|
1620
1918
|
}
|
|
1621
1919
|
}
|
|
1622
|
-
return
|
|
1920
|
+
return servers2;
|
|
1623
1921
|
}
|
|
1624
1922
|
var ClaudeDriver = class {
|
|
1625
1923
|
id = "claude";
|
|
@@ -1667,9 +1965,9 @@ var ClaudeDriver = class {
|
|
|
1667
1965
|
CLAUDE_DISALLOWED_TOOLS
|
|
1668
1966
|
];
|
|
1669
1967
|
if (opts.standingPromptFilePath) {
|
|
1670
|
-
args.push("--system-prompt-file", opts.standingPromptFilePath);
|
|
1968
|
+
args.push("--append-system-prompt-file", opts.standingPromptFilePath);
|
|
1671
1969
|
} else {
|
|
1672
|
-
args.push("--system-prompt", standingPrompt);
|
|
1970
|
+
args.push("--append-system-prompt", standingPrompt);
|
|
1673
1971
|
}
|
|
1674
1972
|
if (config.sessionId) {
|
|
1675
1973
|
args.push("--resume", config.sessionId);
|
|
@@ -1917,13 +2215,29 @@ function resolveWindowsNpmCodexEntry(deps = {}) {
|
|
|
1917
2215
|
}
|
|
1918
2216
|
return null;
|
|
1919
2217
|
}
|
|
2218
|
+
function resolveWindowsCodexDesktopEntry(deps = {}) {
|
|
2219
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync4;
|
|
2220
|
+
const env = deps.env ?? process.env;
|
|
2221
|
+
const homeDir = deps.homeDir;
|
|
2222
|
+
const winPath = path5.win32;
|
|
2223
|
+
const candidates = [
|
|
2224
|
+
env.LOCALAPPDATA ? winPath.join(env.LOCALAPPDATA, "OpenAI", "Codex", "bin", "codex.exe") : null,
|
|
2225
|
+
env.USERPROFILE ? winPath.join(env.USERPROFILE, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null,
|
|
2226
|
+
homeDir ? winPath.join(homeDir, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null
|
|
2227
|
+
].filter((candidate) => Boolean(candidate));
|
|
2228
|
+
for (const candidate of candidates) {
|
|
2229
|
+
if (existsSyncFn(candidate)) return candidate;
|
|
2230
|
+
}
|
|
2231
|
+
return null;
|
|
2232
|
+
}
|
|
1920
2233
|
function resolveCodexCommand(deps = {}) {
|
|
1921
2234
|
const platform = deps.platform ?? process.platform;
|
|
1922
2235
|
if (platform === "win32") {
|
|
1923
2236
|
const npmEntry = resolveWindowsNpmCodexEntry(deps);
|
|
1924
2237
|
if (npmEntry) return npmEntry;
|
|
1925
2238
|
const command = resolveCommandOnPath("codex", deps);
|
|
1926
|
-
|
|
2239
|
+
if (command && !isWindowsSandboxRunner(command)) return command;
|
|
2240
|
+
return resolveWindowsCodexDesktopEntry(deps);
|
|
1927
2241
|
}
|
|
1928
2242
|
const pathCommand = resolveCommandOnPath("codex", deps);
|
|
1929
2243
|
if (pathCommand) return pathCommand;
|
|
@@ -1966,6 +2280,10 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
|
|
|
1966
2280
|
if (command && !isWindowsSandboxRunner(command)) {
|
|
1967
2281
|
return { command, args: commandArgs };
|
|
1968
2282
|
}
|
|
2283
|
+
const desktopEntry = resolveWindowsCodexDesktopEntry(deps);
|
|
2284
|
+
if (desktopEntry) {
|
|
2285
|
+
return { command: desktopEntry, args: commandArgs };
|
|
2286
|
+
}
|
|
1969
2287
|
throw new Error(
|
|
1970
2288
|
"Cannot resolve Codex CLI entry point on Windows. Install Codex Desktop or install @openai/codex globally via npm (npm i -g @openai/codex). Ignoring .codex/.sandbox-bin/codex-command-runner because it is a sandbox helper, not the Codex CLI."
|
|
1971
2289
|
);
|
|
@@ -2775,7 +3093,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
2775
3093
|
}
|
|
2776
3094
|
function runCursorModelsCommand() {
|
|
2777
3095
|
return spawnSync("cursor-agent", ["models"], {
|
|
2778
|
-
env:
|
|
3096
|
+
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
2779
3097
|
encoding: "utf8",
|
|
2780
3098
|
timeout: 5e3
|
|
2781
3099
|
});
|
|
@@ -2822,7 +3140,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
2822
3140
|
}
|
|
2823
3141
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
2824
3142
|
const existsSyncFn = deps.existsSyncFn ?? existsSync6;
|
|
2825
|
-
const env =
|
|
3143
|
+
const env = deps.env ?? process.env;
|
|
2826
3144
|
const winPath = path8.win32;
|
|
2827
3145
|
let geminiEntry = null;
|
|
2828
3146
|
try {
|
|
@@ -2996,16 +3314,13 @@ var GeminiDriver = class {
|
|
|
2996
3314
|
// src/drivers/kimi.ts
|
|
2997
3315
|
import { randomUUID } from "crypto";
|
|
2998
3316
|
import { spawn as spawn6 } from "child_process";
|
|
2999
|
-
import {
|
|
3317
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
3000
3318
|
import os4 from "os";
|
|
3001
3319
|
import path9 from "path";
|
|
3002
3320
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
3003
3321
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
3004
3322
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
3005
3323
|
var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
|
|
3006
|
-
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
3007
|
-
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
3008
|
-
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
3009
3324
|
function parseToolArguments(raw) {
|
|
3010
3325
|
if (typeof raw !== "string") return raw;
|
|
3011
3326
|
try {
|
|
@@ -3014,73 +3329,6 @@ function parseToolArguments(raw) {
|
|
|
3014
3329
|
return raw;
|
|
3015
3330
|
}
|
|
3016
3331
|
}
|
|
3017
|
-
function readKimiConfigSource(home = os4.homedir(), env = process.env) {
|
|
3018
|
-
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3019
|
-
if (inlineConfig && inlineConfig.trim()) {
|
|
3020
|
-
return {
|
|
3021
|
-
raw: inlineConfig,
|
|
3022
|
-
explicitPath: null,
|
|
3023
|
-
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
3024
|
-
};
|
|
3025
|
-
}
|
|
3026
|
-
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3027
|
-
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
|
|
3028
|
-
try {
|
|
3029
|
-
return {
|
|
3030
|
-
raw: readFileSync3(configPath, "utf8"),
|
|
3031
|
-
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3032
|
-
sourcePath: configPath
|
|
3033
|
-
};
|
|
3034
|
-
} catch {
|
|
3035
|
-
return {
|
|
3036
|
-
raw: null,
|
|
3037
|
-
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3038
|
-
sourcePath: configPath
|
|
3039
|
-
};
|
|
3040
|
-
}
|
|
3041
|
-
}
|
|
3042
|
-
function buildKimiSpawnEnv(env = process.env) {
|
|
3043
|
-
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
3044
|
-
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3045
|
-
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3046
|
-
return scrubDaemonChildEnv(spawnEnv);
|
|
3047
|
-
}
|
|
3048
|
-
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
3049
|
-
return {
|
|
3050
|
-
...process.env,
|
|
3051
|
-
...ctx.config.envVars || {},
|
|
3052
|
-
...overrideEnv || {}
|
|
3053
|
-
};
|
|
3054
|
-
}
|
|
3055
|
-
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
3056
|
-
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
3057
|
-
const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
|
|
3058
|
-
const args = [];
|
|
3059
|
-
let configFilePath = null;
|
|
3060
|
-
let configContent = null;
|
|
3061
|
-
if (source.explicitPath) {
|
|
3062
|
-
configFilePath = source.explicitPath;
|
|
3063
|
-
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
3064
|
-
configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
3065
|
-
configContent = source.raw;
|
|
3066
|
-
if (opts.writeGeneratedConfig !== false) {
|
|
3067
|
-
writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
3068
|
-
chmodSync(configFilePath, 384);
|
|
3069
|
-
}
|
|
3070
|
-
}
|
|
3071
|
-
if (configFilePath) {
|
|
3072
|
-
args.push("--config-file", configFilePath);
|
|
3073
|
-
}
|
|
3074
|
-
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3075
|
-
args.push("--model", ctx.config.model);
|
|
3076
|
-
}
|
|
3077
|
-
return {
|
|
3078
|
-
args,
|
|
3079
|
-
env: buildKimiSpawnEnv(env),
|
|
3080
|
-
configFilePath,
|
|
3081
|
-
configContent
|
|
3082
|
-
};
|
|
3083
|
-
}
|
|
3084
3332
|
function resolveKimiSpawn(commandArgs, deps = {}) {
|
|
3085
3333
|
return {
|
|
3086
3334
|
command: resolveCommandOnPath("kimi", deps) ?? "kimi",
|
|
@@ -3104,25 +3352,7 @@ var KimiDriver = class {
|
|
|
3104
3352
|
};
|
|
3105
3353
|
model = {
|
|
3106
3354
|
detectedModelsVerifiedAs: "launchable",
|
|
3107
|
-
toLaunchSpec: (modelId
|
|
3108
|
-
if (!ctx) return { args: ["--model", modelId] };
|
|
3109
|
-
const launchCtx = {
|
|
3110
|
-
...ctx,
|
|
3111
|
-
config: {
|
|
3112
|
-
...ctx.config,
|
|
3113
|
-
model: modelId
|
|
3114
|
-
}
|
|
3115
|
-
};
|
|
3116
|
-
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
3117
|
-
home: opts?.home,
|
|
3118
|
-
writeGeneratedConfig: false
|
|
3119
|
-
});
|
|
3120
|
-
return {
|
|
3121
|
-
args: launch.args,
|
|
3122
|
-
env: launch.env,
|
|
3123
|
-
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
3124
|
-
};
|
|
3125
|
-
}
|
|
3355
|
+
toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
|
|
3126
3356
|
};
|
|
3127
3357
|
supportsStdinNotification = true;
|
|
3128
3358
|
mcpToolPrefix = "";
|
|
@@ -3176,7 +3406,6 @@ var KimiDriver = class {
|
|
|
3176
3406
|
}
|
|
3177
3407
|
}
|
|
3178
3408
|
}), "utf8");
|
|
3179
|
-
const launch = buildKimiLaunchOptions(ctx);
|
|
3180
3409
|
const args = [
|
|
3181
3410
|
"--wire",
|
|
3182
3411
|
"--yolo",
|
|
@@ -3185,15 +3414,14 @@ var KimiDriver = class {
|
|
|
3185
3414
|
"--mcp-config-file",
|
|
3186
3415
|
mcpConfigPath,
|
|
3187
3416
|
"--session",
|
|
3188
|
-
this.sessionId
|
|
3189
|
-
...launch.args
|
|
3417
|
+
this.sessionId
|
|
3190
3418
|
];
|
|
3191
3419
|
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3192
3420
|
args.push("--model", ctx.config.model);
|
|
3193
3421
|
}
|
|
3194
3422
|
const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
|
|
3195
|
-
const
|
|
3196
|
-
const proc = spawn6(
|
|
3423
|
+
const launch = resolveKimiSpawn(args);
|
|
3424
|
+
const proc = spawn6(launch.command, launch.args, {
|
|
3197
3425
|
cwd: ctx.workingDirectory,
|
|
3198
3426
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3199
3427
|
env: spawnEnv,
|
|
@@ -3201,7 +3429,7 @@ var KimiDriver = class {
|
|
|
3201
3429
|
// and has an 8191-character command-line limit. Kimi's official
|
|
3202
3430
|
// installer/uv entrypoint is an executable, so launch it directly and
|
|
3203
3431
|
// keep prompts on stdin / files instead of routing through cmd.exe.
|
|
3204
|
-
shell:
|
|
3432
|
+
shell: launch.shell
|
|
3205
3433
|
});
|
|
3206
3434
|
proc.stdin?.write(JSON.stringify({
|
|
3207
3435
|
jsonrpc: "2.0",
|
|
@@ -3317,9 +3545,14 @@ var KimiDriver = class {
|
|
|
3317
3545
|
return detectKimiModels();
|
|
3318
3546
|
}
|
|
3319
3547
|
};
|
|
3320
|
-
function detectKimiModels(home = os4.homedir()
|
|
3321
|
-
const
|
|
3322
|
-
|
|
3548
|
+
function detectKimiModels(home = os4.homedir()) {
|
|
3549
|
+
const configPath = path9.join(home, ".kimi", "config.toml");
|
|
3550
|
+
let raw;
|
|
3551
|
+
try {
|
|
3552
|
+
raw = readFileSync3(configPath, "utf8");
|
|
3553
|
+
} catch {
|
|
3554
|
+
return null;
|
|
3555
|
+
}
|
|
3323
3556
|
const models = [];
|
|
3324
3557
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
3325
3558
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -3580,7 +3813,7 @@ function detectOpenCodeModels(home = os5.homedir(), runCommand = runOpenCodeMode
|
|
|
3580
3813
|
}
|
|
3581
3814
|
function runOpenCodeModelsCommand(home) {
|
|
3582
3815
|
const result = spawnSync2("opencode", ["models"], {
|
|
3583
|
-
env:
|
|
3816
|
+
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3584
3817
|
encoding: "utf8",
|
|
3585
3818
|
timeout: 5e3
|
|
3586
3819
|
});
|
|
@@ -3738,297 +3971,6 @@ var OpenCodeDriver = class {
|
|
|
3738
3971
|
}
|
|
3739
3972
|
};
|
|
3740
3973
|
|
|
3741
|
-
// src/drivers/pi.ts
|
|
3742
|
-
import { spawn as spawn8 } from "child_process";
|
|
3743
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
3744
|
-
import path11 from "path";
|
|
3745
|
-
import { fileURLToPath } from "url";
|
|
3746
|
-
import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
|
|
3747
|
-
var CHAT_MCP_TOOL_PREFIX2 = "chat_";
|
|
3748
|
-
var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
|
|
3749
|
-
var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
|
|
3750
|
-
var MIN_SUPPORTED_PI_VERSION = "0.74.0";
|
|
3751
|
-
function parseSemver2(version) {
|
|
3752
|
-
const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
3753
|
-
if (!match) return null;
|
|
3754
|
-
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
3755
|
-
}
|
|
3756
|
-
function isSupportedPiVersion(version) {
|
|
3757
|
-
if (!version) return true;
|
|
3758
|
-
const actual = parseSemver2(version);
|
|
3759
|
-
const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
|
|
3760
|
-
if (!actual || !minimum) return true;
|
|
3761
|
-
for (let i = 0; i < 3; i += 1) {
|
|
3762
|
-
if (actual[i] > minimum[i]) return true;
|
|
3763
|
-
if (actual[i] < minimum[i]) return false;
|
|
3764
|
-
}
|
|
3765
|
-
return true;
|
|
3766
|
-
}
|
|
3767
|
-
function unsupportedPiVersionMessage(version) {
|
|
3768
|
-
if (!version || isSupportedPiVersion(version)) return null;
|
|
3769
|
-
return `Pi SDK ${version} is unsupported; requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION}. Upgrade the daemon Pi dependency before starting this runtime.`;
|
|
3770
|
-
}
|
|
3771
|
-
function probePi(version = PI_SDK_VERSION) {
|
|
3772
|
-
const unsupportedMessage = unsupportedPiVersionMessage(version);
|
|
3773
|
-
if (unsupportedMessage) {
|
|
3774
|
-
return {
|
|
3775
|
-
available: false,
|
|
3776
|
-
version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
|
|
3777
|
-
};
|
|
3778
|
-
}
|
|
3779
|
-
return { available: true, version };
|
|
3780
|
-
}
|
|
3781
|
-
function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
|
|
3782
|
-
const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
|
|
3783
|
-
const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
|
|
3784
|
-
if (existsSync8(sourceSibling)) return sourceSibling;
|
|
3785
|
-
const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
|
|
3786
|
-
if (existsSync8(bundledEntry)) return bundledEntry;
|
|
3787
|
-
return path11.join(moduleDir, "piSdkRunner.js");
|
|
3788
|
-
}
|
|
3789
|
-
function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
|
|
3790
|
-
if (runnerPath.endsWith(".ts")) {
|
|
3791
|
-
return [...process.execArgv, runnerPath];
|
|
3792
|
-
}
|
|
3793
|
-
return [runnerPath];
|
|
3794
|
-
}
|
|
3795
|
-
function buildPiLaunchOptions(ctx, opts = {}) {
|
|
3796
|
-
const command = opts.command ?? process.execPath;
|
|
3797
|
-
const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
|
|
3798
|
-
const sessionDir = path11.join(piDir, "sessions");
|
|
3799
|
-
mkdirSync4(sessionDir, { recursive: true });
|
|
3800
|
-
const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
3801
|
-
const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
|
|
3802
|
-
const agentDir = opts.agentDir ?? getAgentDir();
|
|
3803
|
-
const runnerConfigPath = path11.join(
|
|
3804
|
-
piDir,
|
|
3805
|
-
`sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
|
|
3806
|
-
);
|
|
3807
|
-
const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
|
|
3808
|
-
const runnerConfig = {
|
|
3809
|
-
cwd: ctx.workingDirectory,
|
|
3810
|
-
agentDir,
|
|
3811
|
-
sessionDir,
|
|
3812
|
-
sessionId: ctx.config.sessionId || null,
|
|
3813
|
-
standingPrompt: ctx.standingPrompt,
|
|
3814
|
-
prompt: turnPrompt,
|
|
3815
|
-
model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
|
|
3816
|
-
};
|
|
3817
|
-
writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
|
|
3818
|
-
`, { encoding: "utf8", mode: 384 });
|
|
3819
|
-
const args = [
|
|
3820
|
-
...buildPiSdkNodeArgs(runnerPath),
|
|
3821
|
-
"--config",
|
|
3822
|
-
runnerConfigPath
|
|
3823
|
-
];
|
|
3824
|
-
return {
|
|
3825
|
-
command,
|
|
3826
|
-
args,
|
|
3827
|
-
env: slock.spawnEnv,
|
|
3828
|
-
sessionDir,
|
|
3829
|
-
agentDir,
|
|
3830
|
-
runnerConfigPath,
|
|
3831
|
-
sdkVersion: PI_SDK_VERSION
|
|
3832
|
-
};
|
|
3833
|
-
}
|
|
3834
|
-
function isSystemFirstMessageTask2(message) {
|
|
3835
|
-
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
|
|
3836
|
-
}
|
|
3837
|
-
function buildPiSystemPrompt(config) {
|
|
3838
|
-
return buildCliTransportSystemPrompt(config, {
|
|
3839
|
-
toolPrefix: CHAT_MCP_TOOL_PREFIX2,
|
|
3840
|
-
extraCriticalRules: [
|
|
3841
|
-
"- Runtime Profile migration controls are not available in the Pi runtime yet. If asked to acknowledge a runtime migration, explain the blocker instead of inventing a command."
|
|
3842
|
-
],
|
|
3843
|
-
postStartupNotes: [
|
|
3844
|
-
"**Pi runtime note:** Slock launches you as a per-turn process. Complete the current wake using `slock` CLI commands, then stop; the daemon will restart you when new messages arrive."
|
|
3845
|
-
],
|
|
3846
|
-
includeStdinNotificationSection: false,
|
|
3847
|
-
messageNotificationStyle: "poll"
|
|
3848
|
-
});
|
|
3849
|
-
}
|
|
3850
|
-
function contentText(content) {
|
|
3851
|
-
if (!content) return "";
|
|
3852
|
-
const chunks = [];
|
|
3853
|
-
for (const item of content) {
|
|
3854
|
-
if (item.type === "text" && typeof item.text === "string") {
|
|
3855
|
-
chunks.push(item.text);
|
|
3856
|
-
}
|
|
3857
|
-
}
|
|
3858
|
-
return chunks.join("");
|
|
3859
|
-
}
|
|
3860
|
-
function apiKeyErrorMessage(line) {
|
|
3861
|
-
const trimmed = line.trim();
|
|
3862
|
-
if (!trimmed) return null;
|
|
3863
|
-
if (/no api key found/i.test(trimmed)) return trimmed;
|
|
3864
|
-
if (/api key.+required/i.test(trimmed)) return trimmed;
|
|
3865
|
-
if (/no models available/i.test(trimmed)) return trimmed;
|
|
3866
|
-
return null;
|
|
3867
|
-
}
|
|
3868
|
-
var PiDriver = class {
|
|
3869
|
-
id = "pi";
|
|
3870
|
-
lifecycle = {
|
|
3871
|
-
kind: "per_turn",
|
|
3872
|
-
start: "defer_until_concrete_message",
|
|
3873
|
-
exit: "terminate_on_turn_end",
|
|
3874
|
-
inFlightWake: "coalesce_into_pending"
|
|
3875
|
-
};
|
|
3876
|
-
communication = {
|
|
3877
|
-
chat: "slock_cli",
|
|
3878
|
-
runtimeControl: "none"
|
|
3879
|
-
};
|
|
3880
|
-
session = {
|
|
3881
|
-
recovery: "resume_or_fresh"
|
|
3882
|
-
};
|
|
3883
|
-
model = {
|
|
3884
|
-
detectedModelsVerifiedAs: "launchable",
|
|
3885
|
-
toLaunchSpec: (modelId, ctx) => {
|
|
3886
|
-
if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
|
|
3887
|
-
const launchCtx = {
|
|
3888
|
-
...ctx,
|
|
3889
|
-
config: {
|
|
3890
|
-
...ctx.config,
|
|
3891
|
-
model: modelId
|
|
3892
|
-
}
|
|
3893
|
-
};
|
|
3894
|
-
const launch = buildPiLaunchOptions(launchCtx);
|
|
3895
|
-
return {
|
|
3896
|
-
args: launch.args,
|
|
3897
|
-
env: launch.env,
|
|
3898
|
-
configFiles: [launch.runnerConfigPath],
|
|
3899
|
-
params: {
|
|
3900
|
-
agentDir: launch.agentDir,
|
|
3901
|
-
sessionDir: launch.sessionDir,
|
|
3902
|
-
sdkVersion: launch.sdkVersion,
|
|
3903
|
-
resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
|
|
3904
|
-
}
|
|
3905
|
-
};
|
|
3906
|
-
}
|
|
3907
|
-
};
|
|
3908
|
-
supportsStdinNotification = false;
|
|
3909
|
-
mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
|
|
3910
|
-
busyDeliveryMode = "none";
|
|
3911
|
-
terminateProcessOnTurnEnd = true;
|
|
3912
|
-
deferSpawnUntilMessage = true;
|
|
3913
|
-
usesSlockCliForCommunication = true;
|
|
3914
|
-
sessionId = null;
|
|
3915
|
-
sessionAnnounced = false;
|
|
3916
|
-
apiKeyErrorAnnounced = false;
|
|
3917
|
-
turnEnded = false;
|
|
3918
|
-
assistantTextByMessageId = /* @__PURE__ */ new Map();
|
|
3919
|
-
shouldDeferWakeMessage(message) {
|
|
3920
|
-
return isSystemFirstMessageTask2(message);
|
|
3921
|
-
}
|
|
3922
|
-
probe() {
|
|
3923
|
-
return probePi();
|
|
3924
|
-
}
|
|
3925
|
-
async detectModels() {
|
|
3926
|
-
return null;
|
|
3927
|
-
}
|
|
3928
|
-
spawn(ctx) {
|
|
3929
|
-
this.sessionId = ctx.config.sessionId || null;
|
|
3930
|
-
this.sessionAnnounced = false;
|
|
3931
|
-
this.apiKeyErrorAnnounced = false;
|
|
3932
|
-
this.turnEnded = false;
|
|
3933
|
-
this.assistantTextByMessageId.clear();
|
|
3934
|
-
const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
|
|
3935
|
-
if (unsupportedMessage) throw new Error(unsupportedMessage);
|
|
3936
|
-
const launch = buildPiLaunchOptions(ctx);
|
|
3937
|
-
const proc = spawn8(launch.command, launch.args, {
|
|
3938
|
-
cwd: ctx.workingDirectory,
|
|
3939
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
3940
|
-
env: launch.env,
|
|
3941
|
-
shell: false
|
|
3942
|
-
});
|
|
3943
|
-
proc.stdin?.end();
|
|
3944
|
-
return { process: proc };
|
|
3945
|
-
}
|
|
3946
|
-
parseLine(line) {
|
|
3947
|
-
let event;
|
|
3948
|
-
try {
|
|
3949
|
-
event = JSON.parse(line);
|
|
3950
|
-
} catch {
|
|
3951
|
-
if (this.apiKeyErrorAnnounced) return [];
|
|
3952
|
-
const message = apiKeyErrorMessage(line);
|
|
3953
|
-
if (!message) return [];
|
|
3954
|
-
this.apiKeyErrorAnnounced = true;
|
|
3955
|
-
this.turnEnded = true;
|
|
3956
|
-
return [
|
|
3957
|
-
{ kind: "error", message },
|
|
3958
|
-
{ kind: "turn_end", sessionId: this.sessionId || void 0 }
|
|
3959
|
-
];
|
|
3960
|
-
}
|
|
3961
|
-
const events = [];
|
|
3962
|
-
if (event.type === "session" && event.id) {
|
|
3963
|
-
this.sessionId = event.id;
|
|
3964
|
-
}
|
|
3965
|
-
if (!this.sessionAnnounced && this.sessionId) {
|
|
3966
|
-
events.push({ kind: "session_init", sessionId: this.sessionId });
|
|
3967
|
-
this.sessionAnnounced = true;
|
|
3968
|
-
}
|
|
3969
|
-
switch (event.type) {
|
|
3970
|
-
case "agent_start":
|
|
3971
|
-
case "turn_start":
|
|
3972
|
-
events.push({ kind: "thinking", text: "" });
|
|
3973
|
-
break;
|
|
3974
|
-
case "message_update":
|
|
3975
|
-
case "message_end":
|
|
3976
|
-
if (event.message?.role === "assistant") {
|
|
3977
|
-
const key = event.message.id || "current";
|
|
3978
|
-
const currentText = contentText(event.message.content);
|
|
3979
|
-
const previousText = this.assistantTextByMessageId.get(key) ?? "";
|
|
3980
|
-
if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
|
|
3981
|
-
events.push({ kind: "text", text: currentText.slice(previousText.length) });
|
|
3982
|
-
} else if (currentText && currentText !== previousText) {
|
|
3983
|
-
events.push({ kind: "text", text: currentText });
|
|
3984
|
-
}
|
|
3985
|
-
this.assistantTextByMessageId.set(key, currentText);
|
|
3986
|
-
if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
|
|
3987
|
-
events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
|
|
3988
|
-
}
|
|
3989
|
-
}
|
|
3990
|
-
break;
|
|
3991
|
-
case "tool_execution_start":
|
|
3992
|
-
events.push({
|
|
3993
|
-
kind: "tool_call",
|
|
3994
|
-
name: event.toolName || "unknown_tool",
|
|
3995
|
-
input: event.args
|
|
3996
|
-
});
|
|
3997
|
-
break;
|
|
3998
|
-
case "tool_execution_end":
|
|
3999
|
-
events.push({
|
|
4000
|
-
kind: "tool_output",
|
|
4001
|
-
name: event.toolName || "unknown_tool"
|
|
4002
|
-
});
|
|
4003
|
-
if (event.isError) {
|
|
4004
|
-
events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
|
|
4005
|
-
}
|
|
4006
|
-
break;
|
|
4007
|
-
case "compaction_start":
|
|
4008
|
-
events.push({ kind: "compaction_started" });
|
|
4009
|
-
break;
|
|
4010
|
-
case "compaction_end":
|
|
4011
|
-
events.push({ kind: "compaction_finished" });
|
|
4012
|
-
if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
|
|
4013
|
-
break;
|
|
4014
|
-
case "turn_end":
|
|
4015
|
-
case "agent_end":
|
|
4016
|
-
if (!this.turnEnded) {
|
|
4017
|
-
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
4018
|
-
this.turnEnded = true;
|
|
4019
|
-
}
|
|
4020
|
-
break;
|
|
4021
|
-
}
|
|
4022
|
-
return events;
|
|
4023
|
-
}
|
|
4024
|
-
encodeStdinMessage(_text, _sessionId, _opts) {
|
|
4025
|
-
return null;
|
|
4026
|
-
}
|
|
4027
|
-
buildSystemPrompt(config, _agentId) {
|
|
4028
|
-
return buildPiSystemPrompt(config);
|
|
4029
|
-
}
|
|
4030
|
-
};
|
|
4031
|
-
|
|
4032
3974
|
// src/drivers/index.ts
|
|
4033
3975
|
var driverFactories = {
|
|
4034
3976
|
claude: () => new ClaudeDriver(),
|
|
@@ -4037,8 +3979,7 @@ var driverFactories = {
|
|
|
4037
3979
|
cursor: () => new CursorDriver(),
|
|
4038
3980
|
gemini: () => new GeminiDriver(),
|
|
4039
3981
|
kimi: () => new KimiDriver(),
|
|
4040
|
-
opencode: () => new OpenCodeDriver()
|
|
4041
|
-
pi: () => new PiDriver()
|
|
3982
|
+
opencode: () => new OpenCodeDriver()
|
|
4042
3983
|
};
|
|
4043
3984
|
function getDriver(runtimeId) {
|
|
4044
3985
|
const createDriver = driverFactories[runtimeId];
|
|
@@ -4051,7 +3992,7 @@ function getDriver(runtimeId) {
|
|
|
4051
3992
|
|
|
4052
3993
|
// src/workspaces.ts
|
|
4053
3994
|
import { readdir, rm, stat } from "fs/promises";
|
|
4054
|
-
import
|
|
3995
|
+
import path11 from "path";
|
|
4055
3996
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
4056
3997
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
4057
3998
|
}
|
|
@@ -4059,7 +4000,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
4059
4000
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
4060
4001
|
return null;
|
|
4061
4002
|
}
|
|
4062
|
-
return
|
|
4003
|
+
return path11.join(dataDir, directoryName);
|
|
4063
4004
|
}
|
|
4064
4005
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
4065
4006
|
return {
|
|
@@ -4108,7 +4049,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
4108
4049
|
return summary;
|
|
4109
4050
|
}
|
|
4110
4051
|
const childSummaries = await Promise.all(
|
|
4111
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
4052
|
+
entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
|
|
4112
4053
|
);
|
|
4113
4054
|
for (const childSummary of childSummaries) {
|
|
4114
4055
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -4127,7 +4068,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
4127
4068
|
if (!entry.isDirectory()) {
|
|
4128
4069
|
return null;
|
|
4129
4070
|
}
|
|
4130
|
-
const dirPath =
|
|
4071
|
+
const dirPath = path11.join(dataDir, entry.name);
|
|
4131
4072
|
try {
|
|
4132
4073
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
4133
4074
|
return {
|
|
@@ -4240,6 +4181,8 @@ function redactUrlQuery(value) {
|
|
|
4240
4181
|
// src/agentProcessManager.ts
|
|
4241
4182
|
var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
|
|
4242
4183
|
var DEFAULT_AGENT_START_INTERVAL_MS = 500;
|
|
4184
|
+
var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS = 3;
|
|
4185
|
+
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS = 250;
|
|
4243
4186
|
var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
|
|
4244
4187
|
var WORKSPACE_IMAGE_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
|
|
4245
4188
|
var WORKSPACE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -4284,6 +4227,41 @@ function readNonNegativeIntegerEnv(name, fallback) {
|
|
|
4284
4227
|
if (!Number.isFinite(parsed) || parsed < 0) return fallback;
|
|
4285
4228
|
return Math.floor(parsed);
|
|
4286
4229
|
}
|
|
4230
|
+
var RunnerCredentialMintError = class extends Error {
|
|
4231
|
+
code;
|
|
4232
|
+
retryable;
|
|
4233
|
+
status;
|
|
4234
|
+
constructor(message, opts) {
|
|
4235
|
+
super(message);
|
|
4236
|
+
this.name = "RunnerCredentialMintError";
|
|
4237
|
+
this.code = opts.code;
|
|
4238
|
+
this.retryable = opts.retryable ?? false;
|
|
4239
|
+
this.status = opts.status;
|
|
4240
|
+
}
|
|
4241
|
+
};
|
|
4242
|
+
function isRetryableMintHttpFailure(status, code) {
|
|
4243
|
+
if (code === "experimental_surface_disabled") return false;
|
|
4244
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
4245
|
+
}
|
|
4246
|
+
function runnerCredentialErrorDetail(error) {
|
|
4247
|
+
if (error instanceof RunnerCredentialMintError) {
|
|
4248
|
+
return {
|
|
4249
|
+
message: error.message,
|
|
4250
|
+
code: error.code,
|
|
4251
|
+
retryable: error.retryable,
|
|
4252
|
+
status: error.status
|
|
4253
|
+
};
|
|
4254
|
+
}
|
|
4255
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4256
|
+
return {
|
|
4257
|
+
message,
|
|
4258
|
+
code: "runner_credential_mint_network_error",
|
|
4259
|
+
retryable: true
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
async function waitForRunnerCredentialRetry() {
|
|
4263
|
+
await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS));
|
|
4264
|
+
}
|
|
4287
4265
|
function stalledRecoverySigtermTimeoutMs() {
|
|
4288
4266
|
return readNonNegativeIntegerEnv(
|
|
4289
4267
|
"SLOCK_DAEMON_STALLED_RECOVERY_SIGTERM_TIMEOUT_MS",
|
|
@@ -4312,6 +4290,22 @@ function formatMessageTarget(message) {
|
|
|
4312
4290
|
}
|
|
4313
4291
|
return `#${message.channel_name}`;
|
|
4314
4292
|
}
|
|
4293
|
+
function formatVisibleMessageTarget(message) {
|
|
4294
|
+
if (message.channel_type === "thread" && message.parent_channel_name && message.channel_name) {
|
|
4295
|
+
const shortId = getMessageShortId(String(message.channel_name));
|
|
4296
|
+
if (message.parent_channel_type === "dm") {
|
|
4297
|
+
return `dm:@${message.parent_channel_name}:${shortId}`;
|
|
4298
|
+
}
|
|
4299
|
+
return `#${message.parent_channel_name}:${shortId}`;
|
|
4300
|
+
}
|
|
4301
|
+
if (message.channel_type === "dm" && message.channel_name) {
|
|
4302
|
+
return `dm:@${message.channel_name}`;
|
|
4303
|
+
}
|
|
4304
|
+
if (message.channel_name) {
|
|
4305
|
+
return `#${message.channel_name}`;
|
|
4306
|
+
}
|
|
4307
|
+
return null;
|
|
4308
|
+
}
|
|
4315
4309
|
function getMessageShortId(messageId) {
|
|
4316
4310
|
return messageId.startsWith("thread-") ? messageId.slice(7) : messageId.slice(0, 8);
|
|
4317
4311
|
}
|
|
@@ -4331,12 +4325,12 @@ function findSessionJsonl(root, predicate) {
|
|
|
4331
4325
|
for (const entry of entries) {
|
|
4332
4326
|
if (++visited > maxEntries) return null;
|
|
4333
4327
|
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
4334
|
-
return
|
|
4328
|
+
return path12.join(dir, entry.name);
|
|
4335
4329
|
}
|
|
4336
4330
|
for (const entry of entries) {
|
|
4337
4331
|
if (++visited > maxEntries) return null;
|
|
4338
4332
|
if (!entry.isDirectory()) continue;
|
|
4339
|
-
const found = visit(
|
|
4333
|
+
const found = visit(path12.join(dir, entry.name), depth - 1);
|
|
4340
4334
|
if (found) return found;
|
|
4341
4335
|
}
|
|
4342
4336
|
return null;
|
|
@@ -4349,10 +4343,10 @@ function safeSessionFilename(value) {
|
|
|
4349
4343
|
}
|
|
4350
4344
|
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
4351
4345
|
try {
|
|
4352
|
-
const dir =
|
|
4353
|
-
|
|
4354
|
-
const filePath =
|
|
4355
|
-
|
|
4346
|
+
const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
|
|
4347
|
+
mkdirSync4(dir, { recursive: true });
|
|
4348
|
+
const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
4349
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
4356
4350
|
type: "runtime_session_handoff",
|
|
4357
4351
|
runtime,
|
|
4358
4352
|
sessionId,
|
|
@@ -4371,7 +4365,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
4371
4365
|
}
|
|
4372
4366
|
}
|
|
4373
4367
|
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
|
|
4374
|
-
const directPath =
|
|
4368
|
+
const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
|
|
4375
4369
|
if (directPath) {
|
|
4376
4370
|
try {
|
|
4377
4371
|
if (statSync2(directPath).isFile()) {
|
|
@@ -4380,7 +4374,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
|
|
|
4380
4374
|
} catch {
|
|
4381
4375
|
}
|
|
4382
4376
|
}
|
|
4383
|
-
const resolvedPath = runtime === "claude" ? findSessionJsonl(
|
|
4377
|
+
const resolvedPath = runtime === "claude" ? findSessionJsonl(path12.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path12.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
|
|
4384
4378
|
if (!resolvedPath && fallbackDir) {
|
|
4385
4379
|
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
4386
4380
|
if (fallback) return fallback;
|
|
@@ -4932,6 +4926,14 @@ function createRuntimeTraceCounters() {
|
|
|
4932
4926
|
thinkingEvents: 0
|
|
4933
4927
|
};
|
|
4934
4928
|
}
|
|
4929
|
+
function cleanupAgentCredentialProxy(agentId, launchId) {
|
|
4930
|
+
unregisterAgentCredentialProxyForLaunch({ agentId, launchId });
|
|
4931
|
+
}
|
|
4932
|
+
function stripManagedRunnerCredential(config) {
|
|
4933
|
+
if (!config.agentCredentialKey && !config.agentCredentialId) return config;
|
|
4934
|
+
const { agentCredentialKey: _agentCredentialKey, agentCredentialId: _agentCredentialId, ...rest } = config;
|
|
4935
|
+
return rest;
|
|
4936
|
+
}
|
|
4935
4937
|
function createGatedSteeringState() {
|
|
4936
4938
|
return {
|
|
4937
4939
|
phase: "idle",
|
|
@@ -5263,6 +5265,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
5263
5265
|
tracer;
|
|
5264
5266
|
deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
|
|
5265
5267
|
processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
|
|
5268
|
+
agentVisibleBoundaries = /* @__PURE__ */ new Map();
|
|
5266
5269
|
constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
|
|
5267
5270
|
this.chatBridgePath = chatBridgePath;
|
|
5268
5271
|
this.slockCliPath = opts.slockCliPath ?? "";
|
|
@@ -5290,6 +5293,97 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
5290
5293
|
setTracer(tracer) {
|
|
5291
5294
|
this.tracer = tracer;
|
|
5292
5295
|
}
|
|
5296
|
+
visibleBoundaryMap(agentId) {
|
|
5297
|
+
let map = this.agentVisibleBoundaries.get(agentId);
|
|
5298
|
+
if (!map) {
|
|
5299
|
+
map = /* @__PURE__ */ new Map();
|
|
5300
|
+
this.agentVisibleBoundaries.set(agentId, map);
|
|
5301
|
+
}
|
|
5302
|
+
return map;
|
|
5303
|
+
}
|
|
5304
|
+
getVisibleBoundary(agentId, target) {
|
|
5305
|
+
return this.agentVisibleBoundaries.get(agentId)?.get(target);
|
|
5306
|
+
}
|
|
5307
|
+
pendingVisibleMessages(agentId, target) {
|
|
5308
|
+
const collect = (messages) => (messages ?? []).filter((message) => formatMessageTarget(message) === target && typeof message.seq === "number" && message.seq > 0);
|
|
5309
|
+
return [
|
|
5310
|
+
...collect(this.agents.get(agentId)?.inbox),
|
|
5311
|
+
...collect(this.startingInboxes.get(agentId))
|
|
5312
|
+
];
|
|
5313
|
+
}
|
|
5314
|
+
/**
|
|
5315
|
+
* Single-inbox consume point for messages that have been rendered into the
|
|
5316
|
+
* agent-visible input surface. Daemon pending inbox is only a runner cache:
|
|
5317
|
+
* once a message is visible through wake/stdin/check/read/held/preflight, the
|
|
5318
|
+
* same seq must not be injected later through the local pending queue.
|
|
5319
|
+
*/
|
|
5320
|
+
consumeVisibleMessages(agentId, input) {
|
|
5321
|
+
if (input.messages.length === 0 && (!input.target || typeof input.boundarySeq !== "number")) return;
|
|
5322
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
5323
|
+
const ensureBucket = (target) => {
|
|
5324
|
+
let bucket = byTarget.get(target);
|
|
5325
|
+
if (!bucket) {
|
|
5326
|
+
bucket = { maxSeq: 0, boundarySeq: 0, seqs: /* @__PURE__ */ new Set(), ids: /* @__PURE__ */ new Set() };
|
|
5327
|
+
byTarget.set(target, bucket);
|
|
5328
|
+
}
|
|
5329
|
+
return bucket;
|
|
5330
|
+
};
|
|
5331
|
+
for (const message of input.messages) {
|
|
5332
|
+
const seq = Number(message.seq ?? 0);
|
|
5333
|
+
if (!Number.isFinite(seq) || seq <= 0) continue;
|
|
5334
|
+
const target = input.target ?? formatVisibleMessageTarget(message);
|
|
5335
|
+
if (!target) continue;
|
|
5336
|
+
const bucket = ensureBucket(target);
|
|
5337
|
+
bucket.maxSeq = Math.max(bucket.maxSeq, Math.floor(seq));
|
|
5338
|
+
bucket.seqs.add(Math.floor(seq));
|
|
5339
|
+
const id = typeof message.id === "string" ? message.id : typeof message.message_id === "string" ? message.message_id : null;
|
|
5340
|
+
if (id) bucket.ids.add(id);
|
|
5341
|
+
}
|
|
5342
|
+
if (input.target && typeof input.boundarySeq === "number" && Number.isFinite(input.boundarySeq) && input.boundarySeq > 0) {
|
|
5343
|
+
const bucket = ensureBucket(input.target);
|
|
5344
|
+
bucket.boundarySeq = Math.max(bucket.boundarySeq, Math.floor(input.boundarySeq));
|
|
5345
|
+
}
|
|
5346
|
+
if (byTarget.size === 0) return;
|
|
5347
|
+
const boundaryMap = this.visibleBoundaryMap(agentId);
|
|
5348
|
+
for (const [target, bucket] of byTarget) {
|
|
5349
|
+
const highWaterSeq = Math.max(bucket.maxSeq, bucket.boundarySeq);
|
|
5350
|
+
const previous = boundaryMap.get(target) ?? 0;
|
|
5351
|
+
boundaryMap.set(target, Math.max(previous, highWaterSeq));
|
|
5352
|
+
}
|
|
5353
|
+
const suppress = (messages) => {
|
|
5354
|
+
if (!messages || messages.length === 0) return 0;
|
|
5355
|
+
let removed = 0;
|
|
5356
|
+
const retained = messages.filter((message) => {
|
|
5357
|
+
const target = formatMessageTarget(message);
|
|
5358
|
+
const bucket = byTarget.get(target);
|
|
5359
|
+
if (!bucket) return true;
|
|
5360
|
+
const seq = typeof message.seq === "number" ? Math.floor(message.seq) : 0;
|
|
5361
|
+
const id = message.message_id ?? "";
|
|
5362
|
+
const matched = seq > 0 && bucket.boundarySeq > 0 && seq <= bucket.boundarySeq || seq > 0 && bucket.seqs.has(seq) || id.length > 0 && bucket.ids.has(id);
|
|
5363
|
+
if (matched) removed += 1;
|
|
5364
|
+
return !matched;
|
|
5365
|
+
});
|
|
5366
|
+
messages.splice(0, messages.length, ...retained);
|
|
5367
|
+
return removed;
|
|
5368
|
+
};
|
|
5369
|
+
const active = this.agents.get(agentId);
|
|
5370
|
+
const removedActive = suppress(active?.inbox);
|
|
5371
|
+
const removedStarting = suppress(this.startingInboxes.get(agentId));
|
|
5372
|
+
this.recordDaemonTrace("daemon.agent.inbox.visible_consumed", {
|
|
5373
|
+
agentId,
|
|
5374
|
+
source: input.source,
|
|
5375
|
+
targets: [...byTarget.keys()],
|
|
5376
|
+
messages_count: input.messages.length,
|
|
5377
|
+
suppressed_pending_count: removedActive + removedStarting
|
|
5378
|
+
});
|
|
5379
|
+
}
|
|
5380
|
+
createAgentProxyInboxCoordinator(agentId) {
|
|
5381
|
+
return {
|
|
5382
|
+
getBoundary: (target) => this.getVisibleBoundary(agentId, target),
|
|
5383
|
+
getPendingMessages: (target) => this.pendingVisibleMessages(agentId, target),
|
|
5384
|
+
consumeVisibleMessages: (input) => this.consumeVisibleMessages(agentId, input)
|
|
5385
|
+
};
|
|
5386
|
+
}
|
|
5293
5387
|
recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
|
|
5294
5388
|
const span = this.tracer.startSpan(name, {
|
|
5295
5389
|
parent: parseTraceparent(parentTraceparent),
|
|
@@ -5514,26 +5608,26 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
5514
5608
|
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
5515
5609
|
try {
|
|
5516
5610
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
5517
|
-
const agentDataDir =
|
|
5611
|
+
const agentDataDir = path12.join(this.dataDir, agentId);
|
|
5518
5612
|
await mkdir(agentDataDir, { recursive: true });
|
|
5519
5613
|
const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
5520
|
-
const memoryMdPath =
|
|
5614
|
+
const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
|
|
5521
5615
|
try {
|
|
5522
5616
|
await access(memoryMdPath);
|
|
5523
5617
|
} catch {
|
|
5524
5618
|
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
5525
5619
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
5526
5620
|
}
|
|
5527
|
-
const notesDir =
|
|
5621
|
+
const notesDir = path12.join(agentDataDir, "notes");
|
|
5528
5622
|
await mkdir(notesDir, { recursive: true });
|
|
5529
5623
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
5530
5624
|
const seedFiles = buildOnboardingSeedFiles();
|
|
5531
5625
|
for (const { relativePath, content } of seedFiles) {
|
|
5532
|
-
const fullPath =
|
|
5626
|
+
const fullPath = path12.join(agentDataDir, relativePath);
|
|
5533
5627
|
try {
|
|
5534
5628
|
await access(fullPath);
|
|
5535
5629
|
} catch {
|
|
5536
|
-
await mkdir(
|
|
5630
|
+
await mkdir(path12.dirname(fullPath), { recursive: true });
|
|
5537
5631
|
await writeFile(fullPath, content);
|
|
5538
5632
|
}
|
|
5539
5633
|
}
|
|
@@ -5641,7 +5735,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5641
5735
|
chatBridgePath: this.chatBridgePath,
|
|
5642
5736
|
slockCliPath: this.slockCliPath,
|
|
5643
5737
|
daemonApiKey: this.daemonApiKey,
|
|
5644
|
-
launchId: launchId || null
|
|
5738
|
+
launchId: launchId || null,
|
|
5739
|
+
agentCredentialProxyInboxCoordinator: this.createAgentProxyInboxCoordinator(agentId)
|
|
5645
5740
|
});
|
|
5646
5741
|
this.recordDaemonTrace("daemon.agent.spawn.created", {
|
|
5647
5742
|
...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
@@ -5697,6 +5792,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5697
5792
|
this.ackInjectedRuntimeProfileControl(agentId, config.runtimeProfileControl, agentProcess.launchId);
|
|
5698
5793
|
}
|
|
5699
5794
|
if (wakeMessage) {
|
|
5795
|
+
this.consumeVisibleMessages(agentId, { messages: [wakeMessage], source: "spawn_wake_message" });
|
|
5700
5796
|
this.ackInjectedRuntimeProfileMessages(agentId, [wakeMessage], agentProcess.launchId);
|
|
5701
5797
|
}
|
|
5702
5798
|
let buffer = "";
|
|
@@ -5790,11 +5886,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5790
5886
|
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
|
|
5791
5887
|
});
|
|
5792
5888
|
this.interruptCompactionIfActive(agentId);
|
|
5889
|
+
cleanupAgentCredentialProxy(agentId, ap.launchId);
|
|
5890
|
+
this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
|
|
5793
5891
|
this.agents.delete(agentId);
|
|
5794
5892
|
if (missingResumeSession) {
|
|
5795
5893
|
const staleSessionId = ap.sessionId;
|
|
5796
5894
|
const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : "Claude";
|
|
5797
|
-
const restartConfig = { ...ap.config, sessionId: null };
|
|
5895
|
+
const restartConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: null };
|
|
5798
5896
|
logger.warn(
|
|
5799
5897
|
`[Agent ${agentId}] Stored ${runtimeLabel} session ${staleSessionId} is unavailable locally; falling back to cold start`
|
|
5800
5898
|
);
|
|
@@ -5831,7 +5929,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5831
5929
|
const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
|
|
5832
5930
|
if (queuedWakeMessage) {
|
|
5833
5931
|
logger.info(`[Agent ${agentId}] Turn completed; restarting immediately for queued message`);
|
|
5834
|
-
const nextConfig = { ...ap.config, sessionId: ap.sessionId };
|
|
5932
|
+
const nextConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId };
|
|
5835
5933
|
this.idleAgentConfigs.set(agentId, {
|
|
5836
5934
|
config: nextConfig,
|
|
5837
5935
|
sessionId: ap.sessionId,
|
|
@@ -5841,6 +5939,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5841
5939
|
this.idleAgentConfigs.delete(agentId);
|
|
5842
5940
|
this.startAgent(agentId, nextConfig, queuedWakeMessage, unreadSummary2, void 0, ap.launchId || void 0).catch((err) => {
|
|
5843
5941
|
logger.error(`[Agent ${agentId}] Failed to continue with queued message`, err);
|
|
5942
|
+
if (this.reportRunnerCredentialMintFailure(agentId, err, ap.launchId, "queued_continuation")) {
|
|
5943
|
+
return;
|
|
5944
|
+
}
|
|
5844
5945
|
this.idleAgentConfigs.set(agentId, {
|
|
5845
5946
|
config: nextConfig,
|
|
5846
5947
|
sessionId: ap.sessionId,
|
|
@@ -5851,7 +5952,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5851
5952
|
return;
|
|
5852
5953
|
}
|
|
5853
5954
|
this.idleAgentConfigs.set(agentId, {
|
|
5854
|
-
config: { ...ap.config, sessionId: ap.sessionId },
|
|
5955
|
+
config: { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId },
|
|
5855
5956
|
sessionId: ap.sessionId,
|
|
5856
5957
|
launchId: ap.launchId
|
|
5857
5958
|
});
|
|
@@ -5887,27 +5988,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5887
5988
|
}
|
|
5888
5989
|
async buildSpawnConfig(agentId, config) {
|
|
5889
5990
|
const baseConfig = config.serverUrl === this.serverUrl ? config : { ...config, serverUrl: this.serverUrl };
|
|
5991
|
+
const runnerConfig = await this.ensureManagedRunnerCredential(agentId, baseConfig);
|
|
5890
5992
|
if (!this.defaultAgentEnvVarsProvider) {
|
|
5891
|
-
return
|
|
5993
|
+
return runnerConfig;
|
|
5892
5994
|
}
|
|
5893
5995
|
try {
|
|
5894
5996
|
const defaultEnvVars = await this.defaultAgentEnvVarsProvider({
|
|
5895
|
-
runtime:
|
|
5896
|
-
model:
|
|
5897
|
-
envVars:
|
|
5997
|
+
runtime: runnerConfig.runtime,
|
|
5998
|
+
model: runnerConfig.model,
|
|
5999
|
+
envVars: runnerConfig.envVars
|
|
5898
6000
|
});
|
|
5899
6001
|
if (!defaultEnvVars || Object.keys(defaultEnvVars).length === 0) {
|
|
5900
|
-
return
|
|
6002
|
+
return runnerConfig;
|
|
5901
6003
|
}
|
|
5902
6004
|
const mergedEnvVars = {
|
|
5903
6005
|
...defaultEnvVars,
|
|
5904
|
-
...
|
|
6006
|
+
...runnerConfig.envVars ?? {}
|
|
5905
6007
|
};
|
|
5906
|
-
if (this.sameEnvVars(mergedEnvVars,
|
|
5907
|
-
return
|
|
6008
|
+
if (this.sameEnvVars(mergedEnvVars, runnerConfig.envVars)) {
|
|
6009
|
+
return runnerConfig;
|
|
5908
6010
|
}
|
|
5909
6011
|
return {
|
|
5910
|
-
...
|
|
6012
|
+
...runnerConfig,
|
|
5911
6013
|
envVars: mergedEnvVars
|
|
5912
6014
|
};
|
|
5913
6015
|
} catch (error) {
|
|
@@ -5915,8 +6017,130 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5915
6017
|
logger.warn(
|
|
5916
6018
|
`[Agent ${agentId}] Failed to resolve default runtime env vars \u2014 continuing without machine-level defaults (${reason})`
|
|
5917
6019
|
);
|
|
5918
|
-
return
|
|
6020
|
+
return runnerConfig;
|
|
6021
|
+
}
|
|
6022
|
+
}
|
|
6023
|
+
async requestManagedRunnerCredentialOnce(agentId, config) {
|
|
6024
|
+
const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.serverUrl);
|
|
6025
|
+
const res = await fetch(url, {
|
|
6026
|
+
method: "POST",
|
|
6027
|
+
headers: {
|
|
6028
|
+
Authorization: `Bearer ${this.daemonApiKey}`,
|
|
6029
|
+
"Content-Type": "application/json",
|
|
6030
|
+
"X-Slock-Client": "daemon-server-session-worker"
|
|
6031
|
+
},
|
|
6032
|
+
body: JSON.stringify({
|
|
6033
|
+
scopes: ["send", "read", "mentions", "tasks", "reactions", "server", "channels"],
|
|
6034
|
+
name: `runner:${config.runtime}:${agentId.slice(0, 8)}`
|
|
6035
|
+
})
|
|
6036
|
+
});
|
|
6037
|
+
if (!res.ok) {
|
|
6038
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
6039
|
+
let detail = `HTTP ${res.status}`;
|
|
6040
|
+
let code = null;
|
|
6041
|
+
if (contentType.includes("application/json")) {
|
|
6042
|
+
const body2 = await res.json().catch(() => null);
|
|
6043
|
+
const error = typeof body2?.error === "string" ? body2.error : null;
|
|
6044
|
+
code = typeof body2?.code === "string" ? body2.code : null;
|
|
6045
|
+
detail = [detail, code, error].filter(Boolean).join(" ");
|
|
6046
|
+
}
|
|
6047
|
+
throw new RunnerCredentialMintError(detail, {
|
|
6048
|
+
code: code ?? "runner_credential_mint_http_error",
|
|
6049
|
+
retryable: isRetryableMintHttpFailure(res.status, code),
|
|
6050
|
+
status: res.status
|
|
6051
|
+
});
|
|
6052
|
+
}
|
|
6053
|
+
const body = await res.json().catch(() => null);
|
|
6054
|
+
if (typeof body?.apiKey !== "string" || !body.apiKey.startsWith("sk_agent_")) {
|
|
6055
|
+
throw new RunnerCredentialMintError("invalid_agent_credential_payload", {
|
|
6056
|
+
code: "invalid_agent_credential_payload"
|
|
6057
|
+
});
|
|
6058
|
+
}
|
|
6059
|
+
return {
|
|
6060
|
+
apiKey: body.apiKey,
|
|
6061
|
+
credentialId: typeof body.credentialId === "string" ? body.credentialId : null
|
|
6062
|
+
};
|
|
6063
|
+
}
|
|
6064
|
+
async ensureManagedRunnerCredential(agentId, config) {
|
|
6065
|
+
if (config.agentCredentialKey) return config;
|
|
6066
|
+
if (process.env.SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED === "1") {
|
|
6067
|
+
throw new RunnerCredentialMintError("runner credential mint is disabled by SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED", {
|
|
6068
|
+
code: "runner_credentials_disabled"
|
|
6069
|
+
});
|
|
6070
|
+
}
|
|
6071
|
+
let lastError = null;
|
|
6072
|
+
for (let attempt = 1; attempt <= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS; attempt += 1) {
|
|
6073
|
+
try {
|
|
6074
|
+
const credential = await this.requestManagedRunnerCredentialOnce(agentId, config);
|
|
6075
|
+
return {
|
|
6076
|
+
...config,
|
|
6077
|
+
agentCredentialKey: credential.apiKey,
|
|
6078
|
+
agentCredentialId: credential.credentialId
|
|
6079
|
+
};
|
|
6080
|
+
} catch (err) {
|
|
6081
|
+
lastError = err;
|
|
6082
|
+
const detail2 = runnerCredentialErrorDetail(err);
|
|
6083
|
+
this.recordDaemonTrace("daemon.runner_credential_mint.retry", {
|
|
6084
|
+
agentId,
|
|
6085
|
+
runtime: config.runtime,
|
|
6086
|
+
attempt,
|
|
6087
|
+
max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS,
|
|
6088
|
+
status: detail2.status,
|
|
6089
|
+
code: detail2.code,
|
|
6090
|
+
reason: detail2.message,
|
|
6091
|
+
retryable: detail2.retryable
|
|
6092
|
+
}, detail2.retryable && attempt < RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS ? "ok" : "error");
|
|
6093
|
+
if (!detail2.retryable || attempt >= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS) break;
|
|
6094
|
+
await waitForRunnerCredentialRetry();
|
|
6095
|
+
}
|
|
5919
6096
|
}
|
|
6097
|
+
const detail = runnerCredentialErrorDetail(lastError);
|
|
6098
|
+
this.recordDaemonTrace("daemon.runner_credential_mint.failed", {
|
|
6099
|
+
agentId,
|
|
6100
|
+
runtime: config.runtime,
|
|
6101
|
+
status: detail.status,
|
|
6102
|
+
code: detail.code,
|
|
6103
|
+
reason: detail.message,
|
|
6104
|
+
retryable: detail.retryable,
|
|
6105
|
+
max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS
|
|
6106
|
+
}, "error");
|
|
6107
|
+
throw new RunnerCredentialMintError(
|
|
6108
|
+
`runner_credential_mint_failed: ${detail.message}. Managed runner startup requires /internal/computer credential mint; deploy server first or roll back the daemon binary.`,
|
|
6109
|
+
{
|
|
6110
|
+
code: detail.code,
|
|
6111
|
+
retryable: detail.retryable,
|
|
6112
|
+
status: detail.status
|
|
6113
|
+
}
|
|
6114
|
+
);
|
|
6115
|
+
}
|
|
6116
|
+
revokeManagedRunnerCredential(agentId, config, launchId) {
|
|
6117
|
+
const credentialId = config.agentCredentialId;
|
|
6118
|
+
if (!credentialId) return;
|
|
6119
|
+
const url = new URL(
|
|
6120
|
+
`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials/${encodeURIComponent(credentialId)}`,
|
|
6121
|
+
this.serverUrl
|
|
6122
|
+
);
|
|
6123
|
+
void fetch(url, {
|
|
6124
|
+
method: "DELETE",
|
|
6125
|
+
headers: {
|
|
6126
|
+
Authorization: `Bearer ${this.daemonApiKey}`,
|
|
6127
|
+
"X-Slock-Client": "daemon-server-session-worker"
|
|
6128
|
+
}
|
|
6129
|
+
}).then((res) => {
|
|
6130
|
+
this.recordDaemonTrace("daemon.runner_credential.revoke", {
|
|
6131
|
+
agentId,
|
|
6132
|
+
launchId: launchId || void 0,
|
|
6133
|
+
credentialId,
|
|
6134
|
+
status: res.status
|
|
6135
|
+
}, res.ok ? "ok" : "error");
|
|
6136
|
+
}).catch((err) => {
|
|
6137
|
+
this.recordDaemonTrace("daemon.runner_credential.revoke", {
|
|
6138
|
+
agentId,
|
|
6139
|
+
launchId: launchId || void 0,
|
|
6140
|
+
credentialId,
|
|
6141
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
6142
|
+
}, "error");
|
|
6143
|
+
});
|
|
5920
6144
|
}
|
|
5921
6145
|
sameEnvVars(left, right) {
|
|
5922
6146
|
const leftKeys = Object.keys(left ?? {});
|
|
@@ -6006,6 +6230,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6006
6230
|
}
|
|
6007
6231
|
this.clearCompactionWatchdog(ap);
|
|
6008
6232
|
this.clearStalledRecoverySigtermWatchdog(ap);
|
|
6233
|
+
cleanupAgentCredentialProxy(agentId, ap.launchId);
|
|
6234
|
+
this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
|
|
6009
6235
|
this.agents.delete(agentId);
|
|
6010
6236
|
this.processExitTraceAttrs.set(ap.process, {
|
|
6011
6237
|
stop_source: silent ? "daemon_internal" : "explicit_request",
|
|
@@ -6089,10 +6315,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6089
6315
|
session_id_present: Boolean(cached.sessionId),
|
|
6090
6316
|
launchId: cached.launchId || void 0
|
|
6091
6317
|
}));
|
|
6092
|
-
this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).
|
|
6318
|
+
return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
|
|
6093
6319
|
logger.error(`[Agent ${agentId}] Failed to auto-restart`, err);
|
|
6320
|
+
if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "idle_auto_restart")) {
|
|
6321
|
+
return false;
|
|
6322
|
+
}
|
|
6323
|
+
this.idleAgentConfigs.set(agentId, cached);
|
|
6324
|
+
return false;
|
|
6094
6325
|
});
|
|
6095
|
-
return true;
|
|
6096
6326
|
}
|
|
6097
6327
|
logger.warn(`[Agent ${agentId}] Delivery received but no running process or cached idle config exists`);
|
|
6098
6328
|
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
@@ -6242,7 +6472,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6242
6472
|
return true;
|
|
6243
6473
|
}
|
|
6244
6474
|
async resetWorkspace(agentId) {
|
|
6245
|
-
const agentDataDir =
|
|
6475
|
+
const agentDataDir = path12.join(this.dataDir, agentId);
|
|
6246
6476
|
try {
|
|
6247
6477
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
6248
6478
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -6279,7 +6509,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6279
6509
|
return result;
|
|
6280
6510
|
}
|
|
6281
6511
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
6282
|
-
const workspacePath =
|
|
6512
|
+
const workspacePath = path12.join(this.dataDir, agentId);
|
|
6283
6513
|
return {
|
|
6284
6514
|
agentId,
|
|
6285
6515
|
launchId,
|
|
@@ -6411,19 +6641,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6411
6641
|
if (cached) {
|
|
6412
6642
|
logger.info(`[Agent ${agentId}] Starting from idle state for runtime profile ${kind} ${key}`);
|
|
6413
6643
|
this.idleAgentConfigs.delete(agentId);
|
|
6414
|
-
this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).
|
|
6644
|
+
return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
|
|
6415
6645
|
logger.error(`[Agent ${agentId}] Failed to auto-restart for runtime profile notification`, err);
|
|
6416
|
-
this.
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6646
|
+
if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "runtime_profile_auto_restart")) {
|
|
6647
|
+
span.end("error", {
|
|
6648
|
+
attrs: {
|
|
6649
|
+
outcome: "runner_credential_mint_failed",
|
|
6650
|
+
runtime: cached.config.runtime,
|
|
6651
|
+
launchId: cached.launchId || void 0
|
|
6652
|
+
}
|
|
6653
|
+
});
|
|
6654
|
+
return false;
|
|
6424
6655
|
}
|
|
6656
|
+
this.idleAgentConfigs.set(agentId, cached);
|
|
6657
|
+
span.end("error", {
|
|
6658
|
+
attrs: {
|
|
6659
|
+
outcome: "restart_failed",
|
|
6660
|
+
runtime: cached.config.runtime,
|
|
6661
|
+
launchId: cached.launchId || void 0
|
|
6662
|
+
}
|
|
6663
|
+
});
|
|
6664
|
+
return false;
|
|
6425
6665
|
});
|
|
6426
|
-
return true;
|
|
6427
6666
|
}
|
|
6428
6667
|
logger.warn(`[Agent ${agentId}] Runtime profile ${kind} ${key} has no runtime injection path yet; leaving unacked for retry`);
|
|
6429
6668
|
span.end("ok", { attrs: { outcome: "no_path" } });
|
|
@@ -6527,7 +6766,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6527
6766
|
}
|
|
6528
6767
|
// Workspace file browsing
|
|
6529
6768
|
async getFileTree(agentId, dirPath) {
|
|
6530
|
-
const agentDir =
|
|
6769
|
+
const agentDir = path12.join(this.dataDir, agentId);
|
|
6531
6770
|
try {
|
|
6532
6771
|
await stat2(agentDir);
|
|
6533
6772
|
} catch {
|
|
@@ -6535,8 +6774,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6535
6774
|
}
|
|
6536
6775
|
let targetDir = agentDir;
|
|
6537
6776
|
if (dirPath) {
|
|
6538
|
-
const resolved =
|
|
6539
|
-
if (!resolved.startsWith(agentDir +
|
|
6777
|
+
const resolved = path12.resolve(agentDir, dirPath);
|
|
6778
|
+
if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
|
|
6540
6779
|
return [];
|
|
6541
6780
|
}
|
|
6542
6781
|
targetDir = resolved;
|
|
@@ -6544,14 +6783,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6544
6783
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
6545
6784
|
}
|
|
6546
6785
|
async readFile(agentId, filePath) {
|
|
6547
|
-
const agentDir =
|
|
6548
|
-
const resolved =
|
|
6549
|
-
if (!resolved.startsWith(agentDir +
|
|
6786
|
+
const agentDir = path12.join(this.dataDir, agentId);
|
|
6787
|
+
const resolved = path12.resolve(agentDir, filePath);
|
|
6788
|
+
if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
|
|
6550
6789
|
throw new Error("Access denied");
|
|
6551
6790
|
}
|
|
6552
6791
|
const info = await stat2(resolved);
|
|
6553
6792
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
6554
|
-
const ext =
|
|
6793
|
+
const ext = path12.extname(resolved).toLowerCase();
|
|
6555
6794
|
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
6556
6795
|
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
6557
6796
|
const content = await readFile(resolved, "utf-8");
|
|
@@ -6586,13 +6825,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6586
6825
|
const agent = this.agents.get(agentId);
|
|
6587
6826
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
6588
6827
|
const home = os6.homedir();
|
|
6589
|
-
const workspaceDir =
|
|
6828
|
+
const workspaceDir = path12.join(this.dataDir, agentId);
|
|
6590
6829
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
6591
6830
|
const globalResults = await Promise.all(
|
|
6592
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
6831
|
+
paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
|
|
6593
6832
|
);
|
|
6594
6833
|
const workspaceResults = await Promise.all(
|
|
6595
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
6834
|
+
paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
|
|
6596
6835
|
);
|
|
6597
6836
|
const dedup = (skills) => {
|
|
6598
6837
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6621,7 +6860,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6621
6860
|
const skills = [];
|
|
6622
6861
|
for (const entry of entries) {
|
|
6623
6862
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
6624
|
-
const skillMd =
|
|
6863
|
+
const skillMd = path12.join(dir, entry.name, "SKILL.md");
|
|
6625
6864
|
try {
|
|
6626
6865
|
const content = await readFile(skillMd, "utf-8");
|
|
6627
6866
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -6632,7 +6871,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6632
6871
|
} else if (entry.name.endsWith(".md")) {
|
|
6633
6872
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
6634
6873
|
try {
|
|
6635
|
-
const content = await readFile(
|
|
6874
|
+
const content = await readFile(path12.join(dir, entry.name), "utf-8");
|
|
6636
6875
|
const skill = this.parseSkillMd(cmdName, content);
|
|
6637
6876
|
skill.sourcePath = dir;
|
|
6638
6877
|
skills.push(skill);
|
|
@@ -7401,6 +7640,29 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7401
7640
|
sendAgentStatus(agentId, status, launchId) {
|
|
7402
7641
|
this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
|
|
7403
7642
|
}
|
|
7643
|
+
reportRunnerCredentialMintFailure(agentId, err, launchId, source) {
|
|
7644
|
+
if (!(err instanceof RunnerCredentialMintError)) return false;
|
|
7645
|
+
const detail = runnerCredentialErrorDetail(err);
|
|
7646
|
+
this.recordDaemonTrace("daemon.runner_credential_mint.hard_fail", {
|
|
7647
|
+
agentId,
|
|
7648
|
+
launchId: launchId || void 0,
|
|
7649
|
+
source,
|
|
7650
|
+
status: detail.status,
|
|
7651
|
+
code: detail.code,
|
|
7652
|
+
reason: detail.message,
|
|
7653
|
+
retryable: detail.retryable
|
|
7654
|
+
}, "error");
|
|
7655
|
+
const reason = err.message;
|
|
7656
|
+
this.sendAgentStatus(agentId, "inactive", launchId);
|
|
7657
|
+
this.broadcastActivity(
|
|
7658
|
+
agentId,
|
|
7659
|
+
"error",
|
|
7660
|
+
`Start failed: ${reason}`,
|
|
7661
|
+
[{ kind: "text", text: `Error: ${reason}` }],
|
|
7662
|
+
launchId
|
|
7663
|
+
);
|
|
7664
|
+
return true;
|
|
7665
|
+
}
|
|
7404
7666
|
noteRuntimeTraceCounter(ap, event) {
|
|
7405
7667
|
ap.runtimeTraceCounters.events++;
|
|
7406
7668
|
switch (event.kind) {
|
|
@@ -7563,6 +7825,7 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
7563
7825
|
}, "error");
|
|
7564
7826
|
return false;
|
|
7565
7827
|
}
|
|
7828
|
+
this.consumeVisibleMessages(agentId, { messages, source: `stdin_${mode}_delivery` });
|
|
7566
7829
|
const senders = [...new Set(messages.map((message) => `@${message.sender_name}`))].join(", ");
|
|
7567
7830
|
logger.info(
|
|
7568
7831
|
`[Agent ${agentId}] Delivering ${mode} ${messages.length === 1 ? "message" : `${messages.length} messages`} via stdin from ${senders}`
|
|
@@ -7596,8 +7859,8 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
7596
7859
|
const nodes = [];
|
|
7597
7860
|
for (const entry of entries) {
|
|
7598
7861
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
7599
|
-
const fullPath =
|
|
7600
|
-
const relativePath =
|
|
7862
|
+
const fullPath = path12.join(dir, entry.name);
|
|
7863
|
+
const relativePath = path12.relative(rootDir, fullPath);
|
|
7601
7864
|
let info;
|
|
7602
7865
|
try {
|
|
7603
7866
|
info = await stat2(fullPath);
|
|
@@ -7900,9 +8163,9 @@ var ReminderCache = class {
|
|
|
7900
8163
|
|
|
7901
8164
|
// src/machineLock.ts
|
|
7902
8165
|
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
7903
|
-
import { mkdirSync as
|
|
8166
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
7904
8167
|
import os7 from "os";
|
|
7905
|
-
import
|
|
8168
|
+
import path13 from "path";
|
|
7906
8169
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
7907
8170
|
var DaemonMachineLockConflictError = class extends Error {
|
|
7908
8171
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -7924,7 +8187,7 @@ function resolveDefaultMachineStateRoot() {
|
|
|
7924
8187
|
return resolveSlockHomePath("machines");
|
|
7925
8188
|
}
|
|
7926
8189
|
function ownerPath(lockDir) {
|
|
7927
|
-
return
|
|
8190
|
+
return path13.join(lockDir, "owner.json");
|
|
7928
8191
|
}
|
|
7929
8192
|
function readOwner(lockDir) {
|
|
7930
8193
|
try {
|
|
@@ -7954,13 +8217,13 @@ function acquireDaemonMachineLock(options) {
|
|
|
7954
8217
|
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
7955
8218
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
7956
8219
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
7957
|
-
const machineDir =
|
|
7958
|
-
const lockDir =
|
|
8220
|
+
const machineDir = path13.join(rootDir, lockId);
|
|
8221
|
+
const lockDir = path13.join(machineDir, "daemon.lock");
|
|
7959
8222
|
const token = randomUUID2();
|
|
7960
|
-
|
|
8223
|
+
mkdirSync5(machineDir, { recursive: true });
|
|
7961
8224
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
7962
8225
|
try {
|
|
7963
|
-
|
|
8226
|
+
mkdirSync5(lockDir);
|
|
7964
8227
|
const owner = {
|
|
7965
8228
|
pid: process.pid,
|
|
7966
8229
|
token,
|
|
@@ -7970,10 +8233,10 @@ function acquireDaemonMachineLock(options) {
|
|
|
7970
8233
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
7971
8234
|
};
|
|
7972
8235
|
try {
|
|
7973
|
-
|
|
8236
|
+
writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
|
|
7974
8237
|
`, { mode: 384 });
|
|
7975
8238
|
} catch (err) {
|
|
7976
|
-
|
|
8239
|
+
rmSync3(lockDir, { recursive: true, force: true });
|
|
7977
8240
|
throw err;
|
|
7978
8241
|
}
|
|
7979
8242
|
return {
|
|
@@ -7983,7 +8246,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
7983
8246
|
release: () => {
|
|
7984
8247
|
const currentOwner = readOwner(lockDir);
|
|
7985
8248
|
if (currentOwner?.pid === process.pid && currentOwner.token === token) {
|
|
7986
|
-
|
|
8249
|
+
rmSync3(lockDir, { recursive: true, force: true });
|
|
7987
8250
|
}
|
|
7988
8251
|
}
|
|
7989
8252
|
};
|
|
@@ -8000,15 +8263,15 @@ function acquireDaemonMachineLock(options) {
|
|
|
8000
8263
|
throw new DaemonMachineLockConflictError(lockDir, null);
|
|
8001
8264
|
}
|
|
8002
8265
|
}
|
|
8003
|
-
|
|
8266
|
+
rmSync3(lockDir, { recursive: true, force: true });
|
|
8004
8267
|
}
|
|
8005
8268
|
}
|
|
8006
8269
|
throw new DaemonMachineLockConflictError(lockDir, readOwner(lockDir));
|
|
8007
8270
|
}
|
|
8008
8271
|
|
|
8009
8272
|
// src/localTraceSink.ts
|
|
8010
|
-
import { appendFileSync, mkdirSync as
|
|
8011
|
-
import
|
|
8273
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
8274
|
+
import path14 from "path";
|
|
8012
8275
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
8013
8276
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
8014
8277
|
var DEFAULT_MAX_FILES = 8;
|
|
@@ -8044,7 +8307,7 @@ var LocalRotatingTraceSink = class {
|
|
|
8044
8307
|
currentSize = 0;
|
|
8045
8308
|
sequence = 0;
|
|
8046
8309
|
constructor(options) {
|
|
8047
|
-
this.traceDir =
|
|
8310
|
+
this.traceDir = path14.join(options.machineDir, "traces");
|
|
8048
8311
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
8049
8312
|
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
8050
8313
|
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
@@ -8070,15 +8333,15 @@ var LocalRotatingTraceSink = class {
|
|
|
8070
8333
|
return this.currentFile;
|
|
8071
8334
|
}
|
|
8072
8335
|
ensureFile(nextBytes) {
|
|
8073
|
-
|
|
8336
|
+
mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
|
|
8074
8337
|
const nowMs = this.nowMsProvider();
|
|
8075
8338
|
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
8076
8339
|
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
8077
|
-
this.currentFile =
|
|
8340
|
+
this.currentFile = path14.join(
|
|
8078
8341
|
this.traceDir,
|
|
8079
8342
|
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
8080
8343
|
);
|
|
8081
|
-
|
|
8344
|
+
writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
|
|
8082
8345
|
this.currentSize = statSync4(this.currentFile).size;
|
|
8083
8346
|
this.currentFileOpenedAtMs = nowMs;
|
|
8084
8347
|
this.pruneOldFiles();
|
|
@@ -8089,7 +8352,7 @@ var LocalRotatingTraceSink = class {
|
|
|
8089
8352
|
const excess = files.length - this.maxFiles;
|
|
8090
8353
|
if (excess <= 0) return;
|
|
8091
8354
|
for (const file of files.slice(0, excess)) {
|
|
8092
|
-
|
|
8355
|
+
rmSync4(path14.join(this.traceDir, file), { force: true });
|
|
8093
8356
|
}
|
|
8094
8357
|
}
|
|
8095
8358
|
};
|
|
@@ -8176,11 +8439,11 @@ function isDiagnosticErrorAttr(key) {
|
|
|
8176
8439
|
import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
|
|
8177
8440
|
import { gzipSync } from "zlib";
|
|
8178
8441
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
8179
|
-
import
|
|
8442
|
+
import path15 from "path";
|
|
8180
8443
|
|
|
8181
8444
|
// src/directUploadCapability.ts
|
|
8182
|
-
function joinUrl(base,
|
|
8183
|
-
return `${base.replace(/\/+$/, "")}${
|
|
8445
|
+
function joinUrl(base, path17) {
|
|
8446
|
+
return `${base.replace(/\/+$/, "")}${path17}`;
|
|
8184
8447
|
}
|
|
8185
8448
|
function jsonHeaders(apiKey) {
|
|
8186
8449
|
return {
|
|
@@ -8399,7 +8662,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
8399
8662
|
}, nextMs);
|
|
8400
8663
|
}
|
|
8401
8664
|
async findUploadCandidates() {
|
|
8402
|
-
const traceDir =
|
|
8665
|
+
const traceDir = path15.join(this.options.machineDir, "traces");
|
|
8403
8666
|
let names;
|
|
8404
8667
|
try {
|
|
8405
8668
|
names = await readdir3(traceDir);
|
|
@@ -8411,8 +8674,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
8411
8674
|
const currentFile = this.options.currentFileProvider?.();
|
|
8412
8675
|
const candidates = [];
|
|
8413
8676
|
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
8414
|
-
const file =
|
|
8415
|
-
if (currentFile &&
|
|
8677
|
+
const file = path15.join(traceDir, name);
|
|
8678
|
+
if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
|
|
8416
8679
|
if (await this.isUploaded(file)) continue;
|
|
8417
8680
|
try {
|
|
8418
8681
|
const info = await stat3(file);
|
|
@@ -8486,8 +8749,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
8486
8749
|
}
|
|
8487
8750
|
}
|
|
8488
8751
|
uploadStatePath(file) {
|
|
8489
|
-
const stateDir =
|
|
8490
|
-
return
|
|
8752
|
+
const stateDir = path15.join(this.options.machineDir, "trace-uploads");
|
|
8753
|
+
return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
|
|
8491
8754
|
}
|
|
8492
8755
|
async isUploaded(file) {
|
|
8493
8756
|
try {
|
|
@@ -8499,9 +8762,9 @@ var DaemonTraceBundleUploader = class {
|
|
|
8499
8762
|
}
|
|
8500
8763
|
async markUploaded(file, metadata) {
|
|
8501
8764
|
const stateFile = this.uploadStatePath(file);
|
|
8502
|
-
await mkdir2(
|
|
8765
|
+
await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
|
|
8503
8766
|
await writeFile2(stateFile, `${JSON.stringify({
|
|
8504
|
-
file:
|
|
8767
|
+
file: path15.basename(file),
|
|
8505
8768
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8506
8769
|
...metadata
|
|
8507
8770
|
}, null, 2)}
|
|
@@ -8520,10 +8783,48 @@ function readPositiveIntegerEnv2(name, fallback) {
|
|
|
8520
8783
|
|
|
8521
8784
|
// src/core.ts
|
|
8522
8785
|
var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
|
|
8523
|
-
var
|
|
8524
|
-
|
|
8786
|
+
var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
|
|
8787
|
+
var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
|
|
8788
|
+
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
|
|
8789
|
+
var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
|
|
8790
|
+
var RunnerCredentialMintError2 = class extends Error {
|
|
8791
|
+
code;
|
|
8792
|
+
retryable;
|
|
8793
|
+
status;
|
|
8794
|
+
constructor(message, opts) {
|
|
8795
|
+
super(message);
|
|
8796
|
+
this.name = "RunnerCredentialMintError";
|
|
8797
|
+
this.code = opts.code;
|
|
8798
|
+
this.retryable = opts.retryable ?? false;
|
|
8799
|
+
this.status = opts.status;
|
|
8800
|
+
}
|
|
8801
|
+
};
|
|
8802
|
+
function isRetryableMintHttpFailure2(status, code) {
|
|
8803
|
+
if (code === "experimental_surface_disabled") return false;
|
|
8804
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
8805
|
+
}
|
|
8806
|
+
function runnerCredentialErrorDetail2(error) {
|
|
8807
|
+
if (error instanceof RunnerCredentialMintError2) {
|
|
8808
|
+
return {
|
|
8809
|
+
message: error.message,
|
|
8810
|
+
code: error.code,
|
|
8811
|
+
retryable: error.retryable,
|
|
8812
|
+
status: error.status
|
|
8813
|
+
};
|
|
8814
|
+
}
|
|
8815
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8816
|
+
return {
|
|
8817
|
+
message,
|
|
8818
|
+
code: "runner_credential_mint_network_error",
|
|
8819
|
+
retryable: true
|
|
8820
|
+
};
|
|
8821
|
+
}
|
|
8822
|
+
async function waitForRunnerCredentialRetry2() {
|
|
8823
|
+
await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
|
|
8824
|
+
}
|
|
8825
|
+
function parseDaemonCliArgs(args) {
|
|
8525
8826
|
let serverUrl = "";
|
|
8526
|
-
let apiKey =
|
|
8827
|
+
let apiKey = "";
|
|
8527
8828
|
for (let i = 0; i < args.length; i++) {
|
|
8528
8829
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
8529
8830
|
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
@@ -8540,23 +8841,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
8540
8841
|
}
|
|
8541
8842
|
}
|
|
8542
8843
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
8543
|
-
const dirname =
|
|
8544
|
-
const jsPath =
|
|
8844
|
+
const dirname = path16.dirname(fileURLToPath(moduleUrl));
|
|
8845
|
+
const jsPath = path16.resolve(dirname, "chat-bridge.js");
|
|
8545
8846
|
try {
|
|
8546
8847
|
accessSync(jsPath);
|
|
8547
8848
|
return jsPath;
|
|
8548
8849
|
} catch {
|
|
8549
|
-
return
|
|
8850
|
+
return path16.resolve(dirname, "chat-bridge.ts");
|
|
8550
8851
|
}
|
|
8551
8852
|
}
|
|
8552
8853
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
8553
|
-
const thisDir =
|
|
8554
|
-
const bundledDistPath =
|
|
8854
|
+
const thisDir = path16.dirname(fileURLToPath(moduleUrl));
|
|
8855
|
+
const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
|
|
8555
8856
|
try {
|
|
8556
8857
|
accessSync(bundledDistPath);
|
|
8557
8858
|
return bundledDistPath;
|
|
8558
8859
|
} catch {
|
|
8559
|
-
const workspaceDistPath =
|
|
8860
|
+
const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
8560
8861
|
accessSync(workspaceDistPath);
|
|
8561
8862
|
return workspaceDistPath;
|
|
8562
8863
|
}
|
|
@@ -8735,7 +9036,7 @@ var DaemonCore = class {
|
|
|
8735
9036
|
}
|
|
8736
9037
|
resolveMachineStateRoot() {
|
|
8737
9038
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
8738
|
-
if (this.options.dataDir) return
|
|
9039
|
+
if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
|
|
8739
9040
|
return resolveDefaultMachineStateRoot();
|
|
8740
9041
|
}
|
|
8741
9042
|
shouldEnableLocalTrace() {
|
|
@@ -8852,13 +9153,115 @@ var DaemonCore = class {
|
|
|
8852
9153
|
});
|
|
8853
9154
|
span.end(status);
|
|
8854
9155
|
}
|
|
9156
|
+
async requestRunnerCredentialOnce(agentId, config) {
|
|
9157
|
+
const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.options.serverUrl);
|
|
9158
|
+
const res = await fetch(url, {
|
|
9159
|
+
method: "POST",
|
|
9160
|
+
headers: {
|
|
9161
|
+
"Authorization": `Bearer ${this.options.apiKey}`,
|
|
9162
|
+
"Content-Type": "application/json",
|
|
9163
|
+
"X-Slock-Client": "daemon-server-session-worker"
|
|
9164
|
+
},
|
|
9165
|
+
body: JSON.stringify({
|
|
9166
|
+
scopes: RUNNER_CREDENTIAL_SCOPES,
|
|
9167
|
+
name: `runner:${config.runtime}:${agentId.slice(0, 8)}`
|
|
9168
|
+
})
|
|
9169
|
+
});
|
|
9170
|
+
if (!res.ok) {
|
|
9171
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
9172
|
+
let detail = `HTTP ${res.status}`;
|
|
9173
|
+
let code = null;
|
|
9174
|
+
if (contentType.includes("application/json")) {
|
|
9175
|
+
const body2 = await res.json().catch(() => null);
|
|
9176
|
+
const error = typeof body2?.error === "string" ? body2.error : null;
|
|
9177
|
+
code = typeof body2?.code === "string" ? body2.code : null;
|
|
9178
|
+
detail = [detail, code, error].filter(Boolean).join(" ");
|
|
9179
|
+
}
|
|
9180
|
+
throw new RunnerCredentialMintError2(detail, {
|
|
9181
|
+
code: code ?? "runner_credential_mint_http_error",
|
|
9182
|
+
retryable: isRetryableMintHttpFailure2(res.status, code),
|
|
9183
|
+
status: res.status
|
|
9184
|
+
});
|
|
9185
|
+
}
|
|
9186
|
+
const body = await res.json().catch(() => null);
|
|
9187
|
+
if (typeof body?.apiKey !== "string" || !body.apiKey.startsWith("sk_agent_")) {
|
|
9188
|
+
throw new RunnerCredentialMintError2("invalid_agent_credential_payload", {
|
|
9189
|
+
code: "invalid_agent_credential_payload"
|
|
9190
|
+
});
|
|
9191
|
+
}
|
|
9192
|
+
return {
|
|
9193
|
+
apiKey: body.apiKey,
|
|
9194
|
+
credentialId: typeof body.credentialId === "string" ? body.credentialId : null
|
|
9195
|
+
};
|
|
9196
|
+
}
|
|
9197
|
+
async mintRunnerCredential(agentId, config) {
|
|
9198
|
+
if (config.agentCredentialKey) {
|
|
9199
|
+
return { apiKey: config.agentCredentialKey, credentialId: config.agentCredentialId ?? null };
|
|
9200
|
+
}
|
|
9201
|
+
if (process.env.SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED === "1") {
|
|
9202
|
+
throw new RunnerCredentialMintError2("runner credential mint is disabled by SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED", {
|
|
9203
|
+
code: "runner_credentials_disabled"
|
|
9204
|
+
});
|
|
9205
|
+
}
|
|
9206
|
+
let lastError = null;
|
|
9207
|
+
for (let attempt = 1; attempt <= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2; attempt += 1) {
|
|
9208
|
+
try {
|
|
9209
|
+
return await this.requestRunnerCredentialOnce(agentId, config);
|
|
9210
|
+
} catch (err) {
|
|
9211
|
+
lastError = err;
|
|
9212
|
+
const detail2 = runnerCredentialErrorDetail2(err);
|
|
9213
|
+
this.recordDaemonTrace("daemon.runner_credential_mint.retry", {
|
|
9214
|
+
agentId,
|
|
9215
|
+
runtime: config.runtime,
|
|
9216
|
+
attempt,
|
|
9217
|
+
max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2,
|
|
9218
|
+
status: detail2.status,
|
|
9219
|
+
code: detail2.code,
|
|
9220
|
+
reason: detail2.message,
|
|
9221
|
+
retryable: detail2.retryable
|
|
9222
|
+
}, detail2.retryable && attempt < RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 ? "ok" : "error");
|
|
9223
|
+
if (!detail2.retryable || attempt >= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2) break;
|
|
9224
|
+
await waitForRunnerCredentialRetry2();
|
|
9225
|
+
}
|
|
9226
|
+
}
|
|
9227
|
+
const detail = runnerCredentialErrorDetail2(lastError);
|
|
9228
|
+
this.recordDaemonTrace("daemon.runner_credential_mint.failed", {
|
|
9229
|
+
agentId,
|
|
9230
|
+
runtime: config.runtime,
|
|
9231
|
+
status: detail.status,
|
|
9232
|
+
code: detail.code,
|
|
9233
|
+
reason: detail.message,
|
|
9234
|
+
retryable: detail.retryable,
|
|
9235
|
+
max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2
|
|
9236
|
+
}, "error");
|
|
9237
|
+
throw new RunnerCredentialMintError2(
|
|
9238
|
+
`runner_credential_mint_failed: ${detail.message}. Managed runner startup requires /internal/computer credential mint; deploy server first or roll back the daemon binary.`,
|
|
9239
|
+
{
|
|
9240
|
+
code: detail.code,
|
|
9241
|
+
retryable: detail.retryable,
|
|
9242
|
+
status: detail.status
|
|
9243
|
+
}
|
|
9244
|
+
);
|
|
9245
|
+
}
|
|
9246
|
+
async startAgentFromMessage(msg) {
|
|
9247
|
+
const agentCredential = await this.mintRunnerCredential(msg.agentId, msg.config);
|
|
9248
|
+
const config = { ...msg.config, agentCredentialKey: agentCredential.apiKey, agentCredentialId: agentCredential.credentialId };
|
|
9249
|
+
await this.agentManager.startAgent(
|
|
9250
|
+
msg.agentId,
|
|
9251
|
+
config,
|
|
9252
|
+
msg.wakeMessage,
|
|
9253
|
+
msg.unreadSummary,
|
|
9254
|
+
msg.resumePrompt,
|
|
9255
|
+
msg.launchId
|
|
9256
|
+
);
|
|
9257
|
+
}
|
|
8855
9258
|
handleMessage(msg) {
|
|
8856
9259
|
const summary = summarizeIncomingMessage(msg);
|
|
8857
9260
|
logger.info(`[Daemon] Received ${msg.type}${summary ? ` ${summary}` : ""}`);
|
|
8858
9261
|
switch (msg.type) {
|
|
8859
9262
|
case "agent:start":
|
|
8860
9263
|
logger.info(`[Agent ${msg.agentId}] Start requested (runtime=${msg.config.runtime}, model=${msg.config.model}, session=${msg.config.sessionId || "new"}${msg.wakeMessage ? ", wake=true" : ""})`);
|
|
8861
|
-
this.
|
|
9264
|
+
this.startAgentFromMessage(msg).catch((err) => {
|
|
8862
9265
|
const reason = err instanceof Error ? err.message : String(err);
|
|
8863
9266
|
logger.error(`[Agent ${msg.agentId}] Start failed (${reason})`);
|
|
8864
9267
|
this.connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive", launchId: msg.launchId });
|
|
@@ -8891,22 +9294,27 @@ var DaemonCore = class {
|
|
|
8891
9294
|
logger.info(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
|
|
8892
9295
|
try {
|
|
8893
9296
|
span.addEvent("daemon.receive", { seq: msg.seq, deliveryId: msg.deliveryId });
|
|
8894
|
-
const
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
9297
|
+
const acceptedOrPromise = this.agentManager.deliverMessage(msg.agentId, msg.message, { deliveryId: msg.deliveryId });
|
|
9298
|
+
Promise.resolve(acceptedOrPromise).then((accepted) => {
|
|
9299
|
+
span.addEvent("daemon.deliver_to_agent_manager", { accepted });
|
|
9300
|
+
if (!accepted) {
|
|
9301
|
+
span.end("ok", { attrs: { outcome: "not-accepted" } });
|
|
9302
|
+
return;
|
|
9303
|
+
}
|
|
9304
|
+
const ackSeq = msg.seq > 0 ? msg.seq : msg.message.seq ?? 0;
|
|
9305
|
+
span.addEvent("daemon.ack.sent", { seq: ackSeq });
|
|
9306
|
+
this.connection.send({
|
|
9307
|
+
type: "agent:deliver:ack",
|
|
9308
|
+
agentId: msg.agentId,
|
|
9309
|
+
seq: ackSeq,
|
|
9310
|
+
traceparent: formatTraceparent(span.context),
|
|
9311
|
+
deliveryId: msg.deliveryId
|
|
9312
|
+
});
|
|
9313
|
+
span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
|
|
9314
|
+
}, (err) => {
|
|
9315
|
+
logger.error(`[Agent ${msg.agentId}] Delivery handling failed`, err);
|
|
9316
|
+
span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
|
|
8908
9317
|
});
|
|
8909
|
-
span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
|
|
8910
9318
|
} catch (err) {
|
|
8911
9319
|
span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
|
|
8912
9320
|
throw err;
|
|
@@ -8926,8 +9334,14 @@ var DaemonCore = class {
|
|
|
8926
9334
|
}
|
|
8927
9335
|
});
|
|
8928
9336
|
logger.info(`[Agent ${msg.agentId}] Runtime profile migration received (${msg.migrationKey})`);
|
|
8929
|
-
|
|
8930
|
-
|
|
9337
|
+
Promise.resolve(
|
|
9338
|
+
this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.migrationKey, "migration", msg.message, formatTraceparent(span.context))
|
|
9339
|
+
).then((accepted) => {
|
|
9340
|
+
span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
|
|
9341
|
+
}, (err) => {
|
|
9342
|
+
logger.error(`[Agent ${msg.agentId}] Runtime profile migration handling failed`, err);
|
|
9343
|
+
span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
|
|
9344
|
+
});
|
|
8931
9345
|
break;
|
|
8932
9346
|
}
|
|
8933
9347
|
case "agent:runtime_profile:daemon_release_notice": {
|
|
@@ -8943,8 +9357,14 @@ var DaemonCore = class {
|
|
|
8943
9357
|
}
|
|
8944
9358
|
});
|
|
8945
9359
|
logger.info(`[Agent ${msg.agentId}] Runtime profile daemon release notice received (${msg.noticeKey})`);
|
|
8946
|
-
|
|
8947
|
-
|
|
9360
|
+
Promise.resolve(
|
|
9361
|
+
this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.noticeKey, "daemon_release_notice", msg.message, formatTraceparent(span.context))
|
|
9362
|
+
).then((accepted) => {
|
|
9363
|
+
span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
|
|
9364
|
+
}, (err) => {
|
|
9365
|
+
logger.error(`[Agent ${msg.agentId}] Runtime profile daemon release notice handling failed`, err);
|
|
9366
|
+
span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
|
|
9367
|
+
});
|
|
8948
9368
|
break;
|
|
8949
9369
|
}
|
|
8950
9370
|
case "agent:workspace:list":
|
|
@@ -9118,8 +9538,6 @@ var DaemonCore = class {
|
|
|
9118
9538
|
};
|
|
9119
9539
|
|
|
9120
9540
|
export {
|
|
9121
|
-
DAEMON_API_KEY_ENV,
|
|
9122
|
-
scrubDaemonAuthEnv,
|
|
9123
9541
|
resolveWorkspaceDirectoryPath,
|
|
9124
9542
|
scanWorkspaceDirectories,
|
|
9125
9543
|
deleteWorkspaceDirectory,
|