@slock-ai/daemon 0.56.0 → 0.56.1-play.20260604141202
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-4H5XFLYA.js → chunk-IRYY762Z.js} +1249 -647
- package/dist/cli/index.js +36 -3
- package/dist/core.js +6 -2
- package/dist/drivers/piSdkRunner.js +96 -0
- package/dist/index.js +5 -3
- package/package.json +3 -1
|
@@ -932,10 +932,22 @@ var channelAddMemberOperationSchema = z.object({
|
|
|
932
932
|
agents: z.array(idOrHandleSchema).max(64).optional(),
|
|
933
933
|
draftHint: draftHintSchema
|
|
934
934
|
});
|
|
935
|
+
var integrationApproveAgentLoginOperationSchema = z.object({
|
|
936
|
+
type: z.literal("integration:approve_agent_login"),
|
|
937
|
+
requestId: uuidSchema,
|
|
938
|
+
agentId: uuidSchema,
|
|
939
|
+
agentName: z.string().trim().min(1).max(120),
|
|
940
|
+
clientId: uuidSchema,
|
|
941
|
+
clientKey: z.string().trim().min(1).max(120),
|
|
942
|
+
clientName: z.string().trim().min(1).max(120),
|
|
943
|
+
scopes: z.array(z.string().trim().min(1).max(120)).max(64),
|
|
944
|
+
draftHint: draftHintSchema
|
|
945
|
+
});
|
|
935
946
|
var actionCardActionSchema = z.discriminatedUnion("type", [
|
|
936
947
|
channelCreateOperationSchema,
|
|
937
948
|
agentCreateOperationSchema,
|
|
938
|
-
channelAddMemberOperationSchema
|
|
949
|
+
channelAddMemberOperationSchema,
|
|
950
|
+
integrationApproveAgentLoginOperationSchema
|
|
939
951
|
]);
|
|
940
952
|
|
|
941
953
|
// ../shared/src/agentInbox.ts
|
|
@@ -1276,6 +1288,329 @@ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
|
1276
1288
|
import { createRequire } from "module";
|
|
1277
1289
|
import path2 from "path";
|
|
1278
1290
|
|
|
1291
|
+
// src/drivers/slockCliGuide.ts
|
|
1292
|
+
var MESSAGE_HEREDOC_DELIMITER = "SLOCKMSG";
|
|
1293
|
+
function describeSlockInstallation(audience) {
|
|
1294
|
+
if (audience === "managed-runner") {
|
|
1295
|
+
return "The daemon injects a local `slock` wrapper into PATH for you.";
|
|
1296
|
+
}
|
|
1297
|
+
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>`).";
|
|
1298
|
+
}
|
|
1299
|
+
function buildCommunicationSection(audience) {
|
|
1300
|
+
const installation = describeSlockInstallation(audience);
|
|
1301
|
+
return `## Communication \u2014 slock CLI ONLY
|
|
1302
|
+
|
|
1303
|
+
Use the \`slock\` CLI for chat / task / attachment operations. ${installation} Use ONLY these commands for communication:
|
|
1304
|
+
|
|
1305
|
+
1. **\`slock message check\`** \u2014 Non-blocking check for new messages. Use freely during work \u2014 at natural breakpoints or after notifications.
|
|
1306
|
+
2. **\`slock message send\`** \u2014 Send a message to a channel or DM.
|
|
1307
|
+
3. **\`slock server info\`** \u2014 List channels in this server, which ones you have joined, plus all agents and humans.
|
|
1308
|
+
4. **\`slock channel members\`** \u2014 List the members (agents and humans) of a specific channel, DM, or thread target.
|
|
1309
|
+
5. **\`slock channel join\`** \u2014 Join a visible public channel. This only affects your own agent membership.
|
|
1310
|
+
6. **\`slock channel leave\`** \u2014 Leave a regular channel you have joined. This only affects your own agent membership.
|
|
1311
|
+
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.
|
|
1312
|
+
8. **\`slock message read\`** \u2014 Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` anchors and \`around\` for centered context.
|
|
1313
|
+
9. **\`slock message search\`** \u2014 Search messages visible to you, then inspect a hit with \`slock message read\`.
|
|
1314
|
+
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.
|
|
1315
|
+
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.
|
|
1316
|
+
12. **\`slock task list\`** \u2014 View a channel's task board.
|
|
1317
|
+
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).
|
|
1318
|
+
14. **\`slock task claim\`** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
|
|
1319
|
+
15. **\`slock task unclaim\`** \u2014 Release your claim on a task.
|
|
1320
|
+
16. **\`slock task update\`** \u2014 Change a task's status (e.g. to in_review or done).
|
|
1321
|
+
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\`.
|
|
1322
|
+
18. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
1323
|
+
19. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
1324
|
+
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.
|
|
1325
|
+
21. **\`slock integration list\`** \u2014 List built-in Slock apps, registered third-party services, and this agent's active Slock Agent Logins.
|
|
1326
|
+
22. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a built-in Slock app or registered third-party service.
|
|
1327
|
+
23. **\`slock integration env\`** \u2014 Print per-agent local CLI environment for a manifest-backed service that requires isolated HOME/XDG state.
|
|
1328
|
+
24. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
1329
|
+
25. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
|
|
1330
|
+
26. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
|
|
1331
|
+
27. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
|
|
1332
|
+
28. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
1333
|
+
29. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
|
|
1334
|
+
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\`).
|
|
1335
|
+
|
|
1336
|
+
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:
|
|
1337
|
+
- \`Error:\` human-readable error summary
|
|
1338
|
+
- \`Code:\` stable machine-oriented error code
|
|
1339
|
+
- \`Next action:\` optional recovery hint
|
|
1340
|
+
|
|
1341
|
+
Error code prefixes tell you the layer:
|
|
1342
|
+
- \`MISSING_*\` / \`TOKEN_*\` = local auth bootstrap
|
|
1343
|
+
- \`*_FAILED\` = 4xx from server
|
|
1344
|
+
- \`SERVER_5XX\` = server unreachable / crashed`;
|
|
1345
|
+
}
|
|
1346
|
+
function buildCredentialHygieneSection() {
|
|
1347
|
+
return `### Credential hygiene
|
|
1348
|
+
|
|
1349
|
+
**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.
|
|
1350
|
+
|
|
1351
|
+
If a tool or error output contains credential-shaped strings, redact them to \`sk_agent_<redacted>\` / \`sk_machine_<redacted>\` shape before reposting.
|
|
1352
|
+
|
|
1353
|
+
**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.`;
|
|
1354
|
+
}
|
|
1355
|
+
function buildSendingMessagesSection() {
|
|
1356
|
+
const D = MESSAGE_HEREDOC_DELIMITER;
|
|
1357
|
+
return `### Sending messages
|
|
1358
|
+
|
|
1359
|
+
- **Reply to a channel**: \`slock message send --target "#channel-name" <<'${D}'\` followed by the message body and \`${D}\`
|
|
1360
|
+
- **Reply to a DM**: \`slock message send --target dm:@peer-name <<'${D}'\` followed by the message body and \`${D}\`
|
|
1361
|
+
- **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'${D}'\` followed by the message body and \`${D}\`
|
|
1362
|
+
- **Start a NEW DM**: \`slock message send --target dm:@person-name <<'${D}'\` followed by the message body and \`${D}\`
|
|
1363
|
+
|
|
1364
|
+
Message content is always read from stdin. Use a heredoc so quotes, backticks, code blocks, and newlines are not interpreted by the shell:
|
|
1365
|
+
\`\`\`bash
|
|
1366
|
+
slock message send --target "#channel-name" <<'${D}'
|
|
1367
|
+
Long message with "quotes", $vars, \`backticks\`, and code blocks.
|
|
1368
|
+
${D}
|
|
1369
|
+
\`\`\`
|
|
1370
|
+
|
|
1371
|
+
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.
|
|
1372
|
+
|
|
1373
|
+
If Slock says a message was not sent and was saved as a draft, choose one path:
|
|
1374
|
+
- To update the draft, use a normal \`slock message send --target <target>\` with the revised content.
|
|
1375
|
+
- 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.
|
|
1376
|
+
|
|
1377
|
+
**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.`;
|
|
1378
|
+
}
|
|
1379
|
+
function buildRemindersSection() {
|
|
1380
|
+
return `### Reminders
|
|
1381
|
+
|
|
1382
|
+
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.
|
|
1383
|
+
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.
|
|
1384
|
+
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.
|
|
1385
|
+
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.`;
|
|
1386
|
+
}
|
|
1387
|
+
function buildThreadsSection() {
|
|
1388
|
+
const D = MESSAGE_HEREDOC_DELIMITER;
|
|
1389
|
+
return `### Threads
|
|
1390
|
+
|
|
1391
|
+
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
|
|
1392
|
+
|
|
1393
|
+
- **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
|
|
1394
|
+
- 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.
|
|
1395
|
+
- **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.
|
|
1396
|
+
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
1397
|
+
- You can read thread history: \`slock message read --channel "#general:00000000"\`
|
|
1398
|
+
- 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.
|
|
1399
|
+
- Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
|
|
1400
|
+
}
|
|
1401
|
+
function buildDiscoverySection() {
|
|
1402
|
+
return `### Discovering people and channels
|
|
1403
|
+
|
|
1404
|
+
Call \`slock server info\` to see all channels in this server, which ones you have joined, other agents, and humans.
|
|
1405
|
+
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"\`.
|
|
1406
|
+
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.`;
|
|
1407
|
+
}
|
|
1408
|
+
function buildChannelAwarenessSection() {
|
|
1409
|
+
return `### Channel awareness
|
|
1410
|
+
|
|
1411
|
+
Each channel has a **name** and optionally a **description** that define its purpose (visible via \`slock server info\`). Respect them:
|
|
1412
|
+
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
1413
|
+
- **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.
|
|
1414
|
+
- If unsure where something belongs, call \`slock server info\` to review channel descriptions.`;
|
|
1415
|
+
}
|
|
1416
|
+
function buildReadingHistorySection() {
|
|
1417
|
+
return `### Reading history
|
|
1418
|
+
|
|
1419
|
+
\`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
|
|
1420
|
+
|
|
1421
|
+
To jump directly to a specific hit with nearby context, use \`slock message read --channel "..." --around "messageId"\` or \`slock message read --channel "..." --around 12345\`.`;
|
|
1422
|
+
}
|
|
1423
|
+
function buildHistoricalReferencesSection() {
|
|
1424
|
+
return `### Historical references
|
|
1425
|
+
|
|
1426
|
+
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.`;
|
|
1427
|
+
}
|
|
1428
|
+
function buildTasksSection() {
|
|
1429
|
+
const D = MESSAGE_HEREDOC_DELIMITER;
|
|
1430
|
+
return `### Tasks
|
|
1431
|
+
|
|
1432
|
+
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.
|
|
1433
|
+
|
|
1434
|
+
**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.
|
|
1435
|
+
|
|
1436
|
+
**What you see in messages:**
|
|
1437
|
+
- A message already marked as a task: \`@Alice: Fix the login bug [task #3 status=in_progress]\`
|
|
1438
|
+
- A regular message (no task suffix): \`@Alice: Can someone look into the login bug?\`
|
|
1439
|
+
- A system notification about task changes: \`\u{1F4CB} Alice converted a message to task #3 "Fix the login bug"\`
|
|
1440
|
+
|
|
1441
|
+
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.
|
|
1442
|
+
|
|
1443
|
+
\`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.
|
|
1444
|
+
|
|
1445
|
+
**Status flow:** \`todo\` \u2192 \`in_progress\` \u2192 \`in_review\` \u2192 \`done\`
|
|
1446
|
+
|
|
1447
|
+
**Assignee** is independent from status \u2014 a task can be claimed or unclaimed at any status except \`done\`.
|
|
1448
|
+
|
|
1449
|
+
**Workflow:**
|
|
1450
|
+
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)
|
|
1451
|
+
2. If the claim fails, someone else is working on it \u2014 move on to another task
|
|
1452
|
+
3. Post updates in the task's thread: \`slock message send --target "#channel:msgShortId" <<'${D}'\` followed by the message body and \`${D}\`
|
|
1453
|
+
4. When done, set status to \`in_review\` so a human can validate via \`slock task update\`
|
|
1454
|
+
5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
|
|
1455
|
+
|
|
1456
|
+
**What \`slock task create\` really means:**
|
|
1457
|
+
- Tasks live in the same chat flow as messages. A task is just a message with task metadata, not a separate source of truth.
|
|
1458
|
+
- \`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.
|
|
1459
|
+
- \`slock task create\` only creates the task \u2014 to own it, call \`slock task claim\` afterward.
|
|
1460
|
+
- 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.
|
|
1461
|
+
- If someone already sent the work item as a message, just claim that existing message/task instead of creating a new one.
|
|
1462
|
+
- If the work already exists as a message, reuse it via \`slock task claim --message-id ...\`.
|
|
1463
|
+
|
|
1464
|
+
**Creating new tasks:**
|
|
1465
|
+
- 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.
|
|
1466
|
+
- If a message already shows a \`[task #N ...]\` suffix, claim \`#N\` if it is yours to take; otherwise move on.
|
|
1467
|
+
- Before calling \`slock task create\`, first check whether the work already exists on the task board or is already being handled.
|
|
1468
|
+
- Reuse existing tasks and threads instead of creating duplicates.
|
|
1469
|
+
- Use \`slock task create\` only for genuinely new subtasks or follow-up work that does not already have a canonical task.`;
|
|
1470
|
+
}
|
|
1471
|
+
function buildSplittingTasksSection() {
|
|
1472
|
+
return `### Splitting tasks for parallel execution
|
|
1473
|
+
|
|
1474
|
+
When you need to break down a large task into subtasks, structure them so agents can work **in parallel**:
|
|
1475
|
+
- **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.
|
|
1476
|
+
- **Prefer independent subtasks** that don't block each other. Each subtask should be completable without waiting for another.
|
|
1477
|
+
- **Avoid creating sequential chains** where each task depends on the previous one \u2014 this forces agents to work one at a time, wasting capacity.
|
|
1478
|
+
|
|
1479
|
+
When you receive a notification about new tasks, check the task board and claim tasks relevant to your skills.`;
|
|
1480
|
+
}
|
|
1481
|
+
function buildMentionsSection(identity) {
|
|
1482
|
+
return `## @Mentions
|
|
1483
|
+
|
|
1484
|
+
In channel group chats, you can @mention people by their unique name (e.g. @alice or @bob).
|
|
1485
|
+
- Your stable Slock @mention handle is \`@${identity.handle}\`.
|
|
1486
|
+
- Your display name is \`${identity.displayName || identity.handle}\`. Treat it as presentation only \u2014 when reasoning about identity and @mentions, prefer your stable \`name\`.
|
|
1487
|
+
- Every human and agent has a unique \`name\` \u2014 this is their stable identifier for @mentions.
|
|
1488
|
+
- Mention others, not yourself \u2014 assign reviews and follow-ups to teammates.
|
|
1489
|
+
- @mentions only reach people inside the channel \u2014 channels are the isolation boundary.`;
|
|
1490
|
+
}
|
|
1491
|
+
function buildCommunicationStyleSection() {
|
|
1492
|
+
return `## Communication style
|
|
1493
|
+
|
|
1494
|
+
Keep the user informed. They cannot see your internal reasoning, so:
|
|
1495
|
+
- When you receive a task, acknowledge it and briefly outline your plan before starting.
|
|
1496
|
+
- For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
|
|
1497
|
+
- When done, summarize the result.
|
|
1498
|
+
- Keep updates concise \u2014 one or two sentences. Don't flood the chat.`;
|
|
1499
|
+
}
|
|
1500
|
+
function buildConversationEtiquetteSection(taskClaimCmd = "`slock task claim`") {
|
|
1501
|
+
return `### Conversation etiquette
|
|
1502
|
+
|
|
1503
|
+
- **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.
|
|
1504
|
+
- **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.
|
|
1505
|
+
- **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.
|
|
1506
|
+
- **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.
|
|
1507
|
+
- **Skip idle narration.** Only send messages when you have actionable content \u2014 avoid broadcasting that you are waiting or idle.`;
|
|
1508
|
+
}
|
|
1509
|
+
function buildFormattingMentionsChannelsSection() {
|
|
1510
|
+
return `### Formatting \u2014 Mentions & Channel Refs
|
|
1511
|
+
|
|
1512
|
+
Slock auto-renders these inline tokens as interactive links whenever they appear as bare text in your message:
|
|
1513
|
+
|
|
1514
|
+
- @alice \u2014 links to a user
|
|
1515
|
+
- #general or #1 \u2014 links to a channel
|
|
1516
|
+
- #engineering:b885b5ae \u2014 links to a specific thread (channel name + msg ID suffix)
|
|
1517
|
+
- task #123 \u2014 links to a task (always write "task #N", not bare "#N" which is ambiguous with PRs/issues)
|
|
1518
|
+
|
|
1519
|
+
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.
|
|
1520
|
+
|
|
1521
|
+
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.`;
|
|
1522
|
+
}
|
|
1523
|
+
function buildFormattingUrlsSection() {
|
|
1524
|
+
return `### Formatting \u2014 URLs in non-English text
|
|
1525
|
+
|
|
1526
|
+
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.
|
|
1527
|
+
|
|
1528
|
+
- **Wrong**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1Ahttp://localhost:3000\uFF0C\u8BF7\u67E5\u770B\` (the \`\uFF0C\` gets swallowed into the link)
|
|
1529
|
+
- **Correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A<http://localhost:3000>\uFF0C\u8BF7\u67E5\u770B\`
|
|
1530
|
+
- **Also correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A[http://localhost:3000](http://localhost:3000)\uFF0C\u8BF7\u67E5\u770B\``;
|
|
1531
|
+
}
|
|
1532
|
+
function buildWorkspaceAndMemorySection() {
|
|
1533
|
+
return `## Workspace & Memory
|
|
1534
|
+
|
|
1535
|
+
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.
|
|
1536
|
+
|
|
1537
|
+
### MEMORY.md \u2014 Your Memory Index (CRITICAL)
|
|
1538
|
+
|
|
1539
|
+
\`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.
|
|
1540
|
+
|
|
1541
|
+
\`\`\`markdown
|
|
1542
|
+
# <Your Name>
|
|
1543
|
+
|
|
1544
|
+
## Role
|
|
1545
|
+
<your role definition, evolved over time>
|
|
1546
|
+
|
|
1547
|
+
## Key Knowledge
|
|
1548
|
+
- Read notes/user-preferences.md for user preferences and conventions
|
|
1549
|
+
- Read notes/channels.md for what each channel is about and ongoing work
|
|
1550
|
+
- Read notes/domain.md for domain-specific knowledge and conventions
|
|
1551
|
+
- ...
|
|
1552
|
+
|
|
1553
|
+
## Active Context
|
|
1554
|
+
- Currently working on: <brief summary>
|
|
1555
|
+
- Last interaction: <brief summary>
|
|
1556
|
+
\`\`\`
|
|
1557
|
+
|
|
1558
|
+
### What to memorize
|
|
1559
|
+
|
|
1560
|
+
**Actively observe and record** the following kinds of knowledge as you encounter them in conversations:
|
|
1561
|
+
|
|
1562
|
+
1. **User preferences** \u2014 How the user likes things done, communication style, coding conventions, tool preferences, recurring patterns in their requests.
|
|
1563
|
+
2. **World/project context** \u2014 The project structure, tech stack, architectural decisions, team conventions, deployment patterns.
|
|
1564
|
+
3. **Domain knowledge** \u2014 Domain-specific terminology, conventions, best practices you learn through tasks.
|
|
1565
|
+
4. **Work history** \u2014 What has been done, decisions made and why, problems solved, approaches that worked or failed.
|
|
1566
|
+
5. **Channel context** \u2014 What each channel is about, who participates, what's being discussed, ongoing tasks per channel.
|
|
1567
|
+
6. **Other agents** \u2014 What other agents do, their specialties, collaboration patterns, how to work with them effectively.
|
|
1568
|
+
|
|
1569
|
+
### How to organize memory
|
|
1570
|
+
|
|
1571
|
+
- **MEMORY.md** is always the index. Keep it concise but comprehensive as a table of contents.
|
|
1572
|
+
- Create a \`notes/\` directory for detailed knowledge files. Use descriptive names:
|
|
1573
|
+
- \`notes/user-preferences.md\` \u2014 User's preferences and conventions
|
|
1574
|
+
- \`notes/channels.md\` \u2014 Summary of each channel and its purpose
|
|
1575
|
+
- \`notes/work-log.md\` \u2014 Important decisions and completed work
|
|
1576
|
+
- \`notes/<domain>.md\` \u2014 Domain-specific knowledge
|
|
1577
|
+
- You can also create any other files or directories for your work (scripts, notes, data, etc.)
|
|
1578
|
+
- **Update notes proactively** \u2014 Don't wait to be asked. When you learn something important, write it down.
|
|
1579
|
+
- **Keep MEMORY.md current** \u2014 After updating notes, update the index in MEMORY.md if new files were added.`;
|
|
1580
|
+
}
|
|
1581
|
+
function buildCompactionSafetySection() {
|
|
1582
|
+
return `### Compaction safety (CRITICAL)
|
|
1583
|
+
|
|
1584
|
+
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:
|
|
1585
|
+
|
|
1586
|
+
- **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.
|
|
1587
|
+
- **Before a long task**, write a brief "Active Context" note in MEMORY.md so you can resume if interrupted mid-task.
|
|
1588
|
+
- **After completing work**, update your notes and MEMORY.md index so nothing is lost.
|
|
1589
|
+
- 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.`;
|
|
1590
|
+
}
|
|
1591
|
+
function buildSlockCliGuideSections(options) {
|
|
1592
|
+
return {
|
|
1593
|
+
communication: buildCommunicationSection(options.audience),
|
|
1594
|
+
credentialHygiene: buildCredentialHygieneSection(),
|
|
1595
|
+
sendingMessages: buildSendingMessagesSection(),
|
|
1596
|
+
reminders: buildRemindersSection(),
|
|
1597
|
+
threads: buildThreadsSection(),
|
|
1598
|
+
discovery: buildDiscoverySection(),
|
|
1599
|
+
channelAwareness: buildChannelAwarenessSection(),
|
|
1600
|
+
readingHistory: buildReadingHistorySection(),
|
|
1601
|
+
historicalReferences: buildHistoricalReferencesSection(),
|
|
1602
|
+
tasks: buildTasksSection(),
|
|
1603
|
+
splittingTasks: buildSplittingTasksSection(),
|
|
1604
|
+
mentions: buildMentionsSection(options.identity),
|
|
1605
|
+
communicationStyle: buildCommunicationStyleSection(),
|
|
1606
|
+
conversationEtiquette: buildConversationEtiquetteSection(options.taskClaimCmd),
|
|
1607
|
+
formattingMentionsChannels: buildFormattingMentionsChannelsSection(),
|
|
1608
|
+
formattingUrls: buildFormattingUrlsSection(),
|
|
1609
|
+
workspaceAndMemory: buildWorkspaceAndMemorySection(),
|
|
1610
|
+
compactionSafety: buildCompactionSafetySection()
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1279
1614
|
// src/drivers/systemPrompt.ts
|
|
1280
1615
|
function toolRef(prefix, name) {
|
|
1281
1616
|
return `${prefix}${name}`;
|
|
@@ -1304,11 +1639,22 @@ function runtimeContextLines(config) {
|
|
|
1304
1639
|
function buildPrompt(config, variant, opts) {
|
|
1305
1640
|
const isCli = variant === "cli";
|
|
1306
1641
|
const t = (name) => toolRef(opts.toolPrefix, name);
|
|
1642
|
+
const taskClaimCmd = isCli ? "`slock task claim`" : `\`${t("claim_tasks")}\``;
|
|
1643
|
+
const cliGuideSections = buildSlockCliGuideSections({
|
|
1644
|
+
// The daemon prompt audience is always managed-runner regardless of
|
|
1645
|
+
// CLI vs MCP variant — both are daemon-spawned. The self-hosted-runner
|
|
1646
|
+
// audience is only used by the manual topic generator script.
|
|
1647
|
+
audience: "managed-runner",
|
|
1648
|
+
identity: {
|
|
1649
|
+
handle: config.name,
|
|
1650
|
+
displayName: config.displayName || config.name
|
|
1651
|
+
},
|
|
1652
|
+
taskClaimCmd
|
|
1653
|
+
});
|
|
1307
1654
|
const sendCmd = isCli ? "`slock message send`" : `\`${t("send_message")}\``;
|
|
1308
1655
|
const readCmd = isCli ? "`slock message read`" : `\`${t("read_history")}\``;
|
|
1309
1656
|
const checkCmd = isCli ? "`slock message check`" : `\`${t("check_messages")}\``;
|
|
1310
1657
|
const inboxCheckCmd = isCli ? "`slock inbox check`" : checkCmd;
|
|
1311
|
-
const taskClaimCmd = isCli ? "`slock task claim`" : `\`${t("claim_tasks")}\``;
|
|
1312
1658
|
const taskCreateCmd = isCli ? "`slock task create`" : `\`${t("create_tasks")}\``;
|
|
1313
1659
|
const taskUpdateCmd = isCli ? "`slock task update`" : `\`${t("update_task_status")}\``;
|
|
1314
1660
|
const serverInfoCmd = isCli ? "`slock server info`" : `\`${t("list_server")}\``;
|
|
@@ -1347,48 +1693,7 @@ function buildPrompt(config, variant, opts) {
|
|
|
1347
1693
|
`4. When you receive a message, process it and reply with ${sendCmd}.`,
|
|
1348
1694
|
"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
1695
|
];
|
|
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
|
|
1696
|
+
const communicationSection = isCli ? cliGuideSections.communication : `## Communication \u2014 MCP tools ONLY
|
|
1392
1697
|
|
|
1393
1698
|
You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
1394
1699
|
|
|
@@ -1409,34 +1714,18 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
|
1409
1714
|
15. **${scheduleReminderCmd}** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
1410
1715
|
16. **${listRemindersCmd}** \u2014 List your reminders.
|
|
1411
1716
|
17. **${cancelReminderCmd}** \u2014 Cancel one of your reminders by ID.`;
|
|
1412
|
-
const
|
|
1717
|
+
const credentialHygieneSection = isCli ? cliGuideSections.credentialHygiene : `### Credential hygiene
|
|
1718
|
+
|
|
1719
|
+
**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.
|
|
1720
|
+
|
|
1721
|
+
If a tool or error output contains credential-shaped strings, redact them to \`sk_agent_<redacted>\` / \`sk_machine_<redacted>\` shape before reposting.`;
|
|
1722
|
+
const reminderSection = isCli ? cliGuideSections.reminders : `### Reminders
|
|
1413
1723
|
|
|
1414
1724
|
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
1725
|
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
1726
|
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
1727
|
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
|
|
1728
|
+
const sendingMessagesSection = isCli ? cliGuideSections.sendingMessages : `### Sending messages
|
|
1440
1729
|
|
|
1441
1730
|
- **Reply to a channel**: \`${t("send_message")}(target="#channel-name", content="...")\`
|
|
1442
1731
|
- **Reply to a DM**: \`${t("send_message")}(target="dm:@peer-name", content="...")\`
|
|
@@ -1444,17 +1733,7 @@ If Slock says a message was not sent and was saved as a draft, choose one path:
|
|
|
1444
1733
|
- **Start a NEW DM**: \`${t("send_message")}(target="dm:@person-name", content="...")\`
|
|
1445
1734
|
|
|
1446
1735
|
**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
|
|
1736
|
+
const threadsSection = isCli ? cliGuideSections.threads : `### Threads
|
|
1458
1737
|
|
|
1459
1738
|
Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
|
|
1460
1739
|
|
|
@@ -1464,21 +1743,12 @@ Threads are sub-conversations attached to a specific message. They let you discu
|
|
|
1464
1743
|
- When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
|
|
1465
1744
|
- You can read thread history via ${readCmd} with the same thread target.
|
|
1466
1745
|
- 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
|
|
1746
|
+
const discoverySection = isCli ? cliGuideSections.discovery : `### Discovering people and channels
|
|
1472
1747
|
|
|
1473
1748
|
Call ${serverInfoCmd} to see all channels in this server, which ones you have joined, other agents, and humans.
|
|
1474
1749
|
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
1750
|
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
|
|
1751
|
+
const channelAwarenessSection = isCli ? cliGuideSections.channelAwareness : `### Channel awareness
|
|
1482
1752
|
|
|
1483
1753
|
Each channel has a **name** and optionally a **description** that define its purpose (visible via ${serverInfoCmd}). Respect them:
|
|
1484
1754
|
- **Reply in context** \u2014 always respond in the channel/thread the message came from.
|
|
@@ -1489,62 +1759,15 @@ Each channel has a **name** and optionally a **description** that define its pur
|
|
|
1489
1759
|
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
1760
|
|
|
1491
1761
|
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
|
|
1762
|
+
const readingHistorySection = isCli ? cliGuideSections.readingHistory : `### Reading history
|
|
1499
1763
|
|
|
1500
1764
|
Use ${readCmd} with the \`channel\` parameter set to \`"#channel-name"\`, \`"dm:@peer-name"\`, or a thread target like \`"#channel:shortid"\`.
|
|
1501
1765
|
|
|
1502
1766
|
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
|
|
1767
|
+
const historicalReferenceSection = isCli ? cliGuideSections.historicalReferences : `### Historical references
|
|
1506
1768
|
|
|
1507
1769
|
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
|
|
1770
|
+
const tasksSection = isCli ? cliGuideSections.tasks : `### Tasks
|
|
1548
1771
|
|
|
1549
1772
|
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
1773
|
|
|
@@ -1584,7 +1807,6 @@ ${readCmd} shows messages in their current state. If a message was later convert
|
|
|
1584
1807
|
- Before calling ${taskCreateCmd}, first check whether the work already exists on the task board or is already being handled.
|
|
1585
1808
|
- Reuse existing tasks and threads instead of creating duplicates.
|
|
1586
1809
|
- 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
1810
|
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
1811
|
|
|
1590
1812
|
## Who you are
|
|
@@ -1595,6 +1817,8 @@ ${runtimeContextLines(config).join("\n")}
|
|
|
1595
1817
|
|
|
1596
1818
|
${communicationSection}
|
|
1597
1819
|
|
|
1820
|
+
${credentialHygieneSection}
|
|
1821
|
+
|
|
1598
1822
|
CRITICAL RULES:
|
|
1599
1823
|
${criticalRules.join("\n")}
|
|
1600
1824
|
|
|
@@ -1666,117 +1890,21 @@ ${historicalReferenceSection}
|
|
|
1666
1890
|
|
|
1667
1891
|
${tasksSection}
|
|
1668
1892
|
|
|
1669
|
-
|
|
1893
|
+
${cliGuideSections.splittingTasks}
|
|
1670
1894
|
|
|
1671
|
-
|
|
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.
|
|
1895
|
+
${cliGuideSections.mentions}
|
|
1675
1896
|
|
|
1676
|
-
|
|
1897
|
+
${cliGuideSections.communicationStyle}
|
|
1677
1898
|
|
|
1678
|
-
|
|
1899
|
+
${cliGuideSections.conversationEtiquette}
|
|
1679
1900
|
|
|
1680
|
-
|
|
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.
|
|
1901
|
+
${cliGuideSections.formattingMentionsChannels}
|
|
1686
1902
|
|
|
1687
|
-
|
|
1903
|
+
${cliGuideSections.formattingUrls}
|
|
1688
1904
|
|
|
1689
|
-
|
|
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.
|
|
1905
|
+
${cliGuideSections.workspaceAndMemory}
|
|
1713
1906
|
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
### Formatting \u2014 URLs in non-English text
|
|
1717
|
-
|
|
1718
|
-
When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.) and the URL should render as a link, wrap the URL in angle brackets or use markdown link syntax. Otherwise the punctuation may be rendered as part of the URL.
|
|
1719
|
-
|
|
1720
|
-
- **Wrong**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1Ahttp://localhost:3000\uFF0C\u8BF7\u67E5\u770B\` (the \`\uFF0C\` gets swallowed into the link)
|
|
1721
|
-
- **Correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A<http://localhost:3000>\uFF0C\u8BF7\u67E5\u770B\`
|
|
1722
|
-
- **Also correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A[http://localhost:3000](http://localhost:3000)\uFF0C\u8BF7\u67E5\u770B\`
|
|
1723
|
-
|
|
1724
|
-
## Workspace & Memory
|
|
1725
|
-
|
|
1726
|
-
Your working directory (cwd) is your **persistent, agent-owned workspace**; files you create here survive across sessions. Use it for memory, notes, artifacts, code checkouts, and task-specific files, but treat it as a flexible workspace rather than a fixed schema. Keep **MEMORY.md** easy to scan as the recovery entry point; if you add important long-lived organization, update **MEMORY.md** or a note index so future sessions can find it. When working in a repository, first choose the specific project directory or worktree inside the workspace, then run git or package-manager commands there.
|
|
1727
|
-
|
|
1728
|
-
### MEMORY.md \u2014 Your Memory Index (CRITICAL)
|
|
1729
|
-
|
|
1730
|
-
\`MEMORY.md\` is the **entry point** to all your knowledge. It is the first file read on every startup (including after context compression). Structure it as an index that points to everything you know. This file is called \`MEMORY.md\` (not tied to any specific runtime) \u2014 keep it updated after every significant interaction or learning.
|
|
1731
|
-
|
|
1732
|
-
\`\`\`markdown
|
|
1733
|
-
# <Your Name>
|
|
1734
|
-
|
|
1735
|
-
## Role
|
|
1736
|
-
<your role definition, evolved over time>
|
|
1737
|
-
|
|
1738
|
-
## Key Knowledge
|
|
1739
|
-
- Read notes/user-preferences.md for user preferences and conventions
|
|
1740
|
-
- Read notes/channels.md for what each channel is about and ongoing work
|
|
1741
|
-
- Read notes/domain.md for domain-specific knowledge and conventions
|
|
1742
|
-
- ...
|
|
1743
|
-
|
|
1744
|
-
## Active Context
|
|
1745
|
-
- Currently working on: <brief summary>
|
|
1746
|
-
- Last interaction: <brief summary>
|
|
1747
|
-
\`\`\`
|
|
1748
|
-
|
|
1749
|
-
### What to memorize
|
|
1750
|
-
|
|
1751
|
-
**Actively observe and record** the following kinds of knowledge as you encounter them in conversations:
|
|
1752
|
-
|
|
1753
|
-
1. **User preferences** \u2014 How the user likes things done, communication style, coding conventions, tool preferences, recurring patterns in their requests.
|
|
1754
|
-
2. **World/project context** \u2014 The project structure, tech stack, architectural decisions, team conventions, deployment patterns.
|
|
1755
|
-
3. **Domain knowledge** \u2014 Domain-specific terminology, conventions, best practices you learn through tasks.
|
|
1756
|
-
4. **Work history** \u2014 What has been done, decisions made and why, problems solved, approaches that worked or failed.
|
|
1757
|
-
5. **Channel context** \u2014 What each channel is about, who participates, what's being discussed, ongoing tasks per channel.
|
|
1758
|
-
6. **Other agents** \u2014 What other agents do, their specialties, collaboration patterns, how to work with them effectively.
|
|
1759
|
-
|
|
1760
|
-
### How to organize memory
|
|
1761
|
-
|
|
1762
|
-
- **MEMORY.md** is always the index. Keep it concise but comprehensive as a table of contents.
|
|
1763
|
-
- Create a \`notes/\` directory for detailed knowledge files. Use descriptive names:
|
|
1764
|
-
- \`notes/user-preferences.md\` \u2014 User's preferences and conventions
|
|
1765
|
-
- \`notes/channels.md\` \u2014 Summary of each channel and its purpose
|
|
1766
|
-
- \`notes/work-log.md\` \u2014 Important decisions and completed work
|
|
1767
|
-
- \`notes/<domain>.md\` \u2014 Domain-specific knowledge
|
|
1768
|
-
- You can also create any other files or directories for your work (scripts, notes, data, etc.)
|
|
1769
|
-
- **Update notes proactively** \u2014 Don't wait to be asked. When you learn something important, write it down.
|
|
1770
|
-
- **Keep MEMORY.md current** \u2014 After updating notes, update the index in MEMORY.md if new files were added.
|
|
1771
|
-
|
|
1772
|
-
### Compaction safety (CRITICAL)
|
|
1773
|
-
|
|
1774
|
-
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:
|
|
1775
|
-
|
|
1776
|
-
- **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.
|
|
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.
|
|
1907
|
+
${cliGuideSections.compactionSafety}
|
|
1780
1908
|
|
|
1781
1909
|
## Capabilities
|
|
1782
1910
|
|
|
@@ -1861,6 +1989,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
|
|
|
1861
1989
|
return candidates.filter((candidate) => existsSync(candidate.path));
|
|
1862
1990
|
}
|
|
1863
1991
|
|
|
1992
|
+
// src/authEnv.ts
|
|
1993
|
+
var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
|
|
1994
|
+
var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
|
|
1995
|
+
function scrubDaemonAuthEnv(env) {
|
|
1996
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
1997
|
+
return env;
|
|
1998
|
+
}
|
|
1999
|
+
function scrubDaemonChildEnv(env) {
|
|
2000
|
+
delete env[DAEMON_API_KEY_ENV];
|
|
2001
|
+
delete env[SLOCK_AGENT_TOKEN_ENV];
|
|
2002
|
+
return env;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
1864
2005
|
// src/agentCredentialProxy.ts
|
|
1865
2006
|
import { randomBytes } from "crypto";
|
|
1866
2007
|
import http from "http";
|
|
@@ -3016,7 +3157,9 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
|
|
|
3016
3157
|
var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
|
|
3017
3158
|
var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
3018
3159
|
var RAW_CREDENTIAL_ENV_DENYLIST = [
|
|
3019
|
-
"
|
|
3160
|
+
"SLOCK_AGENT_TOKEN",
|
|
3161
|
+
"SLOCK_AGENT_CREDENTIAL_KEY",
|
|
3162
|
+
"SLOCK_AGENT_CREDENTIAL_KEY_FILE"
|
|
3020
3163
|
];
|
|
3021
3164
|
var cachedOpencliBinPath;
|
|
3022
3165
|
function resolveOpencliBinPath() {
|
|
@@ -3231,7 +3374,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
|
|
|
3231
3374
|
...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
|
|
3232
3375
|
PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
|
|
3233
3376
|
};
|
|
3234
|
-
|
|
3377
|
+
scrubDaemonChildEnv(spawnEnv);
|
|
3235
3378
|
for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
|
|
3236
3379
|
delete spawnEnv[key];
|
|
3237
3380
|
}
|
|
@@ -3660,7 +3803,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
|
|
|
3660
3803
|
}
|
|
3661
3804
|
function resolveCommandOnPath(command, deps = {}) {
|
|
3662
3805
|
const platform = deps.platform ?? process.platform;
|
|
3663
|
-
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
3806
|
+
const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
|
|
3664
3807
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
3665
3808
|
const existsSyncFn = deps.existsSyncFn ?? existsSync2;
|
|
3666
3809
|
if (platform === "win32") {
|
|
@@ -3686,7 +3829,7 @@ function firstExistingPath(candidates, deps = {}) {
|
|
|
3686
3829
|
return null;
|
|
3687
3830
|
}
|
|
3688
3831
|
function readCommandVersion(command, args = [], deps = {}) {
|
|
3689
|
-
const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
|
|
3832
|
+
const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
|
|
3690
3833
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
|
|
3691
3834
|
try {
|
|
3692
3835
|
const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
|
|
@@ -5149,11 +5292,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
|
|
|
5149
5292
|
return parseCursorModelsOutput(String(result.stdout || ""));
|
|
5150
5293
|
}
|
|
5151
5294
|
function buildCursorModelProbeEnv(deps = {}) {
|
|
5152
|
-
return withWindowsUserEnvironment({
|
|
5295
|
+
return scrubDaemonChildEnv(withWindowsUserEnvironment({
|
|
5153
5296
|
...deps.env ?? process.env,
|
|
5154
5297
|
FORCE_COLOR: "0",
|
|
5155
5298
|
NO_COLOR: "1"
|
|
5156
|
-
}, deps);
|
|
5299
|
+
}, deps));
|
|
5157
5300
|
}
|
|
5158
5301
|
function runCursorModelsCommand() {
|
|
5159
5302
|
return spawnSync("cursor-agent", ["models"], {
|
|
@@ -5209,7 +5352,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
|
|
|
5209
5352
|
}
|
|
5210
5353
|
const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
|
|
5211
5354
|
const existsSyncFn = deps.existsSyncFn ?? existsSync5;
|
|
5212
|
-
const env = deps.env ?? process.env;
|
|
5355
|
+
const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
|
|
5213
5356
|
const winPath = path8.win32;
|
|
5214
5357
|
let geminiEntry = null;
|
|
5215
5358
|
try {
|
|
@@ -5381,13 +5524,16 @@ var GeminiDriver = class {
|
|
|
5381
5524
|
// src/drivers/kimi.ts
|
|
5382
5525
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
5383
5526
|
import { spawn as spawn7 } from "child_process";
|
|
5384
|
-
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
5527
|
+
import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
5385
5528
|
import os3 from "os";
|
|
5386
5529
|
import path9 from "path";
|
|
5387
5530
|
var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
|
|
5388
5531
|
var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
|
|
5389
5532
|
var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
|
|
5390
5533
|
var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
|
|
5534
|
+
var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
|
|
5535
|
+
var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
|
|
5536
|
+
var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
|
|
5391
5537
|
function parseToolArguments(raw) {
|
|
5392
5538
|
if (typeof raw !== "string") return raw;
|
|
5393
5539
|
try {
|
|
@@ -5396,6 +5542,73 @@ function parseToolArguments(raw) {
|
|
|
5396
5542
|
return raw;
|
|
5397
5543
|
}
|
|
5398
5544
|
}
|
|
5545
|
+
function readKimiConfigSource(home = os3.homedir(), env = process.env) {
|
|
5546
|
+
const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
5547
|
+
if (inlineConfig && inlineConfig.trim()) {
|
|
5548
|
+
return {
|
|
5549
|
+
raw: inlineConfig,
|
|
5550
|
+
explicitPath: null,
|
|
5551
|
+
sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
|
|
5552
|
+
};
|
|
5553
|
+
}
|
|
5554
|
+
const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
5555
|
+
const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
|
|
5556
|
+
try {
|
|
5557
|
+
return {
|
|
5558
|
+
raw: readFileSync3(configPath, "utf8"),
|
|
5559
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
5560
|
+
sourcePath: configPath
|
|
5561
|
+
};
|
|
5562
|
+
} catch {
|
|
5563
|
+
return {
|
|
5564
|
+
raw: null,
|
|
5565
|
+
explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
|
|
5566
|
+
sourcePath: configPath
|
|
5567
|
+
};
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
function buildKimiSpawnEnv(env = process.env) {
|
|
5571
|
+
const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
|
|
5572
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
|
|
5573
|
+
delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
|
|
5574
|
+
return scrubDaemonChildEnv(spawnEnv);
|
|
5575
|
+
}
|
|
5576
|
+
function buildKimiEffectiveEnv(ctx, overrideEnv) {
|
|
5577
|
+
return {
|
|
5578
|
+
...process.env,
|
|
5579
|
+
...ctx.config.envVars || {},
|
|
5580
|
+
...overrideEnv || {}
|
|
5581
|
+
};
|
|
5582
|
+
}
|
|
5583
|
+
function buildKimiLaunchOptions(ctx, opts = {}) {
|
|
5584
|
+
const env = buildKimiEffectiveEnv(ctx, opts.env);
|
|
5585
|
+
const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
|
|
5586
|
+
const args = [];
|
|
5587
|
+
let configFilePath = null;
|
|
5588
|
+
let configContent = null;
|
|
5589
|
+
if (source.explicitPath) {
|
|
5590
|
+
configFilePath = source.explicitPath;
|
|
5591
|
+
} else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
|
|
5592
|
+
configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
|
|
5593
|
+
configContent = source.raw;
|
|
5594
|
+
if (opts.writeGeneratedConfig !== false) {
|
|
5595
|
+
writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
|
|
5596
|
+
chmodSync(configFilePath, 384);
|
|
5597
|
+
}
|
|
5598
|
+
}
|
|
5599
|
+
if (configFilePath) {
|
|
5600
|
+
args.push("--config-file", configFilePath);
|
|
5601
|
+
}
|
|
5602
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
5603
|
+
args.push("--model", ctx.config.model);
|
|
5604
|
+
}
|
|
5605
|
+
return {
|
|
5606
|
+
args,
|
|
5607
|
+
env: buildKimiSpawnEnv(env),
|
|
5608
|
+
configFilePath,
|
|
5609
|
+
configContent
|
|
5610
|
+
};
|
|
5611
|
+
}
|
|
5399
5612
|
function resolveKimiSpawn(commandArgs, deps = {}) {
|
|
5400
5613
|
return {
|
|
5401
5614
|
command: resolveCommandOnPath("kimi", deps) ?? "kimi",
|
|
@@ -5419,7 +5632,25 @@ var KimiDriver = class {
|
|
|
5419
5632
|
};
|
|
5420
5633
|
model = {
|
|
5421
5634
|
detectedModelsVerifiedAs: "launchable",
|
|
5422
|
-
toLaunchSpec: (modelId) =>
|
|
5635
|
+
toLaunchSpec: (modelId, ctx, opts) => {
|
|
5636
|
+
if (!ctx) return { args: ["--model", modelId] };
|
|
5637
|
+
const launchCtx = {
|
|
5638
|
+
...ctx,
|
|
5639
|
+
config: {
|
|
5640
|
+
...ctx.config,
|
|
5641
|
+
model: modelId
|
|
5642
|
+
}
|
|
5643
|
+
};
|
|
5644
|
+
const launch = buildKimiLaunchOptions(launchCtx, {
|
|
5645
|
+
home: opts?.home,
|
|
5646
|
+
writeGeneratedConfig: false
|
|
5647
|
+
});
|
|
5648
|
+
return {
|
|
5649
|
+
args: launch.args,
|
|
5650
|
+
env: launch.env,
|
|
5651
|
+
configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
|
|
5652
|
+
};
|
|
5653
|
+
}
|
|
5423
5654
|
};
|
|
5424
5655
|
supportsStdinNotification = true;
|
|
5425
5656
|
mcpToolPrefix = "";
|
|
@@ -5473,6 +5704,7 @@ var KimiDriver = class {
|
|
|
5473
5704
|
}
|
|
5474
5705
|
}
|
|
5475
5706
|
}), "utf8");
|
|
5707
|
+
const launch = buildKimiLaunchOptions(ctx);
|
|
5476
5708
|
const args = [
|
|
5477
5709
|
"--wire",
|
|
5478
5710
|
"--yolo",
|
|
@@ -5481,15 +5713,16 @@ var KimiDriver = class {
|
|
|
5481
5713
|
"--mcp-config-file",
|
|
5482
5714
|
mcpConfigPath,
|
|
5483
5715
|
"--session",
|
|
5484
|
-
this.sessionId
|
|
5716
|
+
this.sessionId,
|
|
5717
|
+
...launch.args
|
|
5485
5718
|
];
|
|
5486
5719
|
const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
|
|
5487
5720
|
if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
|
|
5488
5721
|
args.push("--model", launchRuntimeFields.model);
|
|
5489
5722
|
}
|
|
5490
5723
|
const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
|
|
5491
|
-
const
|
|
5492
|
-
const proc = spawn7(
|
|
5724
|
+
const spawnTarget = resolveKimiSpawn(args);
|
|
5725
|
+
const proc = spawn7(spawnTarget.command, spawnTarget.args, {
|
|
5493
5726
|
cwd: ctx.workingDirectory,
|
|
5494
5727
|
stdio: ["pipe", "pipe", "pipe"],
|
|
5495
5728
|
env: spawnEnv,
|
|
@@ -5497,7 +5730,7 @@ var KimiDriver = class {
|
|
|
5497
5730
|
// and has an 8191-character command-line limit. Kimi's official
|
|
5498
5731
|
// installer/uv entrypoint is an executable, so launch it directly and
|
|
5499
5732
|
// keep prompts on stdin / files instead of routing through cmd.exe.
|
|
5500
|
-
shell:
|
|
5733
|
+
shell: spawnTarget.shell
|
|
5501
5734
|
});
|
|
5502
5735
|
proc.stdin?.write(JSON.stringify({
|
|
5503
5736
|
jsonrpc: "2.0",
|
|
@@ -5611,14 +5844,9 @@ var KimiDriver = class {
|
|
|
5611
5844
|
return detectKimiModels();
|
|
5612
5845
|
}
|
|
5613
5846
|
};
|
|
5614
|
-
function detectKimiModels(home = os3.homedir()) {
|
|
5615
|
-
const
|
|
5616
|
-
|
|
5617
|
-
try {
|
|
5618
|
-
raw = readFileSync3(configPath, "utf8");
|
|
5619
|
-
} catch {
|
|
5620
|
-
return null;
|
|
5621
|
-
}
|
|
5847
|
+
function detectKimiModels(home = os3.homedir(), opts = {}) {
|
|
5848
|
+
const raw = readKimiConfigSource(home, opts.env).raw;
|
|
5849
|
+
if (raw === null) return null;
|
|
5622
5850
|
const models = [];
|
|
5623
5851
|
const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
|
|
5624
5852
|
const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
|
|
@@ -5882,7 +6110,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
|
|
|
5882
6110
|
const platform = deps.platform ?? process.platform;
|
|
5883
6111
|
const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
|
|
5884
6112
|
const result = spawnSyncFn("opencode", ["models"], {
|
|
5885
|
-
env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
6113
|
+
env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
|
|
5886
6114
|
encoding: "utf8",
|
|
5887
6115
|
timeout: 5e3,
|
|
5888
6116
|
shell: platform === "win32"
|
|
@@ -6145,7 +6373,6 @@ var OpenCodeDriver = class {
|
|
|
6145
6373
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
6146
6374
|
import { EventEmitter } from "events";
|
|
6147
6375
|
import { mkdirSync as mkdirSync4, readdirSync } from "fs";
|
|
6148
|
-
import { PassThrough, Writable } from "stream";
|
|
6149
6376
|
import path11 from "path";
|
|
6150
6377
|
import {
|
|
6151
6378
|
AuthStorage,
|
|
@@ -6164,6 +6391,13 @@ var PI_PROVIDER_LABELS = {
|
|
|
6164
6391
|
openai: "OpenAI",
|
|
6165
6392
|
openrouter: "OpenRouter"
|
|
6166
6393
|
};
|
|
6394
|
+
function createPiSdkEventMappingState(sessionId = null) {
|
|
6395
|
+
return {
|
|
6396
|
+
sessionId,
|
|
6397
|
+
sessionAnnounced: false,
|
|
6398
|
+
sawTextDelta: false
|
|
6399
|
+
};
|
|
6400
|
+
}
|
|
6167
6401
|
function buildPiSessionDir(workingDirectory) {
|
|
6168
6402
|
return path11.join(workingDirectory, PI_SESSION_DIR);
|
|
6169
6403
|
}
|
|
@@ -6189,12 +6423,6 @@ function findPiSessionFile(sessionDir, sessionId) {
|
|
|
6189
6423
|
const match = entries.find((entry) => entry.endsWith(suffix));
|
|
6190
6424
|
return match ? path11.join(sessionDir, match) : null;
|
|
6191
6425
|
}
|
|
6192
|
-
function piSdkEventToJsonLine(event) {
|
|
6193
|
-
if (event.type === "agent_end") {
|
|
6194
|
-
return JSON.stringify({ type: "agent_end" });
|
|
6195
|
-
}
|
|
6196
|
-
return JSON.stringify(event);
|
|
6197
|
-
}
|
|
6198
6426
|
function detectPiModelsFromRegistry(modelRegistry) {
|
|
6199
6427
|
const models = [];
|
|
6200
6428
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6244,114 +6472,309 @@ function piErrorMessage(error) {
|
|
|
6244
6472
|
}
|
|
6245
6473
|
return "Unknown Pi error";
|
|
6246
6474
|
}
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6475
|
+
function pushSessionInitIfNeeded(state, events) {
|
|
6476
|
+
if (!state.sessionAnnounced && state.sessionId) {
|
|
6477
|
+
events.push({ kind: "session_init", sessionId: state.sessionId });
|
|
6478
|
+
state.sessionAnnounced = true;
|
|
6479
|
+
}
|
|
6480
|
+
}
|
|
6481
|
+
function mapPiAssistantMessageEvent(assistantEvent, state) {
|
|
6482
|
+
switch (assistantEvent.type) {
|
|
6483
|
+
case "thinking_delta":
|
|
6484
|
+
return typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0 ? [{ kind: "thinking", text: assistantEvent.delta }] : [];
|
|
6485
|
+
case "text_delta":
|
|
6486
|
+
if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
|
|
6487
|
+
state.sawTextDelta = true;
|
|
6488
|
+
return [{ kind: "text", text: assistantEvent.delta }];
|
|
6260
6489
|
}
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
|
|
6490
|
+
return [];
|
|
6491
|
+
case "text_end":
|
|
6492
|
+
return !state.sawTextDelta && typeof assistantEvent.content === "string" && assistantEvent.content.length > 0 ? [{ kind: "text", text: assistantEvent.content }] : [];
|
|
6493
|
+
case "error":
|
|
6494
|
+
return [{ kind: "error", message: piErrorMessage(assistantEvent.error.errorMessage || assistantEvent.error) }];
|
|
6495
|
+
case "thinking_start":
|
|
6496
|
+
case "thinking_end":
|
|
6497
|
+
case "text_start":
|
|
6498
|
+
case "toolcall_start":
|
|
6499
|
+
case "toolcall_delta":
|
|
6500
|
+
case "toolcall_end":
|
|
6501
|
+
case "start":
|
|
6502
|
+
case "done":
|
|
6503
|
+
return [];
|
|
6504
|
+
default: {
|
|
6505
|
+
const _exhaustive = assistantEvent;
|
|
6506
|
+
return _exhaustive;
|
|
6507
|
+
}
|
|
6508
|
+
}
|
|
6509
|
+
}
|
|
6510
|
+
function mapPiSdkEventToParsedEvents(event, state) {
|
|
6511
|
+
const events = [];
|
|
6512
|
+
pushSessionInitIfNeeded(state, events);
|
|
6513
|
+
switch (event.type) {
|
|
6514
|
+
case "agent_start":
|
|
6515
|
+
case "turn_start":
|
|
6516
|
+
case "turn_end":
|
|
6517
|
+
case "message_end":
|
|
6518
|
+
case "tool_execution_update":
|
|
6519
|
+
case "queue_update":
|
|
6520
|
+
case "session_info_changed":
|
|
6521
|
+
case "thinking_level_changed":
|
|
6522
|
+
case "auto_retry_start":
|
|
6523
|
+
case "auto_retry_end":
|
|
6524
|
+
return events;
|
|
6525
|
+
case "message_start":
|
|
6526
|
+
if (event.message.role === "assistant") {
|
|
6527
|
+
state.sawTextDelta = false;
|
|
6528
|
+
}
|
|
6529
|
+
return events;
|
|
6530
|
+
case "message_update":
|
|
6531
|
+
events.push(...mapPiAssistantMessageEvent(event.assistantMessageEvent, state));
|
|
6532
|
+
return events;
|
|
6533
|
+
case "tool_execution_start":
|
|
6534
|
+
events.push({
|
|
6535
|
+
kind: "tool_call",
|
|
6536
|
+
name: event.toolName || "unknown_tool",
|
|
6537
|
+
input: event.args ?? {}
|
|
6538
|
+
});
|
|
6539
|
+
return events;
|
|
6540
|
+
case "tool_execution_end":
|
|
6541
|
+
events.push({ kind: "tool_output", name: event.toolName || "unknown_tool" });
|
|
6542
|
+
return events;
|
|
6543
|
+
case "compaction_start":
|
|
6544
|
+
events.push({ kind: "compaction_started" });
|
|
6545
|
+
return events;
|
|
6546
|
+
case "compaction_end":
|
|
6547
|
+
events.push({ kind: "compaction_finished" });
|
|
6548
|
+
return events;
|
|
6549
|
+
case "agent_end":
|
|
6550
|
+
events.push({ kind: "turn_end", sessionId: state.sessionId || void 0 });
|
|
6551
|
+
return events;
|
|
6552
|
+
default: {
|
|
6553
|
+
const _exhaustive = event;
|
|
6554
|
+
return _exhaustive;
|
|
6555
|
+
}
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
6558
|
+
var PI_RUNTIME_SESSION_DESCRIPTOR = {
|
|
6559
|
+
transport: "sdk",
|
|
6560
|
+
lifecycle: "sdk_session",
|
|
6561
|
+
input: {
|
|
6562
|
+
initial: "start",
|
|
6563
|
+
idle: "sdk_prompt",
|
|
6564
|
+
busy: "sdk_steer"
|
|
6565
|
+
},
|
|
6566
|
+
readiness: "sdk_ready",
|
|
6567
|
+
turnBoundary: "sdk_event",
|
|
6568
|
+
startPolicy: "immediate",
|
|
6569
|
+
inFlightWake: "steer",
|
|
6570
|
+
busyDelivery: "direct",
|
|
6571
|
+
postTurn: "keep_alive"
|
|
6572
|
+
};
|
|
6573
|
+
async function createPiAgentSessionForContext(ctx, sessionId) {
|
|
6574
|
+
const sessionDir = buildPiSessionDir(ctx.workingDirectory);
|
|
6575
|
+
mkdirSync4(sessionDir, { recursive: true });
|
|
6576
|
+
const spawnEnv = await buildPiSpawnEnv(ctx);
|
|
6577
|
+
const agentDir = spawnEnv.PI_CODING_AGENT_DIR || getAgentDir();
|
|
6578
|
+
const authStorage = AuthStorage.create(path11.join(agentDir, "auth.json"));
|
|
6579
|
+
const modelRegistry = ModelRegistry.create(authStorage, path11.join(agentDir, "models.json"));
|
|
6580
|
+
const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
|
|
6581
|
+
const model = resolvePiModelFromRegistry(launchRuntimeFields.model, modelRegistry);
|
|
6582
|
+
if (launchRuntimeFields.model && launchRuntimeFields.model !== "default" && !model) {
|
|
6583
|
+
throw new Error(`Pi model not found: ${launchRuntimeFields.model}`);
|
|
6584
|
+
}
|
|
6585
|
+
const settingsManager = SettingsManager.inMemory({ compaction: { enabled: false } });
|
|
6586
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
6587
|
+
cwd: ctx.workingDirectory,
|
|
6588
|
+
agentDir,
|
|
6589
|
+
settingsManager,
|
|
6590
|
+
systemPromptOverride: () => ctx.standingPrompt
|
|
6591
|
+
});
|
|
6592
|
+
await resourceLoader.reload();
|
|
6593
|
+
const existingSessionFile = ctx.config.sessionId ? findPiSessionFile(sessionDir, ctx.config.sessionId) : null;
|
|
6594
|
+
const sessionManager = existingSessionFile ? SessionManager.open(existingSessionFile, sessionDir, ctx.workingDirectory) : SessionManager.create(ctx.workingDirectory, sessionDir, { id: sessionId });
|
|
6595
|
+
const { session } = await createAgentSession({
|
|
6596
|
+
cwd: ctx.workingDirectory,
|
|
6597
|
+
agentDir,
|
|
6598
|
+
model,
|
|
6599
|
+
thinkingLevel: launchRuntimeFields.reasoningEffort,
|
|
6600
|
+
authStorage,
|
|
6601
|
+
modelRegistry,
|
|
6602
|
+
resourceLoader,
|
|
6603
|
+
customTools: [
|
|
6604
|
+
createBashTool(ctx.workingDirectory, {
|
|
6605
|
+
spawnHook: (spawnContext) => ({
|
|
6606
|
+
...spawnContext,
|
|
6607
|
+
env: {
|
|
6608
|
+
...spawnContext.env,
|
|
6609
|
+
...spawnEnv
|
|
6610
|
+
}
|
|
6611
|
+
})
|
|
6612
|
+
})
|
|
6613
|
+
],
|
|
6614
|
+
sessionManager,
|
|
6615
|
+
settingsManager
|
|
6616
|
+
});
|
|
6617
|
+
return session;
|
|
6618
|
+
}
|
|
6619
|
+
var PiSdkRuntimeSession = class {
|
|
6620
|
+
constructor(ctx, setCurrentSessionId, sessionFactory = createPiAgentSessionForContext) {
|
|
6621
|
+
this.ctx = ctx;
|
|
6622
|
+
this.setCurrentSessionId = setCurrentSessionId;
|
|
6623
|
+
this.sessionFactory = sessionFactory;
|
|
6624
|
+
this.mappingState = createPiSdkEventMappingState(ctx.config.sessionId || null);
|
|
6625
|
+
}
|
|
6626
|
+
descriptor = PI_RUNTIME_SESSION_DESCRIPTOR;
|
|
6627
|
+
events = new EventEmitter();
|
|
6628
|
+
mappingState;
|
|
6629
|
+
session = null;
|
|
6630
|
+
unsubscribe = null;
|
|
6631
|
+
started = false;
|
|
6632
|
+
didClose = false;
|
|
6633
|
+
requestedStopReason;
|
|
6634
|
+
exitInfo = null;
|
|
6635
|
+
get pid() {
|
|
6636
|
+
return void 0;
|
|
6265
6637
|
}
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
stdin;
|
|
6269
|
-
pid = void 0;
|
|
6270
|
-
exitCode = null;
|
|
6271
|
-
signalCode = null;
|
|
6272
|
-
killed = false;
|
|
6273
|
-
buffer = "";
|
|
6274
|
-
closed = false;
|
|
6275
|
-
kill(signal = "SIGTERM") {
|
|
6276
|
-
if (this.closed) return false;
|
|
6277
|
-
this.killed = true;
|
|
6278
|
-
this.signalCode = typeof signal === "string" ? signal : null;
|
|
6279
|
-
void this.shutdown(null, this.signalCode);
|
|
6280
|
-
return true;
|
|
6638
|
+
get currentSessionId() {
|
|
6639
|
+
return this.mappingState.sessionId;
|
|
6281
6640
|
}
|
|
6282
|
-
|
|
6283
|
-
return this;
|
|
6641
|
+
get exitCode() {
|
|
6642
|
+
return this.exitInfo?.code ?? null;
|
|
6284
6643
|
}
|
|
6285
|
-
|
|
6286
|
-
return this;
|
|
6644
|
+
get signalCode() {
|
|
6645
|
+
return this.exitInfo?.signal ?? null;
|
|
6287
6646
|
}
|
|
6288
|
-
|
|
6289
|
-
this.
|
|
6290
|
-
const lines = this.buffer.split("\n");
|
|
6291
|
-
this.buffer = lines.pop() || "";
|
|
6292
|
-
for (const line of lines) {
|
|
6293
|
-
if (!line.trim() || this.closed) continue;
|
|
6294
|
-
await this.handleCommand(line);
|
|
6295
|
-
}
|
|
6647
|
+
get closed() {
|
|
6648
|
+
return this.didClose;
|
|
6296
6649
|
}
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
return;
|
|
6650
|
+
on(event, cb) {
|
|
6651
|
+
this.events.on(event, cb);
|
|
6652
|
+
}
|
|
6653
|
+
async start(input) {
|
|
6654
|
+
if (this.started) {
|
|
6655
|
+
return { ok: false, reason: "runtime_error", error: "runtime session already started" };
|
|
6304
6656
|
}
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6657
|
+
if (this.didClose) return { ok: false, reason: "closed" };
|
|
6658
|
+
this.started = true;
|
|
6659
|
+
const sessionId = input.sessionId || this.ctx.config.sessionId || randomUUID3();
|
|
6660
|
+
this.mappingState.sessionId = sessionId;
|
|
6661
|
+
this.setCurrentSessionId(sessionId);
|
|
6662
|
+
const session = await this.sessionFactory({
|
|
6663
|
+
...this.ctx,
|
|
6664
|
+
config: {
|
|
6665
|
+
...this.ctx.config,
|
|
6666
|
+
sessionId
|
|
6315
6667
|
}
|
|
6316
|
-
}
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
}
|
|
6668
|
+
}, sessionId);
|
|
6669
|
+
this.session = session;
|
|
6670
|
+
this.mappingState.sessionId = session.sessionId;
|
|
6671
|
+
this.setCurrentSessionId(session.sessionId);
|
|
6672
|
+
this.unsubscribe = session.subscribe((event) => {
|
|
6673
|
+
for (const parsed of mapPiSdkEventToParsedEvents(event, this.mappingState)) {
|
|
6674
|
+
this.events.emit("runtime_event", parsed);
|
|
6675
|
+
}
|
|
6676
|
+
});
|
|
6677
|
+
this.emitSessionInit();
|
|
6678
|
+
this.launchPrompt(input.text);
|
|
6679
|
+
return { ok: true, acceptedAs: "prompt" };
|
|
6680
|
+
}
|
|
6681
|
+
send(input) {
|
|
6682
|
+
if (this.didClose) return { ok: false, reason: "closed" };
|
|
6683
|
+
const session = this.session;
|
|
6684
|
+
if (!session) return { ok: false, reason: "closed" };
|
|
6685
|
+
if (input.mode === "busy") {
|
|
6686
|
+
this.deferSdkCall(() => session.steer(input.text));
|
|
6687
|
+
return { ok: true, acceptedAs: "steer" };
|
|
6688
|
+
}
|
|
6689
|
+
if (session.isStreaming) {
|
|
6690
|
+
return { ok: false, reason: "busy_rejected", error: "Pi session is still streaming" };
|
|
6691
|
+
}
|
|
6692
|
+
this.launchPrompt(input.text);
|
|
6693
|
+
return { ok: true, acceptedAs: "prompt" };
|
|
6694
|
+
}
|
|
6695
|
+
async stop(opts) {
|
|
6696
|
+
if (this.didClose) return;
|
|
6697
|
+
this.requestedStopReason = opts?.reason;
|
|
6698
|
+
const signal = opts?.signal ?? "SIGTERM";
|
|
6699
|
+
const session = this.session;
|
|
6700
|
+
if (session?.isStreaming) {
|
|
6701
|
+
try {
|
|
6702
|
+
await session.abort();
|
|
6703
|
+
} catch (error) {
|
|
6704
|
+
this.events.emit("stderr", piErrorMessage(error));
|
|
6705
|
+
}
|
|
6706
|
+
}
|
|
6707
|
+
await this.disposeSession();
|
|
6708
|
+
this.emitExitAndClose(null, signal);
|
|
6709
|
+
}
|
|
6710
|
+
async dispose() {
|
|
6711
|
+
if (this.didClose) return;
|
|
6712
|
+
await this.disposeSession();
|
|
6713
|
+
this.emitExitAndClose(0, null);
|
|
6714
|
+
}
|
|
6715
|
+
emitSessionInit() {
|
|
6716
|
+
const sessionId = this.mappingState.sessionId;
|
|
6717
|
+
if (!sessionId || this.mappingState.sessionAnnounced) return;
|
|
6718
|
+
this.mappingState.sessionAnnounced = true;
|
|
6719
|
+
this.events.emit("runtime_event", { kind: "session_init", sessionId });
|
|
6720
|
+
}
|
|
6721
|
+
launchPrompt(text) {
|
|
6722
|
+
const session = this.session;
|
|
6723
|
+
if (!session) {
|
|
6724
|
+
this.events.emit("runtime_event", {
|
|
6725
|
+
kind: "error",
|
|
6726
|
+
message: "Pi SDK session is not started"
|
|
6727
|
+
});
|
|
6728
|
+
return;
|
|
6324
6729
|
}
|
|
6730
|
+
this.deferSdkCall(() => session.prompt(text));
|
|
6325
6731
|
}
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6732
|
+
deferSdkCall(invoke) {
|
|
6733
|
+
setImmediate(() => {
|
|
6734
|
+
if (this.didClose) return;
|
|
6735
|
+
try {
|
|
6736
|
+
void invoke().catch((error) => {
|
|
6737
|
+
if (this.didClose) return;
|
|
6738
|
+
this.events.emit("runtime_event", {
|
|
6739
|
+
kind: "error",
|
|
6740
|
+
message: piErrorMessage(error)
|
|
6741
|
+
});
|
|
6742
|
+
});
|
|
6743
|
+
} catch (error) {
|
|
6744
|
+
if (this.didClose) return;
|
|
6745
|
+
this.events.emit("runtime_event", {
|
|
6746
|
+
kind: "error",
|
|
6747
|
+
message: piErrorMessage(error)
|
|
6748
|
+
});
|
|
6749
|
+
}
|
|
6750
|
+
});
|
|
6333
6751
|
}
|
|
6334
|
-
async
|
|
6335
|
-
|
|
6336
|
-
this.
|
|
6337
|
-
this.exitCode = code;
|
|
6338
|
-
this.signalCode = signal;
|
|
6752
|
+
async disposeSession() {
|
|
6753
|
+
const unsubscribe = this.unsubscribe;
|
|
6754
|
+
this.unsubscribe = null;
|
|
6339
6755
|
try {
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
}
|
|
6343
|
-
} catch (error) {
|
|
6344
|
-
this.stderr.write(piErrorMessage(error) + "\n");
|
|
6756
|
+
unsubscribe?.();
|
|
6757
|
+
} catch {
|
|
6345
6758
|
}
|
|
6759
|
+
const session = this.session;
|
|
6760
|
+
this.session = null;
|
|
6346
6761
|
try {
|
|
6347
|
-
|
|
6348
|
-
} catch {
|
|
6762
|
+
session?.dispose();
|
|
6763
|
+
} catch (error) {
|
|
6764
|
+
this.events.emit("stderr", piErrorMessage(error));
|
|
6349
6765
|
}
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
this.
|
|
6353
|
-
this.
|
|
6354
|
-
|
|
6766
|
+
}
|
|
6767
|
+
emitExitAndClose(code, signal) {
|
|
6768
|
+
if (this.didClose) return;
|
|
6769
|
+
this.didClose = true;
|
|
6770
|
+
const info = {
|
|
6771
|
+
code,
|
|
6772
|
+
signal,
|
|
6773
|
+
reason: this.requestedStopReason ? "requested" : "runtime_exit"
|
|
6774
|
+
};
|
|
6775
|
+
this.exitInfo = info;
|
|
6776
|
+
this.events.emit("exit", info);
|
|
6777
|
+
this.events.emit("close", info);
|
|
6355
6778
|
}
|
|
6356
6779
|
};
|
|
6357
6780
|
var PiDriver = class {
|
|
@@ -6378,10 +6801,9 @@ var PiDriver = class {
|
|
|
6378
6801
|
busyDeliveryMode = "direct";
|
|
6379
6802
|
usesSlockCliForCommunication = true;
|
|
6380
6803
|
sessionId = null;
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
process = null;
|
|
6804
|
+
get currentSessionId() {
|
|
6805
|
+
return this.sessionId;
|
|
6806
|
+
}
|
|
6385
6807
|
probe() {
|
|
6386
6808
|
return {
|
|
6387
6809
|
available: true,
|
|
@@ -6391,144 +6813,20 @@ var PiDriver = class {
|
|
|
6391
6813
|
async detectModels() {
|
|
6392
6814
|
return detectPiModels();
|
|
6393
6815
|
}
|
|
6394
|
-
|
|
6395
|
-
this.sessionId = ctx.config.sessionId ||
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
this.requestId = 0;
|
|
6399
|
-
const sessionDir = buildPiSessionDir(ctx.workingDirectory);
|
|
6400
|
-
mkdirSync4(sessionDir, { recursive: true });
|
|
6401
|
-
const spawnEnv = await buildPiSpawnEnv(ctx);
|
|
6402
|
-
const agentDir = spawnEnv.PI_CODING_AGENT_DIR || getAgentDir();
|
|
6403
|
-
const authStorage = AuthStorage.create(path11.join(agentDir, "auth.json"));
|
|
6404
|
-
const modelRegistry = ModelRegistry.create(authStorage, path11.join(agentDir, "models.json"));
|
|
6405
|
-
const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
|
|
6406
|
-
const model = resolvePiModelFromRegistry(launchRuntimeFields.model, modelRegistry);
|
|
6407
|
-
if (launchRuntimeFields.model && launchRuntimeFields.model !== "default" && !model) {
|
|
6408
|
-
throw new Error(`Pi model not found: ${launchRuntimeFields.model}`);
|
|
6409
|
-
}
|
|
6410
|
-
const settingsManager = SettingsManager.inMemory({ compaction: { enabled: false } });
|
|
6411
|
-
const resourceLoader = new DefaultResourceLoader({
|
|
6412
|
-
cwd: ctx.workingDirectory,
|
|
6413
|
-
agentDir,
|
|
6414
|
-
settingsManager,
|
|
6415
|
-
systemPromptOverride: () => ctx.standingPrompt
|
|
6416
|
-
});
|
|
6417
|
-
await resourceLoader.reload();
|
|
6418
|
-
const existingSessionFile = ctx.config.sessionId ? findPiSessionFile(sessionDir, ctx.config.sessionId) : null;
|
|
6419
|
-
const sessionManager = existingSessionFile ? SessionManager.open(existingSessionFile, sessionDir, ctx.workingDirectory) : SessionManager.create(ctx.workingDirectory, sessionDir, { id: this.sessionId });
|
|
6420
|
-
const { session } = await createAgentSession({
|
|
6421
|
-
cwd: ctx.workingDirectory,
|
|
6422
|
-
agentDir,
|
|
6423
|
-
model,
|
|
6424
|
-
thinkingLevel: launchRuntimeFields.reasoningEffort,
|
|
6425
|
-
authStorage,
|
|
6426
|
-
modelRegistry,
|
|
6427
|
-
resourceLoader,
|
|
6428
|
-
customTools: [
|
|
6429
|
-
createBashTool(ctx.workingDirectory, {
|
|
6430
|
-
spawnHook: (spawnContext) => ({
|
|
6431
|
-
...spawnContext,
|
|
6432
|
-
env: {
|
|
6433
|
-
...spawnContext.env,
|
|
6434
|
-
...spawnEnv
|
|
6435
|
-
}
|
|
6436
|
-
})
|
|
6437
|
-
})
|
|
6438
|
-
],
|
|
6439
|
-
sessionManager,
|
|
6440
|
-
settingsManager
|
|
6441
|
-
});
|
|
6442
|
-
this.sessionId = session.sessionId;
|
|
6443
|
-
const proc = new PiSdkProcess(session);
|
|
6444
|
-
this.process = proc;
|
|
6445
|
-
setImmediate(() => {
|
|
6446
|
-
if (this.process === proc && !proc.killed) {
|
|
6447
|
-
this.sendRpcCommand("prompt", { message: ctx.prompt });
|
|
6448
|
-
}
|
|
6816
|
+
createSession(ctx) {
|
|
6817
|
+
this.sessionId = ctx.config.sessionId || null;
|
|
6818
|
+
return new PiSdkRuntimeSession(ctx, (sessionId) => {
|
|
6819
|
+
this.sessionId = sessionId;
|
|
6449
6820
|
});
|
|
6450
|
-
return { process: proc };
|
|
6451
6821
|
}
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
try {
|
|
6455
|
-
event = JSON.parse(line);
|
|
6456
|
-
} catch {
|
|
6457
|
-
return [];
|
|
6458
|
-
}
|
|
6459
|
-
const events = [];
|
|
6460
|
-
if (event.type === "session" && event.id) {
|
|
6461
|
-
this.sessionId = event.id;
|
|
6462
|
-
if (!this.sessionAnnounced) {
|
|
6463
|
-
this.sessionAnnounced = true;
|
|
6464
|
-
events.push({ kind: "session_init", sessionId: event.id });
|
|
6465
|
-
}
|
|
6466
|
-
return events;
|
|
6467
|
-
}
|
|
6468
|
-
if (!this.sessionAnnounced && this.sessionId) {
|
|
6469
|
-
events.push({ kind: "session_init", sessionId: this.sessionId });
|
|
6470
|
-
this.sessionAnnounced = true;
|
|
6471
|
-
}
|
|
6472
|
-
if (event.type === "response") {
|
|
6473
|
-
if (event.data?.sessionId && event.data.sessionId !== this.sessionId) {
|
|
6474
|
-
this.sessionId = event.data.sessionId;
|
|
6475
|
-
}
|
|
6476
|
-
if (event.success === false) {
|
|
6477
|
-
events.push({ kind: "error", message: piErrorMessage(event.error) });
|
|
6478
|
-
}
|
|
6479
|
-
return events;
|
|
6480
|
-
}
|
|
6481
|
-
if (event.type === "message_start" && event.message?.role === "assistant") {
|
|
6482
|
-
this.sawTextDelta = false;
|
|
6483
|
-
return events;
|
|
6484
|
-
}
|
|
6485
|
-
const assistantEvent = event.assistantMessageEvent;
|
|
6486
|
-
if (event.type === "message_update" && assistantEvent) {
|
|
6487
|
-
switch (assistantEvent.type) {
|
|
6488
|
-
case "thinking_delta":
|
|
6489
|
-
if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
|
|
6490
|
-
events.push({ kind: "thinking", text: assistantEvent.delta });
|
|
6491
|
-
}
|
|
6492
|
-
break;
|
|
6493
|
-
case "text_delta":
|
|
6494
|
-
if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
|
|
6495
|
-
this.sawTextDelta = true;
|
|
6496
|
-
events.push({ kind: "text", text: assistantEvent.delta });
|
|
6497
|
-
}
|
|
6498
|
-
break;
|
|
6499
|
-
case "thinking_start":
|
|
6500
|
-
case "text_start":
|
|
6501
|
-
break;
|
|
6502
|
-
case "tool_use":
|
|
6503
|
-
case "tool_call":
|
|
6504
|
-
case "tool_start":
|
|
6505
|
-
events.push({
|
|
6506
|
-
kind: "tool_call",
|
|
6507
|
-
name: assistantEvent.name || assistantEvent.toolName || "unknown_tool",
|
|
6508
|
-
input: assistantEvent.input ?? assistantEvent.parameters ?? {}
|
|
6509
|
-
});
|
|
6510
|
-
break;
|
|
6511
|
-
case "text_end":
|
|
6512
|
-
if (!this.sawTextDelta && typeof assistantEvent.content === "string" && assistantEvent.content.length > 0) {
|
|
6513
|
-
events.push({ kind: "text", text: assistantEvent.content });
|
|
6514
|
-
}
|
|
6515
|
-
break;
|
|
6516
|
-
}
|
|
6517
|
-
return events;
|
|
6518
|
-
}
|
|
6519
|
-
if (event.type === "agent_end") {
|
|
6520
|
-
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
6521
|
-
} else if (event.type === "error") {
|
|
6522
|
-
events.push({ kind: "error", message: piErrorMessage(event.error ?? event.message) });
|
|
6523
|
-
}
|
|
6524
|
-
return events;
|
|
6822
|
+
async spawn(_ctx) {
|
|
6823
|
+
throw new Error("PiDriver uses a native RuntimeSession; child-process spawn is unsupported");
|
|
6525
6824
|
}
|
|
6526
|
-
|
|
6527
|
-
return
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
});
|
|
6825
|
+
parseLine(_line) {
|
|
6826
|
+
return [];
|
|
6827
|
+
}
|
|
6828
|
+
encodeStdinMessage(_text, _sessionId, _opts) {
|
|
6829
|
+
return null;
|
|
6532
6830
|
}
|
|
6533
6831
|
buildSystemPrompt(config, _agentId) {
|
|
6534
6832
|
return buildCliTransportSystemPrompt(config, {
|
|
@@ -6541,18 +6839,141 @@ var PiDriver = class {
|
|
|
6541
6839
|
messageNotificationStyle: "direct"
|
|
6542
6840
|
});
|
|
6543
6841
|
}
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6842
|
+
};
|
|
6843
|
+
|
|
6844
|
+
// src/drivers/runtimeSession.ts
|
|
6845
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
6846
|
+
function descriptorFromDriver(driver) {
|
|
6847
|
+
const lifecycle = driver.lifecycle.kind === "per_turn" ? "turn_based" : "persistent_stream";
|
|
6848
|
+
const idle = driver.supportsStdinNotification ? "stdin" : "unsupported";
|
|
6849
|
+
const busy = driver.supportsStdinNotification ? "stdin_steer" : "unsupported";
|
|
6850
|
+
return {
|
|
6851
|
+
transport: "child_process",
|
|
6852
|
+
lifecycle,
|
|
6853
|
+
input: {
|
|
6854
|
+
initial: "start",
|
|
6855
|
+
idle,
|
|
6856
|
+
busy
|
|
6857
|
+
},
|
|
6858
|
+
readiness: "spawned",
|
|
6859
|
+
turnBoundary: driver.lifecycle.kind === "per_turn" ? "process_exit" : "parsed_event",
|
|
6860
|
+
startPolicy: driver.lifecycle.kind === "per_turn" ? driver.lifecycle.start : "immediate",
|
|
6861
|
+
inFlightWake: driver.lifecycle.inFlightWake,
|
|
6862
|
+
busyDelivery: driver.busyDeliveryMode,
|
|
6863
|
+
postTurn: driver.terminateProcessOnTurnEnd ? "terminate_process" : driver.endStdinOnTurnEnd ? "close_stdin" : "keep_alive"
|
|
6864
|
+
};
|
|
6865
|
+
}
|
|
6866
|
+
var ChildProcessRuntimeSession = class {
|
|
6867
|
+
constructor(driver, ctx) {
|
|
6868
|
+
this.driver = driver;
|
|
6869
|
+
this.ctx = ctx;
|
|
6870
|
+
this.descriptor = descriptorFromDriver(driver);
|
|
6547
6871
|
}
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6872
|
+
descriptor;
|
|
6873
|
+
events = new EventEmitter2();
|
|
6874
|
+
process = null;
|
|
6875
|
+
started = false;
|
|
6876
|
+
stdoutBuffer = "";
|
|
6877
|
+
requestedStopReason;
|
|
6878
|
+
get pid() {
|
|
6879
|
+
return this.process?.pid;
|
|
6880
|
+
}
|
|
6881
|
+
get currentSessionId() {
|
|
6882
|
+
return this.driver.currentSessionId;
|
|
6883
|
+
}
|
|
6884
|
+
get exitCode() {
|
|
6885
|
+
return this.process?.exitCode ?? null;
|
|
6886
|
+
}
|
|
6887
|
+
get signalCode() {
|
|
6888
|
+
return this.process?.signalCode ?? null;
|
|
6889
|
+
}
|
|
6890
|
+
get closed() {
|
|
6891
|
+
return this.process ? this.process.exitCode != null || this.process.signalCode != null : false;
|
|
6892
|
+
}
|
|
6893
|
+
on(event, cb) {
|
|
6894
|
+
this.events.on(event, cb);
|
|
6895
|
+
}
|
|
6896
|
+
async start(input) {
|
|
6897
|
+
if (this.started) {
|
|
6898
|
+
return { ok: false, reason: "runtime_error", error: "runtime session already started" };
|
|
6899
|
+
}
|
|
6900
|
+
this.started = true;
|
|
6901
|
+
const launchCtx = {
|
|
6902
|
+
...this.ctx,
|
|
6903
|
+
prompt: input.text,
|
|
6904
|
+
config: {
|
|
6905
|
+
...this.ctx.config,
|
|
6906
|
+
sessionId: input.sessionId ?? this.ctx.config.sessionId
|
|
6907
|
+
}
|
|
6908
|
+
};
|
|
6909
|
+
const { process: process2 } = await this.driver.spawn(launchCtx);
|
|
6910
|
+
this.process = process2;
|
|
6911
|
+
this.attachProcess(process2);
|
|
6912
|
+
return { ok: true, acceptedAs: "prompt" };
|
|
6913
|
+
}
|
|
6914
|
+
send(input) {
|
|
6915
|
+
const proc = this.process;
|
|
6916
|
+
if (!proc || this.closed) return { ok: false, reason: "closed" };
|
|
6917
|
+
const encoded = this.driver.encodeStdinMessage(input.text, input.sessionId ?? null, { mode: input.mode });
|
|
6918
|
+
if (!encoded) return { ok: false, reason: "unsupported" };
|
|
6919
|
+
proc.stdin?.write(encoded + "\n");
|
|
6920
|
+
return { ok: true, acceptedAs: input.mode === "busy" ? "steer" : "prompt" };
|
|
6921
|
+
}
|
|
6922
|
+
async stop(opts) {
|
|
6923
|
+
const proc = this.process;
|
|
6924
|
+
if (!proc || this.closed) return;
|
|
6925
|
+
this.requestedStopReason = opts?.reason;
|
|
6926
|
+
proc.kill(opts?.signal ?? "SIGTERM");
|
|
6927
|
+
if (!opts?.forceAfterMs) return;
|
|
6928
|
+
setTimeout(() => {
|
|
6929
|
+
if (!this.closed) {
|
|
6930
|
+
try {
|
|
6931
|
+
proc.kill("SIGKILL");
|
|
6932
|
+
} catch {
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6935
|
+
}, opts.forceAfterMs).unref?.();
|
|
6936
|
+
}
|
|
6937
|
+
attachProcess(process2) {
|
|
6938
|
+
process2.stdout?.on("data", (chunk) => {
|
|
6939
|
+
const chunkText = chunk.toString();
|
|
6940
|
+
this.events.emit("stdout", chunkText);
|
|
6941
|
+
this.stdoutBuffer += chunkText;
|
|
6942
|
+
const lines = this.stdoutBuffer.split("\n");
|
|
6943
|
+
this.stdoutBuffer = lines.pop() || "";
|
|
6944
|
+
for (const line of lines) {
|
|
6945
|
+
if (!line.trim()) continue;
|
|
6946
|
+
for (const event of this.driver.parseLine(line)) {
|
|
6947
|
+
this.events.emit("runtime_event", event);
|
|
6948
|
+
}
|
|
6949
|
+
}
|
|
6950
|
+
});
|
|
6951
|
+
process2.stderr?.on("data", (chunk) => {
|
|
6952
|
+
const text = chunk.toString().trim();
|
|
6953
|
+
if (text) this.events.emit("stderr", text);
|
|
6954
|
+
});
|
|
6955
|
+
process2.on("error", (err) => {
|
|
6956
|
+
this.events.emit("error", err);
|
|
6957
|
+
});
|
|
6958
|
+
process2.on("exit", (code, signal) => {
|
|
6959
|
+
this.events.emit("exit", {
|
|
6960
|
+
code,
|
|
6961
|
+
signal,
|
|
6962
|
+
reason: this.requestedStopReason ? "requested" : "runtime_exit"
|
|
6963
|
+
});
|
|
6964
|
+
});
|
|
6965
|
+
process2.on("close", (code, signal) => {
|
|
6966
|
+
this.events.emit("close", {
|
|
6967
|
+
code,
|
|
6968
|
+
signal,
|
|
6969
|
+
reason: this.requestedStopReason ? "requested" : "runtime_exit"
|
|
6970
|
+
});
|
|
6971
|
+
});
|
|
6554
6972
|
}
|
|
6555
6973
|
};
|
|
6974
|
+
function createChildProcessRuntimeSession(driver, ctx) {
|
|
6975
|
+
return new ChildProcessRuntimeSession(driver, ctx);
|
|
6976
|
+
}
|
|
6556
6977
|
|
|
6557
6978
|
// src/drivers/index.ts
|
|
6558
6979
|
var driverFactories = {
|
|
@@ -6953,6 +7374,15 @@ var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS = 3;
|
|
|
6953
7374
|
function assertNeverApmEffect(effect) {
|
|
6954
7375
|
throw new Error(`Unhandled APM gated steering effect: ${String(effect)}`);
|
|
6955
7376
|
}
|
|
7377
|
+
function requireImmediateRuntimeSendResult(result, context) {
|
|
7378
|
+
if (typeof result.then === "function") {
|
|
7379
|
+
throw new Error(`RuntimeSession.send returned async result in synchronous APM path: ${context}`);
|
|
7380
|
+
}
|
|
7381
|
+
return result;
|
|
7382
|
+
}
|
|
7383
|
+
function runtimeSendFailureOutcome(result) {
|
|
7384
|
+
return !result.ok && result.reason === "unsupported" ? "encode_failed" : "send_failed";
|
|
7385
|
+
}
|
|
6956
7386
|
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS = 250;
|
|
6957
7387
|
var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
|
|
6958
7388
|
var WORKSPACE_IMAGE_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
|
|
@@ -7900,7 +8330,7 @@ function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
|
|
|
7900
8330
|
if (ap.lastActivityDetail) {
|
|
7901
8331
|
context.push(`after ${ap.lastActivityDetail}`);
|
|
7902
8332
|
}
|
|
7903
|
-
if (ap.
|
|
8333
|
+
if (ap.runtime.descriptor.busyDelivery === "gated") {
|
|
7904
8334
|
context.push(`phase=${ap.gatedSteering.phase}`);
|
|
7905
8335
|
}
|
|
7906
8336
|
if (ap.gatedSteering.outstandingToolUses > 0) {
|
|
@@ -7932,10 +8362,10 @@ function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
|
|
|
7932
8362
|
sessionIdPresent: Boolean(ap.sessionId),
|
|
7933
8363
|
inboxCount: ap.inbox.length,
|
|
7934
8364
|
pendingNotificationCount: ap.notifications.pendingCount,
|
|
7935
|
-
processPidPresent: typeof ap.
|
|
8365
|
+
processPidPresent: typeof ap.runtime.pid === "number",
|
|
7936
8366
|
busyDeliveryMode: ap.driver.busyDeliveryMode,
|
|
7937
8367
|
supportsStdinNotification: ap.driver.supportsStdinNotification,
|
|
7938
|
-
gatedPhase: ap.
|
|
8368
|
+
gatedPhase: ap.runtime.descriptor.busyDelivery === "gated" ? ap.gatedSteering.phase : void 0,
|
|
7939
8369
|
outstandingToolUses: ap.gatedSteering.outstandingToolUses,
|
|
7940
8370
|
compacting: ap.gatedSteering.compacting,
|
|
7941
8371
|
recentStderrCount: ap.recentStderr.length,
|
|
@@ -8078,8 +8508,10 @@ var RUNTIME_TELEMETRY_RESERVED_ATTR_KEYS = /* @__PURE__ */ new Set([
|
|
|
8078
8508
|
"runtimeResultId",
|
|
8079
8509
|
"daemonVersion",
|
|
8080
8510
|
"daemon_version",
|
|
8511
|
+
"daemon_version_present",
|
|
8081
8512
|
"computerVersion",
|
|
8082
|
-
"computer_version"
|
|
8513
|
+
"computer_version",
|
|
8514
|
+
"computer_version_present"
|
|
8083
8515
|
]);
|
|
8084
8516
|
function sanitizeRuntimeTelemetryPayloadAttrs(attrs) {
|
|
8085
8517
|
const sanitized = {};
|
|
@@ -8136,9 +8568,11 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8136
8568
|
stdinNotificationRetryMs;
|
|
8137
8569
|
cliTransportTraceDir = null;
|
|
8138
8570
|
deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
|
|
8139
|
-
|
|
8571
|
+
runtimeExitTraceAttrs = /* @__PURE__ */ new WeakMap();
|
|
8140
8572
|
agentVisibleBoundaries = /* @__PURE__ */ new Map();
|
|
8141
8573
|
agentVisibleMessageIds = /* @__PURE__ */ new Map();
|
|
8574
|
+
daemonVersion;
|
|
8575
|
+
computerVersion;
|
|
8142
8576
|
constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
|
|
8143
8577
|
this.chatBridgePath = chatBridgePath;
|
|
8144
8578
|
this.slockCliPath = opts.slockCliPath ?? "";
|
|
@@ -8150,6 +8584,8 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8150
8584
|
this.driverResolver = opts.driverResolver || getDriver;
|
|
8151
8585
|
this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
|
|
8152
8586
|
this.tracer = opts.tracer ?? noopTracer;
|
|
8587
|
+
this.daemonVersion = opts.daemonVersion?.trim() || null;
|
|
8588
|
+
this.computerVersion = opts.computerVersion?.trim() || null;
|
|
8153
8589
|
this.stdinNotificationRetryMs = Math.max(
|
|
8154
8590
|
0,
|
|
8155
8591
|
Math.floor(opts.stdinNotificationRetryMs ?? STDIN_NOTIFICATION_RETRY_DELAY_MS)
|
|
@@ -8390,7 +8826,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8390
8826
|
});
|
|
8391
8827
|
span.end(status);
|
|
8392
8828
|
}
|
|
8393
|
-
startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
8829
|
+
startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient = false) {
|
|
8394
8830
|
return {
|
|
8395
8831
|
agentId,
|
|
8396
8832
|
launchId,
|
|
@@ -8399,6 +8835,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8399
8835
|
session_id_present: Boolean(config.sessionId),
|
|
8400
8836
|
launch_id_present: Boolean(launchId),
|
|
8401
8837
|
wake_message_present: Boolean(wakeMessage),
|
|
8838
|
+
wake_message_transient: Boolean(wakeMessage && wakeMessageTransient),
|
|
8402
8839
|
unread_channels_count: unreadSummary ? Object.keys(unreadSummary).length : 0,
|
|
8403
8840
|
resume_prompt_present: Boolean(resumePrompt),
|
|
8404
8841
|
queue_depth: this.agentStartQueue.length,
|
|
@@ -8410,6 +8847,9 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8410
8847
|
getDeliveryTraceContext(message) {
|
|
8411
8848
|
return this.deliveryTraceContexts.get(message) ?? {};
|
|
8412
8849
|
}
|
|
8850
|
+
isTransientDelivery(message) {
|
|
8851
|
+
return this.getDeliveryTraceContext(message).transient === true;
|
|
8852
|
+
}
|
|
8413
8853
|
deliveryTraceAttrs(agentId, message, attrs = {}) {
|
|
8414
8854
|
const context = this.getDeliveryTraceContext(message);
|
|
8415
8855
|
const deliveryCorrelationId = context.deliveryId ?? message.message_id;
|
|
@@ -8421,14 +8861,15 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8421
8861
|
sender_type: message.sender_type,
|
|
8422
8862
|
messageId: message.message_id,
|
|
8423
8863
|
message_id_present: Boolean(message.message_id),
|
|
8864
|
+
transient_delivery: context.transient === true,
|
|
8424
8865
|
...attrs
|
|
8425
8866
|
};
|
|
8426
8867
|
}
|
|
8427
|
-
async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
8428
|
-
this.recordDaemonTrace("daemon.agent.start.requested", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
8868
|
+
async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient = false) {
|
|
8869
|
+
this.recordDaemonTrace("daemon.agent.start.requested", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient));
|
|
8429
8870
|
if (this.agents.has(agentId)) {
|
|
8430
8871
|
this.recordDaemonTrace("daemon.agent.start.ignored", {
|
|
8431
|
-
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
8872
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
|
|
8432
8873
|
reason: "already_running"
|
|
8433
8874
|
});
|
|
8434
8875
|
logger.info(`[Agent ${agentId}] Start ignored (already running)`);
|
|
@@ -8436,7 +8877,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8436
8877
|
}
|
|
8437
8878
|
if (this.agentsStarting.has(agentId)) {
|
|
8438
8879
|
this.recordDaemonTrace("daemon.agent.start.ignored", {
|
|
8439
|
-
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
8880
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
|
|
8440
8881
|
reason: "already_starting"
|
|
8441
8882
|
});
|
|
8442
8883
|
logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
|
|
@@ -8444,7 +8885,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8444
8885
|
}
|
|
8445
8886
|
if (this.queuedAgentStarts.has(agentId)) {
|
|
8446
8887
|
this.recordDaemonTrace("daemon.agent.start.ignored", {
|
|
8447
|
-
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
8888
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
|
|
8448
8889
|
reason: "already_queued"
|
|
8449
8890
|
});
|
|
8450
8891
|
logger.info(`[Agent ${agentId}] Start ignored (startup already queued)`);
|
|
@@ -8455,6 +8896,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8455
8896
|
agentId,
|
|
8456
8897
|
config,
|
|
8457
8898
|
wakeMessage,
|
|
8899
|
+
wakeMessageTransient,
|
|
8458
8900
|
unreadSummary,
|
|
8459
8901
|
resumePrompt,
|
|
8460
8902
|
launchId,
|
|
@@ -8463,7 +8905,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8463
8905
|
};
|
|
8464
8906
|
this.agentStartQueue.push(item);
|
|
8465
8907
|
this.queuedAgentStarts.set(agentId, item);
|
|
8466
|
-
this.recordDaemonTrace("daemon.agent.start.queued", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
8908
|
+
this.recordDaemonTrace("daemon.agent.start.queued", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient));
|
|
8467
8909
|
logger.info(
|
|
8468
8910
|
`[Agent ${agentId}] Start queued (queue=${this.agentStartQueue.length}, active=${this.activeAgentStartCount}, max=${this.maxConcurrentAgentStarts}, interval=${this.agentStartIntervalMs}ms)`
|
|
8469
8911
|
);
|
|
@@ -8480,7 +8922,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8480
8922
|
const waitMs = shouldRateLimit ? Math.max(0, this.agentStartIntervalMs - elapsed) : 0;
|
|
8481
8923
|
if (waitMs > 0) {
|
|
8482
8924
|
this.recordDaemonTrace("daemon.agent.start.rate_limited", {
|
|
8483
|
-
...this.startQueueTraceAttrs(next.agentId, next.config, next.wakeMessage, next.unreadSummary, next.resumePrompt, next.launchId),
|
|
8925
|
+
...this.startQueueTraceAttrs(next.agentId, next.config, next.wakeMessage, next.unreadSummary, next.resumePrompt, next.launchId, next.wakeMessageTransient),
|
|
8484
8926
|
wait_ms: waitMs
|
|
8485
8927
|
});
|
|
8486
8928
|
this.agentStartPumpTimer = setTimeout(() => {
|
|
@@ -8493,7 +8935,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8493
8935
|
if (!item) return;
|
|
8494
8936
|
if (this.queuedAgentStarts.get(item.agentId) !== item) {
|
|
8495
8937
|
this.recordDaemonTrace("daemon.agent.start.skipped", {
|
|
8496
|
-
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
8938
|
+
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId, item.wakeMessageTransient),
|
|
8497
8939
|
reason: "stale_queue_item"
|
|
8498
8940
|
});
|
|
8499
8941
|
this.pumpAgentStartQueue();
|
|
@@ -8502,7 +8944,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8502
8944
|
this.queuedAgentStarts.delete(item.agentId);
|
|
8503
8945
|
if (this.agents.has(item.agentId) || this.agentsStarting.has(item.agentId)) {
|
|
8504
8946
|
this.recordDaemonTrace("daemon.agent.start.skipped", {
|
|
8505
|
-
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
8947
|
+
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId, item.wakeMessageTransient),
|
|
8506
8948
|
reason: "already_running_or_starting"
|
|
8507
8949
|
});
|
|
8508
8950
|
logger.info(`[Agent ${item.agentId}] Queued start skipped (already running or starting)`);
|
|
@@ -8516,14 +8958,15 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8516
8958
|
logger.info(
|
|
8517
8959
|
`[Agent ${item.agentId}] Dequeued start (remaining=${this.agentStartQueue.length}, active=${this.activeAgentStartCount})`
|
|
8518
8960
|
);
|
|
8519
|
-
this.recordDaemonTrace("daemon.agent.start.dequeued", this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId));
|
|
8961
|
+
this.recordDaemonTrace("daemon.agent.start.dequeued", this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId, item.wakeMessageTransient));
|
|
8520
8962
|
this.startAgentNow(
|
|
8521
8963
|
item.agentId,
|
|
8522
8964
|
item.config,
|
|
8523
8965
|
item.wakeMessage,
|
|
8524
8966
|
item.unreadSummary,
|
|
8525
8967
|
item.resumePrompt,
|
|
8526
|
-
item.launchId
|
|
8968
|
+
item.launchId,
|
|
8969
|
+
item.wakeMessageTransient ?? false
|
|
8527
8970
|
).then(() => {
|
|
8528
8971
|
this.releaseAgentStartSlot(item.agentId, "spawn attempted");
|
|
8529
8972
|
item.resolve();
|
|
@@ -8558,7 +9001,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8558
9001
|
this.agentStartPumpTimer = null;
|
|
8559
9002
|
}
|
|
8560
9003
|
this.recordDaemonTrace("daemon.agent.start.cancelled", {
|
|
8561
|
-
...this.startQueueTraceAttrs(agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
9004
|
+
...this.startQueueTraceAttrs(agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId, item.wakeMessageTransient),
|
|
8562
9005
|
reason
|
|
8563
9006
|
}, "cancelled");
|
|
8564
9007
|
logger.info(`[Agent ${agentId}] Queued start cancelled (${reason})`);
|
|
@@ -8569,7 +9012,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8569
9012
|
for (const item of this.agentStartQueue) {
|
|
8570
9013
|
if (this.queuedAgentStarts.get(item.agentId) === item) {
|
|
8571
9014
|
this.recordDaemonTrace("daemon.agent.start.cancelled", {
|
|
8572
|
-
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
|
|
9015
|
+
...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId, item.wakeMessageTransient),
|
|
8573
9016
|
reason
|
|
8574
9017
|
}, "cancelled");
|
|
8575
9018
|
logger.info(`[Agent ${item.agentId}] Queued start cancelled (${reason})`);
|
|
@@ -8584,10 +9027,10 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8584
9027
|
this.agentStartPumpTimer = null;
|
|
8585
9028
|
}
|
|
8586
9029
|
}
|
|
8587
|
-
async startAgentNow(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
|
|
9030
|
+
async startAgentNow(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient = false) {
|
|
8588
9031
|
if (this.agents.has(agentId)) {
|
|
8589
9032
|
this.recordDaemonTrace("daemon.agent.spawn.skipped", {
|
|
8590
|
-
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
9033
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
|
|
8591
9034
|
reason: "already_running"
|
|
8592
9035
|
});
|
|
8593
9036
|
logger.info(`[Agent ${agentId}] Start ignored (already running)`);
|
|
@@ -8595,14 +9038,15 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8595
9038
|
}
|
|
8596
9039
|
if (this.agentsStarting.has(agentId)) {
|
|
8597
9040
|
this.recordDaemonTrace("daemon.agent.spawn.skipped", {
|
|
8598
|
-
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
9041
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
|
|
8599
9042
|
reason: "already_starting"
|
|
8600
9043
|
});
|
|
8601
9044
|
logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
|
|
8602
9045
|
return;
|
|
8603
9046
|
}
|
|
8604
9047
|
this.agentsStarting.add(agentId);
|
|
8605
|
-
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
|
|
9048
|
+
this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient));
|
|
9049
|
+
let agentProcess = null;
|
|
8606
9050
|
try {
|
|
8607
9051
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
8608
9052
|
const legacyWakeRuntimeProfile = wakeMessage ? runtimeProfileNotificationFromMessage(wakeMessage) : null;
|
|
@@ -8665,15 +9109,23 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
8665
9109
|
prompt += getBusyDeliveryNote(driver);
|
|
8666
9110
|
promptSource = "resume_prompt";
|
|
8667
9111
|
} else if (wakeMessage) {
|
|
8668
|
-
const
|
|
8669
|
-
|
|
9112
|
+
const transientWakeMessage = wakeMessageTransient === true;
|
|
9113
|
+
const runtimeProfileControlPrompt = transientWakeMessage ? null : formatRuntimeProfileControlPrompt([wakeMessage]);
|
|
9114
|
+
if (transientWakeMessage) {
|
|
9115
|
+
prompt = `System notice received:
|
|
9116
|
+
|
|
9117
|
+
${formatIncomingMessage(wakeMessage, driver)}
|
|
9118
|
+
|
|
9119
|
+
Respond as appropriate. Complete all your work before stopping.
|
|
9120
|
+
${RESPONSE_TARGET_HINT}`;
|
|
9121
|
+
} else if (runtimeProfileControlPrompt) {
|
|
8670
9122
|
prompt = runtimeProfileControlPrompt;
|
|
8671
9123
|
} else {
|
|
8672
9124
|
wakeMessageDeliveredAsInboxUpdate = true;
|
|
8673
9125
|
prompt = this.formatInboxUpdateRuntimeInput([wakeMessage, ...startingInboxMessages], driver);
|
|
8674
9126
|
}
|
|
8675
|
-
promptSource = runtimeProfileControlPrompt ? "runtime_profile_control_message" : "wake_inbox_update";
|
|
8676
|
-
if (!runtimeProfileControlPrompt && unreadSummary && Object.keys(unreadSummary).length > 0) {
|
|
9127
|
+
promptSource = transientWakeMessage ? "transient_wake_message" : runtimeProfileControlPrompt ? "runtime_profile_control_message" : "wake_inbox_update";
|
|
9128
|
+
if (!transientWakeMessage && !runtimeProfileControlPrompt && unreadSummary && Object.keys(unreadSummary).length > 0) {
|
|
8677
9129
|
const otherUnread = Object.entries(unreadSummary);
|
|
8678
9130
|
if (otherUnread.length > 0) {
|
|
8679
9131
|
prompt += `
|
|
@@ -8730,7 +9182,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8730
9182
|
this.sendAgentStatus(agentId, "active", launchId || null);
|
|
8731
9183
|
this.broadcastActivity(agentId, "online", "Process idle");
|
|
8732
9184
|
this.recordDaemonTrace("daemon.agent.spawn.deferred", {
|
|
8733
|
-
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
|
|
9185
|
+
...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
|
|
8734
9186
|
pending_messages_count: pendingMessages.length,
|
|
8735
9187
|
reason: "defer_until_concrete_message"
|
|
8736
9188
|
});
|
|
@@ -8740,7 +9192,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8740
9192
|
}
|
|
8741
9193
|
return;
|
|
8742
9194
|
}
|
|
8743
|
-
const
|
|
9195
|
+
const runtimeContext = {
|
|
8744
9196
|
agentId,
|
|
8745
9197
|
config: effectiveConfig,
|
|
8746
9198
|
standingPrompt,
|
|
@@ -8752,15 +9204,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8752
9204
|
launchId: launchId || null,
|
|
8753
9205
|
agentCredentialProxyInboxCoordinator: this.createAgentProxyInboxCoordinator(agentId),
|
|
8754
9206
|
cliTransportTraceDir: this.cliTransportTraceDir
|
|
8755
|
-
}
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
new_session: false,
|
|
8760
|
-
process_pid_present: typeof proc.pid === "number"
|
|
8761
|
-
});
|
|
8762
|
-
const agentProcess = {
|
|
8763
|
-
process: proc,
|
|
9207
|
+
};
|
|
9208
|
+
const runtime = driver.createSession?.(runtimeContext) ?? createChildProcessRuntimeSession(driver, runtimeContext);
|
|
9209
|
+
agentProcess = {
|
|
9210
|
+
runtime,
|
|
8764
9211
|
driver,
|
|
8765
9212
|
inbox: wakeMessageDeliveredAsInboxUpdate && wakeMessage ? [wakeMessage, ...startingInboxMessages] : startingInboxMessages,
|
|
8766
9213
|
config: runtimeConfig,
|
|
@@ -8807,30 +9254,20 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8807
9254
|
}
|
|
8808
9255
|
if (wakeMessageDeliveredAsInboxUpdate) {
|
|
8809
9256
|
this.recordInboxUpdateProjection(agentId, agentProcess, agentProcess.inbox, "spawn_wake_inbox_update", "wake", prompt);
|
|
8810
|
-
} else if (wakeMessage) {
|
|
9257
|
+
} else if (wakeMessage && !wakeMessageTransient) {
|
|
8811
9258
|
this.consumeVisibleMessages(agentId, { messages: [wakeMessage], source: "spawn_wake_message" });
|
|
8812
9259
|
this.ackInjectedRuntimeProfileMessages(agentId, [wakeMessage], agentProcess.launchId);
|
|
8813
9260
|
}
|
|
8814
|
-
|
|
8815
|
-
proc.stdout?.on("data", (chunk) => {
|
|
8816
|
-
const chunkText = chunk.toString();
|
|
9261
|
+
runtime.on("stdout", (chunkText) => {
|
|
8817
9262
|
const current = this.agents.get(agentId);
|
|
8818
9263
|
if (current) {
|
|
8819
9264
|
current.recentStdout = pushRecentStdout(current.recentStdout, chunkText);
|
|
8820
9265
|
}
|
|
8821
|
-
buffer += chunkText;
|
|
8822
|
-
const lines = buffer.split("\n");
|
|
8823
|
-
buffer = lines.pop() || "";
|
|
8824
|
-
for (const line of lines) {
|
|
8825
|
-
if (!line.trim()) continue;
|
|
8826
|
-
const events = driver.parseLine(line);
|
|
8827
|
-
for (const event of events) {
|
|
8828
|
-
this.handleParsedEvent(agentId, event, driver);
|
|
8829
|
-
}
|
|
8830
|
-
}
|
|
8831
9266
|
});
|
|
8832
|
-
|
|
8833
|
-
|
|
9267
|
+
runtime.on("runtime_event", (event) => {
|
|
9268
|
+
this.handleParsedEvent(agentId, event, driver);
|
|
9269
|
+
});
|
|
9270
|
+
runtime.on("stderr", (text) => {
|
|
8834
9271
|
if (!text) return;
|
|
8835
9272
|
const current = this.agents.get(agentId);
|
|
8836
9273
|
if (driver.id === "codex" && isCodexProviderReconnectLog(text)) {
|
|
@@ -8855,7 +9292,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8855
9292
|
}
|
|
8856
9293
|
logger.error(`[Agent ${agentId} stderr]: ${text}`);
|
|
8857
9294
|
});
|
|
8858
|
-
|
|
9295
|
+
runtime.on("error", (err) => {
|
|
8859
9296
|
const current = this.agents.get(agentId);
|
|
8860
9297
|
if (current) {
|
|
8861
9298
|
current.spawnError = err.message;
|
|
@@ -8870,9 +9307,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8870
9307
|
}, "error");
|
|
8871
9308
|
logger.error(`[Agent ${agentId}] Process error: ${err.message}`);
|
|
8872
9309
|
});
|
|
8873
|
-
|
|
9310
|
+
runtime.on("exit", ({ code, signal }) => {
|
|
8874
9311
|
const current = this.agents.get(agentId);
|
|
8875
|
-
if (current && current.
|
|
9312
|
+
if (current && current.runtime === runtime) {
|
|
8876
9313
|
current.exitCode = code;
|
|
8877
9314
|
current.exitSignal = signal;
|
|
8878
9315
|
}
|
|
@@ -8887,14 +9324,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
8887
9324
|
runtime_trace_active: Boolean(current?.runtimeTraceSpan),
|
|
8888
9325
|
inbox_count: current?.inbox.length ?? 0,
|
|
8889
9326
|
pending_notification_count: current?.notifications.pendingCount ?? 0,
|
|
8890
|
-
...this.
|
|
9327
|
+
...this.runtimeExitTraceAttrs.get(runtime)
|
|
8891
9328
|
});
|
|
8892
9329
|
logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
|
|
8893
9330
|
});
|
|
8894
|
-
|
|
9331
|
+
runtime.on("close", ({ code, signal }) => {
|
|
8895
9332
|
if (this.agents.has(agentId)) {
|
|
8896
9333
|
const ap = this.agents.get(agentId);
|
|
8897
|
-
if (ap.
|
|
9334
|
+
if (ap.runtime !== runtime) return;
|
|
8898
9335
|
ap.notifications.clearTimer();
|
|
8899
9336
|
if (ap.pendingTrajectory?.timer) {
|
|
8900
9337
|
clearTimeout(ap.pendingTrajectory.timer);
|
|
@@ -9035,14 +9472,54 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9035
9472
|
}
|
|
9036
9473
|
}
|
|
9037
9474
|
});
|
|
9475
|
+
const startResult = await runtime.start({ text: prompt, sessionId: effectiveConfig.sessionId || null });
|
|
9476
|
+
if (!startResult.ok) {
|
|
9477
|
+
throw new Error(`Runtime session failed to start: ${startResult.reason}${startResult.error ? ` (${startResult.error})` : ""}`);
|
|
9478
|
+
}
|
|
9479
|
+
this.recordDaemonTrace("daemon.agent.spawn.created", {
|
|
9480
|
+
...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
|
|
9481
|
+
detached: false,
|
|
9482
|
+
new_session: false,
|
|
9483
|
+
process_pid_present: typeof runtime.pid === "number"
|
|
9484
|
+
});
|
|
9038
9485
|
this.sendAgentStatus(agentId, "active", launchId || null);
|
|
9039
9486
|
this.broadcastActivity(agentId, "working", "Starting\u2026");
|
|
9040
9487
|
this.startRuntimeStartupTimeout(agentId, agentProcess);
|
|
9041
9488
|
} catch (err) {
|
|
9042
9489
|
this.agentsStarting.delete(agentId);
|
|
9490
|
+
this.cleanupFailedRuntimeStart(agentId, agentProcess, err);
|
|
9043
9491
|
throw err;
|
|
9044
9492
|
}
|
|
9045
9493
|
}
|
|
9494
|
+
cleanupFailedRuntimeStart(agentId, ap, err) {
|
|
9495
|
+
if (!ap) return;
|
|
9496
|
+
if (this.agents.get(agentId) !== ap) return;
|
|
9497
|
+
ap.notifications.clearTimer();
|
|
9498
|
+
if (ap.pendingTrajectory?.timer) {
|
|
9499
|
+
clearTimeout(ap.pendingTrajectory.timer);
|
|
9500
|
+
ap.pendingTrajectory.timer = null;
|
|
9501
|
+
}
|
|
9502
|
+
if (ap.activityHeartbeat) {
|
|
9503
|
+
clearInterval(ap.activityHeartbeat);
|
|
9504
|
+
ap.activityHeartbeat = null;
|
|
9505
|
+
}
|
|
9506
|
+
if (ap.compactionWatchdog) {
|
|
9507
|
+
clearTimeout(ap.compactionWatchdog);
|
|
9508
|
+
ap.compactionWatchdog = null;
|
|
9509
|
+
}
|
|
9510
|
+
this.clearRuntimeStartupTimeout(ap);
|
|
9511
|
+
this.clearStalledRecoverySigtermWatchdog(ap);
|
|
9512
|
+
this.endRuntimeTrace(ap, "error", {
|
|
9513
|
+
outcome: "runtime-start-failed",
|
|
9514
|
+
failure_detail: err instanceof Error ? err.message : String(err),
|
|
9515
|
+
...runtimeTraceCounterAttrs(ap),
|
|
9516
|
+
...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
|
|
9517
|
+
});
|
|
9518
|
+
cleanupAgentCredentialProxy(agentId, ap.launchId);
|
|
9519
|
+
this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
|
|
9520
|
+
this.agents.delete(agentId);
|
|
9521
|
+
this.idleAgentConfigs.delete(agentId);
|
|
9522
|
+
}
|
|
9046
9523
|
async buildSpawnConfig(agentId, config) {
|
|
9047
9524
|
const baseConfig = config.serverUrl === this.serverUrl ? config : { ...config, serverUrl: this.serverUrl };
|
|
9048
9525
|
const runnerConfig = await this.ensureManagedRunnerCredential(agentId, baseConfig);
|
|
@@ -9211,7 +9688,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9211
9688
|
ap.inbox.push(message);
|
|
9212
9689
|
if (ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
9213
9690
|
ap.notifications.add();
|
|
9214
|
-
if (ap.
|
|
9691
|
+
if (ap.runtime.descriptor.busyDelivery === "gated") {
|
|
9215
9692
|
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
9216
9693
|
reason: "runtime_profile",
|
|
9217
9694
|
kind,
|
|
@@ -9278,36 +9755,36 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9278
9755
|
cleanupAgentCredentialProxy(agentId, ap.launchId);
|
|
9279
9756
|
this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
|
|
9280
9757
|
this.agents.delete(agentId);
|
|
9281
|
-
this.
|
|
9758
|
+
this.runtimeExitTraceAttrs.set(ap.runtime, {
|
|
9282
9759
|
stop_source: silent ? "daemon_internal" : "explicit_request",
|
|
9283
9760
|
stop_wait_requested: wait,
|
|
9284
9761
|
stop_silent: silent
|
|
9285
9762
|
});
|
|
9286
|
-
ap.
|
|
9763
|
+
await ap.runtime.stop({
|
|
9764
|
+
signal: "SIGTERM",
|
|
9765
|
+
forceAfterMs: wait ? 5e3 : void 0,
|
|
9766
|
+
reason: silent ? "daemon_internal" : "explicit_request"
|
|
9767
|
+
});
|
|
9287
9768
|
if (!silent) {
|
|
9288
|
-
this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId);
|
|
9769
|
+
this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId, "stop");
|
|
9289
9770
|
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
9290
9771
|
this.broadcastActivity(agentId, "offline", "Stopped");
|
|
9291
9772
|
logger.info(`[Agent ${agentId}] Stopped by request`);
|
|
9292
9773
|
}
|
|
9293
9774
|
if (wait) {
|
|
9294
9775
|
await new Promise((resolve) => {
|
|
9295
|
-
const
|
|
9776
|
+
const timeoutTimer = setTimeout(() => {
|
|
9296
9777
|
if (!silent) {
|
|
9297
9778
|
logger.warn(`[Agent ${agentId}] Stop timed out; force killing`);
|
|
9298
9779
|
}
|
|
9299
|
-
try {
|
|
9300
|
-
ap.process.kill("SIGKILL");
|
|
9301
|
-
} catch {
|
|
9302
|
-
}
|
|
9303
9780
|
resolve();
|
|
9304
9781
|
}, 5e3);
|
|
9305
|
-
ap.
|
|
9306
|
-
clearTimeout(
|
|
9782
|
+
ap.runtime.on("exit", () => {
|
|
9783
|
+
clearTimeout(timeoutTimer);
|
|
9307
9784
|
resolve();
|
|
9308
9785
|
});
|
|
9309
|
-
if (ap.
|
|
9310
|
-
clearTimeout(
|
|
9786
|
+
if (ap.runtime.closed) {
|
|
9787
|
+
clearTimeout(timeoutTimer);
|
|
9311
9788
|
resolve();
|
|
9312
9789
|
}
|
|
9313
9790
|
});
|
|
@@ -9317,9 +9794,24 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9317
9794
|
if (traceContext.deliveryId) {
|
|
9318
9795
|
this.deliveryTraceContexts.set(message, traceContext);
|
|
9319
9796
|
}
|
|
9797
|
+
if (traceContext.transient) {
|
|
9798
|
+
this.deliveryTraceContexts.set(message, traceContext);
|
|
9799
|
+
}
|
|
9800
|
+
const transientDelivery = this.isTransientDelivery(message);
|
|
9320
9801
|
const ap = this.agents.get(agentId);
|
|
9321
9802
|
if (!ap) {
|
|
9322
9803
|
if (this.agentsStarting.has(agentId) || this.queuedAgentStarts.has(agentId)) {
|
|
9804
|
+
if (transientDelivery) {
|
|
9805
|
+
const queuedStart2 = this.queuedAgentStarts.get(agentId);
|
|
9806
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9807
|
+
outcome: "transient_dropped_during_start",
|
|
9808
|
+
accepted: true,
|
|
9809
|
+
process_present: false,
|
|
9810
|
+
startup_pending: true,
|
|
9811
|
+
launchId: queuedStart2?.launchId
|
|
9812
|
+
}));
|
|
9813
|
+
return true;
|
|
9814
|
+
}
|
|
9323
9815
|
const queuedStart = this.queuedAgentStarts.get(agentId);
|
|
9324
9816
|
const pending = this.startingInboxes.get(agentId) || [];
|
|
9325
9817
|
pending.push(message);
|
|
@@ -9337,7 +9829,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9337
9829
|
const cached = this.idleAgentConfigs.get(agentId);
|
|
9338
9830
|
if (cached) {
|
|
9339
9831
|
const driver = this.driverResolver(cached.config.runtime || "claude");
|
|
9340
|
-
if (this.shouldDeferWakeMessage(agentId, driver, message)) {
|
|
9832
|
+
if (!transientDelivery && this.shouldDeferWakeMessage(agentId, driver, message)) {
|
|
9341
9833
|
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9342
9834
|
outcome: "deferred_wake_message",
|
|
9343
9835
|
accepted: true,
|
|
@@ -9360,7 +9852,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9360
9852
|
session_id_present: Boolean(cached.sessionId),
|
|
9361
9853
|
launchId: cached.launchId || void 0
|
|
9362
9854
|
}));
|
|
9363
|
-
return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
|
|
9855
|
+
return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0, transientDelivery).then(() => true, (err) => {
|
|
9364
9856
|
logger.error(`[Agent ${agentId}] Failed to auto-restart`, err);
|
|
9365
9857
|
if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "idle_auto_restart")) {
|
|
9366
9858
|
return false;
|
|
@@ -9370,6 +9862,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9370
9862
|
});
|
|
9371
9863
|
}
|
|
9372
9864
|
logger.warn(`[Agent ${agentId}] Delivery received but no running process or cached idle config exists`);
|
|
9865
|
+
if (transientDelivery) {
|
|
9866
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9867
|
+
outcome: "transient_dropped_no_process",
|
|
9868
|
+
accepted: true,
|
|
9869
|
+
process_present: false,
|
|
9870
|
+
cached_idle_config_present: false
|
|
9871
|
+
}));
|
|
9872
|
+
return true;
|
|
9873
|
+
}
|
|
9373
9874
|
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9374
9875
|
outcome: "rejected_no_process",
|
|
9375
9876
|
accepted: false,
|
|
@@ -9380,7 +9881,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9380
9881
|
this.broadcastActivity(agentId, "offline", "Process unavailable; restart required");
|
|
9381
9882
|
return false;
|
|
9382
9883
|
}
|
|
9383
|
-
if (this.shouldDeferWakeMessage(agentId, ap.driver, message)) {
|
|
9884
|
+
if (!transientDelivery && this.shouldDeferWakeMessage(agentId, ap.driver, message)) {
|
|
9384
9885
|
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9385
9886
|
outcome: "deferred_wake_message",
|
|
9386
9887
|
accepted: true,
|
|
@@ -9395,6 +9896,19 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9395
9896
|
}
|
|
9396
9897
|
const stickyTerminalFailure = classifyStickyTerminalFailure(ap);
|
|
9397
9898
|
if (stickyTerminalFailure) {
|
|
9899
|
+
if (transientDelivery) {
|
|
9900
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9901
|
+
outcome: "transient_dropped_terminal_runtime_error",
|
|
9902
|
+
accepted: true,
|
|
9903
|
+
process_present: true,
|
|
9904
|
+
runtime: ap.config.runtime,
|
|
9905
|
+
session_id_present: Boolean(ap.sessionId),
|
|
9906
|
+
launchId: ap.launchId || void 0,
|
|
9907
|
+
is_idle: ap.isIdle,
|
|
9908
|
+
inbox_count: ap.inbox.length
|
|
9909
|
+
}));
|
|
9910
|
+
return true;
|
|
9911
|
+
}
|
|
9398
9912
|
ap.inbox.push(message);
|
|
9399
9913
|
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9400
9914
|
outcome: "queued_terminal_runtime_error",
|
|
@@ -9411,6 +9925,30 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9411
9925
|
return true;
|
|
9412
9926
|
}
|
|
9413
9927
|
if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
9928
|
+
if (transientDelivery) {
|
|
9929
|
+
this.commitApmIdleState(agentId, ap, false);
|
|
9930
|
+
this.startRuntimeTrace(agentId, ap, "stdin-idle-delivery", [message]);
|
|
9931
|
+
this.broadcastActivity(agentId, "working", "Message received");
|
|
9932
|
+
const stdinAccepted2 = this.deliverMessagesViaStdin(
|
|
9933
|
+
agentId,
|
|
9934
|
+
ap,
|
|
9935
|
+
[message],
|
|
9936
|
+
"idle",
|
|
9937
|
+
{ transient: true }
|
|
9938
|
+
);
|
|
9939
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9940
|
+
outcome: "stdin_idle_transient_delivery",
|
|
9941
|
+
accepted: true,
|
|
9942
|
+
process_present: true,
|
|
9943
|
+
runtime: ap.config.runtime,
|
|
9944
|
+
session_id_present: true,
|
|
9945
|
+
launchId: ap.launchId || void 0,
|
|
9946
|
+
stdin_delivery_accepted: stdinAccepted2,
|
|
9947
|
+
delivered_messages_count: 1,
|
|
9948
|
+
inbox_count: ap.inbox.length
|
|
9949
|
+
}));
|
|
9950
|
+
return true;
|
|
9951
|
+
}
|
|
9414
9952
|
ap.inbox.push(message);
|
|
9415
9953
|
const nextMessages = [...ap.inbox];
|
|
9416
9954
|
this.commitApmIdleState(agentId, ap, false);
|
|
@@ -9435,6 +9973,19 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9435
9973
|
}));
|
|
9436
9974
|
return true;
|
|
9437
9975
|
}
|
|
9976
|
+
if (transientDelivery) {
|
|
9977
|
+
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
9978
|
+
outcome: "transient_dropped_busy",
|
|
9979
|
+
accepted: true,
|
|
9980
|
+
process_present: true,
|
|
9981
|
+
runtime: ap.config.runtime,
|
|
9982
|
+
session_id_present: Boolean(ap.sessionId),
|
|
9983
|
+
launchId: ap.launchId || void 0,
|
|
9984
|
+
is_idle: ap.isIdle,
|
|
9985
|
+
inbox_count: ap.inbox.length
|
|
9986
|
+
}));
|
|
9987
|
+
return true;
|
|
9988
|
+
}
|
|
9438
9989
|
ap.inbox.push(message);
|
|
9439
9990
|
if (this.recoverStaleProcessForQueuedMessageIfNeeded(agentId, ap)) {
|
|
9440
9991
|
this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
|
|
@@ -9475,7 +10026,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9475
10026
|
if (ap.gatedSteering.compacting) {
|
|
9476
10027
|
ap.notifications.add();
|
|
9477
10028
|
ap.notifications.clearTimer();
|
|
9478
|
-
if (ap.
|
|
10029
|
+
if (ap.runtime.descriptor.busyDelivery === "gated") {
|
|
9479
10030
|
this.recordGatedSteeringEvent(agentId, ap, "buffer", {
|
|
9480
10031
|
reason: "compaction_boundary",
|
|
9481
10032
|
pendingMessages: ap.inbox.length
|
|
@@ -9500,7 +10051,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9500
10051
|
}));
|
|
9501
10052
|
return true;
|
|
9502
10053
|
}
|
|
9503
|
-
if (ap.
|
|
10054
|
+
if (ap.runtime.descriptor.busyDelivery === "gated") {
|
|
9504
10055
|
ap.notifications.add();
|
|
9505
10056
|
if (!ap.notifications.hasTimer) {
|
|
9506
10057
|
this.scheduleStdinNotification(agentId, ap, STDIN_NOTIFICATION_INITIAL_DELAY_MS);
|
|
@@ -9683,7 +10234,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9683
10234
|
traceparent: formatTraceparent(span.context)
|
|
9684
10235
|
};
|
|
9685
10236
|
const ap = this.agents.get(agentId);
|
|
9686
|
-
if (ap && !(ap.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) && !(ap.sessionId && ap.
|
|
10237
|
+
if (ap && !(ap.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) && !(ap.sessionId && ap.runtime.descriptor.busyDelivery === "direct")) {
|
|
9687
10238
|
this.enqueueRuntimeProfileNotification(agentId, ap, message, kind, key);
|
|
9688
10239
|
span.end("ok", {
|
|
9689
10240
|
attrs: {
|
|
@@ -9713,7 +10264,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9713
10264
|
});
|
|
9714
10265
|
return written;
|
|
9715
10266
|
}
|
|
9716
|
-
if (ap?.sessionId && ap.
|
|
10267
|
+
if (ap?.sessionId && ap.runtime.descriptor.busyDelivery === "direct") {
|
|
9717
10268
|
const written = this.deliverMessagesViaStdin(agentId, ap, [message], "busy");
|
|
9718
10269
|
span.end(written ? "ok" : "error", {
|
|
9719
10270
|
attrs: {
|
|
@@ -9818,7 +10369,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9818
10369
|
});
|
|
9819
10370
|
span.end("ok", { attrs: { outcome: "agent_config_ack_sent" } });
|
|
9820
10371
|
}
|
|
9821
|
-
sendRuntimeProfileWireReport(report) {
|
|
10372
|
+
sendRuntimeProfileWireReport(report, source) {
|
|
9822
10373
|
const span = this.tracer.startSpan("daemon.runtime_profile.report.sent", {
|
|
9823
10374
|
surface: "daemon",
|
|
9824
10375
|
kind: "producer",
|
|
@@ -9826,6 +10377,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9826
10377
|
agentId: report.agentId,
|
|
9827
10378
|
launchId: report.launchId || void 0,
|
|
9828
10379
|
runtime: report.facts.runtime,
|
|
10380
|
+
report_source: source,
|
|
9829
10381
|
model_present: Boolean(report.facts.model),
|
|
9830
10382
|
session_ref_present: Boolean(report.facts.sessionRef),
|
|
9831
10383
|
workspace_ref_present: Boolean(report.facts.workspaceRef || report.facts.workspacePathRef)
|
|
@@ -9836,17 +10388,18 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
9836
10388
|
agentId: report.agentId,
|
|
9837
10389
|
facts: report.facts,
|
|
9838
10390
|
launchId: report.launchId || void 0,
|
|
9839
|
-
traceparent: formatTraceparent(span.context)
|
|
10391
|
+
traceparent: formatTraceparent(span.context),
|
|
10392
|
+
source
|
|
9840
10393
|
});
|
|
9841
10394
|
span.end("ok");
|
|
9842
10395
|
}
|
|
9843
|
-
sendRuntimeProfileReportFor(agentId, config, sessionId, launchId) {
|
|
9844
|
-
this.sendRuntimeProfileWireReport(this.buildRuntimeProfileReport(agentId, config, sessionId, launchId));
|
|
10396
|
+
sendRuntimeProfileReportFor(agentId, config, sessionId, launchId, source) {
|
|
10397
|
+
this.sendRuntimeProfileWireReport(this.buildRuntimeProfileReport(agentId, config, sessionId, launchId), source);
|
|
9845
10398
|
}
|
|
9846
|
-
sendRuntimeProfileReport(agentId) {
|
|
10399
|
+
sendRuntimeProfileReport(agentId, source) {
|
|
9847
10400
|
const report = this.getAgentRuntimeProfileReport(agentId);
|
|
9848
10401
|
if (!report) return;
|
|
9849
|
-
this.sendRuntimeProfileWireReport(report);
|
|
10402
|
+
this.sendRuntimeProfileWireReport(report, source);
|
|
9850
10403
|
}
|
|
9851
10404
|
// Machine-level workspace scanning
|
|
9852
10405
|
async scanAllWorkspaces() {
|
|
@@ -10136,23 +10689,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10136
10689
|
clearTimeout(ap.stalledRecoverySigtermTimer);
|
|
10137
10690
|
ap.stalledRecoverySigtermTimer = null;
|
|
10138
10691
|
}
|
|
10139
|
-
|
|
10140
|
-
this.
|
|
10141
|
-
...this.
|
|
10692
|
+
mergeRuntimeExitTraceAttrs(runtime, attrs) {
|
|
10693
|
+
this.runtimeExitTraceAttrs.set(runtime, {
|
|
10694
|
+
...this.runtimeExitTraceAttrs.get(runtime) ?? {},
|
|
10142
10695
|
...attrs
|
|
10143
10696
|
});
|
|
10144
10697
|
}
|
|
10145
10698
|
startStalledRecoverySigtermWatchdog(agentId, ap, runtimeLabel, queuedMessagesAtSignal, staleForMs) {
|
|
10146
10699
|
this.clearStalledRecoverySigtermWatchdog(ap);
|
|
10147
10700
|
const timeoutMs = stalledRecoverySigtermTimeoutMs();
|
|
10148
|
-
const
|
|
10701
|
+
const runtimeAtSignal = ap.runtime;
|
|
10149
10702
|
ap.stalledRecoverySigtermTimer = setTimeout(() => {
|
|
10150
10703
|
ap.stalledRecoverySigtermTimer = null;
|
|
10151
10704
|
const current = this.agents.get(agentId);
|
|
10152
|
-
if (!current || current !== ap || current.
|
|
10705
|
+
if (!current || current !== ap || current.runtime !== runtimeAtSignal || current.expectedTerminationReason !== "stalled_recovery") {
|
|
10153
10706
|
return;
|
|
10154
10707
|
}
|
|
10155
|
-
this.
|
|
10708
|
+
this.mergeRuntimeExitTraceAttrs(runtimeAtSignal, {
|
|
10156
10709
|
stalled_recovery_sigterm_timeout: true,
|
|
10157
10710
|
stalled_recovery_sigterm_timeout_ms: timeoutMs
|
|
10158
10711
|
});
|
|
@@ -10166,7 +10719,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10166
10719
|
queued_messages_at_signal: queuedMessagesAtSignal,
|
|
10167
10720
|
stale_age_ms_at_signal: staleForMs,
|
|
10168
10721
|
timeout_ms: timeoutMs,
|
|
10169
|
-
process_pid_present: typeof
|
|
10722
|
+
process_pid_present: typeof runtimeAtSignal.pid === "number",
|
|
10170
10723
|
session_id_present: Boolean(current.sessionId),
|
|
10171
10724
|
supports_stdin_notification: current.driver.supportsStdinNotification,
|
|
10172
10725
|
busy_delivery_mode: current.driver.busyDeliveryMode
|
|
@@ -10175,7 +10728,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10175
10728
|
`[Agent ${agentId}] Stalled ${runtimeLabel} runtime did not exit after SIGTERM within ${timeoutMs}ms; force killing`
|
|
10176
10729
|
);
|
|
10177
10730
|
try {
|
|
10178
|
-
|
|
10731
|
+
void runtimeAtSignal.stop({ signal: "SIGKILL", reason: "stalled_recovery_sigterm_timeout" });
|
|
10179
10732
|
} catch (err) {
|
|
10180
10733
|
const reason = err instanceof Error ? err.message : String(err);
|
|
10181
10734
|
this.recordDaemonTrace("daemon.agent.stalled_recovery.sigkill_failed", {
|
|
@@ -10361,7 +10914,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10361
10914
|
ap.runtimeProgress.noteRuntimeEvent(eventKind);
|
|
10362
10915
|
}
|
|
10363
10916
|
recordGatedSteeringEvent(agentId, ap, event, attrs = {}) {
|
|
10364
|
-
if (ap.
|
|
10917
|
+
if (ap.runtime.descriptor.busyDelivery !== "gated") return;
|
|
10365
10918
|
const reduction = reduceApmGatedRecentEvent(ap.gatedSteering, { event });
|
|
10366
10919
|
this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState);
|
|
10367
10920
|
this.recordRuntimeTraceEvent(agentId, ap, `runtime.gated_steering.${event}`, {
|
|
@@ -10396,7 +10949,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10396
10949
|
}
|
|
10397
10950
|
notifyGatedSteeringBoundary(agentId, ap, reason) {
|
|
10398
10951
|
const readiness = reduceApmGatedFlushReadiness(ap.gatedSteering, {
|
|
10399
|
-
isGated: ap.
|
|
10952
|
+
isGated: ap.runtime.descriptor.busyDelivery === "gated",
|
|
10400
10953
|
hasSession: Boolean(ap.sessionId),
|
|
10401
10954
|
inboxLength: ap.inbox.length,
|
|
10402
10955
|
reason
|
|
@@ -10446,7 +10999,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10446
10999
|
});
|
|
10447
11000
|
return true;
|
|
10448
11001
|
}
|
|
10449
|
-
if (ap.
|
|
11002
|
+
if (ap.runtime.descriptor.busyDelivery === "gated") {
|
|
10450
11003
|
const flushReduction = reduceApmGatedFlush(ap.gatedSteering, { reason: effect.reason });
|
|
10451
11004
|
this.commitGatedSteeringDecisionState(agentId, ap, flushReduction.nextState);
|
|
10452
11005
|
this.recordGatedSteeringEvent(agentId, ap, "flush", {
|
|
@@ -10548,12 +11101,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10548
11101
|
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
10549
11102
|
this.idleAgentConfigs.delete(agentId);
|
|
10550
11103
|
try {
|
|
10551
|
-
this.
|
|
11104
|
+
this.runtimeExitTraceAttrs.set(ap.runtime, {
|
|
10552
11105
|
stop_source: "startup_timeout",
|
|
10553
11106
|
expectedTerminationReason: "startup_timeout",
|
|
10554
11107
|
timeout_ms: timeoutMs
|
|
10555
11108
|
});
|
|
10556
|
-
ap.
|
|
11109
|
+
void ap.runtime.stop({ signal: "SIGTERM", reason: "startup_timeout" });
|
|
10557
11110
|
} catch (err) {
|
|
10558
11111
|
const reason = err instanceof Error ? err.message : String(err);
|
|
10559
11112
|
logger.warn(`[Agent ${agentId}] Failed to terminate startup-timed-out ${ap.driver.id} process: ${reason}`);
|
|
@@ -10639,13 +11192,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10639
11192
|
);
|
|
10640
11193
|
this.broadcastActivity(agentId, "working", `Restarting stalled ${runtimeLabel} runtime for queued message`);
|
|
10641
11194
|
try {
|
|
10642
|
-
this.
|
|
11195
|
+
this.runtimeExitTraceAttrs.set(ap.runtime, {
|
|
10643
11196
|
stop_source: "stalled_recovery",
|
|
10644
11197
|
expectedTerminationReason: "stalled_recovery",
|
|
10645
11198
|
queued_messages_count: ap.inbox.length
|
|
10646
11199
|
});
|
|
10647
11200
|
this.startStalledRecoverySigtermWatchdog(agentId, ap, runtimeLabel, ap.inbox.length, staleForMs);
|
|
10648
|
-
ap.
|
|
11201
|
+
void ap.runtime.stop({ signal: "SIGTERM", reason: "stalled_recovery" });
|
|
10649
11202
|
} catch (err) {
|
|
10650
11203
|
this.clearStalledRecoverySigtermWatchdog(ap);
|
|
10651
11204
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -10695,7 +11248,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10695
11248
|
case "session_init":
|
|
10696
11249
|
if (ap) ap.sessionId = event.sessionId;
|
|
10697
11250
|
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId, launchId: ap?.launchId || void 0 });
|
|
10698
|
-
this.sendRuntimeProfileReport(agentId);
|
|
11251
|
+
this.sendRuntimeProfileReport(agentId, "session_init");
|
|
10699
11252
|
break;
|
|
10700
11253
|
case "thinking": {
|
|
10701
11254
|
this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
|
|
@@ -10821,11 +11374,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10821
11374
|
if (ap.driver.terminateProcessOnTurnEnd) {
|
|
10822
11375
|
logger.info(`[Agent ${agentId}] Turn completed; terminating ${ap.driver.id} process`);
|
|
10823
11376
|
try {
|
|
10824
|
-
this.
|
|
11377
|
+
this.runtimeExitTraceAttrs.set(ap.runtime, {
|
|
10825
11378
|
stop_source: "turn_end",
|
|
10826
11379
|
expectedTerminationReason: "turn_end"
|
|
10827
11380
|
});
|
|
10828
|
-
ap.
|
|
11381
|
+
void ap.runtime.stop({ signal: "SIGTERM", reason: "turn_end" });
|
|
10829
11382
|
} catch (err) {
|
|
10830
11383
|
const reason = err instanceof Error ? err.message : String(err);
|
|
10831
11384
|
logger.warn(`[Agent ${agentId}] Failed to terminate ${ap.driver.id} after turn_end: ${reason}`);
|
|
@@ -10834,7 +11387,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10834
11387
|
}
|
|
10835
11388
|
if (event.sessionId) {
|
|
10836
11389
|
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId, launchId: ap?.launchId || void 0 });
|
|
10837
|
-
this.sendRuntimeProfileReport(agentId);
|
|
11390
|
+
this.sendRuntimeProfileReport(agentId, "turn_end");
|
|
10838
11391
|
}
|
|
10839
11392
|
break;
|
|
10840
11393
|
case "error": {
|
|
@@ -10847,7 +11400,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10847
11400
|
if (runtimeErrorDiagnostics.spanAttrs.runtime_error_action_required === true) {
|
|
10848
11401
|
visibleErrorMessage = formatRuntimeLoginRequiredMessage(ap.driver.id);
|
|
10849
11402
|
}
|
|
10850
|
-
const shouldDisableToolBoundaryFlush = ap.
|
|
11403
|
+
const shouldDisableToolBoundaryFlush = ap.runtime.descriptor.busyDelivery === "gated" && this.isThinkingBlockMutationError(event.message);
|
|
10851
11404
|
const terminalFailure = classifyTerminalFailure(ap);
|
|
10852
11405
|
const reduction = reduceApmGatedError(ap.gatedSteering, {
|
|
10853
11406
|
disableToolBoundaryFlush: shouldDisableToolBoundaryFlush,
|
|
@@ -10878,11 +11431,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10878
11431
|
if (terminalFailure.actionRequired) {
|
|
10879
11432
|
logger.warn(`[Agent ${agentId}] ${ap.driver.id} auth requires user action; terminating runtime process`);
|
|
10880
11433
|
try {
|
|
10881
|
-
this.
|
|
11434
|
+
this.runtimeExitTraceAttrs.set(ap.runtime, {
|
|
10882
11435
|
stop_source: "runtime_auth_error",
|
|
10883
11436
|
runtime_error_class: "AuthError"
|
|
10884
11437
|
});
|
|
10885
|
-
ap.
|
|
11438
|
+
void ap.runtime.stop({ signal: "SIGTERM", reason: "runtime_auth_error" });
|
|
10886
11439
|
} catch (err) {
|
|
10887
11440
|
const reason = err instanceof Error ? err.message : String(err);
|
|
10888
11441
|
logger.warn(`[Agent ${agentId}] Failed to terminate ${ap.driver.id} after auth error: ${reason}`);
|
|
@@ -10907,8 +11460,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10907
11460
|
recordRuntimeTelemetry(agentId, ap, event) {
|
|
10908
11461
|
const sessionId = ap.driver.currentSessionId ?? event.sessionId;
|
|
10909
11462
|
const payloadAttrs = sanitizeRuntimeTelemetryPayloadAttrs(event.attrs);
|
|
11463
|
+
const versionAttrs = this.runtimeTelemetryVersionAttrs();
|
|
10910
11464
|
const telemetryAttrs = {
|
|
10911
11465
|
...payloadAttrs,
|
|
11466
|
+
...versionAttrs,
|
|
10912
11467
|
...event.source ? { source: event.source } : {},
|
|
10913
11468
|
...event.usageKind ? { usageKind: event.usageKind } : {},
|
|
10914
11469
|
...sessionId ? { sessionId } : {},
|
|
@@ -10926,6 +11481,24 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
|
|
|
10926
11481
|
ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, telemetryAttrs);
|
|
10927
11482
|
this.recordDaemonTrace(`daemon.runtime.telemetry.${event.name}`, attrs);
|
|
10928
11483
|
}
|
|
11484
|
+
runtimeTelemetryVersionAttrs() {
|
|
11485
|
+
return {
|
|
11486
|
+
...this.daemonVersion ? {
|
|
11487
|
+
daemonVersion: this.daemonVersion,
|
|
11488
|
+
daemon_version: this.daemonVersion,
|
|
11489
|
+
daemon_version_present: true
|
|
11490
|
+
} : {
|
|
11491
|
+
daemon_version_present: false
|
|
11492
|
+
},
|
|
11493
|
+
...this.computerVersion ? {
|
|
11494
|
+
computerVersion: this.computerVersion,
|
|
11495
|
+
computer_version: this.computerVersion,
|
|
11496
|
+
computer_version_present: true
|
|
11497
|
+
} : {
|
|
11498
|
+
computer_version_present: false
|
|
11499
|
+
}
|
|
11500
|
+
};
|
|
11501
|
+
}
|
|
10929
11502
|
sendAgentStatus(agentId, status, launchId) {
|
|
10930
11503
|
this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
|
|
10931
11504
|
}
|
|
@@ -11009,9 +11582,11 @@ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
|
|
|
11009
11582
|
...projectionAttrs
|
|
11010
11583
|
});
|
|
11011
11584
|
logger.info(`[Agent ${agentId}] Sending stdin inbox update: ${inboxRows.length} changed target(s), ${inboxCount} pending message(s)`);
|
|
11012
|
-
const
|
|
11013
|
-
|
|
11014
|
-
|
|
11585
|
+
const sendResult = requireImmediateRuntimeSendResult(
|
|
11586
|
+
ap.runtime.send({ mode: "busy", text: notification, sessionId: ap.sessionId }),
|
|
11587
|
+
"busy inbox notification"
|
|
11588
|
+
);
|
|
11589
|
+
if (sendResult.ok) {
|
|
11015
11590
|
this.recordDaemonTrace("daemon.agent.inbox_update.pushed", {
|
|
11016
11591
|
agentId,
|
|
11017
11592
|
runtime: ap.config.runtime,
|
|
@@ -11036,14 +11611,17 @@ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
|
|
|
11036
11611
|
return true;
|
|
11037
11612
|
} else {
|
|
11038
11613
|
ap.notifications.add(count);
|
|
11039
|
-
const retryScheduled = ap.
|
|
11614
|
+
const retryScheduled = ap.runtime.descriptor.busyDelivery === "direct" ? this.scheduleStdinNotification(agentId, ap, this.stdinNotificationRetryMs) : false;
|
|
11615
|
+
const outcome = runtimeSendFailureOutcome(sendResult);
|
|
11040
11616
|
this.recordDaemonTrace("daemon.agent.stdin_notification", {
|
|
11041
11617
|
agentId,
|
|
11042
11618
|
runtime: ap.config.runtime,
|
|
11043
11619
|
model: ap.config.model,
|
|
11044
11620
|
launchId: ap.launchId || void 0,
|
|
11045
|
-
outcome
|
|
11621
|
+
outcome,
|
|
11046
11622
|
mode: "busy",
|
|
11623
|
+
failure_reason: sendResult.reason,
|
|
11624
|
+
failure_error: sendResult.error,
|
|
11047
11625
|
pending_notification_count: count,
|
|
11048
11626
|
retry_scheduled: retryScheduled,
|
|
11049
11627
|
notification_timer_present: ap.notifications.hasTimer,
|
|
@@ -11094,8 +11672,11 @@ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
|
|
|
11094
11672
|
nativeStandingPrompt: Boolean(ap.driver.supportsNativeStandingPrompt)
|
|
11095
11673
|
});
|
|
11096
11674
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.input.prepared", inputTraceAttrs);
|
|
11097
|
-
const
|
|
11098
|
-
|
|
11675
|
+
const sendResult = requireImmediateRuntimeSendResult(
|
|
11676
|
+
ap.runtime.send({ mode, text: prompt, sessionId: ap.sessionId }),
|
|
11677
|
+
`${mode} inbox update`
|
|
11678
|
+
);
|
|
11679
|
+
if (!sendResult.ok) {
|
|
11099
11680
|
if (mode === "idle") {
|
|
11100
11681
|
this.commitApmIdleState(agentId, ap, true);
|
|
11101
11682
|
}
|
|
@@ -11117,7 +11698,9 @@ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
|
|
|
11117
11698
|
...this.messagesTraceAttrs(messages),
|
|
11118
11699
|
...inputTraceAttrs,
|
|
11119
11700
|
...projectionAttrs,
|
|
11120
|
-
outcome:
|
|
11701
|
+
outcome: runtimeSendFailureOutcome(sendResult),
|
|
11702
|
+
failure_reason: sendResult.reason,
|
|
11703
|
+
failure_error: sendResult.error,
|
|
11121
11704
|
requeued_messages_count: 0,
|
|
11122
11705
|
cursors_advanced: "none"
|
|
11123
11706
|
}, "error");
|
|
@@ -11130,7 +11713,6 @@ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
|
|
|
11130
11713
|
if (this.containsOrdinaryInboxMessage(messages)) {
|
|
11131
11714
|
ap.lastRuntimeError = null;
|
|
11132
11715
|
}
|
|
11133
|
-
ap.process.stdin?.write(encoded + "\n");
|
|
11134
11716
|
this.recordDaemonTrace("daemon.agent.stdin_delivery", {
|
|
11135
11717
|
agentId,
|
|
11136
11718
|
launchId: ap.launchId || void 0,
|
|
@@ -11153,7 +11735,7 @@ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
|
|
|
11153
11735
|
return true;
|
|
11154
11736
|
}
|
|
11155
11737
|
/** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
|
|
11156
|
-
deliverMessagesViaStdin(agentId, ap, messages, mode) {
|
|
11738
|
+
deliverMessagesViaStdin(agentId, ap, messages, mode, options = {}) {
|
|
11157
11739
|
if (messages.length === 0) return true;
|
|
11158
11740
|
const runtimeProfileMigrationMessages = messages.filter((message) => runtimeProfileNotificationFromMessage(message)?.kind === "migration");
|
|
11159
11741
|
if (runtimeProfileMigrationMessages.length > 0) {
|
|
@@ -11191,8 +11773,10 @@ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
|
|
|
11191
11773
|
pending_notification_count: ap.notifications.pendingCount,
|
|
11192
11774
|
busy_delivery_mode: ap.driver.busyDeliveryMode,
|
|
11193
11775
|
supports_stdin_notification: ap.driver.supportsStdinNotification,
|
|
11776
|
+
transient_delivery: options.transient === true,
|
|
11194
11777
|
...this.messagesTraceAttrs(messages)
|
|
11195
11778
|
};
|
|
11779
|
+
const traceSource = options.transient ? `stdin_${mode}_transient_delivery` : `stdin_${mode}_delivery`;
|
|
11196
11780
|
const prompt = formatRuntimeProfileControlPrompt(messages) ?? (messages.length === 1 ? `New message received:
|
|
11197
11781
|
|
|
11198
11782
|
${formatIncomingMessage(messages[0], ap.driver)}
|
|
@@ -11205,16 +11789,21 @@ ${messages.map((message) => formatIncomingMessage(message, ap.driver)).join("\n"
|
|
|
11205
11789
|
Respond as appropriate. Complete all your work before stopping.
|
|
11206
11790
|
${RESPONSE_TARGET_HINT}`);
|
|
11207
11791
|
const inputTraceAttrs = buildRuntimeInputTraceAttrs({
|
|
11208
|
-
source:
|
|
11792
|
+
source: traceSource,
|
|
11209
11793
|
prompt,
|
|
11210
11794
|
messages,
|
|
11211
11795
|
sessionIdPresent: Boolean(ap.sessionId),
|
|
11212
11796
|
nativeStandingPrompt: Boolean(ap.driver.supportsNativeStandingPrompt)
|
|
11213
11797
|
});
|
|
11214
11798
|
this.recordRuntimeTraceEvent(agentId, ap, "runtime.input.prepared", inputTraceAttrs);
|
|
11215
|
-
const
|
|
11216
|
-
|
|
11217
|
-
|
|
11799
|
+
const sendResult = requireImmediateRuntimeSendResult(
|
|
11800
|
+
ap.runtime.send({ mode, text: prompt, sessionId: ap.sessionId }),
|
|
11801
|
+
`${mode} message delivery`
|
|
11802
|
+
);
|
|
11803
|
+
if (!sendResult.ok) {
|
|
11804
|
+
if (!options.transient) {
|
|
11805
|
+
ap.inbox.unshift(...messages);
|
|
11806
|
+
}
|
|
11218
11807
|
if (mode === "idle") {
|
|
11219
11808
|
this.commitApmIdleState(agentId, ap, true);
|
|
11220
11809
|
}
|
|
@@ -11224,12 +11813,16 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
11224
11813
|
this.recordDaemonTrace("daemon.agent.stdin_delivery", {
|
|
11225
11814
|
...traceAttrs,
|
|
11226
11815
|
...inputTraceAttrs,
|
|
11227
|
-
outcome:
|
|
11228
|
-
|
|
11816
|
+
outcome: runtimeSendFailureOutcome(sendResult),
|
|
11817
|
+
failure_reason: sendResult.reason,
|
|
11818
|
+
failure_error: sendResult.error,
|
|
11819
|
+
requeued_messages_count: options.transient ? 0 : messages.length
|
|
11229
11820
|
}, "error");
|
|
11230
11821
|
return false;
|
|
11231
11822
|
}
|
|
11232
|
-
|
|
11823
|
+
if (!options.transient) {
|
|
11824
|
+
this.consumeVisibleMessages(agentId, { messages, source: traceSource });
|
|
11825
|
+
}
|
|
11233
11826
|
const senders = [...new Set(messages.map((message) => `@${message.sender_name}`))].join(", ");
|
|
11234
11827
|
logger.info(
|
|
11235
11828
|
`[Agent ${agentId}] Delivering ${mode} ${messages.length === 1 ? "message" : `${messages.length} messages`} via stdin from ${senders}`
|
|
@@ -11237,7 +11830,6 @@ ${RESPONSE_TARGET_HINT}`);
|
|
|
11237
11830
|
if (this.containsOrdinaryInboxMessage(messages)) {
|
|
11238
11831
|
ap.lastRuntimeError = null;
|
|
11239
11832
|
}
|
|
11240
|
-
ap.process.stdin?.write(encoded + "\n");
|
|
11241
11833
|
this.ackInjectedRuntimeProfileMessages(agentId, messages, ap.launchId);
|
|
11242
11834
|
this.recordDaemonTrace("daemon.agent.stdin_delivery", {
|
|
11243
11835
|
...traceAttrs,
|
|
@@ -12205,7 +12797,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
|
|
|
12205
12797
|
var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
|
|
12206
12798
|
var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
|
|
12207
12799
|
var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
|
|
12208
|
-
var DAEMON_CLI_USAGE =
|
|
12800
|
+
var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
|
|
12209
12801
|
var RunnerCredentialMintError2 = class extends Error {
|
|
12210
12802
|
code;
|
|
12211
12803
|
retryable;
|
|
@@ -12241,9 +12833,9 @@ function runnerCredentialErrorDetail2(error) {
|
|
|
12241
12833
|
async function waitForRunnerCredentialRetry2() {
|
|
12242
12834
|
await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
|
|
12243
12835
|
}
|
|
12244
|
-
function parseDaemonCliArgs(args) {
|
|
12836
|
+
function parseDaemonCliArgs(args, env = {}) {
|
|
12245
12837
|
let serverUrl = "";
|
|
12246
|
-
let apiKey = "";
|
|
12838
|
+
let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
|
|
12247
12839
|
for (let i = 0; i < args.length; i++) {
|
|
12248
12840
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
12249
12841
|
if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
|
|
@@ -12444,7 +13036,9 @@ var DaemonCore = class {
|
|
|
12444
13036
|
serverUrl: options.serverUrl,
|
|
12445
13037
|
defaultAgentEnvVarsProvider: options.defaultAgentEnvVarsProvider,
|
|
12446
13038
|
slockCliPath: this.slockCliPath,
|
|
12447
|
-
tracer: this.tracer
|
|
13039
|
+
tracer: this.tracer,
|
|
13040
|
+
daemonVersion: this.daemonVersion,
|
|
13041
|
+
computerVersion: this.computerVersion
|
|
12448
13042
|
};
|
|
12449
13043
|
this.agentManager = options.agentManagerFactory ? options.agentManagerFactory(this.chatBridgePath, (msg) => connection.send(msg), options.apiKey, agentManagerOptions) : new AgentProcessManager(this.chatBridgePath, (msg) => connection.send(msg), options.apiKey, agentManagerOptions);
|
|
12450
13044
|
const connectionFactory = options.connectionFactory ?? ((connOptions) => new DaemonConnection(connOptions));
|
|
@@ -12695,7 +13289,8 @@ var DaemonCore = class {
|
|
|
12695
13289
|
msg.wakeMessage,
|
|
12696
13290
|
msg.unreadSummary,
|
|
12697
13291
|
msg.resumePrompt,
|
|
12698
|
-
msg.launchId
|
|
13292
|
+
msg.launchId,
|
|
13293
|
+
msg.wakeMessageTransient ?? false
|
|
12699
13294
|
);
|
|
12700
13295
|
}
|
|
12701
13296
|
handleMessage(msg) {
|
|
@@ -12746,7 +13341,10 @@ var DaemonCore = class {
|
|
|
12746
13341
|
logger.info(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
|
|
12747
13342
|
try {
|
|
12748
13343
|
span.addEvent("daemon.receive", { seq: msg.seq, deliveryId: msg.deliveryId });
|
|
12749
|
-
const acceptedOrPromise = this.agentManager.deliverMessage(msg.agentId, msg.message, {
|
|
13344
|
+
const acceptedOrPromise = this.agentManager.deliverMessage(msg.agentId, msg.message, {
|
|
13345
|
+
deliveryId: msg.deliveryId,
|
|
13346
|
+
transient: msg.transient ?? false
|
|
13347
|
+
});
|
|
12750
13348
|
Promise.resolve(acceptedOrPromise).then((accepted) => {
|
|
12751
13349
|
span.addEvent("daemon.deliver_to_agent_manager", { accepted });
|
|
12752
13350
|
if (!accepted) {
|
|
@@ -12976,6 +13574,7 @@ var DaemonCore = class {
|
|
|
12976
13574
|
agentId: report.agentId,
|
|
12977
13575
|
launchId: report.launchId || void 0,
|
|
12978
13576
|
runtime: report.facts.runtime,
|
|
13577
|
+
report_source: "connect",
|
|
12979
13578
|
model_present: Boolean(report.facts.model),
|
|
12980
13579
|
session_ref_present: Boolean(report.facts.sessionRef),
|
|
12981
13580
|
workspace_ref_present: Boolean(report.facts.workspaceRef || report.facts.workspacePathRef)
|
|
@@ -12986,7 +13585,8 @@ var DaemonCore = class {
|
|
|
12986
13585
|
agentId: report.agentId,
|
|
12987
13586
|
facts: report.facts,
|
|
12988
13587
|
launchId: report.launchId || void 0,
|
|
12989
|
-
traceparent: formatTraceparent(span.context)
|
|
13588
|
+
traceparent: formatTraceparent(span.context),
|
|
13589
|
+
source: "connect"
|
|
12990
13590
|
});
|
|
12991
13591
|
span.end("ok");
|
|
12992
13592
|
}
|
|
@@ -13010,6 +13610,8 @@ var DaemonCore = class {
|
|
|
13010
13610
|
};
|
|
13011
13611
|
|
|
13012
13612
|
export {
|
|
13613
|
+
DAEMON_API_KEY_ENV,
|
|
13614
|
+
scrubDaemonAuthEnv,
|
|
13013
13615
|
resolveWorkspaceDirectoryPath,
|
|
13014
13616
|
scanWorkspaceDirectories,
|
|
13015
13617
|
deleteWorkspaceDirectory,
|