@slock-ai/daemon 0.57.3 → 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";
@@ -1058,6 +1058,143 @@ function shortMessageId(value) {
1058
1058
  return value.slice(0, 8);
1059
1059
  }
1060
1060
 
1061
+ // ../shared/src/externalAgentIntegration.ts
1062
+ import { z as z2 } from "zod";
1063
+ var EXTERNAL_AGENT_COMMS_PROTOCOL_VERSION = "agent-comms-core.v1";
1064
+ var EXTERNAL_AGENT_PROOF_SCHEMA_VERSION = "agent-proof.v1";
1065
+ var EXTERNAL_RUNTIME_INTEGRATION_MANIFEST_SCHEMA = "slock-external-runtime-integration.v1";
1066
+ var EXTERNAL_AGENT_WAKE_EVENT_SCHEMA = "slock-external-agent-wake-event.v1";
1067
+ var externalAgentCommsModeValues = ["spawn-core", "attach-core", "import-core"];
1068
+ var externalAgentIntegrationPatternValues = ["external-harness-plugin", "integrated-runtime"];
1069
+ var externalAgentIsolationValues = ["profile-scoped", "session-scoped"];
1070
+ var externalAgentWakeAdapterKindValues = ["claude-code-channels", "hermes-in-process"];
1071
+ var externalAgentWakeProofLevelValues = [
1072
+ "server_delivered",
1073
+ "harness_accepted",
1074
+ "wake_injected"
1075
+ ];
1076
+ var externalAgentProofLevelValues = [
1077
+ ...externalAgentWakeProofLevelValues,
1078
+ "model_seen"
1079
+ ];
1080
+ var externalAgentCommsLifecycleStateValues = [
1081
+ "unbound",
1082
+ "bound_stopped",
1083
+ "starting",
1084
+ "connected_replaying",
1085
+ "listening_idle",
1086
+ "handoff_pending",
1087
+ "degraded_backoff",
1088
+ "stopping",
1089
+ "stopped",
1090
+ "auth_revoked"
1091
+ ];
1092
+ var externalAgentLifecycleSourceValues = ["server_core", "comms_core", "wake_adapter", "server"];
1093
+ var externalAgentLifecycleProvenanceValues = [
1094
+ "slock_core",
1095
+ "adapter_observed",
1096
+ "agent_reported"
1097
+ ];
1098
+ var externalAgentAdapterFailureValues = [
1099
+ "no_session",
1100
+ "busy",
1101
+ "injection_failed",
1102
+ "protocol_mismatch",
1103
+ "auth_revoked"
1104
+ ];
1105
+ var externalAgentServerApiModeValues = ["interim-agent-api-events", "agent-inbox-protocol"];
1106
+ var nonEmptyStringSchema = z2.string().min(1);
1107
+ var isoTimestampSchema = z2.string().refine((value) => !Number.isNaN(Date.parse(value)), {
1108
+ message: "must be a parseable timestamp"
1109
+ });
1110
+ var externalRuntimeIntegrationManifestSchema = z2.object({
1111
+ schema: z2.literal(EXTERNAL_RUNTIME_INTEGRATION_MANIFEST_SCHEMA),
1112
+ runtimeId: nonEmptyStringSchema,
1113
+ integrationPattern: z2.enum(externalAgentIntegrationPatternValues),
1114
+ commsMode: z2.enum(externalAgentCommsModeValues),
1115
+ commsProtocolVersion: z2.literal(EXTERNAL_AGENT_COMMS_PROTOCOL_VERSION),
1116
+ proofSchemaVersion: z2.literal(EXTERNAL_AGENT_PROOF_SCHEMA_VERSION),
1117
+ minSlockCliVersion: nonEmptyStringSchema,
1118
+ multiplex: z2.boolean(),
1119
+ agentIsolation: z2.enum(externalAgentIsolationValues),
1120
+ bridgeLifecycle: z2.object({
1121
+ explicitStartOnly: z2.literal(true),
1122
+ oneShotCommandsBridgeIndependent: z2.literal(true),
1123
+ requiresBridgeFailureCodes: z2.array(z2.enum(["BRIDGE_NOT_RUNNING", "NO_CORE_SESSION"])).min(1),
1124
+ autoStartDefault: z2.literal(false)
1125
+ }).strict(),
1126
+ serverApi: z2.object({
1127
+ mode: z2.enum(externalAgentServerApiModeValues),
1128
+ deliveryOnly: z2.boolean(),
1129
+ cursorAuthority: z2.literal("model_seen_only")
1130
+ }).strict(),
1131
+ wakeAdapter: z2.object({
1132
+ kind: z2.enum(externalAgentWakeAdapterKindValues),
1133
+ protocol: nonEmptyStringSchema,
1134
+ requiresInteractiveSession: z2.boolean().optional()
1135
+ }).strict()
1136
+ }).strict().superRefine((value, ctx) => {
1137
+ if (!value.bridgeLifecycle.requiresBridgeFailureCodes.includes("BRIDGE_NOT_RUNNING")) {
1138
+ ctx.addIssue({
1139
+ code: "custom",
1140
+ path: ["bridgeLifecycle", "requiresBridgeFailureCodes"],
1141
+ message: "bridge-dependent surfaces must include BRIDGE_NOT_RUNNING"
1142
+ });
1143
+ }
1144
+ if (!value.bridgeLifecycle.requiresBridgeFailureCodes.includes("NO_CORE_SESSION")) {
1145
+ ctx.addIssue({
1146
+ code: "custom",
1147
+ path: ["bridgeLifecycle", "requiresBridgeFailureCodes"],
1148
+ message: "bridge-dependent surfaces must include NO_CORE_SESSION"
1149
+ });
1150
+ }
1151
+ if (value.serverApi.mode === "interim-agent-api-events" && value.serverApi.deliveryOnly !== true) {
1152
+ ctx.addIssue({
1153
+ code: "custom",
1154
+ path: ["serverApi", "deliveryOnly"],
1155
+ message: "interim agent-api events mode must be delivery-only"
1156
+ });
1157
+ }
1158
+ });
1159
+ var externalAgentWakeEventBaseSchema = z2.object({
1160
+ schema: z2.literal(EXTERNAL_AGENT_WAKE_EVENT_SCHEMA),
1161
+ eventId: nonEmptyStringSchema,
1162
+ attemptId: nonEmptyStringSchema,
1163
+ messageId: nonEmptyStringSchema,
1164
+ agentId: nonEmptyStringSchema,
1165
+ profile: nonEmptyStringSchema,
1166
+ coreSessionId: nonEmptyStringSchema,
1167
+ adapterInstance: nonEmptyStringSchema,
1168
+ runtimeSession: nonEmptyStringSchema.nullable(),
1169
+ occurredAt: isoTimestampSchema,
1170
+ lifecycleState: z2.enum(externalAgentCommsLifecycleStateValues),
1171
+ authority: z2.object({
1172
+ source: z2.enum(externalAgentLifecycleSourceValues),
1173
+ provenance: z2.enum(externalAgentLifecycleProvenanceValues)
1174
+ }).strict()
1175
+ }).strict();
1176
+ var externalAgentProofEventEnvelopeSchema = externalAgentWakeEventBaseSchema.extend({
1177
+ kind: z2.literal("proof"),
1178
+ proofLevel: z2.enum(externalAgentWakeProofLevelValues),
1179
+ outcome: z2.literal("ok"),
1180
+ failureMeta: z2.undefined().optional(),
1181
+ reason: z2.string().optional()
1182
+ });
1183
+ var externalAgentFailedWakeEventEnvelopeSchema = externalAgentWakeEventBaseSchema.extend({
1184
+ kind: z2.literal("wake_attempt"),
1185
+ proofLevel: z2.undefined().optional(),
1186
+ outcome: z2.literal("failed"),
1187
+ failureMeta: z2.object({
1188
+ failureClass: z2.enum(externalAgentAdapterFailureValues),
1189
+ retryAfterMs: z2.number().int().positive().optional()
1190
+ }).strict(),
1191
+ reason: nonEmptyStringSchema
1192
+ });
1193
+ var externalAgentWakeEventEnvelopeSchema = z2.discriminatedUnion("kind", [
1194
+ externalAgentProofEventEnvelopeSchema,
1195
+ externalAgentFailedWakeEventEnvelopeSchema
1196
+ ]);
1197
+
1061
1198
  // ../shared/src/translationLanguages.ts
