@slock-ai/daemon 0.57.4 → 0.57.5-play.20260611173452

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.
@@ -1,6 +1,6 @@
1
1
  // src/core.ts
2
- import path15 from "path";
3
- import os7 from "os";
2
+ import path16 from "path";
3
+ import os8 from "os";
4
4
  import { createRequire as createRequire2 } from "module";
5
5
  import { accessSync } from "fs";
6
6
  import { fileURLToPath } from "url";
@@ -1301,6 +1301,7 @@ var RUNTIME_MODELS = {
1301
1301
  { id: "claude-opus-4-8", label: "Claude Opus 4.8" },
1302
1302
  { id: "claude-opus-4-7", label: "Claude Opus 4.7" },
1303
1303
  { id: "claude-opus-4-6", label: "Claude Opus 4.6" },
1304
+ { id: "fable", label: "Claude Fable" },
1304
1305
  { id: "sonnet", label: "Claude Sonnet" },
1305
1306
  { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
1306
1307
  { id: "haiku", label: "Claude Haiku" },
@@ -1503,11 +1504,11 @@ var DISPLAY_PLAN_CONFIG = {
1503
1504
  };
1504
1505
 
1505
1506
  // src/agentProcessManager.ts
1506
- import { mkdirSync as mkdirSync3, readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync4 } from "fs";
1507
+ import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync, writeFileSync as writeFileSync4 } from "fs";
1507
1508
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
1508
1509
  import { createHash as createHash3 } from "crypto";
1509
- import path11 from "path";
1510
- import os5 from "os";
1510
+ import path12 from "path";
1511
+ import os6 from "os";
1511
1512
 
1512
1513
  // src/drivers/claude.ts
1513
1514
  import { spawn } from "child_process";
@@ -1841,9 +1842,6 @@ function buildSlockCliGuideSections(options) {
1841
1842
  }
1842
1843
 
1843
1844
  // src/drivers/systemPrompt.ts
1844
- function toolRef(prefix, name) {
1845
- return `${prefix}${name}`;
1846
- }
1847
1845
  function runtimeContextLines(config) {
1848
1846
  const ctx = config.runtimeContext;
1849
1847
  if (!ctx) return [];
@@ -1865,10 +1863,8 @@ function runtimeContextLines(config) {
1865
1863
  if (ctx.workspacePath) lines.push(`- Workspace: ${ctx.workspacePath}`);
1866
1864
  return lines.length > 4 ? lines : [];
1867
1865
  }
1868
- function buildPrompt(config, variant, opts) {
1869
- const isCli = variant === "cli";
1870
- const t = (name) => toolRef(opts.toolPrefix, name);
1871
- const taskClaimCmd = isCli ? "`slock task claim`" : `\`${t("claim_tasks")}\``;
1866
+ function buildPrompt(config, opts) {
1867
+ const taskClaimCmd = "`slock task claim`";
1872
1868
  const cliGuideSections = buildSlockCliGuideSections({
1873
1869
  // The daemon prompt audience is always managed-runner regardless of
1874
1870
  // CLI vs MCP variant — both are daemon-spawned. The self-hosted-runner
@@ -1880,162 +1876,42 @@ function buildPrompt(config, variant, opts) {
1880
1876
  },
1881
1877
  taskClaimCmd
1882
1878
  });
1883
- const sendCmd = isCli ? "`slock message send`" : `\`${t("send_message")}\``;
1884
- const readCmd = isCli ? "`slock message read`" : `\`${t("read_history")}\``;
1885
- const checkCmd = isCli ? "`slock message check`" : `\`${t("check_messages")}\``;
1886
- const inboxCheckCmd = isCli ? "`slock inbox check`" : checkCmd;
1887
- const taskCreateCmd = isCli ? "`slock task create`" : `\`${t("create_tasks")}\``;
1888
- const taskUpdateCmd = isCli ? "`slock task update`" : `\`${t("update_task_status")}\``;
1889
- const serverInfoCmd = isCli ? "`slock server info`" : `\`${t("list_server")}\``;
1890
- const scheduleReminderCmd = isCli ? "`slock reminder schedule`" : `\`${t("schedule_reminder")}\``;
1891
- const listRemindersCmd = isCli ? "`slock reminder list`" : `\`${t("list_reminders")}\``;
1892
- const cancelReminderCmd = isCli ? "`slock reminder cancel`" : `\`${t("cancel_reminder")}\``;
1879
+ const checkCmd = "`slock message check`";
1880
+ const inboxCheckCmd = "`slock inbox check`";
1893
1881
  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.";
1894
- const criticalRules = isCli ? [
1882
+ const noConcreteMessageStartupStep = opts.includeStdinNotificationSection ? `3. If there is no concrete incoming message to handle, but this turn includes a Slock inbox notice, treat it as a wake signal: call ${checkCmd} to read the pending message content before deciding there is no work. If there is neither a concrete message nor an inbox notice, stop and wait. ${messageDeliveryText}` : `3. If there is no concrete incoming message to handle, stop and wait. ${messageDeliveryText}`;
1883
+ const criticalRules = [
1895
1884
  "- Always communicate through `slock` CLI commands. This is your only output channel: text you produce outside a `slock` command is not delivered to anyone.",
1896
1885
  ...opts.extraCriticalRules,
1897
1886
  "- Use only the provided `slock` CLI commands for messaging.",
1898
1887
  "- Do not combine multiple `slock` CLI commands in one shell command. Run one `slock` command per tool call, read its output, then decide the next command.",
1899
1888
  "- Always claim a task via `slock task claim` before starting work on it. If the claim fails, move on to a different task."
1900
- ] : [
1901
- `- Always communicate through ${sendCmd}. This is your only output channel: text you produce outside a messaging tool is not delivered to anyone.`,
1902
- ...opts.extraCriticalRules,
1903
- "- Use only the provided MCP tools for messaging \u2014 they are already available and ready.",
1904
- `- Always claim a task via ${taskClaimCmd} before starting work on it. If the claim fails, move on to a different task.`
1905
1889
  ];
1906
1890
  const runtimeProfileControl = config.runtimeProfileControl?.kind === "daemon_release_notice" ? config.runtimeProfileControl : null;
1907
1891
  const runtimeProfileControlStartupStep = runtimeProfileControl ? [
1908
1892
  "0. If this system prompt contains a **Runtime Profile Control** section, read that notice first. It is informational only; no runtime control action or chat reply is required. Do not read MEMORY.md, check messages, or respond to inbox messages before reading it."
1909
1893
  ] : [];
1910
- const startupSteps = isCli ? [
1894
+ const startupSteps = [
1911
1895
  ...runtimeProfileControlStartupStep,
1912
1896
  "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.",
1913
1897
  "2. Read MEMORY.md (in your cwd) and then only the additional memory/files you need to handle the current turn well.",
1914
- `3. If there is no concrete incoming message to handle, stop and wait. ${messageDeliveryText}`,
1898
+ noConcreteMessageStartupStep,
1915
1899
  "4. When you receive a message, process it and reply with `slock message send`.",
1916
1900
  "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."
1917
- ] : [
1918
- ...runtimeProfileControlStartupStep,
1919
- `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.`,
1920
- "2. Read MEMORY.md (in your cwd) and then only the additional memory/files you need to handle the current turn well.",
1921
- `3. If there is no concrete incoming message to handle, stop and wait. ${messageDeliveryText}`,
1922
- `4. When you receive a message, process it and reply with ${sendCmd}.`,
1923
- "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."
1924
1901
  ];
1925
- const communicationSection = isCli ? cliGuideSections.communication : `## Communication \u2014 MCP tools ONLY
1926
-
1927
- You have MCP tools from the "chat" server. Use ONLY these for communication:
1928
-
1929
- 1. **${checkCmd}** \u2014 Non-blocking check for new messages. Use freely during work \u2014 at natural breakpoints or after notifications.
1930
- 2. **${sendCmd}** \u2014 Send a message to a channel or DM.
1931
- 3. **${serverInfoCmd}** \u2014 List all channels in this server, which ones you have joined, plus all agents and humans.
1932
- 4. **\`${t("join_channel")}\`** \u2014 Join a visible public channel. This only affects your own agent membership.
1933
- 5. **\`${t("leave_channel")}\`** \u2014 Leave a regular channel you have joined. This only affects your own agent membership.
1934
- 6. **${readCmd}** \u2014 Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` pagination and \`around\` for centered context.
1935
- 7. **\`${t("search_messages")}\`** \u2014 Search messages visible to you, then inspect a hit with ${readCmd}.
1936
- 8. **\`${t("list_tasks")}\`** \u2014 View a channel's task board.
1937
- 9. **${taskCreateCmd}** \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).
1938
- 10. **${taskClaimCmd}** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
1939
- 11. **\`${t("unclaim_task")}\`** \u2014 Release your claim on a task.
1940
- 12. **${taskUpdateCmd}** \u2014 Change a task's status (e.g. to in_review or done).
1941
- 13. **\`${t("upload_file")}\`** \u2014 Upload a file up to 50MB to attach to a message. Returns an attachment ID to pass to ${sendCmd}. Videos are downloadable attachments and are not parsed by agents; large PDFs may need to be downloaded and inspected in smaller chunks.
1942
- 14. **\`${t("view_file")}\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
1943
- 15. **${scheduleReminderCmd}** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
1944
- 16. **${listRemindersCmd}** \u2014 List your reminders.
1945
- 17. **${cancelReminderCmd}** \u2014 Cancel one of your reminders by ID.`;
1946
- const credentialHygieneSection = isCli ? cliGuideSections.credentialHygiene : `### Credential hygiene
1947
-
1948
- **Never paste credentials into public Slock channels, public-channel threads, or public-channel task/attachment fields.** Agent tokens (\`sk_agent_*\`), legacy machine API keys (\`sk_machine_*\`), session bearers, JWTs, \`.env\` files, or \`credential.json\` contents must not appear in public channel chat. DMs and private channels are allowed for authorized secret handoff, but verify the audience first. If you accidentally paste one into a public channel, immediately tell the credential owner so they can rotate it.
1949
-
1950
- If a tool or error output contains credential-shaped strings, redact them to \`sk_agent_<redacted>\` / \`sk_machine_<redacted>\` shape before posting to a public channel.`;
1951
- const reminderSection = isCli ? cliGuideSections.reminders : `### Reminders
1952
-
1953
- Use reminders for follow-up that depends on future state you cannot resolve now, whether user-requested or self-driven. A reminder is an author-owned, persistent, observable, snoozable, updatable, and cancelable wake-up signal anchored to a Slock message or thread; when it fires, it wakes the author who scheduled it, not other people. If anchored to a message or thread, the receipt/fire system message is visible in that surface, but wake ownership does not transfer. To notify another human or agent later, schedule your own reminder and then @mention them when it fires. Use reminders instead of keeping the current turn alive with a long sleep or relying on MEMORY to wake you. If you expect the wait to finish within about 1 minute, you may briefly poll, but say so in the relevant thread first.
1954
- When a reminder already exists, prefer \`slock reminder snooze\` to push it later, \`slock reminder update\` to change its meaning or schedule, and \`slock reminder cancel\` only when it is truly no longer needed.
1955
- Use ${scheduleReminderCmd} rather than runtime-native wake or cron tools such as ScheduleWakeup or CronCreate for user-visible reminders, so reminders stay author-owned, persistent, observable, snoozable, updatable, and cancelable in Slock.
1956
- 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.`;
1957
- const sendingMessagesSection = isCli ? cliGuideSections.sendingMessages : `### Sending messages
1958
-
1959
- - **Reply to a channel**: \`${t("send_message")}(target="#channel-name", content="...")\`
1960
- - **Reply to a DM**: \`${t("send_message")}(target="dm:@peer-name", content="...")\`
1961
- - **Reply in a thread**: \`${t("send_message")}(target="#channel:shortid", content="...")\` or \`${t("send_message")}(target="dm:@peer:shortid", content="...")\`
1962
- - **Start a NEW DM**: \`${t("send_message")}(target="dm:@person-name", content="...")\`
1963
-
1964
- **IMPORTANT**: To reply to any message, always reuse the exact \`target\` from the received message. This ensures your reply goes to the right place \u2014 whether it's a channel, DM, or thread.`;
1965
- const threadsSection = isCli ? cliGuideSections.threads : `### Threads
1966
-
1967
- Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
1968
-
1969
- - **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
1970
- - When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
1971
- - **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=00000000 ...]\`, call \`${t("send_message")}(target="#general:00000000", content="...")\`. The thread will be auto-created if it doesn't exist yet. Example IDs like \`00000000\` are placeholders; real message IDs come from received messages.
1972
- - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
1973
- - You can read thread history via ${readCmd} with the same thread target.
1974
- - Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
1975
- const discoverySection = isCli ? cliGuideSections.discovery : `### Discovering people and channels
1976
-
1977
- Call ${serverInfoCmd} to see all channels in this server, which ones you have joined, other agents, and humans.
1978
- Visible public channels may appear even when \`joined=false\`. In that state you can still inspect them with ${readCmd}, but you cannot send messages there or receive ordinary channel delivery until you join with \`${t("join_channel")}\`. Private channels require a human with access to add you. To leave a regular channel you have joined, use \`${t("leave_channel")}\`.
1979
- Private channels are membership-gated. If ${serverInfoCmd} shows a channel as private, treat its name, members, and content as private to that channel; do not disclose that information in other channels, DMs, summaries, or task reports unless a human explicitly asks within an authorized context. In channel member listings, human role labels such as owner/admin show server-level authority; no role label means ordinary member.`;
1980
- const channelAwarenessSection = isCli ? cliGuideSections.channelAwareness : `### Channel awareness
1981
-
1982
- Each channel has a **name** and optionally a **description** that define its purpose (visible via ${serverInfoCmd}). Respect them:
1983
- - **Reply in context** \u2014 always respond in the channel/thread the message came from.
1984
- - **Stay on topic** \u2014 when proactively sharing results or updates, post in the channel most relevant to the work. Don't scatter messages across unrelated channels.
1985
- - If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
1986
- const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
1987
-
1988
- If a built-in Slock app or registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app or built-in Slock app, first run \`slock integration list\` and match the app to a listed service before browsing the app. Use \`slock integration login --service <service>\` to provision or reuse your agent login for that service. If the service exposes an agent behavior manifest and you need to run its local CLI, run \`slock integration env --service <service>\` before invoking that CLI; if it prints exports, apply them first so service credentials stay under a per-agent profile HOME/XDG tree instead of the host user's global HOME. If it reports that no local env is required, do not invent HOME/XDG overrides. If it fails, do not run that local CLI with the host user's HOME; report that the service manifest is unsupported. Slock does not execute commands from remote manifests automatically. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. When the command returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes an app URL, open that URL as the service-provided app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes, call internal Slock integration endpoints directly, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`slock profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the listed service / Slock Agent Login path.` : `### Third-party integrations
1989
-
1990
- If a built-in Slock app or registered third-party service requires login, use Slock Agent Login through the available registered-service interface instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app or built-in Slock app, first inspect the registered-service interface and match the app to a listed service before browsing the app. Once the registered-service interface reports the agent login is ready, the agent-side login is ready. If that interface provides an app URL, use it as the service-provided app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow or treat internal request IDs as OAuth callback codes unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use your Slock profile view. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the listed service / Slock Agent Login path.`;
1991
- const readingHistorySection = isCli ? cliGuideSections.readingHistory : `### Reading history
1992
-
1993
- Use ${readCmd} with the \`channel\` parameter set to \`"#channel-name"\`, \`"dm:@peer-name"\`, or a thread target like \`"#channel:shortid"\`.
1994
-
1995
- To jump directly to a specific hit with nearby context, pass \`around\` set to a message ID or seq number.`;
1996
- const historicalReferenceSection = isCli ? cliGuideSections.historicalReferences : `### Historical references
1997
-
1998
- 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.`;
1999
- const tasksSection = isCli ? cliGuideSections.tasks : `### Tasks
2000
-
2001
- 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.
2002
-
2003
- **Decision rule:** if fulfilling a message requires you to take action beyond just replying (running tools, writing code, making changes), claim the message first. If you're only answering a question or having a conversation, no claim needed.
2004
-
2005
- **What you see in messages:**
2006
- - A message already marked as a task: \`@Alice: Fix the login bug [task #3 status=in_progress]\`
2007
- - A regular message (no task suffix): \`@Alice: Can someone look into the login bug?\`
2008
- - A system notification about task changes: \`\u{1F4CB} Alice converted a message to task #3 "Fix the login bug"\`
2009
-
2010
- Only top-level channel / DM messages can become tasks. Messages inside threads are discussion context \u2014 reply there, but keep claims and conversions to top-level messages.
2011
-
2012
- ${readCmd} shows messages in their current state. If a message was later converted to a task, it will show the \`[task #N ...]\` suffix.
2013
-
2014
- **Status flow:** \`todo\` \u2192 \`in_progress\` \u2192 \`in_review\` \u2192 \`done\`
2015
-
2016
- **Assignee** is independent from status \u2014 a task can be claimed or unclaimed at any status except \`done\`.
2017
-
2018
- **Workflow:**
2019
- 1. Receive a message that requires action \u2192 claim it first (by task number if already a task, or by message ID if it's a regular message)
2020
- 2. If the claim fails, someone else is working on it \u2014 move on to another task
2021
- 3. Post updates in the task's thread via ${sendCmd} with \`target="#channel:msgShortId"\`
2022
- 4. When done, set status to \`in_review\` so a human can validate via ${taskUpdateCmd}
2023
- 5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
2024
-
2025
- **What ${taskCreateCmd} really means:**
2026
- - Tasks live in the same chat flow as messages. A task is just a message with task metadata, not a separate source of truth.
2027
- - ${taskCreateCmd} is a convenience helper for a specific sequence: create a brand-new message, then publish that new message as a task-message.
2028
- - ${taskCreateCmd} only creates the task \u2014 to own it, call ${taskClaimCmd} afterward.
2029
- - Typical uses for ${taskCreateCmd} are breaking down a larger task into parallel subtasks, or batch-creating genuinely new work for others to claim.
2030
- - If someone already sent the work item as a message, just claim that existing message/task instead of creating a new one.
2031
- - If the work already exists as a message, reuse it via ${taskClaimCmd} with the message ID.
2032
-
2033
- **Creating new tasks:**
2034
- - The task system exists to prevent duplicate work. If you see an existing task for the work, either claim that task or leave it alone.
2035
- - If a message already shows a \`[task #N ...]\` suffix, claim \`#N\` if it is yours to take; otherwise move on.
2036
- - Before calling ${taskCreateCmd}, first check whether the work already exists on the task board or is already being handled.
2037
- - Reuse existing tasks and threads instead of creating duplicates.
2038
- - Use ${taskCreateCmd} only for genuinely new subtasks or follow-up work that does not already have a canonical task.`;
1902
+ const communicationSection = cliGuideSections.communication;
1903
+ const credentialHygieneSection = cliGuideSections.credentialHygiene;
1904
+ const reminderSection = cliGuideSections.reminders;
1905
+ const sendingMessagesSection = cliGuideSections.sendingMessages;
1906
+ const threadsSection = cliGuideSections.threads;
1907
+ const discoverySection = cliGuideSections.discovery;
1908
+ const channelAwarenessSection = cliGuideSections.channelAwareness;
1909
+ const thirdPartyIntegrationsSection = `### Third-party integrations
1910
+
1911
+ If a built-in Slock app or registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app or built-in Slock app, first run \`slock integration list\` and match the app to a listed service before browsing the app. Use \`slock integration login --service <service>\` to provision or reuse your agent login for that service. If the service exposes an agent behavior manifest and you need to run its local CLI, run \`slock integration env --service <service>\` before invoking that CLI; if it prints exports, apply them first so service credentials stay under a per-agent profile HOME/XDG tree instead of the host user's global HOME. If it reports that no local env is required, do not invent HOME/XDG overrides. If it fails, do not run that local CLI with the host user's HOME; report that the service manifest is unsupported. Slock does not execute commands from remote manifests automatically. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. When the command returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes an app URL, open that URL as the service-provided app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes, call internal Slock integration endpoints directly, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`slock profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the listed service / Slock Agent Login path.`;
1912
+ const readingHistorySection = cliGuideSections.readingHistory;
1913
+ const historicalReferenceSection = cliGuideSections.historicalReferences;
1914
+ const tasksSection = cliGuideSections.tasks;
2039
1915
  let prompt = `You are "${config.displayName || config.name}", an AI agent in Slock \u2014 a collaborative platform for human-AI collaboration, serving as a shared message service for humans and agents who may be running on different computers.
2040
1916
 
2041
1917
  ## Who you are
@@ -2149,10 +2025,11 @@ While you are working, the daemon may write a batched, content-free inbox update
2149
2025
 
2150
2026
  How to handle these:
2151
2027
  - Treat the notification as a non-urgent signal that new Slock messages are waiting; it does not include the message content and does not require an immediate interruption.
2152
- - Keep working until a natural breakpoint. If you then choose to inspect pending targets, call ${inboxCheckCmd}; use ${checkCmd} / ${readCmd} when you choose to inspect message content.
2028
+ - If an inbox notice is the only input that woke this turn, call ${checkCmd} to read the pending message content before deciding there is no action to take.
2029
+ - Keep working until a natural breakpoint. If you then choose to inspect pending targets, call ${inboxCheckCmd}; use ${checkCmd} / \`slock message read\` when you choose to inspect message content.
2153
2030
  - If a message you explicitly read is higher priority, pivot to it. If not, continue your current work.`;
2154
2031
  } else {
2155
- const notifyExample = isCli ? `\`[Slock inbox notice:\\nInbox update: N unread messages total; M changed targets\\n#channel pending: K messages ...]\`` : `\`[Slock inbox notice:\\nInbox update: N unread messages total; M changed targets\\n#channel pending: K messages ...]\``;
2032
+ const notifyExample = `\`[Slock inbox notice:\\nInbox update: N unread messages total; M changed targets\\n#channel pending: K messages ...]\``;
2156
2033
  prompt += `
2157
2034
 
2158
2035
  ## Message Notifications
@@ -2163,7 +2040,8 @@ ${notifyExample}
2163
2040
 
2164
2041
  How to handle these:
2165
2042
  - The notice is target-scoped and content-free. Its header may show total unread/pending count, while detail rows list the targets changed by this update; it never includes message bodies.
2166
- - Do not interrupt the current step just because a notice arrived. At a natural breakpoint, call ${inboxCheckCmd} if you choose to inspect pending targets, or use ${checkCmd} / ${readCmd} when you choose to inspect message content.
2043
+ - If an inbox notice is the only input that woke this turn, call ${checkCmd} to read the pending message content before deciding there is no action to take.
2044
+ - Do not interrupt the current step just because a notice arrived. At a natural breakpoint, call ${inboxCheckCmd} if you choose to inspect pending targets, or use ${checkCmd} / \`slock message read\` when you choose to inspect message content.
2167
2045
  - If a message you explicitly read is higher priority, you may pivot to it. If not, continue your current work.`;
2168
2046
  }
2169
2047
  }
@@ -2176,7 +2054,7 @@ ${config.description}. This may evolve.`;
2176
2054
  return prompt;
2177
2055
  }
2178
2056
  function buildCliSystemPrompt(config, opts) {
2179
- return buildPrompt(config, "cli", opts);
2057
+ return buildPrompt(config, opts);
2180
2058
  }
2181
2059
 
2182
2060
  // src/slockHome.ts
@@ -2218,6 +2096,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
2218
2096
  return candidates.filter((candidate) => existsSync(candidate.path));
2219
2097
  }
2220
2098
 
2099
+ // src/authEnv.ts
2100
+ var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
2101
+ var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
2102
+ function scrubDaemonAuthEnv(env) {
2103
+ delete env[DAEMON_API_KEY_ENV];
2104
+ return env;
2105
+ }
2106
+ function scrubDaemonChildEnv(env) {
2107
+ delete env[DAEMON_API_KEY_ENV];
2108
+ delete env[SLOCK_AGENT_TOKEN_ENV];
2109
+ return env;
2110
+ }
2111
+
2221
2112
  // src/agentCredentialProxy.ts
2222
2113
  import { randomBytes } from "crypto";
2223
2114
  import http from "http";
@@ -3704,7 +3595,9 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
3704
3595
  var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
3705
3596
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
3706
3597
  var RAW_CREDENTIAL_ENV_DENYLIST = [
3707
- "SLOCK_AGENT_CREDENTIAL_KEY"
3598
+ "SLOCK_AGENT_TOKEN",
3599
+ "SLOCK_AGENT_CREDENTIAL_KEY",
3600
+ "SLOCK_AGENT_CREDENTIAL_KEY_FILE"
3708
3601
  ];
3709
3602
  var cachedOpencliBinPath;
3710
3603
  function resolveOpencliBinPath() {
@@ -3919,7 +3812,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
3919
3812
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
3920
3813
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
3921
3814
  };
3922
- delete spawnEnv.SLOCK_AGENT_TOKEN;
3815
+ scrubDaemonChildEnv(spawnEnv);
3923
3816
  for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
3924
3817
  delete spawnEnv[key];
3925
3818
  }
@@ -4348,7 +4241,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
4348
4241
  }
4349
4242
  function resolveCommandOnPath(command, deps = {}) {
4350
4243
  const platform = deps.platform ?? process.platform;
4351
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
4244
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
4352
4245
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
4353
4246
  const existsSyncFn = deps.existsSyncFn ?? existsSync2;
4354
4247
  if (platform === "win32") {
@@ -4374,7 +4267,7 @@ function firstExistingPath(candidates, deps = {}) {
4374
4267
  return null;
4375
4268
  }
4376
4269
  function readCommandVersion(command, args = [], deps = {}) {
4377
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
4270
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
4378
4271
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
4379
4272
  try {
4380
4273
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -4468,6 +4361,52 @@ function buildClaudeSpawnSpec(claudeCommand, platform = process.platform) {
4468
4361
  };
4469
4362
  }
4470
4363
 
4364
+ // src/drivers/claudeProviderIsolation.ts
4365
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, symlinkSync } from "fs";
4366
+ import os2 from "os";
4367
+ import path5 from "path";
4368
+ function isClaudeCustomProviderConfig(config) {
4369
+ const launchRuntimeFields = runtimeConfigToLaunchFields(config);
4370
+ return launchRuntimeFields.runtime === "claude" && Boolean(launchRuntimeFields.envVars?.ANTHROPIC_BASE_URL) && Boolean(launchRuntimeFields.envVars?.ANTHROPIC_API_KEY);
4371
+ }
4372
+ function getClaudeProviderStatePaths(workingDirectory) {
4373
+ const root = path5.join(workingDirectory, ".slock", "claude-provider");
4374
+ const home = path5.join(root, "home");
4375
+ return {
4376
+ root,
4377
+ home,
4378
+ configDir: path5.join(home, ".claude")
4379
+ };
4380
+ }
4381
+ function linkIfPresent(source, target) {
4382
+ try {
4383
+ if (!existsSync3(source) || existsSync3(target)) return;
4384
+ mkdirSync2(path5.dirname(target), { recursive: true, mode: 448 });
4385
+ symlinkSync(source, target, "dir");
4386
+ } catch {
4387
+ }
4388
+ }
4389
+ function ensureClaudeProviderStatePaths(workingDirectory, hostClaudeHome = path5.join(os2.homedir(), ".claude")) {
4390
+ const paths = getClaudeProviderStatePaths(workingDirectory);
4391
+ mkdirSync2(paths.home, { recursive: true, mode: 448 });
4392
+ mkdirSync2(paths.configDir, { recursive: true, mode: 448 });
4393
+ linkIfPresent(path5.join(hostClaudeHome, "skills"), path5.join(paths.configDir, "skills"));
4394
+ linkIfPresent(path5.join(hostClaudeHome, "commands"), path5.join(paths.configDir, "commands"));
4395
+ return paths;
4396
+ }
4397
+ function buildClaudeProviderIsolationEnv(ctx) {
4398
+ if (!isClaudeCustomProviderConfig(ctx.config)) {
4399
+ return {};
4400
+ }
4401
+ const paths = ensureClaudeProviderStatePaths(ctx.workingDirectory);
4402
+ return {
4403
+ HOME: paths.home,
4404
+ USERPROFILE: paths.home,
4405
+ CLAUDE_CONFIG_DIR: paths.configDir,
4406
+ CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST: "1"
4407
+ };
4408
+ }
4409
+
4471
4410
  // src/drivers/claude.ts
4472
4411
  var ClaudeDriver = class {
4473
4412
  id = "claude";
@@ -4488,8 +4427,6 @@ var ClaudeDriver = class {
4488
4427
  toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
4489
4428
  };
4490
4429
  supportsStdinNotification = true;
4491
- mcpToolPrefix = "";
4492
- usesSlockCliForCommunication = true;
4493
4430
  // Claude Code supports same-turn steering, but raw stdin injection at an
4494
4431
  // arbitrary busy instant can collide with active signed thinking blocks. The
4495
4432
  // daemon therefore gates busy delivery on Claude stream-json boundaries.
@@ -4503,7 +4440,10 @@ var ClaudeDriver = class {
4503
4440
  return buildClaudeArgs(config, opts);
4504
4441
  }
4505
4442
  async spawn(ctx) {
4506
- const { slockDir, tokenFile, spawnEnv } = await prepareCliTransport(ctx);
4443
+ const { slockDir, tokenFile, spawnEnv } = await prepareCliTransport(
4444
+ ctx,
4445
+ buildClaudeProviderIsolationEnv(ctx)
4446
+ );
4507
4447
  const systemPromptPath = writeClaudeSystemPromptFile(ctx.standingPrompt, slockDir);
4508
4448
  const args = this.buildClaudeArgs(ctx.config, {
4509
4449
  standingPromptFilePath: systemPromptPath
@@ -4549,7 +4489,6 @@ var ClaudeDriver = class {
4549
4489
  }
4550
4490
  buildSystemPrompt(config, _agentId) {
4551
4491
  return buildCliTransportSystemPrompt(config, {
4552
- toolPrefix: "",
4553
4492
  extraCriticalRules: [],
4554
4493
  postStartupNotes: [
4555
4494
  "**Claude runtime note:** While you are busy, Slock batches inbox-count notifications instead of injecting message content. Use `slock message check` at natural breakpoints to pull the pending messages before side-effect actions that depend on current context."
@@ -4562,9 +4501,9 @@ var ClaudeDriver = class {
4562
4501
 
4563
4502
  // src/drivers/codex.ts
4564
4503
  import { spawn as spawn2, execFileSync as execFileSync2, execSync } from "child_process";
4565
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
4566
- import os2 from "os";
4567
- import path5 from "path";
4504
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
4505
+ import os3 from "os";
4506
+ import path6 from "path";
4568
4507
 
4569
4508
  // src/runtimeTurnState.ts
4570
4509
  var RuntimeTurnState = class {
@@ -4721,16 +4660,17 @@ function rawResponseItemProgressEvent(message) {
4721
4660
  function nonEmptyString2(value) {
4722
4661
  return typeof value === "string" && value.length > 0 ? value : void 0;
4723
4662
  }
4663
+ function codexMcpToolName(item) {
4664
+ const tool = nonEmptyString2(item.tool) ?? "unknown";
4665
+ const server = nonEmptyString2(item.server);
4666
+ return server ? `mcp_${server}_${tool}` : `mcp_${tool}`;
4667
+ }
4724
4668
  var CodexEventNormalizer = class {
4725
- mcpToolPrefix;
4726
4669
  currentThreadId = null;
4727
4670
  sessionAnnounced = false;
4728
4671
  streamedAgentMessageIds = /* @__PURE__ */ new Set();
4729
4672
  streamedReasoningIds = /* @__PURE__ */ new Set();
4730
4673
  turnState = new RuntimeTurnState();
4731
- constructor(opts) {
4732
- this.mcpToolPrefix = opts.mcpToolPrefix;
4733
- }
4734
4674
  reset(opts = {}) {
4735
4675
  this.currentThreadId = opts.threadId ?? null;
4736
4676
  this.turnState.reset();
@@ -4890,11 +4830,11 @@ var CodexEventNormalizer = class {
4890
4830
  break;
4891
4831
  case "mcpToolCall":
4892
4832
  if (isStarted) {
4893
- const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
4833
+ const toolName = codexMcpToolName(item);
4894
4834
  events.push({ kind: "tool_call", name: toolName, input: item.arguments });
4895
4835
  }
4896
4836
  if (isCompleted) {
4897
- const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
4837
+ const toolName = codexMcpToolName(item);
4898
4838
  events.push({ kind: "tool_output", name: toolName });
4899
4839
  this.turnState.markToolBoundary();
4900
4840
  }
@@ -4950,9 +4890,9 @@ var CodexEventNormalizer = class {
4950
4890
 
4951
4891
  // src/drivers/codex.ts
4952
4892
  function ensureGitRepoForCodex(workingDirectory, deps = {}) {
4953
- const existsSyncFn = deps.existsSyncFn ?? existsSync3;
4893
+ const existsSyncFn = deps.existsSyncFn ?? existsSync4;
4954
4894
  const execSyncFn = deps.execSyncFn ?? execSync;
4955
- const gitDir = path5.join(workingDirectory, ".git");
4895
+ const gitDir = path6.join(workingDirectory, ".git");
4956
4896
  if (existsSyncFn(gitDir)) return;
4957
4897
  execSyncFn("git init", { cwd: workingDirectory, stdio: "pipe" });
4958
4898
  execSyncFn(
@@ -4965,13 +4905,13 @@ function ensureGitRepoForCodex(workingDirectory, deps = {}) {
4965
4905
  }
4966
4906
  var CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
4967
4907
  function isWindowsSandboxRunner(commandPath) {
4968
- return path5.basename(commandPath).toLowerCase().startsWith("codex-command-runner");
4908
+ return path6.basename(commandPath).toLowerCase().startsWith("codex-command-runner");
4969
4909
  }
4970
4910
  function resolveWindowsNpmCodexEntry(deps = {}) {
4971
- const existsSyncFn = deps.existsSyncFn ?? existsSync3;
4911
+ const existsSyncFn = deps.existsSyncFn ?? existsSync4;
4972
4912
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync2;
4973
4913
  const env = deps.env ?? process.env;
4974
- const winPath = path5.win32;
4914
+ const winPath = path6.win32;
4975
4915
  try {
4976
4916
  const globalRoot = String(execFileSyncFn("npm", ["root", "-g"], {
4977
4917
  encoding: "utf8",
@@ -4990,10 +4930,10 @@ function resolveWindowsNpmCodexEntry(deps = {}) {
4990
4930
  return null;
4991
4931
  }
4992
4932
  function resolveWindowsCodexDesktopEntry(deps = {}) {
4993
- const existsSyncFn = deps.existsSyncFn ?? existsSync3;
4933
+ const existsSyncFn = deps.existsSyncFn ?? existsSync4;
4994
4934
  const env = deps.env ?? process.env;
4995
4935
  const homeDir = deps.homeDir;
4996
- const winPath = path5.win32;
4936
+ const winPath = path6.win32;
4997
4937
  const candidates = [
4998
4938
  env.LOCALAPPDATA ? winPath.join(env.LOCALAPPDATA, "OpenAI", "Codex", "bin", "codex.exe") : null,
4999
4939
  env.USERPROFILE ? winPath.join(env.USERPROFILE, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null,
@@ -5081,8 +5021,6 @@ var CodexDriver = class {
5081
5021
  toLaunchSpec: (modelId) => ({ params: { model: modelId } })
5082
5022
  };
5083
5023
  supportsStdinNotification = true;
5084
- mcpToolPrefix = "mcp_chat_";
5085
- usesSlockCliForCommunication = true;
5086
5024
  busyDeliveryMode = "direct";
5087
5025
  supportsNativeStandingPrompt = true;
5088
5026
  probe() {
@@ -5129,7 +5067,7 @@ var CodexDriver = class {
5129
5067
  initializeRequestId = null;
5130
5068
  pendingThreadRequest = null;
5131
5069
  initialTurnStarted = false;
5132
- normalizer = new CodexEventNormalizer({ mcpToolPrefix: this.mcpToolPrefix });
5070
+ normalizer = new CodexEventNormalizer();
5133
5071
  async spawn(ctx) {
5134
5072
  ensureGitRepoForCodex(ctx.workingDirectory);
5135
5073
  const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
@@ -5221,7 +5159,6 @@ var CodexDriver = class {
5221
5159
  }
5222
5160
  buildSystemPrompt(config, _agentId) {
5223
5161
  return buildCliTransportSystemPrompt(config, {
5224
- toolPrefix: "",
5225
5162
  extraCriticalRules: [],
5226
5163
  postStartupNotes: [
5227
5164
  "**IMPORTANT**: Your process stays alive across turns. While you are working, Slock may write batched inbox-count notifications into the current turn; call `slock message check` at natural breakpoints to read the pending messages."
@@ -5265,9 +5202,9 @@ var CodexDriver = class {
5265
5202
  return detectCodexModels();
5266
5203
  }
5267
5204
  };
5268
- function detectCodexModels(home = os2.homedir()) {
5269
- const cachePath = path5.join(home, ".codex", "models_cache.json");
5270
- const configPath = path5.join(home, ".codex", "config.toml");
5205
+ function detectCodexModels(home = os3.homedir()) {
5206
+ const cachePath = path6.join(home, ".codex", "models_cache.json");
5207
+ const configPath = path6.join(home, ".codex", "config.toml");
5271
5208
  let models = [];
5272
5209
  try {
5273
5210
  const raw = readFileSync2(cachePath, "utf8");
@@ -5361,9 +5298,7 @@ var AntigravityDriver = class {
5361
5298
  toLaunchSpec: (_modelId) => ({ args: [] })
5362
5299
  };
5363
5300
  supportsStdinNotification = false;
5364
- mcpToolPrefix = "";
5365
5301
  busyDeliveryMode = "none";
5366
- usesSlockCliForCommunication = true;
5367
5302
  sessionId = null;
5368
5303
  sessionAnnounced = false;
5369
5304
  probe() {
@@ -5411,7 +5346,6 @@ var AntigravityDriver = class {
5411
5346
  }
5412
5347
  buildSystemPrompt(config, _agentId) {
5413
5348
  return buildCliTransportSystemPrompt(config, {
5414
- toolPrefix: "",
5415
5349
  extraCriticalRules: [],
5416
5350
  postStartupNotes: [
5417
5351
  "**Antigravity 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."
@@ -5450,9 +5384,7 @@ var CopilotDriver = class {
5450
5384
  toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
5451
5385
  };
5452
5386
  supportsStdinNotification = false;
5453
- mcpToolPrefix = "";
5454
5387
  busyDeliveryMode = "none";
5455
- usesSlockCliForCommunication = true;
5456
5388
  sessionId = null;
5457
5389
  sessionAnnounced = false;
5458
5390
  async spawn(ctx) {
@@ -5558,7 +5490,6 @@ var CopilotDriver = class {
5558
5490
  }
5559
5491
  buildSystemPrompt(config, _agentId) {
5560
5492
  return buildCliTransportSystemPrompt(config, {
5561
- toolPrefix: "",
5562
5493
  extraCriticalRules: [],
5563
5494
  postStartupNotes: [
5564
5495
  "**Copilot 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."
@@ -5595,9 +5526,7 @@ var CursorDriver = class {
5595
5526
  toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
5596
5527
  };
5597
5528
  supportsStdinNotification = false;
5598
- mcpToolPrefix = "mcp__chat__";
5599
5529
  busyDeliveryMode = "none";
5600
- usesSlockCliForCommunication = true;
5601
5530
  async spawn(ctx) {
5602
5531
  const args = [
5603
5532
  "--print",
@@ -5683,7 +5612,6 @@ var CursorDriver = class {
5683
5612
  }
5684
5613
  buildSystemPrompt(config, _agentId) {
5685
5614
  return buildCliTransportSystemPrompt(config, {
5686
- toolPrefix: "mcp__chat__",
5687
5615
  extraCriticalRules: [],
5688
5616
  postStartupNotes: [
5689
5617
  "**Cursor 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."
@@ -5728,11 +5656,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
5728
5656
  return parseCursorModelsOutput(String(result.stdout || ""));
5729
5657
  }
5730
5658
  function buildCursorModelProbeEnv(deps = {}) {
5731
- return withWindowsUserEnvironment({
5659
+ return scrubDaemonChildEnv(withWindowsUserEnvironment({
5732
5660
  ...deps.env ?? process.env,
5733
5661
  FORCE_COLOR: "0",
5734
5662
  NO_COLOR: "1"
5735
- }, deps);
5663
+ }, deps));
5736
5664
  }
5737
5665
  function runCursorModelsCommand() {
5738
5666
  return spawnSync("cursor-agent", ["models"], {
@@ -5744,8 +5672,8 @@ function runCursorModelsCommand() {
5744
5672
 
5745
5673
  // src/drivers/gemini.ts
5746
5674
  import { execFileSync as execFileSync3, spawn as spawn6 } from "child_process";
5747
- import { existsSync as existsSync4 } from "fs";
5748
- import path6 from "path";
5675
+ import { existsSync as existsSync5 } from "fs";
5676
+ import path7 from "path";
5749
5677
  async function buildGeminiSpawnEnv(ctx, platform = process.platform) {
5750
5678
  const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" }, platform);
5751
5679
  const launchEnvVars = runtimeConfigToLaunchFields(ctx.config).envVars;
@@ -5787,9 +5715,9 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
5787
5715
  return { command: resolveCommandOnPath("gemini", deps) ?? "gemini", args: commandArgs };
5788
5716
  }
5789
5717
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
5790
- const existsSyncFn = deps.existsSyncFn ?? existsSync4;
5791
- const env = deps.env ?? process.env;
5792
- const winPath = path6.win32;
5718
+ const existsSyncFn = deps.existsSyncFn ?? existsSync5;
5719
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
5720
+ const winPath = path7.win32;
5793
5721
  let geminiEntry = null;
5794
5722
  try {
5795
5723
  const globalRoot = normalizeExecOutput2(execFileSyncFn("npm", ["root", "-g"], {
@@ -5843,9 +5771,7 @@ var GeminiDriver = class {
5843
5771
  toLaunchSpec: (modelId) => modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] }
5844
5772
  };
5845
5773
  supportsStdinNotification = false;
5846
- mcpToolPrefix = "";
5847
5774
  busyDeliveryMode = "none";
5848
- usesSlockCliForCommunication = true;
5849
5775
  sessionId = null;
5850
5776
  sessionAnnounced = false;
5851
5777
  async spawn(ctx) {
@@ -5914,7 +5840,6 @@ var GeminiDriver = class {
5914
5840
  }
5915
5841
  buildSystemPrompt(config, _agentId) {
5916
5842
  return buildCliTransportSystemPrompt(config, {
5917
- toolPrefix: "",
5918
5843
  extraCriticalRules: [],
5919
5844
  postStartupNotes: [
5920
5845
  "**Gemini 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."
@@ -5928,12 +5853,15 @@ var GeminiDriver = class {
5928
5853
  // src/drivers/kimi.ts
5929
5854
  import { randomUUID as randomUUID2 } from "crypto";
5930
5855
  import { spawn as spawn7 } from "child_process";
5931
- import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
5932
- import os3 from "os";
5933
- import path7 from "path";
5856
+ import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
5857
+ import os4 from "os";
5858
+ import path8 from "path";
5934
5859
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
5935
5860
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
5936
5861
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
5862
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
5863
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
5864
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
5937
5865
  function parseToolArguments(raw) {
5938
5866
  if (typeof raw !== "string") return raw;
5939
5867
  try {
@@ -5942,6 +5870,73 @@ function parseToolArguments(raw) {
5942
5870
  return raw;
5943
5871
  }
5944
5872
  }
5873
+ function readKimiConfigSource(home = os4.homedir(), env = process.env) {
5874
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5875
+ if (inlineConfig && inlineConfig.trim()) {
5876
+ return {
5877
+ raw: inlineConfig,
5878
+ explicitPath: null,
5879
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
5880
+ };
5881
+ }
5882
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
5883
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path8.join(home, ".kimi", "config.toml");
5884
+ try {
5885
+ return {
5886
+ raw: readFileSync3(configPath, "utf8"),
5887
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5888
+ sourcePath: configPath
5889
+ };
5890
+ } catch {
5891
+ return {
5892
+ raw: null,
5893
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5894
+ sourcePath: configPath
5895
+ };
5896
+ }
5897
+ }
5898
+ function buildKimiSpawnEnv(env = process.env) {
5899
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
5900
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5901
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
5902
+ return scrubDaemonChildEnv(spawnEnv);
5903
+ }
5904
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
5905
+ return {
5906
+ ...process.env,
5907
+ ...ctx.config.envVars || {},
5908
+ ...overrideEnv || {}
5909
+ };
5910
+ }
5911
+ function buildKimiLaunchOptions(ctx, opts = {}) {
5912
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
5913
+ const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
5914
+ const args = [];
5915
+ let configFilePath = null;
5916
+ let configContent = null;
5917
+ if (source.explicitPath) {
5918
+ configFilePath = source.explicitPath;
5919
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
5920
+ configFilePath = path8.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
5921
+ configContent = source.raw;
5922
+ if (opts.writeGeneratedConfig !== false) {
5923
+ writeFileSync3(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
5924
+ chmodSync(configFilePath, 384);
5925
+ }
5926
+ }
5927
+ if (configFilePath) {
5928
+ args.push("--config-file", configFilePath);
5929
+ }
5930
+ if (ctx.config.model && ctx.config.model !== "default") {
5931
+ args.push("--model", ctx.config.model);
5932
+ }
5933
+ return {
5934
+ args,
5935
+ env: buildKimiSpawnEnv(env),
5936
+ configFilePath,
5937
+ configContent
5938
+ };
5939
+ }
5945
5940
  function resolveKimiSpawn(commandArgs, deps = {}) {
5946
5941
  return {
5947
5942
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -5965,12 +5960,28 @@ var KimiDriver = class {
5965
5960
  };
5966
5961
  model = {
5967
5962
  detectedModelsVerifiedAs: "launchable",
5968
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
5963
+ toLaunchSpec: (modelId, ctx, opts) => {
5964
+ if (!ctx) return { args: ["--model", modelId] };
5965
+ const launchCtx = {
5966
+ ...ctx,
5967
+ config: {
5968
+ ...ctx.config,
5969
+ model: modelId
5970
+ }
5971
+ };
5972
+ const launch = buildKimiLaunchOptions(launchCtx, {
5973
+ home: opts?.home,
5974
+ writeGeneratedConfig: false
5975
+ });
5976
+ return {
5977
+ args: launch.args,
5978
+ env: launch.env,
5979
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
5980
+ };
5981
+ }
5969
5982
  };
5970
5983
  supportsStdinNotification = true;
5971
- mcpToolPrefix = "";
5972
5984
  busyDeliveryMode = "direct";
5973
- usesSlockCliForCommunication = true;
5974
5985
  sessionId = null;
5975
5986
  sessionAnnounced = false;
5976
5987
  promptRequestId = null;
@@ -5979,9 +5990,9 @@ var KimiDriver = class {
5979
5990
  this.sessionId = ctx.config.sessionId || randomUUID2();
5980
5991
  this.sessionAnnounced = false;
5981
5992
  this.promptRequestId = randomUUID2();
5982
- const systemPromptPath = path7.join(ctx.workingDirectory, KIMI_SYSTEM_PROMPT_FILE);
5983
- const agentFilePath = path7.join(ctx.workingDirectory, KIMI_AGENT_FILE);
5984
- if (!isResume || !existsSync5(systemPromptPath)) {
5993
+ const systemPromptPath = path8.join(ctx.workingDirectory, KIMI_SYSTEM_PROMPT_FILE);
5994
+ const agentFilePath = path8.join(ctx.workingDirectory, KIMI_AGENT_FILE);
5995
+ if (!isResume || !existsSync6(systemPromptPath)) {
5985
5996
  writeFileSync3(systemPromptPath, ctx.prompt, "utf8");
5986
5997
  }
5987
5998
  writeFileSync3(agentFilePath, [
@@ -5991,21 +6002,23 @@ var KimiDriver = class {
5991
6002
  ` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
5992
6003
  ""
5993
6004
  ].join("\n"), "utf8");
6005
+ const launch = buildKimiLaunchOptions(ctx);
5994
6006
  const args = [
5995
6007
  "--wire",
5996
6008
  "--yolo",
5997
6009
  "--agent-file",
5998
6010
  agentFilePath,
5999
6011
  "--session",
6000
- this.sessionId
6012
+ this.sessionId,
6013
+ ...launch.args
6001
6014
  ];
6002
6015
  const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
6003
6016
  if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
6004
6017
  args.push("--model", launchRuntimeFields.model);
6005
6018
  }
6006
6019
  const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
6007
- const launch = resolveKimiSpawn(args);
6008
- const proc = spawn7(launch.command, launch.args, {
6020
+ const spawnTarget = resolveKimiSpawn(args);
6021
+ const proc = spawn7(spawnTarget.command, spawnTarget.args, {
6009
6022
  cwd: ctx.workingDirectory,
6010
6023
  stdio: ["pipe", "pipe", "pipe"],
6011
6024
  env: spawnEnv,
@@ -6013,7 +6026,7 @@ var KimiDriver = class {
6013
6026
  // and has an 8191-character command-line limit. Kimi's official
6014
6027
  // installer/uv entrypoint is an executable, so launch it directly and
6015
6028
  // keep prompts on stdin / files instead of routing through cmd.exe.
6016
- shell: launch.shell
6029
+ shell: spawnTarget.shell
6017
6030
  });
6018
6031
  proc.stdin?.write(JSON.stringify({
6019
6032
  jsonrpc: "2.0",
@@ -6116,7 +6129,6 @@ var KimiDriver = class {
6116
6129
  }
6117
6130
  buildSystemPrompt(config, _agentId) {
6118
6131
  return buildCliTransportSystemPrompt(config, {
6119
- toolPrefix: "",
6120
6132
  extraCriticalRules: [],
6121
6133
  postStartupNotes: [],
6122
6134
  includeStdinNotificationSection: true,
@@ -6127,14 +6139,9 @@ var KimiDriver = class {
6127
6139
  return detectKimiModels();
6128
6140
  }
6129
6141
  };
6130
- function detectKimiModels(home = os3.homedir()) {
6131
- const configPath = path7.join(home, ".kimi", "config.toml");
6132
- let raw;
6133
- try {
6134
- raw = readFileSync3(configPath, "utf8");
6135
- } catch {
6136
- return null;
6137
- }
6142
+ function detectKimiModels(home = os4.homedir(), opts = {}) {
6143
+ const raw = readKimiConfigSource(home, opts.env).raw;
6144
+ if (raw === null) return null;
6138
6145
  const models = [];
6139
6146
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
6140
6147
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -6155,11 +6162,9 @@ function detectKimiModels(home = os3.homedir()) {
6155
6162
 
6156
6163
  // src/drivers/opencode.ts
6157
6164
  import { spawn as spawn8, spawnSync as spawnSync2 } from "child_process";
6158
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
6159
- import os4 from "os";
6160
- import path8 from "path";
6161
- var CHAT_MCP_SERVER_NAME = "chat";
6162
- var CHAT_MCP_TOOL_PREFIX = `${CHAT_MCP_SERVER_NAME}_`;
6165
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
6166
+ import os5 from "os";
6167
+ import path9 from "path";
6163
6168
  var SLOCK_AGENT_NAME = "slock";
6164
6169
  var NO_MESSAGE_PROMPT = "No new messages are pending. Stop now.";
6165
6170
  var FIRST_MESSAGE_TASK_PREFIX = "First message task (system-triggered):";
@@ -6187,8 +6192,8 @@ function parseUserOpenCodeConfig(ctx) {
6187
6192
  const raw = runtimeConfigToLaunchFields(ctx.config).envVars?.OPENCODE_CONFIG_CONTENT;
6188
6193
  return parseOpenCodeConfigContent(raw);
6189
6194
  }
6190
- function readLocalOpenCodeConfig(home = os4.homedir()) {
6191
- const configPath = path8.join(home, ".config", "opencode", "opencode.json");
6195
+ function readLocalOpenCodeConfig(home = os5.homedir()) {
6196
+ const configPath = path9.join(home, ".config", "opencode", "opencode.json");
6192
6197
  try {
6193
6198
  return parseOpenCodeConfigContent(readFileSync4(configPath, "utf8"));
6194
6199
  } catch {
@@ -6248,7 +6253,7 @@ function mergeOpenCodeConfigs(localConfig, envConfig) {
6248
6253
  }
6249
6254
  };
6250
6255
  }
6251
- function buildOpenCodeConfig(ctx, home = os4.homedir()) {
6256
+ function buildOpenCodeConfig(ctx, home = os5.homedir()) {
6252
6257
  const userConfig = mergeOpenCodeConfigs(readLocalOpenCodeConfig(home), parseUserOpenCodeConfig(ctx));
6253
6258
  const userAgents = recordField(userConfig.agent);
6254
6259
  const userSlockAgent = recordField(userAgents[SLOCK_AGENT_NAME]);
@@ -6266,7 +6271,7 @@ function buildOpenCodeConfig(ctx, home = os4.homedir()) {
6266
6271
  mcp: recordField(userConfig.mcp)
6267
6272
  };
6268
6273
  }
6269
- async function buildOpenCodeLaunchOptions(ctx, home = os4.homedir(), version = null) {
6274
+ async function buildOpenCodeLaunchOptions(ctx, home = os5.homedir(), version = null) {
6270
6275
  const slock = await prepareCliTransport(ctx, { NO_COLOR: "1" });
6271
6276
  const config = buildOpenCodeConfig(ctx, home);
6272
6277
  const env = {
@@ -6365,7 +6370,7 @@ function formatOpenCodeLabelToken(token) {
6365
6370
  if (/^\d/.test(token)) return token;
6366
6371
  return normalized.charAt(0).toUpperCase() + normalized.slice(1);
6367
6372
  }
6368
- function detectOpenCodeModels(home = os4.homedir(), runCommand = runOpenCodeModelsCommand) {
6373
+ function detectOpenCodeModels(home = os5.homedir(), runCommand = runOpenCodeModelsCommand) {
6369
6374
  const commandResult = runCommand(home);
6370
6375
  if (commandResult.error || commandResult.status !== 0) return null;
6371
6376
  return parseOpenCodeModelsOutput(commandResult.stdout);
@@ -6374,7 +6379,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
6374
6379
  const platform = deps.platform ?? process.platform;
6375
6380
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
6376
6381
  const result = spawnSyncFn("opencode", ["models"], {
6377
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
6382
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
6378
6383
  encoding: "utf8",
6379
6384
  timeout: 5e3,
6380
6385
  shell: platform === "win32"
@@ -6386,11 +6391,11 @@ function runOpenCodeModelsCommand(home, deps = {}) {
6386
6391
  };
6387
6392
  }
6388
6393
  function isWindowsCommandShim(commandPath) {
6389
- const ext = path8.win32.extname(commandPath).toLowerCase();
6394
+ const ext = path9.win32.extname(commandPath).toLowerCase();
6390
6395
  return ext === ".cmd" || ext === ".bat";
6391
6396
  }
6392
6397
  function opencodePackageEntryCandidates(packageRoot) {
6393
- const winPath = path8.win32;
6398
+ const winPath = path9.win32;
6394
6399
  return [
6395
6400
  winPath.join(packageRoot, "bin", "opencode.exe"),
6396
6401
  winPath.join(packageRoot, "bin", "opencode.js"),
@@ -6399,16 +6404,16 @@ function opencodePackageEntryCandidates(packageRoot) {
6399
6404
  ];
6400
6405
  }
6401
6406
  function openCodeSpecForEntry(entry, commandArgs) {
6402
- if (path8.win32.extname(entry).toLowerCase() === ".exe") {
6407
+ if (path9.win32.extname(entry).toLowerCase() === ".exe") {
6403
6408
  return { command: entry, args: commandArgs, shell: false };
6404
6409
  }
6405
6410
  return { command: process.execPath, args: [entry, ...commandArgs], shell: false };
6406
6411
  }
6407
6412
  function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
6408
- const existsSyncFn = deps.existsSyncFn ?? existsSync6;
6413
+ const existsSyncFn = deps.existsSyncFn ?? existsSync7;
6409
6414
  const execFileSyncFn = deps.execFileSyncFn;
6410
6415
  const env = deps.env ?? process.env;
6411
- const winPath = path8.win32;
6416
+ const winPath = path9.win32;
6412
6417
  const candidates = [];
6413
6418
  if (execFileSyncFn) {
6414
6419
  try {
@@ -6436,7 +6441,7 @@ function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
6436
6441
  function extractWindowsShimTargets(commandPath, deps = {}) {
6437
6442
  if (!isWindowsCommandShim(commandPath)) return [];
6438
6443
  const readFileSyncFn = deps.readFileSyncFn ?? readFileSync4;
6439
- const commandDir = path8.win32.dirname(commandPath);
6444
+ const commandDir = path9.win32.dirname(commandPath);
6440
6445
  let raw;
6441
6446
  try {
6442
6447
  raw = String(readFileSyncFn(commandPath, "utf8"));
@@ -6447,7 +6452,7 @@ function extractWindowsShimTargets(commandPath, deps = {}) {
6447
6452
  const dp0Pattern = /%~dp0\\?([^"\r\n]*?opencode\.(?:exe|js|mjs|cjs))/gi;
6448
6453
  for (const match of raw.matchAll(dp0Pattern)) {
6449
6454
  const relative = match[1]?.replace(/^\\+/, "");
6450
- if (relative) candidates.push(path8.win32.normalize(path8.win32.join(commandDir, relative)));
6455
+ if (relative) candidates.push(path9.win32.normalize(path9.win32.join(commandDir, relative)));
6451
6456
  }
6452
6457
  return candidates;
6453
6458
  }
@@ -6461,7 +6466,7 @@ function resolveOpenCodeSpawn(commandArgs, deps = {}) {
6461
6466
  };
6462
6467
  }
6463
6468
  const command = resolveCommandOnPath("opencode", deps);
6464
- if (command && path8.win32.extname(command).toLowerCase() === ".exe") {
6469
+ if (command && path9.win32.extname(command).toLowerCase() === ".exe") {
6465
6470
  return { command, args: commandArgs, shell: false };
6466
6471
  }
6467
6472
  const packageEntry = resolveWindowsOpenCodePackageEntry(command, deps);
@@ -6486,7 +6491,6 @@ function isSystemFirstMessageTask(message) {
6486
6491
  }
6487
6492
  function buildOpenCodeSystemPrompt(config) {
6488
6493
  return buildCliTransportSystemPrompt(config, {
6489
- toolPrefix: CHAT_MCP_TOOL_PREFIX,
6490
6494
  extraCriticalRules: [],
6491
6495
  postStartupNotes: [
6492
6496
  "**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."
@@ -6531,11 +6535,9 @@ var OpenCodeDriver = class {
6531
6535
  }
6532
6536
  };
6533
6537
  supportsStdinNotification = false;
6534
- mcpToolPrefix = CHAT_MCP_TOOL_PREFIX;
6535
6538
  busyDeliveryMode = "none";
6536
6539
  terminateProcessOnTurnEnd = true;
6537
6540
  deferSpawnUntilMessage = true;
6538
- usesSlockCliForCommunication = true;
6539
6541
  shouldDeferWakeMessage(message) {
6540
6542
  return isSystemFirstMessageTask(message);
6541
6543
  }
@@ -6569,7 +6571,7 @@ var OpenCodeDriver = class {
6569
6571
  if (unsupportedMessage) {
6570
6572
  throw new Error(unsupportedMessage);
6571
6573
  }
6572
- const launch = await buildOpenCodeLaunchOptions(ctx, os4.homedir(), version);
6574
+ const launch = await buildOpenCodeLaunchOptions(ctx, os5.homedir(), version);
6573
6575
  const spawnSpec = resolveOpenCodeSpawn(launch.args);
6574
6576
  const proc = spawn8(spawnSpec.command, spawnSpec.args, {
6575
6577
  cwd: ctx.workingDirectory,
@@ -6636,8 +6638,8 @@ var OpenCodeDriver = class {
6636
6638
  // src/drivers/pi.ts
6637
6639
  import { randomUUID as randomUUID3 } from "crypto";
6638
6640
  import { EventEmitter } from "events";
6639
- import { mkdirSync as mkdirSync2, readdirSync } from "fs";
6640
- import path9 from "path";
6641
+ import { mkdirSync as mkdirSync3, readdirSync } from "fs";
6642
+ import path10 from "path";
6641
6643
  import {
6642
6644
  AuthStorage,
6643
6645
  createBashTool,
@@ -6664,7 +6666,7 @@ function createPiSdkEventMappingState(sessionId = null) {
6664
6666
  };
6665
6667
  }
6666
6668
  function buildPiSessionDir(workingDirectory) {
6667
- return path9.join(workingDirectory, PI_SESSION_DIR);
6669
+ return path10.join(workingDirectory, PI_SESSION_DIR);
6668
6670
  }
6669
6671
  async function buildPiSpawnEnv(ctx) {
6670
6672
  return (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
@@ -6686,7 +6688,7 @@ function findPiSessionFile(sessionDir, sessionId) {
6686
6688
  }
6687
6689
  const suffix = `_${sessionId}.jsonl`;
6688
6690
  const match = entries.find((entry) => entry.endsWith(suffix));
6689
- return match ? path9.join(sessionDir, match) : null;
6691
+ return match ? path10.join(sessionDir, match) : null;
6690
6692
  }
6691
6693
  function detectPiModelsFromRegistry(modelRegistry) {
6692
6694
  const models = [];
@@ -6839,11 +6841,11 @@ var PI_IDLE_PROMPT_RETRY_MS = 25;
6839
6841
  var PI_IDLE_PROMPT_MAX_WAIT_MS = 1e3;
6840
6842
  async function createPiAgentSessionForContext(ctx, sessionId) {
6841
6843
  const sessionDir = buildPiSessionDir(ctx.workingDirectory);
6842
- mkdirSync2(sessionDir, { recursive: true });
6844
+ mkdirSync3(sessionDir, { recursive: true });
6843
6845
  const spawnEnv = await buildPiSpawnEnv(ctx);
6844
6846
  const agentDir = spawnEnv.PI_CODING_AGENT_DIR || getAgentDir();
6845
- const authStorage = AuthStorage.create(path9.join(agentDir, "auth.json"));
6846
- const modelRegistry = ModelRegistry.create(authStorage, path9.join(agentDir, "models.json"));
6847
+ const authStorage = AuthStorage.create(path10.join(agentDir, "auth.json"));
6848
+ const modelRegistry = ModelRegistry.create(authStorage, path10.join(agentDir, "models.json"));
6847
6849
  const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
6848
6850
  const model = resolvePiModelFromRegistry(launchRuntimeFields.model, modelRegistry);
6849
6851
  if (launchRuntimeFields.model && launchRuntimeFields.model !== "default" && !model) {
@@ -7086,9 +7088,7 @@ var PiDriver = class {
7086
7088
  toLaunchSpec: (modelId) => ({ params: { model: modelId } })
7087
7089
  };
7088
7090
  supportsStdinNotification = true;
7089
- mcpToolPrefix = "";
7090
7091
  busyDeliveryMode = "direct";
7091
- usesSlockCliForCommunication = true;
7092
7092
  sessionId = null;
7093
7093
  get currentSessionId() {
7094
7094
  return this.sessionId;
@@ -7119,7 +7119,6 @@ var PiDriver = class {
7119
7119
  }
7120
7120
  buildSystemPrompt(config, _agentId) {
7121
7121
  return buildCliTransportSystemPrompt(config, {
7122
- toolPrefix: "",
7123
7122
  extraCriticalRules: [],
7124
7123
  postStartupNotes: [
7125
7124
  "**Pi runtime note:** Slock keeps Pi running as a persistent SDK session. While you are working, Slock may send inbox-count notifications into the current turn; call `slock message check` at natural breakpoints."
@@ -7293,7 +7292,7 @@ function getDriver(runtimeId) {
7293
7292
 
7294
7293
  // src/workspaces.ts
7295
7294
  import { readdir, rm, stat } from "fs/promises";
7296
- import path10 from "path";
7295
+ import path11 from "path";
7297
7296
  function isValidWorkspaceDirectoryName(directoryName) {
7298
7297
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
7299
7298
  }
@@ -7301,7 +7300,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
7301
7300
  if (!isValidWorkspaceDirectoryName(directoryName)) {
7302
7301
  return null;
7303
7302
  }
7304
- return path10.join(dataDir, directoryName);
7303
+ return path11.join(dataDir, directoryName);
7305
7304
  }
7306
7305
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
7307
7306
  return {
@@ -7350,7 +7349,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
7350
7349
  return summary;
7351
7350
  }
7352
7351
  const childSummaries = await Promise.all(
7353
- entries.map((entry) => summarizeWorkspaceEntry(path10.join(dirPath, entry.name), entry))
7352
+ entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
7354
7353
  );
7355
7354
  for (const childSummary of childSummaries) {
7356
7355
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -7369,7 +7368,7 @@ async function scanWorkspaceDirectories(dataDir) {
7369
7368
  if (!entry.isDirectory()) {
7370
7369
  return null;
7371
7370
  }
7372
- const dirPath = path10.join(dataDir, entry.name);
7371
+ const dirPath = path11.join(dataDir, entry.name);
7373
7372
  try {
7374
7373
  const summary = await summarizeWorkspaceDirectory(dirPath);
7375
7374
  return {
@@ -7920,12 +7919,12 @@ function findSessionJsonl(root, predicate) {
7920
7919
  for (const entry of entries) {
7921
7920
  if (++visited > maxEntries) return null;
7922
7921
  if (!entry.isFile() || !predicate(entry.name)) continue;
7923
- return path11.join(dir, entry.name);
7922
+ return path12.join(dir, entry.name);
7924
7923
  }
7925
7924
  for (const entry of entries) {
7926
7925
  if (++visited > maxEntries) return null;
7927
7926
  if (!entry.isDirectory()) continue;
7928
- const found = visit(path11.join(dir, entry.name), depth - 1);
7927
+ const found = visit(path12.join(dir, entry.name), depth - 1);
7929
7928
  if (found) return found;
7930
7929
  }
7931
7930
  return null;
@@ -7938,9 +7937,9 @@ function safeSessionFilename(value) {
7938
7937
  }
7939
7938
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
7940
7939
  try {
7941
- const dir = path11.join(fallbackDir, ".slock", "runtime-sessions");
7942
- mkdirSync3(dir, { recursive: true });
7943
- const filePath = path11.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
7940
+ const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
7941
+ mkdirSync4(dir, { recursive: true });
7942
+ const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
7944
7943
  writeFileSync4(filePath, JSON.stringify({
7945
7944
  type: "runtime_session_handoff",
7946
7945
  runtime,
@@ -7959,8 +7958,20 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
7959
7958
  return null;
7960
7959
  }
7961
7960
  }
7962
- function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
7963
- const directPath = path11.isAbsolute(sessionId) ? sessionId : null;
7961
+ function resolveRuntimeHomeDir(config, defaultHomeDir, workspacePath) {
7962
+ if (isClaudeCustomProviderConfig(config)) {
7963
+ return getClaudeProviderStatePaths(workspacePath).home;
7964
+ }
7965
+ return defaultHomeDir;
7966
+ }
7967
+ function ensureRuntimeHomeDir(config, defaultHomeDir, workspacePath) {
7968
+ if (isClaudeCustomProviderConfig(config)) {
7969
+ return ensureClaudeProviderStatePaths(workspacePath).home;
7970
+ }
7971
+ return defaultHomeDir;
7972
+ }
7973
+ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
7974
+ const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
7964
7975
  if (directPath) {
7965
7976
  try {
7966
7977
  if (statSync(directPath).isFile()) {
@@ -7969,7 +7980,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
7969
7980
  } catch {
7970
7981
  }
7971
7982
  }
7972
- 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;
7983
+ const resolvedPath = runtime === "claude" ? findSessionJsonl(path12.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path12.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
7973
7984
  if (!resolvedPath && fallbackDir) {
7974
7985
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
7975
7986
  if (fallback) return fallback;
@@ -8030,13 +8041,7 @@ function formatThreadContextMessage(message) {
8030
8041
  const senderType = formatVisibleActorType(message.sender_type);
8031
8042
  return `- [msg=${msgId} time=${time}${senderType}] ${formatSenderHandle(message)}: ${message.content}`;
8032
8043
  }
8033
- function mcpToolName(driver, name) {
8034
- return `${driver?.mcpToolPrefix ?? ""}${name}`;
8035
- }
8036
- function communicationCommand(driver, name) {
8037
- if (!driver?.usesSlockCliForCommunication) {
8038
- return mcpToolName(driver, name);
8039
- }
8044
+ function communicationCommand(name) {
8040
8045
  switch (name) {
8041
8046
  case "send_message":
8042
8047
  return "slock message send";
@@ -8050,18 +8055,18 @@ function communicationCommand(driver, name) {
8050
8055
  return `slock ${name.replace(/_/g, " ")}`;
8051
8056
  }
8052
8057
  }
8053
- function dynamicReplyInstruction(driver) {
8054
- return driver?.usesSlockCliForCommunication ? "reply using `slock message send --target <exact target>`" : `reply with ${communicationCommand(driver, "send_message")}`;
8058
+ function dynamicReplyInstruction() {
8059
+ return "reply using `slock message send --target <exact target>`";
8055
8060
  }
8056
- function dynamicClaimInstruction(driver) {
8057
- return driver?.usesSlockCliForCommunication ? "claim the relevant task with `slock task claim`" : `claim the relevant task with ${communicationCommand(driver, "claim_tasks")}`;
8061
+ function dynamicClaimInstruction() {
8062
+ return "claim the relevant task with `slock task claim`";
8058
8063
  }
8059
- function formatIncomingMessage(message, driver) {
8064
+ function formatIncomingMessage(message) {
8060
8065
  const threadJoinPrefix = message.thread_join_context ? [
8061
8066
  `[Slock thread context: you were added to a new thread via @mention.]`,
8062
8067
  `parent: ${message.thread_join_context.parent_target}`,
8063
8068
  `thread: ${message.thread_join_context.thread_target}`,
8064
- `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}")`}`,
8069
+ `suggested next step: slock message read --channel "${message.thread_join_context.suggested_read_history_target}"`,
8065
8070
  "",
8066
8071
  "Parent message:",
8067
8072
  formatThreadContextMessage(message.thread_join_context.parent_message),
@@ -9016,7 +9021,7 @@ var AgentProcessManager = class _AgentProcessManager {
9016
9021
  this.daemonApiKey = daemonApiKey;
9017
9022
  this.serverUrl = opts.serverUrl;
9018
9023
  this.dataDir = opts.dataDir || resolveSlockHomePath("agents");
9019
- this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os5.homedir();
9024
+ this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os6.homedir();
9020
9025
  this.driverResolver = opts.driverResolver || getDriver;
9021
9026
  this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
9022
9027
  this.tracer = opts.tracer ?? noopTracer;
@@ -9918,7 +9923,7 @@ var AgentProcessManager = class _AgentProcessManager {
9918
9923
  );
9919
9924
  wakeMessage = void 0;
9920
9925
  }
9921
- const agentDataDir = path11.join(this.dataDir, agentId);
9926
+ const agentDataDir = path12.join(this.dataDir, agentId);
9922
9927
  await mkdir(agentDataDir, { recursive: true });
9923
9928
  let runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
9924
9929
  const legacyRuntimeProfileControl = runtimeConfig.runtimeProfileControl?.kind === "migration" ? runtimeConfig.runtimeProfileControl : null;
@@ -9932,23 +9937,23 @@ var AgentProcessManager = class _AgentProcessManager {
9932
9937
  );
9933
9938
  runtimeConfig = { ...runtimeConfig, runtimeProfileControl: null };
9934
9939
  }
9935
- const memoryMdPath = path11.join(agentDataDir, "MEMORY.md");
9940
+ const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
9936
9941
  try {
9937
9942
  await access(memoryMdPath);
9938
9943
  } catch {
9939
9944
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
9940
9945
  await writeFile(memoryMdPath, initialMemoryMd);
9941
9946
  }
9942
- const notesDir = path11.join(agentDataDir, "notes");
9947
+ const notesDir = path12.join(agentDataDir, "notes");
9943
9948
  await mkdir(notesDir, { recursive: true });
9944
9949
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
9945
9950
  const seedFiles = buildOnboardingSeedFiles();
9946
9951
  for (const { relativePath, content } of seedFiles) {
9947
- const fullPath = path11.join(agentDataDir, relativePath);
9952
+ const fullPath = path12.join(agentDataDir, relativePath);
9948
9953
  try {
9949
9954
  await access(fullPath);
9950
9955
  } catch {
9951
- await mkdir(path11.dirname(fullPath), { recursive: true });
9956
+ await mkdir(path12.dirname(fullPath), { recursive: true });
9952
9957
  await writeFile(fullPath, content);
9953
9958
  }
9954
9959
  }
@@ -9972,7 +9977,7 @@ var AgentProcessManager = class _AgentProcessManager {
9972
9977
  if (transientWakeMessage) {
9973
9978
  prompt = `System notice received:
9974
9979
 
9975
- ${formatIncomingMessage(wakeMessage, driver)}
9980
+ ${formatIncomingMessage(wakeMessage)}
9976
9981
 
9977
9982
  Respond as appropriate. Complete all your work before stopping.
9978
9983
  ${RESPONSE_TARGET_HINT}`;
@@ -10006,7 +10011,7 @@ Use the inbox/read commands at a natural breakpoint if you choose to inspect tho
10006
10011
  }
10007
10012
  prompt += `
10008
10013
 
10009
- 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)}`;
10014
+ Use ${communicationCommand("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("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()} and ${dynamicClaimInstruction()} before starting work. Otherwise, do NOT send any message in this mode. ${getMessageDeliveryText(driver)}`;
10010
10015
  promptSource = "resume_unread_summary";
10011
10016
  } else if (isResume) {
10012
10017
  prompt = `No new messages while you were away. Nothing to do \u2014 just stop. ${getMessageDeliveryText(driver)}`;
@@ -11084,7 +11089,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11084
11089
  return true;
11085
11090
  }
11086
11091
  async resetWorkspace(agentId) {
11087
- const agentDataDir = path11.join(this.dataDir, agentId);
11092
+ const agentDataDir = path12.join(this.dataDir, agentId);
11088
11093
  try {
11089
11094
  await rm2(agentDataDir, { recursive: true, force: true });
11090
11095
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -11145,7 +11150,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11145
11150
  return result;
11146
11151
  }
11147
11152
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
11148
- const workspacePath = path11.join(this.dataDir, agentId);
11153
+ const workspacePath = path12.join(this.dataDir, agentId);
11154
+ const runtimeHomeDir = resolveRuntimeHomeDir(config, this.runtimeSessionHomeDir, workspacePath);
11149
11155
  return {
11150
11156
  agentId,
11151
11157
  launchId,
@@ -11159,7 +11165,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11159
11165
  path: workspacePath,
11160
11166
  reachable: true
11161
11167
  },
11162
- sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, this.runtimeSessionHomeDir, workspacePath) : null
11168
+ sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, runtimeHomeDir, workspacePath) : null
11163
11169
  }
11164
11170
  };
11165
11171
  }
@@ -11438,7 +11444,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11438
11444
  }
11439
11445
  // Workspace file browsing
11440
11446
  async getFileTree(agentId, dirPath) {
11441
- const agentDir = path11.join(this.dataDir, agentId);
11447
+ const agentDir = path12.join(this.dataDir, agentId);
11442
11448
  try {
11443
11449
  await stat2(agentDir);
11444
11450
  } catch {
@@ -11446,8 +11452,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11446
11452
  }
11447
11453
  let targetDir = agentDir;
11448
11454
  if (dirPath) {
11449
- const resolved = path11.resolve(agentDir, dirPath);
11450
- if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
11455
+ const resolved = path12.resolve(agentDir, dirPath);
11456
+ if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
11451
11457
  return [];
11452
11458
  }
11453
11459
  targetDir = resolved;
@@ -11455,14 +11461,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11455
11461
  return this.listDirectoryChildren(targetDir, agentDir);
11456
11462
  }
11457
11463
  async readFile(agentId, filePath) {
11458
- const agentDir = path11.join(this.dataDir, agentId);
11459
- const resolved = path11.resolve(agentDir, filePath);
11460
- if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
11464
+ const agentDir = path12.join(this.dataDir, agentId);
11465
+ const resolved = path12.resolve(agentDir, filePath);
11466
+ if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
11461
11467
  throw new Error("Access denied");
11462
11468
  }
11463
11469
  const info = await stat2(resolved);
11464
11470
  if (info.isDirectory()) throw new Error("Cannot read a directory");
11465
- const ext = path11.extname(resolved).toLowerCase();
11471
+ const ext = path12.extname(resolved).toLowerCase();
11466
11472
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
11467
11473
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
11468
11474
  const content = await readFile(resolved, "utf-8");
@@ -11495,15 +11501,17 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11495
11501
  };
11496
11502
  async listSkills(agentId, runtimeHint) {
11497
11503
  const agent = this.agents.get(agentId);
11498
- const runtime = runtimeHint || agent?.config.runtime || "claude";
11499
- const home = os5.homedir();
11500
- const workspaceDir = path11.join(this.dataDir, agentId);
11504
+ const idle = this.idleAgentConfigs.get(agentId);
11505
+ const config = agent?.config ?? idle?.config ?? null;
11506
+ const runtime = runtimeHint || config?.runtime || "claude";
11507
+ const workspaceDir = path12.join(this.dataDir, agentId);
11508
+ const home = config ? ensureRuntimeHomeDir(config, os6.homedir(), workspaceDir) : os6.homedir();
11501
11509
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
11502
11510
  const globalResults = await Promise.all(
11503
- paths.global.map((p) => this.scanSkillsDir(path11.join(home, p)))
11511
+ paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
11504
11512
  );
11505
11513
  const workspaceResults = await Promise.all(
11506
- paths.workspace.map((p) => this.scanSkillsDir(path11.join(workspaceDir, p)))
11514
+ paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
11507
11515
  );
11508
11516
  const dedup = (skills) => {
11509
11517
  const seen = /* @__PURE__ */ new Set();
@@ -11532,7 +11540,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11532
11540
  const skills = [];
11533
11541
  for (const entry of entries) {
11534
11542
  if (entry.isDirectory() || entry.isSymbolicLink()) {
11535
- const skillMd = path11.join(dir, entry.name, "SKILL.md");
11543
+ const skillMd = path12.join(dir, entry.name, "SKILL.md");
11536
11544
  try {
11537
11545
  const content = await readFile(skillMd, "utf-8");
11538
11546
  const skill = this.parseSkillMd(entry.name, content);
@@ -11543,7 +11551,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11543
11551
  } else if (entry.name.endsWith(".md")) {
11544
11552
  const cmdName = entry.name.replace(/\.md$/, "");
11545
11553
  try {
11546
- const content = await readFile(path11.join(dir, entry.name), "utf-8");
11554
+ const content = await readFile(path12.join(dir, entry.name), "utf-8");
11547
11555
  const skill = this.parseSkillMd(cmdName, content);
11548
11556
  skill.sourcePath = dir;
11549
11557
  skills.push(skill);
@@ -12947,12 +12955,12 @@ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
12947
12955
  const traceSource = options.transient ? `stdin_${mode}_transient_delivery` : `stdin_${mode}_delivery`;
12948
12956
  const prompt = formatRuntimeProfileControlPrompt(messages) ?? (messages.length === 1 ? `New message received:
12949
12957
 
12950
- ${formatIncomingMessage(messages[0], ap.driver)}
12958
+ ${formatIncomingMessage(messages[0])}
12951
12959
 
12952
12960
  Respond as appropriate. Complete all your work before stopping.
12953
12961
  ${RESPONSE_TARGET_HINT}` : `New messages received:
12954
12962
 
12955
- ${messages.map((message) => formatIncomingMessage(message, ap.driver)).join("\n")}
12963
+ ${messages.map((message) => formatIncomingMessage(message)).join("\n")}
12956
12964
 
12957
12965
  Respond as appropriate. Complete all your work before stopping.
12958
12966
  ${RESPONSE_TARGET_HINT}`);
@@ -13023,8 +13031,8 @@ ${RESPONSE_TARGET_HINT}`);
13023
13031
  const nodes = [];
13024
13032
  for (const entry of entries) {
13025
13033
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
13026
- const fullPath = path11.join(dir, entry.name);
13027
- const relativePath = path11.relative(rootDir, fullPath);
13034
+ const fullPath = path12.join(dir, entry.name);
13035
+ const relativePath = path12.relative(rootDir, fullPath);
13028
13036
  let info;
13029
13037
  try {
13030
13038
  info = await stat2(fullPath);
@@ -13398,9 +13406,9 @@ var ReminderCache = class {
13398
13406
 
13399
13407
  // src/machineLock.ts
13400
13408
  import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
13401
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
13402
- import os6 from "os";
13403
- import path12 from "path";
13409
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
13410
+ import os7 from "os";
13411
+ import path13 from "path";
13404
13412
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
13405
13413
  var DaemonMachineLockConflictError = class extends Error {
13406
13414
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -13422,7 +13430,7 @@ function resolveDefaultMachineStateRoot() {
13422
13430
  return resolveSlockHomePath("machines");
13423
13431
  }
13424
13432
  function ownerPath(lockDir) {
13425
- return path12.join(lockDir, "owner.json");
13433
+ return path13.join(lockDir, "owner.json");
13426
13434
  }
13427
13435
  function readOwner(lockDir) {
13428
13436
  try {
@@ -13452,17 +13460,17 @@ function acquireDaemonMachineLock(options) {
13452
13460
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
13453
13461
  const fingerprint = apiKeyFingerprint(options.apiKey);
13454
13462
  const lockId = getDaemonMachineLockId(options.apiKey);
13455
- const machineDir = path12.join(rootDir, lockId);
13456
- const lockDir = path12.join(machineDir, "daemon.lock");
13463
+ const machineDir = path13.join(rootDir, lockId);
13464
+ const lockDir = path13.join(machineDir, "daemon.lock");
13457
13465
  const token = randomUUID4();
13458
- mkdirSync4(machineDir, { recursive: true });
13466
+ mkdirSync5(machineDir, { recursive: true });
13459
13467
  for (let attempt = 0; attempt < 2; attempt += 1) {
13460
13468
  try {
13461
- mkdirSync4(lockDir);
13469
+ mkdirSync5(lockDir);
13462
13470
  const owner = {
13463
13471
  pid: process.pid,
13464
13472
  token,
13465
- hostname: os6.hostname(),
13473
+ hostname: os7.hostname(),
13466
13474
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
13467
13475
  serverUrl: options.serverUrl,
13468
13476
  apiKeyFingerprint: fingerprint.slice(0, 16)
@@ -13513,8 +13521,8 @@ function acquireDaemonMachineLock(options) {
13513
13521
  }
13514
13522
 
13515
13523
  // src/localTraceSink.ts
13516
- import { appendFileSync, mkdirSync as mkdirSync5, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
13517
- import path13 from "path";
13524
+ import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
13525
+ import path14 from "path";
13518
13526
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
13519
13527
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
13520
13528
  var DEFAULT_MAX_FILES = 8;
@@ -13551,7 +13559,7 @@ var LocalRotatingTraceSink = class {
13551
13559
  currentSize = 0;
13552
13560
  sequence = 0;
13553
13561
  constructor(options) {
13554
- this.traceDir = path13.join(options.machineDir, "traces");
13562
+ this.traceDir = path14.join(options.machineDir, "traces");
13555
13563
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
13556
13564
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
13557
13565
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -13577,11 +13585,11 @@ var LocalRotatingTraceSink = class {
13577
13585
  return this.currentFile;
13578
13586
  }
13579
13587
  ensureFile(nextBytes) {
13580
- mkdirSync5(this.traceDir, { recursive: true, mode: 448 });
13588
+ mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
13581
13589
  const nowMs = this.nowMsProvider();
13582
13590
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
13583
13591
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
13584
- this.currentFile = path13.join(
13592
+ this.currentFile = path14.join(
13585
13593
  this.traceDir,
13586
13594
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
13587
13595
  );
@@ -13596,7 +13604,7 @@ var LocalRotatingTraceSink = class {
13596
13604
  const excess = files.length - this.maxFiles;
13597
13605
  if (excess <= 0) return;
13598
13606
  for (const file of files.slice(0, excess)) {
13599
- rmSync4(path13.join(this.traceDir, file), { force: true });
13607
+ rmSync4(path14.join(this.traceDir, file), { force: true });
13600
13608
  }
13601
13609
  }
13602
13610
  };
@@ -13687,7 +13695,7 @@ function isDiagnosticErrorAttr(key) {
13687
13695
  import { createHash as createHash6, randomUUID as randomUUID5 } from "crypto";
13688
13696
  import { gzipSync } from "zlib";
13689
13697
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
13690
- import path14 from "path";
13698
+ import path15 from "path";
13691
13699
 
13692
13700
  // src/chatBridgeRequest.ts
13693
13701
  var DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS = Number.parseInt(
@@ -13787,8 +13795,8 @@ async function executeResponseRequest(url, init, {
13787
13795
  }
13788
13796
 
13789
13797
  // src/directUploadCapability.ts
13790
- function joinUrl(base, path16) {
13791
- return `${base.replace(/\/+$/, "")}${path16}`;
13798
+ function joinUrl(base, path17) {
13799
+ return `${base.replace(/\/+$/, "")}${path17}`;
13792
13800
  }
13793
13801
  function jsonHeaders(apiKey) {
13794
13802
  return {
@@ -14007,7 +14015,7 @@ var DaemonTraceBundleUploader = class {
14007
14015
  }, nextMs);
14008
14016
  }
14009
14017
  async findUploadCandidates() {
14010
- const traceDir = path14.join(this.options.machineDir, "traces");
14018
+ const traceDir = path15.join(this.options.machineDir, "traces");
14011
14019
  let names;
14012
14020
  try {
14013
14021
  names = await readdir3(traceDir);
@@ -14019,8 +14027,8 @@ var DaemonTraceBundleUploader = class {
14019
14027
  const currentFile = this.options.currentFileProvider?.();
14020
14028
  const candidates = [];
14021
14029
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
14022
- const file = path14.join(traceDir, name);
14023
- if (currentFile && path14.resolve(file) === path14.resolve(currentFile)) continue;
14030
+ const file = path15.join(traceDir, name);
14031
+ if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
14024
14032
  if (await this.isUploaded(file)) continue;
14025
14033
  try {
14026
14034
  const info = await stat3(file);
@@ -14094,8 +14102,8 @@ var DaemonTraceBundleUploader = class {
14094
14102
  }
14095
14103
  }
14096
14104
  uploadStatePath(file) {
14097
- const stateDir = path14.join(this.options.machineDir, "trace-uploads");
14098
- return path14.join(stateDir, `${path14.basename(file)}.uploaded.json`);
14105
+ const stateDir = path15.join(this.options.machineDir, "trace-uploads");
14106
+ return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
14099
14107
  }
14100
14108
  async isUploaded(file) {
14101
14109
  try {
@@ -14107,9 +14115,9 @@ var DaemonTraceBundleUploader = class {
14107
14115
  }
14108
14116
  async markUploaded(file, metadata) {
14109
14117
  const stateFile = this.uploadStatePath(file);
14110
- await mkdir2(path14.dirname(stateFile), { recursive: true, mode: 448 });
14118
+ await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
14111
14119
  await writeFile2(stateFile, `${JSON.stringify({
14112
- file: path14.basename(file),
14120
+ file: path15.basename(file),
14113
14121
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
14114
14122
  ...metadata
14115
14123
  }, null, 2)}
@@ -14176,7 +14184,7 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
14176
14184
  spanAttrs: ["running_agents_count", "idle_agents_count"]
14177
14185
  }
14178
14186
  };
14179
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
14187
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
14180
14188
  var RunnerCredentialMintError2 = class extends Error {
14181
14189
  code;
14182
14190
  retryable;
@@ -14212,9 +14220,9 @@ function runnerCredentialErrorDetail2(error) {
14212
14220
  async function waitForRunnerCredentialRetry2() {
14213
14221
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
14214
14222
  }
14215
- function parseDaemonCliArgs(args) {
14223
+ function parseDaemonCliArgs(args, env = {}) {
14216
14224
  let serverUrl = "";
14217
- let apiKey = "";
14225
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
14218
14226
  for (let i = 0; i < args.length; i++) {
14219
14227
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
14220
14228
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -14231,13 +14239,13 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
14231
14239
  }
14232
14240
  }
14233
14241
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
14234
- const thisDir = path15.dirname(fileURLToPath(moduleUrl));
14235
- const bundledDistPath = path15.resolve(thisDir, "cli", "index.js");
14242
+ const thisDir = path16.dirname(fileURLToPath(moduleUrl));
14243
+ const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
14236
14244
  try {
14237
14245
  accessSync(bundledDistPath);
14238
14246
  return bundledDistPath;
14239
14247
  } catch {
14240
- const workspaceDistPath = path15.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
14248
+ const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
14241
14249
  accessSync(workspaceDistPath);
14242
14250
  return workspaceDistPath;
14243
14251
  }
@@ -14426,7 +14434,7 @@ var DaemonCore = class {
14426
14434
  }
14427
14435
  resolveMachineStateRoot() {
14428
14436
  if (this.options.machineStateDir) return this.options.machineStateDir;
14429
- if (this.options.dataDir) return path15.join(path15.dirname(this.options.dataDir), "machines");
14437
+ if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
14430
14438
  return resolveDefaultMachineStateRoot();
14431
14439
  }
14432
14440
  shouldEnableLocalTrace() {
@@ -14453,7 +14461,7 @@ var DaemonCore = class {
14453
14461
  sink: this.localTraceSink
14454
14462
  }));
14455
14463
  this.agentManager.setTracer(this.tracer);
14456
- this.agentManager.setCliTransportTraceDir(path15.join(machineDir, "traces"));
14464
+ this.agentManager.setCliTransportTraceDir(path16.join(machineDir, "traces"));
14457
14465
  }
14458
14466
  installTraceBundleUploader(machineDir) {
14459
14467
  if (!this.shouldEnableLocalTrace()) return;
@@ -14930,8 +14938,8 @@ var DaemonCore = class {
14930
14938
  capabilities: ["agent:start", "agent:stop", "agent:deliver", "workspace:files"],
14931
14939
  runtimes,
14932
14940
  runningAgents: runningAgentIds,
14933
- hostname: this.options.hostname ?? os7.hostname(),
14934
- os: this.options.osDescription ?? `${os7.platform()} ${os7.arch()}`,
14941
+ hostname: this.options.hostname ?? os8.hostname(),
14942
+ os: this.options.osDescription ?? `${os8.platform()} ${os8.arch()}`,
14935
14943
  daemonVersion: this.daemonVersion,
14936
14944
  ...this.computerVersion ? { computerVersion: this.computerVersion } : {}
14937
14945
  });
@@ -14995,6 +15003,8 @@ var DaemonCore = class {
14995
15003
  };
14996
15004
 
14997
15005
  export {
15006
+ DAEMON_API_KEY_ENV,
15007
+ scrubDaemonAuthEnv,
14998
15008
  subscribeDaemonLogs,
14999
15009
  resolveWorkspaceDirectoryPath,
15000
15010
  scanWorkspaceDirectories,