@slock-ai/daemon 0.51.1-play.20260519174619 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-NZM6RHMZ.js → chunk-XEHIMW55.js} +448 -605
- package/dist/cli/index.js +118 -232
- package/dist/core.js +2 -6
- package/dist/index.js +3 -5
- package/package.json +1 -2
- package/dist/drivers/piSdkRunner.js +0 -96
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
} from "./chunk-KNMCE6WB.js";
|
|
8
8
|
|
|
9
9
|
// src/core.ts
|
|
10
|
-
import
|
|
10
|
+
import path16 from "path";
|
|
11
11
|
import os8 from "os";
|
|
12
12
|
import { createRequire } from "module";
|
|
13
13
|
import { accessSync } from "fs";
|
|
14
|
-
import { fileURLToPath
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
15
|
|
|
16
16
|
// ../shared/src/tracing/index.ts
|
|
17
17
|
var DEFAULT_TRACE_FLAGS = "00";
|
|
@@ -723,7 +723,6 @@ var SERVER_CAPABILITY_MATRIX = {
|
|
|
723
723
|
var RUNTIMES = [
|
|
724
724
|
{ id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
|
|
725
725
|
{ id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
|
|
726
|
-
{ id: "pi", displayName: "Pi", binary: "pi", supported: true },
|
|
727
726
|
{ id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
|
|
728
727
|
{ id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
|
|
729
728
|
{ id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
|
|
@@ -765,10 +764,10 @@ var DISPLAY_PLAN_CONFIG = {
|
|
|
765
764
|
};
|
|
766
765
|
|
|
767
766
|
// src/agentProcessManager.ts
|
|
768
|
-
import { mkdirSync as
|
|
767
|
+
import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
769
768
|
import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
|
|
770
769
|
import { createHash as createHash2 } from "crypto";
|
|
771
|
-
import
|
|
770
|
+
import path12 from "path";
|
|
772
771
|
import os6 from "os";
|
|
773
772
|
|
|
774
773
|
// src/drivers/claude.ts
|
|
@@ -872,15 +871,13 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
|
|
|
872
871
|
17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
873
872
|
18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
874
873
|
19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--avatar-url pixel:random:<seed>\`, \`--display-name <name>\`, and \`--description <text>\`. Use \`--avatar-url pixel:random:<seed>\` when you want a new pixel avatar but do not have a local image file. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
|
|
875
|
-
20. **\`slock
|
|
876
|
-
21. **\`slock
|
|
877
|
-
22. **\`slock reminder
|
|
878
|
-
23. **\`slock reminder
|
|
879
|
-
24. **\`slock reminder
|
|
880
|
-
25. **\`slock reminder
|
|
881
|
-
26. **\`slock
|
|
882
|
-
27. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
883
|
-
28. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
|
|
874
|
+
20. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
875
|
+
21. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
|
|
876
|
+
22. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
|
|
877
|
+
23. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
878
|
+
24. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
879
|
+
25. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
880
|
+
26. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
|
|
884
881
|
|
|
885
882
|
The CLI prints human-readable canonical text on success (matching the format you see in received messages and history). On failure it prints JSON to stderr:
|
|
886
883
|
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
@@ -981,11 +978,6 @@ Each channel has a **name** and optionally a **description** that define its pur
|
|
|
981
978
|
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
982
979
|
- **Stay on topic** \u2014 when proactively sharing results or updates, post in the channel most relevant to the work. Don't scatter messages across unrelated channels.
|
|
983
980
|
- If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
|
|
984
|
-
const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
|
|
985
|
-
|
|
986
|
-
If a registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first run \`slock integration list\` and match the app to a registered service before browsing the app. Use \`slock integration login --service <service>\` to provision or reuse your agent login for that service. When the command returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes an app URL, open that URL as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`slock profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.` : `### Third-party integrations
|
|
987
|
-
|
|
988
|
-
If a registered third-party service requires login, use Slock Agent Login through the available registered-service interface instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first inspect the registered-service interface and match the app to a registered service before browsing the app. Once the registered-service interface reports the agent login is ready, the agent-side login is ready. If that interface provides an app URL, use it as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow or treat internal request IDs as OAuth callback codes unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use your Slock profile view. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.`;
|
|
989
981
|
const readingHistorySection = isCli ? `### Reading history
|
|
990
982
|
|
|
991
983
|
\`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
|
|
@@ -1157,8 +1149,6 @@ ${discoverySection}
|
|
|
1157
1149
|
|
|
1158
1150
|
${channelAwarenessSection}
|
|
1159
1151
|
|
|
1160
|
-
${thirdPartyIntegrationsSection}
|
|
1161
|
-
|
|
1162
1152
|
${readingHistorySection}
|
|
1163
1153
|
|
|
1164
1154
|
${historicalReferenceSection}
|
|
@@ -1190,17 +1180,6 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
1190
1180
|
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
1191
1181
|
- When done, summarize the result.
|
|
1192
1182
|
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.
|
|
1193
|
-
- For long answers where users need the conclusion first but details still matter, put the conclusion and next action outside any collapse, then use sanitized HTML details blocks for optional depth:
|
|
1194
|
-
|
|
1195
|
-
\`\`\`html
|
|
1196
|
-
<details>
|
|
1197
|
-
<summary>Evidence, logs, or edge cases</summary>
|
|
1198
|
-
|
|
1199
|
-
Detailed notes go here.
|
|
1200
|
-
</details>
|
|
1201
|
-
\`\`\`
|
|
1202
|
-
|
|
1203
|
-
Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
|
|
1204
1183
|
|
|
1205
1184
|
### Conversation etiquette
|
|
1206
1185
|
|
|
@@ -1296,13 +1275,12 @@ You may develop a specialized role over time through your interactions. Embrace
|
|
|
1296
1275
|
|
|
1297
1276
|
## Message Notifications
|
|
1298
1277
|
|
|
1299
|
-
While you are working,
|
|
1278
|
+
While you are working, the daemon may write a batched inbox-count notification into your current turn.
|
|
1300
1279
|
|
|
1301
1280
|
How to handle these:
|
|
1302
|
-
- Treat
|
|
1303
|
-
-
|
|
1304
|
-
-
|
|
1305
|
-
- Use ${checkCmd} only when you need to inspect other pending channels or recover broader context.`;
|
|
1281
|
+
- Treat the notification as a signal that new Slock messages are waiting; it does not include the message content.
|
|
1282
|
+
- Call ${checkCmd} at the next safe breakpoint to materialize the pending messages before taking side-effect actions that depend on current context.
|
|
1283
|
+
- If the new message is higher priority, pivot after reading it. If not, continue your current work.`;
|
|
1306
1284
|
} else {
|
|
1307
1285
|
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.]\``;
|
|
1308
1286
|
prompt += `
|
|
@@ -1370,19 +1348,6 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
|
|
|
1370
1348
|
return candidates.filter((candidate) => existsSync(candidate.path));
|
|
1371
1349
|
}
|
|
1372
1350
|
|
|
1373
|
-
// src/authEnv.ts
|
|
1374
|
-
var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
|
|
1375
|
-
var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
|
|
1376
|
-
function scrubDaemonAuthEnv(env) {
|
|
1377
|
-
delete env[DAEMON_API_KEY_ENV];
|
|
1378
|
-
return env;
|
|
1379
|
-
}
|
|
1380
|
-
function scrubDaemonChildEnv(env) {
|
|
1381
|
-
delete env[DAEMON_API_KEY_ENV];
|
|
1382
|
-
delete env[SLOCK_AGENT_TOKEN_ENV];
|
|
1383
|
-
return env;
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
1351
|
// src/agentCredentialProxy.ts
|
|
1387
1352
|
import { randomBytes } from "crypto";
|
|
1388
1353
|
import http from "http";
|
|
@@ -1390,6 +1355,7 @@ import { URL as URL2 } from "url";
|
|
|
1390
1355
|
var registrations = /* @__PURE__ */ new Map();
|
|
1391
1356
|
var servers = /* @__PURE__ */ new Map();
|
|
1392
1357
|
var DECODED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding"]);
|
|
1358
|
+
var LOCAL_HELD_CONTEXT_LIMIT = 3;
|
|
1393
1359
|
function allocatePort() {
|
|
1394
1360
|
return 43e3 + randomBytes(2).readUInt16BE(0) % 1e4;
|
|
1395
1361
|
}
|
|
@@ -1422,8 +1388,10 @@ async function handleProxyRequest(req, res) {
|
|
|
1422
1388
|
res.end(JSON.stringify({ error: "invalid local agent proxy token", code: "invalid_agent_proxy_token" }));
|
|
1423
1389
|
return;
|
|
1424
1390
|
}
|
|
1391
|
+
const method = req.method ?? "GET";
|
|
1392
|
+
let target;
|
|
1425
1393
|
try {
|
|
1426
|
-
|
|
1394
|
+
target = new URL2(req.url ?? "/", registration.serverUrl);
|
|
1427
1395
|
const headers = new Headers();
|
|
1428
1396
|
for (const [name, value] of Object.entries(req.headers)) {
|
|
1429
1397
|
if (value === void 0) continue;
|
|
@@ -1439,12 +1407,20 @@ async function handleProxyRequest(req, res) {
|
|
|
1439
1407
|
headers.set("X-Agent-Id", registration.agentId);
|
|
1440
1408
|
headers.set("X-Slock-Client", "cli");
|
|
1441
1409
|
headers.set("X-Slock-Agent-Active-Capabilities", registration.activeCapabilities);
|
|
1442
|
-
const method = req.method ?? "GET";
|
|
1443
1410
|
let body = method === "GET" || method === "HEAD" ? void 0 : req;
|
|
1444
1411
|
let sendTarget;
|
|
1445
|
-
|
|
1412
|
+
const sideEffectAction = agentApiSideEffectAction(target.pathname);
|
|
1413
|
+
if (method === "GET" && target.pathname === "/internal/agent-api/events") {
|
|
1414
|
+
const localEvents = localAgentApiEventsResponse(registration, target);
|
|
1415
|
+
if (localEvents) {
|
|
1416
|
+
res.writeHead(localEvents.status, { "content-type": "application/json" });
|
|
1417
|
+
res.end(JSON.stringify(localEvents.body));
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
if (method === "POST" && sideEffectAction) {
|
|
1446
1422
|
const rawBody = await readRequestBody(req);
|
|
1447
|
-
const prepared = await
|
|
1423
|
+
const prepared = await prepareAgentApiSideEffectForward(registration, headers, rawBody, sideEffectAction);
|
|
1448
1424
|
if (prepared.localResponse) {
|
|
1449
1425
|
const responseText = JSON.stringify(prepared.localResponse);
|
|
1450
1426
|
res.writeHead(200, { "content-type": "application/json" });
|
|
@@ -1452,7 +1428,7 @@ async function handleProxyRequest(req, res) {
|
|
|
1452
1428
|
return;
|
|
1453
1429
|
}
|
|
1454
1430
|
body = prepared.bodyText;
|
|
1455
|
-
sendTarget = prepared.target;
|
|
1431
|
+
if (sideEffectAction === "send") sendTarget = prepared.target;
|
|
1456
1432
|
headers.set("content-type", "application/json");
|
|
1457
1433
|
headers.set("content-length", String(Buffer.byteLength(prepared.bodyText)));
|
|
1458
1434
|
}
|
|
@@ -1486,14 +1462,33 @@ async function handleProxyRequest(req, res) {
|
|
|
1486
1462
|
res.end();
|
|
1487
1463
|
}
|
|
1488
1464
|
} catch (err) {
|
|
1465
|
+
const failure = proxyFailureForError(method, target, err);
|
|
1466
|
+
logger.warn(
|
|
1467
|
+
`[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}`
|
|
1468
|
+
);
|
|
1469
|
+
registration.inboxCoordinator?.recordProxyFailure?.(failure);
|
|
1489
1470
|
res.writeHead(502, { "content-type": "application/json" });
|
|
1490
1471
|
res.end(JSON.stringify({
|
|
1491
1472
|
error: "failed to proxy local agent request",
|
|
1492
1473
|
code: "agent_proxy_failed",
|
|
1493
|
-
detail:
|
|
1474
|
+
detail: failure.errorMessage
|
|
1494
1475
|
}));
|
|
1495
1476
|
}
|
|
1496
1477
|
}
|
|
1478
|
+
function proxyFailureForError(method, target, err) {
|
|
1479
|
+
const queryKeys = target ? [.../* @__PURE__ */ new Set([...target.searchParams.keys()])].sort() : [];
|
|
1480
|
+
return {
|
|
1481
|
+
method,
|
|
1482
|
+
pathname: target?.pathname ?? "unknown",
|
|
1483
|
+
queryKeys,
|
|
1484
|
+
errorName: err instanceof Error ? err.name : typeof err,
|
|
1485
|
+
errorMessage: truncateProxyErrorMessage(err instanceof Error ? err.message : String(err))
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
function truncateProxyErrorMessage(message) {
|
|
1489
|
+
const normalized = message.replace(/\s+/g, " ").trim();
|
|
1490
|
+
return normalized.length > 500 ? `${normalized.slice(0, 497)}...` : normalized;
|
|
1491
|
+
}
|
|
1497
1492
|
async function readRequestBody(req) {
|
|
1498
1493
|
let body = "";
|
|
1499
1494
|
req.setEncoding("utf8");
|
|
@@ -1505,14 +1500,6 @@ async function readRequestBody(req) {
|
|
|
1505
1500
|
function messageSeq(message) {
|
|
1506
1501
|
return Number(message.seq ?? 0);
|
|
1507
1502
|
}
|
|
1508
|
-
function boundaryBefore(messages) {
|
|
1509
|
-
let minSeq = Number.POSITIVE_INFINITY;
|
|
1510
|
-
for (const message of messages) {
|
|
1511
|
-
const seq = Math.floor(messageSeq(message));
|
|
1512
|
-
if (Number.isFinite(seq) && seq > 0) minSeq = Math.min(minSeq, seq);
|
|
1513
|
-
}
|
|
1514
|
-
return Number.isFinite(minSeq) ? Math.max(0, minSeq - 1) : void 0;
|
|
1515
|
-
}
|
|
1516
1503
|
function maxMessageSeq(messages) {
|
|
1517
1504
|
let maxSeq = 0;
|
|
1518
1505
|
for (const message of messages) {
|
|
@@ -1521,6 +1508,117 @@ function maxMessageSeq(messages) {
|
|
|
1521
1508
|
}
|
|
1522
1509
|
return maxSeq > 0 ? maxSeq : void 0;
|
|
1523
1510
|
}
|
|
1511
|
+
function sortBySeq(messages) {
|
|
1512
|
+
return [...messages].sort((a, b) => messageSeq(a) - messageSeq(b));
|
|
1513
|
+
}
|
|
1514
|
+
function latestVisibleMessages(messages, limit) {
|
|
1515
|
+
const sorted = sortBySeq(messages);
|
|
1516
|
+
return sorted.slice(Math.max(0, sorted.length - limit));
|
|
1517
|
+
}
|
|
1518
|
+
function parseAgentApiEventsQuery(target) {
|
|
1519
|
+
const limit = Math.min(Math.max(Number(target.searchParams.get("limit")) || 50, 1), 200);
|
|
1520
|
+
const sinceRaw = target.searchParams.get("since")?.trim() ?? "";
|
|
1521
|
+
if (!sinceRaw) return { limit, sinceSeq: null, sinceCursorKind: null };
|
|
1522
|
+
if (sinceRaw === "latest") return { limit, sinceSeq: null, sinceCursorKind: "latest" };
|
|
1523
|
+
const parsed = Number(sinceRaw);
|
|
1524
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
1525
|
+
return {
|
|
1526
|
+
limit,
|
|
1527
|
+
sinceSeq: null,
|
|
1528
|
+
sinceCursorKind: null,
|
|
1529
|
+
error: {
|
|
1530
|
+
error: "since must be a non-negative integer (messageSeq) or 'latest'",
|
|
1531
|
+
code: "since_invalid"
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
return { limit, sinceSeq: Math.floor(parsed), sinceCursorKind: "seq" };
|
|
1536
|
+
}
|
|
1537
|
+
function localAgentApiEventsResponse(registration, target) {
|
|
1538
|
+
const coordinator = registration.inboxCoordinator;
|
|
1539
|
+
if (!coordinator) return void 0;
|
|
1540
|
+
const pending = coordinator.getAllPendingMessages?.() ?? [];
|
|
1541
|
+
const parsedQuery = parseAgentApiEventsQuery(target);
|
|
1542
|
+
if (parsedQuery.error && pending.length > 0) {
|
|
1543
|
+
return { status: 400, body: parsedQuery.error };
|
|
1544
|
+
}
|
|
1545
|
+
if (pending.length === 0) return void 0;
|
|
1546
|
+
const normalized = sortBySeq(normalizeVisibleMessages(pending));
|
|
1547
|
+
const filtered = parsedQuery.sinceSeq !== null ? normalized.filter((message) => {
|
|
1548
|
+
const seq = messageSeq(message);
|
|
1549
|
+
return Number.isFinite(seq) && seq > parsedQuery.sinceSeq;
|
|
1550
|
+
}) : normalized;
|
|
1551
|
+
const events = filtered.slice(0, parsedQuery.limit);
|
|
1552
|
+
const hasMore = filtered.length > events.length;
|
|
1553
|
+
const newestEvent = events[events.length - 1];
|
|
1554
|
+
const lastSeenMsgId = newestEvent?.message_id ?? newestEvent?.id ?? null;
|
|
1555
|
+
const lastSeenSeq = newestEvent?.seq ?? parsedQuery.sinceSeq;
|
|
1556
|
+
if (events.length > 0) {
|
|
1557
|
+
coordinator.consumeVisibleMessages({ messages: events, source: "agent_api_events" });
|
|
1558
|
+
}
|
|
1559
|
+
coordinator.recordDrainOutcome?.({
|
|
1560
|
+
source: "daemon_pending",
|
|
1561
|
+
sinceCursorKind: parsedQuery.sinceCursorKind,
|
|
1562
|
+
notifiedCount: pending.length,
|
|
1563
|
+
drainedCount: events.length,
|
|
1564
|
+
hasMore
|
|
1565
|
+
});
|
|
1566
|
+
return {
|
|
1567
|
+
status: 200,
|
|
1568
|
+
body: {
|
|
1569
|
+
events,
|
|
1570
|
+
last_seen_msgId: lastSeenMsgId,
|
|
1571
|
+
last_seen_seq: lastSeenSeq,
|
|
1572
|
+
reply_target: null,
|
|
1573
|
+
pending_notice_ids: [],
|
|
1574
|
+
wake_reason: null,
|
|
1575
|
+
has_more: hasMore
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
function heldAvailableActions(action) {
|
|
1580
|
+
return action === "send" ? ["check_messages", "send_draft", "send_anyway"] : ["check_messages", "retry_action"];
|
|
1581
|
+
}
|
|
1582
|
+
function localHeldResponse(input) {
|
|
1583
|
+
if (input.messages.length === 0) return void 0;
|
|
1584
|
+
const normalized = sortBySeq(normalizeVisibleMessages(input.messages, input.target));
|
|
1585
|
+
const heldMessages = latestVisibleMessages(normalized, LOCAL_HELD_CONTEXT_LIMIT);
|
|
1586
|
+
const omittedMessageCount = Math.max(0, normalized.length - heldMessages.length);
|
|
1587
|
+
const seenUpToSeq = maxMessageSeq(normalized);
|
|
1588
|
+
if (seenUpToSeq === void 0) return void 0;
|
|
1589
|
+
input.coordinator.consumeVisibleMessages({
|
|
1590
|
+
target: input.target,
|
|
1591
|
+
messages: heldMessages,
|
|
1592
|
+
boundarySeq: seenUpToSeq,
|
|
1593
|
+
source: input.source
|
|
1594
|
+
});
|
|
1595
|
+
const response = {
|
|
1596
|
+
state: "held",
|
|
1597
|
+
outcome: "held",
|
|
1598
|
+
subtype: "freshness",
|
|
1599
|
+
reason: "newer_messages_available",
|
|
1600
|
+
available_actions: heldAvailableActions(input.action),
|
|
1601
|
+
heldMessages,
|
|
1602
|
+
newMessageCount: normalized.length,
|
|
1603
|
+
shownMessageCount: heldMessages.length,
|
|
1604
|
+
omittedMessageCount
|
|
1605
|
+
};
|
|
1606
|
+
response.seenUpToSeq = seenUpToSeq;
|
|
1607
|
+
return response;
|
|
1608
|
+
}
|
|
1609
|
+
function recordFreshnessDecision(coordinator, decision) {
|
|
1610
|
+
coordinator?.recordFreshnessDecision?.(decision);
|
|
1611
|
+
}
|
|
1612
|
+
function agentApiSideEffectAction(pathname) {
|
|
1613
|
+
if (pathname === "/internal/agent-api/send") return "send";
|
|
1614
|
+
if (pathname === "/internal/agent-api/tasks/claim") return "task_claim";
|
|
1615
|
+
if (pathname === "/internal/agent-api/tasks/update-status") return "task_update";
|
|
1616
|
+
return void 0;
|
|
1617
|
+
}
|
|
1618
|
+
function sideEffectTarget(action, body) {
|
|
1619
|
+
const field = action === "send" ? body.target : body.channel;
|
|
1620
|
+
return typeof field === "string" && field.length > 0 ? field : void 0;
|
|
1621
|
+
}
|
|
1524
1622
|
function parseTargetFields(target) {
|
|
1525
1623
|
if (target.startsWith("dm:@")) {
|
|
1526
1624
|
const rest = target.slice("dm:@".length);
|
|
@@ -1577,42 +1675,98 @@ async function loadRecentTargetMessages(registration, headers, target) {
|
|
|
1577
1675
|
const parsed = await res.json().catch(() => null);
|
|
1578
1676
|
return Array.isArray(parsed?.messages) ? normalizeVisibleMessages(parsed.messages, target) : [];
|
|
1579
1677
|
}
|
|
1580
|
-
async function
|
|
1678
|
+
async function prepareAgentApiSideEffectForward(registration, headers, rawBody, action) {
|
|
1581
1679
|
let body;
|
|
1582
1680
|
try {
|
|
1583
1681
|
body = rawBody ? JSON.parse(rawBody) : {};
|
|
1584
1682
|
} catch {
|
|
1585
1683
|
return { bodyText: rawBody };
|
|
1586
1684
|
}
|
|
1587
|
-
const target =
|
|
1685
|
+
const target = sideEffectTarget(action, body);
|
|
1588
1686
|
const coordinator = registration.inboxCoordinator;
|
|
1589
|
-
if (!target || !coordinator
|
|
1687
|
+
if (!target || !coordinator) {
|
|
1688
|
+
return { bodyText: JSON.stringify(body), target };
|
|
1689
|
+
}
|
|
1690
|
+
if (action === "send" && body.continueAnyway === true) {
|
|
1691
|
+
recordFreshnessDecision(coordinator, {
|
|
1692
|
+
action,
|
|
1693
|
+
decision: "bypass",
|
|
1694
|
+
target,
|
|
1695
|
+
inboxTrustState: "trusted",
|
|
1696
|
+
reason: "continue_anyway"
|
|
1697
|
+
});
|
|
1590
1698
|
return { bodyText: JSON.stringify(body), target };
|
|
1591
1699
|
}
|
|
1592
1700
|
const pending = coordinator.getPendingMessages(target);
|
|
1593
1701
|
if (pending.length > 0) {
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
|
|
1702
|
+
const localResponse = localHeldResponse({
|
|
1703
|
+
action,
|
|
1704
|
+
target,
|
|
1705
|
+
messages: pending,
|
|
1706
|
+
coordinator,
|
|
1707
|
+
source: "side_effect_preflight_context"
|
|
1708
|
+
});
|
|
1709
|
+
if (localResponse) {
|
|
1710
|
+
recordFreshnessDecision(coordinator, {
|
|
1711
|
+
action,
|
|
1712
|
+
decision: "local_hold",
|
|
1713
|
+
target,
|
|
1714
|
+
inboxTrustState: "trusted",
|
|
1715
|
+
reason: "exact_target_pending",
|
|
1716
|
+
pendingCount: pending.length,
|
|
1717
|
+
pendingMaxSeq: maxMessageSeq(pending),
|
|
1718
|
+
modelSeenSeq: coordinator.getBoundary(target),
|
|
1719
|
+
heldMessageCount: typeof localResponse.shownMessageCount === "number" ? localResponse.shownMessageCount : void 0,
|
|
1720
|
+
omittedMessageCount: typeof localResponse.omittedMessageCount === "number" ? localResponse.omittedMessageCount : void 0
|
|
1721
|
+
});
|
|
1597
1722
|
}
|
|
1598
|
-
return {
|
|
1723
|
+
return {
|
|
1724
|
+
bodyText: JSON.stringify(body),
|
|
1725
|
+
target,
|
|
1726
|
+
localResponse
|
|
1727
|
+
};
|
|
1599
1728
|
}
|
|
1600
1729
|
const existingBoundary = typeof body.seenUpToSeq === "number" && Number.isFinite(body.seenUpToSeq) ? Math.max(0, Math.floor(body.seenUpToSeq)) : void 0;
|
|
1601
1730
|
const loadedBoundary = coordinator.getBoundary(target);
|
|
1602
1731
|
const boundary = Math.max(existingBoundary ?? 0, loadedBoundary ?? 0);
|
|
1603
1732
|
if (boundary > 0) {
|
|
1604
|
-
body.seenUpToSeq = boundary;
|
|
1733
|
+
if (action === "send") body.seenUpToSeq = boundary;
|
|
1734
|
+
recordFreshnessDecision(coordinator, {
|
|
1735
|
+
action,
|
|
1736
|
+
decision: "forward",
|
|
1737
|
+
target,
|
|
1738
|
+
inboxTrustState: "trusted",
|
|
1739
|
+
reason: "model_seen_boundary",
|
|
1740
|
+
pendingCount: 0,
|
|
1741
|
+
modelSeenSeq: boundary
|
|
1742
|
+
});
|
|
1605
1743
|
return { bodyText: JSON.stringify(body), target };
|
|
1606
1744
|
}
|
|
1607
1745
|
const recent = await loadRecentTargetMessages(registration, headers, target);
|
|
1608
1746
|
if (recent.length > 0) {
|
|
1609
1747
|
const seenUpToSeq = maxMessageSeq(recent);
|
|
1610
|
-
coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "
|
|
1748
|
+
coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "side_effect_preflight_context" });
|
|
1749
|
+
recordFreshnessDecision(coordinator, {
|
|
1750
|
+
action,
|
|
1751
|
+
decision: "syncing_hold",
|
|
1752
|
+
target,
|
|
1753
|
+
inboxTrustState: "untrusted",
|
|
1754
|
+
reason: "target_first_touch_recent_context",
|
|
1755
|
+
pendingCount: 0,
|
|
1756
|
+
pendingMaxSeq: seenUpToSeq,
|
|
1757
|
+
modelSeenSeq: 0,
|
|
1758
|
+
heldMessageCount: recent.length,
|
|
1759
|
+
omittedMessageCount: 0
|
|
1760
|
+
});
|
|
1611
1761
|
return {
|
|
1612
1762
|
bodyText: JSON.stringify(body),
|
|
1613
1763
|
target,
|
|
1614
1764
|
localResponse: {
|
|
1615
1765
|
state: "held",
|
|
1766
|
+
outcome: "held",
|
|
1767
|
+
subtype: "freshness",
|
|
1768
|
+
reason: "newer_messages_available",
|
|
1769
|
+
available_actions: heldAvailableActions(action),
|
|
1616
1770
|
seenUpToSeq,
|
|
1617
1771
|
heldMessages: recent,
|
|
1618
1772
|
newMessageCount: recent.length,
|
|
@@ -1621,6 +1775,15 @@ async function prepareAgentApiSendForward(registration, headers, rawBody) {
|
|
|
1621
1775
|
}
|
|
1622
1776
|
};
|
|
1623
1777
|
}
|
|
1778
|
+
recordFreshnessDecision(coordinator, {
|
|
1779
|
+
action,
|
|
1780
|
+
decision: "forward",
|
|
1781
|
+
target,
|
|
1782
|
+
inboxTrustState: "trusted",
|
|
1783
|
+
reason: "no_exact_target_pending_or_recent_context",
|
|
1784
|
+
pendingCount: 0,
|
|
1785
|
+
modelSeenSeq: 0
|
|
1786
|
+
});
|
|
1624
1787
|
return { bodyText: JSON.stringify(body), target };
|
|
1625
1788
|
}
|
|
1626
1789
|
function shouldBufferJsonResponse(upstream, pathname, registration) {
|
|
@@ -1648,7 +1811,15 @@ function consumeVisibleResponse(registration, targetUrl, sendTarget, responseTex
|
|
|
1648
1811
|
return;
|
|
1649
1812
|
}
|
|
1650
1813
|
if (targetUrl.pathname === "/internal/agent-api/events" && Array.isArray(parsed.events)) {
|
|
1651
|
-
|
|
1814
|
+
const messages = normalizeVisibleMessages(parsed.events);
|
|
1815
|
+
coordinator.consumeVisibleMessages({ messages, source: "agent_api_events" });
|
|
1816
|
+
coordinator.recordDrainOutcome?.({
|
|
1817
|
+
source: "server_events",
|
|
1818
|
+
sinceCursorKind: parseAgentApiEventsQuery(targetUrl).sinceCursorKind,
|
|
1819
|
+
notifiedCount: 0,
|
|
1820
|
+
drainedCount: messages.length,
|
|
1821
|
+
hasMore: Boolean(parsed.has_more)
|
|
1822
|
+
});
|
|
1652
1823
|
return;
|
|
1653
1824
|
}
|
|
1654
1825
|
if (targetUrl.pathname === "/internal/agent-api/history" && Array.isArray(parsed.messages)) {
|
|
@@ -1767,7 +1938,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
|
|
|
1767
1938
|
...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
|
|
1768
1939
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
1769
1940
|
};
|
|
1770
|
-
|
|
1941
|
+
delete spawnEnv.SLOCK_AGENT_TOKEN;
|
|
1771
1942
|
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
|
|
1772
1943
|
delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
|
|
1773
1944
|
delete spawnEnv.SLOCK_AGENT_PROXY_URL;
|
|
@@ -1814,7 +1985,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
|
|
|
1814
1985
|
}
|
|
1815
1986
|
function resolveCommandOnPath(command, deps = {}) {
|
|
1816
1987
|
const platform = deps.platform ?? process.platform;
|
|
1817
|
-
const env =
|
|
1988
|
+
const env = deps.env ?? process.env;
|
|
1818
1989
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
1819
1990
|
if (platform === "win32") {
|
|
1820
1991
|
return resolveCommandOnWindows(command, env, execFileSyncFn);
|
|
@@ -1839,7 +2010,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
1839
2010
|
return null;
|
|
1840
2011
|
}
|
|
1841
2012
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
1842
|
-
const env =
|
|
2013
|
+
const env = deps.env ?? process.env;
|
|
1843
2014
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
1844
2015
|
try {
|
|
1845
2016
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -2193,8 +2364,7 @@ var ClaudeDriver = class {
|
|
|
2193
2364
|
"- 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."
|
|
2194
2365
|
],
|
|
2195
2366
|
postStartupNotes: [
|
|
2196
|
-
"**Claude runtime note:**
|
|
2197
|
-
"For long tool runs, you can also use `slock message check` at natural breakpoints to pull pending messages explicitly."
|
|
2367
|
+
"**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."
|
|
2198
2368
|
],
|
|
2199
2369
|
includeStdinNotificationSection: true,
|
|
2200
2370
|
messageNotificationStyle: "direct"
|
|
@@ -2688,7 +2858,7 @@ var CodexDriver = class {
|
|
|
2688
2858
|
toolPrefix: "",
|
|
2689
2859
|
extraCriticalRules: [],
|
|
2690
2860
|
postStartupNotes: [
|
|
2691
|
-
"**IMPORTANT**: Your process stays alive across turns.
|
|
2861
|
+
"**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."
|
|
2692
2862
|
],
|
|
2693
2863
|
includeStdinNotificationSection: true,
|
|
2694
2864
|
messageNotificationStyle: "direct"
|
|
@@ -3136,7 +3306,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
3136
3306
|
}
|
|
3137
3307
|
function runCursorModelsCommand() {
|
|
3138
3308
|
return spawnSync("cursor-agent", ["models"], {
|
|
3139
|
-
env:
|
|
3309
|
+
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3140
3310
|
encoding: "utf8",
|
|
3141
3311
|
timeout: 5e3
|
|
3142
3312
|
});
|
|
@@ -3183,7 +3353,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
3183
3353
|
}
|
|
3184
3354
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
3185
3355
|
const existsSyncFn = deps.existsSyncFn ?? existsSync6;
|
|
3186
|
-
const env =
|
|
3356
|
+
const env = deps.env ?? process.env;
|
|
3187
3357
|
const winPath = path8.win32;
|
|
3188
3358
|
let geminiEntry = null;
|
|
3189
3359
|
try {
|
|
@@ -3357,16 +3527,13 @@ var GeminiDriver = class {
|
|
|
3357
3527
|
// src/drivers/kimi.ts
|
|
3358
3528
|
import { randomUUID } from "crypto";
|
|
3359
3529
|
import { spawn as spawn6 } from "child_process";
|
|
3360
|
-
import {
|
|
3530
|
+
import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
3361
3531
|
import os4 from "os";
|
|
3362
3532
|
import path9 from "path";
|
|
3363
3533
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
3364
3534
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
3365
3535
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
3366
3536
|
var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
|
|
3367
|
-
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
3368
|
-
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
3369
|
-
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
3370
3537
|
function parseToolArguments(raw) {
|
|
3371
3538
|
if (typeof raw !== "string") return raw;
|
|
3372
3539
|
try {
|
|
@@ -3375,73 +3542,6 @@ function parseToolArguments(raw) {
|
|
|
3375
3542
|
return raw;
|
|
3376
3543
|
}
|
|
3377
3544
|
}
|
|
3378
|
-
function readKimiConfigSource(home = os4.homedir(), env = process.env) {
|
|
3379
|
-
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3380
|
-
if (inlineConfig && inlineConfig.trim()) {
|
|
3381
|
-
return {
|
|
3382
|
-
raw: inlineConfig,
|
|
3383
|
-
explicitPath: null,
|
|
3384
|
-
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
3385
|
-
};
|
|
3386
|
-
}
|
|
3387
|
-
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3388
|
-
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
|
|
3389
|
-
try {
|
|
3390
|
-
return {
|
|
3391
|
-
raw: readFileSync3(configPath, "utf8"),
|
|
3392
|
-
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3393
|
-
sourcePath: configPath
|
|
3394
|
-
};
|
|
3395
|
-
} catch {
|
|
3396
|
-
return {
|
|
3397
|
-
raw: null,
|
|
3398
|
-
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
3399
|
-
sourcePath: configPath
|
|
3400
|
-
};
|
|
3401
|
-
}
|
|
3402
|
-
}
|
|
3403
|
-
function buildKimiSpawnEnv(env = process.env) {
|
|
3404
|
-
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
3405
|
-
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
3406
|
-
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
3407
|
-
return scrubDaemonChildEnv(spawnEnv);
|
|
3408
|
-
}
|
|
3409
|
-
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
3410
|
-
return {
|
|
3411
|
-
...process.env,
|
|
3412
|
-
...ctx.config.envVars || {},
|
|
3413
|
-
...overrideEnv || {}
|
|
3414
|
-
};
|
|
3415
|
-
}
|
|
3416
|
-
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
3417
|
-
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
3418
|
-
const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
|
|
3419
|
-
const args = [];
|
|
3420
|
-
let configFilePath = null;
|
|
3421
|
-
let configContent = null;
|
|
3422
|
-
if (source.explicitPath) {
|
|
3423
|
-
configFilePath = source.explicitPath;
|
|
3424
|
-
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
3425
|
-
configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
3426
|
-
configContent = source.raw;
|
|
3427
|
-
if (opts.writeGeneratedConfig !== false) {
|
|
3428
|
-
writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
3429
|
-
chmodSync(configFilePath, 384);
|
|
3430
|
-
}
|
|
3431
|
-
}
|
|
3432
|
-
if (configFilePath) {
|
|
3433
|
-
args.push("--config-file", configFilePath);
|
|
3434
|
-
}
|
|
3435
|
-
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3436
|
-
args.push("--model", ctx.config.model);
|
|
3437
|
-
}
|
|
3438
|
-
return {
|
|
3439
|
-
args,
|
|
3440
|
-
env: buildKimiSpawnEnv(env),
|
|
3441
|
-
configFilePath,
|
|
3442
|
-
configContent
|
|
3443
|
-
};
|
|
3444
|
-
}
|
|
3445
3545
|
function resolveKimiSpawn(commandArgs, deps = {}) {
|
|
3446
3546
|
return {
|
|
3447
3547
|
command: resolveCommandOnPath("kimi", deps) ?? "kimi",
|
|
@@ -3465,25 +3565,7 @@ var KimiDriver = class {
|
|
|
3465
3565
|
};
|
|
3466
3566
|
model = {
|
|
3467
3567
|
detectedModelsVerifiedAs: "launchable",
|
|
3468
|
-
toLaunchSpec: (modelId
|
|
3469
|
-
if (!ctx) return { args: ["--model", modelId] };
|
|
3470
|
-
const launchCtx = {
|
|
3471
|
-
...ctx,
|
|
3472
|
-
config: {
|
|
3473
|
-
...ctx.config,
|
|
3474
|
-
model: modelId
|
|
3475
|
-
}
|
|
3476
|
-
};
|
|
3477
|
-
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
3478
|
-
home: opts?.home,
|
|
3479
|
-
writeGeneratedConfig: false
|
|
3480
|
-
});
|
|
3481
|
-
return {
|
|
3482
|
-
args: launch.args,
|
|
3483
|
-
env: launch.env,
|
|
3484
|
-
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
3485
|
-
};
|
|
3486
|
-
}
|
|
3568
|
+
toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
|
|
3487
3569
|
};
|
|
3488
3570
|
supportsStdinNotification = true;
|
|
3489
3571
|
mcpToolPrefix = "";
|
|
@@ -3537,7 +3619,6 @@ var KimiDriver = class {
|
|
|
3537
3619
|
}
|
|
3538
3620
|
}
|
|
3539
3621
|
}), "utf8");
|
|
3540
|
-
const launch = buildKimiLaunchOptions(ctx);
|
|
3541
3622
|
const args = [
|
|
3542
3623
|
"--wire",
|
|
3543
3624
|
"--yolo",
|
|
@@ -3546,15 +3627,14 @@ var KimiDriver = class {
|
|
|
3546
3627
|
"--mcp-config-file",
|
|
3547
3628
|
mcpConfigPath,
|
|
3548
3629
|
"--session",
|
|
3549
|
-
this.sessionId
|
|
3550
|
-
...launch.args
|
|
3630
|
+
this.sessionId
|
|
3551
3631
|
];
|
|
3552
3632
|
if (ctx.config.model && ctx.config.model !== "default") {
|
|
3553
3633
|
args.push("--model", ctx.config.model);
|
|
3554
3634
|
}
|
|
3555
3635
|
const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
|
|
3556
|
-
const
|
|
3557
|
-
const proc = spawn6(
|
|
3636
|
+
const launch = resolveKimiSpawn(args);
|
|
3637
|
+
const proc = spawn6(launch.command, launch.args, {
|
|
3558
3638
|
cwd: ctx.workingDirectory,
|
|
3559
3639
|
stdio: ["pipe", "pipe", "pipe"],
|
|
3560
3640
|
env: spawnEnv,
|
|
@@ -3562,7 +3642,7 @@ var KimiDriver = class {
|
|
|
3562
3642
|
// and has an 8191-character command-line limit. Kimi's official
|
|
3563
3643
|
// installer/uv entrypoint is an executable, so launch it directly and
|
|
3564
3644
|
// keep prompts on stdin / files instead of routing through cmd.exe.
|
|
3565
|
-
shell:
|
|
3645
|
+
shell: launch.shell
|
|
3566
3646
|
});
|
|
3567
3647
|
proc.stdin?.write(JSON.stringify({
|
|
3568
3648
|
jsonrpc: "2.0",
|
|
@@ -3678,9 +3758,14 @@ var KimiDriver = class {
|
|
|
3678
3758
|
return detectKimiModels();
|
|
3679
3759
|
}
|
|
3680
3760
|
};
|
|
3681
|
-
function detectKimiModels(home = os4.homedir()
|
|
3682
|
-
const
|
|
3683
|
-
|
|
3761
|
+
function detectKimiModels(home = os4.homedir()) {
|
|
3762
|
+
const configPath = path9.join(home, ".kimi", "config.toml");
|
|
3763
|
+
let raw;
|
|
3764
|
+
try {
|
|
3765
|
+
raw = readFileSync3(configPath, "utf8");
|
|
3766
|
+
} catch {
|
|
3767
|
+
return null;
|
|
3768
|
+
}
|
|
3684
3769
|
const models = [];
|
|
3685
3770
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
3686
3771
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -3941,7 +4026,7 @@ function detectOpenCodeModels(home = os5.homedir(), runCommand = runOpenCodeMode
|
|
|
3941
4026
|
}
|
|
3942
4027
|
function runOpenCodeModelsCommand(home) {
|
|
3943
4028
|
const result = spawnSync2("opencode", ["models"], {
|
|
3944
|
-
env:
|
|
4029
|
+
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
3945
4030
|
encoding: "utf8",
|
|
3946
4031
|
timeout: 5e3
|
|
3947
4032
|
});
|
|
@@ -4099,297 +4184,6 @@ var OpenCodeDriver = class {
|
|
|
4099
4184
|
}
|
|
4100
4185
|
};
|
|
4101
4186
|
|
|
4102
|
-
// src/drivers/pi.ts
|
|
4103
|
-
import { spawn as spawn8 } from "child_process";
|
|
4104
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
4105
|
-
import path11 from "path";
|
|
4106
|
-
import { fileURLToPath } from "url";
|
|
4107
|
-
import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
|
|
4108
|
-
var CHAT_MCP_TOOL_PREFIX2 = "chat_";
|
|
4109
|
-
var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
|
|
4110
|
-
var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
|
|
4111
|
-
var MIN_SUPPORTED_PI_VERSION = "0.74.0";
|
|
4112
|
-
function parseSemver2(version) {
|
|
4113
|
-
const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
4114
|
-
if (!match) return null;
|
|
4115
|
-
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
4116
|
-
}
|
|
4117
|
-
function isSupportedPiVersion(version) {
|
|
4118
|
-
if (!version) return true;
|
|
4119
|
-
const actual = parseSemver2(version);
|
|
4120
|
-
const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
|
|
4121
|
-
if (!actual || !minimum) return true;
|
|
4122
|
-
for (let i = 0; i < 3; i += 1) {
|
|
4123
|
-
if (actual[i] > minimum[i]) return true;
|
|
4124
|
-
if (actual[i] < minimum[i]) return false;
|
|
4125
|
-
}
|
|
4126
|
-
return true;
|
|
4127
|
-
}
|
|
4128
|
-
function unsupportedPiVersionMessage(version) {
|
|
4129
|
-
if (!version || isSupportedPiVersion(version)) return null;
|
|
4130
|
-
return `Pi SDK ${version} is unsupported; requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION}. Upgrade the daemon Pi dependency before starting this runtime.`;
|
|
4131
|
-
}
|
|
4132
|
-
function probePi(version = PI_SDK_VERSION) {
|
|
4133
|
-
const unsupportedMessage = unsupportedPiVersionMessage(version);
|
|
4134
|
-
if (unsupportedMessage) {
|
|
4135
|
-
return {
|
|
4136
|
-
available: false,
|
|
4137
|
-
version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
|
|
4138
|
-
};
|
|
4139
|
-
}
|
|
4140
|
-
return { available: true, version };
|
|
4141
|
-
}
|
|
4142
|
-
function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
|
|
4143
|
-
const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
|
|
4144
|
-
const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
|
|
4145
|
-
if (existsSync8(sourceSibling)) return sourceSibling;
|
|
4146
|
-
const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
|
|
4147
|
-
if (existsSync8(bundledEntry)) return bundledEntry;
|
|
4148
|
-
return path11.join(moduleDir, "piSdkRunner.js");
|
|
4149
|
-
}
|
|
4150
|
-
function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
|
|
4151
|
-
if (runnerPath.endsWith(".ts")) {
|
|
4152
|
-
return [...process.execArgv, runnerPath];
|
|
4153
|
-
}
|
|
4154
|
-
return [runnerPath];
|
|
4155
|
-
}
|
|
4156
|
-
function buildPiLaunchOptions(ctx, opts = {}) {
|
|
4157
|
-
const command = opts.command ?? process.execPath;
|
|
4158
|
-
const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
|
|
4159
|
-
const sessionDir = path11.join(piDir, "sessions");
|
|
4160
|
-
mkdirSync4(sessionDir, { recursive: true });
|
|
4161
|
-
const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
4162
|
-
const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
|
|
4163
|
-
const agentDir = opts.agentDir ?? getAgentDir();
|
|
4164
|
-
const runnerConfigPath = path11.join(
|
|
4165
|
-
piDir,
|
|
4166
|
-
`sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
|
|
4167
|
-
);
|
|
4168
|
-
const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
|
|
4169
|
-
const runnerConfig = {
|
|
4170
|
-
cwd: ctx.workingDirectory,
|
|
4171
|
-
agentDir,
|
|
4172
|
-
sessionDir,
|
|
4173
|
-
sessionId: ctx.config.sessionId || null,
|
|
4174
|
-
standingPrompt: ctx.standingPrompt,
|
|
4175
|
-
prompt: turnPrompt,
|
|
4176
|
-
model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
|
|
4177
|
-
};
|
|
4178
|
-
writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
|
|
4179
|
-
`, { encoding: "utf8", mode: 384 });
|
|
4180
|
-
const args = [
|
|
4181
|
-
...buildPiSdkNodeArgs(runnerPath),
|
|
4182
|
-
"--config",
|
|
4183
|
-
runnerConfigPath
|
|
4184
|
-
];
|
|
4185
|
-
return {
|
|
4186
|
-
command,
|
|
4187
|
-
args,
|
|
4188
|
-
env: slock.spawnEnv,
|
|
4189
|
-
sessionDir,
|
|
4190
|
-
agentDir,
|
|
4191
|
-
runnerConfigPath,
|
|
4192
|
-
sdkVersion: PI_SDK_VERSION
|
|
4193
|
-
};
|
|
4194
|
-
}
|
|
4195
|
-
function isSystemFirstMessageTask2(message) {
|
|
4196
|
-
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
|
|
4197
|
-
}
|
|
4198
|
-
function buildPiSystemPrompt(config) {
|
|
4199
|
-
return buildCliTransportSystemPrompt(config, {
|
|
4200
|
-
toolPrefix: CHAT_MCP_TOOL_PREFIX2,
|
|
4201
|
-
extraCriticalRules: [
|
|
4202
|
-
"- Runtime Profile migration controls are not available in the Pi runtime yet. If asked to acknowledge a runtime migration, explain the blocker instead of inventing a command."
|
|
4203
|
-
],
|
|
4204
|
-
postStartupNotes: [
|
|
4205
|
-
"**Pi runtime note:** Slock launches you as a per-turn process. Complete the current wake using `slock` CLI commands, then stop; the daemon will restart you when new messages arrive."
|
|
4206
|
-
],
|
|
4207
|
-
includeStdinNotificationSection: false,
|
|
4208
|
-
messageNotificationStyle: "poll"
|
|
4209
|
-
});
|
|
4210
|
-
}
|
|
4211
|
-
function contentText(content) {
|
|
4212
|
-
if (!content) return "";
|
|
4213
|
-
const chunks = [];
|
|
4214
|
-
for (const item of content) {
|
|
4215
|
-
if (item.type === "text" && typeof item.text === "string") {
|
|
4216
|
-
chunks.push(item.text);
|
|
4217
|
-
}
|
|
4218
|
-
}
|
|
4219
|
-
return chunks.join("");
|
|
4220
|
-
}
|
|
4221
|
-
function apiKeyErrorMessage(line) {
|
|
4222
|
-
const trimmed = line.trim();
|
|
4223
|
-
if (!trimmed) return null;
|
|
4224
|
-
if (/no api key found/i.test(trimmed)) return trimmed;
|
|
4225
|
-
if (/api key.+required/i.test(trimmed)) return trimmed;
|
|
4226
|
-
if (/no models available/i.test(trimmed)) return trimmed;
|
|
4227
|
-
return null;
|
|
4228
|
-
}
|
|
4229
|
-
var PiDriver = class {
|
|
4230
|
-
id = "pi";
|
|
4231
|
-
lifecycle = {
|
|
4232
|
-
kind: "per_turn",
|
|
4233
|
-
start: "defer_until_concrete_message",
|
|
4234
|
-
exit: "terminate_on_turn_end",
|
|
4235
|
-
inFlightWake: "coalesce_into_pending"
|
|
4236
|
-
};
|
|
4237
|
-
communication = {
|
|
4238
|
-
chat: "slock_cli",
|
|
4239
|
-
runtimeControl: "none"
|
|
4240
|
-
};
|
|
4241
|
-
session = {
|
|
4242
|
-
recovery: "resume_or_fresh"
|
|
4243
|
-
};
|
|
4244
|
-
model = {
|
|
4245
|
-
detectedModelsVerifiedAs: "launchable",
|
|
4246
|
-
toLaunchSpec: (modelId, ctx) => {
|
|
4247
|
-
if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
|
|
4248
|
-
const launchCtx = {
|
|
4249
|
-
...ctx,
|
|
4250
|
-
config: {
|
|
4251
|
-
...ctx.config,
|
|
4252
|
-
model: modelId
|
|
4253
|
-
}
|
|
4254
|
-
};
|
|
4255
|
-
const launch = buildPiLaunchOptions(launchCtx);
|
|
4256
|
-
return {
|
|
4257
|
-
args: launch.args,
|
|
4258
|
-
env: launch.env,
|
|
4259
|
-
configFiles: [launch.runnerConfigPath],
|
|
4260
|
-
params: {
|
|
4261
|
-
agentDir: launch.agentDir,
|
|
4262
|
-
sessionDir: launch.sessionDir,
|
|
4263
|
-
sdkVersion: launch.sdkVersion,
|
|
4264
|
-
resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
|
|
4265
|
-
}
|
|
4266
|
-
};
|
|
4267
|
-
}
|
|
4268
|
-
};
|
|
4269
|
-
supportsStdinNotification = false;
|
|
4270
|
-
mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
|
|
4271
|
-
busyDeliveryMode = "none";
|
|
4272
|
-
terminateProcessOnTurnEnd = true;
|
|
4273
|
-
deferSpawnUntilMessage = true;
|
|
4274
|
-
usesSlockCliForCommunication = true;
|
|
4275
|
-
sessionId = null;
|
|
4276
|
-
sessionAnnounced = false;
|
|
4277
|
-
apiKeyErrorAnnounced = false;
|
|
4278
|
-
turnEnded = false;
|
|
4279
|
-
assistantTextByMessageId = /* @__PURE__ */ new Map();
|
|
4280
|
-
shouldDeferWakeMessage(message) {
|
|
4281
|
-
return isSystemFirstMessageTask2(message);
|
|
4282
|
-
}
|
|
4283
|
-
probe() {
|
|
4284
|
-
return probePi();
|
|
4285
|
-
}
|
|
4286
|
-
async detectModels() {
|
|
4287
|
-
return null;
|
|
4288
|
-
}
|
|
4289
|
-
spawn(ctx) {
|
|
4290
|
-
this.sessionId = ctx.config.sessionId || null;
|
|
4291
|
-
this.sessionAnnounced = false;
|
|
4292
|
-
this.apiKeyErrorAnnounced = false;
|
|
4293
|
-
this.turnEnded = false;
|
|
4294
|
-
this.assistantTextByMessageId.clear();
|
|
4295
|
-
const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
|
|
4296
|
-
if (unsupportedMessage) throw new Error(unsupportedMessage);
|
|
4297
|
-
const launch = buildPiLaunchOptions(ctx);
|
|
4298
|
-
const proc = spawn8(launch.command, launch.args, {
|
|
4299
|
-
cwd: ctx.workingDirectory,
|
|
4300
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
4301
|
-
env: launch.env,
|
|
4302
|
-
shell: false
|
|
4303
|
-
});
|
|
4304
|
-
proc.stdin?.end();
|
|
4305
|
-
return { process: proc };
|
|
4306
|
-
}
|
|
4307
|
-
parseLine(line) {
|
|
4308
|
-
let event;
|
|
4309
|
-
try {
|
|
4310
|
-
event = JSON.parse(line);
|
|
4311
|
-
} catch {
|
|
4312
|
-
if (this.apiKeyErrorAnnounced) return [];
|
|
4313
|
-
const message = apiKeyErrorMessage(line);
|
|
4314
|
-
if (!message) return [];
|
|
4315
|
-
this.apiKeyErrorAnnounced = true;
|
|
4316
|
-
this.turnEnded = true;
|
|
4317
|
-
return [
|
|
4318
|
-
{ kind: "error", message },
|
|
4319
|
-
{ kind: "turn_end", sessionId: this.sessionId || void 0 }
|
|
4320
|
-
];
|
|
4321
|
-
}
|
|
4322
|
-
const events = [];
|
|
4323
|
-
if (event.type === "session" && event.id) {
|
|
4324
|
-
this.sessionId = event.id;
|
|
4325
|
-
}
|
|
4326
|
-
if (!this.sessionAnnounced && this.sessionId) {
|
|
4327
|
-
events.push({ kind: "session_init", sessionId: this.sessionId });
|
|
4328
|
-
this.sessionAnnounced = true;
|
|
4329
|
-
}
|
|
4330
|
-
switch (event.type) {
|
|
4331
|
-
case "agent_start":
|
|
4332
|
-
case "turn_start":
|
|
4333
|
-
events.push({ kind: "thinking", text: "" });
|
|
4334
|
-
break;
|
|
4335
|
-
case "message_update":
|
|
4336
|
-
case "message_end":
|
|
4337
|
-
if (event.message?.role === "assistant") {
|
|
4338
|
-
const key = event.message.id || "current";
|
|
4339
|
-
const currentText = contentText(event.message.content);
|
|
4340
|
-
const previousText = this.assistantTextByMessageId.get(key) ?? "";
|
|
4341
|
-
if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
|
|
4342
|
-
events.push({ kind: "text", text: currentText.slice(previousText.length) });
|
|
4343
|
-
} else if (currentText && currentText !== previousText) {
|
|
4344
|
-
events.push({ kind: "text", text: currentText });
|
|
4345
|
-
}
|
|
4346
|
-
this.assistantTextByMessageId.set(key, currentText);
|
|
4347
|
-
if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
|
|
4348
|
-
events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
|
|
4349
|
-
}
|
|
4350
|
-
}
|
|
4351
|
-
break;
|
|
4352
|
-
case "tool_execution_start":
|
|
4353
|
-
events.push({
|
|
4354
|
-
kind: "tool_call",
|
|
4355
|
-
name: event.toolName || "unknown_tool",
|
|
4356
|
-
input: event.args
|
|
4357
|
-
});
|
|
4358
|
-
break;
|
|
4359
|
-
case "tool_execution_end":
|
|
4360
|
-
events.push({
|
|
4361
|
-
kind: "tool_output",
|
|
4362
|
-
name: event.toolName || "unknown_tool"
|
|
4363
|
-
});
|
|
4364
|
-
if (event.isError) {
|
|
4365
|
-
events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
|
|
4366
|
-
}
|
|
4367
|
-
break;
|
|
4368
|
-
case "compaction_start":
|
|
4369
|
-
events.push({ kind: "compaction_started" });
|
|
4370
|
-
break;
|
|
4371
|
-
case "compaction_end":
|
|
4372
|
-
events.push({ kind: "compaction_finished" });
|
|
4373
|
-
if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
|
|
4374
|
-
break;
|
|
4375
|
-
case "turn_end":
|
|
4376
|
-
case "agent_end":
|
|
4377
|
-
if (!this.turnEnded) {
|
|
4378
|
-
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
4379
|
-
this.turnEnded = true;
|
|
4380
|
-
}
|
|
4381
|
-
break;
|
|
4382
|
-
}
|
|
4383
|
-
return events;
|
|
4384
|
-
}
|
|
4385
|
-
encodeStdinMessage(_text, _sessionId, _opts) {
|
|
4386
|
-
return null;
|
|
4387
|
-
}
|
|
4388
|
-
buildSystemPrompt(config, _agentId) {
|
|
4389
|
-
return buildPiSystemPrompt(config);
|
|
4390
|
-
}
|
|
4391
|
-
};
|
|
4392
|
-
|
|
4393
4187
|
// src/drivers/index.ts
|
|
4394
4188
|
var driverFactories = {
|
|
4395
4189
|
claude: () => new ClaudeDriver(),
|
|
@@ -4398,8 +4192,7 @@ var driverFactories = {
|
|
|
4398
4192
|
cursor: () => new CursorDriver(),
|
|
4399
4193
|
gemini: () => new GeminiDriver(),
|
|
4400
4194
|
kimi: () => new KimiDriver(),
|
|
4401
|
-
opencode: () => new OpenCodeDriver()
|
|
4402
|
-
pi: () => new PiDriver()
|
|
4195
|
+
opencode: () => new OpenCodeDriver()
|
|
4403
4196
|
};
|
|
4404
4197
|
function getDriver(runtimeId) {
|
|
4405
4198
|
const createDriver = driverFactories[runtimeId];
|
|
@@ -4412,7 +4205,7 @@ function getDriver(runtimeId) {
|
|
|
4412
4205
|
|
|
4413
4206
|
// src/workspaces.ts
|
|
4414
4207
|
import { readdir, rm, stat } from "fs/promises";
|
|
4415
|
-
import
|
|
4208
|
+
import path11 from "path";
|
|
4416
4209
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
4417
4210
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
4418
4211
|
}
|
|
@@ -4420,7 +4213,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
4420
4213
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
4421
4214
|
return null;
|
|
4422
4215
|
}
|
|
4423
|
-
return
|
|
4216
|
+
return path11.join(dataDir, directoryName);
|
|
4424
4217
|
}
|
|
4425
4218
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
4426
4219
|
return {
|
|
@@ -4469,7 +4262,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
4469
4262
|
return summary;
|
|
4470
4263
|
}
|
|
4471
4264
|
const childSummaries = await Promise.all(
|
|
4472
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
4265
|
+
entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
|
|
4473
4266
|
);
|
|
4474
4267
|
for (const childSummary of childSummaries) {
|
|
4475
4268
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -4488,7 +4281,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
4488
4281
|
if (!entry.isDirectory()) {
|
|
4489
4282
|
return null;
|
|
4490
4283
|
}
|
|
4491
|
-
const dirPath =
|
|
4284
|
+
const dirPath = path11.join(dataDir, entry.name);
|
|
4492
4285
|
try {
|
|
4493
4286
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
4494
4287
|
return {
|
|
@@ -4745,12 +4538,12 @@ function findSessionJsonl(root, predicate) {
|
|
|
4745
4538
|
for (const entry of entries) {
|
|
4746
4539
|
if (++visited > maxEntries) return null;
|
|
4747
4540
|
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
4748
|
-
return
|
|
4541
|
+
return path12.join(dir, entry.name);
|
|
4749
4542
|
}
|
|
4750
4543
|
for (const entry of entries) {
|
|
4751
4544
|
if (++visited > maxEntries) return null;
|
|
4752
4545
|
if (!entry.isDirectory()) continue;
|
|
4753
|
-
const found = visit(
|
|
4546
|
+
const found = visit(path12.join(dir, entry.name), depth - 1);
|
|
4754
4547
|
if (found) return found;
|
|
4755
4548
|
}
|
|
4756
4549
|
return null;
|
|
@@ -4763,10 +4556,10 @@ function safeSessionFilename(value) {
|
|
|
4763
4556
|
}
|
|
4764
4557
|
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
4765
4558
|
try {
|
|
4766
|
-
const dir =
|
|
4767
|
-
|
|
4768
|
-
const filePath =
|
|
4769
|
-
|
|
4559
|
+
const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
|
|
4560
|
+
mkdirSync4(dir, { recursive: true });
|
|
4561
|
+
const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
4562
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
4770
4563
|
type: "runtime_session_handoff",
|
|
4771
4564
|
runtime,
|
|
4772
4565
|
sessionId,
|
|
@@ -4785,7 +4578,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
4785
4578
|
}
|
|
4786
4579
|
}
|
|
4787
4580
|
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
|
|
4788
|
-
const directPath =
|
|
4581
|
+
const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
|
|
4789
4582
|
if (directPath) {
|
|
4790
4583
|
try {
|
|
4791
4584
|
if (statSync2(directPath).isFile()) {
|
|
@@ -4794,7 +4587,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
|
|
|
4794
4587
|
} catch {
|
|
4795
4588
|
}
|
|
4796
4589
|
}
|
|
4797
|
-
const resolvedPath = runtime === "claude" ? findSessionJsonl(
|
|
4590
|
+
const resolvedPath = runtime === "claude" ? findSessionJsonl(path12.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path12.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
|
|
4798
4591
|
if (!resolvedPath && fallbackDir) {
|
|
4799
4592
|
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
4800
4593
|
if (fallback) return fallback;
|
|
@@ -5645,10 +5438,10 @@ function getMessageDeliveryText(driver) {
|
|
|
5645
5438
|
function getBusyDeliveryNote(driver) {
|
|
5646
5439
|
if (!driver.supportsStdinNotification) return "";
|
|
5647
5440
|
if (driver.busyDeliveryMode === "direct") {
|
|
5648
|
-
return "\n\nNote: While you are busy,
|
|
5441
|
+
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.";
|
|
5649
5442
|
}
|
|
5650
5443
|
if (driver.busyDeliveryMode === "gated") {
|
|
5651
|
-
return "\n\nNote: While you are busy,
|
|
5444
|
+
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.";
|
|
5652
5445
|
}
|
|
5653
5446
|
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.";
|
|
5654
5447
|
}
|
|
@@ -5724,13 +5517,16 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
5724
5517
|
getVisibleBoundary(agentId, target) {
|
|
5725
5518
|
return this.agentVisibleBoundaries.get(agentId)?.get(target);
|
|
5726
5519
|
}
|
|
5727
|
-
|
|
5728
|
-
const collect = (messages) => (messages ?? []).filter((message) =>
|
|
5520
|
+
allPendingVisibleMessages(agentId) {
|
|
5521
|
+
const collect = (messages) => (messages ?? []).filter((message) => typeof message.seq === "number" && message.seq > 0);
|
|
5729
5522
|
return [
|
|
5730
5523
|
...collect(this.agents.get(agentId)?.inbox),
|
|
5731
5524
|
...collect(this.startingInboxes.get(agentId))
|
|
5732
5525
|
];
|
|
5733
5526
|
}
|
|
5527
|
+
pendingVisibleMessages(agentId, target) {
|
|
5528
|
+
return this.allPendingVisibleMessages(agentId).filter((message) => formatMessageTarget(message) === target);
|
|
5529
|
+
}
|
|
5734
5530
|
/**
|
|
5735
5531
|
* Single-inbox consume point for messages that have been rendered into the
|
|
5736
5532
|
* agent-visible input surface. Daemon pending inbox is only a runner cache:
|
|
@@ -5801,8 +5597,71 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
5801
5597
|
return {
|
|
5802
5598
|
getBoundary: (target) => this.getVisibleBoundary(agentId, target),
|
|
5803
5599
|
getPendingMessages: (target) => this.pendingVisibleMessages(agentId, target),
|
|
5804
|
-
|
|
5600
|
+
getAllPendingMessages: () => this.allPendingVisibleMessages(agentId),
|
|
5601
|
+
consumeVisibleMessages: (input) => this.consumeVisibleMessages(agentId, input),
|
|
5602
|
+
recordDrainOutcome: (input) => {
|
|
5603
|
+
this.recordDaemonTrace("daemon.agent.drain.outcome", {
|
|
5604
|
+
agentId,
|
|
5605
|
+
source: input.source,
|
|
5606
|
+
since_cursor_kind: input.sinceCursorKind ?? void 0,
|
|
5607
|
+
notified_count: input.notifiedCount,
|
|
5608
|
+
drained_count: input.drainedCount,
|
|
5609
|
+
has_more: input.hasMore
|
|
5610
|
+
});
|
|
5611
|
+
},
|
|
5612
|
+
recordProxyFailure: (input) => this.recordAgentProxyFailure(agentId, input),
|
|
5613
|
+
recordFreshnessDecision: (input) => {
|
|
5614
|
+
this.recordDaemonTrace("daemon.agent.inbox.freshness_decision", {
|
|
5615
|
+
agentId,
|
|
5616
|
+
action: input.action,
|
|
5617
|
+
decision: input.decision,
|
|
5618
|
+
target: input.target,
|
|
5619
|
+
inbox_trust_state: input.inboxTrustState,
|
|
5620
|
+
reason: input.reason,
|
|
5621
|
+
pending_count: input.pendingCount,
|
|
5622
|
+
pending_max_seq: input.pendingMaxSeq,
|
|
5623
|
+
model_seen_seq: input.modelSeenSeq,
|
|
5624
|
+
held_message_count: input.heldMessageCount,
|
|
5625
|
+
omitted_message_count: input.omittedMessageCount
|
|
5626
|
+
});
|
|
5627
|
+
this.recordFreshnessDecisionActivity(agentId, input);
|
|
5628
|
+
}
|
|
5629
|
+
};
|
|
5630
|
+
}
|
|
5631
|
+
recordAgentProxyFailure(agentId, input) {
|
|
5632
|
+
this.recordDaemonTrace("daemon.agent.proxy.failure", {
|
|
5633
|
+
agentId,
|
|
5634
|
+
method: input.method,
|
|
5635
|
+
path: input.pathname,
|
|
5636
|
+
query_keys: input.queryKeys,
|
|
5637
|
+
error_name: input.errorName,
|
|
5638
|
+
error_message: input.errorMessage
|
|
5639
|
+
}, "error");
|
|
5640
|
+
}
|
|
5641
|
+
recordFreshnessDecisionActivity(agentId, input) {
|
|
5642
|
+
if (input.decision !== "local_hold" && input.decision !== "syncing_hold") return;
|
|
5643
|
+
const ap = this.agents.get(agentId);
|
|
5644
|
+
const messageCount = input.pendingCount ?? input.heldMessageCount ?? 0;
|
|
5645
|
+
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";
|
|
5646
|
+
const entry = {
|
|
5647
|
+
kind: "slock_action",
|
|
5648
|
+
title,
|
|
5649
|
+
text: [
|
|
5650
|
+
input.target ? `target: ${input.target}` : null,
|
|
5651
|
+
`new messages: ${messageCount} newer message${messageCount === 1 ? "" : "s"}`,
|
|
5652
|
+
`decision: ${input.decision === "syncing_hold" ? "syncing hold" : "local hold"}; review the newer context before retrying`
|
|
5653
|
+
].filter((line) => Boolean(line)).join("\n")
|
|
5805
5654
|
};
|
|
5655
|
+
if (ap) ap.activityClientSeq += 1;
|
|
5656
|
+
this.sendToServer({
|
|
5657
|
+
type: "agent:activity",
|
|
5658
|
+
agentId,
|
|
5659
|
+
activity: ap?.lastActivity || "online",
|
|
5660
|
+
detail: ap?.lastActivityDetail || "",
|
|
5661
|
+
entries: [entry],
|
|
5662
|
+
launchId: ap?.launchId || void 0,
|
|
5663
|
+
clientSeq: ap?.activityClientSeq
|
|
5664
|
+
});
|
|
5806
5665
|
}
|
|
5807
5666
|
recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
|
|
5808
5667
|
const span = this.tracer.startSpan(name, {
|
|
@@ -6028,26 +5887,26 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
6028
5887
|
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
6029
5888
|
try {
|
|
6030
5889
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
6031
|
-
const agentDataDir =
|
|
5890
|
+
const agentDataDir = path12.join(this.dataDir, agentId);
|
|
6032
5891
|
await mkdir(agentDataDir, { recursive: true });
|
|
6033
5892
|
const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
6034
|
-
const memoryMdPath =
|
|
5893
|
+
const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
|
|
6035
5894
|
try {
|
|
6036
5895
|
await access(memoryMdPath);
|
|
6037
5896
|
} catch {
|
|
6038
5897
|
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
6039
5898
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
6040
5899
|
}
|
|
6041
|
-
const notesDir =
|
|
5900
|
+
const notesDir = path12.join(agentDataDir, "notes");
|
|
6042
5901
|
await mkdir(notesDir, { recursive: true });
|
|
6043
5902
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
6044
5903
|
const seedFiles = buildOnboardingSeedFiles();
|
|
6045
5904
|
for (const { relativePath, content } of seedFiles) {
|
|
6046
|
-
const fullPath =
|
|
5905
|
+
const fullPath = path12.join(agentDataDir, relativePath);
|
|
6047
5906
|
try {
|
|
6048
5907
|
await access(fullPath);
|
|
6049
5908
|
} catch {
|
|
6050
|
-
await mkdir(
|
|
5909
|
+
await mkdir(path12.dirname(fullPath), { recursive: true });
|
|
6051
5910
|
await writeFile(fullPath, content);
|
|
6052
5911
|
}
|
|
6053
5912
|
}
|
|
@@ -6857,6 +6716,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6857
6716
|
}
|
|
6858
6717
|
if (ap.driver.busyDeliveryMode === "gated") {
|
|
6859
6718
|
ap.pendingNotificationCount++;
|
|
6719
|
+
if (!ap.notificationTimer) {
|
|
6720
|
+
ap.notificationTimer = setTimeout(() => {
|
|
6721
|
+
this.sendStdinNotification(agentId);
|
|
6722
|
+
}, 3e3);
|
|
6723
|
+
}
|
|
6860
6724
|
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
6861
6725
|
reason: "busy_message",
|
|
6862
6726
|
pendingMessages: ap.inbox.length
|
|
@@ -6869,7 +6733,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6869
6733
|
session_id_present: true,
|
|
6870
6734
|
launchId: ap.launchId || void 0,
|
|
6871
6735
|
inbox_count: ap.inbox.length,
|
|
6872
|
-
pending_notification_count: ap.pendingNotificationCount
|
|
6736
|
+
pending_notification_count: ap.pendingNotificationCount,
|
|
6737
|
+
notification_timer_present: Boolean(ap.notificationTimer)
|
|
6873
6738
|
}));
|
|
6874
6739
|
return true;
|
|
6875
6740
|
}
|
|
@@ -6892,7 +6757,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6892
6757
|
return true;
|
|
6893
6758
|
}
|
|
6894
6759
|
async resetWorkspace(agentId) {
|
|
6895
|
-
const agentDataDir =
|
|
6760
|
+
const agentDataDir = path12.join(this.dataDir, agentId);
|
|
6896
6761
|
try {
|
|
6897
6762
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
6898
6763
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -6929,7 +6794,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
6929
6794
|
return result;
|
|
6930
6795
|
}
|
|
6931
6796
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
6932
|
-
const workspacePath =
|
|
6797
|
+
const workspacePath = path12.join(this.dataDir, agentId);
|
|
6933
6798
|
return {
|
|
6934
6799
|
agentId,
|
|
6935
6800
|
launchId,
|
|
@@ -7186,7 +7051,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7186
7051
|
}
|
|
7187
7052
|
// Workspace file browsing
|
|
7188
7053
|
async getFileTree(agentId, dirPath) {
|
|
7189
|
-
const agentDir =
|
|
7054
|
+
const agentDir = path12.join(this.dataDir, agentId);
|
|
7190
7055
|
try {
|
|
7191
7056
|
await stat2(agentDir);
|
|
7192
7057
|
} catch {
|
|
@@ -7194,8 +7059,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7194
7059
|
}
|
|
7195
7060
|
let targetDir = agentDir;
|
|
7196
7061
|
if (dirPath) {
|
|
7197
|
-
const resolved =
|
|
7198
|
-
if (!resolved.startsWith(agentDir +
|
|
7062
|
+
const resolved = path12.resolve(agentDir, dirPath);
|
|
7063
|
+
if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
|
|
7199
7064
|
return [];
|
|
7200
7065
|
}
|
|
7201
7066
|
targetDir = resolved;
|
|
@@ -7203,14 +7068,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7203
7068
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
7204
7069
|
}
|
|
7205
7070
|
async readFile(agentId, filePath) {
|
|
7206
|
-
const agentDir =
|
|
7207
|
-
const resolved =
|
|
7208
|
-
if (!resolved.startsWith(agentDir +
|
|
7071
|
+
const agentDir = path12.join(this.dataDir, agentId);
|
|
7072
|
+
const resolved = path12.resolve(agentDir, filePath);
|
|
7073
|
+
if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
|
|
7209
7074
|
throw new Error("Access denied");
|
|
7210
7075
|
}
|
|
7211
7076
|
const info = await stat2(resolved);
|
|
7212
7077
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
7213
|
-
const ext =
|
|
7078
|
+
const ext = path12.extname(resolved).toLowerCase();
|
|
7214
7079
|
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
7215
7080
|
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
7216
7081
|
const content = await readFile(resolved, "utf-8");
|
|
@@ -7245,13 +7110,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7245
7110
|
const agent = this.agents.get(agentId);
|
|
7246
7111
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
7247
7112
|
const home = os6.homedir();
|
|
7248
|
-
const workspaceDir =
|
|
7113
|
+
const workspaceDir = path12.join(this.dataDir, agentId);
|
|
7249
7114
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
7250
7115
|
const globalResults = await Promise.all(
|
|
7251
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
7116
|
+
paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
|
|
7252
7117
|
);
|
|
7253
7118
|
const workspaceResults = await Promise.all(
|
|
7254
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
7119
|
+
paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
|
|
7255
7120
|
);
|
|
7256
7121
|
const dedup = (skills) => {
|
|
7257
7122
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7280,7 +7145,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7280
7145
|
const skills = [];
|
|
7281
7146
|
for (const entry of entries) {
|
|
7282
7147
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
7283
|
-
const skillMd =
|
|
7148
|
+
const skillMd = path12.join(dir, entry.name, "SKILL.md");
|
|
7284
7149
|
try {
|
|
7285
7150
|
const content = await readFile(skillMd, "utf-8");
|
|
7286
7151
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -7291,7 +7156,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7291
7156
|
} else if (entry.name.endsWith(".md")) {
|
|
7292
7157
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
7293
7158
|
try {
|
|
7294
|
-
const content = await readFile(
|
|
7159
|
+
const content = await readFile(path12.join(dir, entry.name), "utf-8");
|
|
7295
7160
|
const skill = this.parseSkillMd(cmdName, content);
|
|
7296
7161
|
skill.sourcePath = dir;
|
|
7297
7162
|
skills.push(skill);
|
|
@@ -7694,11 +7559,17 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7694
7559
|
if (reason !== "turn_end") {
|
|
7695
7560
|
if (ap.gatedSteering.toolBoundaryFlushDisabled) return false;
|
|
7696
7561
|
if (ap.gatedSteering.compacting || ap.gatedSteering.outstandingToolUses > 0) return false;
|
|
7562
|
+
this.recordGatedSteeringEvent(agentId, ap, "notify", { reason, pendingMessages: ap.inbox.length });
|
|
7563
|
+
return this.sendStdinNotification(agentId);
|
|
7697
7564
|
}
|
|
7698
7565
|
const pendingMessages = ap.inbox.length;
|
|
7699
7566
|
const pendingNotificationCount = ap.pendingNotificationCount;
|
|
7700
7567
|
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
7701
7568
|
ap.pendingNotificationCount = 0;
|
|
7569
|
+
if (ap.notificationTimer) {
|
|
7570
|
+
clearTimeout(ap.notificationTimer);
|
|
7571
|
+
ap.notificationTimer = null;
|
|
7572
|
+
}
|
|
7702
7573
|
ap.gatedSteering.lastFlushReason = reason;
|
|
7703
7574
|
if (reason !== "turn_end") {
|
|
7704
7575
|
ap.gatedSteering.inFlightBatch = {
|
|
@@ -7726,28 +7597,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7726
7597
|
}
|
|
7727
7598
|
flushCompactionBoundaryMessages(agentId, ap) {
|
|
7728
7599
|
if (!ap.sessionId || !ap.driver.supportsStdinNotification || ap.inbox.length === 0) return false;
|
|
7729
|
-
if (ap.driver.busyDeliveryMode === "gated") {
|
|
7730
|
-
return this.tryFlushGatedSteering(agentId, ap, "compaction_finished");
|
|
7731
|
-
}
|
|
7732
|
-
if (ap.driver.busyDeliveryMode === "direct") {
|
|
7733
|
-
const pendingMessages = ap.inbox.length;
|
|
7734
|
-
const pendingNotificationCount = ap.pendingNotificationCount;
|
|
7735
|
-
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
7736
|
-
ap.pendingNotificationCount = 0;
|
|
7737
|
-
if (ap.notificationTimer) {
|
|
7738
|
-
clearTimeout(ap.notificationTimer);
|
|
7739
|
-
ap.notificationTimer = null;
|
|
7740
|
-
}
|
|
7741
|
-
this.recordRuntimeTraceEvent(agentId, ap, "runtime.compaction_boundary.flush", {
|
|
7742
|
-
mode: "direct",
|
|
7743
|
-
messageCount: nextMessages.length
|
|
7744
|
-
});
|
|
7745
|
-
if (this.deliverMessagesViaStdin(agentId, ap, nextMessages, "busy")) {
|
|
7746
|
-
return true;
|
|
7747
|
-
}
|
|
7748
|
-
ap.pendingNotificationCount += pendingNotificationCount || pendingMessages;
|
|
7749
|
-
return false;
|
|
7750
|
-
}
|
|
7751
7600
|
if (ap.pendingNotificationCount > 0) {
|
|
7752
7601
|
if (ap.notificationTimer) {
|
|
7753
7602
|
clearTimeout(ap.notificationTimer);
|
|
@@ -7966,6 +7815,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
7966
7815
|
if (ap.inbox.length > 0 && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
7967
7816
|
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
7968
7817
|
ap.pendingNotificationCount = 0;
|
|
7818
|
+
if (ap.notificationTimer) {
|
|
7819
|
+
clearTimeout(ap.notificationTimer);
|
|
7820
|
+
ap.notificationTimer = null;
|
|
7821
|
+
}
|
|
7969
7822
|
if (ap.driver.busyDeliveryMode === "gated") {
|
|
7970
7823
|
ap.gatedSteering.lastFlushReason = "turn_end";
|
|
7971
7824
|
this.recordGatedSteeringEvent(agentId, ap, "flush", {
|
|
@@ -8109,13 +7962,16 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8109
7962
|
/** Send a batched notification to the agent via stdin about pending messages */
|
|
8110
7963
|
sendStdinNotification(agentId) {
|
|
8111
7964
|
const ap = this.agents.get(agentId);
|
|
8112
|
-
if (!ap) return;
|
|
7965
|
+
if (!ap) return false;
|
|
8113
7966
|
const count = ap.pendingNotificationCount;
|
|
8114
7967
|
ap.pendingNotificationCount = 0;
|
|
8115
|
-
ap.notificationTimer
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
7968
|
+
if (ap.notificationTimer) {
|
|
7969
|
+
clearTimeout(ap.notificationTimer);
|
|
7970
|
+
ap.notificationTimer = null;
|
|
7971
|
+
}
|
|
7972
|
+
if (count === 0) return false;
|
|
7973
|
+
if (ap.isIdle) return false;
|
|
7974
|
+
if (!ap.sessionId) return false;
|
|
8119
7975
|
if (ap.gatedSteering.compacting) {
|
|
8120
7976
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.compaction_boundary.delivery_suppressed", {
|
|
8121
7977
|
pendingNotificationCount: count,
|
|
@@ -8126,30 +7982,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8126
7982
|
logger.info(
|
|
8127
7983
|
`[Agent ${agentId}] Suppressing stdin delivery until context compaction finishes; pending=${ap.inbox.length}`
|
|
8128
7984
|
);
|
|
8129
|
-
return;
|
|
8130
|
-
}
|
|
8131
|
-
if (ap.driver.busyDeliveryMode === "gated") {
|
|
8132
|
-
this.recordGatedSteeringEvent(agentId, ap, "suppress", {
|
|
8133
|
-
reason: "timer_notification_not_safe_boundary",
|
|
8134
|
-
pendingNotificationCount: count
|
|
8135
|
-
});
|
|
8136
|
-
ap.pendingNotificationCount += count;
|
|
8137
|
-
logger.info(
|
|
8138
|
-
`[Agent ${agentId}] Suppressing raw busy stdin notification until Claude gated steering boundary; pending=${ap.inbox.length}`
|
|
8139
|
-
);
|
|
8140
|
-
return;
|
|
8141
|
-
}
|
|
8142
|
-
if (ap.driver.busyDeliveryMode === "direct" && ap.inbox.length > 0) {
|
|
8143
|
-
const queuedMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
8144
|
-
console.log(`[Agent ${agentId}] Delivering queued message via stdin while busy`);
|
|
8145
|
-
if (this.deliverMessagesViaStdin(agentId, ap, queuedMessages, "busy")) {
|
|
8146
|
-
return;
|
|
8147
|
-
}
|
|
8148
|
-
ap.pendingNotificationCount += count;
|
|
8149
|
-
return;
|
|
7985
|
+
return false;
|
|
8150
7986
|
}
|
|
8151
|
-
const
|
|
8152
|
-
|
|
7987
|
+
const inboxCount = ap.inbox.length;
|
|
7988
|
+
if (inboxCount === 0) return false;
|
|
7989
|
+
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.]`;
|
|
7990
|
+
logger.info(`[Agent ${agentId}] Sending stdin notification: ${inboxCount} pending inbox message(s)`);
|
|
8153
7991
|
const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId, { mode: "busy" });
|
|
8154
7992
|
if (encoded) {
|
|
8155
7993
|
ap.process.stdin?.write(encoded + "\n");
|
|
@@ -8161,9 +7999,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8161
7999
|
outcome: "written",
|
|
8162
8000
|
mode: "busy",
|
|
8163
8001
|
pending_notification_count: count,
|
|
8002
|
+
inbox_count: inboxCount,
|
|
8164
8003
|
session_id_present: true
|
|
8165
8004
|
});
|
|
8005
|
+
return true;
|
|
8166
8006
|
} else {
|
|
8007
|
+
ap.pendingNotificationCount += count;
|
|
8167
8008
|
this.recordDaemonTrace("daemon.agent.stdin_notification", {
|
|
8168
8009
|
agentId,
|
|
8169
8010
|
runtime: ap.config.runtime,
|
|
@@ -8172,8 +8013,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8172
8013
|
outcome: "encode_failed",
|
|
8173
8014
|
mode: "busy",
|
|
8174
8015
|
pending_notification_count: count,
|
|
8016
|
+
inbox_count: inboxCount,
|
|
8175
8017
|
session_id_present: true
|
|
8176
8018
|
}, "error");
|
|
8019
|
+
return false;
|
|
8177
8020
|
}
|
|
8178
8021
|
}
|
|
8179
8022
|
/** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
|
|
@@ -8279,8 +8122,8 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
8279
8122
|
const nodes = [];
|
|
8280
8123
|
for (const entry of entries) {
|
|
8281
8124
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
8282
|
-
const fullPath =
|
|
8283
|
-
const relativePath =
|
|
8125
|
+
const fullPath = path12.join(dir, entry.name);
|
|
8126
|
+
const relativePath = path12.relative(rootDir, fullPath);
|
|
8284
8127
|
let info;
|
|
8285
8128
|
try {
|
|
8286
8129
|
info = await stat2(fullPath);
|
|
@@ -8409,10 +8252,12 @@ var DaemonConnection = class {
|
|
|
8409
8252
|
messageKind = msg.type;
|
|
8410
8253
|
this.markInbound(messageKind);
|
|
8411
8254
|
this.resetWatchdog();
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8255
|
+
if (messageKind !== "ping") {
|
|
8256
|
+
this.trace("daemon.connection.inbound_received", {
|
|
8257
|
+
message_type: messageKind,
|
|
8258
|
+
last_inbound_age_ms_bucket: "0"
|
|
8259
|
+
});
|
|
8260
|
+
}
|
|
8416
8261
|
this.options.onMessage(msg);
|
|
8417
8262
|
} catch (err) {
|
|
8418
8263
|
this.markInbound("invalid_json");
|
|
@@ -8583,9 +8428,9 @@ var ReminderCache = class {
|
|
|
8583
8428
|
|
|
8584
8429
|
// src/machineLock.ts
|
|
8585
8430
|
import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
|
|
8586
|
-
import { mkdirSync as
|
|
8431
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
8587
8432
|
import os7 from "os";
|
|
8588
|
-
import
|
|
8433
|
+
import path13 from "path";
|
|
8589
8434
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
8590
8435
|
var DaemonMachineLockConflictError = class extends Error {
|
|
8591
8436
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -8607,7 +8452,7 @@ function resolveDefaultMachineStateRoot() {
|
|
|
8607
8452
|
return resolveSlockHomePath("machines");
|
|
8608
8453
|
}
|
|
8609
8454
|
function ownerPath(lockDir) {
|
|
8610
|
-
return
|
|
8455
|
+
return path13.join(lockDir, "owner.json");
|
|
8611
8456
|
}
|
|
8612
8457
|
function readOwner(lockDir) {
|
|
8613
8458
|
try {
|
|
@@ -8637,13 +8482,13 @@ function acquireDaemonMachineLock(options) {
|
|
|
8637
8482
|
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
8638
8483
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
8639
8484
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
8640
|
-
const machineDir =
|
|
8641
|
-
const lockDir =
|
|
8485
|
+
const machineDir = path13.join(rootDir, lockId);
|
|
8486
|
+
const lockDir = path13.join(machineDir, "daemon.lock");
|
|
8642
8487
|
const token = randomUUID2();
|
|
8643
|
-
|
|
8488
|
+
mkdirSync5(machineDir, { recursive: true });
|
|
8644
8489
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
8645
8490
|
try {
|
|
8646
|
-
|
|
8491
|
+
mkdirSync5(lockDir);
|
|
8647
8492
|
const owner = {
|
|
8648
8493
|
pid: process.pid,
|
|
8649
8494
|
token,
|
|
@@ -8653,7 +8498,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
8653
8498
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
8654
8499
|
};
|
|
8655
8500
|
try {
|
|
8656
|
-
|
|
8501
|
+
writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
|
|
8657
8502
|
`, { mode: 384 });
|
|
8658
8503
|
} catch (err) {
|
|
8659
8504
|
rmSync3(lockDir, { recursive: true, force: true });
|
|
@@ -8690,8 +8535,8 @@ function acquireDaemonMachineLock(options) {
|
|
|
8690
8535
|
}
|
|
8691
8536
|
|
|
8692
8537
|
// src/localTraceSink.ts
|
|
8693
|
-
import { appendFileSync, mkdirSync as
|
|
8694
|
-
import
|
|
8538
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
8539
|
+
import path14 from "path";
|
|
8695
8540
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
8696
8541
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
8697
8542
|
var DEFAULT_MAX_FILES = 8;
|
|
@@ -8727,7 +8572,7 @@ var LocalRotatingTraceSink = class {
|
|
|
8727
8572
|
currentSize = 0;
|
|
8728
8573
|
sequence = 0;
|
|
8729
8574
|
constructor(options) {
|
|
8730
|
-
this.traceDir =
|
|
8575
|
+
this.traceDir = path14.join(options.machineDir, "traces");
|
|
8731
8576
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
8732
8577
|
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
8733
8578
|
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
@@ -8753,15 +8598,15 @@ var LocalRotatingTraceSink = class {
|
|
|
8753
8598
|
return this.currentFile;
|
|
8754
8599
|
}
|
|
8755
8600
|
ensureFile(nextBytes) {
|
|
8756
|
-
|
|
8601
|
+
mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
|
|
8757
8602
|
const nowMs = this.nowMsProvider();
|
|
8758
8603
|
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
8759
8604
|
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
8760
|
-
this.currentFile =
|
|
8605
|
+
this.currentFile = path14.join(
|
|
8761
8606
|
this.traceDir,
|
|
8762
8607
|
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
8763
8608
|
);
|
|
8764
|
-
|
|
8609
|
+
writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
|
|
8765
8610
|
this.currentSize = statSync4(this.currentFile).size;
|
|
8766
8611
|
this.currentFileOpenedAtMs = nowMs;
|
|
8767
8612
|
this.pruneOldFiles();
|
|
@@ -8772,7 +8617,7 @@ var LocalRotatingTraceSink = class {
|
|
|
8772
8617
|
const excess = files.length - this.maxFiles;
|
|
8773
8618
|
if (excess <= 0) return;
|
|
8774
8619
|
for (const file of files.slice(0, excess)) {
|
|
8775
|
-
rmSync4(
|
|
8620
|
+
rmSync4(path14.join(this.traceDir, file), { force: true });
|
|
8776
8621
|
}
|
|
8777
8622
|
}
|
|
8778
8623
|
};
|
|
@@ -8859,11 +8704,11 @@ function isDiagnosticErrorAttr(key) {
|
|
|
8859
8704
|
import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
|
|
8860
8705
|
import { gzipSync } from "zlib";
|
|
8861
8706
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
8862
|
-
import
|
|
8707
|
+
import path15 from "path";
|
|
8863
8708
|
|
|
8864
8709
|
// src/directUploadCapability.ts
|
|
8865
|
-
function joinUrl(base,
|
|
8866
|
-
return `${base.replace(/\/+$/, "")}${
|
|
8710
|
+
function joinUrl(base, path17) {
|
|
8711
|
+
return `${base.replace(/\/+$/, "")}${path17}`;
|
|
8867
8712
|
}
|
|
8868
8713
|
function jsonHeaders(apiKey) {
|
|
8869
8714
|
return {
|
|
@@ -9082,7 +8927,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
9082
8927
|
}, nextMs);
|
|
9083
8928
|
}
|
|
9084
8929
|
async findUploadCandidates() {
|
|
9085
|
-
const traceDir =
|
|
8930
|
+
const traceDir = path15.join(this.options.machineDir, "traces");
|
|
9086
8931
|
let names;
|
|
9087
8932
|
try {
|
|
9088
8933
|
names = await readdir3(traceDir);
|
|
@@ -9094,8 +8939,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
9094
8939
|
const currentFile = this.options.currentFileProvider?.();
|
|
9095
8940
|
const candidates = [];
|
|
9096
8941
|
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
9097
|
-
const file =
|
|
9098
|
-
if (currentFile &&
|
|
8942
|
+
const file = path15.join(traceDir, name);
|
|
8943
|
+
if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
|
|
9099
8944
|
if (await this.isUploaded(file)) continue;
|
|
9100
8945
|
try {
|
|
9101
8946
|
const info = await stat3(file);
|
|
@@ -9169,8 +9014,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
9169
9014
|
}
|
|
9170
9015
|
}
|
|
9171
9016
|
uploadStatePath(file) {
|
|
9172
|
-
const stateDir =
|
|
9173
|
-
return
|
|
9017
|
+
const stateDir = path15.join(this.options.machineDir, "trace-uploads");
|
|
9018
|
+
return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
|
|
9174
9019
|
}
|
|
9175
9020
|
async isUploaded(file) {
|
|
9176
9021
|
try {
|
|
@@ -9182,9 +9027,9 @@ var DaemonTraceBundleUploader = class {
|
|
|
9182
9027
|
}
|
|
9183
9028
|
async markUploaded(file, metadata) {
|
|
9184
9029
|
const stateFile = this.uploadStatePath(file);
|
|
9185
|
-
await mkdir2(
|
|
9030
|
+
await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
|
|
9186
9031
|
await writeFile2(stateFile, `${JSON.stringify({
|
|
9187
|
-
file:
|
|
9032
|
+
file: path15.basename(file),
|
|
9188
9033
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9189
9034
|
...metadata
|
|
9190
9035
|
}, null, 2)}
|
|
@@ -9206,7 +9051,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
|
|
|
9206
9051
|
var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
|
|
9207
9052
|
var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
|
|
9208
9053
|
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
|
|
9209
|
-
var DAEMON_CLI_USAGE =
|
|
9054
|
+
var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
|
|
9210
9055
|
var RunnerCredentialMintError2 = class extends Error {
|
|
9211
9056
|
code;
|
|
9212
9057
|
retryable;
|
|
@@ -9242,9 +9087,9 @@ function runnerCredentialErrorDetail2(error) {
|
|
|
9242
9087
|
async function waitForRunnerCredentialRetry2() {
|
|
9243
9088
|
await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
|
|
9244
9089
|
}
|
|
9245
|
-
function parseDaemonCliArgs(args
|
|
9090
|
+
function parseDaemonCliArgs(args) {
|
|
9246
9091
|
let serverUrl = "";
|
|
9247
|
-
let apiKey =
|
|
9092
|
+
let apiKey = "";
|
|
9248
9093
|
for (let i = 0; i < args.length; i++) {
|
|
9249
9094
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
9250
9095
|
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
@@ -9261,23 +9106,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
9261
9106
|
}
|
|
9262
9107
|
}
|
|
9263
9108
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
9264
|
-
const dirname =
|
|
9265
|
-
const jsPath =
|
|
9109
|
+
const dirname = path16.dirname(fileURLToPath(moduleUrl));
|
|
9110
|
+
const jsPath = path16.resolve(dirname, "chat-bridge.js");
|
|
9266
9111
|
try {
|
|
9267
9112
|
accessSync(jsPath);
|
|
9268
9113
|
return jsPath;
|
|
9269
9114
|
} catch {
|
|
9270
|
-
return
|
|
9115
|
+
return path16.resolve(dirname, "chat-bridge.ts");
|
|
9271
9116
|
}
|
|
9272
9117
|
}
|
|
9273
9118
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
9274
|
-
const thisDir =
|
|
9275
|
-
const bundledDistPath =
|
|
9119
|
+
const thisDir = path16.dirname(fileURLToPath(moduleUrl));
|
|
9120
|
+
const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
|
|
9276
9121
|
try {
|
|
9277
9122
|
accessSync(bundledDistPath);
|
|
9278
9123
|
return bundledDistPath;
|
|
9279
9124
|
} catch {
|
|
9280
|
-
const workspaceDistPath =
|
|
9125
|
+
const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
9281
9126
|
accessSync(workspaceDistPath);
|
|
9282
9127
|
return workspaceDistPath;
|
|
9283
9128
|
}
|
|
@@ -9456,7 +9301,7 @@ var DaemonCore = class {
|
|
|
9456
9301
|
}
|
|
9457
9302
|
resolveMachineStateRoot() {
|
|
9458
9303
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
9459
|
-
if (this.options.dataDir) return
|
|
9304
|
+
if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
|
|
9460
9305
|
return resolveDefaultMachineStateRoot();
|
|
9461
9306
|
}
|
|
9462
9307
|
shouldEnableLocalTrace() {
|
|
@@ -9958,8 +9803,6 @@ var DaemonCore = class {
|
|
|
9958
9803
|
};
|
|
9959
9804
|
|
|
9960
9805
|
export {
|
|
9961
|
-
DAEMON_API_KEY_ENV,
|
|
9962
|
-
scrubDaemonAuthEnv,
|
|
9963
9806
|
resolveWorkspaceDirectoryPath,
|
|
9964
9807
|
scanWorkspaceDirectories,
|
|
9965
9808
|
deleteWorkspaceDirectory,
|