@slock-ai/daemon 0.56.1 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-RIBO24KM.js → chunk-DQN3TW2I.js} +921 -647
- package/dist/cli/index.js +69 -11
- package/dist/core.js +2 -6
- package/dist/index.js +1 -2
- 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
|
|
|
@@ -1633,150 +1848,54 @@ Messages you receive have a single RFC 5424-style structured data header followe
|
|
|
1633
1848
|
[target=dm:@richard msg=22222222 time=2026-03-15T01:00:02 type=human] @richard: hey, can you help?
|
|
1634
1849
|
[target=#general:00000000 msg=33333333 time=2026-03-15T01:00:03 type=human] @richard: thread reply
|
|
1635
1850
|
[target=dm:@richard:22222222 msg=44444444 time=2026-03-15T01:00:04 type=human] @richard: DM thread reply
|
|
1636
|
-
\`\`\`
|
|
1637
|
-
|
|
1638
|
-
Prompt examples use obvious placeholder IDs such as \`00000000\`, \`11111111\`,
|
|
1639
|
-
and \`22222222\`. They show the shape of a real message ID but are not actual
|
|
1640
|
-
messages. Do not cite them as evidence; use only IDs from messages you actually
|
|
1641
|
-
received or read.
|
|
1642
|
-
|
|
1643
|
-
Header fields:
|
|
1644
|
-
- \`target=\` \u2014 where the message came from. Reuse as the \`target\` parameter when replying.
|
|
1645
|
-
- \`msg=\` \u2014 message short ID (first 8 chars of UUID). Use as thread suffix to start/reply in a thread.
|
|
1646
|
-
- \`time=\` \u2014 timestamp.
|
|
1647
|
-
- \`type=\` \u2014 sender kind. Values are \`human\`, \`agent\`, or \`system\`.
|
|
1648
|
-
|
|
1649
|
-
\`type=system\` messages announce state changes in the channel (task events, channel archived/unarchived, etc.). They are informational \u2014 don't reply to them unless they clearly request action (e.g. a task was just assigned to you). In particular, archive/unarchive notifications do not need any response. If a channel is archived, further writes there will be rejected.
|
|
1650
|
-
|
|
1651
|
-
${sendingMessagesSection}
|
|
1652
|
-
|
|
1653
|
-
${reminderSection}
|
|
1654
|
-
|
|
1655
|
-
${threadsSection}
|
|
1656
|
-
|
|
1657
|
-
${discoverySection}
|
|
1658
|
-
|
|
1659
|
-
${channelAwarenessSection}
|
|
1660
|
-
|
|
1661
|
-
${thirdPartyIntegrationsSection}
|
|
1662
|
-
|
|
1663
|
-
${readingHistorySection}
|
|
1664
|
-
|
|
1665
|
-
${historicalReferenceSection}
|
|
1666
|
-
|
|
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.
|
|
1851
|
+
\`\`\`
|
|
1713
1852
|
|
|
1714
|
-
|
|
1853
|
+
Prompt examples use obvious placeholder IDs such as \`00000000\`, \`11111111\`,
|
|
1854
|
+
and \`22222222\`. They show the shape of a real message ID but are not actual
|
|
1855
|
+
messages. Do not cite them as evidence; use only IDs from messages you actually
|
|
1856
|
+
received or read.
|
|
1715
1857
|
|
|
1716
|
-
|
|
1858
|
+
Header fields:
|
|
1859
|
+
- \`target=\` \u2014 where the message came from. Reuse as the \`target\` parameter when replying.
|
|
1860
|
+
- \`msg=\` \u2014 message short ID (first 8 chars of UUID). Use as thread suffix to start/reply in a thread.
|
|
1861
|
+
- \`time=\` \u2014 timestamp.
|
|
1862
|
+
- \`type=\` \u2014 sender kind. Values are \`human\`, \`agent\`, or \`system\`.
|
|
1717
1863
|
|
|
1718
|
-
|
|
1864
|
+
\`type=system\` messages announce state changes in the channel (task events, channel archived/unarchived, etc.). They are informational \u2014 don't reply to them unless they clearly request action (e.g. a task was just assigned to you). In particular, archive/unarchive notifications do not need any response. If a channel is archived, further writes there will be rejected.
|
|
1719
1865
|
|
|
1720
|
-
|
|
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\`
|
|
1866
|
+
${sendingMessagesSection}
|
|
1723
1867
|
|
|
1724
|
-
|
|
1868
|
+
${reminderSection}
|
|
1725
1869
|
|
|
1726
|
-
|
|
1870
|
+
${threadsSection}
|
|
1727
1871
|
|
|
1728
|
-
|
|
1872
|
+
${discoverySection}
|
|
1729
1873
|
|
|
1730
|
-
|
|
1874
|
+
${channelAwarenessSection}
|
|
1731
1875
|
|
|
1732
|
-
|
|
1733
|
-
# <Your Name>
|
|
1876
|
+
${thirdPartyIntegrationsSection}
|
|
1734
1877
|
|
|
1735
|
-
|
|
1736
|
-
<your role definition, evolved over time>
|
|
1878
|
+
${readingHistorySection}
|
|
1737
1879
|
|
|
1738
|
-
|
|
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
|
-
- ...
|
|
1880
|
+
${historicalReferenceSection}
|
|
1743
1881
|
|
|
1744
|
-
|
|
1745
|
-
- Currently working on: <brief summary>
|
|
1746
|
-
- Last interaction: <brief summary>
|
|
1747
|
-
\`\`\`
|
|
1882
|
+
${tasksSection}
|
|
1748
1883
|
|
|
1749
|
-
|
|
1884
|
+
${cliGuideSections.splittingTasks}
|
|
1750
1885
|
|
|
1751
|
-
|
|
1886
|
+
${cliGuideSections.mentions}
|
|
1752
1887
|
|
|
1753
|
-
|
|
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.
|
|
1888
|
+
${cliGuideSections.communicationStyle}
|
|
1759
1889
|
|
|
1760
|
-
|
|
1890
|
+
${cliGuideSections.conversationEtiquette}
|
|
1761
1891
|
|
|
1762
|
-
|
|
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.
|
|
1892
|
+
${cliGuideSections.formattingMentionsChannels}
|
|
1771
1893
|
|
|
1772
|
-
|
|
1894
|
+
${cliGuideSections.formattingUrls}
|
|
1773
1895
|
|
|
1774
|
-
|
|
1896
|
+
${cliGuideSections.workspaceAndMemory}
|
|
1775
1897
|
|
|
1776
|
-
|
|
1777
|
-
- **Before a long task**, write a brief "Active Context" note in MEMORY.md so you can resume if interrupted mid-task.
|
|
1778
|
-
- **After completing work**, update your notes and MEMORY.md index so nothing is lost.
|
|
1779
|
-
- 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.
|
|
1898
|
+
${cliGuideSections.compactionSafety}
|
|
1780
1899
|
|
|
1781
1900
|
## Capabilities
|
|
1782
1901
|
|
|
@@ -2190,6 +2309,160 @@ function stripUndefined(value) {
|
|
|
2190
2309
|
return value;
|
|
2191
2310
|
}
|
|
2192
2311
|
|
|
2312
|
+
// src/proxy.ts
|
|
2313
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
2314
|
+
import { ProxyAgent } from "undici";
|
|
2315
|
+
var fetchDispatcherCache = /* @__PURE__ */ new Map();
|
|
2316
|
+
function getFetchPreResponseTimeoutMs(env) {
|
|
2317
|
+
const parsed = Number.parseInt(env.SLOCK_DAEMON_FETCH_PRE_RESPONSE_TIMEOUT_MS || "", 10);
|
|
2318
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 3e4;
|
|
2319
|
+
}
|
|
2320
|
+
function getDefaultPort(protocol) {
|
|
2321
|
+
switch (protocol) {
|
|
2322
|
+
case "https:":
|
|
2323
|
+
case "wss:":
|
|
2324
|
+
return "443";
|
|
2325
|
+
case "http:":
|
|
2326
|
+
case "ws:":
|
|
2327
|
+
return "80";
|
|
2328
|
+
default:
|
|
2329
|
+
return "";
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
function hostMatchesNoProxyEntry(hostname, ruleHost) {
|
|
2333
|
+
if (!ruleHost) return false;
|
|
2334
|
+
const normalizedRule = ruleHost.replace(/^\*\./, ".").replace(/^\./, "").toLowerCase();
|
|
2335
|
+
const normalizedHost = hostname.toLowerCase();
|
|
2336
|
+
return normalizedHost === normalizedRule || normalizedHost.endsWith(`.${normalizedRule}`);
|
|
2337
|
+
}
|
|
2338
|
+
function getProxyUrlForTarget(targetUrl, env) {
|
|
2339
|
+
const protocol = new URL(targetUrl).protocol;
|
|
2340
|
+
switch (protocol) {
|
|
2341
|
+
case "wss:":
|
|
2342
|
+
return env.WSS_PROXY || env.wss_proxy || env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
|
|
2343
|
+
case "ws:":
|
|
2344
|
+
return env.WS_PROXY || env.ws_proxy || env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
|
|
2345
|
+
case "https:":
|
|
2346
|
+
return env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
|
|
2347
|
+
case "http:":
|
|
2348
|
+
return env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
|
|
2349
|
+
default:
|
|
2350
|
+
return env.ALL_PROXY || env.all_proxy;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
function shouldBypassProxy(targetUrl, env) {
|
|
2354
|
+
const rawNoProxy = env.NO_PROXY || env.no_proxy;
|
|
2355
|
+
if (!rawNoProxy) return false;
|
|
2356
|
+
const url = new URL(targetUrl);
|
|
2357
|
+
const hostname = url.hostname.toLowerCase();
|
|
2358
|
+
const port = url.port || getDefaultPort(url.protocol);
|
|
2359
|
+
return rawNoProxy.split(",").map((entry) => entry.trim()).filter(Boolean).some((entry) => {
|
|
2360
|
+
if (entry === "*") return true;
|
|
2361
|
+
const [ruleHost, rulePort] = entry.split(":", 2);
|
|
2362
|
+
if (rulePort && rulePort !== port) return false;
|
|
2363
|
+
return hostMatchesNoProxyEntry(hostname, ruleHost);
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
function buildWebSocketOptions(wsUrl, env) {
|
|
2367
|
+
const proxyUrl = getProxyUrlForTarget(wsUrl, env);
|
|
2368
|
+
if (!proxyUrl) return void 0;
|
|
2369
|
+
if (shouldBypassProxy(wsUrl, env)) return void 0;
|
|
2370
|
+
return {
|
|
2371
|
+
agent: new HttpsProxyAgent(proxyUrl)
|
|
2372
|
+
};
|
|
2373
|
+
}
|
|
2374
|
+
function resolveProxyUrl(targetUrl, env) {
|
|
2375
|
+
const proxyUrl = getProxyUrlForTarget(targetUrl, env);
|
|
2376
|
+
if (!proxyUrl) return void 0;
|
|
2377
|
+
if (shouldBypassProxy(targetUrl, env)) return void 0;
|
|
2378
|
+
return proxyUrl;
|
|
2379
|
+
}
|
|
2380
|
+
function buildFetchDispatcher(targetUrl, env) {
|
|
2381
|
+
const proxyUrl = resolveProxyUrl(targetUrl, env);
|
|
2382
|
+
if (!proxyUrl) return void 0;
|
|
2383
|
+
const cached = fetchDispatcherCache.get(proxyUrl);
|
|
2384
|
+
if (cached) return cached;
|
|
2385
|
+
const timeoutMs = getFetchPreResponseTimeoutMs(env);
|
|
2386
|
+
const dispatcher = new ProxyAgent({
|
|
2387
|
+
uri: proxyUrl,
|
|
2388
|
+
// All three are pre-response and body-agnostic (see getFetchPreResponseTimeoutMs):
|
|
2389
|
+
// headersTimeout = headers-hang leg; requestTls.timeout = CONNECT-tunnel
|
|
2390
|
+
// establish leg; connect.timeout = socket to the proxy itself (defensive).
|
|
2391
|
+
connect: { timeout: timeoutMs },
|
|
2392
|
+
requestTls: { timeout: timeoutMs },
|
|
2393
|
+
headersTimeout: timeoutMs
|
|
2394
|
+
});
|
|
2395
|
+
fetchDispatcherCache.set(proxyUrl, dispatcher);
|
|
2396
|
+
return dispatcher;
|
|
2397
|
+
}
|
|
2398
|
+
function evictFetchDispatcher(targetUrl, env) {
|
|
2399
|
+
const proxyUrl = resolveProxyUrl(targetUrl, env);
|
|
2400
|
+
if (!proxyUrl) return false;
|
|
2401
|
+
const cached = fetchDispatcherCache.get(proxyUrl);
|
|
2402
|
+
if (!cached) return false;
|
|
2403
|
+
fetchDispatcherCache.delete(proxyUrl);
|
|
2404
|
+
void Promise.resolve().then(() => cached.close()).catch(() => cached.destroy?.(new Error("evicted"))).catch(() => {
|
|
2405
|
+
});
|
|
2406
|
+
return true;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
// src/daemonFetch.ts
|
|
2410
|
+
function withDaemonFetchProxy(input, init = {}, env = process.env) {
|
|
2411
|
+
const dispatcher = buildFetchDispatcher(input.toString(), env);
|
|
2412
|
+
return dispatcher ? { ...init, dispatcher } : init;
|
|
2413
|
+
}
|
|
2414
|
+
async function daemonFetch(input, init, env = process.env) {
|
|
2415
|
+
try {
|
|
2416
|
+
return await fetch(input, withDaemonFetchProxy(input, init, env));
|
|
2417
|
+
} catch (err) {
|
|
2418
|
+
evictFetchDispatcher(input.toString(), env);
|
|
2419
|
+
throw err;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
// src/logger.ts
|
|
2424
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
2425
|
+
function timestamp() {
|
|
2426
|
+
const d = /* @__PURE__ */ new Date();
|
|
2427
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
2428
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
2429
|
+
}
|
|
2430
|
+
function format(level, msg) {
|
|
2431
|
+
return `${timestamp()} [${level}] ${msg}`;
|
|
2432
|
+
}
|
|
2433
|
+
function emit(event) {
|
|
2434
|
+
for (const listener of listeners) {
|
|
2435
|
+
listener(event);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
function subscribeDaemonLogs(listener) {
|
|
2439
|
+
listeners.add(listener);
|
|
2440
|
+
return () => {
|
|
2441
|
+
listeners.delete(listener);
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
var logger = {
|
|
2445
|
+
info(msg) {
|
|
2446
|
+
const line = format("INFO", msg);
|
|
2447
|
+
console.log(line);
|
|
2448
|
+
emit({ level: "INFO", line, message: msg });
|
|
2449
|
+
},
|
|
2450
|
+
warn(msg) {
|
|
2451
|
+
const line = format("WARN", msg);
|
|
2452
|
+
console.warn(line);
|
|
2453
|
+
emit({ level: "WARN", line, message: msg });
|
|
2454
|
+
},
|
|
2455
|
+
error(msg, err) {
|
|
2456
|
+
const line = format("ERROR", msg);
|
|
2457
|
+
if (err) {
|
|
2458
|
+
console.error(line, err);
|
|
2459
|
+
} else {
|
|
2460
|
+
console.error(line);
|
|
2461
|
+
}
|
|
2462
|
+
emit({ level: "ERROR", line, message: msg, error: err });
|
|
2463
|
+
}
|
|
2464
|
+
};
|
|
2465
|
+
|
|
2193
2466
|
// src/agentCredentialProxy.ts
|
|
2194
2467
|
var registrations = /* @__PURE__ */ new Map();
|
|
2195
2468
|
var proxyServerState = null;
|
|
@@ -4383,7 +4656,7 @@ var CodexDriver = class {
|
|
|
4383
4656
|
};
|
|
4384
4657
|
communication = {
|
|
4385
4658
|
chat: "slock_cli",
|
|
4386
|
-
runtimeControl: "
|
|
4659
|
+
runtimeControl: "none"
|
|
4387
4660
|
};
|
|
4388
4661
|
session = {
|
|
4389
4662
|
recovery: "resume_or_fresh"
|
|
@@ -4400,50 +4673,6 @@ var CodexDriver = class {
|
|
|
4400
4673
|
probe() {
|
|
4401
4674
|
return probeCodex();
|
|
4402
4675
|
}
|
|
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
4676
|
buildThreadRequest(ctx) {
|
|
4448
4677
|
const threadParams = {
|
|
4449
4678
|
cwd: ctx.workingDirectory,
|
|
@@ -4497,7 +4726,6 @@ var CodexDriver = class {
|
|
|
4497
4726
|
this.initialTurnStarted = false;
|
|
4498
4727
|
this.normalizer.reset({ threadId: ctx.config.sessionId || null });
|
|
4499
4728
|
const args = ["app-server", "--listen", "stdio://"];
|
|
4500
|
-
args.push(...this.buildRuntimeActionsConfigArgs(ctx));
|
|
4501
4729
|
const { command, args: spawnArgs } = resolveCodexSpawn(args);
|
|
4502
4730
|
const proc = spawn2(command, spawnArgs, {
|
|
4503
4731
|
cwd: ctx.workingDirectory,
|
|
@@ -4784,8 +5012,6 @@ var AntigravityDriver = class {
|
|
|
4784
5012
|
|
|
4785
5013
|
// src/drivers/copilot.ts
|
|
4786
5014
|
import { spawn as spawn4 } from "child_process";
|
|
4787
|
-
import path6 from "path";
|
|
4788
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
4789
5015
|
async function buildCopilotSpawnEnv(ctx) {
|
|
4790
5016
|
return (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
|
|
4791
5017
|
}
|
|
@@ -4799,7 +5025,7 @@ var CopilotDriver = class {
|
|
|
4799
5025
|
};
|
|
4800
5026
|
communication = {
|
|
4801
5027
|
chat: "slock_cli",
|
|
4802
|
-
runtimeControl: "
|
|
5028
|
+
runtimeControl: "none"
|
|
4803
5029
|
};
|
|
4804
5030
|
session = {
|
|
4805
5031
|
recovery: "resume_or_fresh"
|
|
@@ -4814,43 +5040,14 @@ var CopilotDriver = class {
|
|
|
4814
5040
|
usesSlockCliForCommunication = true;
|
|
4815
5041
|
sessionId = null;
|
|
4816
5042
|
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
5043
|
async spawn(ctx) {
|
|
4843
5044
|
this.sessionId = ctx.config.sessionId || null;
|
|
4844
5045
|
this.sessionAnnounced = false;
|
|
4845
|
-
const mcpConfigPath = path6.join(ctx.workingDirectory, ".slock-copilot-mcp.json");
|
|
4846
|
-
writeFileSync3(mcpConfigPath, this.buildRuntimeActionsMcpConfig(ctx), "utf8");
|
|
4847
5046
|
const args = [
|
|
4848
5047
|
"--output-format",
|
|
4849
5048
|
"json",
|
|
4850
5049
|
"--allow-all-tools",
|
|
4851
5050
|
"--allow-all-paths",
|
|
4852
|
-
"--additional-mcp-config",
|
|
4853
|
-
`@${mcpConfigPath}`,
|
|
4854
5051
|
"-p",
|
|
4855
5052
|
ctx.prompt
|
|
4856
5053
|
];
|
|
@@ -4959,8 +5156,6 @@ var CopilotDriver = class {
|
|
|
4959
5156
|
|
|
4960
5157
|
// src/drivers/cursor.ts
|
|
4961
5158
|
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
5159
|
async function buildCursorSpawnEnv(ctx, deps = {}) {
|
|
4965
5160
|
const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
4966
5161
|
return withWindowsUserEnvironment(spawnEnv, deps);
|
|
@@ -4975,7 +5170,7 @@ var CursorDriver = class {
|
|
|
4975
5170
|
};
|
|
4976
5171
|
communication = {
|
|
4977
5172
|
chat: "slock_cli",
|
|
4978
|
-
runtimeControl: "
|
|
5173
|
+
runtimeControl: "none"
|
|
4979
5174
|
};
|
|
4980
5175
|
session = {
|
|
4981
5176
|
recovery: "resume_or_fresh"
|
|
@@ -4988,38 +5183,7 @@ var CursorDriver = class {
|
|
|
4988
5183
|
mcpToolPrefix = "mcp__chat__";
|
|
4989
5184
|
busyDeliveryMode = "none";
|
|
4990
5185
|
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
5186
|
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
5187
|
const args = [
|
|
5024
5188
|
"--print",
|
|
5025
5189
|
"--output-format",
|
|
@@ -5165,8 +5329,8 @@ function runCursorModelsCommand() {
|
|
|
5165
5329
|
|
|
5166
5330
|
// src/drivers/gemini.ts
|
|
5167
5331
|
import { execFileSync as execFileSync3, spawn as spawn6 } from "child_process";
|
|
5168
|
-
import { existsSync as
|
|
5169
|
-
import
|
|
5332
|
+
import { existsSync as existsSync4 } from "fs";
|
|
5333
|
+
import path6 from "path";
|
|
5170
5334
|
async function buildGeminiSpawnEnv(ctx, platform = process.platform) {
|
|
5171
5335
|
const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" }, platform);
|
|
5172
5336
|
const launchEnvVars = runtimeConfigToLaunchFields(ctx.config).envVars;
|
|
@@ -5208,9 +5372,9 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
5208
5372
|
return { command: resolveCommandOnPath("gemini", deps) ?? "gemini", args: commandArgs };
|
|
5209
5373
|
}
|
|
5210
5374
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
5211
|
-
const existsSyncFn = deps.existsSyncFn ??
|
|
5375
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync4;
|
|
5212
5376
|
const env = deps.env ?? process.env;
|
|
5213
|
-
const winPath =
|
|
5377
|
+
const winPath = path6.win32;
|
|
5214
5378
|
let geminiEntry = null;
|
|
5215
5379
|
try {
|
|
5216
5380
|
const globalRoot = normalizeExecOutput2(execFileSyncFn("npm", ["root", "-g"], {
|
|
@@ -5254,7 +5418,7 @@ var GeminiDriver = class {
|
|
|
5254
5418
|
};
|
|
5255
5419
|
communication = {
|
|
5256
5420
|
chat: "slock_cli",
|
|
5257
|
-
runtimeControl: "
|
|
5421
|
+
runtimeControl: "none"
|
|
5258
5422
|
};
|
|
5259
5423
|
session = {
|
|
5260
5424
|
recovery: "resume_or_fresh"
|
|
@@ -5272,7 +5436,6 @@ var GeminiDriver = class {
|
|
|
5272
5436
|
async spawn(ctx) {
|
|
5273
5437
|
this.sessionId = ctx.config.sessionId || null;
|
|
5274
5438
|
this.sessionAnnounced = false;
|
|
5275
|
-
this.writeGeminiSettings(ctx);
|
|
5276
5439
|
const { command, args } = resolveGeminiSpawn(buildGeminiArgs(ctx.config));
|
|
5277
5440
|
const spawnEnv = await buildGeminiSpawnEnv(ctx);
|
|
5278
5441
|
const proc = spawn6(command, args, {
|
|
@@ -5345,49 +5508,17 @@ var GeminiDriver = class {
|
|
|
5345
5508
|
messageNotificationStyle: "poll"
|
|
5346
5509
|
});
|
|
5347
5510
|
}
|
|
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
5511
|
};
|
|
5380
5512
|
|
|
5381
5513
|
// src/drivers/kimi.ts
|
|
5382
5514
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
5383
5515
|
import { spawn as spawn7 } from "child_process";
|
|
5384
|
-
import { existsSync as
|
|
5516
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5385
5517
|
import os3 from "os";
|
|
5386
|
-
import
|
|
5518
|
+
import path7 from "path";
|
|
5387
5519
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
5388
5520
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
5389
5521
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
5390
|
-
var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
|
|
5391
5522
|
function parseToolArguments(raw) {
|
|
5392
5523
|
if (typeof raw !== "string") return raw;
|
|
5393
5524
|
try {
|
|
@@ -5412,7 +5543,7 @@ var KimiDriver = class {
|
|
|
5412
5543
|
};
|
|
5413
5544
|
communication = {
|
|
5414
5545
|
chat: "slock_cli",
|
|
5415
|
-
runtimeControl: "
|
|
5546
|
+
runtimeControl: "none"
|
|
5416
5547
|
};
|
|
5417
5548
|
session = {
|
|
5418
5549
|
recovery: "resume_or_fresh"
|
|
@@ -5428,58 +5559,28 @@ var KimiDriver = class {
|
|
|
5428
5559
|
sessionId = null;
|
|
5429
5560
|
sessionAnnounced = false;
|
|
5430
5561
|
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
5562
|
async spawn(ctx) {
|
|
5448
5563
|
const isResume = !!ctx.config.sessionId;
|
|
5449
5564
|
this.sessionId = ctx.config.sessionId || randomUUID2();
|
|
5450
5565
|
this.sessionAnnounced = false;
|
|
5451
5566
|
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, [
|
|
5567
|
+
const systemPromptPath = path7.join(ctx.workingDirectory, KIMI_SYSTEM_PROMPT_FILE);
|
|
5568
|
+
const agentFilePath = path7.join(ctx.workingDirectory, KIMI_AGENT_FILE);
|
|
5569
|
+
if (!isResume || !existsSync5(systemPromptPath)) {
|
|
5570
|
+
writeFileSync3(systemPromptPath, ctx.prompt, "utf8");
|
|
5571
|
+
}
|
|
5572
|
+
writeFileSync3(agentFilePath, [
|
|
5462
5573
|
"version: 1",
|
|
5463
5574
|
"agent:",
|
|
5464
5575
|
" extend: default",
|
|
5465
5576
|
` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
|
|
5466
5577
|
""
|
|
5467
5578
|
].join("\n"), "utf8");
|
|
5468
|
-
writeFileSync6(mcpConfigPath, JSON.stringify({
|
|
5469
|
-
mcpServers: {
|
|
5470
|
-
chat: {
|
|
5471
|
-
command,
|
|
5472
|
-
args: bridgeArgs
|
|
5473
|
-
}
|
|
5474
|
-
}
|
|
5475
|
-
}), "utf8");
|
|
5476
5579
|
const args = [
|
|
5477
5580
|
"--wire",
|
|
5478
5581
|
"--yolo",
|
|
5479
5582
|
"--agent-file",
|
|
5480
5583
|
agentFilePath,
|
|
5481
|
-
"--mcp-config-file",
|
|
5482
|
-
mcpConfigPath,
|
|
5483
5584
|
"--session",
|
|
5484
5585
|
this.sessionId
|
|
5485
5586
|
];
|
|
@@ -5612,7 +5713,7 @@ var KimiDriver = class {
|
|
|
5612
5713
|
}
|
|
5613
5714
|
};
|
|
5614
5715
|
function detectKimiModels(home = os3.homedir()) {
|
|
5615
|
-
const configPath =
|
|
5716
|
+
const configPath = path7.join(home, ".kimi", "config.toml");
|
|
5616
5717
|
let raw;
|
|
5617
5718
|
try {
|
|
5618
5719
|
raw = readFileSync3(configPath, "utf8");
|
|
@@ -5639,9 +5740,9 @@ function detectKimiModels(home = os3.homedir()) {
|
|
|
5639
5740
|
|
|
5640
5741
|
// src/drivers/opencode.ts
|
|
5641
5742
|
import { spawn as spawn8, spawnSync as spawnSync2 } from "child_process";
|
|
5642
|
-
import { existsSync as
|
|
5743
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
5643
5744
|
import os4 from "os";
|
|
5644
|
-
import
|
|
5745
|
+
import path8 from "path";
|
|
5645
5746
|
var CHAT_MCP_SERVER_NAME = "chat";
|
|
5646
5747
|
var CHAT_MCP_TOOL_PREFIX = `${CHAT_MCP_SERVER_NAME}_`;
|
|
5647
5748
|
var SLOCK_AGENT_NAME = "slock";
|
|
@@ -5656,23 +5757,6 @@ var OPENCODE_PROVIDER_LABELS = {
|
|
|
5656
5757
|
deepseek: "DeepSeek",
|
|
5657
5758
|
fusecode: "FuseCode"
|
|
5658
5759
|
};
|
|
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
5760
|
function parseOpenCodeConfigContent(raw) {
|
|
5677
5761
|
if (!raw) return {};
|
|
5678
5762
|
try {
|
|
@@ -5689,7 +5773,7 @@ function parseUserOpenCodeConfig(ctx) {
|
|
|
5689
5773
|
return parseOpenCodeConfigContent(raw);
|
|
5690
5774
|
}
|
|
5691
5775
|
function readLocalOpenCodeConfig(home = os4.homedir()) {
|
|
5692
|
-
const configPath =
|
|
5776
|
+
const configPath = path8.join(home, ".config", "opencode", "opencode.json");
|
|
5693
5777
|
try {
|
|
5694
5778
|
return parseOpenCodeConfigContent(readFileSync4(configPath, "utf8"));
|
|
5695
5779
|
} catch {
|
|
@@ -5764,14 +5848,7 @@ function buildOpenCodeConfig(ctx, home = os4.homedir()) {
|
|
|
5764
5848
|
prompt: ctx.standingPrompt
|
|
5765
5849
|
}
|
|
5766
5850
|
},
|
|
5767
|
-
mcp:
|
|
5768
|
-
...recordField(userConfig.mcp),
|
|
5769
|
-
[CHAT_MCP_SERVER_NAME]: {
|
|
5770
|
-
type: "local",
|
|
5771
|
-
command: buildChatBridgeCommand(ctx),
|
|
5772
|
-
enabled: true
|
|
5773
|
-
}
|
|
5774
|
-
}
|
|
5851
|
+
mcp: recordField(userConfig.mcp)
|
|
5775
5852
|
};
|
|
5776
5853
|
}
|
|
5777
5854
|
async function buildOpenCodeLaunchOptions(ctx, home = os4.homedir(), version = null) {
|
|
@@ -5894,11 +5971,11 @@ function runOpenCodeModelsCommand(home, deps = {}) {
|
|
|
5894
5971
|
};
|
|
5895
5972
|
}
|
|
5896
5973
|
function isWindowsCommandShim(commandPath) {
|
|
5897
|
-
const ext =
|
|
5974
|
+
const ext = path8.win32.extname(commandPath).toLowerCase();
|
|
5898
5975
|
return ext === ".cmd" || ext === ".bat";
|
|
5899
5976
|
}
|
|
5900
5977
|
function opencodePackageEntryCandidates(packageRoot) {
|
|
5901
|
-
const winPath =
|
|
5978
|
+
const winPath = path8.win32;
|
|
5902
5979
|
return [
|
|
5903
5980
|
winPath.join(packageRoot, "bin", "opencode.exe"),
|
|
5904
5981
|
winPath.join(packageRoot, "bin", "opencode.js"),
|
|
@@ -5907,16 +5984,16 @@ function opencodePackageEntryCandidates(packageRoot) {
|
|
|
5907
5984
|
];
|
|
5908
5985
|
}
|
|
5909
5986
|
function openCodeSpecForEntry(entry, commandArgs) {
|
|
5910
|
-
if (
|
|
5987
|
+
if (path8.win32.extname(entry).toLowerCase() === ".exe") {
|
|
5911
5988
|
return { command: entry, args: commandArgs, shell: false };
|
|
5912
5989
|
}
|
|
5913
5990
|
return { command: process.execPath, args: [entry, ...commandArgs], shell: false };
|
|
5914
5991
|
}
|
|
5915
5992
|
function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
|
|
5916
|
-
const existsSyncFn = deps.existsSyncFn ??
|
|
5993
|
+
const existsSyncFn = deps.existsSyncFn ?? existsSync6;
|
|
5917
5994
|
const execFileSyncFn = deps.execFileSyncFn;
|
|
5918
5995
|
const env = deps.env ?? process.env;
|
|
5919
|
-
const winPath =
|
|
5996
|
+
const winPath = path8.win32;
|
|
5920
5997
|
const candidates = [];
|
|
5921
5998
|
if (execFileSyncFn) {
|
|
5922
5999
|
try {
|
|
@@ -5944,7 +6021,7 @@ function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
|
|
|
5944
6021
|
function extractWindowsShimTargets(commandPath, deps = {}) {
|
|
5945
6022
|
if (!isWindowsCommandShim(commandPath)) return [];
|
|
5946
6023
|
const readFileSyncFn = deps.readFileSyncFn ?? readFileSync4;
|
|
5947
|
-
const commandDir =
|
|
6024
|
+
const commandDir = path8.win32.dirname(commandPath);
|
|
5948
6025
|
let raw;
|
|
5949
6026
|
try {
|
|
5950
6027
|
raw = String(readFileSyncFn(commandPath, "utf8"));
|
|
@@ -5955,7 +6032,7 @@ function extractWindowsShimTargets(commandPath, deps = {}) {
|
|
|
5955
6032
|
const dp0Pattern = /%~dp0\\?([^"\r\n]*?opencode\.(?:exe|js|mjs|cjs))/gi;
|
|
5956
6033
|
for (const match of raw.matchAll(dp0Pattern)) {
|
|
5957
6034
|
const relative = match[1]?.replace(/^\\+/, "");
|
|
5958
|
-
if (relative) candidates.push(
|
|
6035
|
+
if (relative) candidates.push(path8.win32.normalize(path8.win32.join(commandDir, relative)));
|
|
5959
6036
|
}
|
|
5960
6037
|
return candidates;
|
|
5961
6038
|
}
|
|
@@ -5969,7 +6046,7 @@ function resolveOpenCodeSpawn(commandArgs, deps = {}) {
|
|
|
5969
6046
|
};
|
|
5970
6047
|
}
|
|
5971
6048
|
const command = resolveCommandOnPath("opencode", deps);
|
|
5972
|
-
if (command &&
|
|
6049
|
+
if (command && path8.win32.extname(command).toLowerCase() === ".exe") {
|
|
5973
6050
|
return { command, args: commandArgs, shell: false };
|
|
5974
6051
|
}
|
|
5975
6052
|
const packageEntry = resolveWindowsOpenCodePackageEntry(command, deps);
|
|
@@ -6013,7 +6090,7 @@ var OpenCodeDriver = class {
|
|
|
6013
6090
|
};
|
|
6014
6091
|
communication = {
|
|
6015
6092
|
chat: "slock_cli",
|
|
6016
|
-
runtimeControl: "
|
|
6093
|
+
runtimeControl: "none"
|
|
6017
6094
|
};
|
|
6018
6095
|
session = {
|
|
6019
6096
|
recovery: "resume_or_fresh"
|
|
@@ -6144,8 +6221,8 @@ var OpenCodeDriver = class {
|
|
|
6144
6221
|
// src/drivers/pi.ts
|
|
6145
6222
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6146
6223
|
import { EventEmitter } from "events";
|
|
6147
|
-
import { mkdirSync as
|
|
6148
|
-
import
|
|
6224
|
+
import { mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
6225
|
+
import path9 from "path";
|
|
6149
6226
|
import {
|
|
6150
6227
|
AuthStorage,
|
|
6151
6228
|
createBashTool,
|
|
@@ -6171,7 +6248,7 @@ function createPiSdkEventMappingState(sessionId = null) {
|
|
|
6171
6248
|
};
|
|
6172
6249
|
}
|
|
6173
6250
|
function buildPiSessionDir(workingDirectory) {
|
|
6174
|
-
return
|
|
6251
|
+
return path9.join(workingDirectory, PI_SESSION_DIR);
|
|
6175
6252
|
}
|
|
6176
6253
|
async function buildPiSpawnEnv(ctx) {
|
|
6177
6254
|
return (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
|
|
@@ -6193,7 +6270,7 @@ function findPiSessionFile(sessionDir, sessionId) {
|
|
|
6193
6270
|
}
|
|
6194
6271
|
const suffix = `_${sessionId}.jsonl`;
|
|
6195
6272
|
const match = entries.find((entry) => entry.endsWith(suffix));
|
|
6196
|
-
return match ?
|
|
6273
|
+
return match ? path9.join(sessionDir, match) : null;
|
|
6197
6274
|
}
|
|
6198
6275
|
function detectPiModelsFromRegistry(modelRegistry) {
|
|
6199
6276
|
const models = [];
|
|
@@ -6342,13 +6419,15 @@ var PI_RUNTIME_SESSION_DESCRIPTOR = {
|
|
|
6342
6419
|
busyDelivery: "direct",
|
|
6343
6420
|
postTurn: "keep_alive"
|
|
6344
6421
|
};
|
|
6422
|
+
var PI_IDLE_PROMPT_RETRY_MS = 25;
|
|
6423
|
+
var PI_IDLE_PROMPT_MAX_WAIT_MS = 1e3;
|
|
6345
6424
|
async function createPiAgentSessionForContext(ctx, sessionId) {
|
|
6346
6425
|
const sessionDir = buildPiSessionDir(ctx.workingDirectory);
|
|
6347
|
-
|
|
6426
|
+
mkdirSync2(sessionDir, { recursive: true });
|
|
6348
6427
|
const spawnEnv = await buildPiSpawnEnv(ctx);
|
|
6349
6428
|
const agentDir = spawnEnv.PI_CODING_AGENT_DIR || getAgentDir();
|
|
6350
|
-
const authStorage = AuthStorage.create(
|
|
6351
|
-
const modelRegistry = ModelRegistry.create(authStorage,
|
|
6429
|
+
const authStorage = AuthStorage.create(path9.join(agentDir, "auth.json"));
|
|
6430
|
+
const modelRegistry = ModelRegistry.create(authStorage, path9.join(agentDir, "models.json"));
|
|
6352
6431
|
const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
|
|
6353
6432
|
const model = resolvePiModelFromRegistry(launchRuntimeFields.model, modelRegistry);
|
|
6354
6433
|
if (launchRuntimeFields.model && launchRuntimeFields.model !== "default" && !model) {
|
|
@@ -6459,7 +6538,8 @@ var PiSdkRuntimeSession = class {
|
|
|
6459
6538
|
return { ok: true, acceptedAs: "steer" };
|
|
6460
6539
|
}
|
|
6461
6540
|
if (session.isStreaming) {
|
|
6462
|
-
|
|
6541
|
+
this.launchPromptAfterStreaming(input.text);
|
|
6542
|
+
return { ok: true, acceptedAs: "prompt" };
|
|
6463
6543
|
}
|
|
6464
6544
|
this.launchPrompt(input.text);
|
|
6465
6545
|
return { ok: true, acceptedAs: "prompt" };
|
|
@@ -6501,6 +6581,27 @@ var PiSdkRuntimeSession = class {
|
|
|
6501
6581
|
}
|
|
6502
6582
|
this.deferSdkCall(() => session.prompt(text));
|
|
6503
6583
|
}
|
|
6584
|
+
launchPromptAfterStreaming(text) {
|
|
6585
|
+
this.deferSdkCall(async () => {
|
|
6586
|
+
const session = this.session;
|
|
6587
|
+
if (!session) return;
|
|
6588
|
+
const ready = await this.waitForStreamingToClear(session);
|
|
6589
|
+
if (this.didClose || this.session !== session) {
|
|
6590
|
+
return;
|
|
6591
|
+
}
|
|
6592
|
+
await (ready ? session.prompt(text) : session.steer(text));
|
|
6593
|
+
});
|
|
6594
|
+
}
|
|
6595
|
+
async waitForStreamingToClear(session) {
|
|
6596
|
+
const deadline = Date.now() + PI_IDLE_PROMPT_MAX_WAIT_MS;
|
|
6597
|
+
while (!this.didClose && this.session === session && session.isStreaming) {
|
|
6598
|
+
if (Date.now() >= deadline) {
|
|
6599
|
+
return false;
|
|
6600
|
+
}
|
|
6601
|
+
await delay(PI_IDLE_PROMPT_RETRY_MS);
|
|
6602
|
+
}
|
|
6603
|
+
return !this.didClose && this.session === session && !session.isStreaming;
|
|
6604
|
+
}
|
|
6504
6605
|
deferSdkCall(invoke) {
|
|
6505
6606
|
setImmediate(() => {
|
|
6506
6607
|
if (this.didClose) return;
|
|
@@ -6612,6 +6713,12 @@ var PiDriver = class {
|
|
|
6612
6713
|
});
|
|
6613
6714
|
}
|
|
6614
6715
|
};
|
|
6716
|
+
function delay(ms) {
|
|
6717
|
+
return new Promise((resolve) => {
|
|
6718
|
+
const timer = setTimeout(resolve, ms);
|
|
6719
|
+
timer.unref?.();
|
|
6720
|
+
});
|
|
6721
|
+
}
|
|
6615
6722
|
|
|
6616
6723
|
// src/drivers/runtimeSession.ts
|
|
6617
6724
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
@@ -6770,7 +6877,7 @@ function getDriver(runtimeId) {
|
|
|
6770
6877
|
|
|
6771
6878
|
// src/workspaces.ts
|
|
6772
6879
|
import { readdir, rm, stat } from "fs/promises";
|
|
6773
|
-
import
|
|
6880
|
+
import path10 from "path";
|
|
6774
6881
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
6775
6882
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
6776
6883
|
}
|
|
@@ -6778,7 +6885,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
6778
6885
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
6779
6886
|
return null;
|
|
6780
6887
|
}
|
|
6781
|
-
return
|
|
6888
|
+
return path10.join(dataDir, directoryName);
|
|
6782
6889
|
}
|
|
6783
6890
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
6784
6891
|
return {
|
|
@@ -6827,7 +6934,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
6827
6934
|
return summary;
|
|
6828
6935
|
}
|
|
6829
6936
|
const childSummaries = await Promise.all(
|
|
6830
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
6937
|
+
entries.map((entry) => summarizeWorkspaceEntry(path10.join(dirPath, entry.name), entry))
|
|
6831
6938
|
);
|
|
6832
6939
|
for (const childSummary of childSummaries) {
|
|
6833
6940
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -6846,7 +6953,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
6846
6953
|
if (!entry.isDirectory()) {
|
|
6847
6954
|
return null;
|
|
6848
6955
|
}
|
|
6849
|
-
const dirPath =
|
|
6956
|
+
const dirPath = path10.join(dataDir, entry.name);
|
|
6850
6957
|
try {
|
|
6851
6958
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
6852
6959
|
return {
|
|
@@ -7116,6 +7223,11 @@ var RuntimeNotificationState = class {
|
|
|
7116
7223
|
clearPending() {
|
|
7117
7224
|
this.pendingCountValue = 0;
|
|
7118
7225
|
}
|
|
7226
|
+
remove(count) {
|
|
7227
|
+
if (!Number.isFinite(count) || count <= 0) return this.pendingCountValue;
|
|
7228
|
+
this.pendingCountValue = Math.max(0, this.pendingCountValue - Math.floor(count));
|
|
7229
|
+
return this.pendingCountValue;
|
|
7230
|
+
}
|
|
7119
7231
|
clearTimer() {
|
|
7120
7232
|
if (this.timerValue) {
|
|
7121
7233
|
clearTimeout(this.timerValue);
|
|
@@ -7304,12 +7416,12 @@ function findSessionJsonl(root, predicate) {
|
|
|
7304
7416
|
for (const entry of entries) {
|
|
7305
7417
|
if (++visited > maxEntries) return null;
|
|
7306
7418
|
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
7307
|
-
return
|
|
7419
|
+
return path11.join(dir, entry.name);
|
|
7308
7420
|
}
|
|
7309
7421
|
for (const entry of entries) {
|
|
7310
7422
|
if (++visited > maxEntries) return null;
|
|
7311
7423
|
if (!entry.isDirectory()) continue;
|
|
7312
|
-
const found = visit(
|
|
7424
|
+
const found = visit(path11.join(dir, entry.name), depth - 1);
|
|
7313
7425
|
if (found) return found;
|
|
7314
7426
|
}
|
|
7315
7427
|
return null;
|
|
@@ -7322,10 +7434,10 @@ function safeSessionFilename(value) {
|
|
|
7322
7434
|
}
|
|
7323
7435
|
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
7324
7436
|
try {
|
|
7325
|
-
const dir =
|
|
7326
|
-
|
|
7327
|
-
const filePath =
|
|
7328
|
-
|
|
7437
|
+
const dir = path11.join(fallbackDir, ".slock", "runtime-sessions");
|
|
7438
|
+
mkdirSync3(dir, { recursive: true });
|
|
7439
|
+
const filePath = path11.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
7440
|
+
writeFileSync4(filePath, JSON.stringify({
|
|
7329
7441
|
type: "runtime_session_handoff",
|
|
7330
7442
|
runtime,
|
|
7331
7443
|
sessionId,
|
|
@@ -7344,7 +7456,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
|
7344
7456
|
}
|
|
7345
7457
|
}
|
|
7346
7458
|
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
|
|
7347
|
-
const directPath =
|
|
7459
|
+
const directPath = path11.isAbsolute(sessionId) ? sessionId : null;
|
|
7348
7460
|
if (directPath) {
|
|
7349
7461
|
try {
|
|
7350
7462
|
if (statSync(directPath).isFile()) {
|
|
@@ -7353,7 +7465,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
|
|
|
7353
7465
|
} catch {
|
|
7354
7466
|
}
|
|
7355
7467
|
}
|
|
7356
|
-
const resolvedPath = runtime === "claude" ? findSessionJsonl(
|
|
7468
|
+
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
7469
|
if (!resolvedPath && fallbackDir) {
|
|
7358
7470
|
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
7359
7471
|
if (fallback) return fallback;
|
|
@@ -7956,8 +8068,7 @@ function runtimeProfileTurnControl(kind, key, source) {
|
|
|
7956
8068
|
source,
|
|
7957
8069
|
keyHash: hashRuntimeProfileKey(key) ?? null,
|
|
7958
8070
|
keyPresent: Boolean(key),
|
|
7959
|
-
injectedAtMs: Date.now()
|
|
7960
|
-
migrationDoneToolObserved: false
|
|
8071
|
+
injectedAtMs: Date.now()
|
|
7961
8072
|
};
|
|
7962
8073
|
}
|
|
7963
8074
|
function pushRecentLines(lines, chunk, maxLines, maxLineLength) {
|
|
@@ -8033,6 +8144,9 @@ function classifyStickyTerminalFailure(ap) {
|
|
|
8033
8144
|
if (isRuntimeStartTimeoutText(terminalFailure.detail)) return null;
|
|
8034
8145
|
return null;
|
|
8035
8146
|
}
|
|
8147
|
+
function isAuthClassTerminalLine(text) {
|
|
8148
|
+
return buildRuntimeErrorDiagnosticEnvelope(text).spanAttrs.runtime_error_action_required === true;
|
|
8149
|
+
}
|
|
8036
8150
|
function isProviderStreamFailureText(text) {
|
|
8037
8151
|
return /stream closed before response\.completed|error decoding response body/i.test(text);
|
|
8038
8152
|
}
|
|
@@ -8314,6 +8428,29 @@ var NATIVE_STANDING_PROMPT_STARTUP_INPUT = (
|
|
|
8314
8428
|
);
|
|
8315
8429
|
var AgentProcessManager = class _AgentProcessManager {
|
|
8316
8430
|
agents = /* @__PURE__ */ new Map();
|
|
8431
|
+
/**
|
|
8432
|
+
* Per-agent monotonic clientSeq for `agent:activity` upstream dedup.
|
|
8433
|
+
* Server dedupes ingest by `(launchId, clientSeq)` (#engineering:72283cf7
|
|
8434
|
+
* task #340 PR B). This counter is keyed by agentId at the MANAGER level —
|
|
8435
|
+
* NOT on the per-process `AgentProcess` — so it SURVIVES process recreation
|
|
8436
|
+
* (cold-start-resume / idle-auto-restart / queued-continuation /
|
|
8437
|
+
* runtime-profile restart). Those daemon-internal restarts reuse the
|
|
8438
|
+
* server-issued launchId but spawn a fresh process; if the counter reset to
|
|
8439
|
+
* 0 (as the old per-`ap` field did) the reused launchId's `(launchId,
|
|
8440
|
+
* clientSeq)` window would collide and the server would drop the
|
|
8441
|
+
* post-restart activity as stale — the "activity log stops updating after
|
|
8442
|
+
* error→restart" bug. Keeping it monotonic-per-agent and never resetting on
|
|
8443
|
+
* respawn guarantees a fresh dedup key under the same launchId. launchId is
|
|
8444
|
+
* decoupled: it stays the identity/gate token, this is the clock.
|
|
8445
|
+
* (#proj-o11y:a1e54b59 — RS dedup-epoch ⊥ launchId-identity separation.)
|
|
8446
|
+
*/
|
|
8447
|
+
activityClientSeqByAgent = /* @__PURE__ */ new Map();
|
|
8448
|
+
/** Next monotonic `agent:activity` clientSeq for this agent (never resets on respawn). */
|
|
8449
|
+
nextActivityClientSeq(agentId) {
|
|
8450
|
+
const next = (this.activityClientSeqByAgent.get(agentId) ?? 0) + 1;
|
|
8451
|
+
this.activityClientSeqByAgent.set(agentId, next);
|
|
8452
|
+
return next;
|
|
8453
|
+
}
|
|
8317
8454
|
agentsStarting = /* @__PURE__ */ new Set();
|
|
8318
8455
|
// Prevent concurrent starts of same agent
|
|
8319
8456
|
queuedAgentStarts = /* @__PURE__ */ new Map();
|
|
@@ -8327,7 +8464,6 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8327
8464
|
startingInboxes = /* @__PURE__ */ new Map();
|
|
8328
8465
|
/** Cached configs for agents whose process exited normally — enables auto-restart on next message */
|
|
8329
8466
|
idleAgentConfigs = /* @__PURE__ */ new Map();
|
|
8330
|
-
chatBridgePath;
|
|
8331
8467
|
slockCliPath;
|
|
8332
8468
|
sendToServer;
|
|
8333
8469
|
daemonApiKey;
|
|
@@ -8345,8 +8481,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8345
8481
|
agentVisibleMessageIds = /* @__PURE__ */ new Map();
|
|
8346
8482
|
daemonVersion;
|
|
8347
8483
|
computerVersion;
|
|
8348
|
-
constructor(
|
|
8349
|
-
this.chatBridgePath = chatBridgePath;
|
|
8484
|
+
constructor(sendToServer, daemonApiKey, opts) {
|
|
8350
8485
|
this.slockCliPath = opts.slockCliPath ?? "";
|
|
8351
8486
|
this.sendToServer = sendToServer;
|
|
8352
8487
|
this.daemonApiKey = daemonApiKey;
|
|
@@ -8501,6 +8636,48 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8501
8636
|
suppressed_pending_count: removedActive + removedStarting
|
|
8502
8637
|
});
|
|
8503
8638
|
}
|
|
8639
|
+
purgeInboxMessagesForChannels(agentId, channelIds, reason = "server_purge") {
|
|
8640
|
+
const channelIdSet = new Set(channelIds.filter((id) => typeof id === "string" && id.length > 0));
|
|
8641
|
+
if (channelIdSet.size === 0) return { removedCount: 0 };
|
|
8642
|
+
const purge = (messages) => {
|
|
8643
|
+
if (!messages || messages.length === 0) return 0;
|
|
8644
|
+
let removed = 0;
|
|
8645
|
+
const retained = messages.filter((message) => {
|
|
8646
|
+
const shouldRemove = channelIdSet.has(message.channel_id);
|
|
8647
|
+
if (shouldRemove) removed += 1;
|
|
8648
|
+
return !shouldRemove;
|
|
8649
|
+
});
|
|
8650
|
+
if (removed > 0) {
|
|
8651
|
+
messages.splice(0, messages.length, ...retained);
|
|
8652
|
+
}
|
|
8653
|
+
return removed;
|
|
8654
|
+
};
|
|
8655
|
+
const active = this.agents.get(agentId);
|
|
8656
|
+
const removedActive = purge(active?.inbox);
|
|
8657
|
+
const startingInbox = this.startingInboxes.get(agentId);
|
|
8658
|
+
const removedStarting = purge(startingInbox);
|
|
8659
|
+
if (startingInbox && startingInbox.length === 0) {
|
|
8660
|
+
this.startingInboxes.delete(agentId);
|
|
8661
|
+
}
|
|
8662
|
+
if (active && removedActive > 0) {
|
|
8663
|
+
active.notifications.remove(removedActive);
|
|
8664
|
+
if (active.inbox.length === 0) {
|
|
8665
|
+
active.notifications.clear();
|
|
8666
|
+
}
|
|
8667
|
+
}
|
|
8668
|
+
const removedCount = removedActive + removedStarting;
|
|
8669
|
+
this.recordDaemonTrace("daemon.agent.inbox.purged", {
|
|
8670
|
+
agentId,
|
|
8671
|
+
reason,
|
|
8672
|
+
channel_count: channelIdSet.size,
|
|
8673
|
+
removed_active_count: removedActive,
|
|
8674
|
+
removed_starting_count: removedStarting,
|
|
8675
|
+
removed_count: removedCount,
|
|
8676
|
+
active_inbox_count: active?.inbox.length ?? 0,
|
|
8677
|
+
starting_inbox_count: this.startingInboxes.get(agentId)?.length ?? 0
|
|
8678
|
+
});
|
|
8679
|
+
return { removedCount };
|
|
8680
|
+
}
|
|
8504
8681
|
createAgentProxyInboxCoordinator(agentId) {
|
|
8505
8682
|
return {
|
|
8506
8683
|
getBoundary: (target) => this.getVisibleBoundary(agentId, target),
|
|
@@ -8578,7 +8755,6 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8578
8755
|
target: input.target,
|
|
8579
8756
|
messageCount
|
|
8580
8757
|
}).entry;
|
|
8581
|
-
if (ap) ap.activityClientSeq += 1;
|
|
8582
8758
|
this.sendToServer({
|
|
8583
8759
|
type: "agent:activity",
|
|
8584
8760
|
agentId,
|
|
@@ -8586,7 +8762,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8586
8762
|
detail: ap?.lastActivityDetail || "",
|
|
8587
8763
|
entries: [entry],
|
|
8588
8764
|
launchId: ap?.launchId || void 0,
|
|
8589
|
-
clientSeq: ap
|
|
8765
|
+
clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0
|
|
8590
8766
|
});
|
|
8591
8767
|
}
|
|
8592
8768
|
recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
|
|
@@ -8832,7 +9008,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8832
9008
|
);
|
|
8833
9009
|
wakeMessage = void 0;
|
|
8834
9010
|
}
|
|
8835
|
-
const agentDataDir =
|
|
9011
|
+
const agentDataDir = path11.join(this.dataDir, agentId);
|
|
8836
9012
|
await mkdir(agentDataDir, { recursive: true });
|
|
8837
9013
|
let runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
8838
9014
|
const legacyRuntimeProfileControl = runtimeConfig.runtimeProfileControl?.kind === "migration" ? runtimeConfig.runtimeProfileControl : null;
|
|
@@ -8846,23 +9022,23 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8846
9022
|
);
|
|
8847
9023
|
runtimeConfig = { ...runtimeConfig, runtimeProfileControl: null };
|
|
8848
9024
|
}
|
|
8849
|
-
const memoryMdPath =
|
|
9025
|
+
const memoryMdPath = path11.join(agentDataDir, "MEMORY.md");
|
|
8850
9026
|
try {
|
|
8851
9027
|
await access(memoryMdPath);
|
|
8852
9028
|
} catch {
|
|
8853
9029
|
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
8854
9030
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
8855
9031
|
}
|
|
8856
|
-
const notesDir =
|
|
9032
|
+
const notesDir = path11.join(agentDataDir, "notes");
|
|
8857
9033
|
await mkdir(notesDir, { recursive: true });
|
|
8858
9034
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
8859
9035
|
const seedFiles = buildOnboardingSeedFiles();
|
|
8860
9036
|
for (const { relativePath, content } of seedFiles) {
|
|
8861
|
-
const fullPath =
|
|
9037
|
+
const fullPath = path11.join(agentDataDir, relativePath);
|
|
8862
9038
|
try {
|
|
8863
9039
|
await access(fullPath);
|
|
8864
9040
|
} catch {
|
|
8865
|
-
await mkdir(
|
|
9041
|
+
await mkdir(path11.dirname(fullPath), { recursive: true });
|
|
8866
9042
|
await writeFile(fullPath, content);
|
|
8867
9043
|
}
|
|
8868
9044
|
}
|
|
@@ -8970,7 +9146,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8970
9146
|
standingPrompt,
|
|
8971
9147
|
prompt,
|
|
8972
9148
|
workingDirectory: agentDataDir,
|
|
8973
|
-
chatBridgePath: this.chatBridgePath,
|
|
8974
9149
|
slockCliPath: this.slockCliPath,
|
|
8975
9150
|
daemonApiKey: this.daemonApiKey,
|
|
8976
9151
|
launchId: launchId || null,
|
|
@@ -8999,7 +9174,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8999
9174
|
runtimeTraceCounters: createRuntimeTraceCounters(),
|
|
9000
9175
|
lastActivity: "",
|
|
9001
9176
|
lastActivityDetail: "",
|
|
9002
|
-
activityClientSeq: 0,
|
|
9003
9177
|
recentStdout: [],
|
|
9004
9178
|
recentStderr: [],
|
|
9005
9179
|
lastRuntimeError: null,
|
|
@@ -9118,7 +9292,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9118
9292
|
const startupTimeoutTermination = ap.expectedTerminationReason === "startup_timeout";
|
|
9119
9293
|
const expectedTermination = Boolean(ap.expectedTerminationReason);
|
|
9120
9294
|
const stickyTerminalFailureDetail = classifyStickyTerminalFailure(ap);
|
|
9121
|
-
const processEndedCleanly = !stickyTerminalFailureDetail && (finalCode === 0 || expectedTermination && !ap.lastRuntimeError);
|
|
9295
|
+
const processEndedCleanly = !stickyTerminalFailureDetail && !startupTimeoutTermination && (finalCode === 0 || expectedTermination && !ap.lastRuntimeError);
|
|
9122
9296
|
const terminalFailureDetail = processEndedCleanly ? null : stickyTerminalFailureDetail ?? classifyTerminalFailure(ap);
|
|
9123
9297
|
const resumeRecoveryReason = resumeSessionRecoveryReason(ap);
|
|
9124
9298
|
const shouldColdStartResumeSession = resumeRecoveryReason !== null;
|
|
@@ -9527,6 +9701,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9527
9701
|
cleanupAgentCredentialProxy(agentId, ap.launchId);
|
|
9528
9702
|
this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
|
|
9529
9703
|
this.agents.delete(agentId);
|
|
9704
|
+
if (!silent) {
|
|
9705
|
+
this.activityClientSeqByAgent.delete(agentId);
|
|
9706
|
+
}
|
|
9530
9707
|
this.runtimeExitTraceAttrs.set(ap.runtime, {
|
|
9531
9708
|
stop_source: silent ? "daemon_internal" : "explicit_request",
|
|
9532
9709
|
stop_wait_requested: wait,
|
|
@@ -9538,7 +9715,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9538
9715
|
reason: silent ? "daemon_internal" : "explicit_request"
|
|
9539
9716
|
});
|
|
9540
9717
|
if (!silent) {
|
|
9541
|
-
this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId);
|
|
9718
|
+
this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId, "stop");
|
|
9542
9719
|
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
9543
9720
|
this.broadcastActivity(agentId, "offline", "Stopped");
|
|
9544
9721
|
logger.info(`[Agent ${agentId}] Stopped by request`);
|
|
@@ -9681,20 +9858,37 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9681
9858
|
}));
|
|
9682
9859
|
return true;
|
|
9683
9860
|
}
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
9692
|
-
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9861
|
+
if (stickyTerminalFailure.actionRequired) {
|
|
9862
|
+
if (ap.lastRuntimeError && isAuthClassTerminalLine(ap.lastRuntimeError)) {
|
|
9863
|
+
ap.lastRuntimeError = null;
|
|
9864
|
+
}
|
|
9865
|
+
ap.recentStderr = ap.recentStderr.filter((line) => !isAuthClassTerminalLine(line));
|
|
9866
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9867
|
+
outcome: "user_turn_recover_from_sticky_terminal_error",
|
|
9868
|
+
accepted: true,
|
|
9869
|
+
process_present: true,
|
|
9870
|
+
runtime: ap.config.runtime,
|
|
9871
|
+
session_id_present: Boolean(ap.sessionId),
|
|
9872
|
+
launchId: ap.launchId || void 0,
|
|
9873
|
+
is_idle: ap.isIdle,
|
|
9874
|
+
inbox_count: ap.inbox.length
|
|
9875
|
+
}));
|
|
9876
|
+
} else {
|
|
9877
|
+
ap.inbox.push(message);
|
|
9878
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9879
|
+
outcome: "queued_terminal_runtime_error",
|
|
9880
|
+
accepted: true,
|
|
9881
|
+
process_present: true,
|
|
9882
|
+
runtime: ap.config.runtime,
|
|
9883
|
+
session_id_present: Boolean(ap.sessionId),
|
|
9884
|
+
launchId: ap.launchId || void 0,
|
|
9885
|
+
is_idle: ap.isIdle,
|
|
9886
|
+
inbox_count: ap.inbox.length
|
|
9887
|
+
}));
|
|
9888
|
+
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
9889
|
+
this.broadcastActivity(agentId, "error", stickyTerminalFailure.detail);
|
|
9890
|
+
return true;
|
|
9891
|
+
}
|
|
9698
9892
|
}
|
|
9699
9893
|
if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
9700
9894
|
if (transientDelivery) {
|
|
@@ -9862,7 +10056,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9862
10056
|
return true;
|
|
9863
10057
|
}
|
|
9864
10058
|
async resetWorkspace(agentId) {
|
|
9865
|
-
const agentDataDir =
|
|
10059
|
+
const agentDataDir = path11.join(this.dataDir, agentId);
|
|
9866
10060
|
try {
|
|
9867
10061
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
9868
10062
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -9923,7 +10117,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9923
10117
|
return result;
|
|
9924
10118
|
}
|
|
9925
10119
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
9926
|
-
const workspacePath =
|
|
10120
|
+
const workspacePath = path11.join(this.dataDir, agentId);
|
|
9927
10121
|
return {
|
|
9928
10122
|
agentId,
|
|
9929
10123
|
launchId,
|
|
@@ -10141,7 +10335,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10141
10335
|
});
|
|
10142
10336
|
span.end("ok", { attrs: { outcome: "agent_config_ack_sent" } });
|
|
10143
10337
|
}
|
|
10144
|
-
sendRuntimeProfileWireReport(report) {
|
|
10338
|
+
sendRuntimeProfileWireReport(report, source) {
|
|
10145
10339
|
const span = this.tracer.startSpan("daemon.runtime_profile.report.sent", {
|
|
10146
10340
|
surface: "daemon",
|
|
10147
10341
|
kind: "producer",
|
|
@@ -10149,6 +10343,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10149
10343
|
agentId: report.agentId,
|
|
10150
10344
|
launchId: report.launchId || void 0,
|
|
10151
10345
|
runtime: report.facts.runtime,
|
|
10346
|
+
report_source: source,
|
|
10152
10347
|
model_present: Boolean(report.facts.model),
|
|
10153
10348
|
session_ref_present: Boolean(report.facts.sessionRef),
|
|
10154
10349
|
workspace_ref_present: Boolean(report.facts.workspaceRef || report.facts.workspacePathRef)
|
|
@@ -10159,17 +10354,18 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10159
10354
|
agentId: report.agentId,
|
|
10160
10355
|
facts: report.facts,
|
|
10161
10356
|
launchId: report.launchId || void 0,
|
|
10162
|
-
traceparent: formatTraceparent(span.context)
|
|
10357
|
+
traceparent: formatTraceparent(span.context),
|
|
10358
|
+
source
|
|
10163
10359
|
});
|
|
10164
10360
|
span.end("ok");
|
|
10165
10361
|
}
|
|
10166
|
-
sendRuntimeProfileReportFor(agentId, config, sessionId, launchId) {
|
|
10167
|
-
this.sendRuntimeProfileWireReport(this.buildRuntimeProfileReport(agentId, config, sessionId, launchId));
|
|
10362
|
+
sendRuntimeProfileReportFor(agentId, config, sessionId, launchId, source) {
|
|
10363
|
+
this.sendRuntimeProfileWireReport(this.buildRuntimeProfileReport(agentId, config, sessionId, launchId), source);
|
|
10168
10364
|
}
|
|
10169
|
-
sendRuntimeProfileReport(agentId) {
|
|
10365
|
+
sendRuntimeProfileReport(agentId, source) {
|
|
10170
10366
|
const report = this.getAgentRuntimeProfileReport(agentId);
|
|
10171
10367
|
if (!report) return;
|
|
10172
|
-
this.sendRuntimeProfileWireReport(report);
|
|
10368
|
+
this.sendRuntimeProfileWireReport(report, source);
|
|
10173
10369
|
}
|
|
10174
10370
|
// Machine-level workspace scanning
|
|
10175
10371
|
async scanAllWorkspaces() {
|
|
@@ -10180,7 +10376,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10180
10376
|
}
|
|
10181
10377
|
// Workspace file browsing
|
|
10182
10378
|
async getFileTree(agentId, dirPath) {
|
|
10183
|
-
const agentDir =
|
|
10379
|
+
const agentDir = path11.join(this.dataDir, agentId);
|
|
10184
10380
|
try {
|
|
10185
10381
|
await stat2(agentDir);
|
|
10186
10382
|
} catch {
|
|
@@ -10188,8 +10384,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10188
10384
|
}
|
|
10189
10385
|
let targetDir = agentDir;
|
|
10190
10386
|
if (dirPath) {
|
|
10191
|
-
const resolved =
|
|
10192
|
-
if (!resolved.startsWith(agentDir +
|
|
10387
|
+
const resolved = path11.resolve(agentDir, dirPath);
|
|
10388
|
+
if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
|
|
10193
10389
|
return [];
|
|
10194
10390
|
}
|
|
10195
10391
|
targetDir = resolved;
|
|
@@ -10197,14 +10393,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10197
10393
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
10198
10394
|
}
|
|
10199
10395
|
async readFile(agentId, filePath) {
|
|
10200
|
-
const agentDir =
|
|
10201
|
-
const resolved =
|
|
10202
|
-
if (!resolved.startsWith(agentDir +
|
|
10396
|
+
const agentDir = path11.join(this.dataDir, agentId);
|
|
10397
|
+
const resolved = path11.resolve(agentDir, filePath);
|
|
10398
|
+
if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
|
|
10203
10399
|
throw new Error("Access denied");
|
|
10204
10400
|
}
|
|
10205
10401
|
const info = await stat2(resolved);
|
|
10206
10402
|
if (info.isDirectory()) throw new Error("Cannot read a directory");
|
|
10207
|
-
const ext =
|
|
10403
|
+
const ext = path11.extname(resolved).toLowerCase();
|
|
10208
10404
|
if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
|
|
10209
10405
|
if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
|
|
10210
10406
|
const content = await readFile(resolved, "utf-8");
|
|
@@ -10239,13 +10435,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10239
10435
|
const agent = this.agents.get(agentId);
|
|
10240
10436
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
10241
10437
|
const home = os5.homedir();
|
|
10242
|
-
const workspaceDir =
|
|
10438
|
+
const workspaceDir = path11.join(this.dataDir, agentId);
|
|
10243
10439
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
10244
10440
|
const globalResults = await Promise.all(
|
|
10245
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
10441
|
+
paths.global.map((p) => this.scanSkillsDir(path11.join(home, p)))
|
|
10246
10442
|
);
|
|
10247
10443
|
const workspaceResults = await Promise.all(
|
|
10248
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
10444
|
+
paths.workspace.map((p) => this.scanSkillsDir(path11.join(workspaceDir, p)))
|
|
10249
10445
|
);
|
|
10250
10446
|
const dedup = (skills) => {
|
|
10251
10447
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -10274,7 +10470,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10274
10470
|
const skills = [];
|
|
10275
10471
|
for (const entry of entries) {
|
|
10276
10472
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
10277
|
-
const skillMd =
|
|
10473
|
+
const skillMd = path11.join(dir, entry.name, "SKILL.md");
|
|
10278
10474
|
try {
|
|
10279
10475
|
const content = await readFile(skillMd, "utf-8");
|
|
10280
10476
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -10285,7 +10481,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10285
10481
|
} else if (entry.name.endsWith(".md")) {
|
|
10286
10482
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
10287
10483
|
try {
|
|
10288
|
-
const content = await readFile(
|
|
10484
|
+
const content = await readFile(path11.join(dir, entry.name), "utf-8");
|
|
10289
10485
|
const skill = this.parseSkillMd(cmdName, content);
|
|
10290
10486
|
skill.sourcePath = dir;
|
|
10291
10487
|
skills.push(skill);
|
|
@@ -10334,7 +10530,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10334
10530
|
if (!hasToolStart) {
|
|
10335
10531
|
entries.push({ kind: "status", activity, detail });
|
|
10336
10532
|
}
|
|
10337
|
-
if (ap) ap.activityClientSeq += 1;
|
|
10338
10533
|
this.sendToServer({
|
|
10339
10534
|
type: "agent:activity",
|
|
10340
10535
|
agentId,
|
|
@@ -10342,7 +10537,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10342
10537
|
detail,
|
|
10343
10538
|
entries,
|
|
10344
10539
|
launchId: launchIdOverride || ap?.launchId || void 0,
|
|
10345
|
-
clientSeq: ap
|
|
10540
|
+
clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0
|
|
10346
10541
|
});
|
|
10347
10542
|
if (ap) {
|
|
10348
10543
|
ap.lastActivity = activity;
|
|
@@ -10354,14 +10549,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10354
10549
|
this.recordRuntimeTraceEvent(agentId, ap, "activity.heartbeat.sent", {
|
|
10355
10550
|
activity: ap.lastActivity
|
|
10356
10551
|
});
|
|
10357
|
-
ap.activityClientSeq += 1;
|
|
10358
10552
|
this.sendToServer({
|
|
10359
10553
|
type: "agent:activity",
|
|
10360
10554
|
agentId,
|
|
10361
10555
|
activity: ap.lastActivity,
|
|
10362
10556
|
detail: ap.lastActivityDetail,
|
|
10363
10557
|
launchId: launchIdOverride || ap.launchId || void 0,
|
|
10364
|
-
clientSeq:
|
|
10558
|
+
clientSeq: this.nextActivityClientSeq(agentId)
|
|
10365
10559
|
});
|
|
10366
10560
|
}, ACTIVITY_HEARTBEAT_MS);
|
|
10367
10561
|
}
|
|
@@ -10398,7 +10592,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10398
10592
|
const ap = this.agents.get(agentId);
|
|
10399
10593
|
const activity = ap?.lastActivity || "offline";
|
|
10400
10594
|
const detail = ap?.lastActivityDetail || (ap ? "" : "Agent not running");
|
|
10401
|
-
if (ap) ap.activityClientSeq += 1;
|
|
10402
10595
|
this.sendToServer({
|
|
10403
10596
|
type: "agent:activity",
|
|
10404
10597
|
agentId,
|
|
@@ -10406,7 +10599,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10406
10599
|
detail,
|
|
10407
10600
|
launchId: ap?.launchId || void 0,
|
|
10408
10601
|
probeId,
|
|
10409
|
-
clientSeq: ap
|
|
10602
|
+
clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0
|
|
10410
10603
|
});
|
|
10411
10604
|
}
|
|
10412
10605
|
flushPendingTrajectory(agentId) {
|
|
@@ -10608,8 +10801,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10608
10801
|
runtime_profile_key_hash: control.keyHash || void 0,
|
|
10609
10802
|
runtime_profile_key_present: control.keyPresent,
|
|
10610
10803
|
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
|
|
10804
|
+
runtime_profile_requires_ack: false
|
|
10613
10805
|
};
|
|
10614
10806
|
}
|
|
10615
10807
|
activateRuntimeProfileTurnControl(ap, control) {
|
|
@@ -10621,16 +10813,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10621
10813
|
if (!notification) return null;
|
|
10622
10814
|
return runtimeProfileTurnControl(notification.kind, notification.key, source);
|
|
10623
10815
|
}
|
|
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
10816
|
finalizeRuntimeProfileTurnControl(agentId, ap, terminal) {
|
|
10635
10817
|
const control = ap.runtimeProfileTurnControl;
|
|
10636
10818
|
if (!control) return {};
|
|
@@ -10639,7 +10821,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10639
10821
|
return {
|
|
10640
10822
|
...attrs,
|
|
10641
10823
|
runtime_profile_turn_terminal: terminal,
|
|
10642
|
-
runtime_profile_turn_outcome: control.kind === "migration" ?
|
|
10824
|
+
runtime_profile_turn_outcome: control.kind === "migration" ? "reset_session_notice" : "notice_only"
|
|
10643
10825
|
};
|
|
10644
10826
|
}
|
|
10645
10827
|
startRuntimeTrace(agentId, ap, reason, messages, inputTraceAttrs = {}) {
|
|
@@ -11018,7 +11200,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11018
11200
|
case "session_init":
|
|
11019
11201
|
if (ap) ap.sessionId = event.sessionId;
|
|
11020
11202
|
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId, launchId: ap?.launchId || void 0 });
|
|
11021
|
-
this.sendRuntimeProfileReport(agentId);
|
|
11203
|
+
this.sendRuntimeProfileReport(agentId, "session_init");
|
|
11022
11204
|
break;
|
|
11023
11205
|
case "thinking": {
|
|
11024
11206
|
this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
|
|
@@ -11044,7 +11226,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11044
11226
|
const invocation = normalizeToolDisplayInvocation(event.name, event.input);
|
|
11045
11227
|
if (ap) {
|
|
11046
11228
|
const reduction = reduceApmGatedToolUse(ap.gatedSteering, { kind: "tool_call" });
|
|
11047
|
-
this.noteRuntimeProfileToolCall(agentId, ap, invocation.toolName);
|
|
11048
11229
|
this.recordRuntimeTraceEvent(agentId, ap, "tool.call.started", { tool: invocation.toolName });
|
|
11049
11230
|
this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, {
|
|
11050
11231
|
event: "tool_call",
|
|
@@ -11157,7 +11338,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
11157
11338
|
}
|
|
11158
11339
|
if (event.sessionId) {
|
|
11159
11340
|
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId, launchId: ap?.launchId || void 0 });
|
|
11160
|
-
this.sendRuntimeProfileReport(agentId);
|
|
11341
|
+
this.sendRuntimeProfileReport(agentId, "turn_end");
|
|
11161
11342
|
}
|
|
11162
11343
|
break;
|
|
11163
11344
|
case "error": {
|
|
@@ -11625,8 +11806,8 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
11625
11806
|
const nodes = [];
|
|
11626
11807
|
for (const entry of entries) {
|
|
11627
11808
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
11628
|
-
const fullPath =
|
|
11629
|
-
const relativePath =
|
|
11809
|
+
const fullPath = path11.join(dir, entry.name);
|
|
11810
|
+
const relativePath = path11.relative(rootDir, fullPath);
|
|
11630
11811
|
let info;
|
|
11631
11812
|
try {
|
|
11632
11813
|
info = await stat2(fullPath);
|
|
@@ -11917,7 +12098,7 @@ var ReminderCache = class {
|
|
|
11917
12098
|
logger.warn(`[ReminderCache] Invalid fireAt for ${job.reminderId}: ${job.fireAt}`);
|
|
11918
12099
|
return null;
|
|
11919
12100
|
}
|
|
11920
|
-
const
|
|
12101
|
+
const delay2 = Math.max(0, Math.min(this.maxDelayMs, fireAt - this.clock.now()));
|
|
11921
12102
|
return this.clock.setTimeout(() => {
|
|
11922
12103
|
this.entries.delete(job.reminderId);
|
|
11923
12104
|
try {
|
|
@@ -11925,15 +12106,15 @@ var ReminderCache = class {
|
|
|
11925
12106
|
} catch (err) {
|
|
11926
12107
|
logger.error(`[ReminderCache] onFire threw for ${job.reminderId}`, err);
|
|
11927
12108
|
}
|
|
11928
|
-
},
|
|
12109
|
+
}, delay2);
|
|
11929
12110
|
}
|
|
11930
12111
|
};
|
|
11931
12112
|
|
|
11932
12113
|
// src/machineLock.ts
|
|
11933
12114
|
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
11934
|
-
import { mkdirSync as
|
|
12115
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
11935
12116
|
import os6 from "os";
|
|
11936
|
-
import
|
|
12117
|
+
import path12 from "path";
|
|
11937
12118
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
11938
12119
|
var DaemonMachineLockConflictError = class extends Error {
|
|
11939
12120
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -11955,7 +12136,7 @@ function resolveDefaultMachineStateRoot() {
|
|
|
11955
12136
|
return resolveSlockHomePath("machines");
|
|
11956
12137
|
}
|
|
11957
12138
|
function ownerPath(lockDir) {
|
|
11958
|
-
return
|
|
12139
|
+
return path12.join(lockDir, "owner.json");
|
|
11959
12140
|
}
|
|
11960
12141
|
function readOwner(lockDir) {
|
|
11961
12142
|
try {
|
|
@@ -11985,13 +12166,13 @@ function acquireDaemonMachineLock(options) {
|
|
|
11985
12166
|
const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
|
|
11986
12167
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
11987
12168
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
11988
|
-
const machineDir =
|
|
11989
|
-
const lockDir =
|
|
12169
|
+
const machineDir = path12.join(rootDir, lockId);
|
|
12170
|
+
const lockDir = path12.join(machineDir, "daemon.lock");
|
|
11990
12171
|
const token = randomUUID4();
|
|
11991
|
-
|
|
12172
|
+
mkdirSync4(machineDir, { recursive: true });
|
|
11992
12173
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
11993
12174
|
try {
|
|
11994
|
-
|
|
12175
|
+
mkdirSync4(lockDir);
|
|
11995
12176
|
const owner = {
|
|
11996
12177
|
pid: process.pid,
|
|
11997
12178
|
token,
|
|
@@ -12001,7 +12182,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
12001
12182
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
12002
12183
|
};
|
|
12003
12184
|
try {
|
|
12004
|
-
|
|
12185
|
+
writeFileSync5(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
|
|
12005
12186
|
`, { mode: 384 });
|
|
12006
12187
|
} catch (err) {
|
|
12007
12188
|
rmSync3(lockDir, { recursive: true, force: true });
|
|
@@ -12016,7 +12197,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
12016
12197
|
if (currentOwner?.pid === process.pid && currentOwner.token === token) {
|
|
12017
12198
|
const released = { ...currentOwner, pid: 0 };
|
|
12018
12199
|
try {
|
|
12019
|
-
|
|
12200
|
+
writeFileSync5(ownerPath(lockDir), `${JSON.stringify(released, null, 2)}
|
|
12020
12201
|
`, {
|
|
12021
12202
|
mode: 384
|
|
12022
12203
|
});
|
|
@@ -12046,8 +12227,8 @@ function acquireDaemonMachineLock(options) {
|
|
|
12046
12227
|
}
|
|
12047
12228
|
|
|
12048
12229
|
// src/localTraceSink.ts
|
|
12049
|
-
import { appendFileSync, mkdirSync as
|
|
12050
|
-
import
|
|
12230
|
+
import { appendFileSync, mkdirSync as mkdirSync5, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
12231
|
+
import path13 from "path";
|
|
12051
12232
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
12052
12233
|
var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
|
|
12053
12234
|
var DEFAULT_MAX_FILES = 8;
|
|
@@ -12084,7 +12265,7 @@ var LocalRotatingTraceSink = class {
|
|
|
12084
12265
|
currentSize = 0;
|
|
12085
12266
|
sequence = 0;
|
|
12086
12267
|
constructor(options) {
|
|
12087
|
-
this.traceDir =
|
|
12268
|
+
this.traceDir = path13.join(options.machineDir, "traces");
|
|
12088
12269
|
this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
|
|
12089
12270
|
const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
|
|
12090
12271
|
const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
|
|
@@ -12110,15 +12291,15 @@ var LocalRotatingTraceSink = class {
|
|
|
12110
12291
|
return this.currentFile;
|
|
12111
12292
|
}
|
|
12112
12293
|
ensureFile(nextBytes) {
|
|
12113
|
-
|
|
12294
|
+
mkdirSync5(this.traceDir, { recursive: true, mode: 448 });
|
|
12114
12295
|
const nowMs = this.nowMsProvider();
|
|
12115
12296
|
const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
|
|
12116
12297
|
if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
|
|
12117
|
-
this.currentFile =
|
|
12298
|
+
this.currentFile = path13.join(
|
|
12118
12299
|
this.traceDir,
|
|
12119
12300
|
`daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
|
|
12120
12301
|
);
|
|
12121
|
-
|
|
12302
|
+
writeFileSync6(this.currentFile, "", { flag: "a", mode: 384 });
|
|
12122
12303
|
this.currentSize = statSync3(this.currentFile).size;
|
|
12123
12304
|
this.currentFileOpenedAtMs = nowMs;
|
|
12124
12305
|
this.pruneOldFiles();
|
|
@@ -12129,7 +12310,7 @@ var LocalRotatingTraceSink = class {
|
|
|
12129
12310
|
const excess = files.length - this.maxFiles;
|
|
12130
12311
|
if (excess <= 0) return;
|
|
12131
12312
|
for (const file of files.slice(0, excess)) {
|
|
12132
|
-
rmSync4(
|
|
12313
|
+
rmSync4(path13.join(this.traceDir, file), { force: true });
|
|
12133
12314
|
}
|
|
12134
12315
|
}
|
|
12135
12316
|
};
|
|
@@ -12220,11 +12401,108 @@ function isDiagnosticErrorAttr(key) {
|
|
|
12220
12401
|
import { createHash as createHash6, randomUUID as randomUUID5 } from "crypto";
|
|
12221
12402
|
import { gzipSync } from "zlib";
|
|
12222
12403
|
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
12223
|
-
import
|
|
12404
|
+
import path14 from "path";
|
|
12405
|
+
|
|
12406
|
+
// src/chatBridgeRequest.ts
|
|
12407
|
+
var DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS = Number.parseInt(
|
|
12408
|
+
process.env.SLOCK_CHAT_BRIDGE_TOOL_TIMEOUT_MS || "",
|
|
12409
|
+
10
|
|
12410
|
+
) || 6e4;
|
|
12411
|
+
var ChatBridgeToolTimeoutError = class extends Error {
|
|
12412
|
+
toolName;
|
|
12413
|
+
target;
|
|
12414
|
+
timeoutMs;
|
|
12415
|
+
durationMs;
|
|
12416
|
+
constructor(toolName, target, timeoutMs, durationMs) {
|
|
12417
|
+
super(`${toolName} timed out after ${timeoutMs}ms${target ? ` (target: ${target})` : ""}`);
|
|
12418
|
+
this.name = "ChatBridgeToolTimeoutError";
|
|
12419
|
+
this.toolName = toolName;
|
|
12420
|
+
this.target = target;
|
|
12421
|
+
this.timeoutMs = timeoutMs;
|
|
12422
|
+
this.durationMs = durationMs;
|
|
12423
|
+
}
|
|
12424
|
+
};
|
|
12425
|
+
function describeError(err) {
|
|
12426
|
+
if (err instanceof Error) return `${err.name}: ${err.message}`;
|
|
12427
|
+
return String(err);
|
|
12428
|
+
}
|
|
12429
|
+
async function executeJsonRequest(url, init, {
|
|
12430
|
+
toolName,
|
|
12431
|
+
target = null,
|
|
12432
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
|
|
12433
|
+
fetchImpl,
|
|
12434
|
+
now = () => Date.now(),
|
|
12435
|
+
warn = (message) => logger.warn(message)
|
|
12436
|
+
}) {
|
|
12437
|
+
const startedAt = now();
|
|
12438
|
+
const timeoutController = new AbortController();
|
|
12439
|
+
const signals = [timeoutController.signal];
|
|
12440
|
+
if (init.signal) signals.push(init.signal);
|
|
12441
|
+
const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
12442
|
+
const timeout = setTimeout(() => {
|
|
12443
|
+
timeoutController.abort();
|
|
12444
|
+
}, timeoutMs);
|
|
12445
|
+
timeout.unref?.();
|
|
12446
|
+
try {
|
|
12447
|
+
const response = await fetchImpl(url, { ...init, signal });
|
|
12448
|
+
const data = await response.json();
|
|
12449
|
+
return { response, data, durationMs: now() - startedAt };
|
|
12450
|
+
} catch (err) {
|
|
12451
|
+
const durationMs = now() - startedAt;
|
|
12452
|
+
if (timeoutController.signal.aborted && !init.signal?.aborted) {
|
|
12453
|
+
warn(
|
|
12454
|
+
`[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
|
|
12455
|
+
);
|
|
12456
|
+
throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
|
|
12457
|
+
}
|
|
12458
|
+
warn(
|
|
12459
|
+
`[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
|
|
12460
|
+
);
|
|
12461
|
+
throw err;
|
|
12462
|
+
} finally {
|
|
12463
|
+
clearTimeout(timeout);
|
|
12464
|
+
}
|
|
12465
|
+
}
|
|
12466
|
+
async function executeResponseRequest(url, init, {
|
|
12467
|
+
toolName,
|
|
12468
|
+
target = null,
|
|
12469
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
|
|
12470
|
+
fetchImpl,
|
|
12471
|
+
now = () => Date.now(),
|
|
12472
|
+
warn = (message) => logger.warn(message)
|
|
12473
|
+
}) {
|
|
12474
|
+
const startedAt = now();
|
|
12475
|
+
const timeoutController = new AbortController();
|
|
12476
|
+
const signals = [timeoutController.signal];
|
|
12477
|
+
if (init.signal) signals.push(init.signal);
|
|
12478
|
+
const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
12479
|
+
const timeout = setTimeout(() => {
|
|
12480
|
+
timeoutController.abort();
|
|
12481
|
+
}, timeoutMs);
|
|
12482
|
+
timeout.unref?.();
|
|
12483
|
+
try {
|
|
12484
|
+
const response = await fetchImpl(url, { ...init, signal });
|
|
12485
|
+
return { response, durationMs: now() - startedAt };
|
|
12486
|
+
} catch (err) {
|
|
12487
|
+
const durationMs = now() - startedAt;
|
|
12488
|
+
if (timeoutController.signal.aborted && !init.signal?.aborted) {
|
|
12489
|
+
warn(
|
|
12490
|
+
`[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
|
|
12491
|
+
);
|
|
12492
|
+
throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
|
|
12493
|
+
}
|
|
12494
|
+
warn(
|
|
12495
|
+
`[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
|
|
12496
|
+
);
|
|
12497
|
+
throw err;
|
|
12498
|
+
} finally {
|
|
12499
|
+
clearTimeout(timeout);
|
|
12500
|
+
}
|
|
12501
|
+
}
|
|
12224
12502
|
|
|
12225
12503
|
// src/directUploadCapability.ts
|
|
12226
|
-
function joinUrl(base,
|
|
12227
|
-
return `${base.replace(/\/+$/, "")}${
|
|
12504
|
+
function joinUrl(base, path16) {
|
|
12505
|
+
return `${base.replace(/\/+$/, "")}${path16}`;
|
|
12228
12506
|
}
|
|
12229
12507
|
function jsonHeaders(apiKey) {
|
|
12230
12508
|
return {
|
|
@@ -12443,7 +12721,7 @@ var DaemonTraceBundleUploader = class {
|
|
|
12443
12721
|
}, nextMs);
|
|
12444
12722
|
}
|
|
12445
12723
|
async findUploadCandidates() {
|
|
12446
|
-
const traceDir =
|
|
12724
|
+
const traceDir = path14.join(this.options.machineDir, "traces");
|
|
12447
12725
|
let names;
|
|
12448
12726
|
try {
|
|
12449
12727
|
names = await readdir3(traceDir);
|
|
@@ -12455,8 +12733,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
12455
12733
|
const currentFile = this.options.currentFileProvider?.();
|
|
12456
12734
|
const candidates = [];
|
|
12457
12735
|
for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
|
|
12458
|
-
const file =
|
|
12459
|
-
if (currentFile &&
|
|
12736
|
+
const file = path14.join(traceDir, name);
|
|
12737
|
+
if (currentFile && path14.resolve(file) === path14.resolve(currentFile)) continue;
|
|
12460
12738
|
if (await this.isUploaded(file)) continue;
|
|
12461
12739
|
try {
|
|
12462
12740
|
const info = await stat3(file);
|
|
@@ -12530,8 +12808,8 @@ var DaemonTraceBundleUploader = class {
|
|
|
12530
12808
|
}
|
|
12531
12809
|
}
|
|
12532
12810
|
uploadStatePath(file) {
|
|
12533
|
-
const stateDir =
|
|
12534
|
-
return
|
|
12811
|
+
const stateDir = path14.join(this.options.machineDir, "trace-uploads");
|
|
12812
|
+
return path14.join(stateDir, `${path14.basename(file)}.uploaded.json`);
|
|
12535
12813
|
}
|
|
12536
12814
|
async isUploaded(file) {
|
|
12537
12815
|
try {
|
|
@@ -12543,9 +12821,9 @@ var DaemonTraceBundleUploader = class {
|
|
|
12543
12821
|
}
|
|
12544
12822
|
async markUploaded(file, metadata) {
|
|
12545
12823
|
const stateFile = this.uploadStatePath(file);
|
|
12546
|
-
await mkdir2(
|
|
12824
|
+
await mkdir2(path14.dirname(stateFile), { recursive: true, mode: 448 });
|
|
12547
12825
|
await writeFile2(stateFile, `${JSON.stringify({
|
|
12548
|
-
file:
|
|
12826
|
+
file: path14.basename(file),
|
|
12549
12827
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12550
12828
|
...metadata
|
|
12551
12829
|
}, null, 2)}
|
|
@@ -12621,24 +12899,14 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
12621
12899
|
return "0.0.0-dev";
|
|
12622
12900
|
}
|
|
12623
12901
|
}
|
|
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
12902
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
12635
|
-
const thisDir =
|
|
12636
|
-
const bundledDistPath =
|
|
12903
|
+
const thisDir = path15.dirname(fileURLToPath(moduleUrl));
|
|
12904
|
+
const bundledDistPath = path15.resolve(thisDir, "cli", "index.js");
|
|
12637
12905
|
try {
|
|
12638
12906
|
accessSync(bundledDistPath);
|
|
12639
12907
|
return bundledDistPath;
|
|
12640
12908
|
} catch {
|
|
12641
|
-
const workspaceDistPath =
|
|
12909
|
+
const workspaceDistPath = path15.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
12642
12910
|
accessSync(workspaceDistPath);
|
|
12643
12911
|
return workspaceDistPath;
|
|
12644
12912
|
}
|
|
@@ -12739,6 +13007,8 @@ function summarizeIncomingMessage(msg) {
|
|
|
12739
13007
|
return `(agent=${msg.agentId})`;
|
|
12740
13008
|
case "agent:deliver":
|
|
12741
13009
|
return `(agent=${msg.agentId}, seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`;
|
|
13010
|
+
case "agent:inbox:purge":
|
|
13011
|
+
return `(agent=${msg.agentId}, channels=${msg.channelIds.length}, reason=${msg.reason || "server_purge"})`;
|
|
12742
13012
|
case "agent:runtime_profile:migration":
|
|
12743
13013
|
return `(agent=${msg.agentId}, migration=${msg.migrationKey})`;
|
|
12744
13014
|
case "agent:runtime_profile:daemon_release_notice":
|
|
@@ -12773,7 +13043,6 @@ var DaemonCore = class {
|
|
|
12773
13043
|
// bundle version). Reported in `ready` so the server can surface the
|
|
12774
13044
|
// Computer's own version (distinct from the underlying daemonVersion).
|
|
12775
13045
|
computerVersion;
|
|
12776
|
-
chatBridgePath;
|
|
12777
13046
|
slockCliPath;
|
|
12778
13047
|
slockHome;
|
|
12779
13048
|
runtimeDetector;
|
|
@@ -12789,7 +13058,6 @@ var DaemonCore = class {
|
|
|
12789
13058
|
this.options = options;
|
|
12790
13059
|
this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
|
|
12791
13060
|
this.computerVersion = (process.env.SLOCK_COMPUTER_VERSION || "").trim() || null;
|
|
12792
|
-
this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
|
|
12793
13061
|
this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
|
|
12794
13062
|
this.slockHome = resolveSlockHome();
|
|
12795
13063
|
process.env[SLOCK_HOME_ENV] = this.slockHome;
|
|
@@ -12810,7 +13078,7 @@ var DaemonCore = class {
|
|
|
12810
13078
|
daemonVersion: this.daemonVersion,
|
|
12811
13079
|
computerVersion: this.computerVersion
|
|
12812
13080
|
};
|
|
12813
|
-
this.agentManager = options.agentManagerFactory ? options.agentManagerFactory(
|
|
13081
|
+
this.agentManager = options.agentManagerFactory ? options.agentManagerFactory((msg) => connection.send(msg), options.apiKey, agentManagerOptions) : new AgentProcessManager((msg) => connection.send(msg), options.apiKey, agentManagerOptions);
|
|
12814
13082
|
const connectionFactory = options.connectionFactory ?? ((connOptions) => new DaemonConnection(connOptions));
|
|
12815
13083
|
connection = connectionFactory({
|
|
12816
13084
|
serverUrl: options.serverUrl,
|
|
@@ -12825,7 +13093,7 @@ var DaemonCore = class {
|
|
|
12825
13093
|
}
|
|
12826
13094
|
resolveMachineStateRoot() {
|
|
12827
13095
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
12828
|
-
if (this.options.dataDir) return
|
|
13096
|
+
if (this.options.dataDir) return path15.join(path15.dirname(this.options.dataDir), "machines");
|
|
12829
13097
|
return resolveDefaultMachineStateRoot();
|
|
12830
13098
|
}
|
|
12831
13099
|
shouldEnableLocalTrace() {
|
|
@@ -12851,7 +13119,7 @@ var DaemonCore = class {
|
|
|
12851
13119
|
sink: this.localTraceSink
|
|
12852
13120
|
}));
|
|
12853
13121
|
this.agentManager.setTracer(this.tracer);
|
|
12854
|
-
this.agentManager.setCliTransportTraceDir(
|
|
13122
|
+
this.agentManager.setCliTransportTraceDir(path15.join(machineDir, "traces"));
|
|
12855
13123
|
}
|
|
12856
13124
|
installTraceBundleUploader(machineDir) {
|
|
12857
13125
|
if (!this.shouldEnableLocalTrace()) return;
|
|
@@ -13093,6 +13361,10 @@ var DaemonCore = class {
|
|
|
13093
13361
|
logger.info(`[Agent ${msg.agentId}] Workspace reset requested`);
|
|
13094
13362
|
this.agentManager.resetWorkspace(msg.agentId);
|
|
13095
13363
|
break;
|
|
13364
|
+
case "agent:inbox:purge":
|
|
13365
|
+
logger.info(`[Agent ${msg.agentId}] Inbox purge requested (${msg.channelIds.length} channels, reason=${msg.reason || "server_purge"})`);
|
|
13366
|
+
this.agentManager.purgeInboxMessagesForChannels(msg.agentId, msg.channelIds, msg.reason || "server_purge");
|
|
13367
|
+
break;
|
|
13096
13368
|
case "agent:deliver": {
|
|
13097
13369
|
const parent = parseTraceparent(msg.traceparent);
|
|
13098
13370
|
const span = this.tracer.startSpan("daemon.agent.delivery", {
|
|
@@ -13344,6 +13616,7 @@ var DaemonCore = class {
|
|
|
13344
13616
|
agentId: report.agentId,
|
|
13345
13617
|
launchId: report.launchId || void 0,
|
|
13346
13618
|
runtime: report.facts.runtime,
|
|
13619
|
+
report_source: "connect",
|
|
13347
13620
|
model_present: Boolean(report.facts.model),
|
|
13348
13621
|
session_ref_present: Boolean(report.facts.sessionRef),
|
|
13349
13622
|
workspace_ref_present: Boolean(report.facts.workspaceRef || report.facts.workspacePathRef)
|
|
@@ -13354,7 +13627,8 @@ var DaemonCore = class {
|
|
|
13354
13627
|
agentId: report.agentId,
|
|
13355
13628
|
facts: report.facts,
|
|
13356
13629
|
launchId: report.launchId || void 0,
|
|
13357
|
-
traceparent: formatTraceparent(span.context)
|
|
13630
|
+
traceparent: formatTraceparent(span.context),
|
|
13631
|
+
source: "connect"
|
|
13358
13632
|
});
|
|
13359
13633
|
span.end("ok");
|
|
13360
13634
|
}
|
|
@@ -13378,13 +13652,13 @@ var DaemonCore = class {
|
|
|
13378
13652
|
};
|
|
13379
13653
|
|
|
13380
13654
|
export {
|
|
13655
|
+
subscribeDaemonLogs,
|
|
13381
13656
|
resolveWorkspaceDirectoryPath,
|
|
13382
13657
|
scanWorkspaceDirectories,
|
|
13383
13658
|
deleteWorkspaceDirectory,
|
|
13384
13659
|
DAEMON_CLI_USAGE,
|
|
13385
13660
|
parseDaemonCliArgs,
|
|
13386
13661
|
readDaemonVersion,
|
|
13387
|
-
resolveChatBridgePath,
|
|
13388
13662
|
resolveSlockCliPath,
|
|
13389
13663
|
detectRuntimes,
|
|
13390
13664
|
DaemonCore
|