@slock-ai/daemon 0.51.1 → 0.52.1-play.20260520171228
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-JDSI7JD5.js → chunk-UM3RDELD.js} +1031 -208
- package/dist/cli/index.js +269 -81
- 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
|
|
@@ -823,6 +824,7 @@ function buildPrompt(config, variant, opts) {
|
|
|
823
824
|
"- Always communicate through `slock` CLI commands. This is your only output channel.",
|
|
824
825
|
...opts.extraCriticalRules,
|
|
825
826
|
"- Use only the provided `slock` CLI commands for messaging.",
|
|
827
|
+
"- Do not combine multiple `slock` CLI commands in one shell command. Run one `slock` command per tool call, read its output, then decide the next command.",
|
|
826
828
|
"- Always claim a task via `slock task claim` before starting work on it. If the claim fails, move on to a different task."
|
|
827
829
|
] : [
|
|
828
830
|
`- Always communicate through ${sendCmd}. This is your only output channel.`,
|
|
@@ -871,13 +873,15 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
|
|
|
871
873
|
17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
872
874
|
18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
873
875
|
19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--avatar-url pixel:random:<seed>\`, \`--display-name <name>\`, and \`--description <text>\`. Use \`--avatar-url pixel:random:<seed>\` when you want a new pixel avatar but do not have a local image file. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
|
|
874
|
-
20. **\`slock
|
|
875
|
-
21. **\`slock
|
|
876
|
-
22. **\`slock reminder
|
|
877
|
-
23. **\`slock reminder
|
|
878
|
-
24. **\`slock reminder
|
|
879
|
-
25. **\`slock reminder
|
|
880
|
-
26. **\`slock
|
|
876
|
+
20. **\`slock integration list\`** \u2014 List registered third-party services and this agent's active Slock Agent Logins.
|
|
877
|
+
21. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a registered third-party service.
|
|
878
|
+
22. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
879
|
+
23. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
|
|
880
|
+
24. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
|
|
881
|
+
25. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
882
|
+
26. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
883
|
+
27. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
884
|
+
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
885
|
|
|
882
886
|
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
887
|
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
@@ -978,6 +982,11 @@ Each channel has a **name** and optionally a **description** that define its pur
|
|
|
978
982
|
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
979
983
|
- **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
984
|
- If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
|
|
985
|
+
const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
|
|
986
|
+
|
|
987
|
+
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
|
|
988
|
+
|
|
989
|
+
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
990
|
const readingHistorySection = isCli ? `### Reading history
|
|
982
991
|
|
|
983
992
|
\`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
|
|
@@ -1149,6 +1158,8 @@ ${discoverySection}
|
|
|
1149
1158
|
|
|
1150
1159
|
${channelAwarenessSection}
|
|
1151
1160
|
|
|
1161
|
+
${thirdPartyIntegrationsSection}
|
|
1162
|
+
|
|
1152
1163
|
${readingHistorySection}
|
|
1153
1164
|
|
|
1154
1165
|
${historicalReferenceSection}
|
|
@@ -1180,6 +1191,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
1180
1191
|
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
1181
1192
|
- When done, summarize the result.
|
|
1182
1193
|
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.
|
|
1194
|
+
- 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:
|
|
1195
|
+
|
|
1196
|
+
\`\`\`html
|
|
1197
|
+
<details>
|
|
1198
|
+
<summary>Evidence, logs, or edge cases</summary>
|
|
1199
|
+
|
|
1200
|
+
Detailed notes go here.
|
|
1201
|
+
</details>
|
|
1202
|
+
\`\`\`
|
|
1203
|
+
|
|
1204
|
+
Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
|
|
1183
1205
|
|
|
1184
1206
|
### Conversation etiquette
|
|
1185
1207
|
|
|
@@ -1275,13 +1297,12 @@ You may develop a specialized role over time through your interactions. Embrace
|
|
|
1275
1297
|
|
|
1276
1298
|
## Message Notifications
|
|
1277
1299
|
|
|
1278
|
-
While you are working,
|
|
1300
|
+
While you are working, the daemon may write a batched inbox-count notification into your current turn.
|
|
1279
1301
|
|
|
1280
1302
|
How to handle these:
|
|
1281
|
-
- Treat
|
|
1282
|
-
-
|
|
1283
|
-
-
|
|
1284
|
-
- Use ${checkCmd} only when you need to inspect other pending channels or recover broader context.`;
|
|
1303
|
+
- Treat the notification as a signal that new Slock messages are waiting; it does not include the message content.
|
|
1304
|
+
- Call ${checkCmd} at the next safe breakpoint to materialize the pending messages before taking side-effect actions that depend on current context.
|
|
1305
|
+
- If the new message is higher priority, pivot after reading it. If not, continue your current work.`;
|
|
1285
1306
|
} else {
|
|
1286
1307
|
const notifyExample = isCli ? `\`[System notification: You have N new message(s) waiting. Call slock message check to read them when you're ready.]\`` : `\`[System notification: You have N new message(s) waiting. Call ${t("check_messages")} to read them when you're ready.]\``;
|
|
1287
1308
|
prompt += `
|
|
@@ -1349,6 +1370,19 @@ 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
|
+
|
|
1352
1386
|
// src/agentCredentialProxy.ts
|
|
1353
1387
|
import { randomBytes } from "crypto";
|
|
1354
1388
|
import http from "http";
|
|
@@ -1356,6 +1390,7 @@ import { URL as URL2 } from "url";
|
|
|
1356
1390
|
var registrations = /* @__PURE__ */ new Map();
|
|
1357
1391
|
var servers = /* @__PURE__ */ new Map();
|
|
1358
1392
|
var DECODED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding"]);
|
|
1393
|
+
var LOCAL_HELD_CONTEXT_LIMIT = 3;
|
|
1359
1394
|
function allocatePort() {
|
|
1360
1395
|
return 43e3 + randomBytes(2).readUInt16BE(0) % 1e4;
|
|
1361
1396
|
}
|
|
@@ -1388,8 +1423,10 @@ async function handleProxyRequest(req, res) {
|
|
|
1388
1423
|
res.end(JSON.stringify({ error: "invalid local agent proxy token", code: "invalid_agent_proxy_token" }));
|
|
1389
1424
|
return;
|
|
1390
1425
|
}
|
|
1426
|
+
const method = req.method ?? "GET";
|
|
1427
|
+
let target;
|
|
1391
1428
|
try {
|
|
1392
|
-
|
|
1429
|
+
target = new URL2(req.url ?? "/", registration.serverUrl);
|
|
1393
1430
|
const headers = new Headers();
|
|
1394
1431
|
for (const [name, value] of Object.entries(req.headers)) {
|
|
1395
1432
|
if (value === void 0) continue;
|
|
@@ -1405,12 +1442,20 @@ async function handleProxyRequest(req, res) {
|
|
|
1405
1442
|
headers.set("X-Agent-Id", registration.agentId);
|
|
1406
1443
|
headers.set("X-Slock-Client", "cli");
|
|
1407
1444
|
headers.set("X-Slock-Agent-Active-Capabilities", registration.activeCapabilities);
|
|
1408
|
-
const method = req.method ?? "GET";
|
|
1409
1445
|
let body = method === "GET" || method === "HEAD" ? void 0 : req;
|
|
1410
1446
|
let sendTarget;
|
|
1411
|
-
|
|
1447
|
+
const sideEffectAction = agentApiSideEffectAction(target.pathname);
|
|
1448
|
+
if (method === "GET" && target.pathname === "/internal/agent-api/events") {
|
|
1449
|
+
const localEvents = localAgentApiEventsResponse(registration, target);
|
|
1450
|
+
if (localEvents) {
|
|
1451
|
+
res.writeHead(localEvents.status, { "content-type": "application/json" });
|
|
1452
|
+
res.end(JSON.stringify(localEvents.body));
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
if (method === "POST" && sideEffectAction) {
|
|
1412
1457
|
const rawBody = await readRequestBody(req);
|
|
1413
|
-
const prepared = await
|
|
1458
|
+
const prepared = await prepareAgentApiSideEffectForward(registration, headers, rawBody, sideEffectAction);
|
|
1414
1459
|
if (prepared.localResponse) {
|
|
1415
1460
|
const responseText = JSON.stringify(prepared.localResponse);
|
|
1416
1461
|
res.writeHead(200, { "content-type": "application/json" });
|
|
@@ -1418,7 +1463,7 @@ async function handleProxyRequest(req, res) {
|
|
|
1418
1463
|
return;
|
|
1419
1464
|
}
|
|
1420
1465
|
body = prepared.bodyText;
|
|
1421
|
-
sendTarget = prepared.target;
|
|
1466
|
+
if (sideEffectAction === "send") sendTarget = prepared.target;
|
|
1422
1467
|
headers.set("content-type", "application/json");
|
|
1423
1468
|
headers.set("content-length", String(Buffer.byteLength(prepared.bodyText)));
|
|
1424
1469
|
}
|
|
@@ -1452,14 +1497,33 @@ async function handleProxyRequest(req, res) {
|
|
|
1452
1497
|
res.end();
|
|
1453
1498
|
}
|
|
1454
1499
|
} catch (err) {
|
|
1500
|
+
const failure = proxyFailureForError(method, target, err);
|
|
1501
|
+
logger.warn(
|
|
1502
|
+
`[Agent Credential Proxy] request failed (agent=${registration.agentId}, launch=${registration.launchId ?? "none"}, method=${failure.method}, path=${failure.pathname}, query_keys=${failure.queryKeys.join(",") || "none"}): ${failure.errorName}: ${failure.errorMessage}`
|
|
1503
|
+
);
|
|
1504
|
+
registration.inboxCoordinator?.recordProxyFailure?.(failure);
|
|
1455
1505
|
res.writeHead(502, { "content-type": "application/json" });
|
|
1456
1506
|
res.end(JSON.stringify({
|
|
1457
1507
|
error: "failed to proxy local agent request",
|
|
1458
1508
|
code: "agent_proxy_failed",
|
|
1459
|
-
detail:
|
|
1509
|
+
detail: failure.errorMessage
|
|
1460
1510
|
}));
|
|
1461
1511
|
}
|
|
1462
1512
|
}
|
|
1513
|
+
function proxyFailureForError(method, target, err) {
|
|
1514
|
+
const queryKeys = target ? [.../* @__PURE__ */ new Set([...target.searchParams.keys()])].sort() : [];
|
|
1515
|
+
return {
|
|
1516
|
+
method,
|
|
1517
|
+
pathname: target?.pathname ?? "unknown",
|
|
1518
|
+
queryKeys,
|
|
1519
|
+
errorName: err instanceof Error ? err.name : typeof err,
|
|
1520
|
+
errorMessage: truncateProxyErrorMessage(err instanceof Error ? err.message : String(err))
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
function truncateProxyErrorMessage(message) {
|
|
1524
|
+
const normalized = message.replace(/\s+/g, " ").trim();
|
|
1525
|
+
return normalized.length > 500 ? `${normalized.slice(0, 497)}...` : normalized;
|
|
1526
|
+
}
|
|
1463
1527
|
async function readRequestBody(req) {
|
|
1464
1528
|
let body = "";
|
|
1465
1529
|
req.setEncoding("utf8");
|
|
@@ -1471,14 +1535,6 @@ async function readRequestBody(req) {
|
|
|
1471
1535
|
function messageSeq(message) {
|
|
1472
1536
|
return Number(message.seq ?? 0);
|
|
1473
1537
|
}
|
|
1474
|
-
function boundaryBefore(messages) {
|
|
1475
|
-
let minSeq = Number.POSITIVE_INFINITY;
|
|
1476
|
-
for (const message of messages) {
|
|
1477
|
-
const seq = Math.floor(messageSeq(message));
|
|
1478
|
-
if (Number.isFinite(seq) && seq > 0) minSeq = Math.min(minSeq, seq);
|
|
1479
|
-
}
|
|
1480
|
-
return Number.isFinite(minSeq) ? Math.max(0, minSeq - 1) : void 0;
|
|
1481
|
-
}
|
|
1482
1538
|
function maxMessageSeq(messages) {
|
|
1483
1539
|
let maxSeq = 0;
|
|
1484
1540
|
for (const message of messages) {
|
|
@@ -1487,6 +1543,117 @@ function maxMessageSeq(messages) {
|
|
|
1487
1543
|
}
|
|
1488
1544
|
return maxSeq > 0 ? maxSeq : void 0;
|
|
1489
1545
|
}
|
|
1546
|
+
function sortBySeq(messages) {
|
|
1547
|
+
return [...messages].sort((a, b) => messageSeq(a) - messageSeq(b));
|
|
1548
|
+
}
|
|
1549
|
+
function latestVisibleMessages(messages, limit) {
|
|
1550
|
+
const sorted = sortBySeq(messages);
|
|
1551
|
+
return sorted.slice(Math.max(0, sorted.length - limit));
|
|
1552
|
+
}
|
|
1553
|
+
function parseAgentApiEventsQuery(target) {
|
|
1554
|
+
const limit = Math.min(Math.max(Number(target.searchParams.get("limit")) || 50, 1), 200);
|
|
1555
|
+
const sinceRaw = target.searchParams.get("since")?.trim() ?? "";
|
|
1556
|
+
if (!sinceRaw) return { limit, sinceSeq: null, sinceCursorKind: null };
|
|
1557
|
+
if (sinceRaw === "latest") return { limit, sinceSeq: null, sinceCursorKind: "latest" };
|
|
1558
|
+
const parsed = Number(sinceRaw);
|
|
1559
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
1560
|
+
return {
|
|
1561
|
+
limit,
|
|
1562
|
+
sinceSeq: null,
|
|
1563
|
+
sinceCursorKind: null,
|
|
1564
|
+
error: {
|
|
1565
|
+
error: "since must be a non-negative integer (messageSeq) or 'latest'",
|
|
1566
|
+
code: "since_invalid"
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
return { limit, sinceSeq: Math.floor(parsed), sinceCursorKind: "seq" };
|
|
1571
|
+
}
|
|
1572
|
+
function localAgentApiEventsResponse(registration, target) {
|
|
1573
|
+
const coordinator = registration.inboxCoordinator;
|
|
1574
|
+
if (!coordinator) return void 0;
|
|
1575
|
+
const pending = coordinator.getAllPendingMessages?.() ?? [];
|
|
1576
|
+
const parsedQuery = parseAgentApiEventsQuery(target);
|
|
1577
|
+
if (parsedQuery.error && pending.length > 0) {
|
|
1578
|
+
return { status: 400, body: parsedQuery.error };
|
|
1579
|
+
}
|
|
1580
|
+
if (pending.length === 0) return void 0;
|
|
1581
|
+
const normalized = sortBySeq(normalizeVisibleMessages(pending));
|
|
1582
|
+
const filtered = parsedQuery.sinceSeq !== null ? normalized.filter((message) => {
|
|
1583
|
+
const seq = messageSeq(message);
|
|
1584
|
+
return Number.isFinite(seq) && seq > parsedQuery.sinceSeq;
|
|
1585
|
+
}) : normalized;
|
|
1586
|
+
const events = filtered.slice(0, parsedQuery.limit);
|
|
1587
|
+
const hasMore = filtered.length > events.length;
|
|
1588
|
+
const newestEvent = events[events.length - 1];
|
|
1589
|
+
const lastSeenMsgId = newestEvent?.message_id ?? newestEvent?.id ?? null;
|
|
1590
|
+
const lastSeenSeq = newestEvent?.seq ?? parsedQuery.sinceSeq;
|
|
1591
|
+
if (events.length > 0) {
|
|
1592
|
+
coordinator.consumeVisibleMessages({ messages: events, source: "agent_api_events" });
|
|
1593
|
+
}
|
|
1594
|
+
coordinator.recordDrainOutcome?.({
|
|
1595
|
+
source: "daemon_pending",
|
|
1596
|
+
sinceCursorKind: parsedQuery.sinceCursorKind,
|
|
1597
|
+
notifiedCount: pending.length,
|
|
1598
|
+
drainedCount: events.length,
|
|
1599
|
+
hasMore
|
|
1600
|
+
});
|
|
1601
|
+
return {
|
|
1602
|
+
status: 200,
|
|
1603
|
+
body: {
|
|
1604
|
+
events,
|
|
1605
|
+
last_seen_msgId: lastSeenMsgId,
|
|
1606
|
+
last_seen_seq: lastSeenSeq,
|
|
1607
|
+
reply_target: null,
|
|
1608
|
+
pending_notice_ids: [],
|
|
1609
|
+
wake_reason: null,
|
|
1610
|
+
has_more: hasMore
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
function heldAvailableActions(action) {
|
|
1615
|
+
return action === "send" ? ["check_messages", "send_draft", "send_anyway"] : ["check_messages", "retry_action"];
|
|
1616
|
+
}
|
|
1617
|
+
function localHeldResponse(input) {
|
|
1618
|
+
if (input.messages.length === 0) return void 0;
|
|
1619
|
+
const normalized = sortBySeq(normalizeVisibleMessages(input.messages, input.target));
|
|
1620
|
+
const heldMessages = latestVisibleMessages(normalized, LOCAL_HELD_CONTEXT_LIMIT);
|
|
1621
|
+
const omittedMessageCount = Math.max(0, normalized.length - heldMessages.length);
|
|
1622
|
+
const seenUpToSeq = maxMessageSeq(normalized);
|
|
1623
|
+
if (seenUpToSeq === void 0) return void 0;
|
|
1624
|
+
input.coordinator.consumeVisibleMessages({
|
|
1625
|
+
target: input.target,
|
|
1626
|
+
messages: heldMessages,
|
|
1627
|
+
boundarySeq: seenUpToSeq,
|
|
1628
|
+
source: input.source
|
|
1629
|
+
});
|
|
1630
|
+
const response = {
|
|
1631
|
+
state: "held",
|
|
1632
|
+
outcome: "held",
|
|
1633
|
+
subtype: "freshness",
|
|
1634
|
+
reason: "newer_messages_available",
|
|
1635
|
+
available_actions: heldAvailableActions(input.action),
|
|
1636
|
+
heldMessages,
|
|
1637
|
+
newMessageCount: normalized.length,
|
|
1638
|
+
shownMessageCount: heldMessages.length,
|
|
1639
|
+
omittedMessageCount
|
|
1640
|
+
};
|
|
1641
|
+
response.seenUpToSeq = seenUpToSeq;
|
|
1642
|
+
return response;
|
|
1643
|
+
}
|
|
1644
|
+
function recordFreshnessDecision(coordinator, decision) {
|
|
1645
|
+
coordinator?.recordFreshnessDecision?.(decision);
|
|
1646
|
+
}
|
|
1647
|
+
function agentApiSideEffectAction(pathname) {
|
|
1648
|
+
if (pathname === "/internal/agent-api/send") return "send";
|
|
1649
|
+
if (pathname === "/internal/agent-api/tasks/claim") return "task_claim";
|
|
1650
|
+
if (pathname === "/internal/agent-api/tasks/update-status") return "task_update";
|
|
1651
|
+
return void 0;
|
|
1652
|
+
}
|
|
1653
|
+
function sideEffectTarget(action, body) {
|
|
1654
|
+
const field = action === "send" ? body.target : body.channel;
|
|
1655
|
+
return typeof field === "string" && field.length > 0 ? field : void 0;
|
|
1656
|
+
}
|
|
1490
1657
|
function parseTargetFields(target) {
|
|
1491
1658
|
if (target.startsWith("dm:@")) {
|
|
1492
1659
|
const rest = target.slice("dm:@".length);
|
|
@@ -1543,42 +1710,98 @@ async function loadRecentTargetMessages(registration, headers, target) {
|
|
|
1543
1710
|
const parsed = await res.json().catch(() => null);
|
|
1544
1711
|
return Array.isArray(parsed?.messages) ? normalizeVisibleMessages(parsed.messages, target) : [];
|
|
1545
1712
|
}
|
|
1546
|
-
async function
|
|
1713
|
+
async function prepareAgentApiSideEffectForward(registration, headers, rawBody, action) {
|
|
1547
1714
|
let body;
|
|
1548
1715
|
try {
|
|
1549
1716
|
body = rawBody ? JSON.parse(rawBody) : {};
|
|
1550
1717
|
} catch {
|
|
1551
1718
|
return { bodyText: rawBody };
|
|
1552
1719
|
}
|
|
1553
|
-
const target =
|
|
1720
|
+
const target = sideEffectTarget(action, body);
|
|
1554
1721
|
const coordinator = registration.inboxCoordinator;
|
|
1555
|
-
if (!target || !coordinator
|
|
1722
|
+
if (!target || !coordinator) {
|
|
1723
|
+
return { bodyText: JSON.stringify(body), target };
|
|
1724
|
+
}
|
|
1725
|
+
if (action === "send" && body.continueAnyway === true) {
|
|
1726
|
+
recordFreshnessDecision(coordinator, {
|
|
1727
|
+
action,
|
|
1728
|
+
decision: "bypass",
|
|
1729
|
+
target,
|
|
1730
|
+
inboxTrustState: "trusted",
|
|
1731
|
+
reason: "continue_anyway"
|
|
1732
|
+
});
|
|
1556
1733
|
return { bodyText: JSON.stringify(body), target };
|
|
1557
1734
|
}
|
|
1558
1735
|
const pending = coordinator.getPendingMessages(target);
|
|
1559
1736
|
if (pending.length > 0) {
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
|
|
1737
|
+
const localResponse = localHeldResponse({
|
|
1738
|
+
action,
|
|
1739
|
+
target,
|
|
1740
|
+
messages: pending,
|
|
1741
|
+
coordinator,
|
|
1742
|
+
source: "side_effect_preflight_context"
|
|
1743
|
+
});
|
|
1744
|
+
if (localResponse) {
|
|
1745
|
+
recordFreshnessDecision(coordinator, {
|
|
1746
|
+
action,
|
|
1747
|
+
decision: "local_hold",
|
|
1748
|
+
target,
|
|
1749
|
+
inboxTrustState: "trusted",
|
|
1750
|
+
reason: "exact_target_pending",
|
|
1751
|
+
pendingCount: pending.length,
|
|
1752
|
+
pendingMaxSeq: maxMessageSeq(pending),
|
|
1753
|
+
modelSeenSeq: coordinator.getBoundary(target),
|
|
1754
|
+
heldMessageCount: typeof localResponse.shownMessageCount === "number" ? localResponse.shownMessageCount : void 0,
|
|
1755
|
+
omittedMessageCount: typeof localResponse.omittedMessageCount === "number" ? localResponse.omittedMessageCount : void 0
|
|
1756
|
+
});
|
|
1563
1757
|
}
|
|
1564
|
-
return {
|
|
1758
|
+
return {
|
|
1759
|
+
bodyText: JSON.stringify(body),
|
|
1760
|
+
target,
|
|
1761
|
+
localResponse
|
|
1762
|
+
};
|
|
1565
1763
|
}
|
|
1566
1764
|
const existingBoundary = typeof body.seenUpToSeq === "number" && Number.isFinite(body.seenUpToSeq) ? Math.max(0, Math.floor(body.seenUpToSeq)) : void 0;
|
|
1567
1765
|
const loadedBoundary = coordinator.getBoundary(target);
|
|
1568
1766
|
const boundary = Math.max(existingBoundary ?? 0, loadedBoundary ?? 0);
|
|
1569
1767
|
if (boundary > 0) {
|
|
1570
|
-
body.seenUpToSeq = boundary;
|
|
1768
|
+
if (action === "send") body.seenUpToSeq = boundary;
|
|
1769
|
+
recordFreshnessDecision(coordinator, {
|
|
1770
|
+
action,
|
|
1771
|
+
decision: "forward",
|
|
1772
|
+
target,
|
|
1773
|
+
inboxTrustState: "trusted",
|
|
1774
|
+
reason: "model_seen_boundary",
|
|
1775
|
+
pendingCount: 0,
|
|
1776
|
+
modelSeenSeq: boundary
|
|
1777
|
+
});
|
|
1571
1778
|
return { bodyText: JSON.stringify(body), target };
|
|
1572
1779
|
}
|
|
1573
1780
|
const recent = await loadRecentTargetMessages(registration, headers, target);
|
|
1574
1781
|
if (recent.length > 0) {
|
|
1575
1782
|
const seenUpToSeq = maxMessageSeq(recent);
|
|
1576
|
-
coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "
|
|
1783
|
+
coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "side_effect_preflight_context" });
|
|
1784
|
+
recordFreshnessDecision(coordinator, {
|
|
1785
|
+
action,
|
|
1786
|
+
decision: "syncing_hold",
|
|
1787
|
+
target,
|
|
1788
|
+
inboxTrustState: "untrusted",
|
|
1789
|
+
reason: "target_first_touch_recent_context",
|
|
1790
|
+
pendingCount: 0,
|
|
1791
|
+
pendingMaxSeq: seenUpToSeq,
|
|
1792
|
+
modelSeenSeq: 0,
|
|
1793
|
+
heldMessageCount: recent.length,
|
|
1794
|
+
omittedMessageCount: 0
|
|
1795
|
+
});
|
|
1577
1796
|
return {
|
|
1578
1797
|
bodyText: JSON.stringify(body),
|
|
1579
1798
|
target,
|
|
1580
1799
|
localResponse: {
|
|
1581
1800
|
state: "held",
|
|
1801
|
+
outcome: "held",
|
|
1802
|
+
subtype: "freshness",
|
|
1803
|
+
reason: "newer_messages_available",
|
|
1804
|
+
available_actions: heldAvailableActions(action),
|
|
1582
1805
|
seenUpToSeq,
|
|
1583
1806
|
heldMessages: recent,
|
|
1584
1807
|
newMessageCount: recent.length,
|
|
@@ -1587,6 +1810,15 @@ async function prepareAgentApiSendForward(registration, headers, rawBody) {
|
|
|
1587
1810
|
}
|
|
1588
1811
|
};
|
|
1589
1812
|
}
|
|
1813
|
+
recordFreshnessDecision(coordinator, {
|
|
1814
|
+
action,
|
|
1815
|
+
decision: "forward",
|
|
1816
|
+
target,
|
|
1817
|
+
inboxTrustState: "trusted",
|
|
1818
|
+
reason: "no_exact_target_pending_or_recent_context",
|
|
1819
|
+
pendingCount: 0,
|
|
1820
|
+
modelSeenSeq: 0
|
|
1821
|
+
});
|
|
1590
1822
|
return { bodyText: JSON.stringify(body), target };
|
|
1591
1823
|
}
|
|
1592
1824
|
function shouldBufferJsonResponse(upstream, pathname, registration) {
|
|
@@ -1613,8 +1845,28 @@ function consumeVisibleResponse(registration, targetUrl, sendTarget, responseTex
|
|
|
1613
1845
|
});
|
|
1614
1846
|
return;
|
|
1615
1847
|
}
|
|
1848
|
+
if (targetUrl.pathname === "/internal/agent-api/send" && parsed.state === "sent") {
|
|
1849
|
+
const messageSeq2 = typeof parsed.messageSeq === "number" && Number.isFinite(parsed.messageSeq) ? Math.floor(parsed.messageSeq) : void 0;
|
|
1850
|
+
if (sendTarget && messageSeq2 && messageSeq2 > 0) {
|
|
1851
|
+
coordinator.consumeVisibleMessages({
|
|
1852
|
+
target: sendTarget,
|
|
1853
|
+
messages: normalizeVisibleMessages([{ seq: messageSeq2, id: parsed.messageId }], sendTarget),
|
|
1854
|
+
boundarySeq: messageSeq2,
|
|
1855
|
+
source: "agent_api_send_commit"
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1616
1860
|
if (targetUrl.pathname === "/internal/agent-api/events" && Array.isArray(parsed.events)) {
|
|
1617
|
-
|
|
1861
|
+
const messages = normalizeVisibleMessages(parsed.events);
|
|
1862
|
+
coordinator.consumeVisibleMessages({ messages, source: "agent_api_events" });
|
|
1863
|
+
coordinator.recordDrainOutcome?.({
|
|
1864
|
+
source: "server_events",
|
|
1865
|
+
sinceCursorKind: parseAgentApiEventsQuery(targetUrl).sinceCursorKind,
|
|
1866
|
+
notifiedCount: 0,
|
|
1867
|
+
drainedCount: messages.length,
|
|
1868
|
+
hasMore: Boolean(parsed.has_more)
|
|
1869
|
+
});
|
|
1618
1870
|
return;
|
|
1619
1871
|
}
|
|
1620
1872
|
if (targetUrl.pathname === "/internal/agent-api/history" && Array.isArray(parsed.messages)) {
|
|
@@ -1733,7 +1985,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
|
|
|
1733
1985
|
...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
|
|
1734
1986
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
1735
1987
|
};
|
|
1736
|
-
|
|
1988
|
+
scrubDaemonChildEnv(spawnEnv);
|
|
1737
1989
|
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
|
|
1738
1990
|
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
|
|
1739
1991
|
delete spawnEnv.SLOCK_AGENT_PROXY_URL;
|
|
@@ -1780,7 +2032,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
|
|
|
1780
2032
|
}
|
|
1781
2033
|
function resolveCommandOnPath(command, deps = {}) {
|
|
1782
2034
|
const platform = deps.platform ?? process.platform;
|
|
1783
|
-
const env = deps.env ?? process.env;
|
|
2035
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
1784
2036
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
1785
2037
|
if (platform === "win32") {
|
|
1786
2038
|
return resolveCommandOnWindows(command, env, execFileSyncFn);
|
|
@@ -1805,7 +2057,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
1805
2057
|
return null;
|
|
1806
2058
|
}
|
|
1807
2059
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
1808
|
-
const env = deps.env ?? process.env;
|
|
2060
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
1809
2061
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
1810
2062
|
try {
|
|
1811
2063
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -2159,8 +2411,7 @@ var ClaudeDriver = class {
|
|
|
2159
2411
|
"- Runtime Profile migration completion is the only exception to CLI-only operation: when a migration notice tells you to acknowledge with `runtime_profile_migration_done`, call the `mcp__chat__runtime_profile_migration_done` tool with the exact `migration_key`; do not use `slock` CLI or reply in chat as the acknowledgment."
|
|
2160
2412
|
],
|
|
2161
2413
|
postStartupNotes: [
|
|
2162
|
-
"**Claude runtime note:**
|
|
2163
|
-
"For long tool runs, you can also use `slock message check` at natural breakpoints to pull pending messages explicitly."
|
|
2414
|
+
"**Claude runtime note:** While you are busy, Slock batches inbox-count notifications instead of injecting message content. Use `slock message check` at natural breakpoints to pull the pending messages before side-effect actions that depend on current context."
|
|
2164
2415
|
],
|
|
2165
2416
|
includeStdinNotificationSection: true,
|
|
2166
2417
|
messageNotificationStyle: "direct"
|
|
@@ -2654,7 +2905,7 @@ var CodexDriver = class {
|
|
|
2654
2905
|
toolPrefix: "",
|
|
2655
2906
|
extraCriticalRules: [],
|
|
2656
2907
|
postStartupNotes: [
|
|
2657
|
-
"**IMPORTANT**: Your process stays alive across turns.
|
|
2908
|
+
"**IMPORTANT**: Your process stays alive across turns. While you are working, Slock may write batched inbox-count notifications into the current turn; call `slock message check` at natural breakpoints to read the pending messages."
|
|
2658
2909
|
],
|
|
2659
2910
|
includeStdinNotificationSection: true,
|
|
2660
2911
|
messageNotificationStyle: "direct"
|
|
@@ -3102,7 +3353,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
3102
3353
|
}
|
|
3103
3354
|
function runCursorModelsCommand() {
|
|
3104
3355
|
return spawnSync("cursor-agent", ["models"], {
|
|
3105
|
-
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3356
|
+
env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
3106
3357
|
encoding: "utf8",
|
|
3107
3358
|
timeout: 5e3
|
|
3108
3359
|
});
|
|
@@ -3149,7 +3400,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
3149
3400
|
}
|
|
3150
3401
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
3151
3402
|
const existsSyncFn = deps.existsSyncFn ?? existsSync6;
|
|
3152
|
-
const env = deps.env ?? process.env;
|
|
3403
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
3153
3404
|
const winPath = path8.win32;
|
|
3154
3405
|
let geminiEntry = null;
|
|
3155
3406
|
try {
|
|
@@ -3323,13 +3574,16 @@ var GeminiDriver = class {
|
|
|
3323
3574
|
// src/drivers/kimi.ts
|
|
3324
3575
|
import { randomUUID } from "crypto";
|
|
3325
3576
|
import { spawn as spawn6 } from "child_process";
|
|
3326
|
-
import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
3577
|
+
import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
3327
3578
|
import os4 from "os";
|
|
3328
3579
|
import path9 from "path";
|
|
3329
3580
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
3330
3581
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
3331
3582
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
3332
3583
|
var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
|
|
3584
|
+
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
3585
|
+
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
3586
|
+
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
3333
3587
|
function parseToolArguments(raw) {
|
|
3334
3588
|
if (typeof raw !== "string") return raw;
|
|
3335
3589
|
try {
|
|
@@ -3338,6 +3592,73 @@ function parseToolArguments(raw) {
|
|
|
3338
3592
|
return raw;
|
|
3339
3593
|
}
|
|
3340
3594
|
}
|
|
3595
|
+
function readKimiConfigSource(home = os4.homedir(), env = process.env) {
|
|
3596
|
+
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3597
|
+
if (inlineConfig && inlineConfig.trim()) {
|
|
3598
|
+
return {
|
|
3599
|
+
raw: inlineConfig,
|
|
3600
|
+
explicitPath: null,
|
|
3601
|
+
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3605
|
+
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
|
|
3606
|
+
try {
|
|
3607
|
+
return {
|
|
3608
|
+
raw: readFileSync3(configPath, "utf8"),
|
|
3609
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3610
|
+
sourcePath: configPath
|
|
3611
|
+
};
|
|
3612
|
+
} catch {
|
|
3613
|
+
return {
|
|
3614
|
+
raw: null,
|
|
3615
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3616
|
+
sourcePath: configPath
|
|
3617
|
+
};
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
function buildKimiSpawnEnv(env = process.env) {
|
|
3621
|
+
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
3622
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3623
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3624
|
+
return scrubDaemonChildEnv(spawnEnv);
|
|
3625
|
+
}
|
|
3626
|
+
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
3627
|
+
return {
|
|
3628
|
+
...process.env,
|
|
3629
|
+
...ctx.config.envVars || {},
|
|
3630
|
+
...overrideEnv || {}
|
|
3631
|
+
};
|
|
3632
|
+
}
|
|
3633
|
+
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
3634
|
+
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
3635
|
+
const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
|
|
3636
|
+
const args = [];
|
|
3637
|
+
let configFilePath = null;
|
|
3638
|
+
let configContent = null;
|
|
3639
|
+
if (source.explicitPath) {
|
|
3640
|
+
configFilePath = source.explicitPath;
|
|
3641
|
+
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
3642
|
+
configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
3643
|
+
configContent = source.raw;
|
|
3644
|
+
if (opts.writeGeneratedConfig !== false) {
|
|
3645
|
+
writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
3646
|
+
chmodSync(configFilePath, 384);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
if (configFilePath) {
|
|
3650
|
+
args.push("--config-file", configFilePath);
|
|
3651
|
+
}
|
|
3652
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3653
|
+
args.push("--model", ctx.config.model);
|
|
3654
|
+
}
|
|
3655
|
+
return {
|
|
3656
|
+
args,
|
|
3657
|
+
env: buildKimiSpawnEnv(env),
|
|
3658
|
+
configFilePath,
|
|
3659
|
+
configContent
|
|
3660
|
+
};
|
|
3661
|
+
}
|
|
3341
3662
|
function resolveKimiSpawn(commandArgs, deps = {}) {
|
|
3342
3663
|
return {
|
|
3343
3664
|
command: resolveCommandOnPath("kimi", deps) ?? "kimi",
|
|
@@ -3361,7 +3682,25 @@ var KimiDriver = class {
|
|
|
3361
3682
|
};
|
|
3362
3683
|
model = {
|
|
3363
3684
|
detectedModelsVerifiedAs: "launchable",
|
|
3364
|
-
toLaunchSpec: (modelId) =>
|
|
3685
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
3686
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
3687
|
+
const launchCtx = {
|
|
3688
|
+
...ctx,
|
|
3689
|
+
config: {
|
|
3690
|
+
...ctx.config,
|
|
3691
|
+
model: modelId
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
3695
|
+
home: opts?.home,
|
|
3696
|
+
writeGeneratedConfig: false
|
|
3697
|
+
});
|
|
3698
|
+
return {
|
|
3699
|
+
args: launch.args,
|
|
3700
|
+
env: launch.env,
|
|
3701
|
+
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3365
3704
|
};
|
|
3366
3705
|
supportsStdinNotification = true;
|
|
3367
3706
|
mcpToolPrefix = "";
|
|
@@ -3415,6 +3754,7 @@ var KimiDriver = class {
|
|
|
3415
3754
|
}
|
|
3416
3755
|
}
|
|
3417
3756
|
}), "utf8");
|
|
3757
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
3418
3758
|
const args = [
|
|
3419
3759
|
"--wire",
|
|
3420
3760
|
"--yolo",
|
|
@@ -3423,14 +3763,15 @@ var KimiDriver = class {
|
|
|
3423
3763
|
"--mcp-config-file",
|
|
3424
3764
|
mcpConfigPath,
|
|
3425
3765
|
"--session",
|
|
3426
|
-
this.sessionId
|
|
3766
|
+
this.sessionId,
|
|
3767
|
+
...launch.args
|
|
3427
3768
|
];
|
|
3428
3769
|
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3429
3770
|
args.push("--model", ctx.config.model);
|
|
3430
3771
|
}
|
|
3431
3772
|
const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
|
|
3432
|
-
const
|
|
3433
|
-
const proc = spawn6(
|
|
3773
|
+
const spawnTarget = resolveKimiSpawn(args);
|
|
3774
|
+
const proc = spawn6(spawnTarget.command, spawnTarget.args, {
|
|
3434
3775
|
cwd: ctx.workingDirectory,
|
|
3435
3776
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3436
3777
|
env: spawnEnv,
|
|
@@ -3438,7 +3779,7 @@ var KimiDriver = class {
|
|
|
3438
3779
|
// and has an 8191-character command-line limit. Kimi's official
|
|
3439
3780
|
// installer/uv entrypoint is an executable, so launch it directly and
|
|
3440
3781
|
// keep prompts on stdin / files instead of routing through cmd.exe.
|
|
3441
|
-
shell:
|
|
3782
|
+
shell: spawnTarget.shell
|
|
3442
3783
|
});
|
|
3443
3784
|
proc.stdin?.write(JSON.stringify({
|
|
3444
3785
|
jsonrpc: "2.0",
|
|
@@ -3554,14 +3895,9 @@ var KimiDriver = class {
|
|
|
3554
3895
|
return detectKimiModels();
|
|
3555
3896
|
}
|
|
3556
3897
|
};
|
|
3557
|
-
function detectKimiModels(home = os4.homedir()) {
|
|
3558
|
-
const
|
|
3559
|
-
|
|
3560
|
-
try {
|
|
3561
|
-
raw = readFileSync3(configPath, "utf8");
|
|
3562
|
-
} catch {
|
|
3563
|
-
return null;
|
|
3564
|
-
}
|
|
3898
|
+
function detectKimiModels(home = os4.homedir(), opts = {}) {
|
|
3899
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
3900
|
+
if (raw === null) return null;
|
|
3565
3901
|
const models = [];
|
|
3566
3902
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
3567
3903
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -3582,7 +3918,7 @@ function detectKimiModels(home = os4.homedir()) {
|
|
|
3582
3918
|
|
|
3583
3919
|
// src/drivers/opencode.ts
|
|
3584
3920
|
import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
|
|
3585
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
3921
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
|
|
3586
3922
|
import os5 from "os";
|
|
3587
3923
|
import path10 from "path";
|
|
3588
3924
|
var CHAT_MCP_SERVER_NAME = "chat";
|
|
@@ -3820,11 +4156,14 @@ function detectOpenCodeModels(home = os5.homedir(), runCommand = runOpenCodeMode
|
|
|
3820
4156
|
if (commandResult.error || commandResult.status !== 0) return null;
|
|
3821
4157
|
return parseOpenCodeModelsOutput(commandResult.stdout);
|
|
3822
4158
|
}
|
|
3823
|
-
function runOpenCodeModelsCommand(home) {
|
|
3824
|
-
const
|
|
3825
|
-
|
|
4159
|
+
function runOpenCodeModelsCommand(home, deps = {}) {
|
|
4160
|
+
const platform = deps.platform ?? process.platform;
|
|
4161
|
+
const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
|
|
4162
|
+
const result = spawnSyncFn("opencode", ["models"], {
|
|
4163
|
+
env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
3826
4164
|
encoding: "utf8",
|
|
3827
|
-
timeout: 5e3
|
|
4165
|
+
timeout: 5e3,
|
|
4166
|
+
shell: platform === "win32"
|
|
3828
4167
|
});
|
|
3829
4168
|
return {
|
|
3830
4169
|
status: result.status,
|
|
@@ -3832,6 +4171,102 @@ function runOpenCodeModelsCommand(home) {
|
|
|
3832
4171
|
error: result.error
|
|
3833
4172
|
};
|
|
3834
4173
|
}
|
|
4174
|
+
function isWindowsCommandShim(commandPath) {
|
|
4175
|
+
const ext = path10.win32.extname(commandPath).toLowerCase();
|
|
4176
|
+
return ext === ".cmd" || ext === ".bat";
|
|
4177
|
+
}
|
|
4178
|
+
function opencodePackageEntryCandidates(packageRoot) {
|
|
4179
|
+
const winPath = path10.win32;
|
|
4180
|
+
return [
|
|
4181
|
+
winPath.join(packageRoot, "bin", "opencode.exe"),
|
|
4182
|
+
winPath.join(packageRoot, "bin", "opencode.js"),
|
|
4183
|
+
winPath.join(packageRoot, "bin", "opencode.mjs"),
|
|
4184
|
+
winPath.join(packageRoot, "dist", "index.js")
|
|
4185
|
+
];
|
|
4186
|
+
}
|
|
4187
|
+
function openCodeSpecForEntry(entry, commandArgs) {
|
|
4188
|
+
if (path10.win32.extname(entry).toLowerCase() === ".exe") {
|
|
4189
|
+
return { command: entry, args: commandArgs, shell: false };
|
|
4190
|
+
}
|
|
4191
|
+
return { command: process.execPath, args: [entry, ...commandArgs], shell: false };
|
|
4192
|
+
}
|
|
4193
|
+
function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
|
|
4194
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync8;
|
|
4195
|
+
const execFileSyncFn = deps.execFileSyncFn;
|
|
4196
|
+
const env = deps.env ?? process.env;
|
|
4197
|
+
const winPath = path10.win32;
|
|
4198
|
+
const candidates = [];
|
|
4199
|
+
if (execFileSyncFn) {
|
|
4200
|
+
try {
|
|
4201
|
+
const globalRoot = String(execFileSyncFn("npm", ["root", "-g"], {
|
|
4202
|
+
encoding: "utf8",
|
|
4203
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4204
|
+
env
|
|
4205
|
+
})).trim();
|
|
4206
|
+
if (globalRoot) {
|
|
4207
|
+
candidates.push(...opencodePackageEntryCandidates(winPath.join(globalRoot, "opencode-ai")));
|
|
4208
|
+
}
|
|
4209
|
+
} catch {
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4212
|
+
if (commandPath) {
|
|
4213
|
+
const commandDir = winPath.dirname(commandPath);
|
|
4214
|
+
candidates.push(...opencodePackageEntryCandidates(winPath.join(commandDir, "node_modules", "opencode-ai")));
|
|
4215
|
+
candidates.push(...extractWindowsShimTargets(commandPath, deps));
|
|
4216
|
+
}
|
|
4217
|
+
for (const candidate of candidates) {
|
|
4218
|
+
if (existsSyncFn(candidate)) return candidate;
|
|
4219
|
+
}
|
|
4220
|
+
return null;
|
|
4221
|
+
}
|
|
4222
|
+
function extractWindowsShimTargets(commandPath, deps = {}) {
|
|
4223
|
+
if (!isWindowsCommandShim(commandPath)) return [];
|
|
4224
|
+
const readFileSyncFn = deps.readFileSyncFn ?? readFileSync4;
|
|
4225
|
+
const commandDir = path10.win32.dirname(commandPath);
|
|
4226
|
+
let raw;
|
|
4227
|
+
try {
|
|
4228
|
+
raw = String(readFileSyncFn(commandPath, "utf8"));
|
|
4229
|
+
} catch {
|
|
4230
|
+
return [];
|
|
4231
|
+
}
|
|
4232
|
+
const candidates = [];
|
|
4233
|
+
const dp0Pattern = /%~dp0\\?([^"\r\n]*?opencode\.(?:exe|js|mjs|cjs))/gi;
|
|
4234
|
+
for (const match of raw.matchAll(dp0Pattern)) {
|
|
4235
|
+
const relative = match[1]?.replace(/^\\+/, "");
|
|
4236
|
+
if (relative) candidates.push(path10.win32.normalize(path10.win32.join(commandDir, relative)));
|
|
4237
|
+
}
|
|
4238
|
+
return candidates;
|
|
4239
|
+
}
|
|
4240
|
+
function resolveOpenCodeSpawn(commandArgs, deps = {}) {
|
|
4241
|
+
const platform = deps.platform ?? process.platform;
|
|
4242
|
+
if (platform !== "win32") {
|
|
4243
|
+
return {
|
|
4244
|
+
command: resolveCommandOnPath("opencode", deps) ?? "opencode",
|
|
4245
|
+
args: commandArgs,
|
|
4246
|
+
shell: false
|
|
4247
|
+
};
|
|
4248
|
+
}
|
|
4249
|
+
const command = resolveCommandOnPath("opencode", deps);
|
|
4250
|
+
if (command && path10.win32.extname(command).toLowerCase() === ".exe") {
|
|
4251
|
+
return { command, args: commandArgs, shell: false };
|
|
4252
|
+
}
|
|
4253
|
+
const packageEntry = resolveWindowsOpenCodePackageEntry(command, deps);
|
|
4254
|
+
if (packageEntry) return openCodeSpecForEntry(packageEntry, commandArgs);
|
|
4255
|
+
if (command && !isWindowsCommandShim(command)) {
|
|
4256
|
+
return { command, args: commandArgs, shell: false };
|
|
4257
|
+
}
|
|
4258
|
+
throw new Error(
|
|
4259
|
+
"Cannot resolve OpenCode CLI entry point on Windows without cmd.exe. Install the native OpenCode executable or install opencode-ai globally so Slock can launch node_modules/opencode-ai/bin/opencode.exe directly."
|
|
4260
|
+
);
|
|
4261
|
+
}
|
|
4262
|
+
function readOpenCodeVersion(deps = {}) {
|
|
4263
|
+
try {
|
|
4264
|
+
const launch = resolveOpenCodeSpawn([], deps);
|
|
4265
|
+
return readCommandVersion(launch.command, launch.args, deps);
|
|
4266
|
+
} catch {
|
|
4267
|
+
return null;
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
3835
4270
|
function isSystemFirstMessageTask(message) {
|
|
3836
4271
|
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX);
|
|
3837
4272
|
}
|
|
@@ -3874,7 +4309,7 @@ var OpenCodeDriver = class {
|
|
|
3874
4309
|
model: modelId
|
|
3875
4310
|
}
|
|
3876
4311
|
};
|
|
3877
|
-
const version =
|
|
4312
|
+
const version = readOpenCodeVersion();
|
|
3878
4313
|
const launch = buildOpenCodeLaunchOptions(launchCtx, opts?.home, version);
|
|
3879
4314
|
return {
|
|
3880
4315
|
args: launch.args,
|
|
@@ -3895,8 +4330,13 @@ var OpenCodeDriver = class {
|
|
|
3895
4330
|
sessionId = null;
|
|
3896
4331
|
sessionAnnounced = false;
|
|
3897
4332
|
probe() {
|
|
3898
|
-
|
|
3899
|
-
|
|
4333
|
+
let version;
|
|
4334
|
+
try {
|
|
4335
|
+
const launch = resolveOpenCodeSpawn([]);
|
|
4336
|
+
version = readCommandVersion(launch.command, launch.args);
|
|
4337
|
+
} catch {
|
|
4338
|
+
return { available: false };
|
|
4339
|
+
}
|
|
3900
4340
|
const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
|
|
3901
4341
|
if (unsupportedMessage) {
|
|
3902
4342
|
return {
|
|
@@ -3904,7 +4344,7 @@ var OpenCodeDriver = class {
|
|
|
3904
4344
|
version: `${version} (requires >= ${MIN_SUPPORTED_OPENCODE_VERSION})`
|
|
3905
4345
|
};
|
|
3906
4346
|
}
|
|
3907
|
-
return { available: true, version };
|
|
4347
|
+
return { available: true, version: version ?? void 0 };
|
|
3908
4348
|
}
|
|
3909
4349
|
async detectModels() {
|
|
3910
4350
|
return detectOpenCodeModels();
|
|
@@ -3912,17 +4352,18 @@ var OpenCodeDriver = class {
|
|
|
3912
4352
|
spawn(ctx) {
|
|
3913
4353
|
this.sessionId = ctx.config.sessionId || null;
|
|
3914
4354
|
this.sessionAnnounced = false;
|
|
3915
|
-
const version =
|
|
4355
|
+
const version = readOpenCodeVersion();
|
|
3916
4356
|
const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
|
|
3917
4357
|
if (unsupportedMessage) {
|
|
3918
4358
|
throw new Error(unsupportedMessage);
|
|
3919
4359
|
}
|
|
3920
4360
|
const launch = buildOpenCodeLaunchOptions(ctx, os5.homedir(), version);
|
|
3921
|
-
const
|
|
4361
|
+
const spawnSpec = resolveOpenCodeSpawn(launch.args);
|
|
4362
|
+
const proc = spawn7(spawnSpec.command, spawnSpec.args, {
|
|
3922
4363
|
cwd: ctx.workingDirectory,
|
|
3923
4364
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3924
4365
|
env: launch.env,
|
|
3925
|
-
shell:
|
|
4366
|
+
shell: spawnSpec.shell
|
|
3926
4367
|
});
|
|
3927
4368
|
proc.stdin?.end();
|
|
3928
4369
|
return { process: proc };
|
|
@@ -3980,6 +4421,297 @@ var OpenCodeDriver = class {
|
|
|
3980
4421
|
}
|
|
3981
4422
|
};
|
|
3982
4423
|
|
|
4424
|
+
// src/drivers/pi.ts
|
|
4425
|
+
import { spawn as spawn8 } from "child_process";
|
|
4426
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
4427
|
+
import path11 from "path";
|
|
4428
|
+
import { fileURLToPath } from "url";
|
|
4429
|
+
import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
|
|
4430
|
+
var CHAT_MCP_TOOL_PREFIX2 = "chat_";
|
|
4431
|
+
var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
|
|
4432
|
+
var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
|
|
4433
|
+
var MIN_SUPPORTED_PI_VERSION = "0.74.0";
|
|
4434
|
+
function parseSemver2(version) {
|
|
4435
|
+
const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
4436
|
+
if (!match) return null;
|
|
4437
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
4438
|
+
}
|
|
4439
|
+
function isSupportedPiVersion(version) {
|
|
4440
|
+
if (!version) return true;
|
|
4441
|
+
const actual = parseSemver2(version);
|
|
4442
|
+
const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
|
|
4443
|
+
if (!actual || !minimum) return true;
|
|
4444
|
+
for (let i = 0; i < 3; i += 1) {
|
|
4445
|
+
if (actual[i] > minimum[i]) return true;
|
|
4446
|
+
if (actual[i] < minimum[i]) return false;
|
|
4447
|
+
}
|
|
4448
|
+
return true;
|
|
4449
|
+
}
|
|
4450
|
+
function unsupportedPiVersionMessage(version) {
|
|
4451
|
+
if (!version || isSupportedPiVersion(version)) return null;
|
|
4452
|
+
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.`;
|
|
4453
|
+
}
|
|
4454
|
+
function probePi(version = PI_SDK_VERSION) {
|
|
4455
|
+
const unsupportedMessage = unsupportedPiVersionMessage(version);
|
|
4456
|
+
if (unsupportedMessage) {
|
|
4457
|
+
return {
|
|
4458
|
+
available: false,
|
|
4459
|
+
version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4462
|
+
return { available: true, version };
|
|
4463
|
+
}
|
|
4464
|
+
function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
|
|
4465
|
+
const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
|
|
4466
|
+
const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
|
|
4467
|
+
if (existsSync9(sourceSibling)) return sourceSibling;
|
|
4468
|
+
const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
|
|
4469
|
+
if (existsSync9(bundledEntry)) return bundledEntry;
|
|
4470
|
+
return path11.join(moduleDir, "piSdkRunner.js");
|
|
4471
|
+
}
|
|
4472
|
+
function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
|
|
4473
|
+
if (runnerPath.endsWith(".ts")) {
|
|
4474
|
+
return [...process.execArgv, runnerPath];
|
|
4475
|
+
}
|
|
4476
|
+
return [runnerPath];
|
|
4477
|
+
}
|
|
4478
|
+
function buildPiLaunchOptions(ctx, opts = {}) {
|
|
4479
|
+
const command = opts.command ?? process.execPath;
|
|
4480
|
+
const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
|
|
4481
|
+
const sessionDir = path11.join(piDir, "sessions");
|
|
4482
|
+
mkdirSync4(sessionDir, { recursive: true });
|
|
4483
|
+
const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
4484
|
+
const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
|
|
4485
|
+
const agentDir = opts.agentDir ?? getAgentDir();
|
|
4486
|
+
const runnerConfigPath = path11.join(
|
|
4487
|
+
piDir,
|
|
4488
|
+
`sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
|
|
4489
|
+
);
|
|
4490
|
+
const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
|
|
4491
|
+
const runnerConfig = {
|
|
4492
|
+
cwd: ctx.workingDirectory,
|
|
4493
|
+
agentDir,
|
|
4494
|
+
sessionDir,
|
|
4495
|
+
sessionId: ctx.config.sessionId || null,
|
|
4496
|
+
standingPrompt: ctx.standingPrompt,
|
|
4497
|
+
prompt: turnPrompt,
|
|
4498
|
+
model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
|
|
4499
|
+
};
|
|
4500
|
+
writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
|
|
4501
|
+
`, { encoding: "utf8", mode: 384 });
|
|
4502
|
+
const args = [
|
|
4503
|
+
...buildPiSdkNodeArgs(runnerPath),
|
|
4504
|
+
"--config",
|
|
4505
|
+
runnerConfigPath
|
|
4506
|
+
];
|
|
4507
|
+
return {
|
|
4508
|
+
command,
|
|
4509
|
+
args,
|
|
4510
|
+
env: slock.spawnEnv,
|
|
4511
|
+
sessionDir,
|
|
4512
|
+
agentDir,
|
|
4513
|
+
runnerConfigPath,
|
|
4514
|
+
sdkVersion: PI_SDK_VERSION
|
|
4515
|
+
};
|
|
4516
|
+
}
|
|
4517
|
+
function isSystemFirstMessageTask2(message) {
|
|
4518
|
+
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
|
|
4519
|
+
}
|
|
4520
|
+
function buildPiSystemPrompt(config) {
|
|
4521
|
+
return buildCliTransportSystemPrompt(config, {
|
|
4522
|
+
toolPrefix: CHAT_MCP_TOOL_PREFIX2,
|
|
4523
|
+
extraCriticalRules: [
|
|
4524
|
+
"- 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."
|
|
4525
|
+
],
|
|
4526
|
+
postStartupNotes: [
|
|
4527
|
+
"**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."
|
|
4528
|
+
],
|
|
4529
|
+
includeStdinNotificationSection: false,
|
|
4530
|
+
messageNotificationStyle: "poll"
|
|
4531
|
+
});
|
|
4532
|
+
}
|
|
4533
|
+
function contentText(content) {
|
|
4534
|
+
if (!content) return "";
|
|
4535
|
+
const chunks = [];
|
|
4536
|
+
for (const item of content) {
|
|
4537
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
4538
|
+
chunks.push(item.text);
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
return chunks.join("");
|
|
4542
|
+
}
|
|
4543
|
+
function apiKeyErrorMessage(line) {
|
|
4544
|
+
const trimmed = line.trim();
|
|
4545
|
+
if (!trimmed) return null;
|
|
4546
|
+
if (/no api key found/i.test(trimmed)) return trimmed;
|
|
4547
|
+
if (/api key.+required/i.test(trimmed)) return trimmed;
|
|
4548
|
+
if (/no models available/i.test(trimmed)) return trimmed;
|
|
4549
|
+
return null;
|
|
4550
|
+
}
|
|
4551
|
+
var PiDriver = class {
|
|
4552
|
+
id = "pi";
|
|
4553
|
+
lifecycle = {
|
|
4554
|
+
kind: "per_turn",
|
|
4555
|
+
start: "defer_until_concrete_message",
|
|
4556
|
+
exit: "terminate_on_turn_end",
|
|
4557
|
+
inFlightWake: "coalesce_into_pending"
|
|
4558
|
+
};
|
|
4559
|
+
communication = {
|
|
4560
|
+
chat: "slock_cli",
|
|
4561
|
+
runtimeControl: "none"
|
|
4562
|
+
};
|
|
4563
|
+
session = {
|
|
4564
|
+
recovery: "resume_or_fresh"
|
|
4565
|
+
};
|
|
4566
|
+
model = {
|
|
4567
|
+
detectedModelsVerifiedAs: "launchable",
|
|
4568
|
+
toLaunchSpec: (modelId, ctx) => {
|
|
4569
|
+
if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
|
|
4570
|
+
const launchCtx = {
|
|
4571
|
+
...ctx,
|
|
4572
|
+
config: {
|
|
4573
|
+
...ctx.config,
|
|
4574
|
+
model: modelId
|
|
4575
|
+
}
|
|
4576
|
+
};
|
|
4577
|
+
const launch = buildPiLaunchOptions(launchCtx);
|
|
4578
|
+
return {
|
|
4579
|
+
args: launch.args,
|
|
4580
|
+
env: launch.env,
|
|
4581
|
+
configFiles: [launch.runnerConfigPath],
|
|
4582
|
+
params: {
|
|
4583
|
+
agentDir: launch.agentDir,
|
|
4584
|
+
sessionDir: launch.sessionDir,
|
|
4585
|
+
sdkVersion: launch.sdkVersion,
|
|
4586
|
+
resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
|
|
4587
|
+
}
|
|
4588
|
+
};
|
|
4589
|
+
}
|
|
4590
|
+
};
|
|
4591
|
+
supportsStdinNotification = false;
|
|
4592
|
+
mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
|
|
4593
|
+
busyDeliveryMode = "none";
|
|
4594
|
+
terminateProcessOnTurnEnd = true;
|
|
4595
|
+
deferSpawnUntilMessage = true;
|
|
4596
|
+
usesSlockCliForCommunication = true;
|
|
4597
|
+
sessionId = null;
|
|
4598
|
+
sessionAnnounced = false;
|
|
4599
|
+
apiKeyErrorAnnounced = false;
|
|
4600
|
+
turnEnded = false;
|
|
4601
|
+
assistantTextByMessageId = /* @__PURE__ */ new Map();
|
|
4602
|
+
shouldDeferWakeMessage(message) {
|
|
4603
|
+
return isSystemFirstMessageTask2(message);
|
|
4604
|
+
}
|
|
4605
|
+
probe() {
|
|
4606
|
+
return probePi();
|
|
4607
|
+
}
|
|
4608
|
+
async detectModels() {
|
|
4609
|
+
return null;
|
|
4610
|
+
}
|
|
4611
|
+
spawn(ctx) {
|
|
4612
|
+
this.sessionId = ctx.config.sessionId || null;
|
|
4613
|
+
this.sessionAnnounced = false;
|
|
4614
|
+
this.apiKeyErrorAnnounced = false;
|
|
4615
|
+
this.turnEnded = false;
|
|
4616
|
+
this.assistantTextByMessageId.clear();
|
|
4617
|
+
const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
|
|
4618
|
+
if (unsupportedMessage) throw new Error(unsupportedMessage);
|
|
4619
|
+
const launch = buildPiLaunchOptions(ctx);
|
|
4620
|
+
const proc = spawn8(launch.command, launch.args, {
|
|
4621
|
+
cwd: ctx.workingDirectory,
|
|
4622
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
4623
|
+
env: launch.env,
|
|
4624
|
+
shell: false
|
|
4625
|
+
});
|
|
4626
|
+
proc.stdin?.end();
|
|
4627
|
+
return { process: proc };
|
|
4628
|
+
}
|
|
4629
|
+
parseLine(line) {
|
|
4630
|
+
let event;
|
|
4631
|
+
try {
|
|
4632
|
+
event = JSON.parse(line);
|
|
4633
|
+
} catch {
|
|
4634
|
+
if (this.apiKeyErrorAnnounced) return [];
|
|
4635
|
+
const message = apiKeyErrorMessage(line);
|
|
4636
|
+
if (!message) return [];
|
|
4637
|
+
this.apiKeyErrorAnnounced = true;
|
|
4638
|
+
this.turnEnded = true;
|
|
4639
|
+
return [
|
|
4640
|
+
{ kind: "error", message },
|
|
4641
|
+
{ kind: "turn_end", sessionId: this.sessionId || void 0 }
|
|
4642
|
+
];
|
|
4643
|
+
}
|
|
4644
|
+
const events = [];
|
|
4645
|
+
if (event.type === "session" && event.id) {
|
|
4646
|
+
this.sessionId = event.id;
|
|
4647
|
+
}
|
|
4648
|
+
if (!this.sessionAnnounced && this.sessionId) {
|
|
4649
|
+
events.push({ kind: "session_init", sessionId: this.sessionId });
|
|
4650
|
+
this.sessionAnnounced = true;
|
|
4651
|
+
}
|
|
4652
|
+
switch (event.type) {
|
|
4653
|
+
case "agent_start":
|
|
4654
|
+
case "turn_start":
|
|
4655
|
+
events.push({ kind: "thinking", text: "" });
|
|
4656
|
+
break;
|
|
4657
|
+
case "message_update":
|
|
4658
|
+
case "message_end":
|
|
4659
|
+
if (event.message?.role === "assistant") {
|
|
4660
|
+
const key = event.message.id || "current";
|
|
4661
|
+
const currentText = contentText(event.message.content);
|
|
4662
|
+
const previousText = this.assistantTextByMessageId.get(key) ?? "";
|
|
4663
|
+
if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
|
|
4664
|
+
events.push({ kind: "text", text: currentText.slice(previousText.length) });
|
|
4665
|
+
} else if (currentText && currentText !== previousText) {
|
|
4666
|
+
events.push({ kind: "text", text: currentText });
|
|
4667
|
+
}
|
|
4668
|
+
this.assistantTextByMessageId.set(key, currentText);
|
|
4669
|
+
if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
|
|
4670
|
+
events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
break;
|
|
4674
|
+
case "tool_execution_start":
|
|
4675
|
+
events.push({
|
|
4676
|
+
kind: "tool_call",
|
|
4677
|
+
name: event.toolName || "unknown_tool",
|
|
4678
|
+
input: event.args
|
|
4679
|
+
});
|
|
4680
|
+
break;
|
|
4681
|
+
case "tool_execution_end":
|
|
4682
|
+
events.push({
|
|
4683
|
+
kind: "tool_output",
|
|
4684
|
+
name: event.toolName || "unknown_tool"
|
|
4685
|
+
});
|
|
4686
|
+
if (event.isError) {
|
|
4687
|
+
events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
|
|
4688
|
+
}
|
|
4689
|
+
break;
|
|
4690
|
+
case "compaction_start":
|
|
4691
|
+
events.push({ kind: "compaction_started" });
|
|
4692
|
+
break;
|
|
4693
|
+
case "compaction_end":
|
|
4694
|
+
events.push({ kind: "compaction_finished" });
|
|
4695
|
+
if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
|
|
4696
|
+
break;
|
|
4697
|
+
case "turn_end":
|
|
4698
|
+
case "agent_end":
|
|
4699
|
+
if (!this.turnEnded) {
|
|
4700
|
+
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
4701
|
+
this.turnEnded = true;
|
|
4702
|
+
}
|
|
4703
|
+
break;
|
|
4704
|
+
}
|
|
4705
|
+
return events;
|
|
4706
|
+
}
|
|
4707
|
+
encodeStdinMessage(_text, _sessionId, _opts) {
|
|
4708
|
+
return null;
|
|
4709
|
+
}
|
|
4710
|
+
buildSystemPrompt(config, _agentId) {
|
|
4711
|
+
return buildPiSystemPrompt(config);
|
|
4712
|
+
}
|
|
4713
|
+
};
|
|
4714
|
+
|
|
3983
4715
|
// src/drivers/index.ts
|
|
3984
4716
|
var driverFactories = {
|
|
3985
4717
|
claude: () => new ClaudeDriver(),
|
|
@@ -3988,7 +4720,8 @@ var driverFactories = {
|
|
|
3988
4720
|
cursor: () => new CursorDriver(),
|
|
3989
4721
|
gemini: () => new GeminiDriver(),
|
|
3990
4722
|
kimi: () => new KimiDriver(),
|
|
3991
|
-
opencode: () => new OpenCodeDriver()
|
|
4723
|
+
opencode: () => new OpenCodeDriver(),
|
|
4724
|
+
pi: () => new PiDriver()
|
|
3992
4725
|
};
|
|
3993
4726
|
function getDriver(runtimeId) {
|
|
3994
4727
|
const createDriver = driverFactories[runtimeId];
|
|
@@ -4001,7 +4734,7 @@ function getDriver(runtimeId) {
|
|
|
4001
4734
|
|
|
4002
4735
|
// src/workspaces.ts
|
|
4003
4736
|
import { readdir, rm, stat } from "fs/promises";
|
|
4004
|
-
import
|
|
4737
|
+
import path12 from "path";
|
|
4005
4738
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
4006
4739
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
4007
4740
|
}
|
|
@@ -4009,7 +4742,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
4009
4742
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
4010
4743
|
return null;
|
|
4011
4744
|
}
|
|
4012
|
-
return
|
|
4745
|
+
return path12.join(dataDir, directoryName);
|
|
4013
4746
|
}
|
|
4014
4747
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
4015
4748
|
return {
|
|
@@ -4058,7 +4791,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
4058
4791
|
return summary;
|
|
4059
4792
|
}
|
|
4060
4793
|
const childSummaries = await Promise.all(
|
|
4061
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
4794
|
+
entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
|
|
4062
4795
|
);
|
|
4063
4796
|
for (const childSummary of childSummaries) {
|
|
4064
4797
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -4077,7 +4810,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
4077
4810
|
if (!entry.isDirectory()) {
|
|
4078
4811
|
return null;
|
|
4079
4812
|
}
|
|
4080
|
-
const dirPath =
|
|
4813
|
+
const dirPath = path12.join(dataDir, entry.name);
|
|
4081
4814
|
try {
|
|
4082
4815
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
4083
4816
|
return {
|
|
@@ -4160,6 +4893,7 @@ function classifyRuntimeError(message, httpStatus) {
|
|
|
4160
4893
|
return "ProviderApiError";
|
|
4161
4894
|
}
|
|
4162
4895
|
if (/\btimeout|timed out\b/i.test(message)) return "TimeoutError";
|
|
4896
|
+
if (/stream closed before response\.completed|error decoding response body/i.test(message)) return "ProviderStreamError";
|
|
4163
4897
|
if (/\brate.?limit|too many requests\b/i.test(message)) return "RateLimitError";
|
|
4164
4898
|
if (/\bnot found\b/i.test(message)) return "NotFoundError";
|
|
4165
4899
|
return "RuntimeError";
|
|
@@ -4334,12 +5068,12 @@ function findSessionJsonl(root, predicate) {
|
|
|
4334
5068
|
for (const entry of entries) {
|
|
4335
5069
|
if (++visited > maxEntries) return null;
|
|
4336
5070
|
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
4337
|
-
return
|
|
5071
|
+
return path13.join(dir, entry.name);
|
|
4338
5072
|
}
|
|
4339
5073
|
for (const entry of entries) {
|
|
4340
5074
|
if (++visited > maxEntries) return null;
|
|
4341
5075
|
if (!entry.isDirectory()) continue;
|
|
4342
|
-
const found = visit(
|
|
5076
|
+
const found = visit(path13.join(dir, entry.name), depth - 1);
|
|
4343
5077
|
if (found) return found;
|
|
4344
5078
|
}
|
|
4345
5079
|
return null;
|
|
@@ -4352,10 +5086,10 @@ function safeSessionFilename(value) {
|
|
|
4352
5086
|
}
|
|
4353
5087
|
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
4354
5088
|
try {
|
|
4355
|
-
const dir =
|
|
4356
|
-
|
|
4357
|
-
const filePath =
|
|
4358
|
-
|
|
5089
|
+
const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
|
|
5090
|
+
mkdirSync5(dir, { recursive: true });
|
|
5091
|
+
const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
5092
|
+
writeFileSync8(filePath, JSON.stringify({
|
|
4359
5093
|
type: "runtime_session_handoff",
|
|
4360
5094
|
runtime,
|
|
4361
5095
|
sessionId,
|
|
@@ -4374,7 +5108,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
4374
5108
|
}
|
|
4375
5109
|
}
|
|
4376
5110
|
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
|
|
4377
|
-
const directPath =
|
|
5111
|
+
const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
|
|
4378
5112
|
if (directPath) {
|
|
4379
5113
|
try {
|
|
4380
5114
|
if (statSync2(directPath).isFile()) {
|
|
@@ -4383,7 +5117,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
|
|
|
4383
5117
|
} catch {
|
|
4384
5118
|
}
|
|
4385
5119
|
}
|
|
4386
|
-
const resolvedPath = runtime === "claude" ? findSessionJsonl(
|
|
5120
|
+
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;
|
|
4387
5121
|
if (!resolvedPath && fallbackDir) {
|
|
4388
5122
|
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
4389
5123
|
if (fallback) return fallback;
|
|
@@ -5034,12 +5768,21 @@ function classifyTerminalFailure(ap) {
|
|
|
5034
5768
|
].filter((value) => !!value);
|
|
5035
5769
|
for (const text of candidates) {
|
|
5036
5770
|
const lower = text.toLowerCase();
|
|
5037
|
-
if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found")) {
|
|
5771
|
+
if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found") || isProviderStreamFailureText(text)) {
|
|
5038
5772
|
return text;
|
|
5039
5773
|
}
|
|
5040
5774
|
}
|
|
5041
5775
|
return null;
|
|
5042
5776
|
}
|
|
5777
|
+
function isProviderStreamFailureText(text) {
|
|
5778
|
+
return /stream closed before response\.completed|error decoding response body/i.test(text);
|
|
5779
|
+
}
|
|
5780
|
+
function isCodexProviderReconnectLog(text) {
|
|
5781
|
+
return /Reconnecting\.\.\.\s*\d+\s*\/\s*\d+/i.test(text);
|
|
5782
|
+
}
|
|
5783
|
+
function isCodexBenignTransportLog(text) {
|
|
5784
|
+
return /Falling back from WebSockets/i.test(text);
|
|
5785
|
+
}
|
|
5043
5786
|
function hasDirectStdinRecoveryEvidence(ap) {
|
|
5044
5787
|
const candidates = [
|
|
5045
5788
|
ap.lastRuntimeError,
|
|
@@ -5234,10 +5977,10 @@ function getMessageDeliveryText(driver) {
|
|
|
5234
5977
|
function getBusyDeliveryNote(driver) {
|
|
5235
5978
|
if (!driver.supportsStdinNotification) return "";
|
|
5236
5979
|
if (driver.busyDeliveryMode === "direct") {
|
|
5237
|
-
return "\n\nNote: While you are busy,
|
|
5980
|
+
return "\n\nNote: While you are busy, the daemon may write batched inbox-count notifications into your active turn. Call check_messages to read the pending messages before context-sensitive side effects.";
|
|
5238
5981
|
}
|
|
5239
5982
|
if (driver.busyDeliveryMode === "gated") {
|
|
5240
|
-
return "\n\nNote: While you are busy,
|
|
5983
|
+
return "\n\nNote: While you are busy, the daemon may write batched inbox-count notifications into your active turn at runtime-observed safe boundaries. Call check_messages to read the pending messages before context-sensitive side effects.";
|
|
5241
5984
|
}
|
|
5242
5985
|
return "\n\nNote: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call check_messages to check for messages.";
|
|
5243
5986
|
}
|
|
@@ -5313,13 +6056,16 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
5313
6056
|
getVisibleBoundary(agentId, target) {
|
|
5314
6057
|
return this.agentVisibleBoundaries.get(agentId)?.get(target);
|
|
5315
6058
|
}
|
|
5316
|
-
|
|
5317
|
-
const collect = (messages) => (messages ?? []).filter((message) =>
|
|
6059
|
+
allPendingVisibleMessages(agentId) {
|
|
6060
|
+
const collect = (messages) => (messages ?? []).filter((message) => typeof message.seq === "number" && message.seq > 0);
|
|
5318
6061
|
return [
|
|
5319
6062
|
...collect(this.agents.get(agentId)?.inbox),
|
|
5320
6063
|
...collect(this.startingInboxes.get(agentId))
|
|
5321
6064
|
];
|
|
5322
6065
|
}
|
|
6066
|
+
pendingVisibleMessages(agentId, target) {
|
|
6067
|
+
return this.allPendingVisibleMessages(agentId).filter((message) => formatMessageTarget(message) === target);
|
|
6068
|
+
}
|
|
5323
6069
|
/**
|
|
5324
6070
|
* Single-inbox consume point for messages that have been rendered into the
|
|
5325
6071
|
* agent-visible input surface. Daemon pending inbox is only a runner cache:
|
|
@@ -5390,8 +6136,71 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
5390
6136
|
return {
|
|
5391
6137
|
getBoundary: (target) => this.getVisibleBoundary(agentId, target),
|
|
5392
6138
|
getPendingMessages: (target) => this.pendingVisibleMessages(agentId, target),
|
|
5393
|
-
|
|
6139
|
+
getAllPendingMessages: () => this.allPendingVisibleMessages(agentId),
|
|
6140
|
+
consumeVisibleMessages: (input) => this.consumeVisibleMessages(agentId, input),
|
|
6141
|
+
recordDrainOutcome: (input) => {
|
|
6142
|
+
this.recordDaemonTrace("daemon.agent.drain.outcome", {
|
|
6143
|
+
agentId,
|
|
6144
|
+
source: input.source,
|
|
6145
|
+
since_cursor_kind: input.sinceCursorKind ?? void 0,
|
|
6146
|
+
notified_count: input.notifiedCount,
|
|
6147
|
+
drained_count: input.drainedCount,
|
|
6148
|
+
has_more: input.hasMore
|
|
6149
|
+
});
|
|
6150
|
+
},
|
|
6151
|
+
recordProxyFailure: (input) => this.recordAgentProxyFailure(agentId, input),
|
|
6152
|
+
recordFreshnessDecision: (input) => {
|
|
6153
|
+
this.recordDaemonTrace("daemon.agent.inbox.freshness_decision", {
|
|
6154
|
+
agentId,
|
|
6155
|
+
action: input.action,
|
|
6156
|
+
decision: input.decision,
|
|
6157
|
+
target: input.target,
|
|
6158
|
+
inbox_trust_state: input.inboxTrustState,
|
|
6159
|
+
reason: input.reason,
|
|
6160
|
+
pending_count: input.pendingCount,
|
|
6161
|
+
pending_max_seq: input.pendingMaxSeq,
|
|
6162
|
+
model_seen_seq: input.modelSeenSeq,
|
|
6163
|
+
held_message_count: input.heldMessageCount,
|
|
6164
|
+
omitted_message_count: input.omittedMessageCount
|
|
6165
|
+
});
|
|
6166
|
+
this.recordFreshnessDecisionActivity(agentId, input);
|
|
6167
|
+
}
|
|
6168
|
+
};
|
|
6169
|
+
}
|
|
6170
|
+
recordAgentProxyFailure(agentId, input) {
|
|
6171
|
+
this.recordDaemonTrace("daemon.agent.proxy.failure", {
|
|
6172
|
+
agentId,
|
|
6173
|
+
method: input.method,
|
|
6174
|
+
path: input.pathname,
|
|
6175
|
+
query_keys: input.queryKeys,
|
|
6176
|
+
error_name: input.errorName,
|
|
6177
|
+
error_message: input.errorMessage
|
|
6178
|
+
}, "error");
|
|
6179
|
+
}
|
|
6180
|
+
recordFreshnessDecisionActivity(agentId, input) {
|
|
6181
|
+
if (input.decision !== "local_hold" && input.decision !== "syncing_hold") return;
|
|
6182
|
+
const ap = this.agents.get(agentId);
|
|
6183
|
+
const messageCount = input.pendingCount ?? input.heldMessageCount ?? 0;
|
|
6184
|
+
const title = input.action === "send" ? "Send held by freshness check" : input.action === "task_claim" ? "Task claim held by freshness check" : "Task update held by freshness check";
|
|
6185
|
+
const entry = {
|
|
6186
|
+
kind: "slock_action",
|
|
6187
|
+
title,
|
|
6188
|
+
text: [
|
|
6189
|
+
input.target ? `target: ${input.target}` : null,
|
|
6190
|
+
`new messages: ${messageCount} newer message${messageCount === 1 ? "" : "s"}`,
|
|
6191
|
+
`decision: ${input.decision === "syncing_hold" ? "syncing hold" : "local hold"}; review the newer context before retrying`
|
|
6192
|
+
].filter((line) => Boolean(line)).join("\n")
|
|
5394
6193
|
};
|
|
6194
|
+
if (ap) ap.activityClientSeq += 1;
|
|
6195
|
+
this.sendToServer({
|
|
6196
|
+
type: "agent:activity",
|
|
6197
|
+
agentId,
|
|
6198
|
+
activity: ap?.lastActivity || "online",
|
|
6199
|
+
detail: ap?.lastActivityDetail || "",
|
|
6200
|
+
entries: [entry],
|
|
6201
|
+
launchId: ap?.launchId || void 0,
|
|
6202
|
+
clientSeq: ap?.activityClientSeq
|
|
6203
|
+
});
|
|
5395
6204
|
}
|
|
5396
6205
|
recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
|
|
5397
6206
|
const span = this.tracer.startSpan(name, {
|
|
@@ -5617,26 +6426,26 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
5617
6426
|
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
5618
6427
|
try {
|
|
5619
6428
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
5620
|
-
const agentDataDir =
|
|
6429
|
+
const agentDataDir = path13.join(this.dataDir, agentId);
|
|
5621
6430
|
await mkdir(agentDataDir, { recursive: true });
|
|
5622
6431
|
const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
5623
|
-
const memoryMdPath =
|
|
6432
|
+
const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
|
|
5624
6433
|
try {
|
|
5625
6434
|
await access(memoryMdPath);
|
|
5626
6435
|
} catch {
|
|
5627
6436
|
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
5628
6437
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
5629
6438
|
}
|
|
5630
|
-
const notesDir =
|
|
6439
|
+
const notesDir = path13.join(agentDataDir, "notes");
|
|
5631
6440
|
await mkdir(notesDir, { recursive: true });
|
|
5632
6441
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
5633
6442
|
const seedFiles = buildOnboardingSeedFiles();
|
|
5634
6443
|
for (const { relativePath, content } of seedFiles) {
|
|
5635
|
-
const fullPath =
|
|
6444
|
+
const fullPath = path13.join(agentDataDir, relativePath);
|
|
5636
6445
|
try {
|
|
5637
6446
|
await access(fullPath);
|
|
5638
6447
|
} catch {
|
|
5639
|
-
await mkdir(
|
|
6448
|
+
await mkdir(path13.dirname(fullPath), { recursive: true });
|
|
5640
6449
|
await writeFile(fullPath, content);
|
|
5641
6450
|
}
|
|
5642
6451
|
}
|
|
@@ -5825,8 +6634,24 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5825
6634
|
proc.stderr?.on("data", (chunk) => {
|
|
5826
6635
|
const text = chunk.toString().trim();
|
|
5827
6636
|
if (!text) return;
|
|
5828
|
-
if (/Reconnecting\.\.\.|Falling back from WebSockets/i.test(text)) return;
|
|
5829
6637
|
const current = this.agents.get(agentId);
|
|
6638
|
+
if (driver.id === "codex" && isCodexProviderReconnectLog(text)) {
|
|
6639
|
+
if (current) {
|
|
6640
|
+
current.recentStderr = pushRecentStderr(current.recentStderr, text);
|
|
6641
|
+
}
|
|
6642
|
+
this.recordDaemonTrace("daemon.agent.provider_reconnect", {
|
|
6643
|
+
agentId,
|
|
6644
|
+
launchId: current?.launchId || void 0,
|
|
6645
|
+
runtime: config.runtime,
|
|
6646
|
+
model: config.model
|
|
6647
|
+
});
|
|
6648
|
+
this.broadcastActivity(agentId, "working", "Codex reconnecting to provider\u2026", [
|
|
6649
|
+
{ kind: "text", text }
|
|
6650
|
+
]);
|
|
6651
|
+
logger.info(`[Agent ${agentId} stderr]: ${text}`);
|
|
6652
|
+
return;
|
|
6653
|
+
}
|
|
6654
|
+
if (driver.id === "codex" && isCodexBenignTransportLog(text)) return;
|
|
5830
6655
|
if (current) {
|
|
5831
6656
|
current.recentStderr = pushRecentStderr(current.recentStderr, text);
|
|
5832
6657
|
}
|
|
@@ -5970,10 +6795,20 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
5970
6795
|
}
|
|
5971
6796
|
this.broadcastActivity(agentId, "online", "Process idle");
|
|
5972
6797
|
} else {
|
|
5973
|
-
this.idleAgentConfigs.delete(agentId);
|
|
5974
6798
|
const reason = formatCrashReason(finalCode, finalSignal, ap);
|
|
5975
|
-
|
|
5976
|
-
|
|
6799
|
+
if (terminalFailureDetail && isProviderStreamFailureText(terminalFailureDetail)) {
|
|
6800
|
+
this.idleAgentConfigs.set(agentId, {
|
|
6801
|
+
config: { ...ap.config, sessionId: ap.sessionId },
|
|
6802
|
+
sessionId: ap.sessionId,
|
|
6803
|
+
launchId: ap.launchId
|
|
6804
|
+
});
|
|
6805
|
+
logger.warn(`[Agent ${agentId}] Recoverable provider stream failure (${reason}) \u2014 keeping agent wakeable`);
|
|
6806
|
+
this.sendAgentStatus(agentId, "active", ap.launchId);
|
|
6807
|
+
} else {
|
|
6808
|
+
this.idleAgentConfigs.delete(agentId);
|
|
6809
|
+
logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
|
|
6810
|
+
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
6811
|
+
}
|
|
5977
6812
|
if (terminalFailureDetail) {
|
|
5978
6813
|
this.broadcastActivity(
|
|
5979
6814
|
agentId,
|
|
@@ -6446,6 +7281,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6446
7281
|
}
|
|
6447
7282
|
if (ap.driver.busyDeliveryMode === "gated") {
|
|
6448
7283
|
ap.pendingNotificationCount++;
|
|
7284
|
+
if (!ap.notificationTimer) {
|
|
7285
|
+
ap.notificationTimer = setTimeout(() => {
|
|
7286
|
+
this.sendStdinNotification(agentId);
|
|
7287
|
+
}, 3e3);
|
|
7288
|
+
}
|
|
6449
7289
|
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
6450
7290
|
reason: "busy_message",
|
|
6451
7291
|
pendingMessages: ap.inbox.length
|
|
@@ -6458,7 +7298,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6458
7298
|
session_id_present: true,
|
|
6459
7299
|
launchId: ap.launchId || void 0,
|
|
6460
7300
|
inbox_count: ap.inbox.length,
|
|
6461
|
-
pending_notification_count: ap.pendingNotificationCount
|
|
7301
|
+
pending_notification_count: ap.pendingNotificationCount,
|
|
7302
|
+
notification_timer_present: Boolean(ap.notificationTimer)
|
|
6462
7303
|
}));
|
|
6463
7304
|
return true;
|
|
6464
7305
|
}
|
|
@@ -6481,7 +7322,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6481
7322
|
return true;
|
|
6482
7323
|
}
|
|
6483
7324
|
async resetWorkspace(agentId) {
|
|
6484
|
-
const agentDataDir =
|
|
7325
|
+
const agentDataDir = path13.join(this.dataDir, agentId);
|
|
6485
7326
|
try {
|
|
6486
7327
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
6487
7328
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -6518,7 +7359,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6518
7359
|
return result;
|
|
6519
7360
|
}
|
|
6520
7361
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
6521
|
-
const workspacePath =
|
|
7362
|
+
const workspacePath = path13.join(this.dataDir, agentId);
|
|
6522
7363
|
return {
|
|
6523
7364
|
agentId,
|
|
6524
7365
|
launchId,
|
|
@@ -6775,7 +7616,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6775
7616
|
}
|
|
6776
7617
|
// Workspace file browsing
|
|
6777
7618
|
async getFileTree(agentId, dirPath) {
|
|
6778
|
-
const agentDir =
|
|
7619
|
+
const agentDir = path13.join(this.dataDir, agentId);
|
|
6779
7620
|
try {
|
|
6780
7621
|
await stat2(agentDir);
|
|
6781
7622
|
} catch {
|
|
@@ -6783,8 +7624,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6783
7624
|
}
|
|
6784
7625
|
let targetDir = agentDir;
|
|
6785
7626
|
if (dirPath) {
|
|
6786
|
-
const resolved =
|
|
6787
|
-
if (!resolved.startsWith(agentDir +
|
|
7627
|
+
const resolved = path13.resolve(agentDir, dirPath);
|
|
7628
|
+
if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
|
|
6788
7629
|
return [];
|
|
6789
7630
|
}
|
|
6790
7631
|
targetDir = resolved;
|
|
@@ -6792,14 +7633,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6792
7633
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
6793
7634
|
}
|
|
6794
7635
|
async readFile(agentId, filePath) {
|
|
6795
|
-
const agentDir =
|
|
6796
|
-
const resolved =
|
|
6797
|
-
if (!resolved.startsWith(agentDir +
|
|
7636
|
+
const agentDir = path13.join(this.dataDir, agentId);
|
|
7637
|
+
const resolved = path13.resolve(agentDir, filePath);
|
|
7638
|
+
if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
|
|
6798
7639
|
throw new Error("Access denied");
|
|
6799
7640
|
}
|
|
6800
7641
|
const info = await stat2(resolved);
|
|
6801
7642
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
6802
|
-
const ext =
|
|
7643
|
+
const ext = path13.extname(resolved).toLowerCase();
|
|
6803
7644
|
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
6804
7645
|
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
6805
7646
|
const content = await readFile(resolved, "utf-8");
|
|
@@ -6834,13 +7675,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6834
7675
|
const agent = this.agents.get(agentId);
|
|
6835
7676
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
6836
7677
|
const home = os6.homedir();
|
|
6837
|
-
const workspaceDir =
|
|
7678
|
+
const workspaceDir = path13.join(this.dataDir, agentId);
|
|
6838
7679
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
6839
7680
|
const globalResults = await Promise.all(
|
|
6840
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
7681
|
+
paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
|
|
6841
7682
|
);
|
|
6842
7683
|
const workspaceResults = await Promise.all(
|
|
6843
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
7684
|
+
paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
|
|
6844
7685
|
);
|
|
6845
7686
|
const dedup = (skills) => {
|
|
6846
7687
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6869,7 +7710,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6869
7710
|
const skills = [];
|
|
6870
7711
|
for (const entry of entries) {
|
|
6871
7712
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
6872
|
-
const skillMd =
|
|
7713
|
+
const skillMd = path13.join(dir, entry.name, "SKILL.md");
|
|
6873
7714
|
try {
|
|
6874
7715
|
const content = await readFile(skillMd, "utf-8");
|
|
6875
7716
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -6880,7 +7721,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6880
7721
|
} else if (entry.name.endsWith(".md")) {
|
|
6881
7722
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
6882
7723
|
try {
|
|
6883
|
-
const content = await readFile(
|
|
7724
|
+
const content = await readFile(path13.join(dir, entry.name), "utf-8");
|
|
6884
7725
|
const skill = this.parseSkillMd(cmdName, content);
|
|
6885
7726
|
skill.sourcePath = dir;
|
|
6886
7727
|
skills.push(skill);
|
|
@@ -7283,11 +8124,17 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7283
8124
|
if (reason !== "turn_end") {
|
|
7284
8125
|
if (ap.gatedSteering.toolBoundaryFlushDisabled) return false;
|
|
7285
8126
|
if (ap.gatedSteering.compacting || ap.gatedSteering.outstandingToolUses > 0) return false;
|
|
8127
|
+
this.recordGatedSteeringEvent(agentId, ap, "notify", { reason, pendingMessages: ap.inbox.length });
|
|
8128
|
+
return this.sendStdinNotification(agentId);
|
|
7286
8129
|
}
|
|
7287
8130
|
const pendingMessages = ap.inbox.length;
|
|
7288
8131
|
const pendingNotificationCount = ap.pendingNotificationCount;
|
|
7289
8132
|
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
7290
8133
|
ap.pendingNotificationCount = 0;
|
|
8134
|
+
if (ap.notificationTimer) {
|
|
8135
|
+
clearTimeout(ap.notificationTimer);
|
|
8136
|
+
ap.notificationTimer = null;
|
|
8137
|
+
}
|
|
7291
8138
|
ap.gatedSteering.lastFlushReason = reason;
|
|
7292
8139
|
if (reason !== "turn_end") {
|
|
7293
8140
|
ap.gatedSteering.inFlightBatch = {
|
|
@@ -7315,28 +8162,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7315
8162
|
}
|
|
7316
8163
|
flushCompactionBoundaryMessages(agentId, ap) {
|
|
7317
8164
|
if (!ap.sessionId || !ap.driver.supportsStdinNotification || ap.inbox.length === 0) return false;
|
|
7318
|
-
if (ap.driver.busyDeliveryMode === "gated") {
|
|
7319
|
-
return this.tryFlushGatedSteering(agentId, ap, "compaction_finished");
|
|
7320
|
-
}
|
|
7321
|
-
if (ap.driver.busyDeliveryMode === "direct") {
|
|
7322
|
-
const pendingMessages = ap.inbox.length;
|
|
7323
|
-
const pendingNotificationCount = ap.pendingNotificationCount;
|
|
7324
|
-
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
7325
|
-
ap.pendingNotificationCount = 0;
|
|
7326
|
-
if (ap.notificationTimer) {
|
|
7327
|
-
clearTimeout(ap.notificationTimer);
|
|
7328
|
-
ap.notificationTimer = null;
|
|
7329
|
-
}
|
|
7330
|
-
this.recordRuntimeTraceEvent(agentId, ap, "runtime.compaction_boundary.flush", {
|
|
7331
|
-
mode: "direct",
|
|
7332
|
-
messageCount: nextMessages.length
|
|
7333
|
-
});
|
|
7334
|
-
if (this.deliverMessagesViaStdin(agentId, ap, nextMessages, "busy")) {
|
|
7335
|
-
return true;
|
|
7336
|
-
}
|
|
7337
|
-
ap.pendingNotificationCount += pendingNotificationCount || pendingMessages;
|
|
7338
|
-
return false;
|
|
7339
|
-
}
|
|
7340
8165
|
if (ap.pendingNotificationCount > 0) {
|
|
7341
8166
|
if (ap.notificationTimer) {
|
|
7342
8167
|
clearTimeout(ap.notificationTimer);
|
|
@@ -7555,6 +8380,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7555
8380
|
if (ap.inbox.length > 0 && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
7556
8381
|
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
7557
8382
|
ap.pendingNotificationCount = 0;
|
|
8383
|
+
if (ap.notificationTimer) {
|
|
8384
|
+
clearTimeout(ap.notificationTimer);
|
|
8385
|
+
ap.notificationTimer = null;
|
|
8386
|
+
}
|
|
7558
8387
|
if (ap.driver.busyDeliveryMode === "gated") {
|
|
7559
8388
|
ap.gatedSteering.lastFlushReason = "turn_end";
|
|
7560
8389
|
this.recordGatedSteeringEvent(agentId, ap, "flush", {
|
|
@@ -7698,13 +8527,16 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7698
8527
|
/** Send a batched notification to the agent via stdin about pending messages */
|
|
7699
8528
|
sendStdinNotification(agentId) {
|
|
7700
8529
|
const ap = this.agents.get(agentId);
|
|
7701
|
-
if (!ap) return;
|
|
8530
|
+
if (!ap) return false;
|
|
7702
8531
|
const count = ap.pendingNotificationCount;
|
|
7703
8532
|
ap.pendingNotificationCount = 0;
|
|
7704
|
-
ap.notificationTimer
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
8533
|
+
if (ap.notificationTimer) {
|
|
8534
|
+
clearTimeout(ap.notificationTimer);
|
|
8535
|
+
ap.notificationTimer = null;
|
|
8536
|
+
}
|
|
8537
|
+
if (count === 0) return false;
|
|
8538
|
+
if (ap.isIdle) return false;
|
|
8539
|
+
if (!ap.sessionId) return false;
|
|
7708
8540
|
if (ap.gatedSteering.compacting) {
|
|
7709
8541
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.compaction_boundary.delivery_suppressed", {
|
|
7710
8542
|
pendingNotificationCount: count,
|
|
@@ -7715,30 +8547,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7715
8547
|
logger.info(
|
|
7716
8548
|
`[Agent ${agentId}] Suppressing stdin delivery until context compaction finishes; pending=${ap.inbox.length}`
|
|
7717
8549
|
);
|
|
7718
|
-
return;
|
|
7719
|
-
}
|
|
7720
|
-
if (ap.driver.busyDeliveryMode === "gated") {
|
|
7721
|
-
this.recordGatedSteeringEvent(agentId, ap, "suppress", {
|
|
7722
|
-
reason: "timer_notification_not_safe_boundary",
|
|
7723
|
-
pendingNotificationCount: count
|
|
7724
|
-
});
|
|
7725
|
-
ap.pendingNotificationCount += count;
|
|
7726
|
-
logger.info(
|
|
7727
|
-
`[Agent ${agentId}] Suppressing raw busy stdin notification until Claude gated steering boundary; pending=${ap.inbox.length}`
|
|
7728
|
-
);
|
|
7729
|
-
return;
|
|
7730
|
-
}
|
|
7731
|
-
if (ap.driver.busyDeliveryMode === "direct" && ap.inbox.length > 0) {
|
|
7732
|
-
const queuedMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
7733
|
-
console.log(`[Agent ${agentId}] Delivering queued message via stdin while busy`);
|
|
7734
|
-
if (this.deliverMessagesViaStdin(agentId, ap, queuedMessages, "busy")) {
|
|
7735
|
-
return;
|
|
7736
|
-
}
|
|
7737
|
-
ap.pendingNotificationCount += count;
|
|
7738
|
-
return;
|
|
8550
|
+
return false;
|
|
7739
8551
|
}
|
|
7740
|
-
const
|
|
7741
|
-
|
|
8552
|
+
const inboxCount = ap.inbox.length;
|
|
8553
|
+
if (inboxCount === 0) return false;
|
|
8554
|
+
const notification = `[System notification: You have ${inboxCount} pending inbox message${inboxCount > 1 ? "s" : ""}. Call check_messages to read ${inboxCount > 1 ? "them" : "it"} when you're ready.]`;
|
|
8555
|
+
logger.info(`[Agent ${agentId}] Sending stdin notification: ${inboxCount} pending inbox message(s)`);
|
|
7742
8556
|
const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId, { mode: "busy" });
|
|
7743
8557
|
if (encoded) {
|
|
7744
8558
|
ap.process.stdin?.write(encoded + "\n");
|
|
@@ -7750,9 +8564,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7750
8564
|
outcome: "written",
|
|
7751
8565
|
mode: "busy",
|
|
7752
8566
|
pending_notification_count: count,
|
|
8567
|
+
inbox_count: inboxCount,
|
|
7753
8568
|
session_id_present: true
|
|
7754
8569
|
});
|
|
8570
|
+
return true;
|
|
7755
8571
|
} else {
|
|
8572
|
+
ap.pendingNotificationCount += count;
|
|
7756
8573
|
this.recordDaemonTrace("daemon.agent.stdin_notification", {
|
|
7757
8574
|
agentId,
|
|
7758
8575
|
runtime: ap.config.runtime,
|
|
@@ -7761,8 +8578,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7761
8578
|
outcome: "encode_failed",
|
|
7762
8579
|
mode: "busy",
|
|
7763
8580
|
pending_notification_count: count,
|
|
8581
|
+
inbox_count: inboxCount,
|
|
7764
8582
|
session_id_present: true
|
|
7765
8583
|
}, "error");
|
|
8584
|
+
return false;
|
|
7766
8585
|
}
|
|
7767
8586
|
}
|
|
7768
8587
|
/** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
|
|
@@ -7868,8 +8687,8 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
7868
8687
|
const nodes = [];
|
|
7869
8688
|
for (const entry of entries) {
|
|
7870
8689
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
7871
|
-
const fullPath =
|
|
7872
|
-
const relativePath =
|
|
8690
|
+
const fullPath = path13.join(dir, entry.name);
|
|
8691
|
+
const relativePath = path13.relative(rootDir, fullPath);
|
|
7873
8692
|
let info;
|
|
7874
8693
|
try {
|
|
7875
8694
|
info = await stat2(fullPath);
|
|
@@ -7998,10 +8817,12 @@ var DaemonConnection = class {
|
|
|
7998
8817
|
messageKind = msg.type;
|
|
7999
8818
|
this.markInbound(messageKind);
|
|
8000
8819
|
this.resetWatchdog();
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8820
|
+
if (messageKind !== "ping") {
|
|
8821
|
+
this.trace("daemon.connection.inbound_received", {
|
|
8822
|
+
message_type: messageKind,
|
|
8823
|
+
last_inbound_age_ms_bucket: "0"
|
|
8824
|
+
});
|
|
8825
|
+
}
|
|
8005
8826
|
this.options.onMessage(msg);
|
|
8006
8827
|
} catch (err) {
|
|
8007
8828
|
this.markInbound("invalid_json");
|
|
@@ -8172,9 +8993,9 @@ var ReminderCache = class {
|
|
|
8172
8993
|
|
|
8173
8994
|
// src/machineLock.ts
|
|
8174
8995
|
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
8175
|
-
import { mkdirSync as
|
|
8996
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
8176
8997
|
import os7 from "os";
|
|
8177
|
-
import
|
|
8998
|
+
import path14 from "path";
|
|
8178
8999
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
8179
9000
|
var DaemonMachineLockConflictError = class extends Error {
|
|
8180
9001
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -8196,7 +9017,7 @@ function resolveDefaultMachineStateRoot() {
|
|
|
8196
9017
|
return resolveSlockHomePath("machines");
|
|
8197
9018
|
}
|
|
8198
9019
|
function ownerPath(lockDir) {
|
|
8199
|
-
return
|
|
9020
|
+
return path14.join(lockDir, "owner.json");
|
|
8200
9021
|
}
|
|
8201
9022
|
function readOwner(lockDir) {
|
|
8202
9023
|
try {
|
|
@@ -8226,13 +9047,13 @@ function acquireDaemonMachineLock(options) {
|
|
|
8226
9047
|
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
8227
9048
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
8228
9049
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
8229
|
-
const machineDir =
|
|
8230
|
-
const lockDir =
|
|
9050
|
+
const machineDir = path14.join(rootDir, lockId);
|
|
9051
|
+
const lockDir = path14.join(machineDir, "daemon.lock");
|
|
8231
9052
|
const token = randomUUID2();
|
|
8232
|
-
|
|
9053
|
+
mkdirSync6(machineDir, { recursive: true });
|
|
8233
9054
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
8234
9055
|
try {
|
|
8235
|
-
|
|
9056
|
+
mkdirSync6(lockDir);
|
|
8236
9057
|
const owner = {
|
|
8237
9058
|
pid: process.pid,
|
|
8238
9059
|
token,
|
|
@@ -8242,7 +9063,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
8242
9063
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
8243
9064
|
};
|
|
8244
9065
|
try {
|
|
8245
|
-
|
|
9066
|
+
writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
|
|
8246
9067
|
`, { mode: 384 });
|
|
8247
9068
|
} catch (err) {
|
|
8248
9069
|
rmSync3(lockDir, { recursive: true, force: true });
|
|
@@ -8279,8 +9100,8 @@ function acquireDaemonMachineLock(options) {
|
|
|
8279
9100
|
}
|
|
8280
9101
|
|
|
8281
9102
|
// src/localTraceSink.ts
|
|
8282
|
-
import { appendFileSync, mkdirSync as
|
|
8283
|
-
import
|
|
9103
|
+
import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
|
|
9104
|
+
import path15 from "path";
|
|
8284
9105
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
8285
9106
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
8286
9107
|
var DEFAULT_MAX_FILES = 8;
|
|
@@ -8316,7 +9137,7 @@ var LocalRotatingTraceSink = class {
|
|
|
8316
9137
|
currentSize = 0;
|
|
8317
9138
|
sequence = 0;
|
|
8318
9139
|
constructor(options) {
|
|
8319
|
-
this.traceDir =
|
|
9140
|
+
this.traceDir = path15.join(options.machineDir, "traces");
|
|
8320
9141
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
8321
9142
|
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
8322
9143
|
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
@@ -8342,15 +9163,15 @@ var LocalRotatingTraceSink = class {
|
|
|
8342
9163
|
return this.currentFile;
|
|
8343
9164
|
}
|
|
8344
9165
|
ensureFile(nextBytes) {
|
|
8345
|
-
|
|
9166
|
+
mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
|
|
8346
9167
|
const nowMs = this.nowMsProvider();
|
|
8347
9168
|
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
8348
9169
|
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
8349
|
-
this.currentFile =
|
|
9170
|
+
this.currentFile = path15.join(
|
|
8350
9171
|
this.traceDir,
|
|
8351
9172
|
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
8352
9173
|
);
|
|
8353
|
-
|
|
9174
|
+
writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
|
|
8354
9175
|
this.currentSize = statSync4(this.currentFile).size;
|
|
8355
9176
|
this.currentFileOpenedAtMs = nowMs;
|
|
8356
9177
|
this.pruneOldFiles();
|
|
@@ -8361,7 +9182,7 @@ var LocalRotatingTraceSink = class {
|
|
|
8361
9182
|
const excess = files.length - this.maxFiles;
|
|
8362
9183
|
if (excess <= 0) return;
|
|
8363
9184
|
for (const file of files.slice(0, excess)) {
|
|
8364
|
-
rmSync4(
|
|
9185
|
+
rmSync4(path15.join(this.traceDir, file), { force: true });
|
|
8365
9186
|
}
|
|
8366
9187
|
}
|
|
8367
9188
|
};
|
|
@@ -8448,11 +9269,11 @@ function isDiagnosticErrorAttr(key) {
|
|
|
8448
9269
|
import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
|
|
8449
9270
|
import { gzipSync } from "zlib";
|
|
8450
9271
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
8451
|
-
import
|
|
9272
|
+
import path16 from "path";
|
|
8452
9273
|
|
|
8453
9274
|
// src/directUploadCapability.ts
|
|
8454
|
-
function joinUrl(base,
|
|
8455
|
-
return `${base.replace(/\/+$/, "")}${
|
|
9275
|
+
function joinUrl(base, path18) {
|
|
9276
|
+
return `${base.replace(/\/+$/, "")}${path18}`;
|
|
8456
9277
|
}
|
|
8457
9278
|
function jsonHeaders(apiKey) {
|
|
8458
9279
|
return {
|
|
@@ -8671,7 +9492,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
8671
9492
|
}, nextMs);
|
|
8672
9493
|
}
|
|
8673
9494
|
async findUploadCandidates() {
|
|
8674
|
-
const traceDir =
|
|
9495
|
+
const traceDir = path16.join(this.options.machineDir, "traces");
|
|
8675
9496
|
let names;
|
|
8676
9497
|
try {
|
|
8677
9498
|
names = await readdir3(traceDir);
|
|
@@ -8683,8 +9504,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
8683
9504
|
const currentFile = this.options.currentFileProvider?.();
|
|
8684
9505
|
const candidates = [];
|
|
8685
9506
|
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
8686
|
-
const file =
|
|
8687
|
-
if (currentFile &&
|
|
9507
|
+
const file = path16.join(traceDir, name);
|
|
9508
|
+
if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
|
|
8688
9509
|
if (await this.isUploaded(file)) continue;
|
|
8689
9510
|
try {
|
|
8690
9511
|
const info = await stat3(file);
|
|
@@ -8758,8 +9579,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
8758
9579
|
}
|
|
8759
9580
|
}
|
|
8760
9581
|
uploadStatePath(file) {
|
|
8761
|
-
const stateDir =
|
|
8762
|
-
return
|
|
9582
|
+
const stateDir = path16.join(this.options.machineDir, "trace-uploads");
|
|
9583
|
+
return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
|
|
8763
9584
|
}
|
|
8764
9585
|
async isUploaded(file) {
|
|
8765
9586
|
try {
|
|
@@ -8771,9 +9592,9 @@ var DaemonTraceBundleUploader = class {
|
|
|
8771
9592
|
}
|
|
8772
9593
|
async markUploaded(file, metadata) {
|
|
8773
9594
|
const stateFile = this.uploadStatePath(file);
|
|
8774
|
-
await mkdir2(
|
|
9595
|
+
await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
|
|
8775
9596
|
await writeFile2(stateFile, `${JSON.stringify({
|
|
8776
|
-
file:
|
|
9597
|
+
file: path16.basename(file),
|
|
8777
9598
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8778
9599
|
...metadata
|
|
8779
9600
|
}, null, 2)}
|
|
@@ -8795,7 +9616,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
|
|
|
8795
9616
|
var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
|
|
8796
9617
|
var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
|
|
8797
9618
|
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
|
|
8798
|
-
var DAEMON_CLI_USAGE =
|
|
9619
|
+
var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
|
|
8799
9620
|
var RunnerCredentialMintError2 = class extends Error {
|
|
8800
9621
|
code;
|
|
8801
9622
|
retryable;
|
|
@@ -8831,9 +9652,9 @@ function runnerCredentialErrorDetail2(error) {
|
|
|
8831
9652
|
async function waitForRunnerCredentialRetry2() {
|
|
8832
9653
|
await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
|
|
8833
9654
|
}
|
|
8834
|
-
function parseDaemonCliArgs(args) {
|
|
9655
|
+
function parseDaemonCliArgs(args, env = {}) {
|
|
8835
9656
|
let serverUrl = "";
|
|
8836
|
-
let apiKey = "";
|
|
9657
|
+
let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
|
|
8837
9658
|
for (let i = 0; i < args.length; i++) {
|
|
8838
9659
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
8839
9660
|
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
@@ -8850,23 +9671,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
8850
9671
|
}
|
|
8851
9672
|
}
|
|
8852
9673
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
8853
|
-
const dirname =
|
|
8854
|
-
const jsPath =
|
|
9674
|
+
const dirname = path17.dirname(fileURLToPath2(moduleUrl));
|
|
9675
|
+
const jsPath = path17.resolve(dirname, "chat-bridge.js");
|
|
8855
9676
|
try {
|
|
8856
9677
|
accessSync(jsPath);
|
|
8857
9678
|
return jsPath;
|
|
8858
9679
|
} catch {
|
|
8859
|
-
return
|
|
9680
|
+
return path17.resolve(dirname, "chat-bridge.ts");
|
|
8860
9681
|
}
|
|
8861
9682
|
}
|
|
8862
9683
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
8863
|
-
const thisDir =
|
|
8864
|
-
const bundledDistPath =
|
|
9684
|
+
const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
|
|
9685
|
+
const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
|
|
8865
9686
|
try {
|
|
8866
9687
|
accessSync(bundledDistPath);
|
|
8867
9688
|
return bundledDistPath;
|
|
8868
9689
|
} catch {
|
|
8869
|
-
const workspaceDistPath =
|
|
9690
|
+
const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
8870
9691
|
accessSync(workspaceDistPath);
|
|
8871
9692
|
return workspaceDistPath;
|
|
8872
9693
|
}
|
|
@@ -9045,7 +9866,7 @@ var DaemonCore = class {
|
|
|
9045
9866
|
}
|
|
9046
9867
|
resolveMachineStateRoot() {
|
|
9047
9868
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
9048
|
-
if (this.options.dataDir) return
|
|
9869
|
+
if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
|
|
9049
9870
|
return resolveDefaultMachineStateRoot();
|
|
9050
9871
|
}
|
|
9051
9872
|
shouldEnableLocalTrace() {
|
|
@@ -9547,6 +10368,8 @@ var DaemonCore = class {
|
|
|
9547
10368
|
};
|
|
9548
10369
|
|
|
9549
10370
|
export {
|
|
10371
|
+
DAEMON_API_KEY_ENV,
|
|
10372
|
+
scrubDaemonAuthEnv,
|
|
9550
10373
|
resolveWorkspaceDirectoryPath,
|
|
9551
10374
|
scanWorkspaceDirectories,
|
|
9552
10375
|
deleteWorkspaceDirectory,
|