@slock-ai/daemon 0.50.0 → 0.51.1-play.20260519174619
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-EXJF5JKE.js → chunk-NZM6RHMZ.js} +1491 -181
- package/dist/cli/index.js +357 -35
- package/dist/core.js +6 -2
- package/dist/drivers/piSdkRunner.js +96 -0
- package/dist/index.js +5 -3
- package/package.json +2 -1
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
} from "./chunk-KNMCE6WB.js";
|
|
8
8
|
|
|
9
9
|
// src/core.ts
|
|
10
|
-
import
|
|
10
|
+
import path17 from "path";
|
|
11
11
|
import os8 from "os";
|
|
12
12
|
import { createRequire } from "module";
|
|
13
13
|
import { accessSync } from "fs";
|
|
14
|
-
import { fileURLToPath } from "url";
|
|
14
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15
15
|
|
|
16
16
|
// ../shared/src/tracing/index.ts
|
|
17
17
|
var DEFAULT_TRACE_FLAGS = "00";
|
|
@@ -723,6 +723,7 @@ 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 },
|
|
726
727
|
{ id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
|
|
727
728
|
{ id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
|
|
728
729
|
{ id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
|
|
@@ -764,10 +765,10 @@ var DISPLAY_PLAN_CONFIG = {
|
|
|
764
765
|
};
|
|
765
766
|
|
|
766
767
|
// src/agentProcessManager.ts
|
|
767
|
-
import { mkdirSync as
|
|
768
|
+
import { mkdirSync as mkdirSync5, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
|
|
768
769
|
import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
|
|
769
770
|
import { createHash as createHash2 } from "crypto";
|
|
770
|
-
import
|
|
771
|
+
import path13 from "path";
|
|
771
772
|
import os6 from "os";
|
|
772
773
|
|
|
773
774
|
// src/drivers/claude.ts
|
|
@@ -777,7 +778,7 @@ import os2 from "os";
|
|
|
777
778
|
import path4 from "path";
|
|
778
779
|
|
|
779
780
|
// src/drivers/cliTransport.ts
|
|
780
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
781
|
+
import { mkdirSync, rmSync, writeFileSync } from "fs";
|
|
781
782
|
import path2 from "path";
|
|
782
783
|
|
|
783
784
|
// src/drivers/systemPrompt.ts
|
|
@@ -870,14 +871,16 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
|
|
|
870
871
|
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\`.
|
|
871
872
|
17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
872
873
|
18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
873
|
-
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.
|
|
874
|
-
20. **\`slock
|
|
875
|
-
21. **\`slock
|
|
876
|
-
22. **\`slock reminder
|
|
877
|
-
23. **\`slock reminder
|
|
878
|
-
24. **\`slock reminder
|
|
879
|
-
25. **\`slock reminder
|
|
880
|
-
26. **\`slock
|
|
874
|
+
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.
|
|
875
|
+
20. **\`slock integration list\`** \u2014 List registered third-party services and this agent's active Slock Agent Logins.
|
|
876
|
+
21. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a registered third-party service.
|
|
877
|
+
22. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
878
|
+
23. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
|
|
879
|
+
24. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
|
|
880
|
+
25. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
881
|
+
26. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
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\`).
|
|
881
884
|
|
|
882
885
|
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:
|
|
883
886
|
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
@@ -978,6 +981,11 @@ Each channel has a **name** and optionally a **description** that define its pur
|
|
|
978
981
|
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
979
982
|
- **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.
|
|
980
983
|
- 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.`;
|
|
981
989
|
const readingHistorySection = isCli ? `### Reading history
|
|
982
990
|
|
|
983
991
|
\`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
|
|
@@ -1149,6 +1157,8 @@ ${discoverySection}
|
|
|
1149
1157
|
|
|
1150
1158
|
${channelAwarenessSection}
|
|
1151
1159
|
|
|
1160
|
+
${thirdPartyIntegrationsSection}
|
|
1161
|
+
|
|
1152
1162
|
${readingHistorySection}
|
|
1153
1163
|
|
|
1154
1164
|
${historicalReferenceSection}
|
|
@@ -1180,6 +1190,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
1180
1190
|
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
1181
1191
|
- When done, summarize the result.
|
|
1182
1192
|
- 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.
|
|
1183
1204
|
|
|
1184
1205
|
### Conversation etiquette
|
|
1185
1206
|
|
|
@@ -1349,8 +1370,326 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
|
|
|
1349
1370
|
return candidates.filter((candidate) => existsSync(candidate.path));
|
|
1350
1371
|
}
|
|
1351
1372
|
|
|
1373
|
+
// src/authEnv.ts
|
|
1374
|
+
var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
|
|
1375
|
+
var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
|
|
1376
|
+
function scrubDaemonAuthEnv(env) {
|
|
1377
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
1378
|
+
return env;
|
|
1379
|
+
}
|
|
1380
|
+
function scrubDaemonChildEnv(env) {
|
|
1381
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
1382
|
+
delete env[SLOCK_AGENT_TOKEN_ENV];
|
|
1383
|
+
return env;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// src/agentCredentialProxy.ts
|
|
1387
|
+
import { randomBytes } from "crypto";
|
|
1388
|
+
import http from "http";
|
|
1389
|
+
import { URL as URL2 } from "url";
|
|
1390
|
+
var registrations = /* @__PURE__ */ new Map();
|
|
1391
|
+
var servers = /* @__PURE__ */ new Map();
|
|
1392
|
+
var DECODED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding"]);
|
|
1393
|
+
function allocatePort() {
|
|
1394
|
+
return 43e3 + randomBytes(2).readUInt16BE(0) % 1e4;
|
|
1395
|
+
}
|
|
1396
|
+
function ensureServer(port) {
|
|
1397
|
+
if (servers.has(port)) return;
|
|
1398
|
+
const server = http.createServer((req, res) => {
|
|
1399
|
+
void handleProxyRequest(req, res);
|
|
1400
|
+
});
|
|
1401
|
+
server.on("error", (err) => {
|
|
1402
|
+
logger.warn(`[Agent Credential Proxy] local proxy on port ${port} failed: ${err.message}`);
|
|
1403
|
+
});
|
|
1404
|
+
server.listen(port, "127.0.0.1");
|
|
1405
|
+
server.unref();
|
|
1406
|
+
servers.set(port, server);
|
|
1407
|
+
}
|
|
1408
|
+
function responseHeadersForLocalProxy(upstream) {
|
|
1409
|
+
const headers = {};
|
|
1410
|
+
for (const [name, value] of upstream.headers.entries()) {
|
|
1411
|
+
if (DECODED_RESPONSE_HEADERS.has(name.toLowerCase())) continue;
|
|
1412
|
+
headers[name] = value;
|
|
1413
|
+
}
|
|
1414
|
+
return headers;
|
|
1415
|
+
}
|
|
1416
|
+
async function handleProxyRequest(req, res) {
|
|
1417
|
+
const auth = req.headers.authorization;
|
|
1418
|
+
const token = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice("Bearer ".length) : "";
|
|
1419
|
+
const registration = registrations.get(token);
|
|
1420
|
+
if (!registration) {
|
|
1421
|
+
res.writeHead(401, { "content-type": "application/json" });
|
|
1422
|
+
res.end(JSON.stringify({ error: "invalid local agent proxy token", code: "invalid_agent_proxy_token" }));
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
try {
|
|
1426
|
+
const target = new URL2(req.url ?? "/", registration.serverUrl);
|
|
1427
|
+
const headers = new Headers();
|
|
1428
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
1429
|
+
if (value === void 0) continue;
|
|
1430
|
+
if (name.toLowerCase() === "host") continue;
|
|
1431
|
+
if (name.toLowerCase() === "authorization") continue;
|
|
1432
|
+
if (Array.isArray(value)) {
|
|
1433
|
+
for (const item of value) headers.append(name, item);
|
|
1434
|
+
} else {
|
|
1435
|
+
headers.set(name, value);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
headers.set("Authorization", `Bearer ${registration.apiKey}`);
|
|
1439
|
+
headers.set("X-Agent-Id", registration.agentId);
|
|
1440
|
+
headers.set("X-Slock-Client", "cli");
|
|
1441
|
+
headers.set("X-Slock-Agent-Active-Capabilities", registration.activeCapabilities);
|
|
1442
|
+
const method = req.method ?? "GET";
|
|
1443
|
+
let body = method === "GET" || method === "HEAD" ? void 0 : req;
|
|
1444
|
+
let sendTarget;
|
|
1445
|
+
if (method === "POST" && target.pathname === "/internal/agent-api/send") {
|
|
1446
|
+
const rawBody = await readRequestBody(req);
|
|
1447
|
+
const prepared = await prepareAgentApiSendForward(registration, headers, rawBody);
|
|
1448
|
+
if (prepared.localResponse) {
|
|
1449
|
+
const responseText = JSON.stringify(prepared.localResponse);
|
|
1450
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
1451
|
+
res.end(responseText);
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
body = prepared.bodyText;
|
|
1455
|
+
sendTarget = prepared.target;
|
|
1456
|
+
headers.set("content-type", "application/json");
|
|
1457
|
+
headers.set("content-length", String(Buffer.byteLength(prepared.bodyText)));
|
|
1458
|
+
}
|
|
1459
|
+
const upstream = await fetch(target, {
|
|
1460
|
+
method,
|
|
1461
|
+
headers,
|
|
1462
|
+
body,
|
|
1463
|
+
// Required by undici when forwarding a Node stream.
|
|
1464
|
+
duplex: body ? "half" : void 0
|
|
1465
|
+
});
|
|
1466
|
+
if (shouldBufferJsonResponse(upstream, target.pathname, registration)) {
|
|
1467
|
+
const responseText = await upstream.text();
|
|
1468
|
+
consumeVisibleResponse(registration, target, sendTarget, responseText);
|
|
1469
|
+
res.writeHead(upstream.status, responseHeadersForLocalProxy(upstream));
|
|
1470
|
+
res.end(responseText);
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
res.writeHead(upstream.status, responseHeadersForLocalProxy(upstream));
|
|
1474
|
+
if (upstream.body) {
|
|
1475
|
+
const reader = upstream.body.getReader();
|
|
1476
|
+
try {
|
|
1477
|
+
while (true) {
|
|
1478
|
+
const { done, value } = await reader.read();
|
|
1479
|
+
if (done) break;
|
|
1480
|
+
res.write(Buffer.from(value));
|
|
1481
|
+
}
|
|
1482
|
+
} finally {
|
|
1483
|
+
res.end();
|
|
1484
|
+
}
|
|
1485
|
+
} else {
|
|
1486
|
+
res.end();
|
|
1487
|
+
}
|
|
1488
|
+
} catch (err) {
|
|
1489
|
+
res.writeHead(502, { "content-type": "application/json" });
|
|
1490
|
+
res.end(JSON.stringify({
|
|
1491
|
+
error: "failed to proxy local agent request",
|
|
1492
|
+
code: "agent_proxy_failed",
|
|
1493
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
1494
|
+
}));
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
async function readRequestBody(req) {
|
|
1498
|
+
let body = "";
|
|
1499
|
+
req.setEncoding("utf8");
|
|
1500
|
+
for await (const chunk of req) {
|
|
1501
|
+
body += String(chunk);
|
|
1502
|
+
}
|
|
1503
|
+
return body;
|
|
1504
|
+
}
|
|
1505
|
+
function messageSeq(message) {
|
|
1506
|
+
return Number(message.seq ?? 0);
|
|
1507
|
+
}
|
|
1508
|
+
function boundaryBefore(messages) {
|
|
1509
|
+
let minSeq = Number.POSITIVE_INFINITY;
|
|
1510
|
+
for (const message of messages) {
|
|
1511
|
+
const seq = Math.floor(messageSeq(message));
|
|
1512
|
+
if (Number.isFinite(seq) && seq > 0) minSeq = Math.min(minSeq, seq);
|
|
1513
|
+
}
|
|
1514
|
+
return Number.isFinite(minSeq) ? Math.max(0, minSeq - 1) : void 0;
|
|
1515
|
+
}
|
|
1516
|
+
function maxMessageSeq(messages) {
|
|
1517
|
+
let maxSeq = 0;
|
|
1518
|
+
for (const message of messages) {
|
|
1519
|
+
const seq = Math.floor(messageSeq(message));
|
|
1520
|
+
if (Number.isFinite(seq) && seq > 0) maxSeq = Math.max(maxSeq, seq);
|
|
1521
|
+
}
|
|
1522
|
+
return maxSeq > 0 ? maxSeq : void 0;
|
|
1523
|
+
}
|
|
1524
|
+
function parseTargetFields(target) {
|
|
1525
|
+
if (target.startsWith("dm:@")) {
|
|
1526
|
+
const rest = target.slice("dm:@".length);
|
|
1527
|
+
const [peer, threadId] = rest.split(":", 2);
|
|
1528
|
+
if (threadId) {
|
|
1529
|
+
return {
|
|
1530
|
+
channel_type: "thread",
|
|
1531
|
+
channel_name: threadId,
|
|
1532
|
+
parent_channel_type: "dm",
|
|
1533
|
+
parent_channel_name: peer
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
return { channel_type: "dm", channel_name: peer };
|
|
1537
|
+
}
|
|
1538
|
+
if (target.startsWith("#")) {
|
|
1539
|
+
const rest = target.slice(1);
|
|
1540
|
+
const [channel, threadId] = rest.split(":", 2);
|
|
1541
|
+
if (threadId) {
|
|
1542
|
+
return {
|
|
1543
|
+
channel_type: "thread",
|
|
1544
|
+
channel_name: threadId,
|
|
1545
|
+
parent_channel_type: "channel",
|
|
1546
|
+
parent_channel_name: channel
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
return { channel_type: "channel", channel_name: channel };
|
|
1550
|
+
}
|
|
1551
|
+
return {};
|
|
1552
|
+
}
|
|
1553
|
+
function normalizeVisibleMessage(message, target) {
|
|
1554
|
+
const targetFields = target ? parseTargetFields(target) : {};
|
|
1555
|
+
return {
|
|
1556
|
+
...targetFields,
|
|
1557
|
+
...message,
|
|
1558
|
+
message_id: message.message_id ?? message.id,
|
|
1559
|
+
timestamp: message.timestamp ?? message.createdAt,
|
|
1560
|
+
sender_type: message.sender_type ?? message.senderType,
|
|
1561
|
+
sender_name: message.sender_name ?? message.senderName,
|
|
1562
|
+
sender_description: message.sender_description ?? message.senderDescription ?? null
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
function normalizeVisibleMessages(messages, target) {
|
|
1566
|
+
return messages.map((message) => normalizeVisibleMessage(message, target));
|
|
1567
|
+
}
|
|
1568
|
+
async function loadRecentTargetMessages(registration, headers, target) {
|
|
1569
|
+
const historyUrl = new URL2("/internal/agent-api/history", registration.serverUrl);
|
|
1570
|
+
historyUrl.searchParams.set("channel", target);
|
|
1571
|
+
historyUrl.searchParams.set("limit", "3");
|
|
1572
|
+
const historyHeaders = new Headers(headers);
|
|
1573
|
+
historyHeaders.delete("content-length");
|
|
1574
|
+
historyHeaders.delete("content-type");
|
|
1575
|
+
const res = await fetch(historyUrl, { method: "GET", headers: historyHeaders });
|
|
1576
|
+
if (!res.ok) return [];
|
|
1577
|
+
const parsed = await res.json().catch(() => null);
|
|
1578
|
+
return Array.isArray(parsed?.messages) ? normalizeVisibleMessages(parsed.messages, target) : [];
|
|
1579
|
+
}
|
|
1580
|
+
async function prepareAgentApiSendForward(registration, headers, rawBody) {
|
|
1581
|
+
let body;
|
|
1582
|
+
try {
|
|
1583
|
+
body = rawBody ? JSON.parse(rawBody) : {};
|
|
1584
|
+
} catch {
|
|
1585
|
+
return { bodyText: rawBody };
|
|
1586
|
+
}
|
|
1587
|
+
const target = typeof body.target === "string" ? body.target : void 0;
|
|
1588
|
+
const coordinator = registration.inboxCoordinator;
|
|
1589
|
+
if (!target || !coordinator || body.continueAnyway === true) {
|
|
1590
|
+
return { bodyText: JSON.stringify(body), target };
|
|
1591
|
+
}
|
|
1592
|
+
const pending = coordinator.getPendingMessages(target);
|
|
1593
|
+
if (pending.length > 0) {
|
|
1594
|
+
const staleBoundary = boundaryBefore(pending);
|
|
1595
|
+
if (staleBoundary !== void 0) {
|
|
1596
|
+
body.seenUpToSeq = staleBoundary;
|
|
1597
|
+
}
|
|
1598
|
+
return { bodyText: JSON.stringify(body), target };
|
|
1599
|
+
}
|
|
1600
|
+
const existingBoundary = typeof body.seenUpToSeq === "number" && Number.isFinite(body.seenUpToSeq) ? Math.max(0, Math.floor(body.seenUpToSeq)) : void 0;
|
|
1601
|
+
const loadedBoundary = coordinator.getBoundary(target);
|
|
1602
|
+
const boundary = Math.max(existingBoundary ?? 0, loadedBoundary ?? 0);
|
|
1603
|
+
if (boundary > 0) {
|
|
1604
|
+
body.seenUpToSeq = boundary;
|
|
1605
|
+
return { bodyText: JSON.stringify(body), target };
|
|
1606
|
+
}
|
|
1607
|
+
const recent = await loadRecentTargetMessages(registration, headers, target);
|
|
1608
|
+
if (recent.length > 0) {
|
|
1609
|
+
const seenUpToSeq = maxMessageSeq(recent);
|
|
1610
|
+
coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "send_preflight_context" });
|
|
1611
|
+
return {
|
|
1612
|
+
bodyText: JSON.stringify(body),
|
|
1613
|
+
target,
|
|
1614
|
+
localResponse: {
|
|
1615
|
+
state: "held",
|
|
1616
|
+
seenUpToSeq,
|
|
1617
|
+
heldMessages: recent,
|
|
1618
|
+
newMessageCount: recent.length,
|
|
1619
|
+
shownMessageCount: recent.length,
|
|
1620
|
+
omittedMessageCount: 0
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
return { bodyText: JSON.stringify(body), target };
|
|
1625
|
+
}
|
|
1626
|
+
function shouldBufferJsonResponse(upstream, pathname, registration) {
|
|
1627
|
+
if (!registration.inboxCoordinator) return false;
|
|
1628
|
+
const contentType = upstream.headers.get("content-type") ?? "";
|
|
1629
|
+
if (!contentType.includes("application/json")) return false;
|
|
1630
|
+
return pathname === "/internal/agent-api/send" || pathname === "/internal/agent-api/events" || pathname === "/internal/agent-api/history";
|
|
1631
|
+
}
|
|
1632
|
+
function consumeVisibleResponse(registration, targetUrl, sendTarget, responseText) {
|
|
1633
|
+
const coordinator = registration.inboxCoordinator;
|
|
1634
|
+
if (!coordinator) return;
|
|
1635
|
+
let parsed;
|
|
1636
|
+
try {
|
|
1637
|
+
parsed = JSON.parse(responseText);
|
|
1638
|
+
} catch {
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
if (targetUrl.pathname === "/internal/agent-api/send" && parsed.state === "held" && Array.isArray(parsed.heldMessages)) {
|
|
1642
|
+
coordinator.consumeVisibleMessages({
|
|
1643
|
+
target: sendTarget,
|
|
1644
|
+
messages: normalizeVisibleMessages(parsed.heldMessages, sendTarget),
|
|
1645
|
+
boundarySeq: typeof parsed.seenUpToSeq === "number" ? parsed.seenUpToSeq : void 0,
|
|
1646
|
+
source: "server_held_context"
|
|
1647
|
+
});
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
if (targetUrl.pathname === "/internal/agent-api/events" && Array.isArray(parsed.events)) {
|
|
1651
|
+
coordinator.consumeVisibleMessages({ messages: normalizeVisibleMessages(parsed.events), source: "agent_api_events" });
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
if (targetUrl.pathname === "/internal/agent-api/history" && Array.isArray(parsed.messages)) {
|
|
1655
|
+
const target = targetUrl.searchParams.get("channel") ?? void 0;
|
|
1656
|
+
const messages = normalizeVisibleMessages(parsed.messages, target);
|
|
1657
|
+
coordinator.consumeVisibleMessages({ target, messages, boundarySeq: maxMessageSeq(messages), source: "agent_api_history" });
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
function registerAgentCredentialProxy(input) {
|
|
1661
|
+
const port = allocatePort();
|
|
1662
|
+
ensureServer(port);
|
|
1663
|
+
const proxyToken = `sap_${randomBytes(32).toString("base64url")}`;
|
|
1664
|
+
registrations.set(proxyToken, {
|
|
1665
|
+
serverUrl: input.serverUrl,
|
|
1666
|
+
apiKey: input.apiKey,
|
|
1667
|
+
agentId: input.agentId,
|
|
1668
|
+
launchId: input.launchId ?? null,
|
|
1669
|
+
activeCapabilities: input.activeCapabilities,
|
|
1670
|
+
inboxCoordinator: input.inboxCoordinator
|
|
1671
|
+
});
|
|
1672
|
+
return {
|
|
1673
|
+
proxyUrl: `http://127.0.0.1:${port}`,
|
|
1674
|
+
proxyToken
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
function unregisterAgentCredentialProxyForLaunch(input) {
|
|
1678
|
+
let removed = 0;
|
|
1679
|
+
const launchId = input.launchId ?? null;
|
|
1680
|
+
for (const [token, registration] of registrations) {
|
|
1681
|
+
if (registration.agentId === input.agentId && registration.launchId === launchId) {
|
|
1682
|
+
registrations.delete(token);
|
|
1683
|
+
removed += 1;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
return removed;
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1352
1689
|
// src/drivers/cliTransport.ts
|
|
1353
1690
|
var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1691
|
+
var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
|
|
1692
|
+
var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
1354
1693
|
function runtimeContextEnv(config) {
|
|
1355
1694
|
const ctx = config.runtimeContext;
|
|
1356
1695
|
if (!ctx) return {};
|
|
@@ -1374,17 +1713,43 @@ function prepareCliTransport(ctx, extraEnv = {}, platform = process.platform) {
|
|
|
1374
1713
|
}
|
|
1375
1714
|
const slockDir = path2.join(ctx.workingDirectory, ".slock");
|
|
1376
1715
|
mkdirSync(slockDir, { recursive: true });
|
|
1716
|
+
const slockHome = resolveSlockHome();
|
|
1377
1717
|
const tokenFile = path2.join(slockDir, "agent-token");
|
|
1378
|
-
|
|
1718
|
+
const agentCredentialKey = ctx.config.agentCredentialKey;
|
|
1719
|
+
let agentCredentialProxy = null;
|
|
1720
|
+
let agentCredentialProxyTokenFile = null;
|
|
1721
|
+
if (typeof agentCredentialKey === "string" && agentCredentialKey.length > 0) {
|
|
1722
|
+
rmSync(tokenFile, { force: true });
|
|
1723
|
+
agentCredentialProxy = registerAgentCredentialProxy({
|
|
1724
|
+
agentId: ctx.agentId,
|
|
1725
|
+
launchId: ctx.launchId,
|
|
1726
|
+
serverUrl: ctx.config.serverUrl,
|
|
1727
|
+
apiKey: agentCredentialKey,
|
|
1728
|
+
activeCapabilities: DEFAULT_ACTIVE_CAPABILITIES,
|
|
1729
|
+
inboxCoordinator: ctx.agentCredentialProxyInboxCoordinator
|
|
1730
|
+
});
|
|
1731
|
+
const launchPart = safePathPart(ctx.launchId || `pid-${process.pid}`);
|
|
1732
|
+
const proxyTokenDir = path2.join(slockHome, "agent-proxy-tokens", safePathPart(ctx.agentId));
|
|
1733
|
+
mkdirSync(proxyTokenDir, { recursive: true, mode: 448 });
|
|
1734
|
+
agentCredentialProxyTokenFile = path2.join(proxyTokenDir, `${launchPart}.token`);
|
|
1735
|
+
writeFileSync(agentCredentialProxyTokenFile, agentCredentialProxy.proxyToken, { mode: 384 });
|
|
1736
|
+
} else {
|
|
1737
|
+
writeFileSync(tokenFile, ctx.config.authToken || ctx.daemonApiKey, { mode: 384 });
|
|
1738
|
+
}
|
|
1379
1739
|
const posixWrapper = path2.join(slockDir, "slock");
|
|
1740
|
+
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)} ` : "";
|
|
1380
1741
|
const posixBody = `#!/usr/bin/env bash
|
|
1381
|
-
exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
|
|
1742
|
+
${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
|
|
1382
1743
|
`;
|
|
1383
1744
|
writeFileSync(posixWrapper, posixBody, { mode: 493 });
|
|
1384
1745
|
if (platform === "win32") {
|
|
1385
1746
|
const cmdWrapper = path2.join(slockDir, "slock.cmd");
|
|
1747
|
+
const cmdCredentialLine = agentCredentialProxy ? `set "SLOCK_AGENT_PROXY_URL=${agentCredentialProxy.proxyUrl}"\r
|
|
1748
|
+
set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
|
|
1749
|
+
set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
|
|
1750
|
+
` : "";
|
|
1386
1751
|
const cmdBody = `@echo off\r
|
|
1387
|
-
"${process.execPath}" "${ctx.slockCliPath}" %*\r
|
|
1752
|
+
${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
|
|
1388
1753
|
`;
|
|
1389
1754
|
writeFileSync(cmdWrapper, cmdBody);
|
|
1390
1755
|
}
|
|
@@ -1395,17 +1760,27 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
|
|
|
1395
1760
|
...ctx.config.envVars || {},
|
|
1396
1761
|
...extraEnv,
|
|
1397
1762
|
...runtimeContextEnv(ctx.config),
|
|
1398
|
-
[SLOCK_HOME_ENV]:
|
|
1763
|
+
[SLOCK_HOME_ENV]: slockHome,
|
|
1399
1764
|
SLOCK_AGENT_ID: ctx.agentId,
|
|
1400
1765
|
...ctx.launchId ? { SLOCK_AGENT_LAUNCH_ID: ctx.launchId } : {},
|
|
1401
1766
|
SLOCK_SERVER_URL: ctx.config.serverUrl,
|
|
1402
|
-
SLOCK_AGENT_TOKEN_FILE: tokenFile,
|
|
1767
|
+
...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
|
|
1403
1768
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
1404
1769
|
};
|
|
1405
|
-
|
|
1770
|
+
scrubDaemonChildEnv(spawnEnv);
|
|
1771
|
+
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
|
|
1772
|
+
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
|
|
1773
|
+
delete spawnEnv.SLOCK_AGENT_PROXY_URL;
|
|
1774
|
+
delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN;
|
|
1775
|
+
delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN_FILE;
|
|
1776
|
+
delete spawnEnv.SLOCK_AGENT_ACTIVE_CAPABILITIES;
|
|
1777
|
+
if (agentCredentialProxy) {
|
|
1778
|
+
delete spawnEnv.SLOCK_AGENT_TOKEN_FILE;
|
|
1779
|
+
}
|
|
1406
1780
|
return {
|
|
1407
1781
|
slockDir,
|
|
1408
1782
|
tokenFile,
|
|
1783
|
+
agentCredentialProxyUrl: agentCredentialProxy?.proxyUrl ?? null,
|
|
1409
1784
|
wrapperPath,
|
|
1410
1785
|
spawnEnv
|
|
1411
1786
|
};
|
|
@@ -1439,7 +1814,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
|
|
|
1439
1814
|
}
|
|
1440
1815
|
function resolveCommandOnPath(command, deps = {}) {
|
|
1441
1816
|
const platform = deps.platform ?? process.platform;
|
|
1442
|
-
const env = deps.env ?? process.env;
|
|
1817
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
1443
1818
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
1444
1819
|
if (platform === "win32") {
|
|
1445
1820
|
return resolveCommandOnWindows(command, env, execFileSyncFn);
|
|
@@ -1464,7 +1839,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
1464
1839
|
return null;
|
|
1465
1840
|
}
|
|
1466
1841
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
1467
|
-
const env = deps.env ?? process.env;
|
|
1842
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
1468
1843
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
1469
1844
|
try {
|
|
1470
1845
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -1573,7 +1948,7 @@ function collectClaudeMcpConfigFiles(ctx, home) {
|
|
|
1573
1948
|
return files;
|
|
1574
1949
|
}
|
|
1575
1950
|
function buildClaudeUserMcpServers(ctx, home) {
|
|
1576
|
-
const
|
|
1951
|
+
const servers2 = /* @__PURE__ */ Object.create(null);
|
|
1577
1952
|
for (const configFile of collectClaudeMcpConfigFiles(ctx, home)) {
|
|
1578
1953
|
const mcpServers = readClaudeMcpServers(configFile.path, configFile.vars);
|
|
1579
1954
|
if (!mcpServers) continue;
|
|
@@ -1582,10 +1957,10 @@ function buildClaudeUserMcpServers(ctx, home) {
|
|
|
1582
1957
|
logger.warn(`[Claude] ignoring invalid MCP server "${name}" in ${configFile.path}`);
|
|
1583
1958
|
continue;
|
|
1584
1959
|
}
|
|
1585
|
-
|
|
1960
|
+
servers2[name] = server;
|
|
1586
1961
|
}
|
|
1587
1962
|
}
|
|
1588
|
-
return
|
|
1963
|
+
return servers2;
|
|
1589
1964
|
}
|
|
1590
1965
|
var ClaudeDriver = class {
|
|
1591
1966
|
id = "claude";
|
|
@@ -1633,9 +2008,9 @@ var ClaudeDriver = class {
|
|
|
1633
2008
|
CLAUDE_DISALLOWED_TOOLS
|
|
1634
2009
|
];
|
|
1635
2010
|
if (opts.standingPromptFilePath) {
|
|
1636
|
-
args.push("--system-prompt-file", opts.standingPromptFilePath);
|
|
2011
|
+
args.push("--append-system-prompt-file", opts.standingPromptFilePath);
|
|
1637
2012
|
} else {
|
|
1638
|
-
args.push("--system-prompt", standingPrompt);
|
|
2013
|
+
args.push("--append-system-prompt", standingPrompt);
|
|
1639
2014
|
}
|
|
1640
2015
|
if (config.sessionId) {
|
|
1641
2016
|
args.push("--resume", config.sessionId);
|
|
@@ -1883,13 +2258,29 @@ function resolveWindowsNpmCodexEntry(deps = {}) {
|
|
|
1883
2258
|
}
|
|
1884
2259
|
return null;
|
|
1885
2260
|
}
|
|
2261
|
+
function resolveWindowsCodexDesktopEntry(deps = {}) {
|
|
2262
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync4;
|
|
2263
|
+
const env = deps.env ?? process.env;
|
|
2264
|
+
const homeDir = deps.homeDir;
|
|
2265
|
+
const winPath = path5.win32;
|
|
2266
|
+
const candidates = [
|
|
2267
|
+
env.LOCALAPPDATA ? winPath.join(env.LOCALAPPDATA, "OpenAI", "Codex", "bin", "codex.exe") : null,
|
|
2268
|
+
env.USERPROFILE ? winPath.join(env.USERPROFILE, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null,
|
|
2269
|
+
homeDir ? winPath.join(homeDir, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null
|
|
2270
|
+
].filter((candidate) => Boolean(candidate));
|
|
2271
|
+
for (const candidate of candidates) {
|
|
2272
|
+
if (existsSyncFn(candidate)) return candidate;
|
|
2273
|
+
}
|
|
2274
|
+
return null;
|
|
2275
|
+
}
|
|
1886
2276
|
function resolveCodexCommand(deps = {}) {
|
|
1887
2277
|
const platform = deps.platform ?? process.platform;
|
|
1888
2278
|
if (platform === "win32") {
|
|
1889
2279
|
const npmEntry = resolveWindowsNpmCodexEntry(deps);
|
|
1890
2280
|
if (npmEntry) return npmEntry;
|
|
1891
2281
|
const command = resolveCommandOnPath("codex", deps);
|
|
1892
|
-
|
|
2282
|
+
if (command && !isWindowsSandboxRunner(command)) return command;
|
|
2283
|
+
return resolveWindowsCodexDesktopEntry(deps);
|
|
1893
2284
|
}
|
|
1894
2285
|
const pathCommand = resolveCommandOnPath("codex", deps);
|
|
1895
2286
|
if (pathCommand) return pathCommand;
|
|
@@ -1932,6 +2323,10 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
|
|
|
1932
2323
|
if (command && !isWindowsSandboxRunner(command)) {
|
|
1933
2324
|
return { command, args: commandArgs };
|
|
1934
2325
|
}
|
|
2326
|
+
const desktopEntry = resolveWindowsCodexDesktopEntry(deps);
|
|
2327
|
+
if (desktopEntry) {
|
|
2328
|
+
return { command: desktopEntry, args: commandArgs };
|
|
2329
|
+
}
|
|
1935
2330
|
throw new Error(
|
|
1936
2331
|
"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."
|
|
1937
2332
|
);
|
|
@@ -2741,7 +3136,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
2741
3136
|
}
|
|
2742
3137
|
function runCursorModelsCommand() {
|
|
2743
3138
|
return spawnSync("cursor-agent", ["models"], {
|
|
2744
|
-
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3139
|
+
env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
2745
3140
|
encoding: "utf8",
|
|
2746
3141
|
timeout: 5e3
|
|
2747
3142
|
});
|
|
@@ -2788,7 +3183,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
2788
3183
|
}
|
|
2789
3184
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
2790
3185
|
const existsSyncFn = deps.existsSyncFn ?? existsSync6;
|
|
2791
|
-
const env = deps.env ?? process.env;
|
|
3186
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
2792
3187
|
const winPath = path8.win32;
|
|
2793
3188
|
let geminiEntry = null;
|
|
2794
3189
|
try {
|
|
@@ -2962,13 +3357,16 @@ var GeminiDriver = class {
|
|
|
2962
3357
|
// src/drivers/kimi.ts
|
|
2963
3358
|
import { randomUUID } from "crypto";
|
|
2964
3359
|
import { spawn as spawn6 } from "child_process";
|
|
2965
|
-
import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
3360
|
+
import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
2966
3361
|
import os4 from "os";
|
|
2967
3362
|
import path9 from "path";
|
|
2968
3363
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
2969
3364
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
2970
3365
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
2971
3366
|
var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
|
|
3367
|
+
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
3368
|
+
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
3369
|
+
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
2972
3370
|
function parseToolArguments(raw) {
|
|
2973
3371
|
if (typeof raw !== "string") return raw;
|
|
2974
3372
|
try {
|
|
@@ -2977,6 +3375,73 @@ function parseToolArguments(raw) {
|
|
|
2977
3375
|
return raw;
|
|
2978
3376
|
}
|
|
2979
3377
|
}
|
|
3378
|
+
function readKimiConfigSource(home = os4.homedir(), env = process.env) {
|
|
3379
|
+
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3380
|
+
if (inlineConfig && inlineConfig.trim()) {
|
|
3381
|
+
return {
|
|
3382
|
+
raw: inlineConfig,
|
|
3383
|
+
explicitPath: null,
|
|
3384
|
+
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
3385
|
+
};
|
|
3386
|
+
}
|
|
3387
|
+
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3388
|
+
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
|
|
3389
|
+
try {
|
|
3390
|
+
return {
|
|
3391
|
+
raw: readFileSync3(configPath, "utf8"),
|
|
3392
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3393
|
+
sourcePath: configPath
|
|
3394
|
+
};
|
|
3395
|
+
} catch {
|
|
3396
|
+
return {
|
|
3397
|
+
raw: null,
|
|
3398
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3399
|
+
sourcePath: configPath
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
function buildKimiSpawnEnv(env = process.env) {
|
|
3404
|
+
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
3405
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3406
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3407
|
+
return scrubDaemonChildEnv(spawnEnv);
|
|
3408
|
+
}
|
|
3409
|
+
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
3410
|
+
return {
|
|
3411
|
+
...process.env,
|
|
3412
|
+
...ctx.config.envVars || {},
|
|
3413
|
+
...overrideEnv || {}
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
3417
|
+
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
3418
|
+
const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
|
|
3419
|
+
const args = [];
|
|
3420
|
+
let configFilePath = null;
|
|
3421
|
+
let configContent = null;
|
|
3422
|
+
if (source.explicitPath) {
|
|
3423
|
+
configFilePath = source.explicitPath;
|
|
3424
|
+
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
3425
|
+
configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
3426
|
+
configContent = source.raw;
|
|
3427
|
+
if (opts.writeGeneratedConfig !== false) {
|
|
3428
|
+
writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
3429
|
+
chmodSync(configFilePath, 384);
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
if (configFilePath) {
|
|
3433
|
+
args.push("--config-file", configFilePath);
|
|
3434
|
+
}
|
|
3435
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3436
|
+
args.push("--model", ctx.config.model);
|
|
3437
|
+
}
|
|
3438
|
+
return {
|
|
3439
|
+
args,
|
|
3440
|
+
env: buildKimiSpawnEnv(env),
|
|
3441
|
+
configFilePath,
|
|
3442
|
+
configContent
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
2980
3445
|
function resolveKimiSpawn(commandArgs, deps = {}) {
|
|
2981
3446
|
return {
|
|
2982
3447
|
command: resolveCommandOnPath("kimi", deps) ?? "kimi",
|
|
@@ -3000,7 +3465,25 @@ var KimiDriver = class {
|
|
|
3000
3465
|
};
|
|
3001
3466
|
model = {
|
|
3002
3467
|
detectedModelsVerifiedAs: "launchable",
|
|
3003
|
-
toLaunchSpec: (modelId) =>
|
|
3468
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
3469
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
3470
|
+
const launchCtx = {
|
|
3471
|
+
...ctx,
|
|
3472
|
+
config: {
|
|
3473
|
+
...ctx.config,
|
|
3474
|
+
model: modelId
|
|
3475
|
+
}
|
|
3476
|
+
};
|
|
3477
|
+
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
3478
|
+
home: opts?.home,
|
|
3479
|
+
writeGeneratedConfig: false
|
|
3480
|
+
});
|
|
3481
|
+
return {
|
|
3482
|
+
args: launch.args,
|
|
3483
|
+
env: launch.env,
|
|
3484
|
+
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
3485
|
+
};
|
|
3486
|
+
}
|
|
3004
3487
|
};
|
|
3005
3488
|
supportsStdinNotification = true;
|
|
3006
3489
|
mcpToolPrefix = "";
|
|
@@ -3054,6 +3537,7 @@ var KimiDriver = class {
|
|
|
3054
3537
|
}
|
|
3055
3538
|
}
|
|
3056
3539
|
}), "utf8");
|
|
3540
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
3057
3541
|
const args = [
|
|
3058
3542
|
"--wire",
|
|
3059
3543
|
"--yolo",
|
|
@@ -3062,14 +3546,15 @@ var KimiDriver = class {
|
|
|
3062
3546
|
"--mcp-config-file",
|
|
3063
3547
|
mcpConfigPath,
|
|
3064
3548
|
"--session",
|
|
3065
|
-
this.sessionId
|
|
3549
|
+
this.sessionId,
|
|
3550
|
+
...launch.args
|
|
3066
3551
|
];
|
|
3067
3552
|
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3068
3553
|
args.push("--model", ctx.config.model);
|
|
3069
3554
|
}
|
|
3070
3555
|
const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
|
|
3071
|
-
const
|
|
3072
|
-
const proc = spawn6(
|
|
3556
|
+
const spawnTarget = resolveKimiSpawn(args);
|
|
3557
|
+
const proc = spawn6(spawnTarget.command, spawnTarget.args, {
|
|
3073
3558
|
cwd: ctx.workingDirectory,
|
|
3074
3559
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3075
3560
|
env: spawnEnv,
|
|
@@ -3077,7 +3562,7 @@ var KimiDriver = class {
|
|
|
3077
3562
|
// and has an 8191-character command-line limit. Kimi's official
|
|
3078
3563
|
// installer/uv entrypoint is an executable, so launch it directly and
|
|
3079
3564
|
// keep prompts on stdin / files instead of routing through cmd.exe.
|
|
3080
|
-
shell:
|
|
3565
|
+
shell: spawnTarget.shell
|
|
3081
3566
|
});
|
|
3082
3567
|
proc.stdin?.write(JSON.stringify({
|
|
3083
3568
|
jsonrpc: "2.0",
|
|
@@ -3193,14 +3678,9 @@ var KimiDriver = class {
|
|
|
3193
3678
|
return detectKimiModels();
|
|
3194
3679
|
}
|
|
3195
3680
|
};
|
|
3196
|
-
function detectKimiModels(home = os4.homedir()) {
|
|
3197
|
-
const
|
|
3198
|
-
|
|
3199
|
-
try {
|
|
3200
|
-
raw = readFileSync3(configPath, "utf8");
|
|
3201
|
-
} catch {
|
|
3202
|
-
return null;
|
|
3203
|
-
}
|
|
3681
|
+
function detectKimiModels(home = os4.homedir(), opts = {}) {
|
|
3682
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
3683
|
+
if (raw === null) return null;
|
|
3204
3684
|
const models = [];
|
|
3205
3685
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
3206
3686
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -3297,6 +3777,18 @@ function isSupportedOpenCodeVersion(version) {
|
|
|
3297
3777
|
}
|
|
3298
3778
|
return true;
|
|
3299
3779
|
}
|
|
3780
|
+
var AGENT_FLAG_CUTOFF_VERSION = "1.15.0";
|
|
3781
|
+
function requiresAgentCliFlag(version) {
|
|
3782
|
+
if (!version) return true;
|
|
3783
|
+
const actual = parseSemver(version);
|
|
3784
|
+
const cutoff = parseSemver(AGENT_FLAG_CUTOFF_VERSION);
|
|
3785
|
+
if (!actual || !cutoff) return true;
|
|
3786
|
+
for (let i = 0; i < 3; i += 1) {
|
|
3787
|
+
if (actual[i] > cutoff[i]) return false;
|
|
3788
|
+
if (actual[i] < cutoff[i]) return true;
|
|
3789
|
+
}
|
|
3790
|
+
return false;
|
|
3791
|
+
}
|
|
3300
3792
|
function unsupportedOpenCodeVersionMessage(version) {
|
|
3301
3793
|
if (!version || isSupportedOpenCodeVersion(version)) return null;
|
|
3302
3794
|
return `OpenCode CLI ${version} is unsupported; requires OpenCode >= ${MIN_SUPPORTED_OPENCODE_VERSION}. Upgrade opencode before starting this runtime.`;
|
|
@@ -3344,7 +3836,7 @@ function buildOpenCodeConfig(ctx, home = os5.homedir()) {
|
|
|
3344
3836
|
}
|
|
3345
3837
|
};
|
|
3346
3838
|
}
|
|
3347
|
-
function buildOpenCodeLaunchOptions(ctx, home = os5.homedir()) {
|
|
3839
|
+
function buildOpenCodeLaunchOptions(ctx, home = os5.homedir(), version = null) {
|
|
3348
3840
|
const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
3349
3841
|
const config = buildOpenCodeConfig(ctx, home);
|
|
3350
3842
|
const env = {
|
|
@@ -3363,7 +3855,9 @@ function buildOpenCodeLaunchOptions(ctx, home = os5.homedir()) {
|
|
|
3363
3855
|
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3364
3856
|
args.push("--model", ctx.config.model);
|
|
3365
3857
|
}
|
|
3366
|
-
|
|
3858
|
+
if (requiresAgentCliFlag(version)) {
|
|
3859
|
+
args.push("--agent", SLOCK_AGENT_NAME);
|
|
3860
|
+
}
|
|
3367
3861
|
if (ctx.config.sessionId) {
|
|
3368
3862
|
args.push("--session", ctx.config.sessionId);
|
|
3369
3863
|
}
|
|
@@ -3447,7 +3941,7 @@ function detectOpenCodeModels(home = os5.homedir(), runCommand = runOpenCodeMode
|
|
|
3447
3941
|
}
|
|
3448
3942
|
function runOpenCodeModelsCommand(home) {
|
|
3449
3943
|
const result = spawnSync2("opencode", ["models"], {
|
|
3450
|
-
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3944
|
+
env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
3451
3945
|
encoding: "utf8",
|
|
3452
3946
|
timeout: 5e3
|
|
3453
3947
|
});
|
|
@@ -3499,7 +3993,8 @@ var OpenCodeDriver = class {
|
|
|
3499
3993
|
model: modelId
|
|
3500
3994
|
}
|
|
3501
3995
|
};
|
|
3502
|
-
const
|
|
3996
|
+
const version = readCommandVersion("opencode");
|
|
3997
|
+
const launch = buildOpenCodeLaunchOptions(launchCtx, opts?.home, version);
|
|
3503
3998
|
return {
|
|
3504
3999
|
args: launch.args,
|
|
3505
4000
|
env: launch.env,
|
|
@@ -3536,11 +4031,12 @@ var OpenCodeDriver = class {
|
|
|
3536
4031
|
spawn(ctx) {
|
|
3537
4032
|
this.sessionId = ctx.config.sessionId || null;
|
|
3538
4033
|
this.sessionAnnounced = false;
|
|
3539
|
-
const
|
|
4034
|
+
const version = readCommandVersion("opencode");
|
|
4035
|
+
const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
|
|
3540
4036
|
if (unsupportedMessage) {
|
|
3541
4037
|
throw new Error(unsupportedMessage);
|
|
3542
4038
|
}
|
|
3543
|
-
const launch = buildOpenCodeLaunchOptions(ctx);
|
|
4039
|
+
const launch = buildOpenCodeLaunchOptions(ctx, os5.homedir(), version);
|
|
3544
4040
|
const proc = spawn7("opencode", launch.args, {
|
|
3545
4041
|
cwd: ctx.workingDirectory,
|
|
3546
4042
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -3603,6 +4099,297 @@ var OpenCodeDriver = class {
|
|
|
3603
4099
|
}
|
|
3604
4100
|
};
|
|
3605
4101
|
|
|
4102
|
+
// src/drivers/pi.ts
|
|
4103
|
+
import { spawn as spawn8 } from "child_process";
|
|
4104
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
4105
|
+
import path11 from "path";
|
|
4106
|
+
import { fileURLToPath } from "url";
|
|
4107
|
+
import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
|
|
4108
|
+
var CHAT_MCP_TOOL_PREFIX2 = "chat_";
|
|
4109
|
+
var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
|
|
4110
|
+
var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
|
|
4111
|
+
var MIN_SUPPORTED_PI_VERSION = "0.74.0";
|
|
4112
|
+
function parseSemver2(version) {
|
|
4113
|
+
const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
4114
|
+
if (!match) return null;
|
|
4115
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
4116
|
+
}
|
|
4117
|
+
function isSupportedPiVersion(version) {
|
|
4118
|
+
if (!version) return true;
|
|
4119
|
+
const actual = parseSemver2(version);
|
|
4120
|
+
const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
|
|
4121
|
+
if (!actual || !minimum) return true;
|
|
4122
|
+
for (let i = 0; i < 3; i += 1) {
|
|
4123
|
+
if (actual[i] > minimum[i]) return true;
|
|
4124
|
+
if (actual[i] < minimum[i]) return false;
|
|
4125
|
+
}
|
|
4126
|
+
return true;
|
|
4127
|
+
}
|
|
4128
|
+
function unsupportedPiVersionMessage(version) {
|
|
4129
|
+
if (!version || isSupportedPiVersion(version)) return null;
|
|
4130
|
+
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.`;
|
|
4131
|
+
}
|
|
4132
|
+
function probePi(version = PI_SDK_VERSION) {
|
|
4133
|
+
const unsupportedMessage = unsupportedPiVersionMessage(version);
|
|
4134
|
+
if (unsupportedMessage) {
|
|
4135
|
+
return {
|
|
4136
|
+
available: false,
|
|
4137
|
+
version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
|
|
4138
|
+
};
|
|
4139
|
+
}
|
|
4140
|
+
return { available: true, version };
|
|
4141
|
+
}
|
|
4142
|
+
function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
|
|
4143
|
+
const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
|
|
4144
|
+
const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
|
|
4145
|
+
if (existsSync8(sourceSibling)) return sourceSibling;
|
|
4146
|
+
const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
|
|
4147
|
+
if (existsSync8(bundledEntry)) return bundledEntry;
|
|
4148
|
+
return path11.join(moduleDir, "piSdkRunner.js");
|
|
4149
|
+
}
|
|
4150
|
+
function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
|
|
4151
|
+
if (runnerPath.endsWith(".ts")) {
|
|
4152
|
+
return [...process.execArgv, runnerPath];
|
|
4153
|
+
}
|
|
4154
|
+
return [runnerPath];
|
|
4155
|
+
}
|
|
4156
|
+
function buildPiLaunchOptions(ctx, opts = {}) {
|
|
4157
|
+
const command = opts.command ?? process.execPath;
|
|
4158
|
+
const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
|
|
4159
|
+
const sessionDir = path11.join(piDir, "sessions");
|
|
4160
|
+
mkdirSync4(sessionDir, { recursive: true });
|
|
4161
|
+
const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
4162
|
+
const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
|
|
4163
|
+
const agentDir = opts.agentDir ?? getAgentDir();
|
|
4164
|
+
const runnerConfigPath = path11.join(
|
|
4165
|
+
piDir,
|
|
4166
|
+
`sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
|
|
4167
|
+
);
|
|
4168
|
+
const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
|
|
4169
|
+
const runnerConfig = {
|
|
4170
|
+
cwd: ctx.workingDirectory,
|
|
4171
|
+
agentDir,
|
|
4172
|
+
sessionDir,
|
|
4173
|
+
sessionId: ctx.config.sessionId || null,
|
|
4174
|
+
standingPrompt: ctx.standingPrompt,
|
|
4175
|
+
prompt: turnPrompt,
|
|
4176
|
+
model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
|
|
4177
|
+
};
|
|
4178
|
+
writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
|
|
4179
|
+
`, { encoding: "utf8", mode: 384 });
|
|
4180
|
+
const args = [
|
|
4181
|
+
...buildPiSdkNodeArgs(runnerPath),
|
|
4182
|
+
"--config",
|
|
4183
|
+
runnerConfigPath
|
|
4184
|
+
];
|
|
4185
|
+
return {
|
|
4186
|
+
command,
|
|
4187
|
+
args,
|
|
4188
|
+
env: slock.spawnEnv,
|
|
4189
|
+
sessionDir,
|
|
4190
|
+
agentDir,
|
|
4191
|
+
runnerConfigPath,
|
|
4192
|
+
sdkVersion: PI_SDK_VERSION
|
|
4193
|
+
};
|
|
4194
|
+
}
|
|
4195
|
+
function isSystemFirstMessageTask2(message) {
|
|
4196
|
+
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
|
|
4197
|
+
}
|
|
4198
|
+
function buildPiSystemPrompt(config) {
|
|
4199
|
+
return buildCliTransportSystemPrompt(config, {
|
|
4200
|
+
toolPrefix: CHAT_MCP_TOOL_PREFIX2,
|
|
4201
|
+
extraCriticalRules: [
|
|
4202
|
+
"- 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."
|
|
4203
|
+
],
|
|
4204
|
+
postStartupNotes: [
|
|
4205
|
+
"**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."
|
|
4206
|
+
],
|
|
4207
|
+
includeStdinNotificationSection: false,
|
|
4208
|
+
messageNotificationStyle: "poll"
|
|
4209
|
+
});
|
|
4210
|
+
}
|
|
4211
|
+
function contentText(content) {
|
|
4212
|
+
if (!content) return "";
|
|
4213
|
+
const chunks = [];
|
|
4214
|
+
for (const item of content) {
|
|
4215
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
4216
|
+
chunks.push(item.text);
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
return chunks.join("");
|
|
4220
|
+
}
|
|
4221
|
+
function apiKeyErrorMessage(line) {
|
|
4222
|
+
const trimmed = line.trim();
|
|
4223
|
+
if (!trimmed) return null;
|
|
4224
|
+
if (/no api key found/i.test(trimmed)) return trimmed;
|
|
4225
|
+
if (/api key.+required/i.test(trimmed)) return trimmed;
|
|
4226
|
+
if (/no models available/i.test(trimmed)) return trimmed;
|
|
4227
|
+
return null;
|
|
4228
|
+
}
|
|
4229
|
+
var PiDriver = class {
|
|
4230
|
+
id = "pi";
|
|
4231
|
+
lifecycle = {
|
|
4232
|
+
kind: "per_turn",
|
|
4233
|
+
start: "defer_until_concrete_message",
|
|
4234
|
+
exit: "terminate_on_turn_end",
|
|
4235
|
+
inFlightWake: "coalesce_into_pending"
|
|
4236
|
+
};
|
|
4237
|
+
communication = {
|
|
4238
|
+
chat: "slock_cli",
|
|
4239
|
+
runtimeControl: "none"
|
|
4240
|
+
};
|
|
4241
|
+
session = {
|
|
4242
|
+
recovery: "resume_or_fresh"
|
|
4243
|
+
};
|
|
4244
|
+
model = {
|
|
4245
|
+
detectedModelsVerifiedAs: "launchable",
|
|
4246
|
+
toLaunchSpec: (modelId, ctx) => {
|
|
4247
|
+
if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
|
|
4248
|
+
const launchCtx = {
|
|
4249
|
+
...ctx,
|
|
4250
|
+
config: {
|
|
4251
|
+
...ctx.config,
|
|
4252
|
+
model: modelId
|
|
4253
|
+
}
|
|
4254
|
+
};
|
|
4255
|
+
const launch = buildPiLaunchOptions(launchCtx);
|
|
4256
|
+
return {
|
|
4257
|
+
args: launch.args,
|
|
4258
|
+
env: launch.env,
|
|
4259
|
+
configFiles: [launch.runnerConfigPath],
|
|
4260
|
+
params: {
|
|
4261
|
+
agentDir: launch.agentDir,
|
|
4262
|
+
sessionDir: launch.sessionDir,
|
|
4263
|
+
sdkVersion: launch.sdkVersion,
|
|
4264
|
+
resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
|
|
4265
|
+
}
|
|
4266
|
+
};
|
|
4267
|
+
}
|
|
4268
|
+
};
|
|
4269
|
+
supportsStdinNotification = false;
|
|
4270
|
+
mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
|
|
4271
|
+
busyDeliveryMode = "none";
|
|
4272
|
+
terminateProcessOnTurnEnd = true;
|
|
4273
|
+
deferSpawnUntilMessage = true;
|
|
4274
|
+
usesSlockCliForCommunication = true;
|
|
4275
|
+
sessionId = null;
|
|
4276
|
+
sessionAnnounced = false;
|
|
4277
|
+
apiKeyErrorAnnounced = false;
|
|
4278
|
+
turnEnded = false;
|
|
4279
|
+
assistantTextByMessageId = /* @__PURE__ */ new Map();
|
|
4280
|
+
shouldDeferWakeMessage(message) {
|
|
4281
|
+
return isSystemFirstMessageTask2(message);
|
|
4282
|
+
}
|
|
4283
|
+
probe() {
|
|
4284
|
+
return probePi();
|
|
4285
|
+
}
|
|
4286
|
+
async detectModels() {
|
|
4287
|
+
return null;
|
|
4288
|
+
}
|
|
4289
|
+
spawn(ctx) {
|
|
4290
|
+
this.sessionId = ctx.config.sessionId || null;
|
|
4291
|
+
this.sessionAnnounced = false;
|
|
4292
|
+
this.apiKeyErrorAnnounced = false;
|
|
4293
|
+
this.turnEnded = false;
|
|
4294
|
+
this.assistantTextByMessageId.clear();
|
|
4295
|
+
const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
|
|
4296
|
+
if (unsupportedMessage) throw new Error(unsupportedMessage);
|
|
4297
|
+
const launch = buildPiLaunchOptions(ctx);
|
|
4298
|
+
const proc = spawn8(launch.command, launch.args, {
|
|
4299
|
+
cwd: ctx.workingDirectory,
|
|
4300
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4301
|
+
env: launch.env,
|
|
4302
|
+
shell: false
|
|
4303
|
+
});
|
|
4304
|
+
proc.stdin?.end();
|
|
4305
|
+
return { process: proc };
|
|
4306
|
+
}
|
|
4307
|
+
parseLine(line) {
|
|
4308
|
+
let event;
|
|
4309
|
+
try {
|
|
4310
|
+
event = JSON.parse(line);
|
|
4311
|
+
} catch {
|
|
4312
|
+
if (this.apiKeyErrorAnnounced) return [];
|
|
4313
|
+
const message = apiKeyErrorMessage(line);
|
|
4314
|
+
if (!message) return [];
|
|
4315
|
+
this.apiKeyErrorAnnounced = true;
|
|
4316
|
+
this.turnEnded = true;
|
|
4317
|
+
return [
|
|
4318
|
+
{ kind: "error", message },
|
|
4319
|
+
{ kind: "turn_end", sessionId: this.sessionId || void 0 }
|
|
4320
|
+
];
|
|
4321
|
+
}
|
|
4322
|
+
const events = [];
|
|
4323
|
+
if (event.type === "session" && event.id) {
|
|
4324
|
+
this.sessionId = event.id;
|
|
4325
|
+
}
|
|
4326
|
+
if (!this.sessionAnnounced && this.sessionId) {
|
|
4327
|
+
events.push({ kind: "session_init", sessionId: this.sessionId });
|
|
4328
|
+
this.sessionAnnounced = true;
|
|
4329
|
+
}
|
|
4330
|
+
switch (event.type) {
|
|
4331
|
+
case "agent_start":
|
|
4332
|
+
case "turn_start":
|
|
4333
|
+
events.push({ kind: "thinking", text: "" });
|
|
4334
|
+
break;
|
|
4335
|
+
case "message_update":
|
|
4336
|
+
case "message_end":
|
|
4337
|
+
if (event.message?.role === "assistant") {
|
|
4338
|
+
const key = event.message.id || "current";
|
|
4339
|
+
const currentText = contentText(event.message.content);
|
|
4340
|
+
const previousText = this.assistantTextByMessageId.get(key) ?? "";
|
|
4341
|
+
if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
|
|
4342
|
+
events.push({ kind: "text", text: currentText.slice(previousText.length) });
|
|
4343
|
+
} else if (currentText && currentText !== previousText) {
|
|
4344
|
+
events.push({ kind: "text", text: currentText });
|
|
4345
|
+
}
|
|
4346
|
+
this.assistantTextByMessageId.set(key, currentText);
|
|
4347
|
+
if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
|
|
4348
|
+
events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
break;
|
|
4352
|
+
case "tool_execution_start":
|
|
4353
|
+
events.push({
|
|
4354
|
+
kind: "tool_call",
|
|
4355
|
+
name: event.toolName || "unknown_tool",
|
|
4356
|
+
input: event.args
|
|
4357
|
+
});
|
|
4358
|
+
break;
|
|
4359
|
+
case "tool_execution_end":
|
|
4360
|
+
events.push({
|
|
4361
|
+
kind: "tool_output",
|
|
4362
|
+
name: event.toolName || "unknown_tool"
|
|
4363
|
+
});
|
|
4364
|
+
if (event.isError) {
|
|
4365
|
+
events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
|
|
4366
|
+
}
|
|
4367
|
+
break;
|
|
4368
|
+
case "compaction_start":
|
|
4369
|
+
events.push({ kind: "compaction_started" });
|
|
4370
|
+
break;
|
|
4371
|
+
case "compaction_end":
|
|
4372
|
+
events.push({ kind: "compaction_finished" });
|
|
4373
|
+
if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
|
|
4374
|
+
break;
|
|
4375
|
+
case "turn_end":
|
|
4376
|
+
case "agent_end":
|
|
4377
|
+
if (!this.turnEnded) {
|
|
4378
|
+
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
4379
|
+
this.turnEnded = true;
|
|
4380
|
+
}
|
|
4381
|
+
break;
|
|
4382
|
+
}
|
|
4383
|
+
return events;
|
|
4384
|
+
}
|
|
4385
|
+
encodeStdinMessage(_text, _sessionId, _opts) {
|
|
4386
|
+
return null;
|
|
4387
|
+
}
|
|
4388
|
+
buildSystemPrompt(config, _agentId) {
|
|
4389
|
+
return buildPiSystemPrompt(config);
|
|
4390
|
+
}
|
|
4391
|
+
};
|
|
4392
|
+
|
|
3606
4393
|
// src/drivers/index.ts
|
|
3607
4394
|
var driverFactories = {
|
|
3608
4395
|
claude: () => new ClaudeDriver(),
|
|
@@ -3611,7 +4398,8 @@ var driverFactories = {
|
|
|
3611
4398
|
cursor: () => new CursorDriver(),
|
|
3612
4399
|
gemini: () => new GeminiDriver(),
|
|
3613
4400
|
kimi: () => new KimiDriver(),
|
|
3614
|
-
opencode: () => new OpenCodeDriver()
|
|
4401
|
+
opencode: () => new OpenCodeDriver(),
|
|
4402
|
+
pi: () => new PiDriver()
|
|
3615
4403
|
};
|
|
3616
4404
|
function getDriver(runtimeId) {
|
|
3617
4405
|
const createDriver = driverFactories[runtimeId];
|
|
@@ -3624,7 +4412,7 @@ function getDriver(runtimeId) {
|
|
|
3624
4412
|
|
|
3625
4413
|
// src/workspaces.ts
|
|
3626
4414
|
import { readdir, rm, stat } from "fs/promises";
|
|
3627
|
-
import
|
|
4415
|
+
import path12 from "path";
|
|
3628
4416
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
3629
4417
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
3630
4418
|
}
|
|
@@ -3632,7 +4420,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
3632
4420
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
3633
4421
|
return null;
|
|
3634
4422
|
}
|
|
3635
|
-
return
|
|
4423
|
+
return path12.join(dataDir, directoryName);
|
|
3636
4424
|
}
|
|
3637
4425
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
3638
4426
|
return {
|
|
@@ -3681,7 +4469,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
3681
4469
|
return summary;
|
|
3682
4470
|
}
|
|
3683
4471
|
const childSummaries = await Promise.all(
|
|
3684
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
4472
|
+
entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
|
|
3685
4473
|
);
|
|
3686
4474
|
for (const childSummary of childSummaries) {
|
|
3687
4475
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -3700,7 +4488,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
3700
4488
|
if (!entry.isDirectory()) {
|
|
3701
4489
|
return null;
|
|
3702
4490
|
}
|
|
3703
|
-
const dirPath =
|
|
4491
|
+
const dirPath = path12.join(dataDir, entry.name);
|
|
3704
4492
|
try {
|
|
3705
4493
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
3706
4494
|
return {
|
|
@@ -3813,6 +4601,8 @@ function redactUrlQuery(value) {
|
|
|
3813
4601
|
// src/agentProcessManager.ts
|
|
3814
4602
|
var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
|
|
3815
4603
|
var DEFAULT_AGENT_START_INTERVAL_MS = 500;
|
|
4604
|
+
var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS = 3;
|
|
4605
|
+
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS = 250;
|
|
3816
4606
|
var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
|
|
3817
4607
|
var WORKSPACE_IMAGE_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
|
|
3818
4608
|
var WORKSPACE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
@@ -3857,6 +4647,41 @@ function readNonNegativeIntegerEnv(name, fallback) {
|
|
|
3857
4647
|
if (!Number.isFinite(parsed) || parsed < 0) return fallback;
|
|
3858
4648
|
return Math.floor(parsed);
|
|
3859
4649
|
}
|
|
4650
|
+
var RunnerCredentialMintError = class extends Error {
|
|
4651
|
+
code;
|
|
4652
|
+
retryable;
|
|
4653
|
+
status;
|
|
4654
|
+
constructor(message, opts) {
|
|
4655
|
+
super(message);
|
|
4656
|
+
this.name = "RunnerCredentialMintError";
|
|
4657
|
+
this.code = opts.code;
|
|
4658
|
+
this.retryable = opts.retryable ?? false;
|
|
4659
|
+
this.status = opts.status;
|
|
4660
|
+
}
|
|
4661
|
+
};
|
|
4662
|
+
function isRetryableMintHttpFailure(status, code) {
|
|
4663
|
+
if (code === "experimental_surface_disabled") return false;
|
|
4664
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
4665
|
+
}
|
|
4666
|
+
function runnerCredentialErrorDetail(error) {
|
|
4667
|
+
if (error instanceof RunnerCredentialMintError) {
|
|
4668
|
+
return {
|
|
4669
|
+
message: error.message,
|
|
4670
|
+
code: error.code,
|
|
4671
|
+
retryable: error.retryable,
|
|
4672
|
+
status: error.status
|
|
4673
|
+
};
|
|
4674
|
+
}
|
|
4675
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4676
|
+
return {
|
|
4677
|
+
message,
|
|
4678
|
+
code: "runner_credential_mint_network_error",
|
|
4679
|
+
retryable: true
|
|
4680
|
+
};
|
|
4681
|
+
}
|
|
4682
|
+
async function waitForRunnerCredentialRetry() {
|
|
4683
|
+
await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS));
|
|
4684
|
+
}
|
|
3860
4685
|
function stalledRecoverySigtermTimeoutMs() {
|
|
3861
4686
|
return readNonNegativeIntegerEnv(
|
|
3862
4687
|
"SLOCK_DAEMON_STALLED_RECOVERY_SIGTERM_TIMEOUT_MS",
|
|
@@ -3885,6 +4710,22 @@ function formatMessageTarget(message) {
|
|
|
3885
4710
|
}
|
|
3886
4711
|
return `#${message.channel_name}`;
|
|
3887
4712
|
}
|
|
4713
|
+
function formatVisibleMessageTarget(message) {
|
|
4714
|
+
if (message.channel_type === "thread" && message.parent_channel_name && message.channel_name) {
|
|
4715
|
+
const shortId = getMessageShortId(String(message.channel_name));
|
|
4716
|
+
if (message.parent_channel_type === "dm") {
|
|
4717
|
+
return `dm:@${message.parent_channel_name}:${shortId}`;
|
|
4718
|
+
}
|
|
4719
|
+
return `#${message.parent_channel_name}:${shortId}`;
|
|
4720
|
+
}
|
|
4721
|
+
if (message.channel_type === "dm" && message.channel_name) {
|
|
4722
|
+
return `dm:@${message.channel_name}`;
|
|
4723
|
+
}
|
|
4724
|
+
if (message.channel_name) {
|
|
4725
|
+
return `#${message.channel_name}`;
|
|
4726
|
+
}
|
|
4727
|
+
return null;
|
|
4728
|
+
}
|
|
3888
4729
|
function getMessageShortId(messageId) {
|
|
3889
4730
|
return messageId.startsWith("thread-") ? messageId.slice(7) : messageId.slice(0, 8);
|
|
3890
4731
|
}
|
|
@@ -3904,12 +4745,12 @@ function findSessionJsonl(root, predicate) {
|
|
|
3904
4745
|
for (const entry of entries) {
|
|
3905
4746
|
if (++visited > maxEntries) return null;
|
|
3906
4747
|
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
3907
|
-
return
|
|
4748
|
+
return path13.join(dir, entry.name);
|
|
3908
4749
|
}
|
|
3909
4750
|
for (const entry of entries) {
|
|
3910
4751
|
if (++visited > maxEntries) return null;
|
|
3911
4752
|
if (!entry.isDirectory()) continue;
|
|
3912
|
-
const found = visit(
|
|
4753
|
+
const found = visit(path13.join(dir, entry.name), depth - 1);
|
|
3913
4754
|
if (found) return found;
|
|
3914
4755
|
}
|
|
3915
4756
|
return null;
|
|
@@ -3922,10 +4763,10 @@ function safeSessionFilename(value) {
|
|
|
3922
4763
|
}
|
|
3923
4764
|
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
3924
4765
|
try {
|
|
3925
|
-
const dir =
|
|
3926
|
-
|
|
3927
|
-
const filePath =
|
|
3928
|
-
|
|
4766
|
+
const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
|
|
4767
|
+
mkdirSync5(dir, { recursive: true });
|
|
4768
|
+
const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
4769
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
3929
4770
|
type: "runtime_session_handoff",
|
|
3930
4771
|
runtime,
|
|
3931
4772
|
sessionId,
|
|
@@ -3944,7 +4785,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
3944
4785
|
}
|
|
3945
4786
|
}
|
|
3946
4787
|
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
|
|
3947
|
-
const directPath =
|
|
4788
|
+
const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
|
|
3948
4789
|
if (directPath) {
|
|
3949
4790
|
try {
|
|
3950
4791
|
if (statSync2(directPath).isFile()) {
|
|
@@ -3953,7 +4794,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
|
|
|
3953
4794
|
} catch {
|
|
3954
4795
|
}
|
|
3955
4796
|
}
|
|
3956
|
-
const resolvedPath = runtime === "claude" ? findSessionJsonl(
|
|
4797
|
+
const resolvedPath = runtime === "claude" ? findSessionJsonl(path13.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path13.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
|
|
3957
4798
|
if (!resolvedPath && fallbackDir) {
|
|
3958
4799
|
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
3959
4800
|
if (fallback) return fallback;
|
|
@@ -4505,6 +5346,14 @@ function createRuntimeTraceCounters() {
|
|
|
4505
5346
|
thinkingEvents: 0
|
|
4506
5347
|
};
|
|
4507
5348
|
}
|
|
5349
|
+
function cleanupAgentCredentialProxy(agentId, launchId) {
|
|
5350
|
+
unregisterAgentCredentialProxyForLaunch({ agentId, launchId });
|
|
5351
|
+
}
|
|
5352
|
+
function stripManagedRunnerCredential(config) {
|
|
5353
|
+
if (!config.agentCredentialKey && !config.agentCredentialId) return config;
|
|
5354
|
+
const { agentCredentialKey: _agentCredentialKey, agentCredentialId: _agentCredentialId, ...rest } = config;
|
|
5355
|
+
return rest;
|
|
5356
|
+
}
|
|
4508
5357
|
function createGatedSteeringState() {
|
|
4509
5358
|
return {
|
|
4510
5359
|
phase: "idle",
|
|
@@ -4836,6 +5685,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4836
5685
|
tracer;
|
|
4837
5686
|
deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
|
|
4838
5687
|
processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
|
|
5688
|
+
agentVisibleBoundaries = /* @__PURE__ */ new Map();
|
|
4839
5689
|
constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
|
|
4840
5690
|
this.chatBridgePath = chatBridgePath;
|
|
4841
5691
|
this.slockCliPath = opts.slockCliPath ?? "";
|
|
@@ -4863,6 +5713,97 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
4863
5713
|
setTracer(tracer) {
|
|
4864
5714
|
this.tracer = tracer;
|
|
4865
5715
|
}
|
|
5716
|
+
visibleBoundaryMap(agentId) {
|
|
5717
|
+
let map = this.agentVisibleBoundaries.get(agentId);
|
|
5718
|
+
if (!map) {
|
|
5719
|
+
map = /* @__PURE__ */ new Map();
|
|
5720
|
+
this.agentVisibleBoundaries.set(agentId, map);
|
|
5721
|
+
}
|
|
5722
|
+
return map;
|
|
5723
|
+
}
|
|
5724
|
+
getVisibleBoundary(agentId, target) {
|
|
5725
|
+
return this.agentVisibleBoundaries.get(agentId)?.get(target);
|
|
5726
|
+
}
|
|
5727
|
+
pendingVisibleMessages(agentId, target) {
|
|
5728
|
+
const collect = (messages) => (messages ?? []).filter((message) => formatMessageTarget(message) === target && typeof message.seq === "number" && message.seq > 0);
|
|
5729
|
+
return [
|
|
5730
|
+
...collect(this.agents.get(agentId)?.inbox),
|
|
5731
|
+
...collect(this.startingInboxes.get(agentId))
|
|
5732
|
+
];
|
|
5733
|
+
}
|
|
5734
|
+
/**
|
|
5735
|
+
* Single-inbox consume point for messages that have been rendered into the
|
|
5736
|
+
* agent-visible input surface. Daemon pending inbox is only a runner cache:
|
|
5737
|
+
* once a message is visible through wake/stdin/check/read/held/preflight, the
|
|
5738
|
+
* same seq must not be injected later through the local pending queue.
|
|
5739
|
+
*/
|
|
5740
|
+
consumeVisibleMessages(agentId, input) {
|
|
5741
|
+
if (input.messages.length === 0 && (!input.target || typeof input.boundarySeq !== "number")) return;
|
|
5742
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
5743
|
+
const ensureBucket = (target) => {
|
|
5744
|
+
let bucket = byTarget.get(target);
|
|
5745
|
+
if (!bucket) {
|
|
5746
|
+
bucket = { maxSeq: 0, boundarySeq: 0, seqs: /* @__PURE__ */ new Set(), ids: /* @__PURE__ */ new Set() };
|
|
5747
|
+
byTarget.set(target, bucket);
|
|
5748
|
+
}
|
|
5749
|
+
return bucket;
|
|
5750
|
+
};
|
|
5751
|
+
for (const message of input.messages) {
|
|
5752
|
+
const seq = Number(message.seq ?? 0);
|
|
5753
|
+
if (!Number.isFinite(seq) || seq <= 0) continue;
|
|
5754
|
+
const target = input.target ?? formatVisibleMessageTarget(message);
|
|
5755
|
+
if (!target) continue;
|
|
5756
|
+
const bucket = ensureBucket(target);
|
|
5757
|
+
bucket.maxSeq = Math.max(bucket.maxSeq, Math.floor(seq));
|
|
5758
|
+
bucket.seqs.add(Math.floor(seq));
|
|
5759
|
+
const id = typeof message.id === "string" ? message.id : typeof message.message_id === "string" ? message.message_id : null;
|
|
5760
|
+
if (id) bucket.ids.add(id);
|
|
5761
|
+
}
|
|
5762
|
+
if (input.target && typeof input.boundarySeq === "number" && Number.isFinite(input.boundarySeq) && input.boundarySeq > 0) {
|
|
5763
|
+
const bucket = ensureBucket(input.target);
|
|
5764
|
+
bucket.boundarySeq = Math.max(bucket.boundarySeq, Math.floor(input.boundarySeq));
|
|
5765
|
+
}
|
|
5766
|
+
if (byTarget.size === 0) return;
|
|
5767
|
+
const boundaryMap = this.visibleBoundaryMap(agentId);
|
|
5768
|
+
for (const [target, bucket] of byTarget) {
|
|
5769
|
+
const highWaterSeq = Math.max(bucket.maxSeq, bucket.boundarySeq);
|
|
5770
|
+
const previous = boundaryMap.get(target) ?? 0;
|
|
5771
|
+
boundaryMap.set(target, Math.max(previous, highWaterSeq));
|
|
5772
|
+
}
|
|
5773
|
+
const suppress = (messages) => {
|
|
5774
|
+
if (!messages || messages.length === 0) return 0;
|
|
5775
|
+
let removed = 0;
|
|
5776
|
+
const retained = messages.filter((message) => {
|
|
5777
|
+
const target = formatMessageTarget(message);
|
|
5778
|
+
const bucket = byTarget.get(target);
|
|
5779
|
+
if (!bucket) return true;
|
|
5780
|
+
const seq = typeof message.seq === "number" ? Math.floor(message.seq) : 0;
|
|
5781
|
+
const id = message.message_id ?? "";
|
|
5782
|
+
const matched = seq > 0 && bucket.boundarySeq > 0 && seq <= bucket.boundarySeq || seq > 0 && bucket.seqs.has(seq) || id.length > 0 && bucket.ids.has(id);
|
|
5783
|
+
if (matched) removed += 1;
|
|
5784
|
+
return !matched;
|
|
5785
|
+
});
|
|
5786
|
+
messages.splice(0, messages.length, ...retained);
|
|
5787
|
+
return removed;
|
|
5788
|
+
};
|
|
5789
|
+
const active = this.agents.get(agentId);
|
|
5790
|
+
const removedActive = suppress(active?.inbox);
|
|
5791
|
+
const removedStarting = suppress(this.startingInboxes.get(agentId));
|
|
5792
|
+
this.recordDaemonTrace("daemon.agent.inbox.visible_consumed", {
|
|
5793
|
+
agentId,
|
|
5794
|
+
source: input.source,
|
|
5795
|
+
targets: [...byTarget.keys()],
|
|
5796
|
+
messages_count: input.messages.length,
|
|
5797
|
+
suppressed_pending_count: removedActive + removedStarting
|
|
5798
|
+
});
|
|
5799
|
+
}
|
|
5800
|
+
createAgentProxyInboxCoordinator(agentId) {
|
|
5801
|
+
return {
|
|
5802
|
+
getBoundary: (target) => this.getVisibleBoundary(agentId, target),
|
|
5803
|
+
getPendingMessages: (target) => this.pendingVisibleMessages(agentId, target),
|
|
5804
|
+
consumeVisibleMessages: (input) => this.consumeVisibleMessages(agentId, input)
|
|
5805
|
+
};
|
|
5806
|
+
}
|
|
4866
5807
|
recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
|
|
4867
5808
|
const span = this.tracer.startSpan(name, {
|
|
4868
5809
|
parent: parseTraceparent(parentTraceparent),
|
|
@@ -5087,26 +6028,26 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
5087
6028
|
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
5088
6029
|
try {
|
|
5089
6030
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
5090
|
-
const agentDataDir =
|
|
6031
|
+
const agentDataDir = path13.join(this.dataDir, agentId);
|
|
5091
6032
|
await mkdir(agentDataDir, { recursive: true });
|
|
5092
6033
|
const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
5093
|
-
const memoryMdPath =
|
|
6034
|
+
const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
|
|
5094
6035
|
try {
|
|
5095
6036
|
await access(memoryMdPath);
|
|
5096
6037
|
} catch {
|
|
5097
6038
|
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
5098
6039
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
5099
6040
|
}
|
|
5100
|
-
const notesDir =
|
|
6041
|
+
const notesDir = path13.join(agentDataDir, "notes");
|
|
5101
6042
|
await mkdir(notesDir, { recursive: true });
|
|
5102
6043
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
5103
6044
|
const seedFiles = buildOnboardingSeedFiles();
|
|
5104
6045
|
for (const { relativePath, content } of seedFiles) {
|
|
5105
|
-
const fullPath =
|
|
6046
|
+
const fullPath = path13.join(agentDataDir, relativePath);
|
|
5106
6047
|
try {
|
|
5107
6048
|
await access(fullPath);
|
|
5108
6049
|
} catch {
|
|
5109
|
-
await mkdir(
|
|
6050
|
+
await mkdir(path13.dirname(fullPath), { recursive: true });
|
|
5110
6051
|
await writeFile(fullPath, content);
|
|
5111
6052
|
}
|
|
5112
6053
|
}
|
|
@@ -5214,7 +6155,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5214
6155
|
chatBridgePath: this.chatBridgePath,
|
|
5215
6156
|
slockCliPath: this.slockCliPath,
|
|
5216
6157
|
daemonApiKey: this.daemonApiKey,
|
|
5217
|
-
launchId: launchId || null
|
|
6158
|
+
launchId: launchId || null,
|
|
6159
|
+
agentCredentialProxyInboxCoordinator: this.createAgentProxyInboxCoordinator(agentId)
|
|
5218
6160
|
});
|
|
5219
6161
|
this.recordDaemonTrace("daemon.agent.spawn.created", {
|
|
5220
6162
|
...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
@@ -5270,6 +6212,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5270
6212
|
this.ackInjectedRuntimeProfileControl(agentId, config.runtimeProfileControl, agentProcess.launchId);
|
|
5271
6213
|
}
|
|
5272
6214
|
if (wakeMessage) {
|
|
6215
|
+
this.consumeVisibleMessages(agentId, { messages: [wakeMessage], source: "spawn_wake_message" });
|
|
5273
6216
|
this.ackInjectedRuntimeProfileMessages(agentId, [wakeMessage], agentProcess.launchId);
|
|
5274
6217
|
}
|
|
5275
6218
|
let buffer = "";
|
|
@@ -5362,16 +6305,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5362
6305
|
...runtimeTraceCounterAttrs(ap),
|
|
5363
6306
|
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
|
|
5364
6307
|
});
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
this.clearCompactionWatchdog(ap);
|
|
5369
|
-
}
|
|
6308
|
+
this.interruptCompactionIfActive(agentId);
|
|
6309
|
+
cleanupAgentCredentialProxy(agentId, ap.launchId);
|
|
6310
|
+
this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
|
|
5370
6311
|
this.agents.delete(agentId);
|
|
5371
6312
|
if (missingResumeSession) {
|
|
5372
6313
|
const staleSessionId = ap.sessionId;
|
|
5373
6314
|
const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : "Claude";
|
|
5374
|
-
const restartConfig = { ...ap.config, sessionId: null };
|
|
6315
|
+
const restartConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: null };
|
|
5375
6316
|
logger.warn(
|
|
5376
6317
|
`[Agent ${agentId}] Stored ${runtimeLabel} session ${staleSessionId} is unavailable locally; falling back to cold start`
|
|
5377
6318
|
);
|
|
@@ -5408,7 +6349,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5408
6349
|
const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
|
|
5409
6350
|
if (queuedWakeMessage) {
|
|
5410
6351
|
logger.info(`[Agent ${agentId}] Turn completed; restarting immediately for queued message`);
|
|
5411
|
-
const nextConfig = { ...ap.config, sessionId: ap.sessionId };
|
|
6352
|
+
const nextConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId };
|
|
5412
6353
|
this.idleAgentConfigs.set(agentId, {
|
|
5413
6354
|
config: nextConfig,
|
|
5414
6355
|
sessionId: ap.sessionId,
|
|
@@ -5418,6 +6359,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5418
6359
|
this.idleAgentConfigs.delete(agentId);
|
|
5419
6360
|
this.startAgent(agentId, nextConfig, queuedWakeMessage, unreadSummary2, void 0, ap.launchId || void 0).catch((err) => {
|
|
5420
6361
|
logger.error(`[Agent ${agentId}] Failed to continue with queued message`, err);
|
|
6362
|
+
if (this.reportRunnerCredentialMintFailure(agentId, err, ap.launchId, "queued_continuation")) {
|
|
6363
|
+
return;
|
|
6364
|
+
}
|
|
5421
6365
|
this.idleAgentConfigs.set(agentId, {
|
|
5422
6366
|
config: nextConfig,
|
|
5423
6367
|
sessionId: ap.sessionId,
|
|
@@ -5428,7 +6372,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5428
6372
|
return;
|
|
5429
6373
|
}
|
|
5430
6374
|
this.idleAgentConfigs.set(agentId, {
|
|
5431
|
-
config: { ...ap.config, sessionId: ap.sessionId },
|
|
6375
|
+
config: { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId },
|
|
5432
6376
|
sessionId: ap.sessionId,
|
|
5433
6377
|
launchId: ap.launchId
|
|
5434
6378
|
});
|
|
@@ -5464,27 +6408,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5464
6408
|
}
|
|
5465
6409
|
async buildSpawnConfig(agentId, config) {
|
|
5466
6410
|
const baseConfig = config.serverUrl === this.serverUrl ? config : { ...config, serverUrl: this.serverUrl };
|
|
6411
|
+
const runnerConfig = await this.ensureManagedRunnerCredential(agentId, baseConfig);
|
|
5467
6412
|
if (!this.defaultAgentEnvVarsProvider) {
|
|
5468
|
-
return
|
|
6413
|
+
return runnerConfig;
|
|
5469
6414
|
}
|
|
5470
6415
|
try {
|
|
5471
6416
|
const defaultEnvVars = await this.defaultAgentEnvVarsProvider({
|
|
5472
|
-
runtime:
|
|
5473
|
-
model:
|
|
5474
|
-
envVars:
|
|
6417
|
+
runtime: runnerConfig.runtime,
|
|
6418
|
+
model: runnerConfig.model,
|
|
6419
|
+
envVars: runnerConfig.envVars
|
|
5475
6420
|
});
|
|
5476
6421
|
if (!defaultEnvVars || Object.keys(defaultEnvVars).length === 0) {
|
|
5477
|
-
return
|
|
6422
|
+
return runnerConfig;
|
|
5478
6423
|
}
|
|
5479
6424
|
const mergedEnvVars = {
|
|
5480
6425
|
...defaultEnvVars,
|
|
5481
|
-
...
|
|
6426
|
+
...runnerConfig.envVars ?? {}
|
|
5482
6427
|
};
|
|
5483
|
-
if (this.sameEnvVars(mergedEnvVars,
|
|
5484
|
-
return
|
|
6428
|
+
if (this.sameEnvVars(mergedEnvVars, runnerConfig.envVars)) {
|
|
6429
|
+
return runnerConfig;
|
|
5485
6430
|
}
|
|
5486
6431
|
return {
|
|
5487
|
-
...
|
|
6432
|
+
...runnerConfig,
|
|
5488
6433
|
envVars: mergedEnvVars
|
|
5489
6434
|
};
|
|
5490
6435
|
} catch (error) {
|
|
@@ -5492,8 +6437,130 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5492
6437
|
logger.warn(
|
|
5493
6438
|
`[Agent ${agentId}] Failed to resolve default runtime env vars \u2014 continuing without machine-level defaults (${reason})`
|
|
5494
6439
|
);
|
|
5495
|
-
return
|
|
6440
|
+
return runnerConfig;
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
async requestManagedRunnerCredentialOnce(agentId, config) {
|
|
6444
|
+
const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.serverUrl);
|
|
6445
|
+
const res = await fetch(url, {
|
|
6446
|
+
method: "POST",
|
|
6447
|
+
headers: {
|
|
6448
|
+
Authorization: `Bearer ${this.daemonApiKey}`,
|
|
6449
|
+
"Content-Type": "application/json",
|
|
6450
|
+
"X-Slock-Client": "daemon-server-session-worker"
|
|
6451
|
+
},
|
|
6452
|
+
body: JSON.stringify({
|
|
6453
|
+
scopes: ["send", "read", "mentions", "tasks", "reactions", "server", "channels"],
|
|
6454
|
+
name: `runner:${config.runtime}:${agentId.slice(0, 8)}`
|
|
6455
|
+
})
|
|
6456
|
+
});
|
|
6457
|
+
if (!res.ok) {
|
|
6458
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
6459
|
+
let detail = `HTTP ${res.status}`;
|
|
6460
|
+
let code = null;
|
|
6461
|
+
if (contentType.includes("application/json")) {
|
|
6462
|
+
const body2 = await res.json().catch(() => null);
|
|
6463
|
+
const error = typeof body2?.error === "string" ? body2.error : null;
|
|
6464
|
+
code = typeof body2?.code === "string" ? body2.code : null;
|
|
6465
|
+
detail = [detail, code, error].filter(Boolean).join(" ");
|
|
6466
|
+
}
|
|
6467
|
+
throw new RunnerCredentialMintError(detail, {
|
|
6468
|
+
code: code ?? "runner_credential_mint_http_error",
|
|
6469
|
+
retryable: isRetryableMintHttpFailure(res.status, code),
|
|
6470
|
+
status: res.status
|
|
6471
|
+
});
|
|
6472
|
+
}
|
|
6473
|
+
const body = await res.json().catch(() => null);
|
|
6474
|
+
if (typeof body?.apiKey !== "string" || !body.apiKey.startsWith("sk_agent_")) {
|
|
6475
|
+
throw new RunnerCredentialMintError("invalid_agent_credential_payload", {
|
|
6476
|
+
code: "invalid_agent_credential_payload"
|
|
6477
|
+
});
|
|
6478
|
+
}
|
|
6479
|
+
return {
|
|
6480
|
+
apiKey: body.apiKey,
|
|
6481
|
+
credentialId: typeof body.credentialId === "string" ? body.credentialId : null
|
|
6482
|
+
};
|
|
6483
|
+
}
|
|
6484
|
+
async ensureManagedRunnerCredential(agentId, config) {
|
|
6485
|
+
if (config.agentCredentialKey) return config;
|
|
6486
|
+
if (process.env.SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED === "1") {
|
|
6487
|
+
throw new RunnerCredentialMintError("runner credential mint is disabled by SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED", {
|
|
6488
|
+
code: "runner_credentials_disabled"
|
|
6489
|
+
});
|
|
5496
6490
|
}
|
|
6491
|
+
let lastError = null;
|
|
6492
|
+
for (let attempt = 1; attempt <= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS; attempt += 1) {
|
|
6493
|
+
try {
|
|
6494
|
+
const credential = await this.requestManagedRunnerCredentialOnce(agentId, config);
|
|
6495
|
+
return {
|
|
6496
|
+
...config,
|
|
6497
|
+
agentCredentialKey: credential.apiKey,
|
|
6498
|
+
agentCredentialId: credential.credentialId
|
|
6499
|
+
};
|
|
6500
|
+
} catch (err) {
|
|
6501
|
+
lastError = err;
|
|
6502
|
+
const detail2 = runnerCredentialErrorDetail(err);
|
|
6503
|
+
this.recordDaemonTrace("daemon.runner_credential_mint.retry", {
|
|
6504
|
+
agentId,
|
|
6505
|
+
runtime: config.runtime,
|
|
6506
|
+
attempt,
|
|
6507
|
+
max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS,
|
|
6508
|
+
status: detail2.status,
|
|
6509
|
+
code: detail2.code,
|
|
6510
|
+
reason: detail2.message,
|
|
6511
|
+
retryable: detail2.retryable
|
|
6512
|
+
}, detail2.retryable && attempt < RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS ? "ok" : "error");
|
|
6513
|
+
if (!detail2.retryable || attempt >= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS) break;
|
|
6514
|
+
await waitForRunnerCredentialRetry();
|
|
6515
|
+
}
|
|
6516
|
+
}
|
|
6517
|
+
const detail = runnerCredentialErrorDetail(lastError);
|
|
6518
|
+
this.recordDaemonTrace("daemon.runner_credential_mint.failed", {
|
|
6519
|
+
agentId,
|
|
6520
|
+
runtime: config.runtime,
|
|
6521
|
+
status: detail.status,
|
|
6522
|
+
code: detail.code,
|
|
6523
|
+
reason: detail.message,
|
|
6524
|
+
retryable: detail.retryable,
|
|
6525
|
+
max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS
|
|
6526
|
+
}, "error");
|
|
6527
|
+
throw new RunnerCredentialMintError(
|
|
6528
|
+
`runner_credential_mint_failed: ${detail.message}. Managed runner startup requires /internal/computer credential mint; deploy server first or roll back the daemon binary.`,
|
|
6529
|
+
{
|
|
6530
|
+
code: detail.code,
|
|
6531
|
+
retryable: detail.retryable,
|
|
6532
|
+
status: detail.status
|
|
6533
|
+
}
|
|
6534
|
+
);
|
|
6535
|
+
}
|
|
6536
|
+
revokeManagedRunnerCredential(agentId, config, launchId) {
|
|
6537
|
+
const credentialId = config.agentCredentialId;
|
|
6538
|
+
if (!credentialId) return;
|
|
6539
|
+
const url = new URL(
|
|
6540
|
+
`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials/${encodeURIComponent(credentialId)}`,
|
|
6541
|
+
this.serverUrl
|
|
6542
|
+
);
|
|
6543
|
+
void fetch(url, {
|
|
6544
|
+
method: "DELETE",
|
|
6545
|
+
headers: {
|
|
6546
|
+
Authorization: `Bearer ${this.daemonApiKey}`,
|
|
6547
|
+
"X-Slock-Client": "daemon-server-session-worker"
|
|
6548
|
+
}
|
|
6549
|
+
}).then((res) => {
|
|
6550
|
+
this.recordDaemonTrace("daemon.runner_credential.revoke", {
|
|
6551
|
+
agentId,
|
|
6552
|
+
launchId: launchId || void 0,
|
|
6553
|
+
credentialId,
|
|
6554
|
+
status: res.status
|
|
6555
|
+
}, res.ok ? "ok" : "error");
|
|
6556
|
+
}).catch((err) => {
|
|
6557
|
+
this.recordDaemonTrace("daemon.runner_credential.revoke", {
|
|
6558
|
+
agentId,
|
|
6559
|
+
launchId: launchId || void 0,
|
|
6560
|
+
credentialId,
|
|
6561
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
6562
|
+
}, "error");
|
|
6563
|
+
});
|
|
5497
6564
|
}
|
|
5498
6565
|
sameEnvVars(left, right) {
|
|
5499
6566
|
const leftKeys = Object.keys(left ?? {});
|
|
@@ -5583,6 +6650,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5583
6650
|
}
|
|
5584
6651
|
this.clearCompactionWatchdog(ap);
|
|
5585
6652
|
this.clearStalledRecoverySigtermWatchdog(ap);
|
|
6653
|
+
cleanupAgentCredentialProxy(agentId, ap.launchId);
|
|
6654
|
+
this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
|
|
5586
6655
|
this.agents.delete(agentId);
|
|
5587
6656
|
this.processExitTraceAttrs.set(ap.process, {
|
|
5588
6657
|
stop_source: silent ? "daemon_internal" : "explicit_request",
|
|
@@ -5666,10 +6735,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5666
6735
|
session_id_present: Boolean(cached.sessionId),
|
|
5667
6736
|
launchId: cached.launchId || void 0
|
|
5668
6737
|
}));
|
|
5669
|
-
this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).
|
|
6738
|
+
return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
|
|
5670
6739
|
logger.error(`[Agent ${agentId}] Failed to auto-restart`, err);
|
|
6740
|
+
if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "idle_auto_restart")) {
|
|
6741
|
+
return false;
|
|
6742
|
+
}
|
|
6743
|
+
this.idleAgentConfigs.set(agentId, cached);
|
|
6744
|
+
return false;
|
|
5671
6745
|
});
|
|
5672
|
-
return true;
|
|
5673
6746
|
}
|
|
5674
6747
|
logger.warn(`[Agent ${agentId}] Delivery received but no running process or cached idle config exists`);
|
|
5675
6748
|
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
@@ -5751,6 +6824,37 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5751
6824
|
}));
|
|
5752
6825
|
return true;
|
|
5753
6826
|
}
|
|
6827
|
+
if (ap.gatedSteering.compacting) {
|
|
6828
|
+
ap.pendingNotificationCount++;
|
|
6829
|
+
if (ap.notificationTimer) {
|
|
6830
|
+
clearTimeout(ap.notificationTimer);
|
|
6831
|
+
ap.notificationTimer = null;
|
|
6832
|
+
}
|
|
6833
|
+
if (ap.driver.busyDeliveryMode === "gated") {
|
|
6834
|
+
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
6835
|
+
reason: "compaction_boundary",
|
|
6836
|
+
pendingMessages: ap.inbox.length
|
|
6837
|
+
});
|
|
6838
|
+
}
|
|
6839
|
+
this.recordRuntimeTraceEvent(agentId, ap, "runtime.compaction_boundary.delivery_buffered", {
|
|
6840
|
+
pendingNotificationCount: ap.pendingNotificationCount,
|
|
6841
|
+
pendingMessages: ap.inbox.length,
|
|
6842
|
+
busyDeliveryMode: ap.driver.busyDeliveryMode
|
|
6843
|
+
});
|
|
6844
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
6845
|
+
outcome: "queued_compaction_boundary",
|
|
6846
|
+
accepted: true,
|
|
6847
|
+
process_present: true,
|
|
6848
|
+
runtime: ap.config.runtime,
|
|
6849
|
+
session_id_present: true,
|
|
6850
|
+
launchId: ap.launchId || void 0,
|
|
6851
|
+
inbox_count: ap.inbox.length,
|
|
6852
|
+
pending_notification_count: ap.pendingNotificationCount,
|
|
6853
|
+
busy_delivery_mode: ap.driver.busyDeliveryMode,
|
|
6854
|
+
notification_timer_present: false
|
|
6855
|
+
}));
|
|
6856
|
+
return true;
|
|
6857
|
+
}
|
|
5754
6858
|
if (ap.driver.busyDeliveryMode === "gated") {
|
|
5755
6859
|
ap.pendingNotificationCount++;
|
|
5756
6860
|
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
@@ -5788,7 +6892,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5788
6892
|
return true;
|
|
5789
6893
|
}
|
|
5790
6894
|
async resetWorkspace(agentId) {
|
|
5791
|
-
const agentDataDir =
|
|
6895
|
+
const agentDataDir = path13.join(this.dataDir, agentId);
|
|
5792
6896
|
try {
|
|
5793
6897
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
5794
6898
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -5825,7 +6929,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5825
6929
|
return result;
|
|
5826
6930
|
}
|
|
5827
6931
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
5828
|
-
const workspacePath =
|
|
6932
|
+
const workspacePath = path13.join(this.dataDir, agentId);
|
|
5829
6933
|
return {
|
|
5830
6934
|
agentId,
|
|
5831
6935
|
launchId,
|
|
@@ -5957,19 +7061,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5957
7061
|
if (cached) {
|
|
5958
7062
|
logger.info(`[Agent ${agentId}] Starting from idle state for runtime profile ${kind} ${key}`);
|
|
5959
7063
|
this.idleAgentConfigs.delete(agentId);
|
|
5960
|
-
this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).
|
|
7064
|
+
return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
|
|
5961
7065
|
logger.error(`[Agent ${agentId}] Failed to auto-restart for runtime profile notification`, err);
|
|
5962
|
-
this.
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
7066
|
+
if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "runtime_profile_auto_restart")) {
|
|
7067
|
+
span.end("error", {
|
|
7068
|
+
attrs: {
|
|
7069
|
+
outcome: "runner_credential_mint_failed",
|
|
7070
|
+
runtime: cached.config.runtime,
|
|
7071
|
+
launchId: cached.launchId || void 0
|
|
7072
|
+
}
|
|
7073
|
+
});
|
|
7074
|
+
return false;
|
|
5970
7075
|
}
|
|
7076
|
+
this.idleAgentConfigs.set(agentId, cached);
|
|
7077
|
+
span.end("error", {
|
|
7078
|
+
attrs: {
|
|
7079
|
+
outcome: "restart_failed",
|
|
7080
|
+
runtime: cached.config.runtime,
|
|
7081
|
+
launchId: cached.launchId || void 0
|
|
7082
|
+
}
|
|
7083
|
+
});
|
|
7084
|
+
return false;
|
|
5971
7085
|
});
|
|
5972
|
-
return true;
|
|
5973
7086
|
}
|
|
5974
7087
|
logger.warn(`[Agent ${agentId}] Runtime profile ${kind} ${key} has no runtime injection path yet; leaving unacked for retry`);
|
|
5975
7088
|
span.end("ok", { attrs: { outcome: "no_path" } });
|
|
@@ -6073,7 +7186,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6073
7186
|
}
|
|
6074
7187
|
// Workspace file browsing
|
|
6075
7188
|
async getFileTree(agentId, dirPath) {
|
|
6076
|
-
const agentDir =
|
|
7189
|
+
const agentDir = path13.join(this.dataDir, agentId);
|
|
6077
7190
|
try {
|
|
6078
7191
|
await stat2(agentDir);
|
|
6079
7192
|
} catch {
|
|
@@ -6081,8 +7194,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6081
7194
|
}
|
|
6082
7195
|
let targetDir = agentDir;
|
|
6083
7196
|
if (dirPath) {
|
|
6084
|
-
const resolved =
|
|
6085
|
-
if (!resolved.startsWith(agentDir +
|
|
7197
|
+
const resolved = path13.resolve(agentDir, dirPath);
|
|
7198
|
+
if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
|
|
6086
7199
|
return [];
|
|
6087
7200
|
}
|
|
6088
7201
|
targetDir = resolved;
|
|
@@ -6090,14 +7203,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6090
7203
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
6091
7204
|
}
|
|
6092
7205
|
async readFile(agentId, filePath) {
|
|
6093
|
-
const agentDir =
|
|
6094
|
-
const resolved =
|
|
6095
|
-
if (!resolved.startsWith(agentDir +
|
|
7206
|
+
const agentDir = path13.join(this.dataDir, agentId);
|
|
7207
|
+
const resolved = path13.resolve(agentDir, filePath);
|
|
7208
|
+
if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
|
|
6096
7209
|
throw new Error("Access denied");
|
|
6097
7210
|
}
|
|
6098
7211
|
const info = await stat2(resolved);
|
|
6099
7212
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
6100
|
-
const ext =
|
|
7213
|
+
const ext = path13.extname(resolved).toLowerCase();
|
|
6101
7214
|
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
6102
7215
|
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
6103
7216
|
const content = await readFile(resolved, "utf-8");
|
|
@@ -6132,13 +7245,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6132
7245
|
const agent = this.agents.get(agentId);
|
|
6133
7246
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
6134
7247
|
const home = os6.homedir();
|
|
6135
|
-
const workspaceDir =
|
|
7248
|
+
const workspaceDir = path13.join(this.dataDir, agentId);
|
|
6136
7249
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
6137
7250
|
const globalResults = await Promise.all(
|
|
6138
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
7251
|
+
paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
|
|
6139
7252
|
);
|
|
6140
7253
|
const workspaceResults = await Promise.all(
|
|
6141
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
7254
|
+
paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
|
|
6142
7255
|
);
|
|
6143
7256
|
const dedup = (skills) => {
|
|
6144
7257
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6167,7 +7280,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6167
7280
|
const skills = [];
|
|
6168
7281
|
for (const entry of entries) {
|
|
6169
7282
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
6170
|
-
const skillMd =
|
|
7283
|
+
const skillMd = path13.join(dir, entry.name, "SKILL.md");
|
|
6171
7284
|
try {
|
|
6172
7285
|
const content = await readFile(skillMd, "utf-8");
|
|
6173
7286
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -6178,7 +7291,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6178
7291
|
} else if (entry.name.endsWith(".md")) {
|
|
6179
7292
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
6180
7293
|
try {
|
|
6181
|
-
const content = await readFile(
|
|
7294
|
+
const content = await readFile(path13.join(dir, entry.name), "utf-8");
|
|
6182
7295
|
const skill = this.parseSkillMd(cmdName, content);
|
|
6183
7296
|
skill.sourcePath = dir;
|
|
6184
7297
|
skills.push(skill);
|
|
@@ -6419,11 +7532,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6419
7532
|
ap.compactionWatchdog = null;
|
|
6420
7533
|
this.broadcastActivity(agentId, "working", "Context compaction still running; no finish event observed");
|
|
6421
7534
|
}
|
|
6422
|
-
|
|
7535
|
+
completeCompactionIfActive(agentId, detail = "Context compaction finished", options = {}) {
|
|
6423
7536
|
const ap = this.agents.get(agentId);
|
|
6424
7537
|
if (!ap || !ap.compactionStartedAt) return;
|
|
6425
7538
|
this.clearCompactionWatchdog(ap);
|
|
6426
7539
|
this.broadcastActivity(agentId, "working", detail, [{ kind: "compaction_finished" }]);
|
|
7540
|
+
ap.gatedSteering.compacting = false;
|
|
7541
|
+
this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "compaction_finished", inferred: true });
|
|
7542
|
+
if (options.flushBoundaryMessages ?? true) {
|
|
7543
|
+
this.flushCompactionBoundaryMessages(agentId, ap);
|
|
7544
|
+
}
|
|
7545
|
+
ap.isIdle = false;
|
|
7546
|
+
}
|
|
7547
|
+
interruptCompactionIfActive(agentId) {
|
|
7548
|
+
const ap = this.agents.get(agentId);
|
|
7549
|
+
if (!ap || !ap.compactionStartedAt && !ap.gatedSteering.compacting) return;
|
|
7550
|
+
this.clearCompactionWatchdog(ap);
|
|
7551
|
+
ap.gatedSteering.compacting = false;
|
|
6427
7552
|
}
|
|
6428
7553
|
messagesTraceAttrs(messages) {
|
|
6429
7554
|
if (!messages || messages.length === 0) return {};
|
|
@@ -6746,28 +7871,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6746
7871
|
this.sendRuntimeProfileReport(agentId);
|
|
6747
7872
|
break;
|
|
6748
7873
|
case "thinking": {
|
|
6749
|
-
this.
|
|
7874
|
+
if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
|
|
7875
|
+
this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
|
|
6750
7876
|
this.queueTrajectoryText(agentId, "thinking", event.text);
|
|
6751
7877
|
if (ap) ap.isIdle = false;
|
|
6752
|
-
if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
|
|
6753
7878
|
this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "thinking" });
|
|
6754
7879
|
break;
|
|
6755
7880
|
}
|
|
6756
7881
|
case "text": {
|
|
6757
|
-
this.
|
|
7882
|
+
if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
|
|
7883
|
+
this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
|
|
6758
7884
|
this.queueTrajectoryText(agentId, "text", event.text);
|
|
6759
7885
|
if (ap) ap.isIdle = false;
|
|
6760
|
-
if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
|
|
6761
7886
|
this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "text" });
|
|
6762
7887
|
break;
|
|
6763
7888
|
}
|
|
6764
7889
|
case "tool_call": {
|
|
6765
|
-
this.
|
|
7890
|
+
if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
|
|
7891
|
+
this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed tool use)");
|
|
6766
7892
|
this.flushPendingTrajectory(agentId);
|
|
6767
7893
|
const invocation = normalizeToolDisplayInvocation(event.name, event.input);
|
|
6768
7894
|
if (ap) {
|
|
6769
7895
|
ap.gatedSteering.outstandingToolUses++;
|
|
6770
|
-
this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
|
|
6771
7896
|
this.noteRuntimeProfileToolCall(agentId, ap, invocation.toolName);
|
|
6772
7897
|
this.recordRuntimeTraceEvent(agentId, ap, "tool.call.started", { tool: invocation.toolName });
|
|
6773
7898
|
this.setGatedSteeringPhase(agentId, ap, "tool_wait", {
|
|
@@ -6828,7 +7953,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6828
7953
|
break;
|
|
6829
7954
|
case "turn_end":
|
|
6830
7955
|
if (ap) this.recordRuntimeTraceEvent(agentId, ap, "runtime.turn.completed");
|
|
6831
|
-
this.
|
|
7956
|
+
this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from turn end)", {
|
|
7957
|
+
flushBoundaryMessages: false
|
|
7958
|
+
});
|
|
6832
7959
|
this.flushPendingTrajectory(agentId);
|
|
6833
7960
|
if (ap) {
|
|
6834
7961
|
this.clearGatedInFlightBatch(agentId, ap, "turn_end");
|
|
@@ -6885,7 +8012,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6885
8012
|
}
|
|
6886
8013
|
break;
|
|
6887
8014
|
case "error": {
|
|
6888
|
-
this.
|
|
8015
|
+
this.interruptCompactionIfActive(agentId);
|
|
6889
8016
|
this.flushPendingTrajectory(agentId);
|
|
6890
8017
|
if (ap) ap.lastRuntimeError = event.message;
|
|
6891
8018
|
if (ap) {
|
|
@@ -6933,6 +8060,29 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6933
8060
|
sendAgentStatus(agentId, status, launchId) {
|
|
6934
8061
|
this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
|
|
6935
8062
|
}
|
|
8063
|
+
reportRunnerCredentialMintFailure(agentId, err, launchId, source) {
|
|
8064
|
+
if (!(err instanceof RunnerCredentialMintError)) return false;
|
|
8065
|
+
const detail = runnerCredentialErrorDetail(err);
|
|
8066
|
+
this.recordDaemonTrace("daemon.runner_credential_mint.hard_fail", {
|
|
8067
|
+
agentId,
|
|
8068
|
+
launchId: launchId || void 0,
|
|
8069
|
+
source,
|
|
8070
|
+
status: detail.status,
|
|
8071
|
+
code: detail.code,
|
|
8072
|
+
reason: detail.message,
|
|
8073
|
+
retryable: detail.retryable
|
|
8074
|
+
}, "error");
|
|
8075
|
+
const reason = err.message;
|
|
8076
|
+
this.sendAgentStatus(agentId, "inactive", launchId);
|
|
8077
|
+
this.broadcastActivity(
|
|
8078
|
+
agentId,
|
|
8079
|
+
"error",
|
|
8080
|
+
`Start failed: ${reason}`,
|
|
8081
|
+
[{ kind: "text", text: `Error: ${reason}` }],
|
|
8082
|
+
launchId
|
|
8083
|
+
);
|
|
8084
|
+
return true;
|
|
8085
|
+
}
|
|
6936
8086
|
noteRuntimeTraceCounter(ap, event) {
|
|
6937
8087
|
ap.runtimeTraceCounters.events++;
|
|
6938
8088
|
switch (event.kind) {
|
|
@@ -7095,6 +8245,7 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
7095
8245
|
}, "error");
|
|
7096
8246
|
return false;
|
|
7097
8247
|
}
|
|
8248
|
+
this.consumeVisibleMessages(agentId, { messages, source: `stdin_${mode}_delivery` });
|
|
7098
8249
|
const senders = [...new Set(messages.map((message) => `@${message.sender_name}`))].join(", ");
|
|
7099
8250
|
logger.info(
|
|
7100
8251
|
`[Agent ${agentId}] Delivering ${mode} ${messages.length === 1 ? "message" : `${messages.length} messages`} via stdin from ${senders}`
|
|
@@ -7128,8 +8279,8 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
7128
8279
|
const nodes = [];
|
|
7129
8280
|
for (const entry of entries) {
|
|
7130
8281
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
7131
|
-
const fullPath =
|
|
7132
|
-
const relativePath =
|
|
8282
|
+
const fullPath = path13.join(dir, entry.name);
|
|
8283
|
+
const relativePath = path13.relative(rootDir, fullPath);
|
|
7133
8284
|
let info;
|
|
7134
8285
|
try {
|
|
7135
8286
|
info = await stat2(fullPath);
|
|
@@ -7432,9 +8583,9 @@ var ReminderCache = class {
|
|
|
7432
8583
|
|
|
7433
8584
|
// src/machineLock.ts
|
|
7434
8585
|
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
7435
|
-
import { mkdirSync as
|
|
8586
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
7436
8587
|
import os7 from "os";
|
|
7437
|
-
import
|
|
8588
|
+
import path14 from "path";
|
|
7438
8589
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
7439
8590
|
var DaemonMachineLockConflictError = class extends Error {
|
|
7440
8591
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -7456,7 +8607,7 @@ function resolveDefaultMachineStateRoot() {
|
|
|
7456
8607
|
return resolveSlockHomePath("machines");
|
|
7457
8608
|
}
|
|
7458
8609
|
function ownerPath(lockDir) {
|
|
7459
|
-
return
|
|
8610
|
+
return path14.join(lockDir, "owner.json");
|
|
7460
8611
|
}
|
|
7461
8612
|
function readOwner(lockDir) {
|
|
7462
8613
|
try {
|
|
@@ -7486,13 +8637,13 @@ function acquireDaemonMachineLock(options) {
|
|
|
7486
8637
|
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
7487
8638
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
7488
8639
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
7489
|
-
const machineDir =
|
|
7490
|
-
const lockDir =
|
|
8640
|
+
const machineDir = path14.join(rootDir, lockId);
|
|
8641
|
+
const lockDir = path14.join(machineDir, "daemon.lock");
|
|
7491
8642
|
const token = randomUUID2();
|
|
7492
|
-
|
|
8643
|
+
mkdirSync6(machineDir, { recursive: true });
|
|
7493
8644
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
7494
8645
|
try {
|
|
7495
|
-
|
|
8646
|
+
mkdirSync6(lockDir);
|
|
7496
8647
|
const owner = {
|
|
7497
8648
|
pid: process.pid,
|
|
7498
8649
|
token,
|
|
@@ -7502,10 +8653,10 @@ function acquireDaemonMachineLock(options) {
|
|
|
7502
8653
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
7503
8654
|
};
|
|
7504
8655
|
try {
|
|
7505
|
-
|
|
8656
|
+
writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
|
|
7506
8657
|
`, { mode: 384 });
|
|
7507
8658
|
} catch (err) {
|
|
7508
|
-
|
|
8659
|
+
rmSync3(lockDir, { recursive: true, force: true });
|
|
7509
8660
|
throw err;
|
|
7510
8661
|
}
|
|
7511
8662
|
return {
|
|
@@ -7515,7 +8666,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
7515
8666
|
release: () => {
|
|
7516
8667
|
const currentOwner = readOwner(lockDir);
|
|
7517
8668
|
if (currentOwner?.pid === process.pid && currentOwner.token === token) {
|
|
7518
|
-
|
|
8669
|
+
rmSync3(lockDir, { recursive: true, force: true });
|
|
7519
8670
|
}
|
|
7520
8671
|
}
|
|
7521
8672
|
};
|
|
@@ -7532,15 +8683,15 @@ function acquireDaemonMachineLock(options) {
|
|
|
7532
8683
|
throw new DaemonMachineLockConflictError(lockDir, null);
|
|
7533
8684
|
}
|
|
7534
8685
|
}
|
|
7535
|
-
|
|
8686
|
+
rmSync3(lockDir, { recursive: true, force: true });
|
|
7536
8687
|
}
|
|
7537
8688
|
}
|
|
7538
8689
|
throw new DaemonMachineLockConflictError(lockDir, readOwner(lockDir));
|
|
7539
8690
|
}
|
|
7540
8691
|
|
|
7541
8692
|
// src/localTraceSink.ts
|
|
7542
|
-
import { appendFileSync, mkdirSync as
|
|
7543
|
-
import
|
|
8693
|
+
import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
|
|
8694
|
+
import path15 from "path";
|
|
7544
8695
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
7545
8696
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
7546
8697
|
var DEFAULT_MAX_FILES = 8;
|
|
@@ -7576,7 +8727,7 @@ var LocalRotatingTraceSink = class {
|
|
|
7576
8727
|
currentSize = 0;
|
|
7577
8728
|
sequence = 0;
|
|
7578
8729
|
constructor(options) {
|
|
7579
|
-
this.traceDir =
|
|
8730
|
+
this.traceDir = path15.join(options.machineDir, "traces");
|
|
7580
8731
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
7581
8732
|
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
7582
8733
|
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
@@ -7602,15 +8753,15 @@ var LocalRotatingTraceSink = class {
|
|
|
7602
8753
|
return this.currentFile;
|
|
7603
8754
|
}
|
|
7604
8755
|
ensureFile(nextBytes) {
|
|
7605
|
-
|
|
8756
|
+
mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
|
|
7606
8757
|
const nowMs = this.nowMsProvider();
|
|
7607
8758
|
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
7608
8759
|
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
7609
|
-
this.currentFile =
|
|
8760
|
+
this.currentFile = path15.join(
|
|
7610
8761
|
this.traceDir,
|
|
7611
8762
|
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
7612
8763
|
);
|
|
7613
|
-
|
|
8764
|
+
writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
|
|
7614
8765
|
this.currentSize = statSync4(this.currentFile).size;
|
|
7615
8766
|
this.currentFileOpenedAtMs = nowMs;
|
|
7616
8767
|
this.pruneOldFiles();
|
|
@@ -7621,7 +8772,7 @@ var LocalRotatingTraceSink = class {
|
|
|
7621
8772
|
const excess = files.length - this.maxFiles;
|
|
7622
8773
|
if (excess <= 0) return;
|
|
7623
8774
|
for (const file of files.slice(0, excess)) {
|
|
7624
|
-
|
|
8775
|
+
rmSync4(path15.join(this.traceDir, file), { force: true });
|
|
7625
8776
|
}
|
|
7626
8777
|
}
|
|
7627
8778
|
};
|
|
@@ -7708,11 +8859,11 @@ function isDiagnosticErrorAttr(key) {
|
|
|
7708
8859
|
import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
|
|
7709
8860
|
import { gzipSync } from "zlib";
|
|
7710
8861
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
7711
|
-
import
|
|
8862
|
+
import path16 from "path";
|
|
7712
8863
|
|
|
7713
8864
|
// src/directUploadCapability.ts
|
|
7714
|
-
function joinUrl(base,
|
|
7715
|
-
return `${base.replace(/\/+$/, "")}${
|
|
8865
|
+
function joinUrl(base, path18) {
|
|
8866
|
+
return `${base.replace(/\/+$/, "")}${path18}`;
|
|
7716
8867
|
}
|
|
7717
8868
|
function jsonHeaders(apiKey) {
|
|
7718
8869
|
return {
|
|
@@ -7931,7 +9082,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
7931
9082
|
}, nextMs);
|
|
7932
9083
|
}
|
|
7933
9084
|
async findUploadCandidates() {
|
|
7934
|
-
const traceDir =
|
|
9085
|
+
const traceDir = path16.join(this.options.machineDir, "traces");
|
|
7935
9086
|
let names;
|
|
7936
9087
|
try {
|
|
7937
9088
|
names = await readdir3(traceDir);
|
|
@@ -7943,8 +9094,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
7943
9094
|
const currentFile = this.options.currentFileProvider?.();
|
|
7944
9095
|
const candidates = [];
|
|
7945
9096
|
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
7946
|
-
const file =
|
|
7947
|
-
if (currentFile &&
|
|
9097
|
+
const file = path16.join(traceDir, name);
|
|
9098
|
+
if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
|
|
7948
9099
|
if (await this.isUploaded(file)) continue;
|
|
7949
9100
|
try {
|
|
7950
9101
|
const info = await stat3(file);
|
|
@@ -8018,8 +9169,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
8018
9169
|
}
|
|
8019
9170
|
}
|
|
8020
9171
|
uploadStatePath(file) {
|
|
8021
|
-
const stateDir =
|
|
8022
|
-
return
|
|
9172
|
+
const stateDir = path16.join(this.options.machineDir, "trace-uploads");
|
|
9173
|
+
return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
|
|
8023
9174
|
}
|
|
8024
9175
|
async isUploaded(file) {
|
|
8025
9176
|
try {
|
|
@@ -8031,9 +9182,9 @@ var DaemonTraceBundleUploader = class {
|
|
|
8031
9182
|
}
|
|
8032
9183
|
async markUploaded(file, metadata) {
|
|
8033
9184
|
const stateFile = this.uploadStatePath(file);
|
|
8034
|
-
await mkdir2(
|
|
9185
|
+
await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
|
|
8035
9186
|
await writeFile2(stateFile, `${JSON.stringify({
|
|
8036
|
-
file:
|
|
9187
|
+
file: path16.basename(file),
|
|
8037
9188
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8038
9189
|
...metadata
|
|
8039
9190
|
}, null, 2)}
|
|
@@ -8052,10 +9203,48 @@ function readPositiveIntegerEnv2(name, fallback) {
|
|
|
8052
9203
|
|
|
8053
9204
|
// src/core.ts
|
|
8054
9205
|
var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
|
|
8055
|
-
var
|
|
8056
|
-
|
|
9206
|
+
var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
|
|
9207
|
+
var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
|
|
9208
|
+
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
|
|
9209
|
+
var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
|
|
9210
|
+
var RunnerCredentialMintError2 = class extends Error {
|
|
9211
|
+
code;
|
|
9212
|
+
retryable;
|
|
9213
|
+
status;
|
|
9214
|
+
constructor(message, opts) {
|
|
9215
|
+
super(message);
|
|
9216
|
+
this.name = "RunnerCredentialMintError";
|
|
9217
|
+
this.code = opts.code;
|
|
9218
|
+
this.retryable = opts.retryable ?? false;
|
|
9219
|
+
this.status = opts.status;
|
|
9220
|
+
}
|
|
9221
|
+
};
|
|
9222
|
+
function isRetryableMintHttpFailure2(status, code) {
|
|
9223
|
+
if (code === "experimental_surface_disabled") return false;
|
|
9224
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
9225
|
+
}
|
|
9226
|
+
function runnerCredentialErrorDetail2(error) {
|
|
9227
|
+
if (error instanceof RunnerCredentialMintError2) {
|
|
9228
|
+
return {
|
|
9229
|
+
message: error.message,
|
|
9230
|
+
code: error.code,
|
|
9231
|
+
retryable: error.retryable,
|
|
9232
|
+
status: error.status
|
|
9233
|
+
};
|
|
9234
|
+
}
|
|
9235
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9236
|
+
return {
|
|
9237
|
+
message,
|
|
9238
|
+
code: "runner_credential_mint_network_error",
|
|
9239
|
+
retryable: true
|
|
9240
|
+
};
|
|
9241
|
+
}
|
|
9242
|
+
async function waitForRunnerCredentialRetry2() {
|
|
9243
|
+
await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
|
|
9244
|
+
}
|
|
9245
|
+
function parseDaemonCliArgs(args, env = {}) {
|
|
8057
9246
|
let serverUrl = "";
|
|
8058
|
-
let apiKey = "";
|
|
9247
|
+
let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
|
|
8059
9248
|
for (let i = 0; i < args.length; i++) {
|
|
8060
9249
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
8061
9250
|
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
@@ -8072,23 +9261,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
8072
9261
|
}
|
|
8073
9262
|
}
|
|
8074
9263
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
8075
|
-
const dirname =
|
|
8076
|
-
const jsPath =
|
|
9264
|
+
const dirname = path17.dirname(fileURLToPath2(moduleUrl));
|
|
9265
|
+
const jsPath = path17.resolve(dirname, "chat-bridge.js");
|
|
8077
9266
|
try {
|
|
8078
9267
|
accessSync(jsPath);
|
|
8079
9268
|
return jsPath;
|
|
8080
9269
|
} catch {
|
|
8081
|
-
return
|
|
9270
|
+
return path17.resolve(dirname, "chat-bridge.ts");
|
|
8082
9271
|
}
|
|
8083
9272
|
}
|
|
8084
9273
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
8085
|
-
const thisDir =
|
|
8086
|
-
const bundledDistPath =
|
|
9274
|
+
const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
|
|
9275
|
+
const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
|
|
8087
9276
|
try {
|
|
8088
9277
|
accessSync(bundledDistPath);
|
|
8089
9278
|
return bundledDistPath;
|
|
8090
9279
|
} catch {
|
|
8091
|
-
const workspaceDistPath =
|
|
9280
|
+
const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
8092
9281
|
accessSync(workspaceDistPath);
|
|
8093
9282
|
return workspaceDistPath;
|
|
8094
9283
|
}
|
|
@@ -8267,7 +9456,7 @@ var DaemonCore = class {
|
|
|
8267
9456
|
}
|
|
8268
9457
|
resolveMachineStateRoot() {
|
|
8269
9458
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
8270
|
-
if (this.options.dataDir) return
|
|
9459
|
+
if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
|
|
8271
9460
|
return resolveDefaultMachineStateRoot();
|
|
8272
9461
|
}
|
|
8273
9462
|
shouldEnableLocalTrace() {
|
|
@@ -8384,13 +9573,115 @@ var DaemonCore = class {
|
|
|
8384
9573
|
});
|
|
8385
9574
|
span.end(status);
|
|
8386
9575
|
}
|
|
9576
|
+
async requestRunnerCredentialOnce(agentId, config) {
|
|
9577
|
+
const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.options.serverUrl);
|
|
9578
|
+
const res = await fetch(url, {
|
|
9579
|
+
method: "POST",
|
|
9580
|
+
headers: {
|
|
9581
|
+
"Authorization": `Bearer ${this.options.apiKey}`,
|
|
9582
|
+
"Content-Type": "application/json",
|
|
9583
|
+
"X-Slock-Client": "daemon-server-session-worker"
|
|
9584
|
+
},
|
|
9585
|
+
body: JSON.stringify({
|
|
9586
|
+
scopes: RUNNER_CREDENTIAL_SCOPES,
|
|
9587
|
+
name: `runner:${config.runtime}:${agentId.slice(0, 8)}`
|
|
9588
|
+
})
|
|
9589
|
+
});
|
|
9590
|
+
if (!res.ok) {
|
|
9591
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
9592
|
+
let detail = `HTTP ${res.status}`;
|
|
9593
|
+
let code = null;
|
|
9594
|
+
if (contentType.includes("application/json")) {
|
|
9595
|
+
const body2 = await res.json().catch(() => null);
|
|
9596
|
+
const error = typeof body2?.error === "string" ? body2.error : null;
|
|
9597
|
+
code = typeof body2?.code === "string" ? body2.code : null;
|
|
9598
|
+
detail = [detail, code, error].filter(Boolean).join(" ");
|
|
9599
|
+
}
|
|
9600
|
+
throw new RunnerCredentialMintError2(detail, {
|
|
9601
|
+
code: code ?? "runner_credential_mint_http_error",
|
|
9602
|
+
retryable: isRetryableMintHttpFailure2(res.status, code),
|
|
9603
|
+
status: res.status
|
|
9604
|
+
});
|
|
9605
|
+
}
|
|
9606
|
+
const body = await res.json().catch(() => null);
|
|
9607
|
+
if (typeof body?.apiKey !== "string" || !body.apiKey.startsWith("sk_agent_")) {
|
|
9608
|
+
throw new RunnerCredentialMintError2("invalid_agent_credential_payload", {
|
|
9609
|
+
code: "invalid_agent_credential_payload"
|
|
9610
|
+
});
|
|
9611
|
+
}
|
|
9612
|
+
return {
|
|
9613
|
+
apiKey: body.apiKey,
|
|
9614
|
+
credentialId: typeof body.credentialId === "string" ? body.credentialId : null
|
|
9615
|
+
};
|
|
9616
|
+
}
|
|
9617
|
+
async mintRunnerCredential(agentId, config) {
|
|
9618
|
+
if (config.agentCredentialKey) {
|
|
9619
|
+
return { apiKey: config.agentCredentialKey, credentialId: config.agentCredentialId ?? null };
|
|
9620
|
+
}
|
|
9621
|
+
if (process.env.SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED === "1") {
|
|
9622
|
+
throw new RunnerCredentialMintError2("runner credential mint is disabled by SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED", {
|
|
9623
|
+
code: "runner_credentials_disabled"
|
|
9624
|
+
});
|
|
9625
|
+
}
|
|
9626
|
+
let lastError = null;
|
|
9627
|
+
for (let attempt = 1; attempt <= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2; attempt += 1) {
|
|
9628
|
+
try {
|
|
9629
|
+
return await this.requestRunnerCredentialOnce(agentId, config);
|
|
9630
|
+
} catch (err) {
|
|
9631
|
+
lastError = err;
|
|
9632
|
+
const detail2 = runnerCredentialErrorDetail2(err);
|
|
9633
|
+
this.recordDaemonTrace("daemon.runner_credential_mint.retry", {
|
|
9634
|
+
agentId,
|
|
9635
|
+
runtime: config.runtime,
|
|
9636
|
+
attempt,
|
|
9637
|
+
max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2,
|
|
9638
|
+
status: detail2.status,
|
|
9639
|
+
code: detail2.code,
|
|
9640
|
+
reason: detail2.message,
|
|
9641
|
+
retryable: detail2.retryable
|
|
9642
|
+
}, detail2.retryable && attempt < RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 ? "ok" : "error");
|
|
9643
|
+
if (!detail2.retryable || attempt >= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2) break;
|
|
9644
|
+
await waitForRunnerCredentialRetry2();
|
|
9645
|
+
}
|
|
9646
|
+
}
|
|
9647
|
+
const detail = runnerCredentialErrorDetail2(lastError);
|
|
9648
|
+
this.recordDaemonTrace("daemon.runner_credential_mint.failed", {
|
|
9649
|
+
agentId,
|
|
9650
|
+
runtime: config.runtime,
|
|
9651
|
+
status: detail.status,
|
|
9652
|
+
code: detail.code,
|
|
9653
|
+
reason: detail.message,
|
|
9654
|
+
retryable: detail.retryable,
|
|
9655
|
+
max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2
|
|
9656
|
+
}, "error");
|
|
9657
|
+
throw new RunnerCredentialMintError2(
|
|
9658
|
+
`runner_credential_mint_failed: ${detail.message}. Managed runner startup requires /internal/computer credential mint; deploy server first or roll back the daemon binary.`,
|
|
9659
|
+
{
|
|
9660
|
+
code: detail.code,
|
|
9661
|
+
retryable: detail.retryable,
|
|
9662
|
+
status: detail.status
|
|
9663
|
+
}
|
|
9664
|
+
);
|
|
9665
|
+
}
|
|
9666
|
+
async startAgentFromMessage(msg) {
|
|
9667
|
+
const agentCredential = await this.mintRunnerCredential(msg.agentId, msg.config);
|
|
9668
|
+
const config = { ...msg.config, agentCredentialKey: agentCredential.apiKey, agentCredentialId: agentCredential.credentialId };
|
|
9669
|
+
await this.agentManager.startAgent(
|
|
9670
|
+
msg.agentId,
|
|
9671
|
+
config,
|
|
9672
|
+
msg.wakeMessage,
|
|
9673
|
+
msg.unreadSummary,
|
|
9674
|
+
msg.resumePrompt,
|
|
9675
|
+
msg.launchId
|
|
9676
|
+
);
|
|
9677
|
+
}
|
|
8387
9678
|
handleMessage(msg) {
|
|
8388
9679
|
const summary = summarizeIncomingMessage(msg);
|
|
8389
9680
|
logger.info(`[Daemon] Received ${msg.type}${summary ? ` ${summary}` : ""}`);
|
|
8390
9681
|
switch (msg.type) {
|
|
8391
9682
|
case "agent:start":
|
|
8392
9683
|
logger.info(`[Agent ${msg.agentId}] Start requested (runtime=${msg.config.runtime}, model=${msg.config.model}, session=${msg.config.sessionId || "new"}${msg.wakeMessage ? ", wake=true" : ""})`);
|
|
8393
|
-
this.
|
|
9684
|
+
this.startAgentFromMessage(msg).catch((err) => {
|
|
8394
9685
|
const reason = err instanceof Error ? err.message : String(err);
|
|
8395
9686
|
logger.error(`[Agent ${msg.agentId}] Start failed (${reason})`);
|
|
8396
9687
|
this.connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive", launchId: msg.launchId });
|
|
@@ -8423,22 +9714,27 @@ var DaemonCore = class {
|
|
|
8423
9714
|
logger.info(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
|
|
8424
9715
|
try {
|
|
8425
9716
|
span.addEvent("daemon.receive", { seq: msg.seq, deliveryId: msg.deliveryId });
|
|
8426
|
-
const
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
9717
|
+
const acceptedOrPromise = this.agentManager.deliverMessage(msg.agentId, msg.message, { deliveryId: msg.deliveryId });
|
|
9718
|
+
Promise.resolve(acceptedOrPromise).then((accepted) => {
|
|
9719
|
+
span.addEvent("daemon.deliver_to_agent_manager", { accepted });
|
|
9720
|
+
if (!accepted) {
|
|
9721
|
+
span.end("ok", { attrs: { outcome: "not-accepted" } });
|
|
9722
|
+
return;
|
|
9723
|
+
}
|
|
9724
|
+
const ackSeq = msg.seq > 0 ? msg.seq : msg.message.seq ?? 0;
|
|
9725
|
+
span.addEvent("daemon.ack.sent", { seq: ackSeq });
|
|
9726
|
+
this.connection.send({
|
|
9727
|
+
type: "agent:deliver:ack",
|
|
9728
|
+
agentId: msg.agentId,
|
|
9729
|
+
seq: ackSeq,
|
|
9730
|
+
traceparent: formatTraceparent(span.context),
|
|
9731
|
+
deliveryId: msg.deliveryId
|
|
9732
|
+
});
|
|
9733
|
+
span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
|
|
9734
|
+
}, (err) => {
|
|
9735
|
+
logger.error(`[Agent ${msg.agentId}] Delivery handling failed`, err);
|
|
9736
|
+
span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
|
|
8440
9737
|
});
|
|
8441
|
-
span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
|
|
8442
9738
|
} catch (err) {
|
|
8443
9739
|
span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
|
|
8444
9740
|
throw err;
|
|
@@ -8458,8 +9754,14 @@ var DaemonCore = class {
|
|
|
8458
9754
|
}
|
|
8459
9755
|
});
|
|
8460
9756
|
logger.info(`[Agent ${msg.agentId}] Runtime profile migration received (${msg.migrationKey})`);
|
|
8461
|
-
|
|
8462
|
-
|
|
9757
|
+
Promise.resolve(
|
|
9758
|
+
this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.migrationKey, "migration", msg.message, formatTraceparent(span.context))
|
|
9759
|
+
).then((accepted) => {
|
|
9760
|
+
span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
|
|
9761
|
+
}, (err) => {
|
|
9762
|
+
logger.error(`[Agent ${msg.agentId}] Runtime profile migration handling failed`, err);
|
|
9763
|
+
span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
|
|
9764
|
+
});
|
|
8463
9765
|
break;
|
|
8464
9766
|
}
|
|
8465
9767
|
case "agent:runtime_profile:daemon_release_notice": {
|
|
@@ -8475,8 +9777,14 @@ var DaemonCore = class {
|
|
|
8475
9777
|
}
|
|
8476
9778
|
});
|
|
8477
9779
|
logger.info(`[Agent ${msg.agentId}] Runtime profile daemon release notice received (${msg.noticeKey})`);
|
|
8478
|
-
|
|
8479
|
-
|
|
9780
|
+
Promise.resolve(
|
|
9781
|
+
this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.noticeKey, "daemon_release_notice", msg.message, formatTraceparent(span.context))
|
|
9782
|
+
).then((accepted) => {
|
|
9783
|
+
span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
|
|
9784
|
+
}, (err) => {
|
|
9785
|
+
logger.error(`[Agent ${msg.agentId}] Runtime profile daemon release notice handling failed`, err);
|
|
9786
|
+
span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
|
|
9787
|
+
});
|
|
8480
9788
|
break;
|
|
8481
9789
|
}
|
|
8482
9790
|
case "agent:workspace:list":
|
|
@@ -8650,6 +9958,8 @@ var DaemonCore = class {
|
|
|
8650
9958
|
};
|
|
8651
9959
|
|
|
8652
9960
|
export {
|
|
9961
|
+
DAEMON_API_KEY_ENV,
|
|
9962
|
+
scrubDaemonAuthEnv,
|
|
8653
9963
|
resolveWorkspaceDirectoryPath,
|
|
8654
9964
|
scanWorkspaceDirectories,
|
|
8655
9965
|
deleteWorkspaceDirectory,
|