@slock-ai/daemon 0.56.1 → 0.57.0-play.20260606141931
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-RIBO24KM.js → chunk-Q55N6BNS.js} +1032 -656
- package/dist/cli/index.js +69 -11
- package/dist/core.js +6 -6
- package/dist/drivers/piSdkRunner.js +96 -0
- package/dist/index.js +5 -4
- package/package.json +3 -1
- package/dist/chat-bridge.js +0 -96
- package/dist/chunk-M2KQBJR3.js +0 -260
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
|
|
3
|
-
buildWebSocketOptions,
|
|
4
|
-
daemonFetch,
|
|
5
|
-
executeJsonRequest,
|
|
6
|
-
executeResponseRequest,
|
|
7
|
-
logger
|
|
8
|
-
} from "./chunk-M2KQBJR3.js";
|
|
9
|
-
|
|
10
1
|
// src/core.ts
|
|
11
|
-
import
|
|
2
|
+
import path15 from "path";
|
|
12
3
|
import os7 from "os";
|
|
13
4
|
import { createRequire as createRequire2 } from "module";
|
|
14
5
|
import { accessSync } from "fs";
|
|
@@ -932,10 +923,22 @@ var channelAddMemberOperationSchema = z.object({
|
|
|
932
923
|
agents: z.array(idOrHandleSchema).max(64).optional(),
|
|
933
924
|
draftHint: draftHintSchema
|
|
934
925
|
});
|
|
926
|
+
var integrationApproveAgentLoginOperationSchema = z.object({
|
|
927
|
+
type: z.literal("integration:approve_agent_login"),
|
|
928
|
+
requestId: uuidSchema,
|
|
929
|
+
agentId: uuidSchema,
|
|
930
|
+
agentName: z.string().trim().min(1).max(120),
|
|
931
|
+
clientId: uuidSchema,
|
|
932
|
+
clientKey: z.string().trim().min(1).max(120),
|
|
933
|
+
clientName: z.string().trim().min(1).max(120),
|
|
934
|
+
scopes: z.array(z.string().trim().min(1).max(120)).max(64),
|
|
935
|
+
draftHint: draftHintSchema
|
|
936
|
+
});
|
|
935
937
|
var actionCardActionSchema = z.discriminatedUnion("type", [
|
|
936
938
|
channelCreateOperationSchema,
|
|
937
939
|
agentCreateOperationSchema,
|
|
938
|
-
channelAddMemberOperationSchema
|
|
940
|
+
channelAddMemberOperationSchema,
|
|
941
|
+
integrationApproveAgentLoginOperationSchema
|
|
939
942
|
]);
|
|
940
943
|
|
|
941
944
|
// ../shared/src/agentInbox.ts
|
|
@@ -1262,10 +1265,10 @@ var DISPLAY_PLAN_CONFIG = {
|
|
|
1262
1265
|
};
|
|
1263
1266
|
|
|
1264
1267
|
// src/agentProcessManager.ts
|
|
1265
|
-
import { mkdirSync as
|
|
1268
|
+
import { mkdirSync as mkdirSync3, readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync4 } from "fs";
|
|
1266
1269
|
import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
|
|
1267
1270
|
import { createHash as createHash3 } from "crypto";
|
|
1268
|
-
import
|
|
1271
|
+
import path11 from "path";
|
|
1269
1272
|
import os5 from "os";
|
|
1270
1273
|
|
|
1271
1274
|
// src/drivers/claude.ts
|
|
@@ -1276,6 +1279,329 @@ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
|
1276
1279
|
import { createRequire } from "module";
|
|
1277
1280
|
import path2 from "path";
|
|
1278
1281
|
|
|
1282
|
+
// src/drivers/slockCliGuide.ts
|
|
1283
|
+
var MESSAGE_HEREDOC_DELIMITER = "SLOCKMSG";
|
|
1284
|
+
function describeSlockInstallation(audience) {
|
|
1285
|
+
if (audience === "managed-runner") {
|
|
1286
|
+
return "The daemon injects a local `slock` wrapper into PATH for you.";
|
|
1287
|
+
}
|
|
1288
|
+
return "Install the published agent CLI: `npm i -g @slock-ai/cli@latest`. Discover/select a valid external-CLI agent identity first, for example with `slock agent list --server <serverUrl>` or a Slock setup card; then run `slock agent login --server <serverUrl> --agent <id> --profile-slug <slug>` for the selected agent. After login succeeds, invoke commands as `slock --profile <slug> ...` (or set `SLOCK_PROFILE=<slug>`).";
|
|
1289
|
+
}
|
|
1290
|
+
function buildCommunicationSection(audience) {
|
|
1291
|
+
const installation = describeSlockInstallation(audience);
|
|
1292
|
+
return `## Communication \u2014 slock CLI ONLY
|
|
1293
|
+
|
|
1294
|
+
Use the \`slock\` CLI for chat / task / attachment operations. ${installation} Use ONLY these commands for communication:
|
|
1295
|
+
|
|
1296
|
+
1. **\`slock message check\`** \u2014 Non-blocking check for new messages. Use freely during work \u2014 at natural breakpoints or after notifications.
|
|
1297
|
+
2. **\`slock message send\`** \u2014 Send a message to a channel or DM.
|
|
1298
|
+
3. **\`slock server info\`** \u2014 List channels in this server, which ones you have joined, plus all agents and humans.
|
|
1299
|
+
4. **\`slock channel members\`** \u2014 List the members (agents and humans) of a specific channel, DM, or thread target.
|
|
1300
|
+
5. **\`slock channel join\`** \u2014 Join a visible public channel. This only affects your own agent membership.
|
|
1301
|
+
6. **\`slock channel leave\`** \u2014 Leave a regular channel you have joined. This only affects your own agent membership.
|
|
1302
|
+
7. **\`slock thread unfollow\`** \u2014 Stop receiving ordinary delivery for a thread you no longer need to follow. This only affects your own agent attention state.
|
|
1303
|
+
8. **\`slock message read\`** \u2014 Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` anchors and \`around\` for centered context.
|
|
1304
|
+
9. **\`slock message search\`** \u2014 Search messages visible to you, then inspect a hit with \`slock message read\`.
|
|
1305
|
+
10. **\`slock message resolve\`** \u2014 Verify that a cited message id exists exactly and print its canonical message row. Use this when checking whether a referenced id is real; \`read --around\` is for context, not proof.
|
|
1306
|
+
11. **\`slock message react\`** \u2014 Add or remove your reaction on a message. Use sparingly: prefer acknowledgement/follow-up signals like \u{1F440}, and do not auto-react to every merge, deploy, or task completion with celebratory emoji.
|
|
1307
|
+
12. **\`slock task list\`** \u2014 View a channel's task board.
|
|
1308
|
+
13. **\`slock task create\`** \u2014 Create new task-messages in a channel (supports batch titles; equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
|
|
1309
|
+
14. **\`slock task claim\`** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
|
|
1310
|
+
15. **\`slock task unclaim\`** \u2014 Release your claim on a task.
|
|
1311
|
+
16. **\`slock task update\`** \u2014 Change a task's status (e.g. to in_review or done).
|
|
1312
|
+
17. **\`slock attachment upload\`** \u2014 Upload a file to attach to a message. Uses content sniffing for image previews; pass \`--mime-type\` only when you know the exact type. Returns an attachment ID to pass to \`slock message send\`.
|
|
1313
|
+
18. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
1314
|
+
19. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
1315
|
+
20. **\`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.
|
|
1316
|
+
21. **\`slock integration list\`** \u2014 List built-in Slock apps, registered third-party services, and this agent's active Slock Agent Logins.
|
|
1317
|
+
22. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a built-in Slock app or registered third-party service.
|
|
1318
|
+
23. **\`slock integration env\`** \u2014 Print per-agent local CLI environment for a manifest-backed service that requires isolated HOME/XDG state.
|
|
1319
|
+
24. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
1320
|
+
25. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
|
|
1321
|
+
26. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
|
|
1322
|
+
27. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
1323
|
+
28. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
1324
|
+
29. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
1325
|
+
30. **\`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\`).
|
|
1326
|
+
|
|
1327
|
+
The CLI prints human-readable canonical text on success (matching the format you see in received messages and history). On failure it prints canonical labeled text to stderr:
|
|
1328
|
+
- \`Error:\` human-readable error summary
|
|
1329
|
+
- \`Code:\` stable machine-oriented error code
|
|
1330
|
+
- \`Next action:\` optional recovery hint
|
|
1331
|
+
|
|
1332
|
+
Error code prefixes tell you the layer:
|
|
1333
|
+
- \`MISSING_*\` / \`TOKEN_*\` = local auth bootstrap
|
|
1334
|
+
- \`*_FAILED\` = 4xx from server
|
|
1335
|
+
- \`SERVER_5XX\` = server unreachable / crashed`;
|
|
1336
|
+
}
|
|
1337
|
+
function buildCredentialHygieneSection() {
|
|
1338
|
+
return `### Credential hygiene
|
|
1339
|
+
|
|
1340
|
+
**Never paste credentials into Slock messages, attachments, or task fields.** Agent tokens (\`sk_agent_*\`), legacy machine API keys (\`sk_machine_*\`), session bearers, JWTs, \`.env\` files, or \`credential.json\` contents must never appear in chat \u2014 not in debug traces, error reports, "for context" snippets, or screenshots. If you accidentally paste one, immediately tell the credential owner so they can rotate it; deleting the message does not erase it from message history visible to channel members or search indexes.
|
|
1341
|
+
|
|
1342
|
+
If a tool or error output contains credential-shaped strings, redact them to \`sk_agent_<redacted>\` / \`sk_machine_<redacted>\` shape before reposting.
|
|
1343
|
+
|
|
1344
|
+
**Profile credential resolution is strict.** When invoked as \`slock --profile <slug>\` or with \`SLOCK_PROFILE=<slug>\`, the CLI resolves credentials from \`$SLOCK_PROFILE_DIR\` \u2192 \`$SLOCK_HOME/profiles/<slug>\` \u2192 \`$HOME/.slock/profiles/<slug>\` in that order. It does **not** fall back to a different profile's credential, to an ambient user-level token, or to environment-leaked secrets \u2014 if your designated profile credential is missing or unreadable, the CLI fails closed rather than authenticating as someone else.`;
|
|
1345
|
+
}
|
|
1346
|
+
function buildSendingMessagesSection() {
|
|
1347
|
+
const D = MESSAGE_HEREDOC_DELIMITER;
|
|
1348
|
+
return `### Sending messages
|
|
1349
|
+
|
|
1350
|
+
- **Reply to a channel**: \`slock message send --target "#channel-name" <<'${D}'\` followed by the message body and \`${D}\`
|
|
1351
|
+
- **Reply to a DM**: \`slock message send --target dm:@peer-name <<'${D}'\` followed by the message body and \`${D}\`
|
|
1352
|
+
- **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'${D}'\` followed by the message body and \`${D}\`
|
|
1353
|
+
- **Start a NEW DM**: \`slock message send --target dm:@person-name <<'${D}'\` followed by the message body and \`${D}\`
|
|
1354
|
+
|
|
1355
|
+
Message content is always read from stdin. Use a heredoc so quotes, backticks, code blocks, and newlines are not interpreted by the shell:
|
|
1356
|
+
\`\`\`bash
|
|
1357
|
+
slock message send --target "#channel-name" <<'${D}'
|
|
1358
|
+
Long message with "quotes", $vars, \`backticks\`, and code blocks.
|
|
1359
|
+
${D}
|
|
1360
|
+
\`\`\`
|
|
1361
|
+
|
|
1362
|
+
Use a delimiter that is unlikely to appear in the message body; the examples use \`${D}\` instead of \`EOF\` so shell snippets and recovery drafts are less likely to leak delimiter text into sent messages.
|
|
1363
|
+
|
|
1364
|
+
If Slock says a message was not sent and was saved as a draft, choose one path:
|
|
1365
|
+
- To update the draft, use a normal \`slock message send --target <target>\` with the revised content.
|
|
1366
|
+
- To send the current draft unchanged, use \`slock message send --send-draft --target <target>\` with no stdin. Do not use \`--send-draft\` when changing content.
|
|
1367
|
+
|
|
1368
|
+
**IMPORTANT**: To reply to any message, always reuse the exact \`target\` from the received message. This ensures your reply goes to the right place \u2014 whether it's a channel, DM, or thread.`;
|
|
1369
|
+
}
|
|
1370
|
+
function buildRemindersSection() {
|
|
1371
|
+
return `### Reminders
|
|
1372
|
+
|
|
1373
|
+
Use reminders for follow-up that depends on future state you cannot resolve now, whether user-requested or self-driven. A reminder is an author-owned, persistent, observable, snoozable, updatable, and cancelable wake-up signal anchored to a Slock message or thread; when it fires, it wakes the author who scheduled it, not other people. If anchored to a message or thread, the receipt/fire system message is visible in that surface, but wake ownership does not transfer. To notify another human or agent later, schedule your own reminder and then @mention them when it fires. Use reminders instead of keeping the current turn alive with a long sleep or relying on MEMORY to wake you. If you expect the wait to finish within about 1 minute, you may briefly poll, but say so in the relevant thread first.
|
|
1374
|
+
When a reminder already exists, prefer \`slock reminder snooze\` to push it later, \`slock reminder update\` to change its meaning or schedule, and \`slock reminder cancel\` only when it is truly no longer needed.
|
|
1375
|
+
Use \`slock reminder schedule\` rather than runtime-native wake or cron tools such as ScheduleWakeup or CronCreate for user-visible reminders, so reminders stay author-owned, persistent, observable, snoozable, updatable, and cancelable in Slock.
|
|
1376
|
+
Create agent reminders only after resolving the anchor message from the current conversation and passing its msgId explicitly; if no anchor can be resolved, consider posting a status update in the relevant thread so the intent is visible, then revisit when context is available.`;
|
|
1377
|
+
}
|
|
1378
|
+
function buildThreadsSection() {
|
|
1379
|
+
const D = MESSAGE_HEREDOC_DELIMITER;
|
|
1380
|
+
return `### Threads
|
|
1381
|
+
|
|
1382
|
+
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
|
|
1383
|
+
|
|
1384
|
+
- **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
|
|
1385
|
+
- When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
|
|
1386
|
+
- **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=00000000 ...]\`, reply with \`slock message send --target "#general:00000000" <<'${D}'\` followed by the message body and \`${D}\`. The thread will be auto-created if it doesn't exist yet. Example IDs like \`00000000\` are placeholders; real message IDs come from received messages.
|
|
1387
|
+
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
1388
|
+
- You can read thread history: \`slock message read --channel "#general:00000000"\`
|
|
1389
|
+
- You can stop receiving ordinary delivery for a thread with \`slock thread unfollow --target "#general:00000000"\`. Only do this when your work in that thread is clearly complete or no longer relevant.
|
|
1390
|
+
- Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
|
|
1391
|
+
}
|
|
1392
|
+
function buildDiscoverySection() {
|
|
1393
|
+
return `### Discovering people and channels
|
|
1394
|
+
|
|
1395
|
+
Call \`slock server info\` to see all channels in this server, which ones you have joined, other agents, and humans.
|
|
1396
|
+
Visible public channels may appear even when \`joined=false\`. In that state you can still inspect them with \`slock message read\` and \`slock channel members\`, but you cannot send messages there or receive ordinary channel delivery until you join with \`slock channel join --target "#channel-name"\`. Private channels require a human with access to add you. To leave a regular channel you have joined, use \`slock channel leave --target "#channel-name"\`. To stop following a thread without leaving its parent channel, use \`slock thread unfollow --target "#channel-name:shortid"\`.
|
|
1397
|
+
Private channels are membership-gated. If \`slock server info\` shows a channel as private, treat its name, members, and content as private to that channel; do not disclose that information in other channels, DMs, summaries, or task reports unless a human explicitly asks within an authorized context. In \`slock channel members\`, human role labels such as owner/admin show server-level authority; no role label means ordinary member.`;
|
|
1398
|
+
}
|
|
1399
|
+
function buildChannelAwarenessSection() {
|
|
1400
|
+
return `### Channel awareness
|
|
1401
|
+
|
|
1402
|
+
Each channel has a **name** and optionally a **description** that define its purpose (visible via \`slock server info\`). Respect them:
|
|
1403
|
+
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
1404
|
+
- **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.
|
|
1405
|
+
- If unsure where something belongs, call \`slock server info\` to review channel descriptions.`;
|
|
1406
|
+
}
|
|
1407
|
+
function buildReadingHistorySection() {
|
|
1408
|
+
return `### Reading history
|
|
1409
|
+
|
|
1410
|
+
\`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
|
|
1411
|
+
|
|
1412
|
+
To jump directly to a specific hit with nearby context, use \`slock message read --channel "..." --around "messageId"\` or \`slock message read --channel "..." --around 12345\`.`;
|
|
1413
|
+
}
|
|
1414
|
+
function buildHistoricalReferencesSection() {
|
|
1415
|
+
return `### Historical references
|
|
1416
|
+
|
|
1417
|
+
When a user refers to prior Slock discussion and the relevant context is not already available, first use \`slock message search\` and \`slock message read\` to find the original thread, decision, or owner before answering. If you find it, summarize the original conclusion with the source thread/message; if you cannot find it, say that explicitly.`;
|
|
1418
|
+
}
|
|
1419
|
+
function buildTasksSection() {
|
|
1420
|
+
const D = MESSAGE_HEREDOC_DELIMITER;
|
|
1421
|
+
return `### Tasks
|
|
1422
|
+
|
|
1423
|
+
When someone sends a message that asks you to do something \u2014 fix a bug, write code, review a PR, deploy, investigate an issue \u2014 that is work. Claim it before you start.
|
|
1424
|
+
|
|
1425
|
+
**Decision rule:** if fulfilling a message requires you to take action beyond just replying (running tools, writing code, making changes), claim the message first. If you're only answering a question or having a conversation, no claim needed.
|
|
1426
|
+
|
|
1427
|
+
**What you see in messages:**
|
|
1428
|
+
- A message already marked as a task: \`@Alice: Fix the login bug [task #3 status=in_progress]\`
|
|
1429
|
+
- A regular message (no task suffix): \`@Alice: Can someone look into the login bug?\`
|
|
1430
|
+
- A system notification about task changes: \`\u{1F4CB} Alice converted a message to task #3 "Fix the login bug"\`
|
|
1431
|
+
|
|
1432
|
+
Only top-level channel / DM messages can become tasks. Messages inside threads are discussion context \u2014 reply there, but keep claims and conversions to top-level messages.
|
|
1433
|
+
|
|
1434
|
+
\`slock message read\` shows messages in their current state. If a message was later converted to a task, it will show the \`[task #N ...]\` suffix.
|
|
1435
|
+
|
|
1436
|
+
**Status flow:** \`todo\` \u2192 \`in_progress\` \u2192 \`in_review\` \u2192 \`done\`
|
|
1437
|
+
|
|
1438
|
+
**Assignee** is independent from status \u2014 a task can be claimed or unclaimed at any status except \`done\`.
|
|
1439
|
+
|
|
1440
|
+
**Workflow:**
|
|
1441
|
+
1. Receive a message that requires action \u2192 claim it first (by task number if already a task, or by message ID if it's a regular message)
|
|
1442
|
+
2. If the claim fails, someone else is working on it \u2014 move on to another task
|
|
1443
|
+
3. Post updates in the task's thread: \`slock message send --target "#channel:msgShortId" <<'${D}'\` followed by the message body and \`${D}\`
|
|
1444
|
+
4. When done, set status to \`in_review\` so a human can validate via \`slock task update\`
|
|
1445
|
+
5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
|
|
1446
|
+
|
|
1447
|
+
**What \`slock task create\` really means:**
|
|
1448
|
+
- Tasks live in the same chat flow as messages. A task is just a message with task metadata, not a separate source of truth.
|
|
1449
|
+
- \`slock task create\` is a convenience helper for a specific sequence: create a brand-new message, then publish that new message as a task-message.
|
|
1450
|
+
- \`slock task create\` only creates the task \u2014 to own it, call \`slock task claim\` afterward.
|
|
1451
|
+
- Typical uses for \`slock task create\` are breaking down a larger task into parallel subtasks, or batch-creating genuinely new work for others to claim.
|
|
1452
|
+
- If someone already sent the work item as a message, just claim that existing message/task instead of creating a new one.
|
|
1453
|
+
- If the work already exists as a message, reuse it via \`slock task claim --message-id ...\`.
|
|
1454
|
+
|
|
1455
|
+
**Creating new tasks:**
|
|
1456
|
+
- The task system exists to prevent duplicate work. If you see an existing task for the work, either claim that task or leave it alone.
|
|
1457
|
+
- If a message already shows a \`[task #N ...]\` suffix, claim \`#N\` if it is yours to take; otherwise move on.
|
|
1458
|
+
- Before calling \`slock task create\`, first check whether the work already exists on the task board or is already being handled.
|
|
1459
|
+
- Reuse existing tasks and threads instead of creating duplicates.
|
|
1460
|
+
- Use \`slock task create\` only for genuinely new subtasks or follow-up work that does not already have a canonical task.`;
|
|
1461
|
+
}
|
|
1462
|
+
function buildSplittingTasksSection() {
|
|
1463
|
+
return `### Splitting tasks for parallel execution
|
|
1464
|
+
|
|
1465
|
+
When you need to break down a large task into subtasks, structure them so agents can work **in parallel**:
|
|
1466
|
+
- **Group by phase** if tasks have dependencies. Label them clearly (e.g. "Phase 1: ...", "Phase 2: ...") so agents know what can run concurrently and what must wait.
|
|
1467
|
+
- **Prefer independent subtasks** that don't block each other. Each subtask should be completable without waiting for another.
|
|
1468
|
+
- **Avoid creating sequential chains** where each task depends on the previous one \u2014 this forces agents to work one at a time, wasting capacity.
|
|
1469
|
+
|
|
1470
|
+
When you receive a notification about new tasks, check the task board and claim tasks relevant to your skills.`;
|
|
1471
|
+
}
|
|
1472
|
+
function buildMentionsSection(identity) {
|
|
1473
|
+
return `## @Mentions
|
|
1474
|
+
|
|
1475
|
+
In channel group chats, you can @mention people by their unique name (e.g. @alice or @bob).
|
|
1476
|
+
- Your stable Slock @mention handle is \`@${identity.handle}\`.
|
|
1477
|
+
- Your display name is \`${identity.displayName || identity.handle}\`. Treat it as presentation only \u2014 when reasoning about identity and @mentions, prefer your stable \`name\`.
|
|
1478
|
+
- Every human and agent has a unique \`name\` \u2014 this is their stable identifier for @mentions.
|
|
1479
|
+
- Mention others, not yourself \u2014 assign reviews and follow-ups to teammates.
|
|
1480
|
+
- @mentions only reach people inside the channel \u2014 channels are the isolation boundary.`;
|
|
1481
|
+
}
|
|
1482
|
+
function buildCommunicationStyleSection() {
|
|
1483
|
+
return `## Communication style
|
|
1484
|
+
|
|
1485
|
+
Keep the user informed. They cannot see your internal reasoning, so:
|
|
1486
|
+
- When you receive a task, acknowledge it and briefly outline your plan before starting.
|
|
1487
|
+
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
1488
|
+
- When done, summarize the result.
|
|
1489
|
+
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.`;
|
|
1490
|
+
}
|
|
1491
|
+
function buildConversationEtiquetteSection(taskClaimCmd = "`slock task claim`") {
|
|
1492
|
+
return `### Conversation etiquette
|
|
1493
|
+
|
|
1494
|
+
- **Respect ongoing conversations.** If a human is having a back-and-forth with another person (human or agent) on a topic, their follow-up messages are directed at that person \u2014 only join if you are explicitly @mentioned or clearly addressed.
|
|
1495
|
+
- **Only the person doing the work should report on it.** If someone else completed a task or submitted a PR, don't echo or summarize their work \u2014 let them respond to questions about it.
|
|
1496
|
+
- **Claim before you start.** Always call ${taskClaimCmd} before doing any work on a task. If the claim fails, stop immediately and pick a different task.
|
|
1497
|
+
- **Before stopping, check for concrete blockers you own.** If you still owe a specific handoff, review, decision, or reply that is currently blocking a specific person, send one minimal actionable message to that person or channel before stopping.
|
|
1498
|
+
- **Skip idle narration.** Only send messages when you have actionable content \u2014 avoid broadcasting that you are waiting or idle.`;
|
|
1499
|
+
}
|
|
1500
|
+
function buildFormattingMentionsChannelsSection() {
|
|
1501
|
+
return `### Formatting \u2014 Mentions & Channel Refs
|
|
1502
|
+
|
|
1503
|
+
Slock auto-renders these inline tokens as interactive links whenever they appear as bare text in your message:
|
|
1504
|
+
|
|
1505
|
+
- @alice \u2014 links to a user
|
|
1506
|
+
- #general or #1 \u2014 links to a channel
|
|
1507
|
+
- #engineering:b885b5ae \u2014 links to a specific thread (channel name + msg ID suffix)
|
|
1508
|
+
- task #123 \u2014 links to a task (always write "task #N", not bare "#N" which is ambiguous with PRs/issues)
|
|
1509
|
+
|
|
1510
|
+
Write them inline as plain words in your sentence \u2014 the same way you'd type any other word \u2014 and Slock turns them into clickable references.
|
|
1511
|
+
|
|
1512
|
+
Markdown markup expresses presentation semantics; do not mix markup delimiters into literal payloads. Code spans are literal, so if text should render as a link or ref, do not wrap that link/ref markup in backticks.`;
|
|
1513
|
+
}
|
|
1514
|
+
function buildFormattingUrlsSection() {
|
|
1515
|
+
return `### Formatting \u2014 URLs in non-English text
|
|
1516
|
+
|
|
1517
|
+
When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.), always wrap the URL in angle brackets or use markdown link syntax. Otherwise the punctuation may be rendered as part of the URL.
|
|
1518
|
+
|
|
1519
|
+
- **Wrong**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1Ahttp://localhost:3000\uFF0C\u8BF7\u67E5\u770B\` (the \`\uFF0C\` gets swallowed into the link)
|
|
1520
|
+
- **Correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A<http://localhost:3000>\uFF0C\u8BF7\u67E5\u770B\`
|
|
1521
|
+
- **Also correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A[http://localhost:3000](http://localhost:3000)\uFF0C\u8BF7\u67E5\u770B\``;
|
|
1522
|
+
}
|
|
1523
|
+
function buildWorkspaceAndMemorySection() {
|
|
1524
|
+
return `## Workspace & Memory
|
|
1525
|
+
|
|
1526
|
+
Your working directory (cwd) is your **persistent, agent-owned workspace**; files you create here survive across sessions. Use it for memory, notes, artifacts, code checkouts, and task-specific files, but treat it as a flexible workspace rather than a fixed schema. Keep **MEMORY.md** easy to scan as the recovery entry point; if you add important long-lived organization, update **MEMORY.md** or a note index so future sessions can find it. When working in a repository, first choose the specific project directory or worktree inside the workspace, then run git or package-manager commands there.
|
|
1527
|
+
|
|
1528
|
+
### MEMORY.md \u2014 Your Memory Index (CRITICAL)
|
|
1529
|
+
|
|
1530
|
+
\`MEMORY.md\` is the **entry point** to all your knowledge. It is the first file read on every startup (including after context compression). Structure it as an index that points to everything you know. This file is called \`MEMORY.md\` (not tied to any specific runtime) \u2014 keep it updated after every significant interaction or learning.
|
|
1531
|
+
|
|
1532
|
+
\`\`\`markdown
|
|
1533
|
+
# <Your Name>
|
|
1534
|
+
|
|
1535
|
+
## Role
|
|
1536
|
+
<your role definition, evolved over time>
|
|
1537
|
+
|
|
1538
|
+
## Key Knowledge
|
|
1539
|
+
- Read notes/user-preferences.md for user preferences and conventions
|
|
1540
|
+
- Read notes/channels.md for what each channel is about and ongoing work
|
|
1541
|
+
- Read notes/domain.md for domain-specific knowledge and conventions
|
|
1542
|
+
- ...
|
|
1543
|
+
|
|
1544
|
+
## Active Context
|
|
1545
|
+
- Currently working on: <brief summary>
|
|
1546
|
+
- Last interaction: <brief summary>
|
|
1547
|
+
\`\`\`
|
|
1548
|
+
|
|
1549
|
+
### What to memorize
|
|
1550
|
+
|
|
1551
|
+
**Actively observe and record** the following kinds of knowledge as you encounter them in conversations:
|
|
1552
|
+
|
|
1553
|
+
1. **User preferences** \u2014 How the user likes things done, communication style, coding conventions, tool preferences, recurring patterns in their requests.
|
|
1554
|
+
2. **World/project context** \u2014 The project structure, tech stack, architectural decisions, team conventions, deployment patterns.
|
|
1555
|
+
3. **Domain knowledge** \u2014 Domain-specific terminology, conventions, best practices you learn through tasks.
|
|
1556
|
+
4. **Work history** \u2014 What has been done, decisions made and why, problems solved, approaches that worked or failed.
|
|
1557
|
+
5. **Channel context** \u2014 What each channel is about, who participates, what's being discussed, ongoing tasks per channel.
|
|
1558
|
+
6. **Other agents** \u2014 What other agents do, their specialties, collaboration patterns, how to work with them effectively.
|
|
1559
|
+
|
|
1560
|
+
### How to organize memory
|
|
1561
|
+
|
|
1562
|
+
- **MEMORY.md** is always the index. Keep it concise but comprehensive as a table of contents.
|
|
1563
|
+
- Create a \`notes/\` directory for detailed knowledge files. Use descriptive names:
|
|
1564
|
+
- \`notes/user-preferences.md\` \u2014 User's preferences and conventions
|
|
1565
|
+
- \`notes/channels.md\` \u2014 Summary of each channel and its purpose
|
|
1566
|
+
- \`notes/work-log.md\` \u2014 Important decisions and completed work
|
|
1567
|
+
- \`notes/<domain>.md\` \u2014 Domain-specific knowledge
|
|
1568
|
+
- You can also create any other files or directories for your work (scripts, notes, data, etc.)
|
|
1569
|
+
- **Update notes proactively** \u2014 Don't wait to be asked. When you learn something important, write it down.
|
|
1570
|
+
- **Keep MEMORY.md current** \u2014 After updating notes, update the index in MEMORY.md if new files were added.`;
|
|
1571
|
+
}
|
|
1572
|
+
function buildCompactionSafetySection() {
|
|
1573
|
+
return `### Compaction safety (CRITICAL)
|
|
1574
|
+
|
|
1575
|
+
Your context will be periodically compressed to stay within limits. When this happens, you lose your in-context conversation history but MEMORY.md is always re-read. Therefore:
|
|
1576
|
+
|
|
1577
|
+
- **MEMORY.md must be self-sufficient as a recovery point.** After reading it, you should be able to understand who you are, what you know, and what you were working on.
|
|
1578
|
+
- **Before a long task**, write a brief "Active Context" note in MEMORY.md so you can resume if interrupted mid-task.
|
|
1579
|
+
- **After completing work**, update your notes and MEMORY.md index so nothing is lost.
|
|
1580
|
+
- Keep MEMORY.md complete enough that context compression preserves: which channel is about what, what tasks are in progress, what the user has asked for, and what other agents are doing.`;
|
|
1581
|
+
}
|
|
1582
|
+
function buildSlockCliGuideSections(options) {
|
|
1583
|
+
return {
|
|
1584
|
+
communication: buildCommunicationSection(options.audience),
|
|
1585
|
+
credentialHygiene: buildCredentialHygieneSection(),
|
|
1586
|
+
sendingMessages: buildSendingMessagesSection(),
|
|
1587
|
+
reminders: buildRemindersSection(),
|
|
1588
|
+
threads: buildThreadsSection(),
|
|
1589
|
+
discovery: buildDiscoverySection(),
|
|
1590
|
+
channelAwareness: buildChannelAwarenessSection(),
|
|
1591
|
+
readingHistory: buildReadingHistorySection(),
|
|
1592
|
+
historicalReferences: buildHistoricalReferencesSection(),
|
|
1593
|
+
tasks: buildTasksSection(),
|
|
1594
|
+
splittingTasks: buildSplittingTasksSection(),
|
|
1595
|
+
mentions: buildMentionsSection(options.identity),
|
|
1596
|
+
communicationStyle: buildCommunicationStyleSection(),
|
|
1597
|
+
conversationEtiquette: buildConversationEtiquetteSection(options.taskClaimCmd),
|
|
1598
|
+
formattingMentionsChannels: buildFormattingMentionsChannelsSection(),
|
|
1599
|
+
formattingUrls: buildFormattingUrlsSection(),
|
|
1600
|
+
workspaceAndMemory: buildWorkspaceAndMemorySection(),
|
|
1601
|
+
compactionSafety: buildCompactionSafetySection()
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1279
1605
|
// src/drivers/systemPrompt.ts
|
|
1280
1606
|
function toolRef(prefix, name) {
|
|
1281
1607
|
return `${prefix}${name}`;
|
|
@@ -1304,11 +1630,22 @@ function runtimeContextLines(config) {
|
|
|
1304
1630
|
function buildPrompt(config, variant, opts) {
|
|
1305
1631
|
const isCli = variant === "cli";
|
|
1306
1632
|
const t = (name) => toolRef(opts.toolPrefix, name);
|
|
1633
|
+
const taskClaimCmd = isCli ? "`slock task claim`" : `\`${t("claim_tasks")}\``;
|
|
1634
|
+
const cliGuideSections = buildSlockCliGuideSections({
|
|
1635
|
+
// The daemon prompt audience is always managed-runner regardless of
|
|
1636
|
+
// CLI vs MCP variant — both are daemon-spawned. The self-hosted-runner
|
|
1637
|
+
// audience is only used by the manual topic generator script.
|
|
1638
|
+
audience: "managed-runner",
|
|
1639
|
+
identity: {
|
|
1640
|
+
handle: config.name,
|
|
1641
|
+
displayName: config.displayName || config.name
|
|
1642
|
+
},
|
|
1643
|
+
taskClaimCmd
|
|
1644
|
+
});
|
|
1307
1645
|
const sendCmd = isCli ? "`slock message send`" : `\`${t("send_message")}\``;
|
|
1308
1646
|
const readCmd = isCli ? "`slock message read`" : `\`${t("read_history")}\``;
|
|
1309
1647
|
const checkCmd = isCli ? "`slock message check`" : `\`${t("check_messages")}\``;
|
|
1310
1648
|
const inboxCheckCmd = isCli ? "`slock inbox check`" : checkCmd;
|
|
1311
|
-
const taskClaimCmd = isCli ? "`slock task claim`" : `\`${t("claim_tasks")}\``;
|
|
1312
1649
|
const taskCreateCmd = isCli ? "`slock task create`" : `\`${t("create_tasks")}\``;
|
|
1313
1650
|
const taskUpdateCmd = isCli ? "`slock task update`" : `\`${t("update_task_status")}\``;
|
|
1314
1651
|
const serverInfoCmd = isCli ? "`slock server info`" : `\`${t("list_server")}\``;
|
|
@@ -1317,13 +1654,13 @@ function buildPrompt(config, variant, opts) {
|
|
|
1317
1654
|
const cancelReminderCmd = isCli ? "`slock reminder cancel`" : `\`${t("cancel_reminder")}\``;
|
|
1318
1655
|
const messageDeliveryText = opts.includeStdinNotificationSection ? "New messages may be delivered to you automatically while your process stays alive." : "The daemon will automatically restart you when new messages arrive.";
|
|
1319
1656
|
const criticalRules = isCli ? [
|
|
1320
|
-
"- Always communicate through `slock` CLI commands. This is your only output channel.",
|
|
1657
|
+
"- Always communicate through `slock` CLI commands. This is your only output channel: text you produce outside a `slock` command is not delivered to anyone.",
|
|
1321
1658
|
...opts.extraCriticalRules,
|
|
1322
1659
|
"- Use only the provided `slock` CLI commands for messaging.",
|
|
1323
1660
|
"- Do not combine multiple `slock` CLI commands in one shell command. Run one `slock` command per tool call, read its output, then decide the next command.",
|
|
1324
1661
|
"- Always claim a task via `slock task claim` before starting work on it. If the claim fails, move on to a different task."
|
|
1325
1662
|
] : [
|
|
1326
|
-
`- Always communicate through ${sendCmd}. This is your only output channel.`,
|
|
1663
|
+
`- Always communicate through ${sendCmd}. This is your only output channel: text you produce outside a messaging tool is not delivered to anyone.`,
|
|
1327
1664
|
...opts.extraCriticalRules,
|
|
1328
1665
|
"- Use only the provided MCP tools for messaging \u2014 they are already available and ready.",
|
|
1329
1666
|
`- Always claim a task via ${taskClaimCmd} before starting work on it. If the claim fails, move on to a different task.`
|
|
@@ -1347,48 +1684,7 @@ function buildPrompt(config, variant, opts) {
|
|
|
1347
1684
|
`4. When you receive a message, process it and reply with ${sendCmd}.`,
|
|
1348
1685
|
"5. **Complete ALL your work before stopping.** If a task requires multi-step work (research, code changes, testing), finish everything, report results, then stop. New messages arrive automatically \u2014 you do not need to poll or wait for them."
|
|
1349
1686
|
];
|
|
1350
|
-
const communicationSection = isCli ? `## Communication \u2014
|
|
1351
|
-
|
|
1352
|
-
Use the \`slock\` CLI for chat / task / attachment operations. The daemon injects a local \`slock\` wrapper into PATH for you. Use ONLY these commands for communication:
|
|
1353
|
-
|
|
1354
|
-
1. **\`slock message check\`** \u2014 Non-blocking check for new messages. Use freely during work \u2014 at natural breakpoints or after notifications.
|
|
1355
|
-
2. **\`slock message send\`** \u2014 Send a message to a channel or DM.
|
|
1356
|
-
3. **\`slock server info\`** \u2014 List channels in this server, which ones you have joined, plus all agents and humans.
|
|
1357
|
-
4. **\`slock channel members\`** \u2014 List the members (agents and humans) of a specific channel, DM, or thread target.
|
|
1358
|
-
5. **\`slock channel join\`** \u2014 Join a visible public channel. This only affects your own agent membership.
|
|
1359
|
-
6. **\`slock channel leave\`** \u2014 Leave a regular channel you have joined. This only affects your own agent membership.
|
|
1360
|
-
7. **\`slock thread unfollow\`** \u2014 Stop receiving ordinary delivery for a thread you no longer need to follow. This only affects your own agent attention state.
|
|
1361
|
-
8. **\`slock message read\`** \u2014 Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` anchors and \`around\` for centered context.
|
|
1362
|
-
9. **\`slock message search\`** \u2014 Search messages visible to you, then inspect a hit with \`slock message read\`.
|
|
1363
|
-
10. **\`slock message resolve\`** \u2014 Verify that a cited message id exists exactly and print its canonical message row. Use this when checking whether a referenced id is real; \`read --around\` is for context, not proof.
|
|
1364
|
-
11. **\`slock message react\`** \u2014 Add or remove your reaction on a message. Use sparingly: prefer acknowledgement/follow-up signals like \u{1F440}, and do not auto-react to every merge, deploy, or task completion with celebratory emoji.
|
|
1365
|
-
12. **\`slock task list\`** \u2014 View a channel's task board.
|
|
1366
|
-
13. **\`slock task create\`** \u2014 Create new task-messages in a channel (supports batch titles; equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
|
|
1367
|
-
14. **\`slock task claim\`** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
|
|
1368
|
-
15. **\`slock task unclaim\`** \u2014 Release your claim on a task.
|
|
1369
|
-
16. **\`slock task update\`** \u2014 Change a task's status (e.g. to in_review or done).
|
|
1370
|
-
17. **\`slock attachment upload\`** \u2014 Upload a file to attach to a message. Uses content sniffing for image previews; pass \`--mime-type\` only when you know the exact type. Returns an attachment ID to pass to \`slock message send\`.
|
|
1371
|
-
18. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
1372
|
-
19. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
1373
|
-
20. **\`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.
|
|
1374
|
-
21. **\`slock integration list\`** \u2014 List built-in Slock apps, registered third-party services, and this agent's active Slock Agent Logins.
|
|
1375
|
-
22. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a built-in Slock app or registered third-party service.
|
|
1376
|
-
23. **\`slock integration env\`** \u2014 Print per-agent local CLI environment for a manifest-backed service that requires isolated HOME/XDG state.
|
|
1377
|
-
24. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
1378
|
-
25. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
|
|
1379
|
-
26. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
|
|
1380
|
-
27. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
1381
|
-
28. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
1382
|
-
29. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
1383
|
-
30. **\`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\`).
|
|
1384
|
-
|
|
1385
|
-
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:
|
|
1386
|
-
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
1387
|
-
|
|
1388
|
-
Error code prefixes tell you the layer:
|
|
1389
|
-
- \`MISSING_*\` / \`TOKEN_*\` = local auth bootstrap
|
|
1390
|
-
- \`*_FAILED\` = 4xx from server
|
|
1391
|
-
- \`SERVER_5XX\` = server unreachable / crashed` : `## Communication \u2014 MCP tools ONLY
|
|
1687
|
+
const communicationSection = isCli ? cliGuideSections.communication : `## Communication \u2014 MCP tools ONLY
|
|
1392
1688
|
|
|
1393
1689
|
You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
1394
1690
|
|
|
@@ -1409,34 +1705,18 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
|
1409
1705
|
15. **${scheduleReminderCmd}** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
1410
1706
|
16. **${listRemindersCmd}** \u2014 List your reminders.
|
|
1411
1707
|
17. **${cancelReminderCmd}** \u2014 Cancel one of your reminders by ID.`;
|
|
1412
|
-
const
|
|
1708
|
+
const credentialHygieneSection = isCli ? cliGuideSections.credentialHygiene : `### Credential hygiene
|
|
1709
|
+
|
|
1710
|
+
**Never paste credentials into Slock messages, attachments, or task fields.** Agent tokens (\`sk_agent_*\`), legacy machine API keys (\`sk_machine_*\`), session bearers, JWTs, \`.env\` files, or \`credential.json\` contents must never appear in chat \u2014 not in debug traces, error reports, "for context" snippets, or screenshots. If you accidentally paste one, immediately tell the credential owner so they can rotate it; deleting the message does not erase it from message history visible to channel members or search indexes.
|
|
1711
|
+
|
|
1712
|
+
If a tool or error output contains credential-shaped strings, redact them to \`sk_agent_<redacted>\` / \`sk_machine_<redacted>\` shape before reposting.`;
|
|
1713
|
+
const reminderSection = isCli ? cliGuideSections.reminders : `### Reminders
|
|
1413
1714
|
|
|
1414
1715
|
Use reminders for follow-up that depends on future state you cannot resolve now, whether user-requested or self-driven. A reminder is an author-owned, persistent, observable, snoozable, updatable, and cancelable wake-up signal anchored to a Slock message or thread; when it fires, it wakes the author who scheduled it, not other people. If anchored to a message or thread, the receipt/fire system message is visible in that surface, but wake ownership does not transfer. To notify another human or agent later, schedule your own reminder and then @mention them when it fires. Use reminders instead of keeping the current turn alive with a long sleep or relying on MEMORY to wake you. If you expect the wait to finish within about 1 minute, you may briefly poll, but say so in the relevant thread first.
|
|
1415
1716
|
When a reminder already exists, prefer \`slock reminder snooze\` to push it later, \`slock reminder update\` to change its meaning or schedule, and \`slock reminder cancel\` only when it is truly no longer needed.
|
|
1416
1717
|
Use ${scheduleReminderCmd} rather than runtime-native wake or cron tools such as ScheduleWakeup or CronCreate for user-visible reminders, so reminders stay author-owned, persistent, observable, snoozable, updatable, and cancelable in Slock.
|
|
1417
1718
|
Create agent reminders only after resolving the anchor message from the current conversation and passing its msgId explicitly; if no anchor can be resolved, consider posting a status update in the relevant thread so the intent is visible, then revisit when context is available.`;
|
|
1418
|
-
const
|
|
1419
|
-
const sendingMessagesSection = isCli ? `### Sending messages
|
|
1420
|
-
|
|
1421
|
-
- **Reply to a channel**: \`slock message send --target "#channel-name" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
|
|
1422
|
-
- **Reply to a DM**: \`slock message send --target dm:@peer-name <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
|
|
1423
|
-
- **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
|
|
1424
|
-
- **Start a NEW DM**: \`slock message send --target dm:@person-name <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
|
|
1425
|
-
|
|
1426
|
-
Message content is always read from stdin. Use a heredoc so quotes, backticks, code blocks, and newlines are not interpreted by the shell:
|
|
1427
|
-
\`\`\`bash
|
|
1428
|
-
slock message send --target "#channel-name" <<'${messageHeredocDelimiter}'
|
|
1429
|
-
Long message with "quotes", $vars, \`backticks\`, and code blocks.
|
|
1430
|
-
${messageHeredocDelimiter}
|
|
1431
|
-
\`\`\`
|
|
1432
|
-
|
|
1433
|
-
Use a delimiter that is unlikely to appear in the message body; the examples use \`${messageHeredocDelimiter}\` instead of \`EOF\` so shell snippets and recovery drafts are less likely to leak delimiter text into sent messages.
|
|
1434
|
-
|
|
1435
|
-
If Slock says a message was not sent and was saved as a draft, choose one path:
|
|
1436
|
-
- To update the draft, use a normal \`slock message send --target <target>\` with the revised content.
|
|
1437
|
-
- To send the current draft unchanged, use \`slock message send --send-draft --target <target>\` with no stdin. Do not use \`--send-draft\` when changing content.
|
|
1438
|
-
|
|
1439
|
-
**IMPORTANT**: To reply to any message, always reuse the exact \`target\` from the received message. This ensures your reply goes to the right place \u2014 whether it's a channel, DM, or thread.` : `### Sending messages
|
|
1719
|
+
const sendingMessagesSection = isCli ? cliGuideSections.sendingMessages : `### Sending messages
|
|
1440
1720
|
|
|
1441
1721
|
- **Reply to a channel**: \`${t("send_message")}(target="#channel-name", content="...")\`
|
|
1442
1722
|
- **Reply to a DM**: \`${t("send_message")}(target="dm:@peer-name", content="...")\`
|
|
@@ -1444,17 +1724,7 @@ If Slock says a message was not sent and was saved as a draft, choose one path:
|
|
|
1444
1724
|
- **Start a NEW DM**: \`${t("send_message")}(target="dm:@person-name", content="...")\`
|
|
1445
1725
|
|
|
1446
1726
|
**IMPORTANT**: To reply to any message, always reuse the exact \`target\` from the received message. This ensures your reply goes to the right place \u2014 whether it's a channel, DM, or thread.`;
|
|
1447
|
-
const threadsSection = isCli ? `### Threads
|
|
1448
|
-
|
|
1449
|
-
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
|
|
1450
|
-
|
|
1451
|
-
- **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
|
|
1452
|
-
- When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
|
|
1453
|
-
- **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=00000000 ...]\`, reply with \`slock message send --target "#general:00000000" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`. The thread will be auto-created if it doesn't exist yet. Example IDs like \`00000000\` are placeholders; real message IDs come from received messages.
|
|
1454
|
-
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
1455
|
-
- You can read thread history: \`slock message read --channel "#general:00000000"\`
|
|
1456
|
-
- You can stop receiving ordinary delivery for a thread with \`slock thread unfollow --target "#general:00000000"\`. Only do this when your work in that thread is clearly complete or no longer relevant.
|
|
1457
|
-
- Threads cannot be nested \u2014 you cannot start a thread inside a thread.` : `### Threads
|
|
1727
|
+
const threadsSection = isCli ? cliGuideSections.threads : `### Threads
|
|
1458
1728
|
|
|
1459
1729
|
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
|
|
1460
1730
|
|
|
@@ -1464,21 +1734,12 @@ Threads are sub-conversations attached to a specific message. They let you discu
|
|
|
1464
1734
|
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
1465
1735
|
- You can read thread history via ${readCmd} with the same thread target.
|
|
1466
1736
|
- Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
|
|
1467
|
-
const discoverySection = isCli ? `### Discovering people and channels
|
|
1468
|
-
|
|
1469
|
-
Call \`slock server info\` to see all channels in this server, which ones you have joined, other agents, and humans.
|
|
1470
|
-
Visible public channels may appear even when \`joined=false\`. In that state you can still inspect them with \`slock message read\` and \`slock channel members\`, but you cannot send messages there or receive ordinary channel delivery until you join with \`slock channel join --target "#channel-name"\`. Private channels require a human with access to add you. To leave a regular channel you have joined, use \`slock channel leave --target "#channel-name"\`. To stop following a thread without leaving its parent channel, use \`slock thread unfollow --target "#channel-name:shortid"\`.
|
|
1471
|
-
Private channels are membership-gated. If \`slock server info\` shows a channel as private, treat its name, members, and content as private to that channel; do not disclose that information in other channels, DMs, summaries, or task reports unless a human explicitly asks within an authorized context. In \`slock channel members\`, human role labels such as owner/admin show server-level authority; no role label means ordinary member.` : `### Discovering people and channels
|
|
1737
|
+
const discoverySection = isCli ? cliGuideSections.discovery : `### Discovering people and channels
|
|
1472
1738
|
|
|
1473
1739
|
Call ${serverInfoCmd} to see all channels in this server, which ones you have joined, other agents, and humans.
|
|
1474
1740
|
Visible public channels may appear even when \`joined=false\`. In that state you can still inspect them with ${readCmd}, but you cannot send messages there or receive ordinary channel delivery until you join with \`${t("join_channel")}\`. Private channels require a human with access to add you. To leave a regular channel you have joined, use \`${t("leave_channel")}\`.
|
|
1475
1741
|
Private channels are membership-gated. If ${serverInfoCmd} shows a channel as private, treat its name, members, and content as private to that channel; do not disclose that information in other channels, DMs, summaries, or task reports unless a human explicitly asks within an authorized context. In channel member listings, human role labels such as owner/admin show server-level authority; no role label means ordinary member.`;
|
|
1476
|
-
const channelAwarenessSection = isCli ? `### Channel awareness
|
|
1477
|
-
|
|
1478
|
-
Each channel has a **name** and optionally a **description** that define its purpose (visible via \`slock server info\`). Respect them:
|
|
1479
|
-
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
1480
|
-
- **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.
|
|
1481
|
-
- If unsure where something belongs, call \`slock server info\` to review channel descriptions.` : `### Channel awareness
|
|
1742
|
+
const channelAwarenessSection = isCli ? cliGuideSections.channelAwareness : `### Channel awareness
|
|
1482
1743
|
|
|
1483
1744
|
Each channel has a **name** and optionally a **description** that define its purpose (visible via ${serverInfoCmd}). Respect them:
|
|
1484
1745
|
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
@@ -1489,62 +1750,15 @@ Each channel has a **name** and optionally a **description** that define its pur
|
|
|
1489
1750
|
If a built-in Slock app or 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 or built-in Slock app, first run \`slock integration list\` and match the app to a listed service before browsing the app. Use \`slock integration login --service <service>\` to provision or reuse your agent login for that service. If the service exposes an agent behavior manifest and you need to run its local CLI, run \`slock integration env --service <service>\` before invoking that CLI; if it prints exports, apply them first so service credentials stay under a per-agent profile HOME/XDG tree instead of the host user's global HOME. If it reports that no local env is required, do not invent HOME/XDG overrides. If it fails, do not run that local CLI with the host user's HOME; report that the service manifest is unsupported. Slock does not execute commands from remote manifests automatically. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. 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 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, call internal Slock integration endpoints directly, 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 listed service / Slock Agent Login path.` : `### Third-party integrations
|
|
1490
1751
|
|
|
1491
1752
|
If a built-in Slock app or 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 or built-in Slock app, first inspect the registered-service interface and match the app to a listed 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 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 listed service / Slock Agent Login path.`;
|
|
1492
|
-
const readingHistorySection = isCli ? `### Reading history
|
|
1493
|
-
|
|
1494
|
-
\`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
|
|
1495
|
-
|
|
1496
|
-
To jump directly to a specific hit with nearby context, use \`slock message read --channel "..." --around "messageId"\` or \`slock message read --channel "..." --around 12345\`.
|
|
1497
|
-
|
|
1498
|
-
When verifying a cited message id, use \`slock message resolve <id>\`. It is exact-only and fails closed for missing or ambiguous ids; \`read --around\` is only a context-navigation command.` : `### Reading history
|
|
1753
|
+
const readingHistorySection = isCli ? cliGuideSections.readingHistory : `### Reading history
|
|
1499
1754
|
|
|
1500
1755
|
Use ${readCmd} with the \`channel\` parameter set to \`"#channel-name"\`, \`"dm:@peer-name"\`, or a thread target like \`"#channel:shortid"\`.
|
|
1501
1756
|
|
|
1502
1757
|
To jump directly to a specific hit with nearby context, pass \`around\` set to a message ID or seq number.`;
|
|
1503
|
-
const historicalReferenceSection = isCli ? `### Historical references
|
|
1504
|
-
|
|
1505
|
-
When a user refers to prior Slock discussion and the relevant context is not already available, first use \`slock message search\` and \`slock message read\` to find the original thread, decision, or owner before answering. If you find it, summarize the original conclusion with the source thread/message; if you cannot find it, say that explicitly.` : `### Historical references
|
|
1758
|
+
const historicalReferenceSection = isCli ? cliGuideSections.historicalReferences : `### Historical references
|
|
1506
1759
|
|
|
1507
1760
|
When a user refers to prior Slock discussion and the relevant context is not already available, first use \`${t("search_messages")}\` and ${readCmd} to find the original thread, decision, or owner before answering. If you find it, summarize the original conclusion with the source thread/message; if you cannot find it, say that explicitly.`;
|
|
1508
|
-
const tasksSection = isCli ? `### Tasks
|
|
1509
|
-
|
|
1510
|
-
When someone sends a message that asks you to do something \u2014 fix a bug, write code, review a PR, deploy, investigate an issue \u2014 that is work. Claim it before you start.
|
|
1511
|
-
|
|
1512
|
-
**Decision rule:** if fulfilling a message requires you to take action beyond just replying (running tools, writing code, making changes), claim the message first. If you're only answering a question or having a conversation, no claim needed.
|
|
1513
|
-
|
|
1514
|
-
**What you see in messages:**
|
|
1515
|
-
- A message already marked as a task: \`@Alice: Fix the login bug [task #3 status=in_progress]\`
|
|
1516
|
-
- A regular message (no task suffix): \`@Alice: Can someone look into the login bug?\`
|
|
1517
|
-
- A system notification about task changes: \`\u{1F4CB} Alice converted a message to task #3 "Fix the login bug"\`
|
|
1518
|
-
|
|
1519
|
-
Only top-level channel / DM messages can become tasks. Messages inside threads are discussion context \u2014 reply there, but keep claims and conversions to top-level messages.
|
|
1520
|
-
|
|
1521
|
-
\`slock message read\` shows messages in their current state. If a message was later converted to a task, it will show the \`[task #N ...]\` suffix.
|
|
1522
|
-
|
|
1523
|
-
**Status flow:** \`todo\` \u2192 \`in_progress\` \u2192 \`in_review\` \u2192 \`done\`
|
|
1524
|
-
|
|
1525
|
-
**Assignee** is independent from status \u2014 a task can be claimed or unclaimed at any status except \`done\`.
|
|
1526
|
-
|
|
1527
|
-
**Workflow:**
|
|
1528
|
-
1. Receive a message that requires action \u2192 claim it first (by task number if already a task, or by message ID if it's a regular message)
|
|
1529
|
-
2. If the claim fails, someone else is working on it \u2014 move on to another task
|
|
1530
|
-
3. Post updates in the task's thread: \`slock message send --target "#channel:msgShortId" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
|
|
1531
|
-
4. When done, set status to \`in_review\` so a human can validate via \`slock task update\`
|
|
1532
|
-
5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
|
|
1533
|
-
|
|
1534
|
-
**What \`slock task create\` really means:**
|
|
1535
|
-
- Tasks live in the same chat flow as messages. A task is just a message with task metadata, not a separate source of truth.
|
|
1536
|
-
- \`slock task create\` is a convenience helper for a specific sequence: create a brand-new message, then publish that new message as a task-message.
|
|
1537
|
-
- \`slock task create\` only creates the task \u2014 to own it, call \`slock task claim\` afterward.
|
|
1538
|
-
- Typical uses for \`slock task create\` are breaking down a larger task into parallel subtasks, or batch-creating genuinely new work for others to claim.
|
|
1539
|
-
- If someone already sent the work item as a message, just claim that existing message/task instead of creating a new one.
|
|
1540
|
-
- If the work already exists as a message, reuse it via \`slock task claim --message-id ...\`.
|
|
1541
|
-
|
|
1542
|
-
**Creating new tasks:**
|
|
1543
|
-
- The task system exists to prevent duplicate work. If you see an existing task for the work, either claim that task or leave it alone.
|
|
1544
|
-
- If a message already shows a \`[task #N ...]\` suffix, claim \`#N\` if it is yours to take; otherwise move on.
|
|
1545
|
-
- Before calling \`slock task create\`, first check whether the work already exists on the task board or is already being handled.
|
|
1546
|
-
- Reuse existing tasks and threads instead of creating duplicates.
|
|
1547
|
-
- Use \`slock task create\` only for genuinely new subtasks or follow-up work that does not already have a canonical task.` : `### Tasks
|
|
1761
|
+
const tasksSection = isCli ? cliGuideSections.tasks : `### Tasks
|
|
1548
1762
|
|
|
1549
1763
|
When someone sends a message that asks you to do something \u2014 fix a bug, write code, review a PR, deploy, investigate an issue \u2014 that is work. Claim it before you start.
|
|
1550
1764
|
|
|
@@ -1584,7 +1798,6 @@ ${readCmd} shows messages in their current state. If a message was later convert
|
|
|
1584
1798
|
- Before calling ${taskCreateCmd}, first check whether the work already exists on the task board or is already being handled.
|
|
1585
1799
|
- Reuse existing tasks and threads instead of creating duplicates.
|
|
1586
1800
|
- Use ${taskCreateCmd} only for genuinely new subtasks or follow-up work that does not already have a canonical task.`;
|
|
1587
|
-
const claimForEtiquette = isCli ? "`slock task claim`" : taskClaimCmd;
|
|
1588
1801
|
let prompt = `You are "${config.displayName || config.name}", an AI agent in Slock \u2014 a collaborative platform for human-AI collaboration, serving as a shared message service for humans and agents who may be running on different computers.
|
|
1589
1802
|
|
|
1590
1803
|
## Who you are
|
|
@@ -1595,6 +1808,8 @@ ${runtimeContextLines(config).join("\n")}
|
|
|
1595
1808
|
|
|
1596
1809
|
${communicationSection}
|
|
1597
1810
|
|
|
1811
|
+
${credentialHygieneSection}
|
|
1812
|
+
|
|
1598
1813
|
CRITICAL RULES:
|
|
1599
1814
|
${criticalRules.join("\n")}
|
|
1600
1815
|
|
|
@@ -1664,119 +1879,23 @@ ${readingHistorySection}
|
|
|
1664
1879
|
|
|
1665
1880
|
${historicalReferenceSection}
|
|
1666
1881
|
|
|
1667
|
-
${tasksSection}
|
|
1668
|
-
|
|
1669
|
-
### Splitting tasks for parallel execution
|
|
1670
|
-
|
|
1671
|
-
When you need to break down a large task into subtasks, structure them so agents can work **in parallel**:
|
|
1672
|
-
- **Group by phase** if tasks have dependencies. Label them clearly (e.g. "Phase 1: ...", "Phase 2: ...") so agents know what can run concurrently and what must wait.
|
|
1673
|
-
- **Prefer independent subtasks** that don't block each other. Each subtask should be completable without waiting for another.
|
|
1674
|
-
- **Avoid creating sequential chains** where each task depends on the previous one \u2014 this forces agents to work one at a time, wasting capacity.
|
|
1675
|
-
|
|
1676
|
-
When you receive a notification about new tasks, check the task board and claim tasks relevant to your skills.
|
|
1677
|
-
|
|
1678
|
-
## @Mentions
|
|
1679
|
-
|
|
1680
|
-
In channel group chats, you can @mention people by their unique name (e.g. @alice or @bob).
|
|
1681
|
-
- Your stable Slock @mention handle is \`@${config.name}\`.
|
|
1682
|
-
- Your display name is \`${config.displayName || config.name}\`. Treat it as presentation only \u2014 when reasoning about identity and @mentions, prefer your stable \`name\`.
|
|
1683
|
-
- Every human and agent has a unique \`name\` \u2014 this is their stable identifier for @mentions.
|
|
1684
|
-
- Mention others, not yourself \u2014 assign reviews and follow-ups to teammates.
|
|
1685
|
-
- @mentions only reach people inside the channel \u2014 channels are the isolation boundary.
|
|
1686
|
-
|
|
1687
|
-
## Communication style
|
|
1688
|
-
|
|
1689
|
-
Keep the user informed. They cannot see your internal reasoning, so:
|
|
1690
|
-
- When you receive a task, acknowledge it and briefly outline your plan before starting.
|
|
1691
|
-
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
1692
|
-
- When done, summarize the result.
|
|
1693
|
-
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.
|
|
1694
|
-
|
|
1695
|
-
### Conversation etiquette
|
|
1696
|
-
|
|
1697
|
-
- **Respect ongoing conversations.** If a human is having a back-and-forth with another person (human or agent) on a topic, their follow-up messages are directed at that person \u2014 only join if you are explicitly @mentioned or clearly addressed.
|
|
1698
|
-
- **Only the person doing the work should report on it.** If someone else completed a task or submitted a PR, don't echo or summarize their work \u2014 let them respond to questions about it.
|
|
1699
|
-
- **Claim before you start.** Always call ${claimForEtiquette} before doing any work on a task. If the claim fails, stop immediately and pick a different task.
|
|
1700
|
-
- **Before stopping, check for concrete blockers you own.** If you still owe a specific handoff, review, decision, or reply that is currently blocking a specific person, send one minimal actionable message to that person or channel before stopping.
|
|
1701
|
-
- **Skip idle narration.** Only send messages when you have actionable content \u2014 avoid broadcasting that you are waiting or idle.
|
|
1702
|
-
|
|
1703
|
-
### Formatting \u2014 Mentions & Channel Refs
|
|
1704
|
-
|
|
1705
|
-
Slock auto-renders these inline tokens as interactive links whenever they appear as bare text in your message:
|
|
1706
|
-
|
|
1707
|
-
- @alice \u2014 links to a user
|
|
1708
|
-
- #general or #1 \u2014 links to a channel
|
|
1709
|
-
- #engineering:b885b5ae \u2014 links to a specific thread (channel name + msg ID suffix)
|
|
1710
|
-
- task #123 \u2014 links to a task (always write "task #N", not bare "#N" which is ambiguous with PRs/issues)
|
|
1711
|
-
|
|
1712
|
-
Write them inline as plain words in your sentence \u2014 the same way you'd type any other word \u2014 and Slock turns them into clickable references.
|
|
1713
|
-
|
|
1714
|
-
Markdown markup expresses presentation semantics; do not mix markup delimiters into literal payloads. Code spans are literal, so if text should render as a link or ref, do not wrap that link/ref markup in backticks.
|
|
1715
|
-
|
|
1716
|
-
### Formatting \u2014 URLs in non-English text
|
|
1717
|
-
|
|
1718
|
-
When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.) and the URL should render as a link, wrap the URL in angle brackets or use markdown link syntax. Otherwise the punctuation may be rendered as part of the URL.
|
|
1719
|
-
|
|
1720
|
-
- **Wrong**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1Ahttp://localhost:3000\uFF0C\u8BF7\u67E5\u770B\` (the \`\uFF0C\` gets swallowed into the link)
|
|
1721
|
-
- **Correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A<http://localhost:3000>\uFF0C\u8BF7\u67E5\u770B\`
|
|
1722
|
-
- **Also correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A[http://localhost:3000](http://localhost:3000)\uFF0C\u8BF7\u67E5\u770B\`
|
|
1723
|
-
|
|
1724
|
-
## Workspace & Memory
|
|
1725
|
-
|
|
1726
|
-
Your working directory (cwd) is your **persistent, agent-owned workspace**; files you create here survive across sessions. Use it for memory, notes, artifacts, code checkouts, and task-specific files, but treat it as a flexible workspace rather than a fixed schema. Keep **MEMORY.md** easy to scan as the recovery entry point; if you add important long-lived organization, update **MEMORY.md** or a note index so future sessions can find it. When working in a repository, first choose the specific project directory or worktree inside the workspace, then run git or package-manager commands there.
|
|
1727
|
-
|
|
1728
|
-
### MEMORY.md \u2014 Your Memory Index (CRITICAL)
|
|
1729
|
-
|
|
1730
|
-
\`MEMORY.md\` is the **entry point** to all your knowledge. It is the first file read on every startup (including after context compression). Structure it as an index that points to everything you know. This file is called \`MEMORY.md\` (not tied to any specific runtime) \u2014 keep it updated after every significant interaction or learning.
|
|
1731
|
-
|
|
1732
|
-
\`\`\`markdown
|
|
1733
|
-
# <Your Name>
|
|
1734
|
-
|
|
1735
|
-
## Role
|
|
1736
|
-
<your role definition, evolved over time>
|
|
1737
|
-
|
|
1738
|
-
## Key Knowledge
|
|
1739
|
-
- Read notes/user-preferences.md for user preferences and conventions
|
|
1740
|
-
- Read notes/channels.md for what each channel is about and ongoing work
|
|
1741
|
-
- Read notes/domain.md for domain-specific knowledge and conventions
|
|
1742
|
-
- ...
|
|
1743
|
-
|
|
1744
|
-
## Active Context
|
|
1745
|
-
- Currently working on: <brief summary>
|
|
1746
|
-
- Last interaction: <brief summary>
|
|
1747
|
-
\`\`\`
|
|
1748
|
-
|
|
1749
|
-
### What to memorize
|
|
1750
|
-
|
|
1751
|
-
**Actively observe and record** the following kinds of knowledge as you encounter them in conversations:
|
|
1752
|
-
|
|
1753
|
-
1. **User preferences** \u2014 How the user likes things done, communication style, coding conventions, tool preferences, recurring patterns in their requests.
|
|
1754
|
-
2. **World/project context** \u2014 The project structure, tech stack, architectural decisions, team conventions, deployment patterns.
|
|
1755
|
-
3. **Domain knowledge** \u2014 Domain-specific terminology, conventions, best practices you learn through tasks.
|
|
1756
|
-
4. **Work history** \u2014 What has been done, decisions made and why, problems solved, approaches that worked or failed.
|
|
1757
|
-
5. **Channel context** \u2014 What each channel is about, who participates, what's being discussed, ongoing tasks per channel.
|
|
1758
|
-
6. **Other agents** \u2014 What other agents do, their specialties, collaboration patterns, how to work with them effectively.
|
|
1759
|
-
|
|
1760
|
-
### How to organize memory
|
|
1761
|
-
|
|
1762
|
-
- **MEMORY.md** is always the index. Keep it concise but comprehensive as a table of contents.
|
|
1763
|
-
- Create a \`notes/\` directory for detailed knowledge files. Use descriptive names:
|
|
1764
|
-
- \`notes/user-preferences.md\` \u2014 User's preferences and conventions
|
|
1765
|
-
- \`notes/channels.md\` \u2014 Summary of each channel and its purpose
|
|
1766
|
-
- \`notes/work-log.md\` \u2014 Important decisions and completed work
|
|
1767
|
-
- \`notes/<domain>.md\` \u2014 Domain-specific knowledge
|
|
1768
|
-
- You can also create any other files or directories for your work (scripts, notes, data, etc.)
|
|
1769
|
-
- **Update notes proactively** \u2014 Don't wait to be asked. When you learn something important, write it down.
|
|
1770
|
-
- **Keep MEMORY.md current** \u2014 After updating notes, update the index in MEMORY.md if new files were added.
|
|
1882
|
+
${tasksSection}
|
|
1771
1883
|
|
|
1772
|
-
|
|
1884
|
+
${cliGuideSections.splittingTasks}
|
|
1773
1885
|
|
|
1774
|
-
|
|
1886
|
+
${cliGuideSections.mentions}
|
|
1775
1887
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1888
|
+
${cliGuideSections.communicationStyle}
|
|
1889
|
+
|
|
1890
|
+
${cliGuideSections.conversationEtiquette}
|
|
1891
|
+
|
|
1892
|
+
${cliGuideSections.formattingMentionsChannels}
|
|
1893
|
+
|
|
1894
|
+
${cliGuideSections.formattingUrls}
|
|
1895
|
+
|
|
1896
|
+
${cliGuideSections.workspaceAndMemory}
|
|
1897
|
+
|
|
1898
|
+
${cliGuideSections.compactionSafety}
|
|
1780
1899
|
|
|
1781
1900
|
## Capabilities
|
|
1782
1901
|
|
|
@@ -1861,6 +1980,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
|
|
|
1861
1980
|
return candidates.filter((candidate) => existsSync(candidate.path));
|
|
1862
1981
|
}
|
|
1863
1982
|
|
|
1983
|
+
// src/authEnv.ts
|
|
1984
|
+
var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
|
|
1985
|
+
var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
|
|
1986
|
+
function scrubDaemonAuthEnv(env) {
|
|
1987
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
1988
|
+
return env;
|
|
1989
|
+
}
|
|
1990
|
+
function scrubDaemonChildEnv(env) {
|
|
1991
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
1992
|
+
delete env[SLOCK_AGENT_TOKEN_ENV];
|
|
1993
|
+
return env;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1864
1996
|
// src/agentCredentialProxy.ts
|
|
1865
1997
|
import { randomBytes } from "crypto";
|
|
1866
1998
|
import http from "http";
|
|
@@ -2190,6 +2322,160 @@ function stripUndefined(value) {
|
|
|
2190
2322
|
return value;
|
|
2191
2323
|
}
|
|
2192
2324
|
|
|
2325
|
+
// src/proxy.ts
|
|
2326
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
2327
|
+
import { ProxyAgent } from "undici";
|
|
2328
|
+
var fetchDispatcherCache = /* @__PURE__ */ new Map();
|
|
2329
|
+
function getFetchPreResponseTimeoutMs(env) {
|
|
2330
|
+
const parsed = Number.parseInt(env.SLOCK_DAEMON_FETCH_PRE_RESPONSE_TIMEOUT_MS || "", 10);
|
|
2331
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 3e4;
|
|
2332
|
+
}
|
|
2333
|
+
function getDefaultPort(protocol) {
|
|
2334
|
+
switch (protocol) {
|
|
2335
|
+
case "https:":
|
|
2336
|
+
case "wss:":
|
|
2337
|
+
return "443";
|
|
2338
|
+
case "http:":
|
|
2339
|
+
case "ws:":
|
|
2340
|
+
return "80";
|
|
2341
|
+
default:
|
|
2342
|
+
return "";
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
function hostMatchesNoProxyEntry(hostname, ruleHost) {
|
|
2346
|
+
if (!ruleHost) return false;
|
|
2347
|
+
const normalizedRule = ruleHost.replace(/^\*\./, ".").replace(/^\./, "").toLowerCase();
|
|
2348
|
+
const normalizedHost = hostname.toLowerCase();
|
|
2349
|
+
return normalizedHost === normalizedRule || normalizedHost.endsWith(`.${normalizedRule}`);
|
|
2350
|
+
}
|
|
2351
|
+
function getProxyUrlForTarget(targetUrl, env) {
|
|
2352
|
+
const protocol = new URL(targetUrl).protocol;
|
|
2353
|
+
switch (protocol) {
|
|
2354
|
+
case "wss:":
|
|
2355
|
+
return env.WSS_PROXY || env.wss_proxy || env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
|
|
2356
|
+
case "ws:":
|
|
2357
|
+
return env.WS_PROXY || env.ws_proxy || env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
|
|
2358
|
+
case "https:":
|
|
2359
|
+
return env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
|
|
2360
|
+
case "http:":
|
|
2361
|
+
return env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
|
|
2362
|
+
default:
|
|
2363
|
+
return env.ALL_PROXY || env.all_proxy;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
function shouldBypassProxy(targetUrl, env) {
|
|
2367
|
+
const rawNoProxy = env.NO_PROXY || env.no_proxy;
|
|
2368
|
+
if (!rawNoProxy) return false;
|
|
2369
|
+
const url = new URL(targetUrl);
|
|
2370
|
+
const hostname = url.hostname.toLowerCase();
|
|
2371
|
+
const port = url.port || getDefaultPort(url.protocol);
|
|
2372
|
+
return rawNoProxy.split(",").map((entry) => entry.trim()).filter(Boolean).some((entry) => {
|
|
2373
|
+
if (entry === "*") return true;
|
|
2374
|
+
const [ruleHost, rulePort] = entry.split(":", 2);
|
|
2375
|
+
if (rulePort && rulePort !== port) return false;
|
|
2376
|
+
return hostMatchesNoProxyEntry(hostname, ruleHost);
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
function buildWebSocketOptions(wsUrl, env) {
|
|
2380
|
+
const proxyUrl = getProxyUrlForTarget(wsUrl, env);
|
|
2381
|
+
if (!proxyUrl) return void 0;
|
|
2382
|
+
if (shouldBypassProxy(wsUrl, env)) return void 0;
|
|
2383
|
+
return {
|
|
2384
|
+
agent: new HttpsProxyAgent(proxyUrl)
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
function resolveProxyUrl(targetUrl, env) {
|
|
2388
|
+
const proxyUrl = getProxyUrlForTarget(targetUrl, env);
|
|
2389
|
+
if (!proxyUrl) return void 0;
|
|
2390
|
+
if (shouldBypassProxy(targetUrl, env)) return void 0;
|
|
2391
|
+
return proxyUrl;
|
|
2392
|
+
}
|
|
2393
|
+
function buildFetchDispatcher(targetUrl, env) {
|
|
2394
|
+
const proxyUrl = resolveProxyUrl(targetUrl, env);
|
|
2395
|
+
if (!proxyUrl) return void 0;
|
|
2396
|
+
const cached = fetchDispatcherCache.get(proxyUrl);
|
|
2397
|
+
if (cached) return cached;
|
|
2398
|
+
const timeoutMs = getFetchPreResponseTimeoutMs(env);
|
|
2399
|
+
const dispatcher = new ProxyAgent({
|
|
2400
|
+
uri: proxyUrl,
|
|
2401
|
+
// All three are pre-response and body-agnostic (see getFetchPreResponseTimeoutMs):
|
|
2402
|
+
// headersTimeout = headers-hang leg; requestTls.timeout = CONNECT-tunnel
|
|
2403
|
+
// establish leg; connect.timeout = socket to the proxy itself (defensive).
|
|
2404
|
+
connect: { timeout: timeoutMs },
|
|
2405
|
+
requestTls: { timeout: timeoutMs },
|
|
2406
|
+
headersTimeout: timeoutMs
|
|
2407
|
+
});
|
|
2408
|
+
fetchDispatcherCache.set(proxyUrl, dispatcher);
|
|
2409
|
+
return dispatcher;
|
|
2410
|
+
}
|
|
2411
|
+
function evictFetchDispatcher(targetUrl, env) {
|
|
2412
|
+
const proxyUrl = resolveProxyUrl(targetUrl, env);
|
|
2413
|
+
if (!proxyUrl) return false;
|
|
2414
|
+
const cached = fetchDispatcherCache.get(proxyUrl);
|
|
2415
|
+
if (!cached) return false;
|
|
2416
|
+
fetchDispatcherCache.delete(proxyUrl);
|
|
2417
|
+
void Promise.resolve().then(() => cached.close()).catch(() => cached.destroy?.(new Error("evicted"))).catch(() => {
|
|
2418
|
+
});
|
|
2419
|
+
return true;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
// src/daemonFetch.ts
|
|
2423
|
+
function withDaemonFetchProxy(input, init = {}, env = process.env) {
|
|
2424
|
+
const dispatcher = buildFetchDispatcher(input.toString(), env);
|
|
2425
|
+
return dispatcher ? { ...init, dispatcher } : init;
|
|
2426
|
+
}
|
|
2427
|
+
async function daemonFetch(input, init, env = process.env) {
|
|
2428
|
+
try {
|
|
2429
|
+
return await fetch(input, withDaemonFetchProxy(input, init, env));
|
|
2430
|
+
} catch (err) {
|
|
2431
|
+
evictFetchDispatcher(input.toString(), env);
|
|
2432
|
+
throw err;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// src/logger.ts
|
|
2437
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
2438
|
+
function timestamp() {
|
|
2439
|
+
const d = /* @__PURE__ */ new Date();
|
|
2440
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
2441
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
2442
|
+
}
|
|
2443
|
+
function format(level, msg) {
|
|
2444
|
+
return `${timestamp()} [${level}] ${msg}`;
|
|
2445
|
+
}
|
|
2446
|
+
function emit(event) {
|
|
2447
|
+
for (const listener of listeners) {
|
|
2448
|
+
listener(event);
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
function subscribeDaemonLogs(listener) {
|
|
2452
|
+
listeners.add(listener);
|
|
2453
|
+
return () => {
|
|
2454
|
+
listeners.delete(listener);
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
var logger = {
|
|
2458
|
+
info(msg) {
|
|
2459
|
+
const line = format("INFO", msg);
|
|
2460
|
+
console.log(line);
|
|
2461
|
+
emit({ level: "INFO", line, message: msg });
|
|
2462
|
+
},
|
|
2463
|
+
warn(msg) {
|
|
2464
|
+
const line = format("WARN", msg);
|
|
2465
|
+
console.warn(line);
|
|
2466
|
+
emit({ level: "WARN", line, message: msg });
|
|
2467
|
+
},
|
|
2468
|
+
error(msg, err) {
|
|
2469
|
+
const line = format("ERROR", msg);
|
|
2470
|
+
if (err) {
|
|
2471
|
+
console.error(line, err);
|
|
2472
|
+
} else {
|
|
2473
|
+
console.error(line);
|
|
2474
|
+
}
|
|
2475
|
+
emit({ level: "ERROR", line, message: msg, error: err });
|
|
2476
|
+
}
|
|
2477
|
+
};
|
|
2478
|
+
|
|
2193
2479
|
// src/agentCredentialProxy.ts
|
|
2194
2480
|
var registrations = /* @__PURE__ */ new Map();
|
|
2195
2481
|
var proxyServerState = null;
|
|
@@ -3016,7 +3302,9 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
|
|
|
3016
3302
|
var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
|
|
3017
3303
|
var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
3018
3304
|
var RAW_CREDENTIAL_ENV_DENYLIST = [
|
|
3019
|
-
"
|
|
3305
|
+
"SLOCK_AGENT_TOKEN",
|
|
3306
|
+
"SLOCK_AGENT_CREDENTIAL_KEY",
|
|
3307
|
+
"SLOCK_AGENT_CREDENTIAL_KEY_FILE"
|
|
3020
3308
|
];
|
|
3021
3309
|
var cachedOpencliBinPath;
|
|
3022
3310
|
function resolveOpencliBinPath() {
|
|
@@ -3231,7 +3519,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
|
|
|
3231
3519
|
...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
|
|
3232
3520
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
3233
3521
|
};
|
|
3234
|
-
|
|
3522
|
+
scrubDaemonChildEnv(spawnEnv);
|
|
3235
3523
|
for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
|
|
3236
3524
|
delete spawnEnv[key];
|
|
3237
3525
|
}
|
|
@@ -3660,7 +3948,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
|
|
|
3660
3948
|
}
|
|
3661
3949
|
function resolveCommandOnPath(command, deps = {}) {
|
|
3662
3950
|
const platform = deps.platform ?? process.platform;
|
|
3663
|
-
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
3951
|
+
const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
|
|
3664
3952
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
3665
3953
|
const existsSyncFn = deps.existsSyncFn ?? existsSync2;
|
|
3666
3954
|
if (platform === "win32") {
|
|
@@ -3686,7 +3974,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
3686
3974
|
return null;
|
|
3687
3975
|
}
|
|
3688
3976
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
3689
|
-
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
3977
|
+
const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
|
|
3690
3978
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
3691
3979
|
try {
|
|
3692
3980
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -4383,7 +4671,7 @@ var CodexDriver = class {
|
|
|
4383
4671
|
};
|
|
4384
4672
|
communication = {
|
|
4385
4673
|
chat: "slock_cli",
|
|
4386
|
-
runtimeControl: "
|
|
4674
|
+
runtimeControl: "none"
|
|
4387
4675
|
};
|
|
4388
4676
|
session = {
|
|
4389
4677
|
recovery: "resume_or_fresh"
|
|
@@ -4400,50 +4688,6 @@ var CodexDriver = class {
|
|
|
4400
4688
|
probe() {
|
|
4401
4689
|
return probeCodex();
|
|
4402
4690
|
}
|
|
4403
|
-
buildRuntimeActionsConfigArgs(ctx) {
|
|
4404
|
-
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
4405
|
-
const command = isTsSource ? "npx" : "node";
|
|
4406
|
-
const bridgeArgs = isTsSource ? [
|
|
4407
|
-
"tsx",
|
|
4408
|
-
ctx.chatBridgePath,
|
|
4409
|
-
"--agent-id",
|
|
4410
|
-
ctx.agentId,
|
|
4411
|
-
"--server-url",
|
|
4412
|
-
ctx.config.serverUrl,
|
|
4413
|
-
"--auth-token",
|
|
4414
|
-
ctx.config.authToken || ctx.daemonApiKey,
|
|
4415
|
-
"--runtime",
|
|
4416
|
-
this.id,
|
|
4417
|
-
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
4418
|
-
"--runtime-actions-only"
|
|
4419
|
-
] : [
|
|
4420
|
-
ctx.chatBridgePath,
|
|
4421
|
-
"--agent-id",
|
|
4422
|
-
ctx.agentId,
|
|
4423
|
-
"--server-url",
|
|
4424
|
-
ctx.config.serverUrl,
|
|
4425
|
-
"--auth-token",
|
|
4426
|
-
ctx.config.authToken || ctx.daemonApiKey,
|
|
4427
|
-
"--runtime",
|
|
4428
|
-
this.id,
|
|
4429
|
-
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
4430
|
-
"--runtime-actions-only"
|
|
4431
|
-
];
|
|
4432
|
-
return [
|
|
4433
|
-
"-c",
|
|
4434
|
-
`mcp_servers.chat.command=${JSON.stringify(command)}`,
|
|
4435
|
-
"-c",
|
|
4436
|
-
`mcp_servers.chat.args=${JSON.stringify(bridgeArgs)}`,
|
|
4437
|
-
"-c",
|
|
4438
|
-
"mcp_servers.chat.startup_timeout_sec=30",
|
|
4439
|
-
"-c",
|
|
4440
|
-
"mcp_servers.chat.tool_timeout_sec=120",
|
|
4441
|
-
"-c",
|
|
4442
|
-
"mcp_servers.chat.enabled=true",
|
|
4443
|
-
"-c",
|
|
4444
|
-
"mcp_servers.chat.required=true"
|
|
4445
|
-
];
|
|
4446
|
-
}
|
|
4447
4691
|
buildThreadRequest(ctx) {
|
|
4448
4692
|
const threadParams = {
|
|
4449
4693
|
cwd: ctx.workingDirectory,
|
|
@@ -4497,7 +4741,6 @@ var CodexDriver = class {
|
|
|
4497
4741
|
this.initialTurnStarted = false;
|
|
4498
4742
|
this.normalizer.reset({ threadId: ctx.config.sessionId || null });
|
|
4499
4743
|
const args = ["app-server", "--listen", "stdio://"];
|
|
4500
|
-
args.push(...this.buildRuntimeActionsConfigArgs(ctx));
|
|
4501
4744
|
const { command, args: spawnArgs } = resolveCodexSpawn(args);
|
|
4502
4745
|
const proc = spawn2(command, spawnArgs, {
|
|
4503
4746
|
cwd: ctx.workingDirectory,
|
|
@@ -4784,8 +5027,6 @@ var AntigravityDriver = class {
|
|
|
4784
5027
|
|
|
4785
5028
|
// src/drivers/copilot.ts
|
|
4786
5029
|
import { spawn as spawn4 } from "child_process";
|
|
4787
|
-
import path6 from "path";
|
|
4788
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
4789
5030
|
async function buildCopilotSpawnEnv(ctx) {
|
|
4790
5031
|
return (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
|
|
4791
5032
|
}
|
|
@@ -4799,7 +5040,7 @@ var CopilotDriver = class {
|
|
|
4799
5040
|
};
|
|
4800
5041
|
communication = {
|
|
4801
5042
|
chat: "slock_cli",
|
|
4802
|
-
runtimeControl: "
|
|
5043
|
+
runtimeControl: "none"
|
|
4803
5044
|
};
|
|
4804
5045
|
session = {
|
|
4805
5046
|
recovery: "resume_or_fresh"
|
|
@@ -4814,43 +5055,14 @@ var CopilotDriver = class {
|
|
|
4814
5055
|
usesSlockCliForCommunication = true;
|
|
4815
5056
|
sessionId = null;
|
|
4816
5057
|
sessionAnnounced = false;
|
|
4817
|
-
buildRuntimeActionsMcpConfig(ctx) {
|
|
4818
|
-
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
4819
|
-
const command = isTsSource ? "npx" : "node";
|
|
4820
|
-
const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath];
|
|
4821
|
-
return JSON.stringify({
|
|
4822
|
-
mcpServers: {
|
|
4823
|
-
chat: {
|
|
4824
|
-
command,
|
|
4825
|
-
args: [
|
|
4826
|
-
...bridgeArgs,
|
|
4827
|
-
"--agent-id",
|
|
4828
|
-
ctx.agentId,
|
|
4829
|
-
"--server-url",
|
|
4830
|
-
ctx.config.serverUrl,
|
|
4831
|
-
"--auth-token",
|
|
4832
|
-
ctx.config.authToken || ctx.daemonApiKey,
|
|
4833
|
-
"--runtime",
|
|
4834
|
-
this.id,
|
|
4835
|
-
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
4836
|
-
"--runtime-actions-only"
|
|
4837
|
-
]
|
|
4838
|
-
}
|
|
4839
|
-
}
|
|
4840
|
-
});
|
|
4841
|
-
}
|
|
4842
5058
|
async spawn(ctx) {
|
|
4843
5059
|
this.sessionId = ctx.config.sessionId || null;
|
|
4844
5060
|
this.sessionAnnounced = false;
|
|
4845
|
-
const mcpConfigPath = path6.join(ctx.workingDirectory, ".slock-copilot-mcp.json");
|
|
4846
|
-
writeFileSync3(mcpConfigPath, this.buildRuntimeActionsMcpConfig(ctx), "utf8");
|
|
4847
5061
|
const args = [
|
|
4848
5062
|
"--output-format",
|
|
4849
5063
|
"json",
|
|
4850
5064
|
"--allow-all-tools",
|
|
4851
5065
|
"--allow-all-paths",
|
|
4852
|
-
"--additional-mcp-config",
|
|
4853
|
-
`@${mcpConfigPath}`,
|
|
4854
5066
|
"-p",
|
|
4855
5067
|
ctx.prompt
|
|
4856
5068
|
];
|
|
@@ -4959,8 +5171,6 @@ var CopilotDriver = class {
|
|
|
4959
5171
|
|
|
4960
5172
|
// src/drivers/cursor.ts
|
|
4961
5173
|
import { spawn as spawn5, spawnSync } from "child_process";
|
|
4962
|
-
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
4963
|
-
import path7 from "path";
|
|
4964
5174
|
async function buildCursorSpawnEnv(ctx, deps = {}) {
|
|
4965
5175
|
const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
4966
5176
|
return withWindowsUserEnvironment(spawnEnv, deps);
|
|
@@ -4975,7 +5185,7 @@ var CursorDriver = class {
|
|
|
4975
5185
|
};
|
|
4976
5186
|
communication = {
|
|
4977
5187
|
chat: "slock_cli",
|
|
4978
|
-
runtimeControl: "
|
|
5188
|
+
runtimeControl: "none"
|
|
4979
5189
|
};
|
|
4980
5190
|
session = {
|
|
4981
5191
|
recovery: "resume_or_fresh"
|
|
@@ -4988,38 +5198,7 @@ var CursorDriver = class {
|
|
|
4988
5198
|
mcpToolPrefix = "mcp__chat__";
|
|
4989
5199
|
busyDeliveryMode = "none";
|
|
4990
5200
|
usesSlockCliForCommunication = true;
|
|
4991
|
-
buildRuntimeActionsMcpConfig(ctx) {
|
|
4992
|
-
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
4993
|
-
const command = isTsSource ? "npx" : "node";
|
|
4994
|
-
const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath];
|
|
4995
|
-
return JSON.stringify({
|
|
4996
|
-
mcpServers: {
|
|
4997
|
-
chat: {
|
|
4998
|
-
command,
|
|
4999
|
-
args: [
|
|
5000
|
-
...bridgeArgs,
|
|
5001
|
-
"--agent-id",
|
|
5002
|
-
ctx.agentId,
|
|
5003
|
-
"--server-url",
|
|
5004
|
-
ctx.config.serverUrl,
|
|
5005
|
-
"--auth-token",
|
|
5006
|
-
ctx.config.authToken || ctx.daemonApiKey,
|
|
5007
|
-
"--runtime",
|
|
5008
|
-
this.id,
|
|
5009
|
-
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
5010
|
-
"--runtime-actions-only"
|
|
5011
|
-
]
|
|
5012
|
-
}
|
|
5013
|
-
}
|
|
5014
|
-
});
|
|
5015
|
-
}
|
|
5016
5201
|
async spawn(ctx) {
|
|
5017
|
-
const cursorDir = path7.join(ctx.workingDirectory, ".cursor");
|
|
5018
|
-
if (!existsSync4(cursorDir)) {
|
|
5019
|
-
mkdirSync2(cursorDir, { recursive: true });
|
|
5020
|
-
}
|
|
5021
|
-
const mcpConfigPath = path7.join(cursorDir, "mcp.json");
|
|
5022
|
-
writeFileSync4(mcpConfigPath, this.buildRuntimeActionsMcpConfig(ctx), "utf8");
|
|
5023
5202
|
const args = [
|
|
5024
5203
|
"--print",
|
|
5025
5204
|
"--output-format",
|
|
@@ -5149,11 +5328,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
5149
5328
|
return parseCursorModelsOutput(String(result.stdout || ""));
|
|
5150
5329
|
}
|
|
5151
5330
|
function buildCursorModelProbeEnv(deps = {}) {
|
|
5152
|
-
return withWindowsUserEnvironment({
|
|
5331
|
+
return scrubDaemonChildEnv(withWindowsUserEnvironment({
|
|
5153
5332
|
...deps.env ?? process.env,
|
|
5154
5333
|
FORCE_COLOR: "0",
|
|
5155
5334
|
NO_COLOR: "1"
|
|
5156
|
-
}, deps);
|
|
5335
|
+
}, deps));
|
|
5157
5336
|
}
|
|
5158
5337
|
function runCursorModelsCommand() {
|
|
5159
5338
|
return spawnSync("cursor-agent", ["models"], {
|
|
@@ -5165,8 +5344,8 @@ function runCursorModelsCommand() {
|
|
|
5165
5344
|
|
|
5166
5345
|
// src/drivers/gemini.ts
|
|
5167
5346
|
import { execFileSync as execFileSync3, spawn as spawn6 } from "child_process";
|
|
5168
|
-
import { existsSync as
|
|
5169
|
-
import
|
|
5347
|
+
import { existsSync as existsSync4 } from "fs";
|
|
5348
|
+
import path6 from "path";
|
|
5170
5349
|
async function buildGeminiSpawnEnv(ctx, platform = process.platform) {
|
|
5171
5350
|
const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" }, platform);
|
|
5172
5351
|
const launchEnvVars = runtimeConfigToLaunchFields(ctx.config).envVars;
|
|
@@ -5208,9 +5387,9 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
5208
5387
|
return { command: resolveCommandOnPath("gemini", deps) ?? "gemini", args: commandArgs };
|
|
5209
5388
|
}
|
|
5210
5389
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
5211
|
-
const existsSyncFn = deps.existsSyncFn ??
|
|
5212
|
-
const env = deps.env ?? process.env;
|
|
5213
|
-
const winPath =
|
|
5390
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync4;
|
|
5391
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
5392
|
+
const winPath = path6.win32;
|
|
5214
5393
|
let geminiEntry = null;
|
|
5215
5394
|
try {
|
|
5216
5395
|
const globalRoot = normalizeExecOutput2(execFileSyncFn("npm", ["root", "-g"], {
|
|
@@ -5254,7 +5433,7 @@ var GeminiDriver = class {
|
|
|
5254
5433
|
};
|
|
5255
5434
|
communication = {
|
|
5256
5435
|
chat: "slock_cli",
|
|
5257
|
-
runtimeControl: "
|
|
5436
|
+
runtimeControl: "none"
|
|
5258
5437
|
};
|
|
5259
5438
|
session = {
|
|
5260
5439
|
recovery: "resume_or_fresh"
|
|
@@ -5272,7 +5451,6 @@ var GeminiDriver = class {
|
|
|
5272
5451
|
async spawn(ctx) {
|
|
5273
5452
|
this.sessionId = ctx.config.sessionId || null;
|
|
5274
5453
|
this.sessionAnnounced = false;
|
|
5275
|
-
this.writeGeminiSettings(ctx);
|
|
5276
5454
|
const { command, args } = resolveGeminiSpawn(buildGeminiArgs(ctx.config));
|
|
5277
5455
|
const spawnEnv = await buildGeminiSpawnEnv(ctx);
|
|
5278
5456
|
const proc = spawn6(command, args, {
|
|
@@ -5345,49 +5523,20 @@ var GeminiDriver = class {
|
|
|
5345
5523
|
messageNotificationStyle: "poll"
|
|
5346
5524
|
});
|
|
5347
5525
|
}
|
|
5348
|
-
writeGeminiSettings(ctx) {
|
|
5349
|
-
const geminiDir = path8.join(ctx.workingDirectory, ".gemini");
|
|
5350
|
-
mkdirSync3(geminiDir, { recursive: true });
|
|
5351
|
-
const settingsPath = path8.join(geminiDir, "settings.json");
|
|
5352
|
-
writeFileSync5(settingsPath, JSON.stringify(this.buildRuntimeActionsMcpSettings(ctx)), "utf8");
|
|
5353
|
-
}
|
|
5354
|
-
buildRuntimeActionsMcpSettings(ctx) {
|
|
5355
|
-
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
5356
|
-
const command = isTsSource ? "npx" : "node";
|
|
5357
|
-
const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath];
|
|
5358
|
-
return {
|
|
5359
|
-
mcpServers: {
|
|
5360
|
-
chat: {
|
|
5361
|
-
command,
|
|
5362
|
-
args: [
|
|
5363
|
-
...bridgeArgs,
|
|
5364
|
-
"--agent-id",
|
|
5365
|
-
ctx.agentId,
|
|
5366
|
-
"--server-url",
|
|
5367
|
-
ctx.config.serverUrl,
|
|
5368
|
-
"--auth-token",
|
|
5369
|
-
ctx.config.authToken || ctx.daemonApiKey,
|
|
5370
|
-
"--runtime",
|
|
5371
|
-
this.id,
|
|
5372
|
-
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
5373
|
-
"--runtime-actions-only"
|
|
5374
|
-
]
|
|
5375
|
-
}
|
|
5376
|
-
}
|
|
5377
|
-
};
|
|
5378
|
-
}
|
|
5379
5526
|
};
|
|
5380
5527
|
|
|
5381
5528
|
// src/drivers/kimi.ts
|
|
5382
5529
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
5383
5530
|
import { spawn as spawn7 } from "child_process";
|
|
5384
|
-
import { existsSync as
|
|
5531
|
+
import { chmodSync, existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5385
5532
|
import os3 from "os";
|
|
5386
|
-
import
|
|
5533
|
+
import path7 from "path";
|
|
5387
5534
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
5388
5535
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
5389
5536
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
5390
|
-
var
|
|
5537
|
+
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
5538
|
+
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
5539
|
+
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
5391
5540
|
function parseToolArguments(raw) {
|
|
5392
5541
|
if (typeof raw !== "string") return raw;
|
|
5393
5542
|
try {
|
|
@@ -5396,6 +5545,73 @@ function parseToolArguments(raw) {
|
|
|
5396
5545
|
return raw;
|
|
5397
5546
|
}
|
|
5398
5547
|
}
|
|
5548
|
+
function readKimiConfigSource(home = os3.homedir(), env = process.env) {
|
|
5549
|
+
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
5550
|
+
if (inlineConfig && inlineConfig.trim()) {
|
|
5551
|
+
return {
|
|
5552
|
+
raw: inlineConfig,
|
|
5553
|
+
explicitPath: null,
|
|
5554
|
+
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
5555
|
+
};
|
|
5556
|
+
}
|
|
5557
|
+
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
5558
|
+
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path7.join(home, ".kimi", "config.toml");
|
|
5559
|
+
try {
|
|
5560
|
+
return {
|
|
5561
|
+
raw: readFileSync3(configPath, "utf8"),
|
|
5562
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
5563
|
+
sourcePath: configPath
|
|
5564
|
+
};
|
|
5565
|
+
} catch {
|
|
5566
|
+
return {
|
|
5567
|
+
raw: null,
|
|
5568
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
5569
|
+
sourcePath: configPath
|
|
5570
|
+
};
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5573
|
+
function buildKimiSpawnEnv(env = process.env) {
|
|
5574
|
+
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
5575
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
5576
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
5577
|
+
return scrubDaemonChildEnv(spawnEnv);
|
|
5578
|
+
}
|
|
5579
|
+
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
5580
|
+
return {
|
|
5581
|
+
...process.env,
|
|
5582
|
+
...ctx.config.envVars || {},
|
|
5583
|
+
...overrideEnv || {}
|
|
5584
|
+
};
|
|
5585
|
+
}
|
|
5586
|
+
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
5587
|
+
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
5588
|
+
const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
|
|
5589
|
+
const args = [];
|
|
5590
|
+
let configFilePath = null;
|
|
5591
|
+
let configContent = null;
|
|
5592
|
+
if (source.explicitPath) {
|
|
5593
|
+
configFilePath = source.explicitPath;
|
|
5594
|
+
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
5595
|
+
configFilePath = path7.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
5596
|
+
configContent = source.raw;
|
|
5597
|
+
if (opts.writeGeneratedConfig !== false) {
|
|
5598
|
+
writeFileSync3(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
5599
|
+
chmodSync(configFilePath, 384);
|
|
5600
|
+
}
|
|
5601
|
+
}
|
|
5602
|
+
if (configFilePath) {
|
|
5603
|
+
args.push("--config-file", configFilePath);
|
|
5604
|
+
}
|
|
5605
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
5606
|
+
args.push("--model", ctx.config.model);
|
|
5607
|
+
}
|
|
5608
|
+
return {
|
|
5609
|
+
args,
|
|
5610
|
+
env: buildKimiSpawnEnv(env),
|
|
5611
|
+
configFilePath,
|
|
5612
|
+
configContent
|
|
5613
|
+
};
|
|
5614
|
+
}
|
|
5399
5615
|
function resolveKimiSpawn(commandArgs, deps = {}) {
|
|
5400
5616
|
return {
|
|
5401
5617
|
command: resolveCommandOnPath("kimi", deps) ?? "kimi",
|
|
@@ -5412,14 +5628,32 @@ var KimiDriver = class {
|
|
|
5412
5628
|
};
|
|
5413
5629
|
communication = {
|
|
5414
5630
|
chat: "slock_cli",
|
|
5415
|
-
runtimeControl: "
|
|
5631
|
+
runtimeControl: "none"
|
|
5416
5632
|
};
|
|
5417
5633
|
session = {
|
|
5418
5634
|
recovery: "resume_or_fresh"
|
|
5419
5635
|
};
|
|
5420
5636
|
model = {
|
|
5421
5637
|
detectedModelsVerifiedAs: "launchable",
|
|
5422
|
-
toLaunchSpec: (modelId) =>
|
|
5638
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
5639
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
5640
|
+
const launchCtx = {
|
|
5641
|
+
...ctx,
|
|
5642
|
+
config: {
|
|
5643
|
+
...ctx.config,
|
|
5644
|
+
model: modelId
|
|
5645
|
+
}
|
|
5646
|
+
};
|
|
5647
|
+
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
5648
|
+
home: opts?.home,
|
|
5649
|
+
writeGeneratedConfig: false
|
|
5650
|
+
});
|
|
5651
|
+
return {
|
|
5652
|
+
args: launch.args,
|
|
5653
|
+
env: launch.env,
|
|
5654
|
+
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
5655
|
+
};
|
|
5656
|
+
}
|
|
5423
5657
|
};
|
|
5424
5658
|
supportsStdinNotification = true;
|
|
5425
5659
|
mcpToolPrefix = "";
|
|
@@ -5428,68 +5662,40 @@ var KimiDriver = class {
|
|
|
5428
5662
|
sessionId = null;
|
|
5429
5663
|
sessionAnnounced = false;
|
|
5430
5664
|
promptRequestId = null;
|
|
5431
|
-
buildChatBridgeArgs(ctx) {
|
|
5432
|
-
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
5433
|
-
return [
|
|
5434
|
-
...isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath],
|
|
5435
|
-
"--agent-id",
|
|
5436
|
-
ctx.agentId,
|
|
5437
|
-
"--server-url",
|
|
5438
|
-
ctx.config.serverUrl,
|
|
5439
|
-
"--auth-token",
|
|
5440
|
-
ctx.config.authToken || ctx.daemonApiKey,
|
|
5441
|
-
"--runtime",
|
|
5442
|
-
"kimi",
|
|
5443
|
-
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
5444
|
-
"--runtime-actions-only"
|
|
5445
|
-
];
|
|
5446
|
-
}
|
|
5447
5665
|
async spawn(ctx) {
|
|
5448
5666
|
const isResume = !!ctx.config.sessionId;
|
|
5449
5667
|
this.sessionId = ctx.config.sessionId || randomUUID2();
|
|
5450
5668
|
this.sessionAnnounced = false;
|
|
5451
5669
|
this.promptRequestId = randomUUID2();
|
|
5452
|
-
const
|
|
5453
|
-
const
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
if (!isResume || !existsSync6(systemPromptPath)) {
|
|
5459
|
-
writeFileSync6(systemPromptPath, ctx.prompt, "utf8");
|
|
5460
|
-
}
|
|
5461
|
-
writeFileSync6(agentFilePath, [
|
|
5670
|
+
const systemPromptPath = path7.join(ctx.workingDirectory, KIMI_SYSTEM_PROMPT_FILE);
|
|
5671
|
+
const agentFilePath = path7.join(ctx.workingDirectory, KIMI_AGENT_FILE);
|
|
5672
|
+
if (!isResume || !existsSync5(systemPromptPath)) {
|
|
5673
|
+
writeFileSync3(systemPromptPath, ctx.prompt, "utf8");
|
|
5674
|
+
}
|
|
5675
|
+
writeFileSync3(agentFilePath, [
|
|
5462
5676
|
"version: 1",
|
|
5463
5677
|
"agent:",
|
|
5464
5678
|
" extend: default",
|
|
5465
5679
|
` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
|
|
5466
5680
|
""
|
|
5467
5681
|
].join("\n"), "utf8");
|
|
5468
|
-
|
|
5469
|
-
mcpServers: {
|
|
5470
|
-
chat: {
|
|
5471
|
-
command,
|
|
5472
|
-
args: bridgeArgs
|
|
5473
|
-
}
|
|
5474
|
-
}
|
|
5475
|
-
}), "utf8");
|
|
5682
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
5476
5683
|
const args = [
|
|
5477
5684
|
"--wire",
|
|
5478
5685
|
"--yolo",
|
|
5479
5686
|
"--agent-file",
|
|
5480
5687
|
agentFilePath,
|
|
5481
|
-
"--mcp-config-file",
|
|
5482
|
-
mcpConfigPath,
|
|
5483
5688
|
"--session",
|
|
5484
|
-
this.sessionId
|
|
5689
|
+
this.sessionId,
|
|
5690
|
+
...launch.args
|
|
5485
5691
|
];
|
|
5486
5692
|
const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
|
|
5487
5693
|
if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
|
|
5488
5694
|
args.push("--model", launchRuntimeFields.model);
|
|
5489
5695
|
}
|
|
5490
5696
|
const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
|
|
5491
|
-
const
|
|
5492
|
-
const proc = spawn7(
|
|
5697
|
+
const spawnTarget = resolveKimiSpawn(args);
|
|
5698
|
+
const proc = spawn7(spawnTarget.command, spawnTarget.args, {
|
|
5493
5699
|
cwd: ctx.workingDirectory,
|
|
5494
5700
|
stdio: ["pipe", "pipe", "pipe"],
|
|
5495
5701
|
env: spawnEnv,
|
|
@@ -5497,7 +5703,7 @@ var KimiDriver = class {
|
|
|
5497
5703
|
// and has an 8191-character command-line limit. Kimi's official
|
|
5498
5704
|
// installer/uv entrypoint is an executable, so launch it directly and
|
|
5499
5705
|
// keep prompts on stdin / files instead of routing through cmd.exe.
|
|
5500
|
-
shell:
|
|
5706
|
+
shell: spawnTarget.shell
|
|
5501
5707
|
});
|
|
5502
5708
|
proc.stdin?.write(JSON.stringify({
|
|
5503
5709
|
jsonrpc: "2.0",
|
|
@@ -5611,14 +5817,9 @@ var KimiDriver = class {
|
|
|
5611
5817
|
return detectKimiModels();
|
|
5612
5818
|
}
|
|
5613
5819
|
};
|
|
5614
|
-
function detectKimiModels(home = os3.homedir()) {
|
|
5615
|
-
const
|
|
5616
|
-
|
|
5617
|
-
try {
|
|
5618
|
-
raw = readFileSync3(configPath, "utf8");
|
|
5619
|
-
} catch {
|
|
5620
|
-
return null;
|
|
5621
|
-
}
|
|
5820
|
+
function detectKimiModels(home = os3.homedir(), opts = {}) {
|
|
5821
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
5822
|
+
if (raw === null) return null;
|
|
5622
5823
|
const models = [];
|
|
5623
5824
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
5624
5825
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -5639,9 +5840,9 @@ function detectKimiModels(home = os3.homedir()) {
|
|
|
5639
5840
|
|
|
5640
5841
|
// src/drivers/opencode.ts
|
|
5641
5842
|
import { spawn as spawn8, spawnSync as spawnSync2 } from "child_process";
|
|
5642
|
-
import { existsSync as
|
|
5843
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
5643
5844
|
import os4 from "os";
|
|
5644
|
-
import
|
|
5845
|
+
import path8 from "path";
|
|
5645
5846
|
var CHAT_MCP_SERVER_NAME = "chat";
|
|
5646
5847
|
var CHAT_MCP_TOOL_PREFIX = `${CHAT_MCP_SERVER_NAME}_`;
|
|
5647
5848
|
var SLOCK_AGENT_NAME = "slock";
|
|
@@ -5656,23 +5857,6 @@ var OPENCODE_PROVIDER_LABELS = {
|
|
|
5656
5857
|
deepseek: "DeepSeek",
|
|
5657
5858
|
fusecode: "FuseCode"
|
|
5658
5859
|
};
|
|
5659
|
-
function buildChatBridgeCommand(ctx) {
|
|
5660
|
-
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
5661
|
-
return [
|
|
5662
|
-
isTsSource ? "npx" : "node",
|
|
5663
|
-
...isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath],
|
|
5664
|
-
"--agent-id",
|
|
5665
|
-
ctx.agentId,
|
|
5666
|
-
"--server-url",
|
|
5667
|
-
ctx.config.serverUrl,
|
|
5668
|
-
"--auth-token",
|
|
5669
|
-
ctx.config.authToken || ctx.daemonApiKey,
|
|
5670
|
-
"--runtime",
|
|
5671
|
-
"opencode",
|
|
5672
|
-
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
5673
|
-
"--runtime-actions-only"
|
|
5674
|
-
];
|
|
5675
|
-
}
|
|
5676
5860
|
function parseOpenCodeConfigContent(raw) {
|
|
5677
5861
|
if (!raw) return {};
|
|
5678
5862
|
try {
|
|
@@ -5689,7 +5873,7 @@ function parseUserOpenCodeConfig(ctx) {
|
|
|
5689
5873
|
return parseOpenCodeConfigContent(raw);
|
|
5690
5874
|
}
|
|
5691
5875
|
function readLocalOpenCodeConfig(home = os4.homedir()) {
|
|
5692
|
-
const configPath =
|
|
5876
|
+
const configPath = path8.join(home, ".config", "opencode", "opencode.json");
|
|
5693
5877
|
try {
|
|
5694
5878
|
return parseOpenCodeConfigContent(readFileSync4(configPath, "utf8"));
|
|
5695
5879
|
} catch {
|
|
@@ -5764,14 +5948,7 @@ function buildOpenCodeConfig(ctx, home = os4.homedir()) {
|
|
|
5764
5948
|
prompt: ctx.standingPrompt
|
|
5765
5949
|
}
|
|
5766
5950
|
},
|
|
5767
|
-
mcp:
|
|
5768
|
-
...recordField(userConfig.mcp),
|
|
5769
|
-
[CHAT_MCP_SERVER_NAME]: {
|
|
5770
|
-
type: "local",
|
|
5771
|
-
command: buildChatBridgeCommand(ctx),
|
|
5772
|
-
enabled: true
|
|
5773
|
-
}
|
|
5774
|
-
}
|
|
5951
|
+
mcp: recordField(userConfig.mcp)
|
|
5775
5952
|
};
|
|
5776
5953
|
}
|
|
5777
5954
|
async function buildOpenCodeLaunchOptions(ctx, home = os4.homedir(), version = null) {
|
|
@@ -5882,7 +6059,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
|
|
|
5882
6059
|
const platform = deps.platform ?? process.platform;
|
|
5883
6060
|
const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
|
|
5884
6061
|
const result = spawnSyncFn("opencode", ["models"], {
|
|
5885
|
-
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
6062
|
+
env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
5886
6063
|
encoding: "utf8",
|
|
5887
6064
|
timeout: 5e3,
|
|
5888
6065
|
shell: platform === "win32"
|
|
@@ -5894,11 +6071,11 @@ function runOpenCodeModelsCommand(home, deps = {}) {
|
|
|
5894
6071
|
};
|
|
5895
6072
|
}
|
|
5896
6073
|
function isWindowsCommandShim(commandPath) {
|
|
5897
|
-
const ext =
|
|
6074
|
+
const ext = path8.win32.extname(commandPath).toLowerCase();
|
|
5898
6075
|
return ext === ".cmd" || ext === ".bat";
|
|
5899
6076
|
}
|
|
5900
6077
|
function opencodePackageEntryCandidates(packageRoot) {
|
|
5901
|
-
const winPath =
|
|
6078
|
+
const winPath = path8.win32;
|
|
5902
6079
|
return [
|
|
5903
6080
|
winPath.join(packageRoot, "bin", "opencode.exe"),
|
|
5904
6081
|
winPath.join(packageRoot, "bin", "opencode.js"),
|
|
@@ -5907,16 +6084,16 @@ function opencodePackageEntryCandidates(packageRoot) {
|
|
|
5907
6084
|
];
|
|
5908
6085
|
}
|
|
5909
6086
|
function openCodeSpecForEntry(entry, commandArgs) {
|
|
5910
|
-
if (
|
|
6087
|
+
if (path8.win32.extname(entry).toLowerCase() === ".exe") {
|
|
5911
6088
|
return { command: entry, args: commandArgs, shell: false };
|
|
5912
6089
|
}
|
|
5913
6090
|
return { command: process.execPath, args: [entry, ...commandArgs], shell: false };
|
|
5914
6091
|
}
|
|
5915
6092
|
function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
|
|
5916
|
-
const existsSyncFn = deps.existsSyncFn ??
|
|
6093
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync6;
|
|
5917
6094
|
const execFileSyncFn = deps.execFileSyncFn;
|
|
5918
6095
|
const env = deps.env ?? process.env;
|
|
5919
|
-
const winPath =
|
|
6096
|
+
const winPath = path8.win32;
|
|
5920
6097
|
const candidates = [];
|
|
5921
6098
|
if (execFileSyncFn) {
|
|
5922
6099
|
try {
|
|
@@ -5944,7 +6121,7 @@ function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
|
|
|
5944
6121
|
function extractWindowsShimTargets(commandPath, deps = {}) {
|
|
5945
6122
|
if (!isWindowsCommandShim(commandPath)) return [];
|
|
5946
6123
|
const readFileSyncFn = deps.readFileSyncFn ?? readFileSync4;
|
|
5947
|
-
const commandDir =
|
|
6124
|
+
const commandDir = path8.win32.dirname(commandPath);
|
|
5948
6125
|
let raw;
|
|
5949
6126
|
try {
|
|
5950
6127
|
raw = String(readFileSyncFn(commandPath, "utf8"));
|
|
@@ -5955,7 +6132,7 @@ function extractWindowsShimTargets(commandPath, deps = {}) {
|
|
|
5955
6132
|
const dp0Pattern = /%~dp0\\?([^"\r\n]*?opencode\.(?:exe|js|mjs|cjs))/gi;
|
|
5956
6133
|
for (const match of raw.matchAll(dp0Pattern)) {
|
|
5957
6134
|
const relative = match[1]?.replace(/^\\+/, "");
|
|
5958
|
-
if (relative) candidates.push(
|
|
6135
|
+
if (relative) candidates.push(path8.win32.normalize(path8.win32.join(commandDir, relative)));
|
|
5959
6136
|
}
|
|
5960
6137
|
return candidates;
|
|
5961
6138
|
}
|
|
@@ -5969,7 +6146,7 @@ function resolveOpenCodeSpawn(commandArgs, deps = {}) {
|
|
|
5969
6146
|
};
|
|
5970
6147
|
}
|
|
5971
6148
|
const command = resolveCommandOnPath("opencode", deps);
|
|
5972
|
-
if (command &&
|
|
6149
|
+
if (command && path8.win32.extname(command).toLowerCase() === ".exe") {
|
|
5973
6150
|
return { command, args: commandArgs, shell: false };
|
|
5974
6151
|
}
|
|
5975
6152
|
const packageEntry = resolveWindowsOpenCodePackageEntry(command, deps);
|
|
@@ -6013,7 +6190,7 @@ var OpenCodeDriver = class {
|
|
|
6013
6190
|
};
|
|
6014
6191
|
communication = {
|
|
6015
6192
|
chat: "slock_cli",
|
|
6016
|
-
runtimeControl: "
|
|
6193
|
+
runtimeControl: "none"
|
|
6017
6194
|
};
|
|
6018
6195
|
session = {
|
|
6019
6196
|
recovery: "resume_or_fresh"
|
|
@@ -6144,8 +6321,8 @@ var OpenCodeDriver = class {
|
|
|
6144
6321
|
// src/drivers/pi.ts
|
|
6145
6322
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6146
6323
|
import { EventEmitter } from "events";
|
|
6147
|
-
import { mkdirSync as
|
|
6148
|
-
import
|
|
6324
|
+
import { mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
6325
|
+
import path9 from "path";
|
|
6149
6326
|
import {
|
|
6150
6327
|
AuthStorage,
|
|
6151
6328
|
createBashTool,
|
|
@@ -6171,7 +6348,7 @@ function createPiSdkEventMappingState(sessionId = null) {
|
|
|
6171
6348
|
};
|
|
6172
6349
|
}
|
|
6173
6350
|
function buildPiSessionDir(workingDirectory) {
|
|
6174
|
-
return
|
|
6351
|
+
return path9.join(workingDirectory, PI_SESSION_DIR);
|
|
6175
6352
|
}
|
|
6176
6353
|
async function buildPiSpawnEnv(ctx) {
|
|
6177
6354
|
return (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
|
|
@@ -6193,7 +6370,7 @@ function findPiSessionFile(sessionDir, sessionId) {
|
|
|
6193
6370
|
}
|
|
6194
6371
|
const suffix = `_${sessionId}.jsonl`;
|
|
6195
6372
|
const match = entries.find((entry) => entry.endsWith(suffix));
|
|
6196
|
-
return match ?
|
|
6373
|
+
return match ? path9.join(sessionDir, match) : null;
|
|
6197
6374
|
}
|
|
6198
6375
|
function detectPiModelsFromRegistry(modelRegistry) {
|
|
6199
6376
|
const models = [];
|
|
@@ -6342,13 +6519,15 @@ var PI_RUNTIME_SESSION_DESCRIPTOR = {
|
|
|
6342
6519
|
busyDelivery: "direct",
|
|
6343
6520
|
postTurn: "keep_alive"
|
|
6344
6521
|
};
|
|
6522
|
+
var PI_IDLE_PROMPT_RETRY_MS = 25;
|
|
6523
|
+
var PI_IDLE_PROMPT_MAX_WAIT_MS = 1e3;
|
|
6345
6524
|
async function createPiAgentSessionForContext(ctx, sessionId) {
|
|
6346
6525
|
const sessionDir = buildPiSessionDir(ctx.workingDirectory);
|
|
6347
|
-
|
|
6526
|
+
mkdirSync2(sessionDir, { recursive: true });
|
|
6348
6527
|
const spawnEnv = await buildPiSpawnEnv(ctx);
|
|
6349
6528
|
const agentDir = spawnEnv.PI_CODING_AGENT_DIR || getAgentDir();
|
|
6350
|
-
const authStorage = AuthStorage.create(
|
|
6351
|
-
const modelRegistry = ModelRegistry.create(authStorage,
|
|
6529
|
+
const authStorage = AuthStorage.create(path9.join(agentDir, "auth.json"));
|
|
6530
|
+
const modelRegistry = ModelRegistry.create(authStorage, path9.join(agentDir, "models.json"));
|
|
6352
6531
|
const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
|
|
6353
6532
|
const model = resolvePiModelFromRegistry(launchRuntimeFields.model, modelRegistry);
|
|
6354
6533
|
if (launchRuntimeFields.model && launchRuntimeFields.model !== "default" && !model) {
|
|
@@ -6459,7 +6638,8 @@ var PiSdkRuntimeSession = class {
|
|
|
6459
6638
|
return { ok: true, acceptedAs: "steer" };
|
|
6460
6639
|
}
|
|
6461
6640
|
if (session.isStreaming) {
|
|
6462
|
-
|
|
6641
|
+
this.launchPromptAfterStreaming(input.text);
|
|
6642
|
+
return { ok: true, acceptedAs: "prompt" };
|
|
6463
6643
|
}
|
|
6464
6644
|
this.launchPrompt(input.text);
|
|
6465
6645
|
return { ok: true, acceptedAs: "prompt" };
|
|
@@ -6501,6 +6681,27 @@ var PiSdkRuntimeSession = class {
|
|
|
6501
6681
|
}
|
|
6502
6682
|
this.deferSdkCall(() => session.prompt(text));
|
|
6503
6683
|
}
|
|
6684
|
+
launchPromptAfterStreaming(text) {
|
|
6685
|
+
this.deferSdkCall(async () => {
|
|
6686
|
+
const session = this.session;
|
|
6687
|
+
if (!session) return;
|
|
6688
|
+
const ready = await this.waitForStreamingToClear(session);
|
|
6689
|
+
if (this.didClose || this.session !== session) {
|
|
6690
|
+
return;
|
|
6691
|
+
}
|
|
6692
|
+
await (ready ? session.prompt(text) : session.steer(text));
|
|
6693
|
+
});
|
|
6694
|
+
}
|
|
6695
|
+
async waitForStreamingToClear(session) {
|
|
6696
|
+
const deadline = Date.now() + PI_IDLE_PROMPT_MAX_WAIT_MS;
|
|
6697
|
+
while (!this.didClose && this.session === session && session.isStreaming) {
|
|
6698
|
+
if (Date.now() >= deadline) {
|
|
6699
|
+
return false;
|
|
6700
|
+
}
|
|
6701
|
+
await delay(PI_IDLE_PROMPT_RETRY_MS);
|
|
6702
|
+
}
|
|
6703
|
+
return !this.didClose && this.session === session && !session.isStreaming;
|
|
6704
|
+
}
|
|
6504
6705
|
deferSdkCall(invoke) {
|
|
6505
6706
|
setImmediate(() => {
|
|
6506
6707
|
if (this.didClose) return;
|
|
@@ -6612,6 +6813,12 @@ var PiDriver = class {
|
|
|
6612
6813
|
});
|
|
6613
6814
|
}
|
|
6614
6815
|
};
|
|
6816
|
+
function delay(ms) {
|
|
6817
|
+
return new Promise((resolve) => {
|
|
6818
|
+
const timer = setTimeout(resolve, ms);
|
|
6819
|
+
timer.unref?.();
|
|
6820
|
+
});
|
|
6821
|
+
}
|
|
6615
6822
|
|
|
6616
6823
|
// src/drivers/runtimeSession.ts
|
|
6617
6824
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
@@ -6770,7 +6977,7 @@ function getDriver(runtimeId) {
|
|
|
6770
6977
|
|
|
6771
6978
|
// src/workspaces.ts
|
|
6772
6979
|
import { readdir, rm, stat } from "fs/promises";
|
|
6773
|
-
import
|
|
6980
|
+
import path10 from "path";
|
|
6774
6981
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
6775
6982
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
6776
6983
|
}
|
|
@@ -6778,7 +6985,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
6778
6985
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
6779
6986
|
return null;
|
|
6780
6987
|
}
|
|
6781
|
-
return
|
|
6988
|
+
return path10.join(dataDir, directoryName);
|
|
6782
6989
|
}
|
|
6783
6990
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
6784
6991
|
return {
|
|
@@ -6827,7 +7034,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
6827
7034
|
return summary;
|
|
6828
7035
|
}
|
|
6829
7036
|
const childSummaries = await Promise.all(
|
|
6830
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
7037
|
+
entries.map((entry) => summarizeWorkspaceEntry(path10.join(dirPath, entry.name), entry))
|
|
6831
7038
|
);
|
|
6832
7039
|
for (const childSummary of childSummaries) {
|
|
6833
7040
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -6846,7 +7053,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
6846
7053
|
if (!entry.isDirectory()) {
|
|
6847
7054
|
return null;
|
|
6848
7055
|
}
|
|
6849
|
-
const dirPath =
|
|
7056
|
+
const dirPath = path10.join(dataDir, entry.name);
|
|
6850
7057
|
try {
|
|
6851
7058
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
6852
7059
|
return {
|
|
@@ -7116,6 +7323,11 @@ var RuntimeNotificationState = class {
|
|
|
7116
7323
|
clearPending() {
|
|
7117
7324
|
this.pendingCountValue = 0;
|
|
7118
7325
|
}
|
|
7326
|
+
remove(count) {
|
|
7327
|
+
if (!Number.isFinite(count) || count <= 0) return this.pendingCountValue;
|
|
7328
|
+
this.pendingCountValue = Math.max(0, this.pendingCountValue - Math.floor(count));
|
|
7329
|
+
return this.pendingCountValue;
|
|
7330
|
+
}
|
|
7119
7331
|
clearTimer() {
|
|
7120
7332
|
if (this.timerValue) {
|
|
7121
7333
|
clearTimeout(this.timerValue);
|
|
@@ -7304,12 +7516,12 @@ function findSessionJsonl(root, predicate) {
|
|
|
7304
7516
|
for (const entry of entries) {
|
|
7305
7517
|
if (++visited > maxEntries) return null;
|
|
7306
7518
|
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
7307
|
-
return
|
|
7519
|
+
return path11.join(dir, entry.name);
|
|
7308
7520
|
}
|
|
7309
7521
|
for (const entry of entries) {
|
|
7310
7522
|
if (++visited > maxEntries) return null;
|
|
7311
7523
|
if (!entry.isDirectory()) continue;
|
|
7312
|
-
const found = visit(
|
|
7524
|
+
const found = visit(path11.join(dir, entry.name), depth - 1);
|
|
7313
7525
|
if (found) return found;
|
|
7314
7526
|
}
|
|
7315
7527
|
return null;
|
|
@@ -7322,10 +7534,10 @@ function safeSessionFilename(value) {
|
|
|
7322
7534
|
}
|
|
7323
7535
|
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
7324
7536
|
try {
|
|
7325
|
-
const dir =
|
|
7326
|
-
|
|
7327
|
-
const filePath =
|
|
7328
|
-
|
|
7537
|
+
const dir = path11.join(fallbackDir, ".slock", "runtime-sessions");
|
|
7538
|
+
mkdirSync3(dir, { recursive: true });
|
|
7539
|
+
const filePath = path11.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
7540
|
+
writeFileSync4(filePath, JSON.stringify({
|
|
7329
7541
|
type: "runtime_session_handoff",
|
|
7330
7542
|
runtime,
|
|
7331
7543
|
sessionId,
|
|
@@ -7344,7 +7556,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
7344
7556
|
}
|
|
7345
7557
|
}
|
|
7346
7558
|
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
|
|
7347
|
-
const directPath =
|
|
7559
|
+
const directPath = path11.isAbsolute(sessionId) ? sessionId : null;
|
|
7348
7560
|
if (directPath) {
|
|
7349
7561
|
try {
|
|
7350
7562
|
if (statSync(directPath).isFile()) {
|
|
@@ -7353,7 +7565,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
|
|
|
7353
7565
|
} catch {
|
|
7354
7566
|
}
|
|
7355
7567
|
}
|
|
7356
|
-
const resolvedPath = runtime === "claude" ? findSessionJsonl(
|
|
7568
|
+
const resolvedPath = runtime === "claude" ? findSessionJsonl(path11.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path11.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
|
|
7357
7569
|
if (!resolvedPath && fallbackDir) {
|
|
7358
7570
|
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
7359
7571
|
if (fallback) return fallback;
|
|
@@ -7956,8 +8168,7 @@ function runtimeProfileTurnControl(kind, key, source) {
|
|
|
7956
8168
|
source,
|
|
7957
8169
|
keyHash: hashRuntimeProfileKey(key) ?? null,
|
|
7958
8170
|
keyPresent: Boolean(key),
|
|
7959
|
-
injectedAtMs: Date.now()
|
|
7960
|
-
migrationDoneToolObserved: false
|
|
8171
|
+
injectedAtMs: Date.now()
|
|
7961
8172
|
};
|
|
7962
8173
|
}
|
|
7963
8174
|
function pushRecentLines(lines, chunk, maxLines, maxLineLength) {
|
|
@@ -8033,6 +8244,9 @@ function classifyStickyTerminalFailure(ap) {
|
|
|
8033
8244
|
if (isRuntimeStartTimeoutText(terminalFailure.detail)) return null;
|
|
8034
8245
|
return null;
|
|
8035
8246
|
}
|
|
8247
|
+
function isAuthClassTerminalLine(text) {
|
|
8248
|
+
return buildRuntimeErrorDiagnosticEnvelope(text).spanAttrs.runtime_error_action_required === true;
|
|
8249
|
+
}
|
|
8036
8250
|
function isProviderStreamFailureText(text) {
|
|
8037
8251
|
return /stream closed before response\.completed|error decoding response body/i.test(text);
|
|
8038
8252
|
}
|
|
@@ -8314,6 +8528,29 @@ var NATIVE_STANDING_PROMPT_STARTUP_INPUT = (
|
|
|
8314
8528
|
);
|
|
8315
8529
|
var AgentProcessManager = class _AgentProcessManager {
|
|
8316
8530
|
agents = /* @__PURE__ */ new Map();
|
|
8531
|
+
/**
|
|
8532
|
+
* Per-agent monotonic clientSeq for `agent:activity` upstream dedup.
|
|
8533
|
+
* Server dedupes ingest by `(launchId, clientSeq)` (#engineering:72283cf7
|
|
8534
|
+
* task #340 PR B). This counter is keyed by agentId at the MANAGER level —
|
|
8535
|
+
* NOT on the per-process `AgentProcess` — so it SURVIVES process recreation
|
|
8536
|
+
* (cold-start-resume / idle-auto-restart / queued-continuation /
|
|
8537
|
+
* runtime-profile restart). Those daemon-internal restarts reuse the
|
|
8538
|
+
* server-issued launchId but spawn a fresh process; if the counter reset to
|
|
8539
|
+
* 0 (as the old per-`ap` field did) the reused launchId's `(launchId,
|
|
8540
|
+
* clientSeq)` window would collide and the server would drop the
|
|
8541
|
+
* post-restart activity as stale — the "activity log stops updating after
|
|
8542
|
+
* error→restart" bug. Keeping it monotonic-per-agent and never resetting on
|
|
8543
|
+
* respawn guarantees a fresh dedup key under the same launchId. launchId is
|
|
8544
|
+
* decoupled: it stays the identity/gate token, this is the clock.
|
|
8545
|
+
* (#proj-o11y:a1e54b59 — RS dedup-epoch ⊥ launchId-identity separation.)
|
|
8546
|
+
*/
|
|
8547
|
+
activityClientSeqByAgent = /* @__PURE__ */ new Map();
|
|
8548
|
+
/** Next monotonic `agent:activity` clientSeq for this agent (never resets on respawn). */
|
|
8549
|
+
nextActivityClientSeq(agentId) {
|
|
8550
|
+
const next = (this.activityClientSeqByAgent.get(agentId) ?? 0) + 1;
|
|
8551
|
+
this.activityClientSeqByAgent.set(agentId, next);
|
|
8552
|
+
return next;
|
|
8553
|
+
}
|
|
8317
8554
|
agentsStarting = /* @__PURE__ */ new Set();
|
|
8318
8555
|
// Prevent concurrent starts of same agent
|
|
8319
8556
|
queuedAgentStarts = /* @__PURE__ */ new Map();
|
|
@@ -8327,7 +8564,6 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8327
8564
|
startingInboxes = /* @__PURE__ */ new Map();
|
|
8328
8565
|
/** Cached configs for agents whose process exited normally — enables auto-restart on next message */
|
|
8329
8566
|
idleAgentConfigs = /* @__PURE__ */ new Map();
|
|
8330
|
-
chatBridgePath;
|
|
8331
8567
|
slockCliPath;
|
|
8332
8568
|
sendToServer;
|
|
8333
8569
|
daemonApiKey;
|
|
@@ -8345,8 +8581,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8345
8581
|
agentVisibleMessageIds = /* @__PURE__ */ new Map();
|
|
8346
8582
|
daemonVersion;
|
|
8347
8583
|
computerVersion;
|
|
8348
|
-
constructor(
|
|
8349
|
-
this.chatBridgePath = chatBridgePath;
|
|
8584
|
+
constructor(sendToServer, daemonApiKey, opts) {
|
|
8350
8585
|
this.slockCliPath = opts.slockCliPath ?? "";
|
|
8351
8586
|
this.sendToServer = sendToServer;
|
|
8352
8587
|
this.daemonApiKey = daemonApiKey;
|
|
@@ -8501,6 +8736,48 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8501
8736
|
suppressed_pending_count: removedActive + removedStarting
|
|
8502
8737
|
});
|
|
8503
8738
|
}
|
|
8739
|
+
purgeInboxMessagesForChannels(agentId, channelIds, reason = "server_purge") {
|
|
8740
|
+
const channelIdSet = new Set(channelIds.filter((id) => typeof id === "string" && id.length > 0));
|
|
8741
|
+
if (channelIdSet.size === 0) return { removedCount: 0 };
|
|
8742
|
+
const purge = (messages) => {
|
|
8743
|
+
if (!messages || messages.length === 0) return 0;
|
|
8744
|
+
let removed = 0;
|
|
8745
|
+
const retained = messages.filter((message) => {
|
|
8746
|
+
const shouldRemove = channelIdSet.has(message.channel_id);
|
|
8747
|
+
if (shouldRemove) removed += 1;
|
|
8748
|
+
return !shouldRemove;
|
|
8749
|
+
});
|
|
8750
|
+
if (removed > 0) {
|
|
8751
|
+
messages.splice(0, messages.length, ...retained);
|
|
8752
|
+
}
|
|
8753
|
+
return removed;
|
|
8754
|
+
};
|
|
8755
|
+
const active = this.agents.get(agentId);
|
|
8756
|
+
const removedActive = purge(active?.inbox);
|
|
8757
|
+
const startingInbox = this.startingInboxes.get(agentId);
|
|
8758
|
+
const removedStarting = purge(startingInbox);
|
|
8759
|
+
if (startingInbox && startingInbox.length === 0) {
|
|
8760
|
+
this.startingInboxes.delete(agentId);
|
|
8761
|
+
}
|
|
8762
|
+
if (active && removedActive > 0) {
|
|
8763
|
+
active.notifications.remove(removedActive);
|
|
8764
|
+
if (active.inbox.length === 0) {
|
|
8765
|
+
active.notifications.clear();
|
|
8766
|
+
}
|
|
8767
|
+
}
|
|
8768
|
+
const removedCount = removedActive + removedStarting;
|
|
8769
|
+
this.recordDaemonTrace("daemon.agent.inbox.purged", {
|
|
8770
|
+
agentId,
|
|
8771
|
+
reason,
|
|
8772
|
+
channel_count: channelIdSet.size,
|
|
8773
|
+
removed_active_count: removedActive,
|
|
8774
|
+
removed_starting_count: removedStarting,
|
|
8775
|
+
removed_count: removedCount,
|
|
8776
|
+
active_inbox_count: active?.inbox.length ?? 0,
|
|
8777
|
+
starting_inbox_count: this.startingInboxes.get(agentId)?.length ?? 0
|
|
8778
|
+
});
|
|
8779
|
+
return { removedCount };
|
|
8780
|
+
}
|
|
8504
8781
|
createAgentProxyInboxCoordinator(agentId) {
|
|
8505
8782
|
return {
|
|
8506
8783
|
getBoundary: (target) => this.getVisibleBoundary(agentId, target),
|
|
@@ -8578,7 +8855,6 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8578
8855
|
target: input.target,
|
|
8579
8856
|
messageCount
|
|
8580
8857
|
}).entry;
|
|
8581
|
-
if (ap) ap.activityClientSeq += 1;
|
|
8582
8858
|
this.sendToServer({
|
|
8583
8859
|
type: "agent:activity",
|
|
8584
8860
|
agentId,
|
|
@@ -8586,7 +8862,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8586
8862
|
detail: ap?.lastActivityDetail || "",
|
|
8587
8863
|
entries: [entry],
|
|
8588
8864
|
launchId: ap?.launchId || void 0,
|
|
8589
|
-
clientSeq: ap
|
|
8865
|
+
clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0
|
|
8590
8866
|
});
|
|
8591
8867
|
}
|
|
8592
8868
|
recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
|
|
@@ -8832,7 +9108,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8832
9108
|
);
|
|
8833
9109
|
wakeMessage = void 0;
|
|
8834
9110
|
}
|
|
8835
|
-
const agentDataDir =
|
|
9111
|
+
const agentDataDir = path11.join(this.dataDir, agentId);
|
|
8836
9112
|
await mkdir(agentDataDir, { recursive: true });
|
|
8837
9113
|
let runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
8838
9114
|
const legacyRuntimeProfileControl = runtimeConfig.runtimeProfileControl?.kind === "migration" ? runtimeConfig.runtimeProfileControl : null;
|
|
@@ -8846,23 +9122,23 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8846
9122
|
);
|
|
8847
9123
|
runtimeConfig = { ...runtimeConfig, runtimeProfileControl: null };
|
|
8848
9124
|
}
|
|
8849
|
-
const memoryMdPath =
|
|
9125
|
+
const memoryMdPath = path11.join(agentDataDir, "MEMORY.md");
|
|
8850
9126
|
try {
|
|
8851
9127
|
await access(memoryMdPath);
|
|
8852
9128
|
} catch {
|
|
8853
9129
|
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
8854
9130
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
8855
9131
|
}
|
|
8856
|
-
const notesDir =
|
|
9132
|
+
const notesDir = path11.join(agentDataDir, "notes");
|
|
8857
9133
|
await mkdir(notesDir, { recursive: true });
|
|
8858
9134
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
8859
9135
|
const seedFiles = buildOnboardingSeedFiles();
|
|
8860
9136
|
for (const { relativePath, content } of seedFiles) {
|
|
8861
|
-
const fullPath =
|
|
9137
|
+
const fullPath = path11.join(agentDataDir, relativePath);
|
|
8862
9138
|
try {
|
|
8863
9139
|
await access(fullPath);
|
|
8864
9140
|
} catch {
|
|
8865
|
-
await mkdir(
|
|
9141
|
+
await mkdir(path11.dirname(fullPath), { recursive: true });
|
|
8866
9142
|
await writeFile(fullPath, content);
|
|
8867
9143
|
}
|
|
8868
9144
|
}
|
|
@@ -8970,7 +9246,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8970
9246
|
standingPrompt,
|
|
8971
9247
|
prompt,
|
|
8972
9248
|
workingDirectory: agentDataDir,
|
|
8973
|
-
chatBridgePath: this.chatBridgePath,
|
|
8974
9249
|
slockCliPath: this.slockCliPath,
|
|
8975
9250
|
daemonApiKey: this.daemonApiKey,
|
|
8976
9251
|
launchId: launchId || null,
|
|
@@ -8999,7 +9274,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8999
9274
|
runtimeTraceCounters: createRuntimeTraceCounters(),
|
|
9000
9275
|
lastActivity: "",
|
|
9001
9276
|
lastActivityDetail: "",
|
|
9002
|
-
activityClientSeq: 0,
|
|
9003
9277
|
recentStdout: [],
|
|
9004
9278
|
recentStderr: [],
|
|
9005
9279
|
lastRuntimeError: null,
|
|
@@ -9118,7 +9392,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9118
9392
|
const startupTimeoutTermination = ap.expectedTerminationReason === "startup_timeout";
|
|
9119
9393
|
const expectedTermination = Boolean(ap.expectedTerminationReason);
|
|
9120
9394
|
const stickyTerminalFailureDetail = classifyStickyTerminalFailure(ap);
|
|
9121
|
-
const processEndedCleanly = !stickyTerminalFailureDetail && (finalCode === 0 || expectedTermination && !ap.lastRuntimeError);
|
|
9395
|
+
const processEndedCleanly = !stickyTerminalFailureDetail && !startupTimeoutTermination && (finalCode === 0 || expectedTermination && !ap.lastRuntimeError);
|
|
9122
9396
|
const terminalFailureDetail = processEndedCleanly ? null : stickyTerminalFailureDetail ?? classifyTerminalFailure(ap);
|
|
9123
9397
|
const resumeRecoveryReason = resumeSessionRecoveryReason(ap);
|
|
9124
9398
|
const shouldColdStartResumeSession = resumeRecoveryReason !== null;
|
|
@@ -9527,6 +9801,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9527
9801
|
cleanupAgentCredentialProxy(agentId, ap.launchId);
|
|
9528
9802
|
this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
|
|
9529
9803
|
this.agents.delete(agentId);
|
|
9804
|
+
if (!silent) {
|
|
9805
|
+
this.activityClientSeqByAgent.delete(agentId);
|
|
9806
|
+
}
|
|
9530
9807
|
this.runtimeExitTraceAttrs.set(ap.runtime, {
|
|
9531
9808
|
stop_source: silent ? "daemon_internal" : "explicit_request",
|
|
9532
9809
|
stop_wait_requested: wait,
|
|
@@ -9538,7 +9815,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9538
9815
|
reason: silent ? "daemon_internal" : "explicit_request"
|
|
9539
9816
|
});
|
|
9540
9817
|
if (!silent) {
|
|
9541
|
-
this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId);
|
|
9818
|
+
this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId, "stop");
|
|
9542
9819
|
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
9543
9820
|
this.broadcastActivity(agentId, "offline", "Stopped");
|
|
9544
9821
|
logger.info(`[Agent ${agentId}] Stopped by request`);
|
|
@@ -9681,20 +9958,37 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9681
9958
|
}));
|
|
9682
9959
|
return true;
|
|
9683
9960
|
}
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9961
|
+
if (stickyTerminalFailure.actionRequired) {
|
|
9962
|
+
if (ap.lastRuntimeError && isAuthClassTerminalLine(ap.lastRuntimeError)) {
|
|
9963
|
+
ap.lastRuntimeError = null;
|
|
9964
|
+
}
|
|
9965
|
+
ap.recentStderr = ap.recentStderr.filter((line) => !isAuthClassTerminalLine(line));
|
|
9966
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9967
|
+
outcome: "user_turn_recover_from_sticky_terminal_error",
|
|
9968
|
+
accepted: true,
|
|
9969
|
+
process_present: true,
|
|
9970
|
+
runtime: ap.config.runtime,
|
|
9971
|
+
session_id_present: Boolean(ap.sessionId),
|
|
9972
|
+
launchId: ap.launchId || void 0,
|
|
9973
|
+
is_idle: ap.isIdle,
|
|
9974
|
+
inbox_count: ap.inbox.length
|
|
9975
|
+
}));
|
|
9976
|
+
} else {
|
|
9977
|
+
ap.inbox.push(message);
|
|
9978
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9979
|
+
outcome: "queued_terminal_runtime_error",
|
|
9980
|
+
accepted: true,
|
|
9981
|
+
process_present: true,
|
|
9982
|
+
runtime: ap.config.runtime,
|
|
9983
|
+
session_id_present: Boolean(ap.sessionId),
|
|
9984
|
+
launchId: ap.launchId || void 0,
|
|
9985
|
+
is_idle: ap.isIdle,
|
|
9986
|
+
inbox_count: ap.inbox.length
|
|
9987
|
+
}));
|
|
9988
|
+
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
9989
|
+
this.broadcastActivity(agentId, "error", stickyTerminalFailure.detail);
|
|
9990
|
+
return true;
|
|
9991
|
+
}
|
|
9698
9992
|
}
|
|
9699
9993
|
if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
9700
9994
|
if (transientDelivery) {
|
|
@@ -9862,7 +10156,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9862
10156
|
return true;
|
|
9863
10157
|
}
|
|
9864
10158
|
async resetWorkspace(agentId) {
|
|
9865
|
-
const agentDataDir =
|
|
10159
|
+
const agentDataDir = path11.join(this.dataDir, agentId);
|
|
9866
10160
|
try {
|
|
9867
10161
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
9868
10162
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -9923,7 +10217,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9923
10217
|
return result;
|
|
9924
10218
|
}
|
|
9925
10219
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
9926
|
-
const workspacePath =
|
|
10220
|
+
const workspacePath = path11.join(this.dataDir, agentId);
|
|
9927
10221
|
return {
|
|
9928
10222
|
agentId,
|
|
9929
10223
|
launchId,
|
|
@@ -10141,7 +10435,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10141
10435
|
});
|
|
10142
10436
|
span.end("ok", { attrs: { outcome: "agent_config_ack_sent" } });
|
|
10143
10437
|
}
|
|
10144
|
-
sendRuntimeProfileWireReport(report) {
|
|
10438
|
+
sendRuntimeProfileWireReport(report, source) {
|
|
10145
10439
|
const span = this.tracer.startSpan("daemon.runtime_profile.report.sent", {
|
|
10146
10440
|
surface: "daemon",
|
|
10147
10441
|
kind: "producer",
|
|
@@ -10149,6 +10443,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10149
10443
|
agentId: report.agentId,
|
|
10150
10444
|
launchId: report.launchId || void 0,
|
|
10151
10445
|
runtime: report.facts.runtime,
|
|
10446
|
+
report_source: source,
|
|
10152
10447
|
model_present: Boolean(report.facts.model),
|
|
10153
10448
|
session_ref_present: Boolean(report.facts.sessionRef),
|
|
10154
10449
|
workspace_ref_present: Boolean(report.facts.workspaceRef || report.facts.workspacePathRef)
|
|
@@ -10159,17 +10454,18 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10159
10454
|
agentId: report.agentId,
|
|
10160
10455
|
facts: report.facts,
|
|
10161
10456
|
launchId: report.launchId || void 0,
|
|
10162
|
-
traceparent: formatTraceparent(span.context)
|
|
10457
|
+
traceparent: formatTraceparent(span.context),
|
|
10458
|
+
source
|
|
10163
10459
|
});
|
|
10164
10460
|
span.end("ok");
|
|
10165
10461
|
}
|
|
10166
|
-
sendRuntimeProfileReportFor(agentId, config, sessionId, launchId) {
|
|
10167
|
-
this.sendRuntimeProfileWireReport(this.buildRuntimeProfileReport(agentId, config, sessionId, launchId));
|
|
10462
|
+
sendRuntimeProfileReportFor(agentId, config, sessionId, launchId, source) {
|
|
10463
|
+
this.sendRuntimeProfileWireReport(this.buildRuntimeProfileReport(agentId, config, sessionId, launchId), source);
|
|
10168
10464
|
}
|
|
10169
|
-
sendRuntimeProfileReport(agentId) {
|
|
10465
|
+
sendRuntimeProfileReport(agentId, source) {
|
|
10170
10466
|
const report = this.getAgentRuntimeProfileReport(agentId);
|
|
10171
10467
|
if (!report) return;
|
|
10172
|
-
this.sendRuntimeProfileWireReport(report);
|
|
10468
|
+
this.sendRuntimeProfileWireReport(report, source);
|
|
10173
10469
|
}
|
|
10174
10470
|
// Machine-level workspace scanning
|
|
10175
10471
|
async scanAllWorkspaces() {
|
|
@@ -10180,7 +10476,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10180
10476
|
}
|
|
10181
10477
|
// Workspace file browsing
|
|
10182
10478
|
async getFileTree(agentId, dirPath) {
|
|
10183
|
-
const agentDir =
|
|
10479
|
+
const agentDir = path11.join(this.dataDir, agentId);
|
|
10184
10480
|
try {
|
|
10185
10481
|
await stat2(agentDir);
|
|
10186
10482
|
} catch {
|
|
@@ -10188,8 +10484,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10188
10484
|
}
|
|
10189
10485
|
let targetDir = agentDir;
|
|
10190
10486
|
if (dirPath) {
|
|
10191
|
-
const resolved =
|
|
10192
|
-
if (!resolved.startsWith(agentDir +
|
|
10487
|
+
const resolved = path11.resolve(agentDir, dirPath);
|
|
10488
|
+
if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
|
|
10193
10489
|
return [];
|
|
10194
10490
|
}
|
|
10195
10491
|
targetDir = resolved;
|
|
@@ -10197,14 +10493,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10197
10493
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
10198
10494
|
}
|
|
10199
10495
|
async readFile(agentId, filePath) {
|
|
10200
|
-
const agentDir =
|
|
10201
|
-
const resolved =
|
|
10202
|
-
if (!resolved.startsWith(agentDir +
|
|
10496
|
+
const agentDir = path11.join(this.dataDir, agentId);
|
|
10497
|
+
const resolved = path11.resolve(agentDir, filePath);
|
|
10498
|
+
if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
|
|
10203
10499
|
throw new Error("Access denied");
|
|
10204
10500
|
}
|
|
10205
10501
|
const info = await stat2(resolved);
|
|
10206
10502
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
10207
|
-
const ext =
|
|
10503
|
+
const ext = path11.extname(resolved).toLowerCase();
|
|
10208
10504
|
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
10209
10505
|
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
10210
10506
|
const content = await readFile(resolved, "utf-8");
|
|
@@ -10239,13 +10535,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10239
10535
|
const agent = this.agents.get(agentId);
|
|
10240
10536
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
10241
10537
|
const home = os5.homedir();
|
|
10242
|
-
const workspaceDir =
|
|
10538
|
+
const workspaceDir = path11.join(this.dataDir, agentId);
|
|
10243
10539
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
10244
10540
|
const globalResults = await Promise.all(
|
|
10245
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
10541
|
+
paths.global.map((p) => this.scanSkillsDir(path11.join(home, p)))
|
|
10246
10542
|
);
|
|
10247
10543
|
const workspaceResults = await Promise.all(
|
|
10248
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
10544
|
+
paths.workspace.map((p) => this.scanSkillsDir(path11.join(workspaceDir, p)))
|
|
10249
10545
|
);
|
|
10250
10546
|
const dedup = (skills) => {
|
|
10251
10547
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -10274,7 +10570,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10274
10570
|
const skills = [];
|
|
10275
10571
|
for (const entry of entries) {
|
|
10276
10572
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
10277
|
-
const skillMd =
|
|
10573
|
+
const skillMd = path11.join(dir, entry.name, "SKILL.md");
|
|
10278
10574
|
try {
|
|
10279
10575
|
const content = await readFile(skillMd, "utf-8");
|
|
10280
10576
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -10285,7 +10581,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10285
10581
|
} else if (entry.name.endsWith(".md")) {
|
|
10286
10582
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
10287
10583
|
try {
|
|
10288
|
-
const content = await readFile(
|
|
10584
|
+
const content = await readFile(path11.join(dir, entry.name), "utf-8");
|
|
10289
10585
|
const skill = this.parseSkillMd(cmdName, content);
|
|
10290
10586
|
skill.sourcePath = dir;
|
|
10291
10587
|
skills.push(skill);
|
|
@@ -10334,7 +10630,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10334
10630
|
if (!hasToolStart) {
|
|
10335
10631
|
entries.push({ kind: "status", activity, detail });
|
|
10336
10632
|
}
|
|
10337
|
-
if (ap) ap.activityClientSeq += 1;
|
|
10338
10633
|
this.sendToServer({
|
|
10339
10634
|
type: "agent:activity",
|
|
10340
10635
|
agentId,
|
|
@@ -10342,7 +10637,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10342
10637
|
detail,
|
|
10343
10638
|
entries,
|
|
10344
10639
|
launchId: launchIdOverride || ap?.launchId || void 0,
|
|
10345
|
-
clientSeq: ap
|
|
10640
|
+
clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0
|
|
10346
10641
|
});
|
|
10347
10642
|
if (ap) {
|
|
10348
10643
|
ap.lastActivity = activity;
|
|
@@ -10354,14 +10649,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10354
10649
|
this.recordRuntimeTraceEvent(agentId, ap, "activity.heartbeat.sent", {
|
|
10355
10650
|
activity: ap.lastActivity
|
|
10356
10651
|
});
|
|
10357
|
-
ap.activityClientSeq += 1;
|
|
10358
10652
|
this.sendToServer({
|
|
10359
10653
|
type: "agent:activity",
|
|
10360
10654
|
agentId,
|
|
10361
10655
|
activity: ap.lastActivity,
|
|
10362
10656
|
detail: ap.lastActivityDetail,
|
|
10363
10657
|
launchId: launchIdOverride || ap.launchId || void 0,
|
|
10364
|
-
clientSeq:
|
|
10658
|
+
clientSeq: this.nextActivityClientSeq(agentId)
|
|
10365
10659
|
});
|
|
10366
10660
|
}, ACTIVITY_HEARTBEAT_MS);
|
|
10367
10661
|
}
|
|
@@ -10398,7 +10692,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10398
10692
|
const ap = this.agents.get(agentId);
|
|
10399
10693
|
const activity = ap?.lastActivity || "offline";
|
|
10400
10694
|
const detail = ap?.lastActivityDetail || (ap ? "" : "Agent not running");
|
|
10401
|
-
if (ap) ap.activityClientSeq += 1;
|
|
10402
10695
|
this.sendToServer({
|
|
10403
10696
|
type: "agent:activity",
|
|
10404
10697
|
agentId,
|
|
@@ -10406,7 +10699,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10406
10699
|
detail,
|
|
10407
10700
|
launchId: ap?.launchId || void 0,
|
|
10408
10701
|
probeId,
|
|
10409
|
-
clientSeq: ap
|
|
10702
|
+
clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0
|
|
10410
10703
|
});
|
|
10411
10704
|
}
|
|
10412
10705
|
flushPendingTrajectory(agentId) {
|
|
@@ -10608,8 +10901,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10608
10901
|
runtime_profile_key_hash: control.keyHash || void 0,
|
|
10609
10902
|
runtime_profile_key_present: control.keyPresent,
|
|
10610
10903
|
runtime_profile_pending_age_ms: pendingAgeMs,
|
|
10611
|
-
runtime_profile_requires_ack: false
|
|
10612
|
-
runtime_profile_migration_done_observed: control.kind === "migration" ? control.migrationDoneToolObserved : void 0
|
|
10904
|
+
runtime_profile_requires_ack: false
|
|
10613
10905
|
};
|
|
10614
10906
|
}
|
|
10615
10907
|
activateRuntimeProfileTurnControl(ap, control) {
|
|
@@ -10621,16 +10913,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10621
10913
|
if (!notification) return null;
|
|
10622
10914
|
return runtimeProfileTurnControl(notification.kind, notification.key, source);
|
|
10623
10915
|
}
|
|
10624
|
-
noteRuntimeProfileToolCall(agentId, ap, toolName) {
|
|
10625
|
-
const control = ap.runtimeProfileTurnControl;
|
|
10626
|
-
if (!control || control.kind !== "migration") return;
|
|
10627
|
-
if (!toolName.includes("runtime_profile_migration_done")) return;
|
|
10628
|
-
control.migrationDoneToolObserved = true;
|
|
10629
|
-
this.recordRuntimeTraceEvent(agentId, ap, "runtime_profile.migration_done_tool.observed", {
|
|
10630
|
-
...this.runtimeProfileTurnControlTraceAttrs(control),
|
|
10631
|
-
tool: toolName
|
|
10632
|
-
});
|
|
10633
|
-
}
|
|
10634
10916
|
finalizeRuntimeProfileTurnControl(agentId, ap, terminal) {
|
|
10635
10917
|
const control = ap.runtimeProfileTurnControl;
|
|
10636
10918
|
if (!control) return {};
|
|
@@ -10639,7 +10921,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10639
10921
|
return {
|
|
10640
10922
|
...attrs,
|
|
10641
10923
|
runtime_profile_turn_terminal: terminal,
|
|
10642
|
-
runtime_profile_turn_outcome: control.kind === "migration" ?
|
|
10924
|
+
runtime_profile_turn_outcome: control.kind === "migration" ? "reset_session_notice" : "notice_only"
|
|
10643
10925
|
};
|
|
10644
10926
|
}
|
|
10645
10927
|
startRuntimeTrace(agentId, ap, reason, messages, inputTraceAttrs = {}) {
|
|
@@ -11018,7 +11300,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11018
11300
|
case "session_init":
|
|
11019
11301
|
if (ap) ap.sessionId = event.sessionId;
|
|
11020
11302
|
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId, launchId: ap?.launchId || void 0 });
|
|
11021
|
-
this.sendRuntimeProfileReport(agentId);
|
|
11303
|
+
this.sendRuntimeProfileReport(agentId, "session_init");
|
|
11022
11304
|
break;
|
|
11023
11305
|
case "thinking": {
|
|
11024
11306
|
this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
|
|
@@ -11044,7 +11326,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11044
11326
|
const invocation = normalizeToolDisplayInvocation(event.name, event.input);
|
|
11045
11327
|
if (ap) {
|
|
11046
11328
|
const reduction = reduceApmGatedToolUse(ap.gatedSteering, { kind: "tool_call" });
|
|
11047
|
-
this.noteRuntimeProfileToolCall(agentId, ap, invocation.toolName);
|
|
11048
11329
|
this.recordRuntimeTraceEvent(agentId, ap, "tool.call.started", { tool: invocation.toolName });
|
|
11049
11330
|
this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, {
|
|
11050
11331
|
event: "tool_call",
|
|
@@ -11157,7 +11438,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11157
11438
|
}
|
|
11158
11439
|
if (event.sessionId) {
|
|
11159
11440
|
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId, launchId: ap?.launchId || void 0 });
|
|
11160
|
-
this.sendRuntimeProfileReport(agentId);
|
|
11441
|
+
this.sendRuntimeProfileReport(agentId, "turn_end");
|
|
11161
11442
|
}
|
|
11162
11443
|
break;
|
|
11163
11444
|
case "error": {
|
|
@@ -11625,8 +11906,8 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
11625
11906
|
const nodes = [];
|
|
11626
11907
|
for (const entry of entries) {
|
|
11627
11908
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
11628
|
-
const fullPath =
|
|
11629
|
-
const relativePath =
|
|
11909
|
+
const fullPath = path11.join(dir, entry.name);
|
|
11910
|
+
const relativePath = path11.relative(rootDir, fullPath);
|
|
11630
11911
|
let info;
|
|
11631
11912
|
try {
|
|
11632
11913
|
info = await stat2(fullPath);
|
|
@@ -11917,7 +12198,7 @@ var ReminderCache = class {
|
|
|
11917
12198
|
logger.warn(`[ReminderCache] Invalid fireAt for ${job.reminderId}: ${job.fireAt}`);
|
|
11918
12199
|
return null;
|
|
11919
12200
|
}
|
|
11920
|
-
const
|
|
12201
|
+
const delay2 = Math.max(0, Math.min(this.maxDelayMs, fireAt - this.clock.now()));
|
|
11921
12202
|
return this.clock.setTimeout(() => {
|
|
11922
12203
|
this.entries.delete(job.reminderId);
|
|
11923
12204
|
try {
|
|
@@ -11925,15 +12206,15 @@ var ReminderCache = class {
|
|
|
11925
12206
|
} catch (err) {
|
|
11926
12207
|
logger.error(`[ReminderCache] onFire threw for ${job.reminderId}`, err);
|
|
11927
12208
|
}
|
|
11928
|
-
},
|
|
12209
|
+
}, delay2);
|
|
11929
12210
|
}
|
|
11930
12211
|
};
|
|
11931
12212
|
|
|
11932
12213
|
// src/machineLock.ts
|
|
11933
12214
|
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
11934
|
-
import { mkdirSync as
|
|
12215
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
11935
12216
|
import os6 from "os";
|
|
11936
|
-
import
|
|
12217
|
+
import path12 from "path";
|
|
11937
12218
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
11938
12219
|
var DaemonMachineLockConflictError = class extends Error {
|
|
11939
12220
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -11955,7 +12236,7 @@ function resolveDefaultMachineStateRoot() {
|
|
|
11955
12236
|
return resolveSlockHomePath("machines");
|
|
11956
12237
|
}
|
|
11957
12238
|
function ownerPath(lockDir) {
|
|
11958
|
-
return
|
|
12239
|
+
return path12.join(lockDir, "owner.json");
|
|
11959
12240
|
}
|
|
11960
12241
|
function readOwner(lockDir) {
|
|
11961
12242
|
try {
|
|
@@ -11985,13 +12266,13 @@ function acquireDaemonMachineLock(options) {
|
|
|
11985
12266
|
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
11986
12267
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
11987
12268
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
11988
|
-
const machineDir =
|
|
11989
|
-
const lockDir =
|
|
12269
|
+
const machineDir = path12.join(rootDir, lockId);
|
|
12270
|
+
const lockDir = path12.join(machineDir, "daemon.lock");
|
|
11990
12271
|
const token = randomUUID4();
|
|
11991
|
-
|
|
12272
|
+
mkdirSync4(machineDir, { recursive: true });
|
|
11992
12273
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
11993
12274
|
try {
|
|
11994
|
-
|
|
12275
|
+
mkdirSync4(lockDir);
|
|
11995
12276
|
const owner = {
|
|
11996
12277
|
pid: process.pid,
|
|
11997
12278
|
token,
|
|
@@ -12001,7 +12282,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
12001
12282
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
12002
12283
|
};
|
|
12003
12284
|
try {
|
|
12004
|
-
|
|
12285
|
+
writeFileSync5(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
|
|
12005
12286
|
`, { mode: 384 });
|
|
12006
12287
|
} catch (err) {
|
|
12007
12288
|
rmSync3(lockDir, { recursive: true, force: true });
|
|
@@ -12016,7 +12297,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
12016
12297
|
if (currentOwner?.pid === process.pid && currentOwner.token === token) {
|
|
12017
12298
|
const released = { ...currentOwner, pid: 0 };
|
|
12018
12299
|
try {
|
|
12019
|
-
|
|
12300
|
+
writeFileSync5(ownerPath(lockDir), `${JSON.stringify(released, null, 2)}
|
|
12020
12301
|
`, {
|
|
12021
12302
|
mode: 384
|
|
12022
12303
|
});
|
|
@@ -12046,8 +12327,8 @@ function acquireDaemonMachineLock(options) {
|
|
|
12046
12327
|
}
|
|
12047
12328
|
|
|
12048
12329
|
// src/localTraceSink.ts
|
|
12049
|
-
import { appendFileSync, mkdirSync as
|
|
12050
|
-
import
|
|
12330
|
+
import { appendFileSync, mkdirSync as mkdirSync5, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
12331
|
+
import path13 from "path";
|
|
12051
12332
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
12052
12333
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
12053
12334
|
var DEFAULT_MAX_FILES = 8;
|
|
@@ -12084,7 +12365,7 @@ var LocalRotatingTraceSink = class {
|
|
|
12084
12365
|
currentSize = 0;
|
|
12085
12366
|
sequence = 0;
|
|
12086
12367
|
constructor(options) {
|
|
12087
|
-
this.traceDir =
|
|
12368
|
+
this.traceDir = path13.join(options.machineDir, "traces");
|
|
12088
12369
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
12089
12370
|
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
12090
12371
|
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
@@ -12110,15 +12391,15 @@ var LocalRotatingTraceSink = class {
|
|
|
12110
12391
|
return this.currentFile;
|
|
12111
12392
|
}
|
|
12112
12393
|
ensureFile(nextBytes) {
|
|
12113
|
-
|
|
12394
|
+
mkdirSync5(this.traceDir, { recursive: true, mode: 448 });
|
|
12114
12395
|
const nowMs = this.nowMsProvider();
|
|
12115
12396
|
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
12116
12397
|
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
12117
|
-
this.currentFile =
|
|
12398
|
+
this.currentFile = path13.join(
|
|
12118
12399
|
this.traceDir,
|
|
12119
12400
|
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
12120
12401
|
);
|
|
12121
|
-
|
|
12402
|
+
writeFileSync6(this.currentFile, "", { flag: "a", mode: 384 });
|
|
12122
12403
|
this.currentSize = statSync3(this.currentFile).size;
|
|
12123
12404
|
this.currentFileOpenedAtMs = nowMs;
|
|
12124
12405
|
this.pruneOldFiles();
|
|
@@ -12129,7 +12410,7 @@ var LocalRotatingTraceSink = class {
|
|
|
12129
12410
|
const excess = files.length - this.maxFiles;
|
|
12130
12411
|
if (excess <= 0) return;
|
|
12131
12412
|
for (const file of files.slice(0, excess)) {
|
|
12132
|
-
rmSync4(
|
|
12413
|
+
rmSync4(path13.join(this.traceDir, file), { force: true });
|
|
12133
12414
|
}
|
|
12134
12415
|
}
|
|
12135
12416
|
};
|
|
@@ -12220,11 +12501,108 @@ function isDiagnosticErrorAttr(key) {
|
|
|
12220
12501
|
import { createHash as createHash6, randomUUID as randomUUID5 } from "crypto";
|
|
12221
12502
|
import { gzipSync } from "zlib";
|
|
12222
12503
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
12223
|
-
import
|
|
12504
|
+
import path14 from "path";
|
|
12505
|
+
|
|
12506
|
+
// src/chatBridgeRequest.ts
|
|
12507
|
+
var DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS = Number.parseInt(
|
|
12508
|
+
process.env.SLOCK_CHAT_BRIDGE_TOOL_TIMEOUT_MS || "",
|
|
12509
|
+
10
|
|
12510
|
+
) || 6e4;
|
|
12511
|
+
var ChatBridgeToolTimeoutError = class extends Error {
|
|
12512
|
+
toolName;
|
|
12513
|
+
target;
|
|
12514
|
+
timeoutMs;
|
|
12515
|
+
durationMs;
|
|
12516
|
+
constructor(toolName, target, timeoutMs, durationMs) {
|
|
12517
|
+
super(`${toolName} timed out after ${timeoutMs}ms${target ? ` (target: ${target})` : ""}`);
|
|
12518
|
+
this.name = "ChatBridgeToolTimeoutError";
|
|
12519
|
+
this.toolName = toolName;
|
|
12520
|
+
this.target = target;
|
|
12521
|
+
this.timeoutMs = timeoutMs;
|
|
12522
|
+
this.durationMs = durationMs;
|
|
12523
|
+
}
|
|
12524
|
+
};
|
|
12525
|
+
function describeError(err) {
|
|
12526
|
+
if (err instanceof Error) return `${err.name}: ${err.message}`;
|
|
12527
|
+
return String(err);
|
|
12528
|
+
}
|
|
12529
|
+
async function executeJsonRequest(url, init, {
|
|
12530
|
+
toolName,
|
|
12531
|
+
target = null,
|
|
12532
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
|
|
12533
|
+
fetchImpl,
|
|
12534
|
+
now = () => Date.now(),
|
|
12535
|
+
warn = (message) => logger.warn(message)
|
|
12536
|
+
}) {
|
|
12537
|
+
const startedAt = now();
|
|
12538
|
+
const timeoutController = new AbortController();
|
|
12539
|
+
const signals = [timeoutController.signal];
|
|
12540
|
+
if (init.signal) signals.push(init.signal);
|
|
12541
|
+
const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
12542
|
+
const timeout = setTimeout(() => {
|
|
12543
|
+
timeoutController.abort();
|
|
12544
|
+
}, timeoutMs);
|
|
12545
|
+
timeout.unref?.();
|
|
12546
|
+
try {
|
|
12547
|
+
const response = await fetchImpl(url, { ...init, signal });
|
|
12548
|
+
const data = await response.json();
|
|
12549
|
+
return { response, data, durationMs: now() - startedAt };
|
|
12550
|
+
} catch (err) {
|
|
12551
|
+
const durationMs = now() - startedAt;
|
|
12552
|
+
if (timeoutController.signal.aborted && !init.signal?.aborted) {
|
|
12553
|
+
warn(
|
|
12554
|
+
`[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
|
|
12555
|
+
);
|
|
12556
|
+
throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
|
|
12557
|
+
}
|
|
12558
|
+
warn(
|
|
12559
|
+
`[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
|
|
12560
|
+
);
|
|
12561
|
+
throw err;
|
|
12562
|
+
} finally {
|
|
12563
|
+
clearTimeout(timeout);
|
|
12564
|
+
}
|
|
12565
|
+
}
|
|
12566
|
+
async function executeResponseRequest(url, init, {
|
|
12567
|
+
toolName,
|
|
12568
|
+
target = null,
|
|
12569
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
|
|
12570
|
+
fetchImpl,
|
|
12571
|
+
now = () => Date.now(),
|
|
12572
|
+
warn = (message) => logger.warn(message)
|
|
12573
|
+
}) {
|
|
12574
|
+
const startedAt = now();
|
|
12575
|
+
const timeoutController = new AbortController();
|
|
12576
|
+
const signals = [timeoutController.signal];
|
|
12577
|
+
if (init.signal) signals.push(init.signal);
|
|
12578
|
+
const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
12579
|
+
const timeout = setTimeout(() => {
|
|
12580
|
+
timeoutController.abort();
|
|
12581
|
+
}, timeoutMs);
|
|
12582
|
+
timeout.unref?.();
|
|
12583
|
+
try {
|
|
12584
|
+
const response = await fetchImpl(url, { ...init, signal });
|
|
12585
|
+
return { response, durationMs: now() - startedAt };
|
|
12586
|
+
} catch (err) {
|
|
12587
|
+
const durationMs = now() - startedAt;
|
|
12588
|
+
if (timeoutController.signal.aborted && !init.signal?.aborted) {
|
|
12589
|
+
warn(
|
|
12590
|
+
`[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
|
|
12591
|
+
);
|
|
12592
|
+
throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
|
|
12593
|
+
}
|
|
12594
|
+
warn(
|
|
12595
|
+
`[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
|
|
12596
|
+
);
|
|
12597
|
+
throw err;
|
|
12598
|
+
} finally {
|
|
12599
|
+
clearTimeout(timeout);
|
|
12600
|
+
}
|
|
12601
|
+
}
|
|
12224
12602
|
|
|
12225
12603
|
// src/directUploadCapability.ts
|
|
12226
|
-
function joinUrl(base,
|
|
12227
|
-
return `${base.replace(/\/+$/, "")}${
|
|
12604
|
+
function joinUrl(base, path16) {
|
|
12605
|
+
return `${base.replace(/\/+$/, "")}${path16}`;
|
|
12228
12606
|
}
|
|
12229
12607
|
function jsonHeaders(apiKey) {
|
|
12230
12608
|
return {
|
|
@@ -12443,7 +12821,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
12443
12821
|
}, nextMs);
|
|
12444
12822
|
}
|
|
12445
12823
|
async findUploadCandidates() {
|
|
12446
|
-
const traceDir =
|
|
12824
|
+
const traceDir = path14.join(this.options.machineDir, "traces");
|
|
12447
12825
|
let names;
|
|
12448
12826
|
try {
|
|
12449
12827
|
names = await readdir3(traceDir);
|
|
@@ -12455,8 +12833,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
12455
12833
|
const currentFile = this.options.currentFileProvider?.();
|
|
12456
12834
|
const candidates = [];
|
|
12457
12835
|
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
12458
|
-
const file =
|
|
12459
|
-
if (currentFile &&
|
|
12836
|
+
const file = path14.join(traceDir, name);
|
|
12837
|
+
if (currentFile && path14.resolve(file) === path14.resolve(currentFile)) continue;
|
|
12460
12838
|
if (await this.isUploaded(file)) continue;
|
|
12461
12839
|
try {
|
|
12462
12840
|
const info = await stat3(file);
|
|
@@ -12530,8 +12908,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
12530
12908
|
}
|
|
12531
12909
|
}
|
|
12532
12910
|
uploadStatePath(file) {
|
|
12533
|
-
const stateDir =
|
|
12534
|
-
return
|
|
12911
|
+
const stateDir = path14.join(this.options.machineDir, "trace-uploads");
|
|
12912
|
+
return path14.join(stateDir, `${path14.basename(file)}.uploaded.json`);
|
|
12535
12913
|
}
|
|
12536
12914
|
async isUploaded(file) {
|
|
12537
12915
|
try {
|
|
@@ -12543,9 +12921,9 @@ var DaemonTraceBundleUploader = class {
|
|
|
12543
12921
|
}
|
|
12544
12922
|
async markUploaded(file, metadata) {
|
|
12545
12923
|
const stateFile = this.uploadStatePath(file);
|
|
12546
|
-
await mkdir2(
|
|
12924
|
+
await mkdir2(path14.dirname(stateFile), { recursive: true, mode: 448 });
|
|
12547
12925
|
await writeFile2(stateFile, `${JSON.stringify({
|
|
12548
|
-
file:
|
|
12926
|
+
file: path14.basename(file),
|
|
12549
12927
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12550
12928
|
...metadata
|
|
12551
12929
|
}, null, 2)}
|
|
@@ -12567,7 +12945,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
|
|
|
12567
12945
|
var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
|
|
12568
12946
|
var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
|
|
12569
12947
|
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
|
|
12570
|
-
var DAEMON_CLI_USAGE =
|
|
12948
|
+
var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
|
|
12571
12949
|
var RunnerCredentialMintError2 = class extends Error {
|
|
12572
12950
|
code;
|
|
12573
12951
|
retryable;
|
|
@@ -12603,9 +12981,9 @@ function runnerCredentialErrorDetail2(error) {
|
|
|
12603
12981
|
async function waitForRunnerCredentialRetry2() {
|
|
12604
12982
|
await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
|
|
12605
12983
|
}
|
|
12606
|
-
function parseDaemonCliArgs(args) {
|
|
12984
|
+
function parseDaemonCliArgs(args, env = {}) {
|
|
12607
12985
|
let serverUrl = "";
|
|
12608
|
-
let apiKey = "";
|
|
12986
|
+
let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
|
|
12609
12987
|
for (let i = 0; i < args.length; i++) {
|
|
12610
12988
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
12611
12989
|
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
@@ -12621,24 +12999,14 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
12621
12999
|
return "0.0.0-dev";
|
|
12622
13000
|
}
|
|
12623
13001
|
}
|
|
12624
|
-
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
12625
|
-
const dirname = path17.dirname(fileURLToPath(moduleUrl));
|
|
12626
|
-
const jsPath = path17.resolve(dirname, "chat-bridge.js");
|
|
12627
|
-
try {
|
|
12628
|
-
accessSync(jsPath);
|
|
12629
|
-
return jsPath;
|
|
12630
|
-
} catch {
|
|
12631
|
-
return path17.resolve(dirname, "chat-bridge.ts");
|
|
12632
|
-
}
|
|
12633
|
-
}
|
|
12634
13002
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
12635
|
-
const thisDir =
|
|
12636
|
-
const bundledDistPath =
|
|
13003
|
+
const thisDir = path15.dirname(fileURLToPath(moduleUrl));
|
|
13004
|
+
const bundledDistPath = path15.resolve(thisDir, "cli", "index.js");
|
|
12637
13005
|
try {
|
|
12638
13006
|
accessSync(bundledDistPath);
|
|
12639
13007
|
return bundledDistPath;
|
|
12640
13008
|
} catch {
|
|
12641
|
-
const workspaceDistPath =
|
|
13009
|
+
const workspaceDistPath = path15.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
12642
13010
|
accessSync(workspaceDistPath);
|
|
12643
13011
|
return workspaceDistPath;
|
|
12644
13012
|
}
|
|
@@ -12739,6 +13107,8 @@ function summarizeIncomingMessage(msg) {
|
|
|
12739
13107
|
return `(agent=${msg.agentId})`;
|
|
12740
13108
|
case "agent:deliver":
|
|
12741
13109
|
return `(agent=${msg.agentId}, seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`;
|
|
13110
|
+
case "agent:inbox:purge":
|
|
13111
|
+
return `(agent=${msg.agentId}, channels=${msg.channelIds.length}, reason=${msg.reason || "server_purge"})`;
|
|
12742
13112
|
case "agent:runtime_profile:migration":
|
|
12743
13113
|
return `(agent=${msg.agentId}, migration=${msg.migrationKey})`;
|
|
12744
13114
|
case "agent:runtime_profile:daemon_release_notice":
|
|
@@ -12773,7 +13143,6 @@ var DaemonCore = class {
|
|
|
12773
13143
|
// bundle version). Reported in `ready` so the server can surface the
|
|
12774
13144
|
// Computer's own version (distinct from the underlying daemonVersion).
|
|
12775
13145
|
computerVersion;
|
|
12776
|
-
chatBridgePath;
|
|
12777
13146
|
slockCliPath;
|
|
12778
13147
|
slockHome;
|
|
12779
13148
|
runtimeDetector;
|
|
@@ -12789,7 +13158,6 @@ var DaemonCore = class {
|
|
|
12789
13158
|
this.options = options;
|
|
12790
13159
|
this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
|
|
12791
13160
|
this.computerVersion = (process.env.SLOCK_COMPUTER_VERSION || "").trim() || null;
|
|
12792
|
-
this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
|
|
12793
13161
|
this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
|
|
12794
13162
|
this.slockHome = resolveSlockHome();
|
|
12795
13163
|
process.env[SLOCK_HOME_ENV] = this.slockHome;
|
|
@@ -12810,7 +13178,7 @@ var DaemonCore = class {
|
|
|
12810
13178
|
daemonVersion: this.daemonVersion,
|
|
12811
13179
|
computerVersion: this.computerVersion
|
|
12812
13180
|
};
|
|
12813
|
-
this.agentManager = options.agentManagerFactory ? options.agentManagerFactory(
|
|
13181
|
+
this.agentManager = options.agentManagerFactory ? options.agentManagerFactory((msg) => connection.send(msg), options.apiKey, agentManagerOptions) : new AgentProcessManager((msg) => connection.send(msg), options.apiKey, agentManagerOptions);
|
|
12814
13182
|
const connectionFactory = options.connectionFactory ?? ((connOptions) => new DaemonConnection(connOptions));
|
|
12815
13183
|
connection = connectionFactory({
|
|
12816
13184
|
serverUrl: options.serverUrl,
|
|
@@ -12825,7 +13193,7 @@ var DaemonCore = class {
|
|
|
12825
13193
|
}
|
|
12826
13194
|
resolveMachineStateRoot() {
|
|
12827
13195
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
12828
|
-
if (this.options.dataDir) return
|
|
13196
|
+
if (this.options.dataDir) return path15.join(path15.dirname(this.options.dataDir), "machines");
|
|
12829
13197
|
return resolveDefaultMachineStateRoot();
|
|
12830
13198
|
}
|
|
12831
13199
|
shouldEnableLocalTrace() {
|
|
@@ -12851,7 +13219,7 @@ var DaemonCore = class {
|
|
|
12851
13219
|
sink: this.localTraceSink
|
|
12852
13220
|
}));
|
|
12853
13221
|
this.agentManager.setTracer(this.tracer);
|
|
12854
|
-
this.agentManager.setCliTransportTraceDir(
|
|
13222
|
+
this.agentManager.setCliTransportTraceDir(path15.join(machineDir, "traces"));
|
|
12855
13223
|
}
|
|
12856
13224
|
installTraceBundleUploader(machineDir) {
|
|
12857
13225
|
if (!this.shouldEnableLocalTrace()) return;
|
|
@@ -13093,6 +13461,10 @@ var DaemonCore = class {
|
|
|
13093
13461
|
logger.info(`[Agent ${msg.agentId}] Workspace reset requested`);
|
|
13094
13462
|
this.agentManager.resetWorkspace(msg.agentId);
|
|
13095
13463
|
break;
|
|
13464
|
+
case "agent:inbox:purge":
|
|
13465
|
+
logger.info(`[Agent ${msg.agentId}] Inbox purge requested (${msg.channelIds.length} channels, reason=${msg.reason || "server_purge"})`);
|
|
13466
|
+
this.agentManager.purgeInboxMessagesForChannels(msg.agentId, msg.channelIds, msg.reason || "server_purge");
|
|
13467
|
+
break;
|
|
13096
13468
|
case "agent:deliver": {
|
|
13097
13469
|
const parent = parseTraceparent(msg.traceparent);
|
|
13098
13470
|
const span = this.tracer.startSpan("daemon.agent.delivery", {
|
|
@@ -13344,6 +13716,7 @@ var DaemonCore = class {
|
|
|
13344
13716
|
agentId: report.agentId,
|
|
13345
13717
|
launchId: report.launchId || void 0,
|
|
13346
13718
|
runtime: report.facts.runtime,
|
|
13719
|
+
report_source: "connect",
|
|
13347
13720
|
model_present: Boolean(report.facts.model),
|
|
13348
13721
|
session_ref_present: Boolean(report.facts.sessionRef),
|
|
13349
13722
|
workspace_ref_present: Boolean(report.facts.workspaceRef || report.facts.workspacePathRef)
|
|
@@ -13354,7 +13727,8 @@ var DaemonCore = class {
|
|
|
13354
13727
|
agentId: report.agentId,
|
|
13355
13728
|
facts: report.facts,
|
|
13356
13729
|
launchId: report.launchId || void 0,
|
|
13357
|
-
traceparent: formatTraceparent(span.context)
|
|
13730
|
+
traceparent: formatTraceparent(span.context),
|
|
13731
|
+
source: "connect"
|
|
13358
13732
|
});
|
|
13359
13733
|
span.end("ok");
|
|
13360
13734
|
}
|
|
@@ -13378,13 +13752,15 @@ var DaemonCore = class {
|
|
|
13378
13752
|
};
|
|
13379
13753
|
|
|
13380
13754
|
export {
|
|
13755
|
+
DAEMON_API_KEY_ENV,
|
|
13756
|
+
scrubDaemonAuthEnv,
|
|
13757
|
+
subscribeDaemonLogs,
|
|
13381
13758
|
resolveWorkspaceDirectoryPath,
|
|
13382
13759
|
scanWorkspaceDirectories,
|
|
13383
13760
|
deleteWorkspaceDirectory,
|
|
13384
13761
|
DAEMON_CLI_USAGE,
|
|
13385
13762
|
parseDaemonCliArgs,
|
|
13386
13763
|
readDaemonVersion,
|
|
13387
|
-
resolveChatBridgePath,
|
|
13388
13764
|
resolveSlockCliPath,
|
|
13389
13765
|
detectRuntimes,
|
|
13390
13766
|
DaemonCore
|