@slock-ai/daemon 0.41.1-alpha.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat-bridge.js +25 -2
- package/dist/{chunk-JAB3HALZ.js → chunk-37O7EHYE.js} +910 -159
- package/dist/cli/index.js +542 -4
- package/dist/core.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
} from "./chunk-JG7ONJZ6.js";
|
|
5
5
|
|
|
6
6
|
// src/core.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
7
|
+
import path13 from "path";
|
|
8
|
+
import os6 from "os";
|
|
9
9
|
import { createRequire } from "module";
|
|
10
10
|
import { accessSync } from "fs";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
@@ -545,8 +545,57 @@ var RUNTIMES = [
|
|
|
545
545
|
{ id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
|
|
546
546
|
{ id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
|
|
547
547
|
{ id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
|
|
548
|
-
{ id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: true }
|
|
548
|
+
{ id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: true },
|
|
549
|
+
{ id: "opencode", displayName: "OpenCode", binary: "opencode", supported: true }
|
|
549
550
|
];
|
|
551
|
+
var RUNTIME_MODELS = {
|
|
552
|
+
claude: [
|
|
553
|
+
{ id: "sonnet", label: "Sonnet" },
|
|
554
|
+
{ id: "opus", label: "Opus" },
|
|
555
|
+
{ id: "haiku", label: "Haiku" }
|
|
556
|
+
],
|
|
557
|
+
codex: [
|
|
558
|
+
{ id: "gpt-5.5", label: "GPT-5.5" },
|
|
559
|
+
{ id: "gpt-5.4", label: "GPT-5.4" },
|
|
560
|
+
{ id: "gpt-5.3-codex", label: "GPT-5.3 Codex" },
|
|
561
|
+
{ id: "gpt-5.3-codex-spark", label: "GPT-5.3 Codex Spark" },
|
|
562
|
+
{ id: "gpt-5.2-codex", label: "GPT-5.2 Codex" },
|
|
563
|
+
{ id: "gpt-5.2", label: "GPT-5.2" },
|
|
564
|
+
{ id: "gpt-5.1-codex-max", label: "GPT-5.1 Codex Max" },
|
|
565
|
+
{ id: "gpt-5.1-codex", label: "GPT-5.1 Codex" },
|
|
566
|
+
{ id: "gpt-5-codex", label: "GPT-5 Codex" },
|
|
567
|
+
{ id: "gpt-5", label: "GPT-5" }
|
|
568
|
+
],
|
|
569
|
+
copilot: [
|
|
570
|
+
{ id: "gpt-5.4", label: "GPT-5.4" },
|
|
571
|
+
{ id: "gpt-5.2", label: "GPT-5.2" },
|
|
572
|
+
{ id: "claude-4-sonnet", label: "Claude 4 Sonnet" },
|
|
573
|
+
{ id: "claude-4.5-sonnet", label: "Claude 4.5 Sonnet" }
|
|
574
|
+
],
|
|
575
|
+
cursor: [
|
|
576
|
+
{ id: "composer-2-fast", label: "Composer 2 Fast" },
|
|
577
|
+
{ id: "composer-2", label: "Composer 2" },
|
|
578
|
+
{ id: "auto", label: "Auto" }
|
|
579
|
+
],
|
|
580
|
+
gemini: [
|
|
581
|
+
{ id: "gemini-3.1-pro-preview", label: "Gemini 3.1 Pro (Preview)" },
|
|
582
|
+
{ id: "gemini-3-flash-preview", label: "Gemini 3 Flash (Preview)" },
|
|
583
|
+
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro" },
|
|
584
|
+
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash" }
|
|
585
|
+
],
|
|
586
|
+
opencode: [
|
|
587
|
+
{ id: "opencode/gpt-5-nano", label: "GPT-5 Nano (OpenCode)" },
|
|
588
|
+
{ id: "opencode/big-pickle", label: "Big Pickle (OpenCode)" },
|
|
589
|
+
{ id: "opencode/hy3-preview-free", label: "HY3 Preview Free (OpenCode)" },
|
|
590
|
+
{ id: "opencode/minimax-m2.5-free", label: "MiniMax M2.5 Free (OpenCode)" },
|
|
591
|
+
{ id: "opencode/nemotron-3-super-free", label: "Nemotron 3 Super Free (OpenCode)" }
|
|
592
|
+
],
|
|
593
|
+
// Kimi CLI resolves model keys from each user's local config, so the safest
|
|
594
|
+
// built-in option is to defer to whatever default model the CLI already uses.
|
|
595
|
+
kimi: [
|
|
596
|
+
{ id: "default", label: "Configured Default" }
|
|
597
|
+
]
|
|
598
|
+
};
|
|
550
599
|
var PLAN_CONFIG = {
|
|
551
600
|
free: {
|
|
552
601
|
displayName: "Hobby",
|
|
@@ -582,9 +631,10 @@ var DISPLAY_PLAN_CONFIG = {
|
|
|
582
631
|
};
|
|
583
632
|
|
|
584
633
|
// src/agentProcessManager.ts
|
|
634
|
+
import { mkdirSync as mkdirSync4, readdirSync, statSync, writeFileSync as writeFileSync7 } from "fs";
|
|
585
635
|
import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
|
|
586
|
-
import
|
|
587
|
-
import
|
|
636
|
+
import path11 from "path";
|
|
637
|
+
import os4 from "os";
|
|
588
638
|
|
|
589
639
|
// src/drivers/claude.ts
|
|
590
640
|
import { spawn } from "child_process";
|
|
@@ -599,6 +649,27 @@ import path from "path";
|
|
|
599
649
|
function toolRef(prefix, name) {
|
|
600
650
|
return `${prefix}${name}`;
|
|
601
651
|
}
|
|
652
|
+
function runtimeContextLines(config) {
|
|
653
|
+
const ctx = config.runtimeContext;
|
|
654
|
+
if (!ctx) return [];
|
|
655
|
+
const lines = [
|
|
656
|
+
"## Current Runtime Context",
|
|
657
|
+
"",
|
|
658
|
+
"This is authoritative context injected by Slock. Do not infer computer identity from hostname or cwd when this section is present.",
|
|
659
|
+
""
|
|
660
|
+
];
|
|
661
|
+
if (ctx.agentId) lines.push(`- Agent ID: ${ctx.agentId}`);
|
|
662
|
+
if (ctx.serverId) lines.push(`- Server ID: ${ctx.serverId}`);
|
|
663
|
+
if (ctx.machineName || ctx.machineId) {
|
|
664
|
+
const label = ctx.machineName && ctx.machineId ? `${ctx.machineName} (${ctx.machineId})` : ctx.machineName || ctx.machineId;
|
|
665
|
+
lines.push(`- Computer: ${label}`);
|
|
666
|
+
}
|
|
667
|
+
if (ctx.machineHostname) lines.push(`- Hostname: ${ctx.machineHostname}`);
|
|
668
|
+
if (ctx.machineOs) lines.push(`- OS: ${ctx.machineOs}`);
|
|
669
|
+
if (ctx.daemonVersion) lines.push(`- Daemon: v${ctx.daemonVersion}`);
|
|
670
|
+
if (ctx.workspacePath) lines.push(`- Workspace: ${ctx.workspacePath}`);
|
|
671
|
+
return lines.length > 4 ? lines : [];
|
|
672
|
+
}
|
|
602
673
|
function buildPrompt(config, variant, opts) {
|
|
603
674
|
const isCli = variant === "cli";
|
|
604
675
|
const t = (name) => toolRef(opts.toolPrefix, name);
|
|
@@ -609,6 +680,9 @@ function buildPrompt(config, variant, opts) {
|
|
|
609
680
|
const taskCreateCmd = isCli ? "`slock task create`" : `\`${t("create_tasks")}\``;
|
|
610
681
|
const taskUpdateCmd = isCli ? "`slock task update`" : `\`${t("update_task_status")}\``;
|
|
611
682
|
const serverInfoCmd = isCli ? "`slock server info`" : `\`${t("list_server")}\``;
|
|
683
|
+
const scheduleReminderCmd = isCli ? "`slock reminder schedule`" : `\`${t("schedule_reminder")}\``;
|
|
684
|
+
const listRemindersCmd = isCli ? "`slock reminder list`" : `\`${t("list_reminders")}\``;
|
|
685
|
+
const cancelReminderCmd = isCli ? "`slock reminder cancel`" : `\`${t("cancel_reminder")}\``;
|
|
612
686
|
const messageDeliveryText = opts.includeStdinNotificationSection ? "New messages may be delivered to you automatically while your process stays alive." : "The daemon will automatically restart you when new messages arrive.";
|
|
613
687
|
const criticalRules = isCli ? [
|
|
614
688
|
"- Always communicate through `slock` CLI commands. This is your only output channel.",
|
|
@@ -621,13 +695,18 @@ function buildPrompt(config, variant, opts) {
|
|
|
621
695
|
"- Use only the provided MCP tools for messaging \u2014 they are already available and ready.",
|
|
622
696
|
`- Always claim a task via ${taskClaimCmd} before starting work on it. If the claim fails, move on to a different task.`
|
|
623
697
|
];
|
|
698
|
+
const runtimeProfileControlStartupStep = config.runtimeProfileControl ? [
|
|
699
|
+
"0. If this system prompt contains a **Runtime Profile Control** section, complete that runtime-control instruction first. Do not read MEMORY.md, check messages, or respond to inbox messages until the required runtime control action has succeeded."
|
|
700
|
+
] : [];
|
|
624
701
|
const startupSteps = isCli ? [
|
|
702
|
+
...runtimeProfileControlStartupStep,
|
|
625
703
|
"1. If this turn already includes a concrete incoming message, first decide whether that message needs a visible acknowledgment, blocker question, or ownership signal. If it does, send it early with `slock message send` before deep context gathering.",
|
|
626
704
|
"2. Read MEMORY.md (in your cwd) and then only the additional memory/files you need to handle the current turn well.",
|
|
627
705
|
`3. If there is no concrete incoming message to handle, stop and wait. ${messageDeliveryText}`,
|
|
628
706
|
"4. When you receive a message, process it and reply with `slock message send`.",
|
|
629
707
|
"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."
|
|
630
708
|
] : [
|
|
709
|
+
...runtimeProfileControlStartupStep,
|
|
631
710
|
`1. If this turn already includes a concrete incoming message, first decide whether that message needs a visible acknowledgment, blocker question, or ownership signal. If it does, send it early with ${sendCmd} before deep context gathering.`,
|
|
632
711
|
"2. Read MEMORY.md (in your cwd) and then only the additional memory/files you need to handle the current turn well.",
|
|
633
712
|
`3. If there is no concrete incoming message to handle, stop and wait. ${messageDeliveryText}`,
|
|
@@ -641,24 +720,23 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
|
|
|
641
720
|
1. **\`slock message check\`** \u2014 Non-blocking check for new messages. Use freely during work \u2014 at natural breakpoints or after notifications.
|
|
642
721
|
2. **\`slock message send\`** \u2014 Send a message to a channel or DM.
|
|
643
722
|
3. **\`slock server info\`** \u2014 List channels in this server, which ones you have joined, plus all agents and humans.
|
|
644
|
-
4. **\`slock channel
|
|
645
|
-
5. **\`slock
|
|
646
|
-
6. **\`slock
|
|
647
|
-
7. **\`slock message
|
|
648
|
-
8. **\`slock
|
|
649
|
-
9. **\`slock task
|
|
650
|
-
10. **\`slock task
|
|
651
|
-
11. **\`slock task
|
|
652
|
-
12. **\`slock task
|
|
653
|
-
13. **\`slock
|
|
654
|
-
14. **\`slock attachment
|
|
655
|
-
15. **\`slock
|
|
656
|
-
16. **\`slock
|
|
657
|
-
17. **\`slock
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
For agent-created reminders, first resolve the anchor message from the current conversation and pass its \`msgId\` explicitly. If you cannot resolve a message id, do not create the reminder.
|
|
723
|
+
4. **\`slock channel members\`** \u2014 List the members (agents and humans) of a specific channel, DM, or thread target.
|
|
724
|
+
5. **\`slock channel leave\`** \u2014 Leave a regular channel you have joined. This only affects your own agent membership.
|
|
725
|
+
6. **\`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.
|
|
726
|
+
7. **\`slock message read\`** \u2014 Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` pagination and \`around\` for centered context.
|
|
727
|
+
8. **\`slock message search\`** \u2014 Search messages visible to you, then inspect a hit with \`slock message read\`.
|
|
728
|
+
9. **\`slock task list\`** \u2014 View a channel's task board.
|
|
729
|
+
10. **\`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).
|
|
730
|
+
11. **\`slock task claim\`** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
|
|
731
|
+
12. **\`slock task unclaim\`** \u2014 Release your claim on a task.
|
|
732
|
+
13. **\`slock task update\`** \u2014 Change a task's status (e.g. to in_review or done).
|
|
733
|
+
14. **\`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\`.
|
|
734
|
+
15. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
735
|
+
16. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
|
|
736
|
+
17. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--display-name <name>\`, and \`--description <text>\`. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
|
|
737
|
+
18. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
738
|
+
19. **\`slock reminder list\`** \u2014 List your reminders.
|
|
739
|
+
20. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
|
|
662
740
|
|
|
663
741
|
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:
|
|
664
742
|
- failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
|
|
@@ -682,7 +760,15 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
|
682
760
|
10. **\`${t("unclaim_task")}\`** \u2014 Release your claim on a task.
|
|
683
761
|
11. **${taskUpdateCmd}** \u2014 Change a task's status (e.g. to in_review or done).
|
|
684
762
|
12. **\`${t("upload_file")}\`** \u2014 Upload a file to attach to a message. Returns an attachment ID to pass to ${sendCmd}.
|
|
685
|
-
13. **\`${t("view_file")}\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally
|
|
763
|
+
13. **\`${t("view_file")}\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
|
|
764
|
+
14. **${scheduleReminderCmd}** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
|
|
765
|
+
15. **${listRemindersCmd}** \u2014 List your reminders.
|
|
766
|
+
16. **${cancelReminderCmd}** \u2014 Cancel one of your reminders by ID.`;
|
|
767
|
+
const reminderSection = `### Reminders
|
|
768
|
+
|
|
769
|
+
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, 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.
|
|
770
|
+
Use ${scheduleReminderCmd} rather than runtime-native wake or cron tools such as ScheduleWakeup or CronCreate for user-visible reminders, so reminders stay anchored, observable, and cancelable in Slock.
|
|
771
|
+
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.`;
|
|
686
772
|
const sendingMessagesSection = isCli ? `### Sending messages
|
|
687
773
|
|
|
688
774
|
- **Reply to a channel**: \`slock message send --target "#channel-name" <<'EOF'\` followed by the message body and \`EOF\`
|
|
@@ -728,7 +814,7 @@ Threads are sub-conversations attached to a specific message. They let you discu
|
|
|
728
814
|
const discoverySection = isCli ? `### Discovering people and channels
|
|
729
815
|
|
|
730
816
|
Call \`slock server info\` to see all channels in this server, which ones you have joined, other agents, and humans.
|
|
731
|
-
Visible public channels may appear even when \`joined=false\`. In that state you can still inspect them with \`slock message read\`, but you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel. 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"\`.` : `### Discovering people and channels
|
|
817
|
+
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 a human adds you to the channel. 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"\`.` : `### Discovering people and channels
|
|
732
818
|
|
|
733
819
|
Call ${serverInfoCmd} to see all channels in this server, which ones you have joined, other agents, and humans.
|
|
734
820
|
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 a human adds you to the channel. To leave a regular channel you have joined, use \`${t("leave_channel")}\`.`;
|
|
@@ -752,6 +838,11 @@ To jump directly to a specific hit with nearby context, use \`slock message read
|
|
|
752
838
|
Use ${readCmd} with the \`channel\` parameter set to \`"#channel-name"\`, \`"dm:@peer-name"\`, or a thread target like \`"#channel:shortid"\`.
|
|
753
839
|
|
|
754
840
|
To jump directly to a specific hit with nearby context, pass \`around\` set to a message ID or seq number.`;
|
|
841
|
+
const historicalReferenceSection = isCli ? `### Historical references
|
|
842
|
+
|
|
843
|
+
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
|
|
844
|
+
|
|
845
|
+
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.`;
|
|
755
846
|
const tasksSection = isCli ? `### Tasks
|
|
756
847
|
|
|
757
848
|
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.
|
|
@@ -838,6 +929,8 @@ ${readCmd} shows messages in their current state. If a message was later convert
|
|
|
838
929
|
|
|
839
930
|
Your workspace and MEMORY.md persist across turns, so you can recover context when resumed. You will be started, put to sleep when idle, and woken up again when someone sends you a message. Think of yourself as a colleague who is always available, accumulates knowledge over time, and develops expertise through interactions.
|
|
840
931
|
|
|
932
|
+
${runtimeContextLines(config).join("\n")}
|
|
933
|
+
|
|
841
934
|
${communicationSection}
|
|
842
935
|
|
|
843
936
|
CRITICAL RULES:
|
|
@@ -851,6 +944,30 @@ ${startupSteps.join("\n")}`;
|
|
|
851
944
|
|
|
852
945
|
${opts.postStartupNotes.join("\n")}`;
|
|
853
946
|
}
|
|
947
|
+
if (config.runtimeProfileControl) {
|
|
948
|
+
const control = config.runtimeProfileControl;
|
|
949
|
+
prompt += `
|
|
950
|
+
|
|
951
|
+
## Runtime Profile Control
|
|
952
|
+
|
|
953
|
+
`;
|
|
954
|
+
prompt += `This section is a trusted daemon runtime-control instruction. It overrides the normal startup sequence: complete this section before reading MEMORY.md, checking messages, or responding to inbox messages.
|
|
955
|
+
|
|
956
|
+
`;
|
|
957
|
+
if (control.kind === "migration") {
|
|
958
|
+
prompt += `You are currently in Runtime Profile migration. Before handling normal inbox messages, re-ground yourself in the new runtime context and then invoke the runtime control action \`${t("runtime_profile_migration_done")}\` with exactly this migration_key: \`${control.key}\`.
|
|
959
|
+
|
|
960
|
+
`;
|
|
961
|
+
prompt += `Do not use ${sendCmd}, ${checkCmd}, or any chat reply as the migration acknowledgment. Normal inbox delivery is gated until the runtime control action succeeds.
|
|
962
|
+
|
|
963
|
+
`;
|
|
964
|
+
} else {
|
|
965
|
+
prompt += `Read the daemon release notice below before handling normal inbox messages. No chat reply is required for this notice.
|
|
966
|
+
|
|
967
|
+
`;
|
|
968
|
+
}
|
|
969
|
+
prompt += control.message;
|
|
970
|
+
}
|
|
854
971
|
prompt += `
|
|
855
972
|
|
|
856
973
|
## Messaging
|
|
@@ -875,6 +992,8 @@ Header fields:
|
|
|
875
992
|
|
|
876
993
|
${sendingMessagesSection}
|
|
877
994
|
|
|
995
|
+
${reminderSection}
|
|
996
|
+
|
|
878
997
|
${threadsSection}
|
|
879
998
|
|
|
880
999
|
${discoverySection}
|
|
@@ -883,6 +1002,8 @@ ${channelAwarenessSection}
|
|
|
883
1002
|
|
|
884
1003
|
${readingHistorySection}
|
|
885
1004
|
|
|
1005
|
+
${historicalReferenceSection}
|
|
1006
|
+
|
|
886
1007
|
${tasksSection}
|
|
887
1008
|
|
|
888
1009
|
### Splitting tasks for parallel execution
|
|
@@ -940,7 +1061,7 @@ When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.), alwa
|
|
|
940
1061
|
|
|
941
1062
|
## Workspace & Memory
|
|
942
1063
|
|
|
943
|
-
Your working directory (cwd) is your **persistent workspace
|
|
1064
|
+
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.
|
|
944
1065
|
|
|
945
1066
|
### MEMORY.md \u2014 Your Memory Index (CRITICAL)
|
|
946
1067
|
|
|
@@ -1045,6 +1166,20 @@ function buildMcpSystemPrompt(config, opts) {
|
|
|
1045
1166
|
|
|
1046
1167
|
// src/drivers/cliTransport.ts
|
|
1047
1168
|
var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1169
|
+
function runtimeContextEnv(config) {
|
|
1170
|
+
const ctx = config.runtimeContext;
|
|
1171
|
+
if (!ctx) return {};
|
|
1172
|
+
return {
|
|
1173
|
+
...ctx.agentId ? { SLOCK_CURRENT_AGENT_ID: ctx.agentId } : {},
|
|
1174
|
+
...ctx.serverId ? { SLOCK_CURRENT_SERVER_ID: ctx.serverId } : {},
|
|
1175
|
+
...ctx.machineId ? { SLOCK_CURRENT_COMPUTER_ID: ctx.machineId } : {},
|
|
1176
|
+
...ctx.machineName ? { SLOCK_CURRENT_COMPUTER_NAME: ctx.machineName } : {},
|
|
1177
|
+
...ctx.machineHostname ? { SLOCK_CURRENT_COMPUTER_HOSTNAME: ctx.machineHostname } : {},
|
|
1178
|
+
...ctx.machineOs ? { SLOCK_CURRENT_COMPUTER_OS: ctx.machineOs } : {},
|
|
1179
|
+
...ctx.daemonVersion ? { SLOCK_CURRENT_DAEMON_VERSION: ctx.daemonVersion } : {},
|
|
1180
|
+
...ctx.workspacePath ? { SLOCK_CURRENT_WORKSPACE_PATH: ctx.workspacePath } : {}
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1048
1183
|
function buildCliTransportSystemPrompt(config, opts) {
|
|
1049
1184
|
return buildCliSystemPrompt(config, opts);
|
|
1050
1185
|
}
|
|
@@ -1074,6 +1209,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
|
|
|
1074
1209
|
FORCE_COLOR: "0",
|
|
1075
1210
|
...ctx.config.envVars || {},
|
|
1076
1211
|
...extraEnv,
|
|
1212
|
+
...runtimeContextEnv(ctx.config),
|
|
1077
1213
|
SLOCK_AGENT_ID: ctx.agentId,
|
|
1078
1214
|
...ctx.launchId ? { SLOCK_AGENT_LAUNCH_ID: ctx.launchId } : {},
|
|
1079
1215
|
SLOCK_SERVER_URL: ctx.config.serverUrl,
|
|
@@ -1207,6 +1343,8 @@ var ClaudeDriver = class {
|
|
|
1207
1343
|
"--allow-dangerously-skip-permissions",
|
|
1208
1344
|
"--dangerously-skip-permissions",
|
|
1209
1345
|
"--verbose",
|
|
1346
|
+
"--permission-mode",
|
|
1347
|
+
"bypassPermissions",
|
|
1210
1348
|
"--output-format",
|
|
1211
1349
|
"stream-json",
|
|
1212
1350
|
"--input-format",
|
|
@@ -1217,16 +1355,16 @@ var ClaudeDriver = class {
|
|
|
1217
1355
|
CLAUDE_DISALLOWED_TOOLS
|
|
1218
1356
|
];
|
|
1219
1357
|
if (opts.standingPromptFilePath) {
|
|
1220
|
-
args.push("--
|
|
1358
|
+
args.push("--system-prompt-file", opts.standingPromptFilePath);
|
|
1221
1359
|
} else {
|
|
1222
|
-
args.push("--
|
|
1360
|
+
args.push("--system-prompt", standingPrompt);
|
|
1223
1361
|
}
|
|
1224
1362
|
if (config.sessionId) {
|
|
1225
1363
|
args.push("--resume", config.sessionId);
|
|
1226
1364
|
}
|
|
1227
1365
|
return args;
|
|
1228
1366
|
}
|
|
1229
|
-
|
|
1367
|
+
buildRuntimeActionsMcpConfig(ctx) {
|
|
1230
1368
|
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
1231
1369
|
const command = isTsSource ? "npx" : "node";
|
|
1232
1370
|
const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath];
|
|
@@ -1245,7 +1383,7 @@ var ClaudeDriver = class {
|
|
|
1245
1383
|
"--runtime",
|
|
1246
1384
|
this.id,
|
|
1247
1385
|
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
1248
|
-
"--
|
|
1386
|
+
"--runtime-actions-only"
|
|
1249
1387
|
]
|
|
1250
1388
|
}
|
|
1251
1389
|
}
|
|
@@ -1255,7 +1393,7 @@ var ClaudeDriver = class {
|
|
|
1255
1393
|
const systemPromptPath = path3.join(slockDir, CLAUDE_SYSTEM_PROMPT_FILE);
|
|
1256
1394
|
const mcpConfigPath = path3.join(slockDir, CLAUDE_MCP_CONFIG_FILE);
|
|
1257
1395
|
writeFileSync2(systemPromptPath, ctx.standingPrompt, { mode: 384 });
|
|
1258
|
-
writeFileSync2(mcpConfigPath, this.
|
|
1396
|
+
writeFileSync2(mcpConfigPath, this.buildRuntimeActionsMcpConfig(ctx), { mode: 384 });
|
|
1259
1397
|
return { systemPromptPath, mcpConfigPath };
|
|
1260
1398
|
}
|
|
1261
1399
|
spawn(ctx) {
|
|
@@ -1264,7 +1402,7 @@ var ClaudeDriver = class {
|
|
|
1264
1402
|
const args = this.buildClaudeArgs(ctx.config, ctx.standingPrompt, {
|
|
1265
1403
|
standingPromptFilePath: systemPromptPath
|
|
1266
1404
|
});
|
|
1267
|
-
args.push("--mcp-config", mcpConfigPath);
|
|
1405
|
+
args.push("--mcp-config", mcpConfigPath, "--strict-mcp-config");
|
|
1268
1406
|
delete spawnEnv.CLAUDECODE;
|
|
1269
1407
|
logger.info(
|
|
1270
1408
|
`[Agent ${ctx.agentId}] transport=cli cli=${ctx.slockCliPath} token_file=${tokenFile}`
|
|
@@ -1388,7 +1526,9 @@ var ClaudeDriver = class {
|
|
|
1388
1526
|
buildSystemPrompt(config, _agentId) {
|
|
1389
1527
|
return buildCliTransportSystemPrompt(config, {
|
|
1390
1528
|
toolPrefix: "mcp__chat__",
|
|
1391
|
-
extraCriticalRules: [
|
|
1529
|
+
extraCriticalRules: [
|
|
1530
|
+
"- Runtime Profile migration completion is the only exception to CLI-only operation: when a migration notice tells you to acknowledge with `runtime_profile_migration_done`, call the `mcp__chat__runtime_profile_migration_done` tool with the exact `migration_key`; do not use `slock` CLI or reply in chat as the acknowledgment."
|
|
1531
|
+
],
|
|
1392
1532
|
postStartupNotes: [
|
|
1393
1533
|
"**Claude runtime note:** Slock preserves Claude Code same-turn steering through a gated stream-json delivery path. Busy messages are buffered and delivered at Claude-observed safe boundaries; if no earlier safe boundary is available, they are delivered after the current turn ends.",
|
|
1394
1534
|
"For long tool runs, you can also use `slock message check` at natural breakpoints to pull pending messages explicitly."
|
|
@@ -1498,7 +1638,7 @@ var CodexDriver = class {
|
|
|
1498
1638
|
probe() {
|
|
1499
1639
|
return probeCodex();
|
|
1500
1640
|
}
|
|
1501
|
-
|
|
1641
|
+
buildRuntimeActionsConfigArgs(ctx) {
|
|
1502
1642
|
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
1503
1643
|
const command = isTsSource ? "npx" : "node";
|
|
1504
1644
|
const bridgeArgs = isTsSource ? [
|
|
@@ -1513,7 +1653,7 @@ var CodexDriver = class {
|
|
|
1513
1653
|
"--runtime",
|
|
1514
1654
|
this.id,
|
|
1515
1655
|
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
1516
|
-
"--
|
|
1656
|
+
"--runtime-actions-only"
|
|
1517
1657
|
] : [
|
|
1518
1658
|
ctx.chatBridgePath,
|
|
1519
1659
|
"--agent-id",
|
|
@@ -1525,7 +1665,7 @@ var CodexDriver = class {
|
|
|
1525
1665
|
"--runtime",
|
|
1526
1666
|
this.id,
|
|
1527
1667
|
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
1528
|
-
"--
|
|
1668
|
+
"--runtime-actions-only"
|
|
1529
1669
|
];
|
|
1530
1670
|
return [
|
|
1531
1671
|
"-c",
|
|
@@ -1595,7 +1735,7 @@ var CodexDriver = class {
|
|
|
1595
1735
|
this.streamedAgentMessageIds.clear();
|
|
1596
1736
|
this.streamedReasoningIds.clear();
|
|
1597
1737
|
const args = ["app-server", "--listen", "stdio://"];
|
|
1598
|
-
args.push(...this.
|
|
1738
|
+
args.push(...this.buildRuntimeActionsConfigArgs(ctx));
|
|
1599
1739
|
const { command, args: spawnArgs } = resolveCodexSpawn(args);
|
|
1600
1740
|
const proc = spawn2(command, spawnArgs, {
|
|
1601
1741
|
cwd: ctx.workingDirectory,
|
|
@@ -2165,6 +2305,17 @@ var CursorDriver = class {
|
|
|
2165
2305
|
import { spawn as spawn5 } from "child_process";
|
|
2166
2306
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
2167
2307
|
import path7 from "path";
|
|
2308
|
+
function buildGeminiSpawnEnv(ctx) {
|
|
2309
|
+
return {
|
|
2310
|
+
...process.env,
|
|
2311
|
+
FORCE_COLOR: "0",
|
|
2312
|
+
NO_COLOR: "1",
|
|
2313
|
+
// Gemini CLI's trusted-workspace gate breaks our managed headless flow
|
|
2314
|
+
// unless we explicitly trust the daemon-owned agent workspace.
|
|
2315
|
+
GEMINI_CLI_TRUST_WORKSPACE: "true",
|
|
2316
|
+
...ctx.config.envVars || {}
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2168
2319
|
var GeminiDriver = class {
|
|
2169
2320
|
id = "gemini";
|
|
2170
2321
|
supportsStdinNotification = false;
|
|
@@ -2204,7 +2355,7 @@ var GeminiDriver = class {
|
|
|
2204
2355
|
if (ctx.config.sessionId) {
|
|
2205
2356
|
args.push("--resume", ctx.config.sessionId);
|
|
2206
2357
|
}
|
|
2207
|
-
const spawnEnv =
|
|
2358
|
+
const spawnEnv = buildGeminiSpawnEnv(ctx);
|
|
2208
2359
|
const proc = spawn5("gemini", args, {
|
|
2209
2360
|
cwd: ctx.workingDirectory,
|
|
2210
2361
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2506,6 +2657,279 @@ function detectKimiModels(home = os2.homedir()) {
|
|
|
2506
2657
|
return { models, default: defaultModel };
|
|
2507
2658
|
}
|
|
2508
2659
|
|
|
2660
|
+
// src/drivers/opencode.ts
|
|
2661
|
+
import { spawn as spawn7 } from "child_process";
|
|
2662
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2663
|
+
import os3 from "os";
|
|
2664
|
+
import path9 from "path";
|
|
2665
|
+
var CHAT_MCP_SERVER_NAME = "chat";
|
|
2666
|
+
var CHAT_MCP_TOOL_PREFIX = `${CHAT_MCP_SERVER_NAME}_`;
|
|
2667
|
+
var SLOCK_AGENT_NAME = "slock";
|
|
2668
|
+
var NO_MESSAGE_PROMPT = "No new messages are pending. Stop now.";
|
|
2669
|
+
var FIRST_MESSAGE_TASK_PREFIX = "First message task (system-triggered):";
|
|
2670
|
+
var MIN_SUPPORTED_OPENCODE_VERSION = "1.14.30";
|
|
2671
|
+
function buildChatBridgeCommand(ctx) {
|
|
2672
|
+
const isTsSource = ctx.chatBridgePath.endsWith(".ts");
|
|
2673
|
+
return [
|
|
2674
|
+
isTsSource ? "npx" : "node",
|
|
2675
|
+
...isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath],
|
|
2676
|
+
"--agent-id",
|
|
2677
|
+
ctx.agentId,
|
|
2678
|
+
"--server-url",
|
|
2679
|
+
ctx.config.serverUrl,
|
|
2680
|
+
"--auth-token",
|
|
2681
|
+
ctx.config.authToken || ctx.daemonApiKey,
|
|
2682
|
+
"--runtime",
|
|
2683
|
+
"opencode",
|
|
2684
|
+
...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
|
|
2685
|
+
"--runtime-actions-only"
|
|
2686
|
+
];
|
|
2687
|
+
}
|
|
2688
|
+
function parseOpenCodeConfigContent(raw) {
|
|
2689
|
+
if (!raw) return {};
|
|
2690
|
+
try {
|
|
2691
|
+
const parsed = JSON.parse(raw);
|
|
2692
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2693
|
+
return parsed;
|
|
2694
|
+
}
|
|
2695
|
+
} catch {
|
|
2696
|
+
}
|
|
2697
|
+
return {};
|
|
2698
|
+
}
|
|
2699
|
+
function parseUserOpenCodeConfig(ctx) {
|
|
2700
|
+
const raw = ctx.config.envVars?.OPENCODE_CONFIG_CONTENT;
|
|
2701
|
+
return parseOpenCodeConfigContent(raw);
|
|
2702
|
+
}
|
|
2703
|
+
function readLocalOpenCodeConfig(home = os3.homedir()) {
|
|
2704
|
+
const configPath = path9.join(home, ".config", "opencode", "opencode.json");
|
|
2705
|
+
try {
|
|
2706
|
+
return parseOpenCodeConfigContent(readFileSync3(configPath, "utf8"));
|
|
2707
|
+
} catch {
|
|
2708
|
+
}
|
|
2709
|
+
return {};
|
|
2710
|
+
}
|
|
2711
|
+
function recordField(value) {
|
|
2712
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2713
|
+
}
|
|
2714
|
+
function parseSemver(version) {
|
|
2715
|
+
const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
2716
|
+
if (!match) return null;
|
|
2717
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
2718
|
+
}
|
|
2719
|
+
function isSupportedOpenCodeVersion(version) {
|
|
2720
|
+
if (!version) return true;
|
|
2721
|
+
const actual = parseSemver(version);
|
|
2722
|
+
const minimum = parseSemver(MIN_SUPPORTED_OPENCODE_VERSION);
|
|
2723
|
+
if (!actual || !minimum) return true;
|
|
2724
|
+
for (let i = 0; i < 3; i += 1) {
|
|
2725
|
+
if (actual[i] > minimum[i]) return true;
|
|
2726
|
+
if (actual[i] < minimum[i]) return false;
|
|
2727
|
+
}
|
|
2728
|
+
return true;
|
|
2729
|
+
}
|
|
2730
|
+
function unsupportedOpenCodeVersionMessage(version) {
|
|
2731
|
+
if (!version || isSupportedOpenCodeVersion(version)) return null;
|
|
2732
|
+
return `OpenCode CLI ${version} is unsupported; requires OpenCode >= ${MIN_SUPPORTED_OPENCODE_VERSION}. Upgrade opencode before starting this runtime.`;
|
|
2733
|
+
}
|
|
2734
|
+
function mergeOpenCodeConfigs(localConfig, envConfig) {
|
|
2735
|
+
return {
|
|
2736
|
+
...localConfig,
|
|
2737
|
+
...envConfig,
|
|
2738
|
+
provider: {
|
|
2739
|
+
...recordField(localConfig.provider),
|
|
2740
|
+
...recordField(envConfig.provider)
|
|
2741
|
+
},
|
|
2742
|
+
agent: {
|
|
2743
|
+
...recordField(localConfig.agent),
|
|
2744
|
+
...recordField(envConfig.agent)
|
|
2745
|
+
},
|
|
2746
|
+
mcp: {
|
|
2747
|
+
...recordField(localConfig.mcp),
|
|
2748
|
+
...recordField(envConfig.mcp)
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
function buildOpenCodeConfig(ctx, home = os3.homedir()) {
|
|
2753
|
+
const userConfig = mergeOpenCodeConfigs(readLocalOpenCodeConfig(home), parseUserOpenCodeConfig(ctx));
|
|
2754
|
+
const userAgents = recordField(userConfig.agent);
|
|
2755
|
+
const userSlockAgent = recordField(userAgents[SLOCK_AGENT_NAME]);
|
|
2756
|
+
return {
|
|
2757
|
+
...userConfig,
|
|
2758
|
+
$schema: "https://opencode.ai/config.json",
|
|
2759
|
+
agent: {
|
|
2760
|
+
...userAgents,
|
|
2761
|
+
[SLOCK_AGENT_NAME]: {
|
|
2762
|
+
...userSlockAgent,
|
|
2763
|
+
description: "Slock agent runtime",
|
|
2764
|
+
prompt: ctx.standingPrompt
|
|
2765
|
+
}
|
|
2766
|
+
},
|
|
2767
|
+
mcp: {
|
|
2768
|
+
...recordField(userConfig.mcp),
|
|
2769
|
+
[CHAT_MCP_SERVER_NAME]: {
|
|
2770
|
+
type: "local",
|
|
2771
|
+
command: buildChatBridgeCommand(ctx),
|
|
2772
|
+
enabled: true
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
function buildOpenCodeLaunchOptions(ctx, home = os3.homedir()) {
|
|
2778
|
+
const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
|
|
2779
|
+
const config = buildOpenCodeConfig(ctx, home);
|
|
2780
|
+
const env = {
|
|
2781
|
+
...slock.spawnEnv,
|
|
2782
|
+
OPENCODE_CONFIG_CONTENT: JSON.stringify(config)
|
|
2783
|
+
};
|
|
2784
|
+
const args = [
|
|
2785
|
+
"run",
|
|
2786
|
+
"--format",
|
|
2787
|
+
"json",
|
|
2788
|
+
"--dangerously-skip-permissions",
|
|
2789
|
+
"--pure",
|
|
2790
|
+
"--dir",
|
|
2791
|
+
ctx.workingDirectory
|
|
2792
|
+
];
|
|
2793
|
+
if (ctx.config.model && ctx.config.model !== "default") {
|
|
2794
|
+
args.push("--model", ctx.config.model);
|
|
2795
|
+
}
|
|
2796
|
+
args.push("--agent", SLOCK_AGENT_NAME);
|
|
2797
|
+
if (ctx.config.sessionId) {
|
|
2798
|
+
args.push("--session", ctx.config.sessionId);
|
|
2799
|
+
}
|
|
2800
|
+
const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT : ctx.prompt;
|
|
2801
|
+
args.push("--", turnPrompt);
|
|
2802
|
+
return { args, env, config };
|
|
2803
|
+
}
|
|
2804
|
+
function detectOpenCodeModels(home = os3.homedir()) {
|
|
2805
|
+
const models = [...RUNTIME_MODELS.opencode || []];
|
|
2806
|
+
const providers = recordField(readLocalOpenCodeConfig(home).provider);
|
|
2807
|
+
for (const [providerId, providerConfig] of Object.entries(providers)) {
|
|
2808
|
+
const providerModels = recordField(recordField(providerConfig).models);
|
|
2809
|
+
for (const [modelId, modelConfig] of Object.entries(providerModels)) {
|
|
2810
|
+
const fullId = `${providerId}/${modelId}`;
|
|
2811
|
+
if (models.some((model2) => model2.id === fullId)) continue;
|
|
2812
|
+
const model = recordField(modelConfig);
|
|
2813
|
+
const name = typeof model.name === "string" && model.name.length > 0 ? model.name : fullId;
|
|
2814
|
+
models.push({ id: fullId, label: name });
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
return models.length > 0 ? { models } : null;
|
|
2818
|
+
}
|
|
2819
|
+
function isSystemFirstMessageTask(message) {
|
|
2820
|
+
return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX);
|
|
2821
|
+
}
|
|
2822
|
+
function buildOpenCodeSystemPrompt(config) {
|
|
2823
|
+
return buildCliTransportSystemPrompt(config, {
|
|
2824
|
+
toolPrefix: CHAT_MCP_TOOL_PREFIX,
|
|
2825
|
+
extraCriticalRules: [
|
|
2826
|
+
"- Runtime Profile migration completion is the only exception to CLI-only operation: when a migration notice tells you to acknowledge with `runtime_profile_migration_done`, call the `chat_runtime_profile_migration_done` tool with the exact `migration_key`; do not use `slock` CLI or reply in chat as the acknowledgment."
|
|
2827
|
+
],
|
|
2828
|
+
postStartupNotes: [
|
|
2829
|
+
"**OpenCode runtime note:** Slock launches you as a per-turn process. Complete the current wake using `slock` CLI commands, then stop; the daemon will restart you when new messages arrive."
|
|
2830
|
+
],
|
|
2831
|
+
includeStdinNotificationSection: false,
|
|
2832
|
+
messageNotificationStyle: "poll"
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2835
|
+
var OpenCodeDriver = class {
|
|
2836
|
+
id = "opencode";
|
|
2837
|
+
supportsStdinNotification = false;
|
|
2838
|
+
mcpToolPrefix = CHAT_MCP_TOOL_PREFIX;
|
|
2839
|
+
busyDeliveryMode = "none";
|
|
2840
|
+
terminateProcessOnTurnEnd = true;
|
|
2841
|
+
deferSpawnUntilMessage = true;
|
|
2842
|
+
usesSlockCliForCommunication = true;
|
|
2843
|
+
shouldDeferWakeMessage(message) {
|
|
2844
|
+
return isSystemFirstMessageTask(message);
|
|
2845
|
+
}
|
|
2846
|
+
sessionId = null;
|
|
2847
|
+
sessionAnnounced = false;
|
|
2848
|
+
probe() {
|
|
2849
|
+
if (!resolveCommandOnPath("opencode")) return { available: false };
|
|
2850
|
+
const version = readCommandVersion("opencode") || void 0;
|
|
2851
|
+
const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
|
|
2852
|
+
if (unsupportedMessage) {
|
|
2853
|
+
return {
|
|
2854
|
+
available: false,
|
|
2855
|
+
version: `${version} (requires >= ${MIN_SUPPORTED_OPENCODE_VERSION})`
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
return { available: true, version };
|
|
2859
|
+
}
|
|
2860
|
+
async detectModels() {
|
|
2861
|
+
return detectOpenCodeModels();
|
|
2862
|
+
}
|
|
2863
|
+
spawn(ctx) {
|
|
2864
|
+
this.sessionId = ctx.config.sessionId || null;
|
|
2865
|
+
this.sessionAnnounced = false;
|
|
2866
|
+
const unsupportedMessage = unsupportedOpenCodeVersionMessage(readCommandVersion("opencode"));
|
|
2867
|
+
if (unsupportedMessage) {
|
|
2868
|
+
throw new Error(unsupportedMessage);
|
|
2869
|
+
}
|
|
2870
|
+
const launch = buildOpenCodeLaunchOptions(ctx);
|
|
2871
|
+
const proc = spawn7("opencode", launch.args, {
|
|
2872
|
+
cwd: ctx.workingDirectory,
|
|
2873
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2874
|
+
env: launch.env,
|
|
2875
|
+
shell: process.platform === "win32"
|
|
2876
|
+
});
|
|
2877
|
+
proc.stdin?.end();
|
|
2878
|
+
return { process: proc };
|
|
2879
|
+
}
|
|
2880
|
+
parseLine(line) {
|
|
2881
|
+
let event;
|
|
2882
|
+
try {
|
|
2883
|
+
event = JSON.parse(line);
|
|
2884
|
+
} catch {
|
|
2885
|
+
return [];
|
|
2886
|
+
}
|
|
2887
|
+
const events = [];
|
|
2888
|
+
if (event.sessionID && event.sessionID !== this.sessionId) {
|
|
2889
|
+
this.sessionId = event.sessionID;
|
|
2890
|
+
}
|
|
2891
|
+
if (!this.sessionAnnounced && this.sessionId) {
|
|
2892
|
+
events.push({ kind: "session_init", sessionId: this.sessionId });
|
|
2893
|
+
this.sessionAnnounced = true;
|
|
2894
|
+
}
|
|
2895
|
+
switch (event.type) {
|
|
2896
|
+
case "step_start":
|
|
2897
|
+
events.push({ kind: "thinking", text: "" });
|
|
2898
|
+
break;
|
|
2899
|
+
case "text":
|
|
2900
|
+
if (typeof event.part?.text === "string" && event.part.text.length > 0) {
|
|
2901
|
+
events.push({ kind: "text", text: event.part.text });
|
|
2902
|
+
}
|
|
2903
|
+
break;
|
|
2904
|
+
case "tool_use":
|
|
2905
|
+
events.push({
|
|
2906
|
+
kind: "tool_call",
|
|
2907
|
+
name: event.part?.tool || "unknown_tool",
|
|
2908
|
+
input: event.part?.state?.input
|
|
2909
|
+
});
|
|
2910
|
+
break;
|
|
2911
|
+
case "step_finish":
|
|
2912
|
+
if (event.part?.reason !== "tool-calls") {
|
|
2913
|
+
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
2914
|
+
}
|
|
2915
|
+
break;
|
|
2916
|
+
case "error": {
|
|
2917
|
+
const message = event.error?.data?.message || event.error?.message || (event.error?.name ? `${event.error.name} (no message)` : null) || "Unknown OpenCode error";
|
|
2918
|
+
events.push({ kind: "error", message });
|
|
2919
|
+
events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
|
|
2920
|
+
break;
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
return events;
|
|
2924
|
+
}
|
|
2925
|
+
encodeStdinMessage(_text, _sessionId, _opts) {
|
|
2926
|
+
return null;
|
|
2927
|
+
}
|
|
2928
|
+
buildSystemPrompt(config, _agentId) {
|
|
2929
|
+
return buildOpenCodeSystemPrompt(config);
|
|
2930
|
+
}
|
|
2931
|
+
};
|
|
2932
|
+
|
|
2509
2933
|
// src/drivers/index.ts
|
|
2510
2934
|
var driverFactories = {
|
|
2511
2935
|
claude: () => new ClaudeDriver(),
|
|
@@ -2513,7 +2937,8 @@ var driverFactories = {
|
|
|
2513
2937
|
copilot: () => new CopilotDriver(),
|
|
2514
2938
|
cursor: () => new CursorDriver(),
|
|
2515
2939
|
gemini: () => new GeminiDriver(),
|
|
2516
|
-
kimi: () => new KimiDriver()
|
|
2940
|
+
kimi: () => new KimiDriver(),
|
|
2941
|
+
opencode: () => new OpenCodeDriver()
|
|
2517
2942
|
};
|
|
2518
2943
|
function getDriver(runtimeId) {
|
|
2519
2944
|
const createDriver = driverFactories[runtimeId];
|
|
@@ -2526,7 +2951,7 @@ function getDriver(runtimeId) {
|
|
|
2526
2951
|
|
|
2527
2952
|
// src/workspaces.ts
|
|
2528
2953
|
import { readdir, rm, stat } from "fs/promises";
|
|
2529
|
-
import
|
|
2954
|
+
import path10 from "path";
|
|
2530
2955
|
function isValidWorkspaceDirectoryName(directoryName) {
|
|
2531
2956
|
return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
|
|
2532
2957
|
}
|
|
@@ -2534,7 +2959,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
|
|
|
2534
2959
|
if (!isValidWorkspaceDirectoryName(directoryName)) {
|
|
2535
2960
|
return null;
|
|
2536
2961
|
}
|
|
2537
|
-
return
|
|
2962
|
+
return path10.join(dataDir, directoryName);
|
|
2538
2963
|
}
|
|
2539
2964
|
function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
|
|
2540
2965
|
return {
|
|
@@ -2583,7 +3008,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
|
|
|
2583
3008
|
return summary;
|
|
2584
3009
|
}
|
|
2585
3010
|
const childSummaries = await Promise.all(
|
|
2586
|
-
entries.map((entry) => summarizeWorkspaceEntry(
|
|
3011
|
+
entries.map((entry) => summarizeWorkspaceEntry(path10.join(dirPath, entry.name), entry))
|
|
2587
3012
|
);
|
|
2588
3013
|
for (const childSummary of childSummaries) {
|
|
2589
3014
|
summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
|
|
@@ -2602,7 +3027,7 @@ async function scanWorkspaceDirectories(dataDir) {
|
|
|
2602
3027
|
if (!entry.isDirectory()) {
|
|
2603
3028
|
return null;
|
|
2604
3029
|
}
|
|
2605
|
-
const dirPath =
|
|
3030
|
+
const dirPath = path10.join(dataDir, entry.name);
|
|
2606
3031
|
try {
|
|
2607
3032
|
const summary = await summarizeWorkspaceDirectory(dirPath);
|
|
2608
3033
|
return {
|
|
@@ -2634,7 +3059,7 @@ async function deleteWorkspaceDirectory(dataDir, directoryName) {
|
|
|
2634
3059
|
}
|
|
2635
3060
|
|
|
2636
3061
|
// src/agentProcessManager.ts
|
|
2637
|
-
var DATA_DIR =
|
|
3062
|
+
var DATA_DIR = path11.join(os4.homedir(), ".slock", "agents");
|
|
2638
3063
|
function toLocalTime(iso) {
|
|
2639
3064
|
const d = new Date(iso);
|
|
2640
3065
|
if (isNaN(d.getTime())) return iso;
|
|
@@ -2660,6 +3085,86 @@ function formatMessageTarget(message) {
|
|
|
2660
3085
|
function getMessageShortId(messageId) {
|
|
2661
3086
|
return messageId.startsWith("thread-") ? messageId.slice(7) : messageId.slice(0, 8);
|
|
2662
3087
|
}
|
|
3088
|
+
function findSessionJsonl(root, predicate) {
|
|
3089
|
+
let visited = 0;
|
|
3090
|
+
const maxEntries = 1e4;
|
|
3091
|
+
const maxDepth = 8;
|
|
3092
|
+
const visit = (dir, depth) => {
|
|
3093
|
+
if (depth < 0 || visited >= maxEntries) return null;
|
|
3094
|
+
let entries;
|
|
3095
|
+
try {
|
|
3096
|
+
entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
|
|
3097
|
+
} catch {
|
|
3098
|
+
return null;
|
|
3099
|
+
}
|
|
3100
|
+
for (const entry of entries) {
|
|
3101
|
+
if (++visited > maxEntries) return null;
|
|
3102
|
+
if (!entry.isFile() || !predicate(entry.name)) continue;
|
|
3103
|
+
return path11.join(dir, entry.name);
|
|
3104
|
+
}
|
|
3105
|
+
for (const entry of entries) {
|
|
3106
|
+
if (++visited > maxEntries) return null;
|
|
3107
|
+
if (!entry.isDirectory()) continue;
|
|
3108
|
+
const found = visit(path11.join(dir, entry.name), depth - 1);
|
|
3109
|
+
if (found) return found;
|
|
3110
|
+
}
|
|
3111
|
+
return null;
|
|
3112
|
+
};
|
|
3113
|
+
return visit(root, maxDepth);
|
|
3114
|
+
}
|
|
3115
|
+
function safeSessionFilename(value) {
|
|
3116
|
+
const normalized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3117
|
+
return normalized || "unknown-session";
|
|
3118
|
+
}
|
|
3119
|
+
function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
|
|
3120
|
+
try {
|
|
3121
|
+
const dir = path11.join(fallbackDir, ".slock", "runtime-sessions");
|
|
3122
|
+
mkdirSync4(dir, { recursive: true });
|
|
3123
|
+
const filePath = path11.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
|
|
3124
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
3125
|
+
type: "runtime_session_handoff",
|
|
3126
|
+
runtime,
|
|
3127
|
+
sessionId,
|
|
3128
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3129
|
+
note: "The native runtime transcript file was not found on this machine; this daemon-created handoff records the previous session identity for Runtime Profile migration."
|
|
3130
|
+
}) + "\n", { mode: 384 });
|
|
3131
|
+
return {
|
|
3132
|
+
label: sessionId,
|
|
3133
|
+
path: filePath,
|
|
3134
|
+
runtime,
|
|
3135
|
+
reachable: true,
|
|
3136
|
+
reason: "native session file path not found; using daemon handoff file"
|
|
3137
|
+
};
|
|
3138
|
+
} catch {
|
|
3139
|
+
return null;
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os4.homedir(), fallbackDir) {
|
|
3143
|
+
const directPath = path11.isAbsolute(sessionId) ? sessionId : null;
|
|
3144
|
+
if (directPath) {
|
|
3145
|
+
try {
|
|
3146
|
+
if (statSync(directPath).isFile()) {
|
|
3147
|
+
return { label: sessionId, path: directPath, runtime, reachable: true };
|
|
3148
|
+
}
|
|
3149
|
+
} catch {
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
const resolvedPath = runtime === "claude" ? findSessionJsonl(path11.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path11.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
|
|
3153
|
+
if (!resolvedPath && fallbackDir) {
|
|
3154
|
+
const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
|
|
3155
|
+
if (fallback) return fallback;
|
|
3156
|
+
}
|
|
3157
|
+
const ref = {
|
|
3158
|
+
label: sessionId,
|
|
3159
|
+
path: resolvedPath ?? sessionId,
|
|
3160
|
+
runtime,
|
|
3161
|
+
reachable: Boolean(resolvedPath)
|
|
3162
|
+
};
|
|
3163
|
+
if (!resolvedPath) {
|
|
3164
|
+
ref.reason = "session file path not found";
|
|
3165
|
+
}
|
|
3166
|
+
return ref;
|
|
3167
|
+
}
|
|
2663
3168
|
function formatSenderHandle(message) {
|
|
2664
3169
|
return message.sender_description ? `@${message.sender_name} \u2014 ${message.sender_description}` : `@${message.sender_name}`;
|
|
2665
3170
|
}
|
|
@@ -2675,12 +3180,38 @@ function formatThreadContextMessage(message) {
|
|
|
2675
3180
|
const senderType = formatVisibleActorType(message.sender_type);
|
|
2676
3181
|
return `- [msg=${msgId} time=${time}${senderType}] ${formatSenderHandle(message)}: ${message.content}`;
|
|
2677
3182
|
}
|
|
2678
|
-
function
|
|
3183
|
+
function mcpToolName(driver, name) {
|
|
3184
|
+
return `${driver?.mcpToolPrefix ?? ""}${name}`;
|
|
3185
|
+
}
|
|
3186
|
+
function communicationCommand(driver, name) {
|
|
3187
|
+
if (!driver?.usesSlockCliForCommunication) {
|
|
3188
|
+
return mcpToolName(driver, name);
|
|
3189
|
+
}
|
|
3190
|
+
switch (name) {
|
|
3191
|
+
case "send_message":
|
|
3192
|
+
return "slock message send";
|
|
3193
|
+
case "read_history":
|
|
3194
|
+
return "slock message read";
|
|
3195
|
+
case "check_messages":
|
|
3196
|
+
return "slock message check";
|
|
3197
|
+
case "claim_tasks":
|
|
3198
|
+
return "slock task claim";
|
|
3199
|
+
default:
|
|
3200
|
+
return `slock ${name.replace(/_/g, " ")}`;
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
function dynamicReplyInstruction(driver) {
|
|
3204
|
+
return driver?.usesSlockCliForCommunication ? "reply using `slock message send --target <exact target>`" : `reply with ${communicationCommand(driver, "send_message")}`;
|
|
3205
|
+
}
|
|
3206
|
+
function dynamicClaimInstruction(driver) {
|
|
3207
|
+
return driver?.usesSlockCliForCommunication ? "claim the relevant task with `slock task claim`" : `claim the relevant task with ${communicationCommand(driver, "claim_tasks")}`;
|
|
3208
|
+
}
|
|
3209
|
+
function formatIncomingMessage(message, driver) {
|
|
2679
3210
|
const threadJoinPrefix = message.thread_join_context ? [
|
|
2680
3211
|
`[System: You were added to a new thread via @mention. Read this context before replying.]`,
|
|
2681
3212
|
`parent: ${message.thread_join_context.parent_target}`,
|
|
2682
3213
|
`thread: ${message.thread_join_context.thread_target}`,
|
|
2683
|
-
`suggested next step: read_history(channel="${message.thread_join_context.suggested_read_history_target}")`,
|
|
3214
|
+
`suggested next step: ${driver?.usesSlockCliForCommunication ? `slock message read --channel "${message.thread_join_context.suggested_read_history_target}"` : `${communicationCommand(driver, "read_history")}(channel="${message.thread_join_context.suggested_read_history_target}")`}`,
|
|
2684
3215
|
"",
|
|
2685
3216
|
"Parent message:",
|
|
2686
3217
|
formatThreadContextMessage(message.thread_join_context.parent_message),
|
|
@@ -2699,6 +3230,42 @@ function formatIncomingMessage(message) {
|
|
|
2699
3230
|
return threadJoinPrefix ? `${threadJoinPrefix}
|
|
2700
3231
|
${body}` : body;
|
|
2701
3232
|
}
|
|
3233
|
+
function formatRuntimeProfileControlPrompt(messages) {
|
|
3234
|
+
const controls = messages.map((message) => ({
|
|
3235
|
+
message,
|
|
3236
|
+
notification: runtimeProfileNotificationFromMessage(message)
|
|
3237
|
+
}));
|
|
3238
|
+
if (controls.length === 0 || controls.some(({ notification }) => !notification)) {
|
|
3239
|
+
return null;
|
|
3240
|
+
}
|
|
3241
|
+
const body = controls.map(({ message }) => message.content).join("\n\n---\n\n");
|
|
3242
|
+
return [
|
|
3243
|
+
"Runtime Profile control notice.",
|
|
3244
|
+
"",
|
|
3245
|
+
"Complete the required runtime control action before reading or responding to normal inbox messages.",
|
|
3246
|
+
"",
|
|
3247
|
+
body,
|
|
3248
|
+
"",
|
|
3249
|
+
"Do not answer this notice in prose as the acknowledgment."
|
|
3250
|
+
].join("\n");
|
|
3251
|
+
}
|
|
3252
|
+
function formatRuntimeProfileControlStartupInput(control, driver) {
|
|
3253
|
+
if (control.kind !== "migration") {
|
|
3254
|
+
return [
|
|
3255
|
+
"Read the Runtime Profile daemon release notice from your system prompt before normal work.",
|
|
3256
|
+
"No chat reply is required for this notice. Stop after reading it; queued inbox messages will be delivered separately."
|
|
3257
|
+
].join("\n");
|
|
3258
|
+
}
|
|
3259
|
+
const actionName = `${driver.mcpToolPrefix}runtime_profile_migration_done`;
|
|
3260
|
+
return [
|
|
3261
|
+
"Runtime Profile migration is required before normal work.",
|
|
3262
|
+
`Invoke the available runtime control action \`${actionName}\` with exactly this migration_key: \`${control.key}\`.`,
|
|
3263
|
+
"Do not read MEMORY.md, check messages, or send a chat reply before this tool call.",
|
|
3264
|
+
"After the runtime control action succeeds, stop. Queued inbox messages will be delivered separately.",
|
|
3265
|
+
"",
|
|
3266
|
+
control.message
|
|
3267
|
+
].join("\n");
|
|
3268
|
+
}
|
|
2702
3269
|
function buildUnreadSummary(messages, excludeChannel) {
|
|
2703
3270
|
const summary = /* @__PURE__ */ new Map();
|
|
2704
3271
|
for (const message of messages) {
|
|
@@ -2723,6 +3290,16 @@ var FIRST_CINDY_SEED_MODE = "first-cindy";
|
|
|
2723
3290
|
function getOnboardingSeedMode(config) {
|
|
2724
3291
|
return (config.envVars?.[ONBOARDING_MEMORY_SEED_ENV] || "").trim().toLowerCase();
|
|
2725
3292
|
}
|
|
3293
|
+
function withLocalRuntimeContext(config, agentId, workspacePath) {
|
|
3294
|
+
return {
|
|
3295
|
+
...config,
|
|
3296
|
+
runtimeContext: {
|
|
3297
|
+
...config.runtimeContext ?? {},
|
|
3298
|
+
agentId: config.runtimeContext?.agentId ?? agentId,
|
|
3299
|
+
workspacePath
|
|
3300
|
+
}
|
|
3301
|
+
};
|
|
3302
|
+
}
|
|
2726
3303
|
function buildOnboardingPlaybookMd() {
|
|
2727
3304
|
return `# Cindy Onboarding Playbook
|
|
2728
3305
|
|
|
@@ -2731,14 +3308,18 @@ Start warm and brief.
|
|
|
2731
3308
|
Move quickly to one useful action, not a feature tour.
|
|
2732
3309
|
Keep activation energy low: invite the user to start with one sentence about what they need now.
|
|
2733
3310
|
|
|
2734
|
-
## Step 2:
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
3311
|
+
## Step 2: Activate or Propose
|
|
3312
|
+
Use one decision: does the user already know what they want to do?
|
|
3313
|
+
- Yes: skip role/work intake and propose a starter plan.
|
|
3314
|
+
- No: ask what they do and what they are working on. These questions are activation, not a questionnaire.
|
|
3315
|
+
|
|
3316
|
+
After any usable signal, stop asking and propose.
|
|
3317
|
+
After confirming language preference, do not give a generic product introduction; move into the user's work or a starter action.
|
|
2738
3318
|
|
|
2739
3319
|
## Step 3: Route by Intent (A-E)
|
|
2740
3320
|
- A: Specific project/task
|
|
2741
|
-
-
|
|
3321
|
+
- Enter starter-task mode immediately.
|
|
3322
|
+
- Propose first setup actions before asking for more detail.
|
|
2742
3323
|
- B: "What can you do?" curiosity
|
|
2743
3324
|
- Proactively share 1-2 interview-grounded examples, then ask the user to pick one.
|
|
2744
3325
|
- Use this opener tone: "Here are some examples our users have shared with us. I'm sharing these to inspire you."
|
|
@@ -2749,6 +3330,28 @@ Ask only what is needed to route:
|
|
|
2749
3330
|
- E: Low-intent greeting/testing
|
|
2750
3331
|
- Use a low-pressure prompt and guide to one concrete starter action.
|
|
2751
3332
|
|
|
3333
|
+
### Starter Plan Output
|
|
3334
|
+
A starter plan should make the next action executable, not just descriptive:
|
|
3335
|
+
- agent name + role description that can be copied into the create-agent form
|
|
3336
|
+
- suggested channel or workstream
|
|
3337
|
+
- first task to send after creation
|
|
3338
|
+
- next UI action if the user needs to create an agent or channel
|
|
3339
|
+
|
|
3340
|
+
Do not use a rigid keyword routing table. Use examples as inspiration, then adapt to the user's context.
|
|
3341
|
+
If details are missing but not blocking, state reasonable defaults and invite correction.
|
|
3342
|
+
Only ask one blocking question first if the answer is required before any useful starter plan can be proposed.
|
|
3343
|
+
Do not imply you have already created agents or channels unless the action has actually happened.
|
|
3344
|
+
|
|
3345
|
+
### Capability Boundary Pivot
|
|
3346
|
+
If the user's primary request is outside current capabilities, acknowledge the limitation once and pivot immediately to the nearest useful alternative.
|
|
3347
|
+
Do not repeat that something is impossible across multiple turns.
|
|
3348
|
+
Offer a concrete substitute: a manual input path, a narrower analysis task, an agent/team setup, or another workflow Slock can execute now.
|
|
3349
|
+
|
|
3350
|
+
### Active-Elsewhere Handoff
|
|
3351
|
+
Channel silence is not failure.
|
|
3352
|
+
If the user is already active outside the onboarding channel, follow the work instead of trying to pull them back.
|
|
3353
|
+
Offer a concrete next step in the context they are using: first task, second agent suggestion, channel structure, or reminder.
|
|
3354
|
+
|
|
2752
3355
|
## Step 4: Progress Setup (Soft Guidance)
|
|
2753
3356
|
While helping with real work, progressively shape:
|
|
2754
3357
|
- initial team target >= 3 agents
|
|
@@ -2762,6 +3365,8 @@ Do not force setup before value.
|
|
|
2762
3365
|
|
|
2763
3366
|
## Step 5: End Every Turn with One Next Step
|
|
2764
3367
|
Each reply should end with one clear, immediate action.
|
|
3368
|
+
At wrap-up, if there is a concrete next check-in, ask consent to set one contextual reminder.
|
|
3369
|
+
The reminder must reference the user's goal, agent, recent step, or suggested next action; do not send generic "come back later" reminders.
|
|
2765
3370
|
|
|
2766
3371
|
## Inspiration Stories (Interview-Grounded)
|
|
2767
3372
|
- Story 1: "Sense of abundance" \u2014 agents self-organize, you do not need to micro-manage.
|
|
@@ -2954,6 +3559,34 @@ Do not copy these answers verbatim.
|
|
|
2954
3559
|
|
|
2955
3560
|
### Guardrail
|
|
2956
3561
|
- Keep contact guidance concrete and current; do not invent alternative support channels.
|
|
3562
|
+
|
|
3563
|
+
## FAQ 14: Can I use Slock on my phone?
|
|
3564
|
+
### Answer idea
|
|
3565
|
+
- Yes. Slock can be used from a mobile browser.
|
|
3566
|
+
- For easier return access, users can add Slock to their phone home screen as a web app:
|
|
3567
|
+
- iPhone: Safari \u2192 Share \u2192 Add to Home Screen
|
|
3568
|
+
- Android: Chrome \u2192 menu \u2192 Add to Home Screen / Install app
|
|
3569
|
+
- Good mobile use cases: quick check-ins, todos/reminders, short replies, and reviewing agent updates.
|
|
3570
|
+
|
|
3571
|
+
### Next step
|
|
3572
|
+
- If the user wants mobile access now, ask whether they use iPhone or Android, then guide the matching Add to Home Screen step.
|
|
3573
|
+
|
|
3574
|
+
### Guardrail
|
|
3575
|
+
- Do not imply Slock has a native iOS/Android App Store app.
|
|
3576
|
+
- Do not over-sell it as fully equivalent to a native app; call it mobile browser / home-screen web app.
|
|
3577
|
+
|
|
3578
|
+
## FAQ 15: How do I create agents or channels?
|
|
3579
|
+
### Answer idea
|
|
3580
|
+
- The user can create agents from the Agents section in the People tab by clicking the + button, or from a computer/machine context menu via Create Agent.
|
|
3581
|
+
- The user can create channels from the Channels section by clicking the + button.
|
|
3582
|
+
- When recommending setup, provide copyable agent names/descriptions, suggested channel names, and the first task to send after creation.
|
|
3583
|
+
|
|
3584
|
+
### Next step
|
|
3585
|
+
- Give the exact agent/channel spec the user can copy, then guide them to the relevant + button or creation dialog.
|
|
3586
|
+
|
|
3587
|
+
### Guardrail
|
|
3588
|
+
- Do not imply you already created agents or channels unless that action actually happened.
|
|
3589
|
+
- If you cannot directly create something, avoid a long permissions explanation; give the copyable spec and the next UI action.
|
|
2957
3590
|
`;
|
|
2958
3591
|
}
|
|
2959
3592
|
function buildOnboardingSeedFiles() {
|
|
@@ -3112,9 +3745,20 @@ function classifyTerminalFailure(ap) {
|
|
|
3112
3745
|
return null;
|
|
3113
3746
|
}
|
|
3114
3747
|
function isMissingResumeSession(ap) {
|
|
3115
|
-
if (ap.driver.id !== "claude") return false;
|
|
3116
3748
|
if (!ap.sessionId) return false;
|
|
3117
|
-
|
|
3749
|
+
const candidates = [
|
|
3750
|
+
ap.lastRuntimeError,
|
|
3751
|
+
...ap.recentStderr
|
|
3752
|
+
].filter((value) => !!value);
|
|
3753
|
+
if (ap.driver.id === "claude") {
|
|
3754
|
+
return candidates.some((text) => /No conversation found with session ID/i.test(text));
|
|
3755
|
+
}
|
|
3756
|
+
if (ap.driver.id === "opencode") {
|
|
3757
|
+
return candidates.some(
|
|
3758
|
+
(text) => /Session not found/i.test(text) && text.includes(ap.sessionId)
|
|
3759
|
+
);
|
|
3760
|
+
}
|
|
3761
|
+
return false;
|
|
3118
3762
|
}
|
|
3119
3763
|
function getMessageDeliveryText(driver) {
|
|
3120
3764
|
return driver.supportsStdinNotification ? "New messages may be delivered to you automatically while your process stays alive." : "The daemon will automatically restart you when new messages arrive.";
|
|
@@ -3129,7 +3773,12 @@ function getBusyDeliveryNote(driver) {
|
|
|
3129
3773
|
}
|
|
3130
3774
|
return "\n\nNote: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call check_messages to check for messages.";
|
|
3131
3775
|
}
|
|
3132
|
-
var NATIVE_STANDING_PROMPT_STARTUP_INPUT =
|
|
3776
|
+
var NATIVE_STANDING_PROMPT_STARTUP_INPUT = (
|
|
3777
|
+
// Claude Code 2.1.114 treats "follow your system prompt" style user turns as
|
|
3778
|
+
// prompt-injection bait even when the prompt is supplied via --system-prompt-file.
|
|
3779
|
+
// A neutral starter lets the native standing prompt drive startup/migration.
|
|
3780
|
+
"Start."
|
|
3781
|
+
);
|
|
3133
3782
|
var AgentProcessManager = class _AgentProcessManager {
|
|
3134
3783
|
agents = /* @__PURE__ */ new Map();
|
|
3135
3784
|
agentsStarting = /* @__PURE__ */ new Set();
|
|
@@ -3143,6 +3792,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
3143
3792
|
daemonApiKey;
|
|
3144
3793
|
serverUrl;
|
|
3145
3794
|
dataDir;
|
|
3795
|
+
runtimeSessionHomeDir;
|
|
3146
3796
|
driverResolver;
|
|
3147
3797
|
defaultAgentEnvVarsProvider;
|
|
3148
3798
|
tracer;
|
|
@@ -3153,6 +3803,7 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
3153
3803
|
this.daemonApiKey = daemonApiKey;
|
|
3154
3804
|
this.serverUrl = opts.serverUrl;
|
|
3155
3805
|
this.dataDir = opts.dataDir || DATA_DIR;
|
|
3806
|
+
this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os4.homedir();
|
|
3156
3807
|
this.driverResolver = opts.driverResolver || getDriver;
|
|
3157
3808
|
this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
|
|
3158
3809
|
this.tracer = opts.tracer ?? noopTracer;
|
|
@@ -3169,41 +3820,45 @@ var AgentProcessManager = class _AgentProcessManager {
|
|
|
3169
3820
|
this.agentsStarting.add(agentId);
|
|
3170
3821
|
try {
|
|
3171
3822
|
const driver = this.driverResolver(config.runtime || "claude");
|
|
3172
|
-
const agentDataDir =
|
|
3823
|
+
const agentDataDir = path11.join(this.dataDir, agentId);
|
|
3173
3824
|
await mkdir(agentDataDir, { recursive: true });
|
|
3174
|
-
const
|
|
3825
|
+
const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
|
|
3826
|
+
const memoryMdPath = path11.join(agentDataDir, "MEMORY.md");
|
|
3175
3827
|
try {
|
|
3176
3828
|
await access(memoryMdPath);
|
|
3177
3829
|
} catch {
|
|
3178
|
-
const initialMemoryMd = buildInitialMemoryMd(
|
|
3830
|
+
const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
|
|
3179
3831
|
await writeFile(memoryMdPath, initialMemoryMd);
|
|
3180
3832
|
}
|
|
3181
|
-
const notesDir =
|
|
3833
|
+
const notesDir = path11.join(agentDataDir, "notes");
|
|
3182
3834
|
await mkdir(notesDir, { recursive: true });
|
|
3183
3835
|
if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
|
|
3184
3836
|
const seedFiles = buildOnboardingSeedFiles();
|
|
3185
3837
|
for (const { relativePath, content } of seedFiles) {
|
|
3186
|
-
const fullPath =
|
|
3838
|
+
const fullPath = path11.join(agentDataDir, relativePath);
|
|
3187
3839
|
try {
|
|
3188
3840
|
await access(fullPath);
|
|
3189
3841
|
} catch {
|
|
3190
|
-
await mkdir(
|
|
3842
|
+
await mkdir(path11.dirname(fullPath), { recursive: true });
|
|
3191
3843
|
await writeFile(fullPath, content);
|
|
3192
3844
|
}
|
|
3193
3845
|
}
|
|
3194
3846
|
}
|
|
3195
|
-
const isResume = !!
|
|
3196
|
-
const standingPrompt = driver.buildSystemPrompt(
|
|
3847
|
+
const isResume = !!runtimeConfig.sessionId;
|
|
3848
|
+
const standingPrompt = driver.buildSystemPrompt(runtimeConfig, agentId);
|
|
3197
3849
|
let prompt;
|
|
3198
|
-
if (
|
|
3850
|
+
if (runtimeConfig.runtimeProfileControl && !wakeMessage) {
|
|
3851
|
+
prompt = driver.supportsNativeStandingPrompt ? NATIVE_STANDING_PROMPT_STARTUP_INPUT : formatRuntimeProfileControlStartupInput(runtimeConfig.runtimeProfileControl, driver);
|
|
3852
|
+
} else if (isResume && resumePrompt) {
|
|
3199
3853
|
prompt = resumePrompt;
|
|
3200
3854
|
prompt += getBusyDeliveryNote(driver);
|
|
3201
3855
|
} else if (wakeMessage) {
|
|
3856
|
+
const runtimeProfileControlPrompt = formatRuntimeProfileControlPrompt([wakeMessage]);
|
|
3202
3857
|
const channelLabel = formatChannelLabel(wakeMessage);
|
|
3203
|
-
prompt = `New message received:
|
|
3858
|
+
prompt = runtimeProfileControlPrompt ?? `New message received:
|
|
3204
3859
|
|
|
3205
|
-
${formatIncomingMessage(wakeMessage)}`;
|
|
3206
|
-
if (unreadSummary && Object.keys(unreadSummary).length > 0) {
|
|
3860
|
+
${formatIncomingMessage(wakeMessage, driver)}`;
|
|
3861
|
+
if (!runtimeProfileControlPrompt && unreadSummary && Object.keys(unreadSummary).length > 0) {
|
|
3207
3862
|
const otherUnread = Object.entries(unreadSummary).filter(([key]) => key !== channelLabel);
|
|
3208
3863
|
if (otherUnread.length > 0) {
|
|
3209
3864
|
prompt += `
|
|
@@ -3215,15 +3870,17 @@ You also have unread messages in other channels:`;
|
|
|
3215
3870
|
}
|
|
3216
3871
|
prompt += `
|
|
3217
3872
|
|
|
3218
|
-
Use read_history to catch up, or respond to the message above first.`;
|
|
3873
|
+
Use ${communicationCommand(driver, "read_history")} to catch up, or respond to the message above first.`;
|
|
3219
3874
|
}
|
|
3220
3875
|
}
|
|
3221
|
-
|
|
3876
|
+
if (!runtimeProfileControlPrompt) {
|
|
3877
|
+
prompt += `
|
|
3222
3878
|
|
|
3223
|
-
Respond as appropriate \u2014
|
|
3879
|
+
Respond as appropriate \u2014 ${dynamicReplyInstruction(driver)}, or take action as needed. Complete ALL your work before stopping.
|
|
3224
3880
|
|
|
3225
3881
|
IMPORTANT: If the message requires multi-step work (e.g. research, code changes, testing), complete ALL steps before stopping. Sending a progress update does NOT mean your task is done \u2014 only stop when you have NO more work to do. ${getMessageDeliveryText(driver)}`;
|
|
3226
|
-
|
|
3882
|
+
prompt += getBusyDeliveryNote(driver);
|
|
3883
|
+
}
|
|
3227
3884
|
} else if (isResume && unreadSummary && Object.keys(unreadSummary).length > 0) {
|
|
3228
3885
|
prompt = `You have unread messages from while you were offline:`;
|
|
3229
3886
|
for (const [ch, count] of Object.entries(unreadSummary)) {
|
|
@@ -3232,14 +3889,32 @@ IMPORTANT: If the message requires multi-step work (e.g. research, code changes,
|
|
|
3232
3889
|
}
|
|
3233
3890
|
prompt += `
|
|
3234
3891
|
|
|
3235
|
-
Use read_history to catch up on the channels listed above, then stop. Read each listed channel at most once unless a read fails. Do NOT call check_messages in this mode. If the history reveals a direct request, assignment, @mention, review request, or task clearly addressed to you, switch into active handling instead of stopping:
|
|
3892
|
+
Use ${communicationCommand(driver, "read_history")} to catch up on the channels listed above, then stop. Read each listed channel at most once unless a read fails. Do NOT call ${communicationCommand(driver, "check_messages")} in this mode. If the history reveals a direct request, assignment, @mention, review request, or task clearly addressed to you, switch into active handling instead of stopping: ${dynamicReplyInstruction(driver)} and ${dynamicClaimInstruction(driver)} before starting work. Otherwise, do NOT send any message in this mode. ${getMessageDeliveryText(driver)}`;
|
|
3236
3893
|
} else if (isResume) {
|
|
3237
3894
|
prompt = `No new messages while you were away. Nothing to do \u2014 just stop. ${getMessageDeliveryText(driver)}`;
|
|
3238
3895
|
prompt += getBusyDeliveryNote(driver);
|
|
3239
3896
|
} else {
|
|
3240
3897
|
prompt = driver.supportsNativeStandingPrompt ? NATIVE_STANDING_PROMPT_STARTUP_INPUT : standingPrompt;
|
|
3241
3898
|
}
|
|
3242
|
-
const effectiveConfig = await this.buildSpawnConfig(agentId,
|
|
3899
|
+
const effectiveConfig = await this.buildSpawnConfig(agentId, runtimeConfig);
|
|
3900
|
+
const canDeferEmptyStart = driver.deferSpawnUntilMessage === true && !wakeMessage && !runtimeConfig.runtimeProfileControl && (!unreadSummary || Object.keys(unreadSummary).length === 0);
|
|
3901
|
+
if (canDeferEmptyStart) {
|
|
3902
|
+
const pendingMessages = this.startingInboxes.get(agentId) || [];
|
|
3903
|
+
this.startingInboxes.delete(agentId);
|
|
3904
|
+
this.agentsStarting.delete(agentId);
|
|
3905
|
+
this.idleAgentConfigs.set(agentId, {
|
|
3906
|
+
config: effectiveConfig,
|
|
3907
|
+
sessionId: effectiveConfig.sessionId || null,
|
|
3908
|
+
launchId: launchId || null
|
|
3909
|
+
});
|
|
3910
|
+
this.sendAgentStatus(agentId, "active", launchId || null);
|
|
3911
|
+
this.broadcastActivity(agentId, "online", "Process idle");
|
|
3912
|
+
logger.info(`[Agent ${agentId}] Deferred ${driver.id} spawn until first concrete message`);
|
|
3913
|
+
for (const message of pendingMessages) {
|
|
3914
|
+
this.deliverMessage(agentId, message);
|
|
3915
|
+
}
|
|
3916
|
+
return;
|
|
3917
|
+
}
|
|
3243
3918
|
const { process: proc } = driver.spawn({
|
|
3244
3919
|
agentId,
|
|
3245
3920
|
config: effectiveConfig,
|
|
@@ -3255,9 +3930,12 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3255
3930
|
process: proc,
|
|
3256
3931
|
driver,
|
|
3257
3932
|
inbox: this.startingInboxes.get(agentId) || [],
|
|
3258
|
-
config,
|
|
3259
|
-
sessionId:
|
|
3933
|
+
config: runtimeConfig,
|
|
3934
|
+
sessionId: runtimeConfig.sessionId || null,
|
|
3260
3935
|
launchId: launchId || null,
|
|
3936
|
+
startupWakeMessage: wakeMessage,
|
|
3937
|
+
startupUnreadSummary: unreadSummary,
|
|
3938
|
+
startupResumePrompt: resumePrompt,
|
|
3261
3939
|
isIdle: false,
|
|
3262
3940
|
notificationTimer: null,
|
|
3263
3941
|
pendingNotificationCount: 0,
|
|
@@ -3275,6 +3953,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3275
3953
|
spawnError: null,
|
|
3276
3954
|
exitCode: null,
|
|
3277
3955
|
exitSignal: null,
|
|
3956
|
+
expectedTerminationReason: null,
|
|
3278
3957
|
pendingTrajectory: null,
|
|
3279
3958
|
gatedSteering: createGatedSteeringState()
|
|
3280
3959
|
};
|
|
@@ -3282,6 +3961,9 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3282
3961
|
this.agents.set(agentId, agentProcess);
|
|
3283
3962
|
this.startRuntimeTrace(agentId, agentProcess, "spawn");
|
|
3284
3963
|
this.agentsStarting.delete(agentId);
|
|
3964
|
+
if (config.runtimeProfileControl) {
|
|
3965
|
+
this.ackInjectedRuntimeProfileControl(agentId, config.runtimeProfileControl, agentProcess.launchId);
|
|
3966
|
+
}
|
|
3285
3967
|
if (wakeMessage) {
|
|
3286
3968
|
this.ackInjectedRuntimeProfileMessages(agentId, [wakeMessage], agentProcess.launchId);
|
|
3287
3969
|
}
|
|
@@ -3341,20 +4023,60 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3341
4023
|
}
|
|
3342
4024
|
const finalCode = ap.exitCode ?? code;
|
|
3343
4025
|
const finalSignal = ap.exitSignal ?? signal;
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
|
|
4026
|
+
const expectedTermination = Boolean(ap.expectedTerminationReason);
|
|
4027
|
+
const processEndedCleanly = finalCode === 0 || expectedTermination && !ap.lastRuntimeError;
|
|
4028
|
+
const terminalFailureDetail = processEndedCleanly ? null : classifyTerminalFailure(ap);
|
|
4029
|
+
const missingResumeSession = isMissingResumeSession(ap);
|
|
4030
|
+
const summary = summarizeCrash(finalCode, finalSignal);
|
|
4031
|
+
this.endRuntimeTrace(ap, processEndedCleanly ? "ok" : "error", {
|
|
4032
|
+
outcome: processEndedCleanly ? "process-exit" : "process-crash",
|
|
4033
|
+
expectedTerminationReason: ap.expectedTerminationReason || void 0,
|
|
3347
4034
|
exitCode: finalCode,
|
|
3348
4035
|
exitSignal: finalSignal
|
|
3349
4036
|
});
|
|
3350
|
-
if (
|
|
4037
|
+
if (processEndedCleanly) {
|
|
3351
4038
|
this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from process exit)");
|
|
3352
4039
|
} else {
|
|
3353
4040
|
this.clearCompactionWatchdog(ap);
|
|
3354
4041
|
}
|
|
3355
4042
|
this.agents.delete(agentId);
|
|
3356
|
-
if (
|
|
3357
|
-
const
|
|
4043
|
+
if (missingResumeSession) {
|
|
4044
|
+
const staleSessionId = ap.sessionId;
|
|
4045
|
+
const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : "Claude";
|
|
4046
|
+
const restartConfig = { ...ap.config, sessionId: null };
|
|
4047
|
+
logger.warn(
|
|
4048
|
+
`[Agent ${agentId}] Stored ${runtimeLabel} session ${staleSessionId} is unavailable locally; falling back to cold start`
|
|
4049
|
+
);
|
|
4050
|
+
this.broadcastActivity(
|
|
4051
|
+
agentId,
|
|
4052
|
+
"working",
|
|
4053
|
+
`Stored ${runtimeLabel} session missing; cold-starting a new session\u2026`,
|
|
4054
|
+
[{ kind: "text", text: `Stored ${runtimeLabel} session ${staleSessionId} was not found locally. Falling back to a cold start.` }]
|
|
4055
|
+
);
|
|
4056
|
+
this.startAgent(
|
|
4057
|
+
agentId,
|
|
4058
|
+
restartConfig,
|
|
4059
|
+
ap.startupWakeMessage,
|
|
4060
|
+
ap.startupUnreadSummary,
|
|
4061
|
+
ap.startupResumePrompt,
|
|
4062
|
+
ap.launchId || void 0
|
|
4063
|
+
).catch((err) => {
|
|
4064
|
+
logger.error(`[Agent ${agentId}] Cold start recovery failed`, err);
|
|
4065
|
+
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
4066
|
+
this.broadcastActivity(agentId, "offline", `Crashed (${summary})`, [], ap.launchId);
|
|
4067
|
+
});
|
|
4068
|
+
return;
|
|
4069
|
+
}
|
|
4070
|
+
if (processEndedCleanly) {
|
|
4071
|
+
let queuedWakeMessage;
|
|
4072
|
+
if (!ap.driver.supportsStdinNotification) {
|
|
4073
|
+
while (ap.inbox.length > 0) {
|
|
4074
|
+
const candidate = ap.inbox.shift();
|
|
4075
|
+
if (this.shouldDeferWakeMessage(agentId, ap.driver, candidate)) continue;
|
|
4076
|
+
queuedWakeMessage = candidate;
|
|
4077
|
+
break;
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
3358
4080
|
const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
|
|
3359
4081
|
if (queuedWakeMessage) {
|
|
3360
4082
|
logger.info(`[Agent ${agentId}] Turn completed; restarting immediately for queued message`);
|
|
@@ -3389,26 +4111,6 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3389
4111
|
} else {
|
|
3390
4112
|
this.idleAgentConfigs.delete(agentId);
|
|
3391
4113
|
const reason = formatCrashReason(finalCode, finalSignal, ap);
|
|
3392
|
-
const summary = summarizeCrash(finalCode, finalSignal);
|
|
3393
|
-
if (isMissingResumeSession(ap)) {
|
|
3394
|
-
const staleSessionId = ap.sessionId;
|
|
3395
|
-
const restartConfig = { ...ap.config, sessionId: null };
|
|
3396
|
-
logger.warn(
|
|
3397
|
-
`[Agent ${agentId}] Stored Claude session ${staleSessionId} is unavailable locally; falling back to cold start`
|
|
3398
|
-
);
|
|
3399
|
-
this.broadcastActivity(
|
|
3400
|
-
agentId,
|
|
3401
|
-
"working",
|
|
3402
|
-
"Stored Claude session missing; cold-starting a new session\u2026",
|
|
3403
|
-
[{ kind: "text", text: `Stored Claude session ${staleSessionId} was not found locally. Falling back to a cold start.` }]
|
|
3404
|
-
);
|
|
3405
|
-
this.startAgent(agentId, restartConfig, void 0, void 0, void 0, ap.launchId || void 0).catch((err) => {
|
|
3406
|
-
logger.error(`[Agent ${agentId}] Cold start recovery failed`, err);
|
|
3407
|
-
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
3408
|
-
this.broadcastActivity(agentId, "offline", `Crashed (${summary})`, [], ap.launchId);
|
|
3409
|
-
});
|
|
3410
|
-
return;
|
|
3411
|
-
}
|
|
3412
4114
|
logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
|
|
3413
4115
|
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
3414
4116
|
if (terminalFailureDetail) {
|
|
@@ -3492,6 +4194,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3492
4194
|
this.agents.delete(agentId);
|
|
3493
4195
|
ap.process.kill("SIGTERM");
|
|
3494
4196
|
if (!silent) {
|
|
4197
|
+
this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId);
|
|
3495
4198
|
this.sendAgentStatus(agentId, "inactive", ap.launchId);
|
|
3496
4199
|
this.broadcastActivity(agentId, "offline", "Stopped");
|
|
3497
4200
|
logger.info(`[Agent ${agentId}] Stopped by request`);
|
|
@@ -3530,6 +4233,10 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3530
4233
|
}
|
|
3531
4234
|
const cached = this.idleAgentConfigs.get(agentId);
|
|
3532
4235
|
if (cached) {
|
|
4236
|
+
const driver = this.driverResolver(cached.config.runtime || "claude");
|
|
4237
|
+
if (this.shouldDeferWakeMessage(agentId, driver, message)) {
|
|
4238
|
+
return;
|
|
4239
|
+
}
|
|
3533
4240
|
logger.info(`[Agent ${agentId}] Starting from idle state for new message`);
|
|
3534
4241
|
this.idleAgentConfigs.delete(agentId);
|
|
3535
4242
|
this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).catch((err) => {
|
|
@@ -3538,6 +4245,9 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3538
4245
|
}
|
|
3539
4246
|
return;
|
|
3540
4247
|
}
|
|
4248
|
+
if (this.shouldDeferWakeMessage(agentId, ap.driver, message)) {
|
|
4249
|
+
return;
|
|
4250
|
+
}
|
|
3541
4251
|
if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
3542
4252
|
const nextMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
3543
4253
|
nextMessages.push(message);
|
|
@@ -3566,7 +4276,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3566
4276
|
}
|
|
3567
4277
|
}
|
|
3568
4278
|
async resetWorkspace(agentId) {
|
|
3569
|
-
const agentDataDir =
|
|
4279
|
+
const agentDataDir = path11.join(this.dataDir, agentId);
|
|
3570
4280
|
try {
|
|
3571
4281
|
await rm2(agentDataDir, { recursive: true, force: true });
|
|
3572
4282
|
logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
|
|
@@ -3582,6 +4292,11 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3582
4292
|
getRunningAgentIds() {
|
|
3583
4293
|
return [...this.agents.keys()];
|
|
3584
4294
|
}
|
|
4295
|
+
shouldDeferWakeMessage(agentId, driver, message) {
|
|
4296
|
+
if (!driver.shouldDeferWakeMessage?.(message)) return false;
|
|
4297
|
+
logger.info(`[Agent ${agentId}] Deferred non-concrete wake message for ${driver.id}`);
|
|
4298
|
+
return true;
|
|
4299
|
+
}
|
|
3585
4300
|
getAgentSessionId(agentId) {
|
|
3586
4301
|
return this.agents.get(agentId)?.sessionId ?? null;
|
|
3587
4302
|
}
|
|
@@ -3596,7 +4311,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3596
4311
|
return result;
|
|
3597
4312
|
}
|
|
3598
4313
|
buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
|
|
3599
|
-
const workspacePath =
|
|
4314
|
+
const workspacePath = path11.join(this.dataDir, agentId);
|
|
3600
4315
|
return {
|
|
3601
4316
|
agentId,
|
|
3602
4317
|
launchId,
|
|
@@ -3610,12 +4325,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3610
4325
|
path: workspacePath,
|
|
3611
4326
|
reachable: true
|
|
3612
4327
|
},
|
|
3613
|
-
sessionRef: sessionId ?
|
|
3614
|
-
label: sessionId,
|
|
3615
|
-
path: sessionId,
|
|
3616
|
-
runtime: config.runtime,
|
|
3617
|
-
reachable: true
|
|
3618
|
-
} : null
|
|
4328
|
+
sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, this.runtimeSessionHomeDir, workspacePath) : null
|
|
3619
4329
|
}
|
|
3620
4330
|
};
|
|
3621
4331
|
}
|
|
@@ -3706,9 +4416,26 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3706
4416
|
}
|
|
3707
4417
|
}
|
|
3708
4418
|
}
|
|
3709
|
-
|
|
3710
|
-
const
|
|
3711
|
-
|
|
4419
|
+
ackInjectedRuntimeProfileControl(agentId, control, launchId) {
|
|
4420
|
+
const title = runtimeProfileNotificationTitle(control.kind);
|
|
4421
|
+
this.broadcastActivity(agentId, "working", title, [{ kind: "system", title, text: control.message }], launchId);
|
|
4422
|
+
if (control.kind === "migration") {
|
|
4423
|
+
this.sendToServer({
|
|
4424
|
+
type: "agent:runtime_profile:migration:ack",
|
|
4425
|
+
agentId,
|
|
4426
|
+
migrationKey: control.key,
|
|
4427
|
+
launchId: launchId || void 0
|
|
4428
|
+
});
|
|
4429
|
+
} else {
|
|
4430
|
+
this.sendToServer({
|
|
4431
|
+
type: "agent:runtime_profile:daemon_release_notice:ack",
|
|
4432
|
+
agentId,
|
|
4433
|
+
noticeKey: control.key,
|
|
4434
|
+
launchId: launchId || void 0
|
|
4435
|
+
});
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
sendRuntimeProfileWireReport(report) {
|
|
3712
4439
|
this.sendToServer({
|
|
3713
4440
|
type: "agent:runtime_profile",
|
|
3714
4441
|
agentId: report.agentId,
|
|
@@ -3716,6 +4443,14 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3716
4443
|
launchId: report.launchId || void 0
|
|
3717
4444
|
});
|
|
3718
4445
|
}
|
|
4446
|
+
sendRuntimeProfileReportFor(agentId, config, sessionId, launchId) {
|
|
4447
|
+
this.sendRuntimeProfileWireReport(this.buildRuntimeProfileReport(agentId, config, sessionId, launchId));
|
|
4448
|
+
}
|
|
4449
|
+
sendRuntimeProfileReport(agentId) {
|
|
4450
|
+
const report = this.getAgentRuntimeProfileReport(agentId);
|
|
4451
|
+
if (!report) return;
|
|
4452
|
+
this.sendRuntimeProfileWireReport(report);
|
|
4453
|
+
}
|
|
3719
4454
|
// Machine-level workspace scanning
|
|
3720
4455
|
async scanAllWorkspaces() {
|
|
3721
4456
|
return scanWorkspaceDirectories(this.dataDir);
|
|
@@ -3725,7 +4460,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3725
4460
|
}
|
|
3726
4461
|
// Workspace file browsing
|
|
3727
4462
|
async getFileTree(agentId, dirPath) {
|
|
3728
|
-
const agentDir =
|
|
4463
|
+
const agentDir = path11.join(this.dataDir, agentId);
|
|
3729
4464
|
try {
|
|
3730
4465
|
await stat2(agentDir);
|
|
3731
4466
|
} catch {
|
|
@@ -3733,8 +4468,8 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3733
4468
|
}
|
|
3734
4469
|
let targetDir = agentDir;
|
|
3735
4470
|
if (dirPath) {
|
|
3736
|
-
const resolved =
|
|
3737
|
-
if (!resolved.startsWith(agentDir +
|
|
4471
|
+
const resolved = path11.resolve(agentDir, dirPath);
|
|
4472
|
+
if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
|
|
3738
4473
|
return [];
|
|
3739
4474
|
}
|
|
3740
4475
|
targetDir = resolved;
|
|
@@ -3742,9 +4477,9 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3742
4477
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
3743
4478
|
}
|
|
3744
4479
|
async readFile(agentId, filePath) {
|
|
3745
|
-
const agentDir =
|
|
3746
|
-
const resolved =
|
|
3747
|
-
if (!resolved.startsWith(agentDir +
|
|
4480
|
+
const agentDir = path11.join(this.dataDir, agentId);
|
|
4481
|
+
const resolved = path11.resolve(agentDir, filePath);
|
|
4482
|
+
if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
|
|
3748
4483
|
throw new Error("Access denied");
|
|
3749
4484
|
}
|
|
3750
4485
|
const info = await stat2(resolved);
|
|
@@ -3768,7 +4503,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3768
4503
|
".sh",
|
|
3769
4504
|
".py"
|
|
3770
4505
|
]);
|
|
3771
|
-
const ext =
|
|
4506
|
+
const ext = path11.extname(resolved).toLowerCase();
|
|
3772
4507
|
if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
|
|
3773
4508
|
return { content: null, binary: true };
|
|
3774
4509
|
}
|
|
@@ -3794,14 +4529,14 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3794
4529
|
async listSkills(agentId, runtimeHint) {
|
|
3795
4530
|
const agent = this.agents.get(agentId);
|
|
3796
4531
|
const runtime = runtimeHint || agent?.config.runtime || "claude";
|
|
3797
|
-
const home =
|
|
3798
|
-
const workspaceDir =
|
|
4532
|
+
const home = os4.homedir();
|
|
4533
|
+
const workspaceDir = path11.join(this.dataDir, agentId);
|
|
3799
4534
|
const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
|
|
3800
4535
|
const globalResults = await Promise.all(
|
|
3801
|
-
paths.global.map((p) => this.scanSkillsDir(
|
|
4536
|
+
paths.global.map((p) => this.scanSkillsDir(path11.join(home, p)))
|
|
3802
4537
|
);
|
|
3803
4538
|
const workspaceResults = await Promise.all(
|
|
3804
|
-
paths.workspace.map((p) => this.scanSkillsDir(
|
|
4539
|
+
paths.workspace.map((p) => this.scanSkillsDir(path11.join(workspaceDir, p)))
|
|
3805
4540
|
);
|
|
3806
4541
|
const dedup = (skills) => {
|
|
3807
4542
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3830,7 +4565,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3830
4565
|
const skills = [];
|
|
3831
4566
|
for (const entry of entries) {
|
|
3832
4567
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
3833
|
-
const skillMd =
|
|
4568
|
+
const skillMd = path11.join(dir, entry.name, "SKILL.md");
|
|
3834
4569
|
try {
|
|
3835
4570
|
const content = await readFile(skillMd, "utf-8");
|
|
3836
4571
|
const skill = this.parseSkillMd(entry.name, content);
|
|
@@ -3841,7 +4576,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
3841
4576
|
} else if (entry.name.endsWith(".md")) {
|
|
3842
4577
|
const cmdName = entry.name.replace(/\.md$/, "");
|
|
3843
4578
|
try {
|
|
3844
|
-
const content = await readFile(
|
|
4579
|
+
const content = await readFile(path11.join(dir, entry.name), "utf-8");
|
|
3845
4580
|
const skill = this.parseSkillMd(cmdName, content);
|
|
3846
4581
|
skill.sourcePath = dir;
|
|
3847
4582
|
skills.push(skill);
|
|
@@ -4056,7 +4791,9 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
4056
4791
|
logger.info(
|
|
4057
4792
|
`[Agent ${agentId}] Claude gated steering flush reason=${reason} messages=${nextMessages.length}`
|
|
4058
4793
|
);
|
|
4059
|
-
|
|
4794
|
+
if (reason === "turn_end") {
|
|
4795
|
+
this.broadcastActivity(agentId, "working", "Message received");
|
|
4796
|
+
}
|
|
4060
4797
|
if (this.deliverMessagesViaStdin(agentId, ap, nextMessages, reason === "turn_end" ? "idle" : "busy")) {
|
|
4061
4798
|
return true;
|
|
4062
4799
|
}
|
|
@@ -4233,6 +4970,16 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
4233
4970
|
this.broadcastActivity(agentId, "online", "Idle");
|
|
4234
4971
|
}
|
|
4235
4972
|
this.endRuntimeTrace(ap, "ok", { outcome: "turn-completed" });
|
|
4973
|
+
if (ap.driver.terminateProcessOnTurnEnd) {
|
|
4974
|
+
ap.expectedTerminationReason = "turn_end";
|
|
4975
|
+
logger.info(`[Agent ${agentId}] Turn completed; terminating ${ap.driver.id} process`);
|
|
4976
|
+
try {
|
|
4977
|
+
ap.process.kill("SIGTERM");
|
|
4978
|
+
} catch (err) {
|
|
4979
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
4980
|
+
logger.warn(`[Agent ${agentId}] Failed to terminate ${ap.driver.id} after turn_end: ${reason}`);
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4236
4983
|
}
|
|
4237
4984
|
if (event.sessionId) {
|
|
4238
4985
|
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId, launchId: ap?.launchId || void 0 });
|
|
@@ -4294,7 +5041,6 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
4294
5041
|
if (ap.driver.busyDeliveryMode === "direct" && ap.inbox.length > 0) {
|
|
4295
5042
|
const queuedMessages = ap.inbox.splice(0, ap.inbox.length);
|
|
4296
5043
|
console.log(`[Agent ${agentId}] Delivering queued message via stdin while busy`);
|
|
4297
|
-
this.broadcastActivity(agentId, "working", "Message received");
|
|
4298
5044
|
if (this.deliverMessagesViaStdin(agentId, ap, queuedMessages, "busy")) {
|
|
4299
5045
|
return;
|
|
4300
5046
|
}
|
|
@@ -4311,15 +5057,15 @@ Use read_history to catch up on the channels listed above, then stop. Read each
|
|
|
4311
5057
|
/** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
|
|
4312
5058
|
deliverMessagesViaStdin(agentId, ap, messages, mode) {
|
|
4313
5059
|
if (messages.length === 0) return true;
|
|
4314
|
-
const prompt = messages.length === 1 ? `New message received:
|
|
5060
|
+
const prompt = formatRuntimeProfileControlPrompt(messages) ?? (messages.length === 1 ? `New message received:
|
|
4315
5061
|
|
|
4316
|
-
${formatIncomingMessage(messages[0])}
|
|
5062
|
+
${formatIncomingMessage(messages[0], ap.driver)}
|
|
4317
5063
|
|
|
4318
5064
|
Respond as appropriate. Complete all your work before stopping.` : `New messages received:
|
|
4319
5065
|
|
|
4320
|
-
${messages.map((message) => formatIncomingMessage(message)).join("\n")}
|
|
5066
|
+
${messages.map((message) => formatIncomingMessage(message, ap.driver)).join("\n")}
|
|
4321
5067
|
|
|
4322
|
-
Respond as appropriate. Complete all your work before stopping
|
|
5068
|
+
Respond as appropriate. Complete all your work before stopping.`);
|
|
4323
5069
|
const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId, { mode });
|
|
4324
5070
|
if (!encoded) {
|
|
4325
5071
|
ap.inbox.unshift(...messages);
|
|
@@ -4355,8 +5101,8 @@ Respond as appropriate. Complete all your work before stopping.`;
|
|
|
4355
5101
|
const nodes = [];
|
|
4356
5102
|
for (const entry of entries) {
|
|
4357
5103
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
4358
|
-
const fullPath =
|
|
4359
|
-
const relativePath =
|
|
5104
|
+
const fullPath = path11.join(dir, entry.name);
|
|
5105
|
+
const relativePath = path11.relative(rootDir, fullPath);
|
|
4360
5106
|
let info;
|
|
4361
5107
|
try {
|
|
4362
5108
|
info = await stat2(fullPath);
|
|
@@ -4587,10 +5333,10 @@ var ReminderCache = class {
|
|
|
4587
5333
|
|
|
4588
5334
|
// src/machineLock.ts
|
|
4589
5335
|
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
4590
|
-
import { mkdirSync as
|
|
4591
|
-
import
|
|
4592
|
-
import
|
|
4593
|
-
var DEFAULT_MACHINE_STATE_ROOT =
|
|
5336
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync4, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
|
|
5337
|
+
import os5 from "os";
|
|
5338
|
+
import path12 from "path";
|
|
5339
|
+
var DEFAULT_MACHINE_STATE_ROOT = path12.join(os5.homedir(), ".slock", "machines");
|
|
4594
5340
|
var INCOMPLETE_LOCK_STALE_MS = 3e4;
|
|
4595
5341
|
var DaemonMachineLockConflictError = class extends Error {
|
|
4596
5342
|
code = "DAEMON_MACHINE_LOCK_HELD";
|
|
@@ -4609,18 +5355,18 @@ function getDaemonMachineLockId(apiKey) {
|
|
|
4609
5355
|
return `machine-${apiKeyFingerprint(apiKey).slice(0, 16)}`;
|
|
4610
5356
|
}
|
|
4611
5357
|
function ownerPath(lockDir) {
|
|
4612
|
-
return
|
|
5358
|
+
return path12.join(lockDir, "owner.json");
|
|
4613
5359
|
}
|
|
4614
5360
|
function readOwner(lockDir) {
|
|
4615
5361
|
try {
|
|
4616
|
-
return JSON.parse(
|
|
5362
|
+
return JSON.parse(readFileSync4(ownerPath(lockDir), "utf8"));
|
|
4617
5363
|
} catch {
|
|
4618
5364
|
return null;
|
|
4619
5365
|
}
|
|
4620
5366
|
}
|
|
4621
5367
|
function lockAgeMs(lockDir) {
|
|
4622
5368
|
try {
|
|
4623
|
-
return Date.now() -
|
|
5369
|
+
return Date.now() - statSync2(lockDir).mtimeMs;
|
|
4624
5370
|
} catch {
|
|
4625
5371
|
return null;
|
|
4626
5372
|
}
|
|
@@ -4639,26 +5385,26 @@ function acquireDaemonMachineLock(options) {
|
|
|
4639
5385
|
const rootDir = options.rootDir ?? DEFAULT_MACHINE_STATE_ROOT;
|
|
4640
5386
|
const fingerprint = apiKeyFingerprint(options.apiKey);
|
|
4641
5387
|
const lockId = getDaemonMachineLockId(options.apiKey);
|
|
4642
|
-
const machineDir =
|
|
4643
|
-
const lockDir =
|
|
5388
|
+
const machineDir = path12.join(rootDir, lockId);
|
|
5389
|
+
const lockDir = path12.join(machineDir, "daemon.lock");
|
|
4644
5390
|
const token = randomUUID2();
|
|
4645
|
-
|
|
5391
|
+
mkdirSync5(machineDir, { recursive: true });
|
|
4646
5392
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
4647
5393
|
try {
|
|
4648
|
-
|
|
5394
|
+
mkdirSync5(lockDir);
|
|
4649
5395
|
const owner = {
|
|
4650
5396
|
pid: process.pid,
|
|
4651
5397
|
token,
|
|
4652
|
-
hostname:
|
|
5398
|
+
hostname: os5.hostname(),
|
|
4653
5399
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4654
5400
|
serverUrl: options.serverUrl,
|
|
4655
5401
|
apiKeyFingerprint: fingerprint.slice(0, 16)
|
|
4656
5402
|
};
|
|
4657
5403
|
try {
|
|
4658
|
-
|
|
5404
|
+
writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
|
|
4659
5405
|
`, { mode: 384 });
|
|
4660
5406
|
} catch (err) {
|
|
4661
|
-
|
|
5407
|
+
rmSync2(lockDir, { recursive: true, force: true });
|
|
4662
5408
|
throw err;
|
|
4663
5409
|
}
|
|
4664
5410
|
return {
|
|
@@ -4668,7 +5414,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
4668
5414
|
release: () => {
|
|
4669
5415
|
const currentOwner = readOwner(lockDir);
|
|
4670
5416
|
if (currentOwner?.pid === process.pid && currentOwner.token === token) {
|
|
4671
|
-
|
|
5417
|
+
rmSync2(lockDir, { recursive: true, force: true });
|
|
4672
5418
|
}
|
|
4673
5419
|
}
|
|
4674
5420
|
};
|
|
@@ -4685,7 +5431,7 @@ function acquireDaemonMachineLock(options) {
|
|
|
4685
5431
|
throw new DaemonMachineLockConflictError(lockDir, null);
|
|
4686
5432
|
}
|
|
4687
5433
|
}
|
|
4688
|
-
|
|
5434
|
+
rmSync2(lockDir, { recursive: true, force: true });
|
|
4689
5435
|
}
|
|
4690
5436
|
}
|
|
4691
5437
|
throw new DaemonMachineLockConflictError(lockDir, readOwner(lockDir));
|
|
@@ -4712,23 +5458,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
|
|
|
4712
5458
|
}
|
|
4713
5459
|
}
|
|
4714
5460
|
function resolveChatBridgePath(moduleUrl = import.meta.url) {
|
|
4715
|
-
const dirname =
|
|
4716
|
-
const jsPath =
|
|
5461
|
+
const dirname = path13.dirname(fileURLToPath(moduleUrl));
|
|
5462
|
+
const jsPath = path13.resolve(dirname, "chat-bridge.js");
|
|
4717
5463
|
try {
|
|
4718
5464
|
accessSync(jsPath);
|
|
4719
5465
|
return jsPath;
|
|
4720
5466
|
} catch {
|
|
4721
|
-
return
|
|
5467
|
+
return path13.resolve(dirname, "chat-bridge.ts");
|
|
4722
5468
|
}
|
|
4723
5469
|
}
|
|
4724
5470
|
function resolveSlockCliPath(moduleUrl = import.meta.url) {
|
|
4725
|
-
const thisDir =
|
|
4726
|
-
const bundledDistPath =
|
|
5471
|
+
const thisDir = path13.dirname(fileURLToPath(moduleUrl));
|
|
5472
|
+
const bundledDistPath = path13.resolve(thisDir, "cli", "index.js");
|
|
4727
5473
|
try {
|
|
4728
5474
|
accessSync(bundledDistPath);
|
|
4729
5475
|
return bundledDistPath;
|
|
4730
5476
|
} catch {
|
|
4731
|
-
const workspaceDistPath =
|
|
5477
|
+
const workspaceDistPath = path13.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
|
|
4732
5478
|
accessSync(workspaceDistPath);
|
|
4733
5479
|
return workspaceDistPath;
|
|
4734
5480
|
}
|
|
@@ -4737,9 +5483,14 @@ function detectRuntimes() {
|
|
|
4737
5483
|
const ids = [];
|
|
4738
5484
|
const versions = {};
|
|
4739
5485
|
for (const runtime of RUNTIMES) {
|
|
5486
|
+
const driver = getDriver(runtime.id);
|
|
4740
5487
|
try {
|
|
4741
|
-
|
|
4742
|
-
|
|
5488
|
+
if (driver.probe) {
|
|
5489
|
+
const probe = driver.probe();
|
|
5490
|
+
if (!probe.available) {
|
|
5491
|
+
if (probe.version) versions[runtime.id] = probe.version;
|
|
5492
|
+
continue;
|
|
5493
|
+
}
|
|
4743
5494
|
ids.push(runtime.id);
|
|
4744
5495
|
if (probe.version) versions[runtime.id] = probe.version;
|
|
4745
5496
|
continue;
|
|
@@ -4840,7 +5591,7 @@ var DaemonCore = class {
|
|
|
4840
5591
|
}
|
|
4841
5592
|
resolveMachineStateRoot() {
|
|
4842
5593
|
if (this.options.machineStateDir) return this.options.machineStateDir;
|
|
4843
|
-
if (this.options.dataDir) return
|
|
5594
|
+
if (this.options.dataDir) return path13.join(path13.dirname(this.options.dataDir), "machines");
|
|
4844
5595
|
return DEFAULT_MACHINE_STATE_ROOT;
|
|
4845
5596
|
}
|
|
4846
5597
|
start() {
|
|
@@ -5031,8 +5782,8 @@ var DaemonCore = class {
|
|
|
5031
5782
|
capabilities: ["agent:start", "agent:stop", "agent:deliver", "workspace:files"],
|
|
5032
5783
|
runtimes,
|
|
5033
5784
|
runningAgents: this.agentManager.getRunningAgentIds(),
|
|
5034
|
-
hostname: this.options.hostname ??
|
|
5035
|
-
os: this.options.osDescription ?? `${
|
|
5785
|
+
hostname: this.options.hostname ?? os6.hostname(),
|
|
5786
|
+
os: this.options.osDescription ?? `${os6.platform()} ${os6.arch()}`,
|
|
5036
5787
|
daemonVersion: this.daemonVersion
|
|
5037
5788
|
});
|
|
5038
5789
|
for (const agentId of this.agentManager.getRunningAgentIds()) {
|