1062
1199
  var SUPPORTED_TRANSLATION_LANGUAGE_CODES = [
1063
1200
  "en",
@@ -1164,6 +1301,7 @@ var RUNTIME_MODELS = {
1164
1301
  { id: "claude-opus-4-8", label: "Claude Opus 4.8" },
1165
1302
  { id: "claude-opus-4-7", label: "Claude Opus 4.7" },
1166
1303
  { id: "claude-opus-4-6", label: "Claude Opus 4.6" },
1304
+ { id: "fable", label: "Claude Fable" },
1167
1305
  { id: "sonnet", label: "Claude Sonnet" },
1168
1306
  { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
1169
1307
  { id: "haiku", label: "Claude Haiku" },
@@ -1366,11 +1504,11 @@ var DISPLAY_PLAN_CONFIG = {
1366
1504
  };
1367
1505
 
1368
1506
  // src/agentProcessManager.ts
1369
- 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";
1370
1508
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
1371
1509
  import { createHash as createHash3 } from "crypto";
1372
- import path11 from "path";
1373
- import os5 from "os";
1510
+ import path12 from "path";
1511
+ import os6 from "os";
1374
1512
 
1375
1513
  // src/drivers/claude.ts
1376
1514
  import { spawn } from "child_process";
@@ -1704,9 +1842,6 @@ function buildSlockCliGuideSections(options) {
1704
1842
  }
1705
1843
 
1706
1844
  // src/drivers/systemPrompt.ts
1707
- function toolRef(prefix, name) {
1708
- return `${prefix}${name}`;
1709
- }
1710
1845
  function runtimeContextLines(config) {
1711
1846
  const ctx = config.runtimeContext;
1712
1847
  if (!ctx) return [];
@@ -1728,10 +1863,8 @@ function runtimeContextLines(config) {
1728
1863
  if (ctx.workspacePath) lines.push(`- Workspace: ${ctx.workspacePath}`);
1729
1864
  return lines.length > 4 ? lines : [];
1730
1865
  }
1731
- function buildPrompt(config, variant, opts) {
1732
- const isCli = variant === "cli";
1733
- const t = (name) => toolRef(opts.toolPrefix, name);
1734
- const taskClaimCmd = isCli ? "`slock task claim`" : `\`${t("claim_tasks")}\``;
1866
+ function buildPrompt(config, opts) {
1867
+ const taskClaimCmd = "`slock task claim`";
1735
1868
  const cliGuideSections = buildSlockCliGuideSections({
1736
1869
  // The daemon prompt audience is always managed-runner regardless of
1737
1870
  // CLI vs MCP variant — both are daemon-spawned. The self-hosted-runner
@@ -1743,162 +1876,42 @@ function buildPrompt(config, variant, opts) {
1743
1876
  },
1744
1877
  taskClaimCmd
1745
1878
  });
1746
- const sendCmd = isCli ? "`slock message send`" : `\`${t("send_message")}\``;
1747
- const readCmd = isCli ? "`slock message read`" : `\`${t("read_history")}\``;
1748
- const checkCmd = isCli ? "`slock message check`" : `\`${t("check_messages")}\``;
1749
- const inboxCheckCmd = isCli ? "`slock inbox check`" : checkCmd;
1750
- const taskCreateCmd = isCli ? "`slock task create`" : `\`${t("create_tasks")}\``;
1751
- const taskUpdateCmd = isCli ? "`slock task update`" : `\`${t("update_task_status")}\``;
1752
- const serverInfoCmd = isCli ? "`slock server info`" : `\`${t("list_server")}\``;
1753
- const scheduleReminderCmd = isCli ? "`slock reminder schedule`" : `\`${t("schedule_reminder")}\``;
1754
- const listRemindersCmd = isCli ? "`slock reminder list`" : `\`${t("list_reminders")}\``;
1755
- const cancelReminderCmd = isCli ? "`slock reminder cancel`" : `\`${t("cancel_reminder")}\``;
1879
+ const checkCmd = "`slock message check`";
1880
+ const inboxCheckCmd = "`slock inbox check`";
1756
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.";
1757
- 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 = [
1758
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.",
1759
1885
  ...opts.extraCriticalRules,
1760
1886
  "- Use only the provided `slock` CLI commands for messaging.",
1761
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.",
1762
1888
  "- Always claim a task via `slock task claim` before starting work on it. If the claim fails, move on to a different task."
1763
- ] : [
1764
- `- Always communicate through ${sendCmd}. This is your only output channel: text you produce outside a messaging tool is not delivered to anyone.`,
1765
- ...opts.extraCriticalRules,
1766
- "- Use only the provided MCP tools for messaging \u2014 they are already available and ready.",
1767
- `- Always claim a task via ${taskClaimCmd} before starting work on it. If the claim fails, move on to a different task.`
1768
1889
  ];
1769
1890
  const runtimeProfileControl = config.runtimeProfileControl?.kind === "daemon_release_notice" ? config.runtimeProfileControl : null;
1770
1891
  const runtimeProfileControlStartupStep = runtimeProfileControl ? [
1771
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."
1772
1893
  ] : [];
1773
- const startupSteps = isCli ? [
1894
+ const startupSteps = [
1774
1895
  ...runtimeProfileControlStartupStep,
1775
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.",
1776
1897
  "2. Read MEMORY.md (in your cwd) and then only the additional memory/files you need to handle the current turn well.",
1777
- `3. If there is no concrete incoming message to handle, stop and wait. ${messageDeliveryText}`,
1898
+ noConcreteMessageStartupStep,
1778
1899
  "4. When you receive a message, process it and reply with `slock message send`.",
1779
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."
1780
- ] : [
1781
- ...runtimeProfileControlStartupStep,
1782
- `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.`,
1783
- "2. Read MEMORY.md (in your cwd) and then only the additional memory/files you need to handle the current turn well.",
1784
- `3. If there is no concrete incoming message to handle, stop and wait. ${messageDeliveryText}`,
1785
- `4. When you receive a message, process it and reply with ${sendCmd}.`,
1786
- "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."
1787
1901
  ];
1788
- const communicationSection = isCli ? cliGuideSections.communication : `## Communication \u2014 MCP tools ONLY
1789
-
1790
- You have MCP tools from the "chat" server. Use ONLY these for communication:
1791
-
1792
- 1. **${checkCmd}** \u2014 Non-blocking check for new messages. Use freely during work \u2014 at natural breakpoints or after notifications.
1793
- 2. **${sendCmd}** \u2014 Send a message to a channel or DM.
1794
- 3. **${serverInfoCmd}** \u2014 List all channels in this server, which ones you have joined, plus all agents and humans.
1795
- 4. **\`${t("join_channel")}\`** \u2014 Join a visible public channel. This only affects your own agent membership.
1796
- 5. **\`${t("leave_channel")}\`** \u2014 Leave a regular channel you have joined. This only affects your own agent membership.
1797
- 6. **${readCmd}** \u2014 Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` pagination and \`around\` for centered context.
1798
- 7. **\`${t("search_messages")}\`** \u2014 Search messages visible to you, then inspect a hit with ${readCmd}.
1799
- 8. **\`${t("list_tasks")}\`** \u2014 View a channel's task board.
1800
- 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).
1801
- 10. **${taskClaimCmd}** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
1802
- 11. **\`${t("unclaim_task")}\`** \u2014 Release your claim on a task.
1803
- 12. **${taskUpdateCmd}** \u2014 Change a task's status (e.g. to in_review or done).
1804
- 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.
1805
- 14. **\`${t("view_file")}\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
1806
- 15. **${scheduleReminderCmd}** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
1807
- 16. **${listRemindersCmd}** \u2014 List your reminders.
1808
- 17. **${cancelReminderCmd}** \u2014 Cancel one of your reminders by ID.`;
1809
- const credentialHygieneSection = isCli ? cliGuideSections.credentialHygiene : `### Credential hygiene
1810
-
1811
- **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.
1812
-
1813
- 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.`;
1814
- const reminderSection = isCli ? cliGuideSections.reminders : `### Reminders
1815
-
1816
- 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.
1817
- 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.
1818
- 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.
1819
- 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.`;
1820
- const sendingMessagesSection = isCli ? cliGuideSections.sendingMessages : `### Sending messages
1821
-
1822
- - **Reply to a channel**: \`${t("send_message")}(target="#channel-name", content="...")\`
1823
- - **Reply to a DM**: \`${t("send_message")}(target="dm:@peer-name", content="...")\`
1824
- - **Reply in a thread**: \`${t("send_message")}(target="#channel:shortid", content="...")\` or \`${t("send_message")}(target="dm:@peer:shortid", content="...")\`
1825
- - **Start a NEW DM**: \`${t("send_message")}(target="dm:@person-name", content="...")\`
1826
-
1827
- **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.`;
1828
- const threadsSection = isCli ? cliGuideSections.threads : `### Threads
1829
-
1830
- Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
1831
-
1832
- - **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
1833
- - 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.
1834
- - **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.
1835
- - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
1836
- - You can read thread history via ${readCmd} with the same thread target.
1837
- - Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
1838
- const discoverySection = isCli ? cliGuideSections.discovery : `### Discovering people and channels
1839
-
1840
- Call ${serverInfoCmd} to see all channels in this server, which ones you have joined, other agents, and humans.
1841
- 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")}\`.
1842
- 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.`;
1843
- const channelAwarenessSection = isCli ? cliGuideSections.channelAwareness : `### Channel awareness
1844
-
1845
- Each channel has a **name** and optionally a **description** that define its purpose (visible via ${serverInfoCmd}). Respect them:
1846
- - **Reply in context** \u2014 always respond in the channel/thread the message came from.
1847
- - **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.
1848
- - If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
1849
- const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
1850
-
1851
- 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
1852
-
1853
- 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.`;
1854
- const readingHistorySection = isCli ? cliGuideSections.readingHistory : `### Reading history
1855
-
1856
- Use ${readCmd} with the \`channel\` parameter set to \`"#channel-name"\`, \`"dm:@peer-name"\`, or a thread target like \`"#channel:shortid"\`.
1857
-
1858
- To jump directly to a specific hit with nearby context, pass \`around\` set to a message ID or seq number.`;
1859
- const historicalReferenceSection = isCli ? cliGuideSections.historicalReferences : `### Historical references
1860
-
1861
- 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.`;
1862
- const tasksSection = isCli ? cliGuideSections.tasks : `### Tasks
1863
-
1864
- 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.
1865
-
1866
- **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.
1867
-
1868
- **What you see in messages:**
1869
- - A message already marked as a task: \`@Alice: Fix the login bug [task #3 status=in_progress]\`
1870
- - A regular message (no task suffix): \`@Alice: Can someone look into the login bug?\`
1871
- - A system notification about task changes: \`\u{1F4CB} Alice converted a message to task #3 "Fix the login bug"\`
1872
-
1873
- 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.
1874
-
1875
- ${readCmd} shows messages in their current state. If a message was later converted to a task, it will show the \`[task #N ...]\` suffix.
1876
-
1877
- **Status flow:** \`todo\` \u2192 \`in_progress\` \u2192 \`in_review\` \u2192 \`done\`
1878
-
1879
- **Assignee** is independent from status \u2014 a task can be claimed or unclaimed at any status except \`done\`.
1880
-
1881
- **Workflow:**
1882
- 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)
1883
- 2. If the claim fails, someone else is working on it \u2014 move on to another task
1884
- 3. Post updates in the task's thread via ${sendCmd} with \`target="#channel:msgShortId"\`
1885
- 4. When done, set status to \`in_review\` so a human can validate via ${taskUpdateCmd}
1886
- 5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
1887
-
1888
- **What ${taskCreateCmd} really means:**
1889
- - Tasks live in the same chat flow as messages. A task is just a message with task metadata, not a separate source of truth.
1890
- - ${taskCreateCmd} is a convenience helper for a specific sequence: create a brand-new message, then publish that new message as a task-message.
1891
- - ${taskCreateCmd} only creates the task \u2014 to own it, call ${taskClaimCmd} afterward.
1892
- - Typical uses for ${taskCreateCmd} are breaking down a larger task into parallel subtasks, or batch-creating genuinely new work for others to claim.
1893
- - If someone already sent the work item as a message, just claim that existing message/task instead of creating a new one.
1894
- - If the work already exists as a message, reuse it via ${taskClaimCmd} with the message ID.
1895
-
1896
- **Creating new tasks:**
1897
- - 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.
1898
- - If a message already shows a \`[task #N ...]\` suffix, claim \`#N\` if it is yours to take; otherwise move on.
1899
- - Before calling ${taskCreateCmd}, first check whether the work already exists on the task board or is already being handled.
1900
- - Reuse existing tasks and threads instead of creating duplicates.
1901
- - 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;
1902
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.
1903
1916
 
1904
1917
  ## Who you are
@@ -2012,10 +2025,11 @@ While you are working, the daemon may write a batched, content-free inbox update
2012
2025
 
2013
2026
  How to handle these:
2014
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.
2015
- - 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.
2016
2030
  - If a message you explicitly read is higher priority, pivot to it. If not, continue your current work.`;
2017
2031
  } else {
2018
- 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 ...]\``;
2019
2033
  prompt += `
2020
2034
 
2021
2035
  ## Message Notifications
@@ -2026,7 +2040,8 @@ ${notifyExample}
2026
2040
 
2027
2041
  How to handle these:
2028
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.
2029
- - 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.
2030
2045
  - If a message you explicitly read is higher priority, you may pivot to it. If not, continue your current work.`;
2031
2046
  }
2032
2047
  }
@@ -2039,7 +2054,7 @@ ${config.description}. This may evolve.`;
2039
2054
  return prompt;
2040
2055
  }
2041
2056
  function buildCliSystemPrompt(config, opts) {
2042
- return buildPrompt(config, "cli", opts);
2057
+ return buildPrompt(config, opts);
2043
2058
  }
2044
2059
 
2045
2060
  // src/slockHome.ts
@@ -2081,6 +2096,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
2081
2096
  return candidates.filter((candidate) => existsSync(candidate.path));
2082
2097
  }
2083
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
+
2084
2112
  // src/agentCredentialProxy.ts
2085
2113
  import { randomBytes } from "crypto";
2086
2114
  import http from "http";
@@ -3567,7 +3595,9 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
3567
3595
  var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
3568
3596
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
3569
3597
  var RAW_CREDENTIAL_ENV_DENYLIST = [
3570
- "SLOCK_AGENT_CREDENTIAL_KEY"
3598
+ "SLOCK_AGENT_TOKEN",
3599
+ "SLOCK_AGENT_CREDENTIAL_KEY",
3600
+ "SLOCK_AGENT_CREDENTIAL_KEY_FILE"
3571
3601
  ];
3572
3602
  var cachedOpencliBinPath;
3573
3603
  function resolveOpencliBinPath() {
@@ -3782,7 +3812,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
3782
3812
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
3783
3813
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
3784
3814
  };
3785
- delete spawnEnv.SLOCK_AGENT_TOKEN;
3815
+ scrubDaemonChildEnv(spawnEnv);
3786
3816
  for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
3787
3817
  delete spawnEnv[key];
3788
3818
  }
@@ -4211,7 +4241,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
4211
4241
  }
4212
4242
  function resolveCommandOnPath(command, deps = {}) {
4213
4243
  const platform = deps.platform ?? process.platform;
4214
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
4244
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
4215
4245
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
4216
4246
  const existsSyncFn = deps.existsSyncFn ?? existsSync2;
4217
4247
  if (platform === "win32") {
@@ -4237,7 +4267,7 @@ function firstExistingPath(candidates, deps = {}) {
4237
4267
  return null;
4238
4268
  }
4239
4269
  function readCommandVersion(command, args = [], deps = {}) {
4240
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
4270
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
4241
4271
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
4242
4272
  try {
4243
4273
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -4331,6 +4361,52 @@ function buildClaudeSpawnSpec(claudeCommand, platform = process.platform) {
4331
4361
  };
4332
4362
  }
4333
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
+
4334
4410
  // src/drivers/claude.ts
4335
4411
  var ClaudeDriver = class {
4336
4412
  id = "claude";
@@ -4351,8 +4427,6 @@ var ClaudeDriver = class {
4351
4427
  toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
4352
4428
  };
4353
4429
  supportsStdinNotification = true;
4354
- mcpToolPrefix = "";
4355
- usesSlockCliForCommunication = true;
4356
4430
  // Claude Code supports same-turn steering, but raw stdin injection at an
4357
4431
  // arbitrary busy instant can collide with active signed thinking blocks. The
4358
4432
  // daemon therefore gates busy delivery on Claude stream-json boundaries.
@@ -4366,7 +4440,10 @@ var ClaudeDriver = class {
4366
4440
  return buildClaudeArgs(config, opts);
4367
4441
  }
4368
4442
  async spawn(ctx) {
4369
- const { slockDir, tokenFile, spawnEnv } = await prepareCliTransport(ctx);
4443
+ const { slockDir, tokenFile, spawnEnv } = await prepareCliTransport(
4444
+ ctx,
4445
+ buildClaudeProviderIsolationEnv(ctx)
4446
+ );
4370
4447
  const systemPromptPath = writeClaudeSystemPromptFile(ctx.standingPrompt, slockDir);
4371
4448
  const args = this.buildClaudeArgs(ctx.config, {
4372
4449
  standingPromptFilePath: systemPromptPath
@@ -4412,7 +4489,6 @@ var ClaudeDriver = class {
4412
4489
  }
4413
4490
  buildSystemPrompt(config, _agentId) {
4414
4491
  return buildCliTransportSystemPrompt(config, {
4415
- toolPrefix: "",
4416
4492
  extraCriticalRules: [],
4417
4493
  postStartupNotes: [
4418
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."
@@ -4425,9 +4501,9 @@ var ClaudeDriver = class {
4425
4501
 
4426
4502
  // src/drivers/codex.ts
4427
4503
  import { spawn as spawn2, execFileSync as execFileSync2, execSync } from "child_process";
4428
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
4429
- import os2 from "os";
4430
- import path5 from "path";
4504
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
4505
+ import os3 from "os";
4506
+ import path6 from "path";
4431
4507
 
4432
4508
  // src/runtimeTurnState.ts
4433
4509
  var RuntimeTurnState = class {
@@ -4584,16 +4660,17 @@ function rawResponseItemProgressEvent(message) {
4584
4660
  function nonEmptyString2(value) {
4585
4661
  return typeof value === "string" && value.length > 0 ? value : void 0;
4586
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
+ }
4587
4668
  var CodexEventNormalizer = class {
4588
- mcpToolPrefix;
4589
4669
  currentThreadId = null;
4590
4670
  sessionAnnounced = false;
4591
4671
  streamedAgentMessageIds = /* @__PURE__ */ new Set();
4592
4672
  streamedReasoningIds = /* @__PURE__ */ new Set();
4593
4673
  turnState = new RuntimeTurnState();
4594
- constructor(opts) {
4595
- this.mcpToolPrefix = opts.mcpToolPrefix;
4596
- }
4597
4674
  reset(opts = {}) {
4598
4675
  this.currentThreadId = opts.threadId ?? null;
4599
4676
  this.turnState.reset();
@@ -4753,11 +4830,11 @@ var CodexEventNormalizer = class {
4753
4830
  break;
4754
4831
  case "mcpToolCall":
4755
4832
  if (isStarted) {
4756
- const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
4833
+ const toolName = codexMcpToolName(item);
4757
4834
  events.push({ kind: "tool_call", name: toolName, input: item.arguments });
4758
4835
  }
4759
4836
  if (isCompleted) {
4760
- const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
4837
+ const toolName = codexMcpToolName(item);
4761
4838
  events.push({ kind: "tool_output", name: toolName });
4762
4839
  this.turnState.markToolBoundary();
4763
4840
  }
@@ -4813,9 +4890,9 @@ var CodexEventNormalizer = class {
4813
4890
 
4814
4891
  // src/drivers/codex.ts
4815
4892
  function ensureGitRepoForCodex(workingDirectory, deps = {}) {
4816
- const existsSyncFn = deps.existsSyncFn ?? existsSync3;
4893
+ const existsSyncFn = deps.existsSyncFn ?? existsSync4;
4817
4894
  const execSyncFn = deps.execSyncFn ?? execSync;
4818
- const gitDir = path5.join(workingDirectory, ".git");
4895
+ const gitDir = path6.join(workingDirectory, ".git");
4819
4896
  if (existsSyncFn(gitDir)) return;
4820
4897
  execSyncFn("git init", { cwd: workingDirectory, stdio: "pipe" });
4821
4898
  execSyncFn(
@@ -4828,13 +4905,13 @@ function ensureGitRepoForCodex(workingDirectory, deps = {}) {
4828
4905
  }
4829
4906
  var CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
4830
4907
  function isWindowsSandboxRunner(commandPath) {
4831
- return path5.basename(commandPath).toLowerCase().startsWith("codex-command-runner");
4908
+ return path6.basename(commandPath).toLowerCase().startsWith("codex-command-runner");
4832
4909
  }
4833
4910
  function resolveWindowsNpmCodexEntry(deps = {}) {
4834
- const existsSyncFn = deps.existsSyncFn ?? existsSync3;
4911
+ const existsSyncFn = deps.existsSyncFn ?? existsSync4;
4835
4912
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync2;
4836
4913
  const env = deps.env ?? process.env;
4837
- const winPath = path5.win32;
4914
+ const winPath = path6.win32;
4838
4915
  try {
4839
4916
  const globalRoot = String(execFileSyncFn("npm", ["root", "-g"], {
4840
4917
  encoding: "utf8",
@@ -4853,10 +4930,10 @@ function resolveWindowsNpmCodexEntry(deps = {}) {
4853
4930
  return null;
4854
4931
  }
4855
4932
  function resolveWindowsCodexDesktopEntry(deps = {}) {
4856
- const existsSyncFn = deps.existsSyncFn ?? existsSync3;
4933
+ const existsSyncFn = deps.existsSyncFn ?? existsSync4;
4857
4934
  const env = deps.env ?? process.env;
4858
4935
  const homeDir = deps.homeDir;
4859
- const winPath = path5.win32;
4936
+ const winPath = path6.win32;
4860
4937
  const candidates = [
4861
4938
  env.LOCALAPPDATA ? winPath.join(env.LOCALAPPDATA, "OpenAI", "Codex", "bin", "codex.exe") : null,
4862
4939
  env.USERPROFILE ? winPath.join(env.USERPROFILE, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null,
@@ -4944,8 +5021,6 @@ var CodexDriver = class {
4944
5021
  toLaunchSpec: (modelId) => ({ params: { model: modelId } })
4945
5022
  };
4946
5023
  supportsStdinNotification = true;
4947
- mcpToolPrefix = "mcp_chat_";
4948
- usesSlockCliForCommunication = true;
4949
5024
  busyDeliveryMode = "direct";
4950
5025
  supportsNativeStandingPrompt = true;
4951
5026
  probe() {
@@ -4992,7 +5067,7 @@ var CodexDriver = class {
4992
5067
  initializeRequestId = null;
4993
5068
  pendingThreadRequest = null;
4994
5069
  initialTurnStarted = false;
4995
- normalizer = new CodexEventNormalizer({ mcpToolPrefix: this.mcpToolPrefix });
5070
+ normalizer = new CodexEventNormalizer();
4996
5071
  async spawn(ctx) {
4997
5072
  ensureGitRepoForCodex(ctx.workingDirectory);
4998
5073
  const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
@@ -5084,7 +5159,6 @@ var CodexDriver = class {
5084
5159
  }
5085
5160
  buildSystemPrompt(config, _agentId) {
5086
5161
  return buildCliTransportSystemPrompt(config, {
5087
- toolPrefix: "",
5088
5162
  extraCriticalRules: [],
5089
5163
  postStartupNotes: [
5090
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."
@@ -5128,9 +5202,9 @@ var CodexDriver = class {
5128
5202
  return detectCodexModels();
5129
5203
  }
5130
5204
  };
5131
- function detectCodexModels(home = os2.homedir()) {
5132
- const cachePath = path5.join(home, ".codex", "models_cache.json");
5133
- 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");
5134
5208
  let models = [];
5135
5209
  try {
5136
5210
  const raw = readFileSync2(cachePath, "utf8");
@@ -5224,9 +5298,7 @@ var AntigravityDriver = class {
5224
5298
  toLaunchSpec: (_modelId) => ({ args: [] })
5225
5299
  };
5226
5300
  supportsStdinNotification = false;
5227
- mcpToolPrefix = "";
5228
5301
  busyDeliveryMode = "none";
5229
- usesSlockCliForCommunication = true;
5230
5302
  sessionId = null;
5231
5303
  sessionAnnounced = false;
5232
5304
  probe() {
@@ -5274,7 +5346,6 @@ var AntigravityDriver = class {
5274
5346
  }
5275
5347
  buildSystemPrompt(config, _agentId) {
5276
5348
  return buildCliTransportSystemPrompt(config, {
5277
- toolPrefix: "",
5278
5349
  extraCriticalRules: [],
5279
5350
  postStartupNotes: [
5280
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."
@@ -5313,9 +5384,7 @@ var CopilotDriver = class {
5313
5384
  toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
5314
5385
  };
5315
5386
  supportsStdinNotification = false;
5316
- mcpToolPrefix = "";
5317
5387
  busyDeliveryMode = "none";
5318
- usesSlockCliForCommunication = true;
5319
5388
  sessionId = null;
5320
5389
  sessionAnnounced = false;
5321
5390
  async spawn(ctx) {
@@ -5421,7 +5490,6 @@ var CopilotDriver = class {
5421
5490
  }
5422
5491
  buildSystemPrompt(config, _agentId) {
5423
5492
  return buildCliTransportSystemPrompt(config, {
5424
- toolPrefix: "",
5425
5493
  extraCriticalRules: [],
5426
5494
  postStartupNotes: [
5427
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."
@@ -5458,9 +5526,7 @@ var CursorDriver = class {
5458
5526
  toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
5459
5527
  };
5460
5528
  supportsStdinNotification = false;
5461
- mcpToolPrefix = "mcp__chat__";
5462
5529
  busyDeliveryMode = "none";
5463
- usesSlockCliForCommunication = true;
5464
5530
  async spawn(ctx) {
5465
5531
  const args = [
5466
5532
  "--print",
@@ -5546,7 +5612,6 @@ var CursorDriver = class {
5546
5612
  }
5547
5613
  buildSystemPrompt(config, _agentId) {
5548
5614
  return buildCliTransportSystemPrompt(config, {
5549
- toolPrefix: "mcp__chat__",
5550
5615
  extraCriticalRules: [],
5551
5616
  postStartupNotes: [
5552
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."
@@ -5591,11 +5656,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
5591
5656
  return parseCursorModelsOutput(String(result.stdout || ""));
5592
5657
  }
5593
5658
  function buildCursorModelProbeEnv(deps = {}) {
5594
- return withWindowsUserEnvironment({
5659
+ return scrubDaemonChildEnv(withWindowsUserEnvironment({
5595
5660
  ...deps.env ?? process.env,
5596
5661
  FORCE_COLOR: "0",
5597
5662
  NO_COLOR: "1"
5598
- }, deps);
5663
+ }, deps));
5599
5664
  }
5600
5665
  function runCursorModelsCommand() {
5601
5666
  return spawnSync("cursor-agent", ["models"], {
@@ -5607,8 +5672,8 @@ function runCursorModelsCommand() {
5607
5672
 
5608
5673
  // src/drivers/gemini.ts
5609
5674
  import { execFileSync as execFileSync3, spawn as spawn6 } from "child_process";
5610
- import { existsSync as existsSync4 } from "fs";
5611
- import path6 from "path";
5675
+ import { existsSync as existsSync5 } from "fs";
5676
+ import path7 from "path";
5612
5677
  async function buildGeminiSpawnEnv(ctx, platform = process.platform) {
5613
5678
  const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" }, platform);
5614
5679
  const launchEnvVars = runtimeConfigToLaunchFields(ctx.config).envVars;
@@ -5650,9 +5715,9 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
5650
5715
  return { command: resolveCommandOnPath("gemini", deps) ?? "gemini", args: commandArgs };
5651
5716
  }
5652
5717
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
5653
- const existsSyncFn = deps.existsSyncFn ?? existsSync4;
5654
- const env = deps.env ?? process.env;
5655
- const winPath = path6.win32;
5718
+ const existsSyncFn = deps.existsSyncFn ?? existsSync5;
5719
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
5720
+ const winPath = path7.win32;
5656
5721
  let geminiEntry = null;
5657
5722
  try {
5658
5723
  const globalRoot = normalizeExecOutput2(execFileSyncFn("npm", ["root", "-g"], {
@@ -5706,9 +5771,7 @@ var GeminiDriver = class {
5706
5771
  toLaunchSpec: (modelId) => modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] }
5707
5772
  };
5708
5773
  supportsStdinNotification = false;
5709
- mcpToolPrefix = "";
5710
5774
  busyDeliveryMode = "none";
5711
- usesSlockCliForCommunication = true;
5712
5775
  sessionId = null;
5713
5776
  sessionAnnounced = false;
5714
5777
  async spawn(ctx) {
@@ -5777,7 +5840,6 @@ var GeminiDriver = class {
5777
5840
  }
5778
5841
  buildSystemPrompt(config, _agentId) {
5779
5842
  return buildCliTransportSystemPrompt(config, {
5780
- toolPrefix: "",
5781
5843
  extraCriticalRules: [],
5782
5844
  postStartupNotes: [
5783
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."
@@ -5791,12 +5853,15 @@ var GeminiDriver = class {
5791
5853
  // src/drivers/kimi.ts
5792
5854
  import { randomUUID as randomUUID2 } from "crypto";
5793
5855
  import { spawn as spawn7 } from "child_process";
5794
- import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
5795
- import os3 from "os";
5796
- 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";
5797
5859
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
5798
5860
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
5799
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";
5800
5865
  function parseToolArguments(raw) {
5801
5866
  if (typeof raw !== "string") return raw;
5802
5867
  try {
@@ -5805,6 +5870,73 @@ function parseToolArguments(raw) {
5805
5870
  return raw;
5806
5871
  }
5807
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
+ }
5808
5940
  function resolveKimiSpawn(commandArgs, deps = {}) {
5809
5941
  return {
5810
5942
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -5828,12 +5960,28 @@ var KimiDriver = class {
5828
5960
  };
5829
5961
  model = {
5830
5962
  detectedModelsVerifiedAs: "launchable",
5831
- 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
+ }
5832
5982
  };
5833
5983
  supportsStdinNotification = true;
5834
- mcpToolPrefix = "";
5835
5984
  busyDeliveryMode = "direct";
5836
- usesSlockCliForCommunication = true;
5837
5985
  sessionId = null;
5838
5986
  sessionAnnounced = false;
5839
5987
  promptRequestId = null;
@@ -5842,9 +5990,9 @@ var KimiDriver = class {
5842
5990
  this.sessionId = ctx.config.sessionId || randomUUID2();
5843
5991
  this.sessionAnnounced = false;
5844
5992
  this.promptRequestId = randomUUID2();
5845
- const systemPromptPath = path7.join(ctx.workingDirectory, KIMI_SYSTEM_PROMPT_FILE);
5846
- const agentFilePath = path7.join(ctx.workingDirectory, KIMI_AGENT_FILE);
5847
- 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)) {
5848
5996
  writeFileSync3(systemPromptPath, ctx.prompt, "utf8");
5849
5997
  }
5850
5998
  writeFileSync3(agentFilePath, [
@@ -5854,21 +6002,23 @@ var KimiDriver = class {
5854
6002
  ` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
5855
6003
  ""
5856
6004
  ].join("\n"), "utf8");
6005
+ const launch = buildKimiLaunchOptions(ctx);
5857
6006
  const args = [
5858
6007
  "--wire",
5859
6008
  "--yolo",
5860
6009
  "--agent-file",
5861
6010
  agentFilePath,
5862
6011
  "--session",
5863
- this.sessionId
6012
+ this.sessionId,
6013
+ ...launch.args
5864
6014
  ];
5865
6015
  const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
5866
6016
  if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
5867
6017
  args.push("--model", launchRuntimeFields.model);
5868
6018
  }
5869
6019
  const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
5870
- const launch = resolveKimiSpawn(args);
5871
- const proc = spawn7(launch.command, launch.args, {
6020
+ const spawnTarget = resolveKimiSpawn(args);
6021
+ const proc = spawn7(spawnTarget.command, spawnTarget.args, {
5872
6022
  cwd: ctx.workingDirectory,
5873
6023
  stdio: ["pipe", "pipe", "pipe"],
5874
6024
  env: spawnEnv,
@@ -5876,7 +6026,7 @@ var KimiDriver = class {
5876
6026
  // and has an 8191-character command-line limit. Kimi's official
5877
6027
  // installer/uv entrypoint is an executable, so launch it directly and
5878
6028
  // keep prompts on stdin / files instead of routing through cmd.exe.
5879
- shell: launch.shell
6029
+ shell: spawnTarget.shell
5880
6030
  });
5881
6031
  proc.stdin?.write(JSON.stringify({
5882
6032
  jsonrpc: "2.0",
@@ -5979,7 +6129,6 @@ var KimiDriver = class {
5979
6129
  }
5980
6130
  buildSystemPrompt(config, _agentId) {
5981
6131
  return buildCliTransportSystemPrompt(config, {
5982
- toolPrefix: "",
5983
6132
  extraCriticalRules: [],
5984
6133
  postStartupNotes: [],
5985
6134
  includeStdinNotificationSection: true,
@@ -5990,14 +6139,9 @@ var KimiDriver = class {
5990
6139
  return detectKimiModels();
5991
6140
  }
5992
6141
  };
5993
- function detectKimiModels(home = os3.homedir()) {
5994
- const configPath = path7.join(home, ".kimi", "config.toml");
5995
- let raw;
5996
- try {
5997
- raw = readFileSync3(configPath, "utf8");
5998
- } catch {
5999
- return null;
6000
- }
6142
+ function detectKimiModels(home = os4.homedir(), opts = {}) {
6143
+ const raw = readKimiConfigSource(home, opts.env).raw;
6144
+ if (raw === null) return null;
6001
6145
  const models = [];
6002
6146
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
6003
6147
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -6018,11 +6162,9 @@ function detectKimiModels(home = os3.homedir()) {
6018
6162
 
6019
6163
  // src/drivers/opencode.ts
6020
6164
  import { spawn as spawn8, spawnSync as spawnSync2 } from "child_process";
6021
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
6022
- import os4 from "os";
6023
- import path8 from "path";
6024
- var CHAT_MCP_SERVER_NAME = "chat";
6025
- 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";
6026
6168
  var SLOCK_AGENT_NAME = "slock";
6027
6169
  var NO_MESSAGE_PROMPT = "No new messages are pending. Stop now.";
6028
6170
  var FIRST_MESSAGE_TASK_PREFIX = "First message task (system-triggered):";
@@ -6050,8 +6192,8 @@ function parseUserOpenCodeConfig(ctx) {
6050
6192
  const raw = runtimeConfigToLaunchFields(ctx.config).envVars?.OPENCODE_CONFIG_CONTENT;
6051
6193
  return parseOpenCodeConfigContent(raw);
6052
6194
  }
6053
- function readLocalOpenCodeConfig(home = os4.homedir()) {
6054
- 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");
6055
6197
  try {
6056
6198
  return parseOpenCodeConfigContent(readFileSync4(configPath, "utf8"));
6057
6199
  } catch {
@@ -6111,7 +6253,7 @@ function mergeOpenCodeConfigs(localConfig, envConfig) {
6111
6253
  }
6112
6254
  };
6113
6255
  }
6114
- function buildOpenCodeConfig(ctx, home = os4.homedir()) {
6256
+ function buildOpenCodeConfig(ctx, home = os5.homedir()) {
6115
6257
  const userConfig = mergeOpenCodeConfigs(readLocalOpenCodeConfig(home), parseUserOpenCodeConfig(ctx));
6116
6258
  const userAgents = recordField(userConfig.agent);
6117
6259
  const userSlockAgent = recordField(userAgents[SLOCK_AGENT_NAME]);
@@ -6129,7 +6271,7 @@ function buildOpenCodeConfig(ctx, home = os4.homedir()) {
6129
6271
  mcp: recordField(userConfig.mcp)
6130
6272
  };
6131
6273
  }
6132
- async function buildOpenCodeLaunchOptions(ctx, home = os4.homedir(), version = null) {
6274
+ async function buildOpenCodeLaunchOptions(ctx, home = os5.homedir(), version = null) {
6133
6275
  const slock = await prepareCliTransport(ctx, { NO_COLOR: "1" });
6134
6276
  const config = buildOpenCodeConfig(ctx, home);
6135
6277
  const env = {
@@ -6228,7 +6370,7 @@ function formatOpenCodeLabelToken(token) {
6228
6370
  if (/^\d/.test(token)) return token;
6229
6371
  return normalized.charAt(0).toUpperCase() + normalized.slice(1);
6230
6372
  }
6231
- function detectOpenCodeModels(home = os4.homedir(), runCommand = runOpenCodeModelsCommand) {
6373
+ function detectOpenCodeModels(home = os5.homedir(), runCommand = runOpenCodeModelsCommand) {
6232
6374
  const commandResult = runCommand(home);
6233
6375
  if (commandResult.error || commandResult.status !== 0) return null;
6234
6376
  return parseOpenCodeModelsOutput(commandResult.stdout);
@@ -6237,7 +6379,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
6237
6379
  const platform = deps.platform ?? process.platform;
6238
6380
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
6239
6381
  const result = spawnSyncFn("opencode", ["models"], {
6240
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
6382
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
6241
6383
  encoding: "utf8",
6242
6384
  timeout: 5e3,
6243
6385
  shell: platform === "win32"
@@ -6249,11 +6391,11 @@ function runOpenCodeModelsCommand(home, deps = {}) {
6249
6391
  };
6250
6392
  }
6251
6393
  function isWindowsCommandShim(commandPath) {
6252
- const ext = path8.win32.extname(commandPath).toLowerCase();
6394
+ const ext = path9.win32.extname(commandPath).toLowerCase();
6253
6395
  return ext === ".cmd" || ext === ".bat";
6254
6396
  }
6255
6397
  function opencodePackageEntryCandidates(packageRoot) {
6256
- const winPath = path8.win32;
6398
+ const winPath = path9.win32;
6257
6399
  return [
6258
6400
  winPath.join(packageRoot, "bin", "opencode.exe"),
6259
6401
  winPath.join(packageRoot, "bin", "opencode.js"),
@@ -6262,16 +6404,16 @@ function opencodePackageEntryCandidates(packageRoot) {
6262
6404
  ];
6263
6405
  }
6264
6406
  function openCodeSpecForEntry(entry, commandArgs) {
6265
- if (path8.win32.extname(entry).toLowerCase() === ".exe") {
6407
+ if (path9.win32.extname(entry).toLowerCase() === ".exe") {
6266
6408
  return { command: entry, args: commandArgs, shell: false };
6267
6409
  }
6268
6410
  return { command: process.execPath, args: [entry, ...commandArgs], shell: false };
6269
6411
  }
6270
6412
  function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
6271
- const existsSyncFn = deps.existsSyncFn ?? existsSync6;
6413
+ const existsSyncFn = deps.existsSyncFn ?? existsSync7;
6272
6414
  const execFileSyncFn = deps.execFileSyncFn;
6273
6415
  const env = deps.env ?? process.env;
6274
- const winPath = path8.win32;
6416
+ const winPath = path9.win32;
6275
6417
  const candidates = [];
6276
6418
  if (execFileSyncFn) {
6277
6419
  try {
@@ -6299,7 +6441,7 @@ function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
6299
6441
  function extractWindowsShimTargets(commandPath, deps = {}) {
6300
6442
  if (!isWindowsCommandShim(commandPath)) return [];
6301
6443
  const readFileSyncFn = deps.readFileSyncFn ?? readFileSync4;
6302
- const commandDir = path8.win32.dirname(commandPath);
6444
+ const commandDir = path9.win32.dirname(commandPath);
6303
6445
  let raw;
6304
6446
  try {
6305
6447
  raw = String(readFileSyncFn(commandPath, "utf8"));
@@ -6310,7 +6452,7 @@ function extractWindowsShimTargets(commandPath, deps = {}) {
6310
6452
  const dp0Pattern = /%~dp0\\?([^"\r\n]*?opencode\.(?:exe|js|mjs|cjs))/gi;
6311
6453
  for (const match of raw.matchAll(dp0Pattern)) {
6312
6454
  const relative = match[1]?.replace(/^\\+/, "");
6313
- 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)));
6314
6456
  }
6315
6457
  return candidates;
6316
6458
  }
@@ -6324,7 +6466,7 @@ function resolveOpenCodeSpawn(commandArgs, deps = {}) {
6324
6466
  };
6325
6467
  }
6326
6468
  const command = resolveCommandOnPath("opencode", deps);
6327
- if (command && path8.win32.extname(command).toLowerCase() === ".exe") {
6469
+ if (command && path9.win32.extname(command).toLowerCase() === ".exe") {
6328
6470
  return { command, args: commandArgs, shell: false };
6329
6471
  }
6330
6472
  const packageEntry = resolveWindowsOpenCodePackageEntry(command, deps);
@@ -6349,7 +6491,6 @@ function isSystemFirstMessageTask(message) {
6349
6491
  }
6350
6492
  function buildOpenCodeSystemPrompt(config) {
6351
6493
  return buildCliTransportSystemPrompt(config, {
6352
- toolPrefix: CHAT_MCP_TOOL_PREFIX,
6353
6494
  extraCriticalRules: [],
6354
6495
  postStartupNotes: [
6355
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."
@@ -6394,11 +6535,9 @@ var OpenCodeDriver = class {
6394
6535
  }
6395
6536
  };
6396
6537
  supportsStdinNotification = false;
6397
- mcpToolPrefix = CHAT_MCP_TOOL_PREFIX;
6398
6538
  busyDeliveryMode = "none";
6399
6539
  terminateProcessOnTurnEnd = true;
6400
6540
  deferSpawnUntilMessage = true;
6401
- usesSlockCliForCommunication = true;
6402
6541
  shouldDeferWakeMessage(message) {
6403
6542
  return isSystemFirstMessageTask(message);
6404
6543
  }
@@ -6432,7 +6571,7 @@ var OpenCodeDriver = class {
6432
6571
  if (unsupportedMessage) {
6433
6572
  throw new Error(unsupportedMessage);
6434
6573
  }
6435
- const launch = await buildOpenCodeLaunchOptions(ctx, os4.homedir(), version);
6574
+ const launch = await buildOpenCodeLaunchOptions(ctx, os5.homedir(), version);
6436
6575
  const spawnSpec = resolveOpenCodeSpawn(launch.args);
6437
6576
  const proc = spawn8(spawnSpec.command, spawnSpec.args, {
6438
6577
  cwd: ctx.workingDirectory,
@@ -6499,8 +6638,8 @@ var OpenCodeDriver = class {
6499
6638
  // src/drivers/pi.ts
6500
6639
  import { randomUUID as randomUUID3 } from "crypto";
6501
6640
  import { EventEmitter } from "events";
6502
- import { mkdirSync as mkdirSync2, readdirSync } from "fs";
6503
- import path9 from "path";
6641
+ import { mkdirSync as mkdirSync3, readdirSync } from "fs";
6642
+ import path10 from "path";
6504
6643
  import {
6505
6644
  AuthStorage,
6506
6645
  createBashTool,
@@ -6513,6 +6652,7 @@ import {
6513
6652
  VERSION as PI_SDK_VERSION
6514
6653
  } from "@earendil-works/pi-coding-agent";
6515
6654
  var PI_SESSION_DIR = ".pi-sessions";
6655
+ var PI_SDK_COMPACTION_ENABLED = true;
6516
6656
  var PI_PROVIDER_LABELS = {
6517
6657
  google: "Google",
6518
6658
  openai: "OpenAI",
@@ -6526,7 +6666,7 @@ function createPiSdkEventMappingState(sessionId = null) {
6526
6666
  };
6527
6667
  }
6528
6668
  function buildPiSessionDir(workingDirectory) {
6529
- return path9.join(workingDirectory, PI_SESSION_DIR);
6669
+ return path10.join(workingDirectory, PI_SESSION_DIR);
6530
6670
  }
6531
6671
  async function buildPiSpawnEnv(ctx) {
6532
6672
  return (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
@@ -6548,7 +6688,7 @@ function findPiSessionFile(sessionDir, sessionId) {
6548
6688
  }
6549
6689
  const suffix = `_${sessionId}.jsonl`;
6550
6690
  const match = entries.find((entry) => entry.endsWith(suffix));
6551
- return match ? path9.join(sessionDir, match) : null;
6691
+ return match ? path10.join(sessionDir, match) : null;
6552
6692
  }
6553
6693
  function detectPiModelsFromRegistry(modelRegistry) {
6554
6694
  const models = [];
@@ -6701,17 +6841,17 @@ var PI_IDLE_PROMPT_RETRY_MS = 25;
6701
6841
  var PI_IDLE_PROMPT_MAX_WAIT_MS = 1e3;
6702
6842
  async function createPiAgentSessionForContext(ctx, sessionId) {
6703
6843
  const sessionDir = buildPiSessionDir(ctx.workingDirectory);
6704
- mkdirSync2(sessionDir, { recursive: true });
6844
+ mkdirSync3(sessionDir, { recursive: true });
6705
6845
  const spawnEnv = await buildPiSpawnEnv(ctx);
6706
6846
  const agentDir = spawnEnv.PI_CODING_AGENT_DIR || getAgentDir();
6707
- const authStorage = AuthStorage.create(path9.join(agentDir, "auth.json"));
6708
- 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"));
6709
6849
  const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
6710
6850
  const model = resolvePiModelFromRegistry(launchRuntimeFields.model, modelRegistry);
6711
6851
  if (launchRuntimeFields.model && launchRuntimeFields.model !== "default" && !model) {
6712
6852
  throw new Error(`Pi model not found: ${launchRuntimeFields.model}`);
6713
6853
  }
6714
- const settingsManager = SettingsManager.inMemory({ compaction: { enabled: false } });
6854
+ const settingsManager = SettingsManager.inMemory({ compaction: { enabled: PI_SDK_COMPACTION_ENABLED } });
6715
6855
  const resourceLoader = new DefaultResourceLoader({
6716
6856
  cwd: ctx.workingDirectory,
6717
6857
  agentDir,
@@ -6948,9 +7088,7 @@ var PiDriver = class {
6948
7088
  toLaunchSpec: (modelId) => ({ params: { model: modelId } })
6949
7089
  };
6950
7090
  supportsStdinNotification = true;
6951
- mcpToolPrefix = "";
6952
7091
  busyDeliveryMode = "direct";
6953
- usesSlockCliForCommunication = true;
6954
7092
  sessionId = null;
6955
7093
  get currentSessionId() {
6956
7094
  return this.sessionId;
@@ -6981,7 +7119,6 @@ var PiDriver = class {
6981
7119
  }
6982
7120
  buildSystemPrompt(config, _agentId) {
6983
7121
  return buildCliTransportSystemPrompt(config, {
6984
- toolPrefix: "",
6985
7122
  extraCriticalRules: [],
6986
7123
  postStartupNotes: [
6987
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."
@@ -7155,7 +7292,7 @@ function getDriver(runtimeId) {
7155
7292
 
7156
7293
  // src/workspaces.ts
7157
7294
  import { readdir, rm, stat } from "fs/promises";
7158
- import path10 from "path";
7295
+ import path11 from "path";
7159
7296
  function isValidWorkspaceDirectoryName(directoryName) {
7160
7297
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
7161
7298
  }
@@ -7163,7 +7300,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
7163
7300
  if (!isValidWorkspaceDirectoryName(directoryName)) {
7164
7301
  return null;
7165
7302
  }
7166
- return path10.join(dataDir, directoryName);
7303
+ return path11.join(dataDir, directoryName);
7167
7304
  }
7168
7305
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
7169
7306
  return {
@@ -7212,7 +7349,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
7212
7349
  return summary;
7213
7350
  }
7214
7351
  const childSummaries = await Promise.all(
7215
- entries.map((entry) => summarizeWorkspaceEntry(path10.join(dirPath, entry.name), entry))
7352
+ entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
7216
7353
  );
7217
7354
  for (const childSummary of childSummaries) {
7218
7355
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -7231,7 +7368,7 @@ async function scanWorkspaceDirectories(dataDir) {
7231
7368
  if (!entry.isDirectory()) {
7232
7369
  return null;
7233
7370
  }
7234
- const dirPath = path10.join(dataDir, entry.name);
7371
+ const dirPath = path11.join(dataDir, entry.name);
7235
7372
  try {
7236
7373
  const summary = await summarizeWorkspaceDirectory(dirPath);
7237
7374
  return {
@@ -7782,12 +7919,12 @@ function findSessionJsonl(root, predicate) {
7782
7919
  for (const entry of entries) {
7783
7920
  if (++visited > maxEntries) return null;
7784
7921
  if (!entry.isFile() || !predicate(entry.name)) continue;
7785
- return path11.join(dir, entry.name);
7922
+ return path12.join(dir, entry.name);
7786
7923
  }
7787
7924
  for (const entry of entries) {
7788
7925
  if (++visited > maxEntries) return null;
7789
7926
  if (!entry.isDirectory()) continue;
7790
- const found = visit(path11.join(dir, entry.name), depth - 1);
7927
+ const found = visit(path12.join(dir, entry.name), depth - 1);
7791
7928
  if (found) return found;
7792
7929
  }
7793
7930
  return null;
@@ -7800,9 +7937,9 @@ function safeSessionFilename(value) {
7800
7937
  }
7801
7938
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
7802
7939
  try {
7803
- const dir = path11.join(fallbackDir, ".slock", "runtime-sessions");
7804
- mkdirSync3(dir, { recursive: true });
7805
- 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`);
7806
7943
  writeFileSync4(filePath, JSON.stringify({
7807
7944
  type: "runtime_session_handoff",
7808
7945
  runtime,
@@ -7821,8 +7958,20 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
7821
7958
  return null;
7822
7959
  }
7823
7960
  }
7824
- function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
7825
- 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;
7826
7975
  if (directPath) {
7827
7976
  try {
7828
7977
  if (statSync(directPath).isFile()) {
@@ -7831,7 +7980,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
7831
7980
  } catch {
7832
7981
  }
7833
7982
  }
7834
- 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;
7835
7984
  if (!resolvedPath && fallbackDir) {
7836
7985
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
7837
7986
  if (fallback) return fallback;
@@ -7892,13 +8041,7 @@ function formatThreadContextMessage(message) {
7892
8041
  const senderType = formatVisibleActorType(message.sender_type);
7893
8042
  return `- [msg=${msgId} time=${time}${senderType}] ${formatSenderHandle(message)}: ${message.content}`;
7894
8043
  }
7895
- function mcpToolName(driver, name) {
7896
- return `${driver?.mcpToolPrefix ?? ""}${name}`;
7897
- }
7898
- function communicationCommand(driver, name) {
7899
- if (!driver?.usesSlockCliForCommunication) {
7900
- return mcpToolName(driver, name);
7901
- }
8044
+ function communicationCommand(name) {
7902
8045
  switch (name) {
7903
8046
  case "send_message":
7904
8047
  return "slock message send";
@@ -7912,18 +8055,18 @@ function communicationCommand(driver, name) {
7912
8055
  return `slock ${name.replace(/_/g, " ")}`;
7913
8056
  }
7914
8057
  }
7915
- function dynamicReplyInstruction(driver) {
7916
- 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>`";
7917
8060
  }
7918
- function dynamicClaimInstruction(driver) {
7919
- 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`";
7920
8063
  }
7921
- function formatIncomingMessage(message, driver) {
8064
+ function formatIncomingMessage(message) {
7922
8065
  const threadJoinPrefix = message.thread_join_context ? [
7923
8066
  `[Slock thread context: you were added to a new thread via @mention.]`,
7924
8067
  `parent: ${message.thread_join_context.parent_target}`,
7925
8068
  `thread: ${message.thread_join_context.thread_target}`,
7926
- `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}"`,
7927
8070
  "",
7928
8071
  "Parent message:",
7929
8072
  formatThreadContextMessage(message.thread_join_context.parent_message),
@@ -8843,6 +8986,7 @@ var AgentProcessManager = class _AgentProcessManager {
8843
8986
  maxConcurrentAgentStarts;
8844
8987
  agentStartIntervalMs;
8845
8988
  startingInboxes = /* @__PURE__ */ new Map();
8989
+ pendingStartRebinds = /* @__PURE__ */ new Map();
8846
8990
  /** Cached configs for agents whose process exited normally — enables auto-restart on next message */
8847
8991
  idleAgentConfigs = /* @__PURE__ */ new Map();
8848
8992
  slockCliPath;
@@ -8877,7 +9021,7 @@ var AgentProcessManager = class _AgentProcessManager {
8877
9021
  this.daemonApiKey = daemonApiKey;
8878
9022
  this.serverUrl = opts.serverUrl;
8879
9023
  this.dataDir = opts.dataDir || resolveSlockHomePath("agents");
8880
- this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os5.homedir();
9024
+ this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os6.homedir();
8881
9025
  this.driverResolver = opts.driverResolver || getDriver;
8882
9026
  this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
8883
9027
  this.tracer = opts.tracer ?? noopTracer;
@@ -9488,6 +9632,92 @@ var AgentProcessManager = class _AgentProcessManager {
9488
9632
  ...attrs
9489
9633
  };
9490
9634
  }
9635
+ recordStartRebind(agentId, start, reason, previousLaunchId, nextLaunchId, sessionId) {
9636
+ this.recordDaemonTrace("daemon.agent.start.rebound", {
9637
+ ...this.startQueueTraceAttrs(
9638
+ agentId,
9639
+ start.config,
9640
+ start.wakeMessage,
9641
+ start.unreadSummary,
9642
+ start.resumePrompt,
9643
+ start.launchId,
9644
+ start.wakeMessageTransient ?? false
9645
+ ),
9646
+ reason,
9647
+ previous_launch_id_present: Boolean(previousLaunchId),
9648
+ next_launch_id_present: Boolean(nextLaunchId),
9649
+ session_id_present: Boolean(sessionId)
9650
+ });
9651
+ }
9652
+ sameWakeMessage(left, right) {
9653
+ if (!left || !right) return left === right;
9654
+ if (left.message_id && right.message_id) return left.message_id === right.message_id;
9655
+ return left === right;
9656
+ }
9657
+ rebindQueuedStart(agentId, start, reason) {
9658
+ const item = this.queuedAgentStarts.get(agentId);
9659
+ if (!item) {
9660
+ return false;
9661
+ }
9662
+ const previousLaunchId = item.launchId || null;
9663
+ const nextLaunchId = start.launchId || previousLaunchId;
9664
+ const previousWakeMessage = item.wakeMessage;
9665
+ if (previousWakeMessage && start.wakeMessage && !this.sameWakeMessage(previousWakeMessage, start.wakeMessage)) {
9666
+ const pending = this.startingInboxes.get(agentId) || [];
9667
+ pending.push(previousWakeMessage);
9668
+ this.startingInboxes.set(agentId, pending);
9669
+ }
9670
+ item.config = start.config;
9671
+ item.unreadSummary = start.unreadSummary;
9672
+ item.resumePrompt = start.resumePrompt;
9673
+ item.launchId = nextLaunchId || void 0;
9674
+ if (start.wakeMessage) {
9675
+ item.wakeMessage = start.wakeMessage;
9676
+ item.wakeMessageTransient = start.wakeMessageTransient;
9677
+ }
9678
+ this.recordStartRebind(
9679
+ agentId,
9680
+ { ...start, launchId: nextLaunchId || void 0 },
9681
+ reason,
9682
+ previousLaunchId,
9683
+ nextLaunchId,
9684
+ null
9685
+ );
9686
+ return true;
9687
+ }
9688
+ rebindRunningStart(agentId, start, reason) {
9689
+ const ap = this.agents.get(agentId);
9690
+ if (!ap) {
9691
+ this.pendingStartRebinds.set(agentId, start);
9692
+ return false;
9693
+ }
9694
+ const previousLaunchId = ap.launchId;
9695
+ const nextLaunchId = start.launchId || ap.launchId || null;
9696
+ const nextSessionId = ap.sessionId || start.config.sessionId || null;
9697
+ ap.launchId = nextLaunchId;
9698
+ ap.sessionId = nextSessionId;
9699
+ ap.config = {
9700
+ ...start.config,
9701
+ sessionId: nextSessionId
9702
+ };
9703
+ this.idleAgentConfigs.set(agentId, {
9704
+ config: { ...stripManagedRunnerCredential(ap.config), sessionId: nextSessionId },
9705
+ sessionId: nextSessionId,
9706
+ launchId: nextLaunchId
9707
+ });
9708
+ this.recordStartRebind(agentId, start, reason, previousLaunchId, nextLaunchId, nextSessionId);
9709
+ this.sendAgentStatus(agentId, "active", nextLaunchId);
9710
+ if (nextSessionId) {
9711
+ this.sendToServer({ type: "agent:session", agentId, sessionId: nextSessionId, launchId: nextLaunchId || void 0 });
9712
+ }
9713
+ if (start.wakeMessage) {
9714
+ const accepted = this.deliverMessage(agentId, start.wakeMessage, { transient: start.wakeMessageTransient === true });
9715
+ if (accepted instanceof Promise) {
9716
+ accepted.catch((err) => logger.error(`[Agent ${agentId}] Failed to deliver wake message after start rebind`, err));
9717
+ }
9718
+ }
9719
+ return true;
9720
+ }
9491
9721
  async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient = false) {
9492
9722
  this.recordDaemonTrace("daemon.agent.start.requested", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient));
9493
9723
  if (this.agents.has(agentId)) {
@@ -9495,7 +9725,8 @@ var AgentProcessManager = class _AgentProcessManager {
9495
9725
  ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
9496
9726
  reason: "already_running"
9497
9727
  });
9498
- logger.info(`[Agent ${agentId}] Start ignored (already running)`);
9728
+ this.rebindRunningStart(agentId, { config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient }, "already_running");
9729
+ logger.info(`[Agent ${agentId}] Start rebound (already running)`);
9499
9730
  return;
9500
9731
  }
9501
9732
  if (this.agentsStarting.has(agentId)) {
@@ -9503,7 +9734,8 @@ var AgentProcessManager = class _AgentProcessManager {
9503
9734
  ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
9504
9735
  reason: "already_starting"
9505
9736
  });
9506
- logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
9737
+ this.pendingStartRebinds.set(agentId, { config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient });
9738
+ logger.info(`[Agent ${agentId}] Start rebind deferred (startup in progress)`);
9507
9739
  return;
9508
9740
  }
9509
9741
  if (this.queuedAgentStarts.has(agentId)) {
@@ -9511,7 +9743,8 @@ var AgentProcessManager = class _AgentProcessManager {
9511
9743
  ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
9512
9744
  reason: "already_queued"
9513
9745
  });
9514
- logger.info(`[Agent ${agentId}] Start ignored (startup already queued)`);
9746
+ this.rebindQueuedStart(agentId, { config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient }, "already_queued");
9747
+ logger.info(`[Agent ${agentId}] Queued start rebound (startup already queued)`);
9515
9748
  return;
9516
9749
  }
9517
9750
  return new Promise((resolve, reject) => {
@@ -9570,6 +9803,11 @@ var AgentProcessManager = class _AgentProcessManager {
9570
9803
  ...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId, item.wakeMessageTransient),
9571
9804
  reason: "already_running_or_starting"
9572
9805
  });
9806
+ if (this.agents.has(item.agentId)) {
9807
+ this.rebindRunningStart(item.agentId, item, "already_running_or_starting");
9808
+ } else {
9809
+ this.pendingStartRebinds.set(item.agentId, item);
9810
+ }
9573
9811
  logger.info(`[Agent ${item.agentId}] Queued start skipped (already running or starting)`);
9574
9812
  item.resolve();
9575
9813
  this.pumpAgentStartQueue();
@@ -9656,7 +9894,8 @@ var AgentProcessManager = class _AgentProcessManager {
9656
9894
  ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
9657
9895
  reason: "already_running"
9658
9896
  });
9659
- logger.info(`[Agent ${agentId}] Start ignored (already running)`);
9897
+ this.rebindRunningStart(agentId, { config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient }, "already_running");
9898
+ logger.info(`[Agent ${agentId}] Start rebound (already running)`);
9660
9899
  return;
9661
9900
  }
9662
9901
  if (this.agentsStarting.has(agentId)) {
@@ -9664,7 +9903,8 @@ var AgentProcessManager = class _AgentProcessManager {
9664
9903
  ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
9665
9904
  reason: "already_starting"
9666
9905
  });
9667
- logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
9906
+ this.pendingStartRebinds.set(agentId, { config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient });
9907
+ logger.info(`[Agent ${agentId}] Start rebind deferred (startup in progress)`);
9668
9908
  return;
9669
9909
  }
9670
9910
  this.agentsStarting.add(agentId);
@@ -9683,7 +9923,7 @@ var AgentProcessManager = class _AgentProcessManager {
9683
9923
  );
9684
9924
  wakeMessage = void 0;
9685
9925
  }
9686
- const agentDataDir = path11.join(this.dataDir, agentId);
9926
+ const agentDataDir = path12.join(this.dataDir, agentId);
9687
9927
  await mkdir(agentDataDir, { recursive: true });
9688
9928
  let runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
9689
9929
  const legacyRuntimeProfileControl = runtimeConfig.runtimeProfileControl?.kind === "migration" ? runtimeConfig.runtimeProfileControl : null;
@@ -9697,23 +9937,23 @@ var AgentProcessManager = class _AgentProcessManager {
9697
9937
  );
9698
9938
  runtimeConfig = { ...runtimeConfig, runtimeProfileControl: null };
9699
9939
  }
9700
- const memoryMdPath = path11.join(agentDataDir, "MEMORY.md");
9940
+ const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
9701
9941
  try {
9702
9942
  await access(memoryMdPath);
9703
9943
  } catch {
9704
9944
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
9705
9945
  await writeFile(memoryMdPath, initialMemoryMd);
9706
9946
  }
9707
- const notesDir = path11.join(agentDataDir, "notes");
9947
+ const notesDir = path12.join(agentDataDir, "notes");
9708
9948
  await mkdir(notesDir, { recursive: true });
9709
9949
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
9710
9950
  const seedFiles = buildOnboardingSeedFiles();
9711
9951
  for (const { relativePath, content } of seedFiles) {
9712
- const fullPath = path11.join(agentDataDir, relativePath);
9952
+ const fullPath = path12.join(agentDataDir, relativePath);
9713
9953
  try {
9714
9954
  await access(fullPath);
9715
9955
  } catch {
9716
- await mkdir(path11.dirname(fullPath), { recursive: true });
9956
+ await mkdir(path12.dirname(fullPath), { recursive: true });
9717
9957
  await writeFile(fullPath, content);
9718
9958
  }
9719
9959
  }
@@ -9737,7 +9977,7 @@ var AgentProcessManager = class _AgentProcessManager {
9737
9977
  if (transientWakeMessage) {
9738
9978
  prompt = `System notice received:
9739
9979
 
9740
- ${formatIncomingMessage(wakeMessage, driver)}
9980
+ ${formatIncomingMessage(wakeMessage)}
9741
9981
 
9742
9982
  Respond as appropriate. Complete all your work before stopping.
9743
9983
  ${RESPONSE_TARGET_HINT}`;
@@ -9771,7 +10011,7 @@ Use the inbox/read commands at a natural breakpoint if you choose to inspect tho
9771
10011
  }
9772
10012
  prompt += `
9773
10013
 
9774
- 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)}`;
9775
10015
  promptSource = "resume_unread_summary";
9776
10016
  } else if (isResume) {
9777
10017
  prompt = `No new messages while you were away. Nothing to do \u2014 just stop. ${getMessageDeliveryText(driver)}`;
@@ -9792,7 +10032,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9792
10032
  nativeStandingPrompt: Boolean(driver.supportsNativeStandingPrompt)
9793
10033
  });
9794
10034
  const effectiveConfig = await this.buildSpawnConfig(agentId, runtimeConfig);
9795
- const canDeferEmptyStart = driver.deferSpawnUntilMessage === true && !wakeMessage && !runtimeConfig.runtimeProfileControl && (!unreadSummary || Object.keys(unreadSummary).length === 0);
10035
+ const pendingStartRebind = this.pendingStartRebinds.get(agentId);
10036
+ if (pendingStartRebind) {
10037
+ this.pendingStartRebinds.delete(agentId);
10038
+ }
10039
+ const effectiveLaunchId = pendingStartRebind?.launchId || launchId || null;
10040
+ const canDeferEmptyStart = driver.deferSpawnUntilMessage === true && !wakeMessage && !pendingStartRebind?.wakeMessage && !runtimeConfig.runtimeProfileControl && (!unreadSummary || Object.keys(unreadSummary).length === 0);
9796
10041
  if (canDeferEmptyStart) {
9797
10042
  const pendingMessages = this.startingInboxes.get(agentId) || [];
9798
10043
  this.startingInboxes.delete(agentId);
@@ -9800,12 +10045,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9800
10045
  this.idleAgentConfigs.set(agentId, {
9801
10046
  config: effectiveConfig,
9802
10047
  sessionId: effectiveConfig.sessionId || null,
9803
- launchId: launchId || null
10048
+ launchId: effectiveLaunchId
9804
10049
  });
9805
- this.sendAgentStatus(agentId, "active", launchId || null);
10050
+ this.sendAgentStatus(agentId, "active", effectiveLaunchId);
9806
10051
  this.broadcastActivity(agentId, "online", "Process idle");
9807
10052
  this.recordDaemonTrace("daemon.agent.spawn.deferred", {
9808
- ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
10053
+ ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, effectiveLaunchId || void 0, wakeMessageTransient),
9809
10054
  pending_messages_count: pendingMessages.length,
9810
10055
  reason: "defer_until_concrete_message"
9811
10056
  });
@@ -9823,7 +10068,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9823
10068
  workingDirectory: agentDataDir,
9824
10069
  slockCliPath: this.slockCliPath,
9825
10070
  daemonApiKey: this.daemonApiKey,
9826
- launchId: launchId || null,
10071
+ launchId: effectiveLaunchId,
9827
10072
  agentCredentialProxyInboxCoordinator: this.createAgentProxyInboxCoordinator(agentId),
9828
10073
  cliTransportTraceDir: this.cliTransportTraceDir
9829
10074
  };
@@ -9834,7 +10079,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9834
10079
  inbox: wakeMessageDeliveredAsInboxUpdate && wakeMessage ? [wakeMessage, ...startingInboxMessages] : startingInboxMessages,
9835
10080
  config: runtimeConfig,
9836
10081
  sessionId: runtimeConfig.sessionId || null,
9837
- launchId: launchId || null,
10082
+ launchId: effectiveLaunchId,
9838
10083
  startupWakeMessage: wakeMessage,
9839
10084
  startupUnreadSummary: unreadSummary,
9840
10085
  startupResumePrompt: resumePrompt,
@@ -9870,8 +10115,11 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9870
10115
  this.idleAgentConfigs.set(agentId, {
9871
10116
  config: { ...effectiveConfig, sessionId: effectiveConfig.sessionId || null },
9872
10117
  sessionId: effectiveConfig.sessionId || null,
9873
- launchId: launchId || null
10118
+ launchId: effectiveLaunchId
9874
10119
  });
10120
+ if (pendingStartRebind) {
10121
+ this.recordStartRebind(agentId, pendingStartRebind, "startup_registered", launchId || null, effectiveLaunchId, agentProcess.sessionId);
10122
+ }
9875
10123
  this.startRuntimeTrace(agentId, agentProcess, "spawn", wakeMessage ? [wakeMessage] : void 0, runtimeInputTraceAttrs);
9876
10124
  this.agentsStarting.delete(agentId);
9877
10125
  if (runtimeConfig.runtimeProfileControl) {
@@ -10106,16 +10354,26 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10106
10354
  throw new Error(`Runtime session failed to start: ${startResult.reason}${startResult.error ? ` (${startResult.error})` : ""}`);
10107
10355
  }
10108
10356
  this.recordDaemonTrace("daemon.agent.spawn.created", {
10109
- ...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
10357
+ ...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, agentProcess.launchId || void 0, wakeMessageTransient),
10110
10358
  detached: false,
10111
10359
  new_session: false,
10112
10360
  process_pid_present: typeof runtime.pid === "number"
10113
10361
  });
10114
- this.sendAgentStatus(agentId, "active", launchId || null);
10362
+ this.sendAgentStatus(agentId, "active", agentProcess.launchId);
10363
+ if (pendingStartRebind && agentProcess.sessionId) {
10364
+ this.sendToServer({ type: "agent:session", agentId, sessionId: agentProcess.sessionId, launchId: agentProcess.launchId || void 0 });
10365
+ }
10366
+ if (pendingStartRebind?.wakeMessage) {
10367
+ const accepted = this.deliverMessage(agentId, pendingStartRebind.wakeMessage, { transient: pendingStartRebind.wakeMessageTransient === true });
10368
+ if (accepted instanceof Promise) {
10369
+ accepted.catch((err) => logger.error(`[Agent ${agentId}] Failed to deliver wake message after startup rebind`, err));
10370
+ }
10371
+ }
10115
10372
  this.broadcastActivity(agentId, "working", "Starting\u2026");
10116
10373
  this.startRuntimeStartupTimeout(agentId, agentProcess);
10117
10374
  } catch (err) {
10118
10375
  this.agentsStarting.delete(agentId);
10376
+ this.pendingStartRebinds.delete(agentId);
10119
10377
  this.cleanupFailedRuntimeStart(agentId, agentProcess, err);
10120
10378
  throw err;
10121
10379
  }
@@ -10384,6 +10642,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10384
10642
  }
10385
10643
  async stopAgent(agentId, { wait = false, silent = false } = {}) {
10386
10644
  this.cancelQueuedAgentStart(agentId, "stop requested");
10645
+ this.pendingStartRebinds.delete(agentId);
10387
10646
  this.idleAgentConfigs.delete(agentId);
10388
10647
  const ap = this.agents.get(agentId);
10389
10648
  if (!ap) {
@@ -10830,7 +11089,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10830
11089
  return true;
10831
11090
  }
10832
11091
  async resetWorkspace(agentId) {
10833
- const agentDataDir = path11.join(this.dataDir, agentId);
11092
+ const agentDataDir = path12.join(this.dataDir, agentId);
10834
11093
  try {
10835
11094
  await rm2(agentDataDir, { recursive: true, force: true });
10836
11095
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -10891,7 +11150,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10891
11150
  return result;
10892
11151
  }
10893
11152
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
10894
- const workspacePath = path11.join(this.dataDir, agentId);
11153
+ const workspacePath = path12.join(this.dataDir, agentId);
11154
+ const runtimeHomeDir = resolveRuntimeHomeDir(config, this.runtimeSessionHomeDir, workspacePath);
10895
11155
  return {
10896
11156
  agentId,
10897
11157
  launchId,
@@ -10905,7 +11165,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10905
11165
  path: workspacePath,
10906
11166
  reachable: true
10907
11167
  },
10908
- sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, this.runtimeSessionHomeDir, workspacePath) : null
11168
+ sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, runtimeHomeDir, workspacePath) : null
10909
11169
  }
10910
11170
  };
10911
11171
  }
@@ -11184,7 +11444,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11184
11444
  }
11185
11445
  // Workspace file browsing
11186
11446
  async getFileTree(agentId, dirPath) {
11187
- const agentDir = path11.join(this.dataDir, agentId);
11447
+ const agentDir = path12.join(this.dataDir, agentId);
11188
11448
  try {
11189
11449
  await stat2(agentDir);
11190
11450
  } catch {
@@ -11192,8 +11452,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11192
11452
  }
11193
11453
  let targetDir = agentDir;
11194
11454
  if (dirPath) {
11195
- const resolved = path11.resolve(agentDir, dirPath);
11196
- if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
11455
+ const resolved = path12.resolve(agentDir, dirPath);
11456
+ if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
11197
11457
  return [];
11198
11458
  }
11199
11459
  targetDir = resolved;
@@ -11201,14 +11461,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11201
11461
  return this.listDirectoryChildren(targetDir, agentDir);
11202
11462
  }
11203
11463
  async readFile(agentId, filePath) {
11204
- const agentDir = path11.join(this.dataDir, agentId);
11205
- const resolved = path11.resolve(agentDir, filePath);
11206
- 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) {
11207
11467
  throw new Error("Access denied");
11208
11468
  }
11209
11469
  const info = await stat2(resolved);
11210
11470
  if (info.isDirectory()) throw new Error("Cannot read a directory");
11211
- const ext = path11.extname(resolved).toLowerCase();
11471
+ const ext = path12.extname(resolved).toLowerCase();
11212
11472
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
11213
11473
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
11214
11474
  const content = await readFile(resolved, "utf-8");
@@ -11241,15 +11501,17 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11241
11501
  };
11242
11502
  async listSkills(agentId, runtimeHint) {
11243
11503
  const agent = this.agents.get(agentId);
11244
- const runtime = runtimeHint || agent?.config.runtime || "claude";
11245
- const home = os5.homedir();
11246
- 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();
11247
11509
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
11248
11510
  const globalResults = await Promise.all(
11249
- paths.global.map((p) => this.scanSkillsDir(path11.join(home, p)))
11511
+ paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
11250
11512
  );
11251
11513
  const workspaceResults = await Promise.all(
11252
- paths.workspace.map((p) => this.scanSkillsDir(path11.join(workspaceDir, p)))
11514
+ paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
11253
11515
  );
11254
11516
  const dedup = (skills) => {
11255
11517
  const seen = /* @__PURE__ */ new Set();
@@ -11278,7 +11540,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11278
11540
  const skills = [];
11279
11541
  for (const entry of entries) {
11280
11542
  if (entry.isDirectory() || entry.isSymbolicLink()) {
11281
- const skillMd = path11.join(dir, entry.name, "SKILL.md");
11543
+ const skillMd = path12.join(dir, entry.name, "SKILL.md");
11282
11544
  try {
11283
11545
  const content = await readFile(skillMd, "utf-8");
11284
11546
  const skill = this.parseSkillMd(entry.name, content);
@@ -11289,7 +11551,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11289
11551
  } else if (entry.name.endsWith(".md")) {
11290
11552
  const cmdName = entry.name.replace(/\.md$/, "");
11291
11553
  try {
11292
- const content = await readFile(path11.join(dir, entry.name), "utf-8");
11554
+ const content = await readFile(path12.join(dir, entry.name), "utf-8");
11293
11555
  const skill = this.parseSkillMd(cmdName, content);
11294
11556
  skill.sourcePath = dir;
11295
11557
  skills.push(skill);
@@ -12693,12 +12955,12 @@ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
12693
12955
  const traceSource = options.transient ? `stdin_${mode}_transient_delivery` : `stdin_${mode}_delivery`;
12694
12956
  const prompt = formatRuntimeProfileControlPrompt(messages) ?? (messages.length === 1 ? `New message received:
12695
12957
 
12696
- ${formatIncomingMessage(messages[0], ap.driver)}
12958
+ ${formatIncomingMessage(messages[0])}
12697
12959
 
12698
12960
  Respond as appropriate. Complete all your work before stopping.
12699
12961
  ${RESPONSE_TARGET_HINT}` : `New messages received:
12700
12962
 
12701
- ${messages.map((message) => formatIncomingMessage(message, ap.driver)).join("\n")}
12963
+ ${messages.map((message) => formatIncomingMessage(message)).join("\n")}
12702
12964
 
12703
12965
  Respond as appropriate. Complete all your work before stopping.
12704
12966
  ${RESPONSE_TARGET_HINT}`);
@@ -12769,8 +13031,8 @@ ${RESPONSE_TARGET_HINT}`);
12769
13031
  const nodes = [];
12770
13032
  for (const entry of entries) {
12771
13033
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
12772
- const fullPath = path11.join(dir, entry.name);
12773
- const relativePath = path11.relative(rootDir, fullPath);
13034
+ const fullPath = path12.join(dir, entry.name);
13035
+ const relativePath = path12.relative(rootDir, fullPath);
12774
13036
  let info;
12775
13037
  try {
12776
13038
  info = await stat2(fullPath);
@@ -13144,9 +13406,9 @@ var ReminderCache = class {
13144
13406
 
13145
13407
  // src/machineLock.ts
13146
13408
  import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
13147
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
13148
- import os6 from "os";
13149
- 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";
13150
13412
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
13151
13413
  var DaemonMachineLockConflictError = class extends Error {
13152
13414
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -13168,7 +13430,7 @@ function resolveDefaultMachineStateRoot() {
13168
13430
  return resolveSlockHomePath("machines");
13169
13431
  }
13170
13432
  function ownerPath(lockDir) {
13171
- return path12.join(lockDir, "owner.json");
13433
+ return path13.join(lockDir, "owner.json");
13172
13434
  }
13173
13435
  function readOwner(lockDir) {
13174
13436
  try {
@@ -13198,17 +13460,17 @@ function acquireDaemonMachineLock(options) {
13198
13460
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
13199
13461
  const fingerprint = apiKeyFingerprint(options.apiKey);
13200
13462
  const lockId = getDaemonMachineLockId(options.apiKey);
13201
- const machineDir = path12.join(rootDir, lockId);
13202
- const lockDir = path12.join(machineDir, "daemon.lock");
13463
+ const machineDir = path13.join(rootDir, lockId);
13464
+ const lockDir = path13.join(machineDir, "daemon.lock");
13203
13465
  const token = randomUUID4();
13204
- mkdirSync4(machineDir, { recursive: true });
13466
+ mkdirSync5(machineDir, { recursive: true });
13205
13467
  for (let attempt = 0; attempt < 2; attempt += 1) {
13206
13468
  try {
13207
- mkdirSync4(lockDir);
13469
+ mkdirSync5(lockDir);
13208
13470
  const owner = {
13209
13471
  pid: process.pid,
13210
13472
  token,
13211
- hostname: os6.hostname(),
13473
+ hostname: os7.hostname(),
13212
13474
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
13213
13475
  serverUrl: options.serverUrl,
13214
13476
  apiKeyFingerprint: fingerprint.slice(0, 16)
@@ -13259,8 +13521,8 @@ function acquireDaemonMachineLock(options) {
13259
13521
  }
13260
13522
 
13261
13523
  // src/localTraceSink.ts
13262
- import { appendFileSync, mkdirSync as mkdirSync5, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
13263
- 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";
13264
13526
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
13265
13527
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
13266
13528
  var DEFAULT_MAX_FILES = 8;
@@ -13297,7 +13559,7 @@ var LocalRotatingTraceSink = class {
13297
13559
  currentSize = 0;
13298
13560
  sequence = 0;
13299
13561
  constructor(options) {
13300
- this.traceDir = path13.join(options.machineDir, "traces");
13562
+ this.traceDir = path14.join(options.machineDir, "traces");
13301
13563
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
13302
13564
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
13303
13565
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -13323,11 +13585,11 @@ var LocalRotatingTraceSink = class {
13323
13585
  return this.currentFile;
13324
13586
  }
13325
13587
  ensureFile(nextBytes) {
13326
- mkdirSync5(this.traceDir, { recursive: true, mode: 448 });
13588
+ mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
13327
13589
  const nowMs = this.nowMsProvider();
13328
13590
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
13329
13591
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
13330
- this.currentFile = path13.join(
13592
+ this.currentFile = path14.join(
13331
13593
  this.traceDir,
13332
13594
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
13333
13595
  );
@@ -13342,7 +13604,7 @@ var LocalRotatingTraceSink = class {
13342
13604
  const excess = files.length - this.maxFiles;
13343
13605
  if (excess <= 0) return;
13344
13606
  for (const file of files.slice(0, excess)) {
13345
- rmSync4(path13.join(this.traceDir, file), { force: true });
13607
+ rmSync4(path14.join(this.traceDir, file), { force: true });
13346
13608
  }
13347
13609
  }
13348
13610
  };
@@ -13433,7 +13695,7 @@ function isDiagnosticErrorAttr(key) {
13433
13695
  import { createHash as createHash6, randomUUID as randomUUID5 } from "crypto";
13434
13696
  import { gzipSync } from "zlib";
13435
13697
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
13436
- import path14 from "path";
13698
+ import path15 from "path";
13437
13699
 
13438
13700
  // src/chatBridgeRequest.ts
13439
13701
  var DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS = Number.parseInt(
@@ -13533,8 +13795,8 @@ async function executeResponseRequest(url, init, {
13533
13795
  }
13534
13796
 
13535
13797
  // src/directUploadCapability.ts
13536
- function joinUrl(base, path16) {
13537
- return `${base.replace(/\/+$/, "")}${path16}`;
13798
+ function joinUrl(base, path17) {
13799
+ return `${base.replace(/\/+$/, "")}${path17}`;
13538
13800
  }
13539
13801
  function jsonHeaders(apiKey) {
13540
13802
  return {
@@ -13753,7 +14015,7 @@ var DaemonTraceBundleUploader = class {
13753
14015
  }, nextMs);
13754
14016
  }
13755
14017
  async findUploadCandidates() {
13756
- const traceDir = path14.join(this.options.machineDir, "traces");
14018
+ const traceDir = path15.join(this.options.machineDir, "traces");
13757
14019
  let names;
13758
14020
  try {
13759
14021
  names = await readdir3(traceDir);
@@ -13765,8 +14027,8 @@ var DaemonTraceBundleUploader = class {
13765
14027
  const currentFile = this.options.currentFileProvider?.();
13766
14028
  const candidates = [];
13767
14029
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
13768
- const file = path14.join(traceDir, name);
13769
- 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;
13770
14032
  if (await this.isUploaded(file)) continue;
13771
14033
  try {
13772
14034
  const info = await stat3(file);
@@ -13840,8 +14102,8 @@ var DaemonTraceBundleUploader = class {
13840
14102
  }
13841
14103
  }
13842
14104
  uploadStatePath(file) {
13843
- const stateDir = path14.join(this.options.machineDir, "trace-uploads");
13844
- 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`);
13845
14107
  }
13846
14108
  async isUploaded(file) {
13847
14109
  try {
@@ -13853,9 +14115,9 @@ var DaemonTraceBundleUploader = class {
13853
14115
  }
13854
14116
  async markUploaded(file, metadata) {
13855
14117
  const stateFile = this.uploadStatePath(file);
13856
- await mkdir2(path14.dirname(stateFile), { recursive: true, mode: 448 });
14118
+ await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
13857
14119
  await writeFile2(stateFile, `${JSON.stringify({
13858
- file: path14.basename(file),
14120
+ file: path15.basename(file),
13859
14121
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
13860
14122
  ...metadata
13861
14123
  }, null, 2)}
@@ -13922,7 +14184,7 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
13922
14184
  spanAttrs: ["running_agents_count", "idle_agents_count"]
13923
14185
  }
13924
14186
  };
13925
- 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>)`;
13926
14188
  var RunnerCredentialMintError2 = class extends Error {
13927
14189
  code;
13928
14190
  retryable;
@@ -13958,9 +14220,9 @@ function runnerCredentialErrorDetail2(error) {
13958
14220
  async function waitForRunnerCredentialRetry2() {
13959
14221
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
13960
14222
  }
13961
- function parseDaemonCliArgs(args) {
14223
+ function parseDaemonCliArgs(args, env = {}) {
13962
14224
  let serverUrl = "";
13963
- let apiKey = "";
14225
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
13964
14226
  for (let i = 0; i < args.length; i++) {
13965
14227
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
13966
14228
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -13977,13 +14239,13 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
13977
14239
  }
13978
14240
  }
13979
14241
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
13980
- const thisDir = path15.dirname(fileURLToPath(moduleUrl));
13981
- const bundledDistPath = path15.resolve(thisDir, "cli", "index.js");
14242
+ const thisDir = path16.dirname(fileURLToPath(moduleUrl));
14243
+ const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
13982
14244
  try {
13983
14245
  accessSync(bundledDistPath);
13984
14246
  return bundledDistPath;
13985
14247
  } catch {
13986
- const workspaceDistPath = path15.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
14248
+ const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
13987
14249
  accessSync(workspaceDistPath);
13988
14250
  return workspaceDistPath;
13989
14251
  }
@@ -14172,7 +14434,7 @@ var DaemonCore = class {
14172
14434
  }
14173
14435
  resolveMachineStateRoot() {
14174
14436
  if (this.options.machineStateDir) return this.options.machineStateDir;
14175
- 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");
14176
14438
  return resolveDefaultMachineStateRoot();
14177
14439
  }
14178
14440
  shouldEnableLocalTrace() {
@@ -14199,7 +14461,7 @@ var DaemonCore = class {
14199
14461
  sink: this.localTraceSink
14200
14462
  }));
14201
14463
  this.agentManager.setTracer(this.tracer);
14202
- this.agentManager.setCliTransportTraceDir(path15.join(machineDir, "traces"));
14464
+ this.agentManager.setCliTransportTraceDir(path16.join(machineDir, "traces"));
14203
14465
  }
14204
14466
  installTraceBundleUploader(machineDir) {
14205
14467
  if (!this.shouldEnableLocalTrace()) return;
@@ -14676,8 +14938,8 @@ var DaemonCore = class {
14676
14938
  capabilities: ["agent:start", "agent:stop", "agent:deliver", "workspace:files"],
14677
14939
  runtimes,
14678
14940
  runningAgents: runningAgentIds,
14679
- hostname: this.options.hostname ?? os7.hostname(),
14680
- os: this.options.osDescription ?? `${os7.platform()} ${os7.arch()}`,
14941
+ hostname: this.options.hostname ?? os8.hostname(),
14942
+ os: this.options.osDescription ?? `${os8.platform()} ${os8.arch()}`,
14681
14943
  daemonVersion: this.daemonVersion,
14682
14944
  ...this.computerVersion ? { computerVersion: this.computerVersion } : {}
14683
14945
  });
@@ -14741,6 +15003,8 @@ var DaemonCore = class {
14741
15003
  };
14742
15004
 
14743
15005
  export {
15006
+ DAEMON_API_KEY_ENV,
15007
+ scrubDaemonAuthEnv,
14744
15008
  subscribeDaemonLogs,
14745
15009
  resolveWorkspaceDirectoryPath,
14746
15010
  scanWorkspaceDirectories,