@slock-ai/daemon 0.53.2 → 0.54.1

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,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- buildFetchDispatcher,
3
+ daemonFetch,
4
4
  executeJsonRequest
5
- } from "./chunk-KNMCE6WB.js";
5
+ } from "./chunk-VOZJ2ELH.js";
6
6
 
7
7
  // src/chat-bridge.ts
8
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -47,9 +47,7 @@ var runtimeActionHeaders = {
47
47
  ...launchId ? { "X-Agent-Launch-Id": launchId } : {}
48
48
  };
49
49
  function bridgeFetch(url, init = {}) {
50
- const dispatcher = buildFetchDispatcher(url, process.env);
51
- const requestInit = dispatcher ? { ...init, dispatcher } : init;
52
- return fetch(url, requestInit);
50
+ return daemonFetch(url, init);
53
51
  }
54
52
  var server = new McpServer({
55
53
  name: "chat",
@@ -207,6 +207,15 @@ function buildFetchDispatcher(targetUrl, env) {
207
207
  return dispatcher;
208
208
  }
209
209
 
210
+ // src/daemonFetch.ts
211
+ function withDaemonFetchProxy(input, init = {}, env = process.env) {
212
+ const dispatcher = buildFetchDispatcher(input.toString(), env);
213
+ return dispatcher ? { ...init, dispatcher } : init;
214
+ }
215
+ function daemonFetch(input, init, env = process.env) {
216
+ return fetch(input, withDaemonFetchProxy(input, init, env));
217
+ }
218
+
210
219
  export {
211
220
  subscribeDaemonLogs,
212
221
  logger,
@@ -214,5 +223,5 @@ export {
214
223
  executeJsonRequest,
215
224
  executeResponseRequest,
216
225
  buildWebSocketOptions,
217
- buildFetchDispatcher
226
+ daemonFetch
218
227
  };
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
3
3
  buildWebSocketOptions,
4
+ daemonFetch,
4
5
  executeJsonRequest,
5
6
  executeResponseRequest,
6
7
  logger
7
- } from "./chunk-KNMCE6WB.js";
8
+ } from "./chunk-VOZJ2ELH.js";
8
9
 
9
10
  // src/core.ts
10
11
  import path16 from "path";
@@ -609,13 +610,16 @@ var agentCreateOperationSchema = z.object({
609
610
  name: z.string().trim().min(1).max(60),
610
611
  description: z.string().trim().max(500).optional(),
611
612
  /**
612
- * Agent can only suggest semantic intent (name + description). Technical
613
- * configuration (which computer / runtime / model / reasoning effort) is
614
- * a user prerogative the human picks those in the create dialog when
615
- * they click "Create Agent" on the card. Per stdrc 2026-05-10
616
- * #proj-approval msg=ae4ecedd: "为什么 computer、runtime 和 model 还是
617
- * 帮用户选了?" — don't let the agent prefill those.
613
+ * Optional computer placement contract. Agents may only set this when the
614
+ * human request is explicitly computer-bound; server prepare resolves the
615
+ * name/UUID and stores the UUID-only form. `suggestedComputer` preselects
616
+ * the dialog when available; `requiredComputer` prevents silent fallback to
617
+ * any other computer.
618
+ *
619
+ * Runtime / model / reasoning effort remain human-picked technical fields.
618
620
  */
621
+ suggestedComputer: idOrHandleSchema.optional(),
622
+ requiredComputer: idOrHandleSchema.optional(),
619
623
  draftHint: draftHintSchema
620
624
  });
621
625
  var channelAddMemberOperationSchema = z.object({
@@ -1501,7 +1505,7 @@ async function handleProxyRequest(req, res) {
1501
1505
  headers.set("content-type", "application/json");
1502
1506
  headers.set("content-length", String(Buffer.byteLength(prepared.bodyText)));
1503
1507
  }
1504
- const upstream = await fetch(target, {
1508
+ const upstream = await daemonFetch(target, {
1505
1509
  method,
1506
1510
  headers,
1507
1511
  body,
@@ -1518,15 +1522,12 @@ async function handleProxyRequest(req, res) {
1518
1522
  res.writeHead(upstream.status, responseHeadersForLocalProxy(upstream));
1519
1523
  if (upstream.body) {
1520
1524
  const reader = upstream.body.getReader();
1521
- try {
1522
- while (true) {
1523
- const { done, value } = await reader.read();
1524
- if (done) break;
1525
- res.write(Buffer.from(value));
1526
- }
1527
- } finally {
1528
- res.end();
1525
+ while (true) {
1526
+ const { done, value } = await reader.read();
1527
+ if (done) break;
1528
+ res.write(Buffer.from(value));
1529
1529
  }
1530
+ res.end();
1530
1531
  } else {
1531
1532
  res.end();
1532
1533
  }
@@ -1536,13 +1537,21 @@ async function handleProxyRequest(req, res) {
1536
1537
  `[Agent Credential Proxy] request failed (agent=${registration.agentId}, launch=${registration.launchId ?? "none"}, method=${failure.method}, path=${failure.pathname}, query_keys=${failure.queryKeys.join(",") || "none"}): ${failure.errorName}: ${failure.errorMessage}`
1537
1538
  );
1538
1539
  registration.inboxCoordinator?.recordProxyFailure?.(failure);
1539
- res.writeHead(502, { "content-type": "application/json" });
1540
- res.end(JSON.stringify({
1541
- error: "failed to proxy local agent request",
1542
- code: "agent_proxy_failed",
1543
- detail: failure.errorMessage
1544
- }));
1540
+ writeProxyFailureResponse(res, failure);
1541
+ }
1542
+ }
1543
+ function writeProxyFailureResponse(res, failure) {
1544
+ if (res.writableEnded) return;
1545
+ if (res.headersSent) {
1546
+ res.destroy();
1547
+ return;
1545
1548
  }
1549
+ res.writeHead(502, { "content-type": "application/json" });
1550
+ res.end(JSON.stringify({
1551
+ error: "failed to proxy local agent request",
1552
+ code: "agent_proxy_failed",
1553
+ detail: failure.errorMessage
1554
+ }));
1546
1555
  }
1547
1556
  function proxyFailureForError(method, target, err) {
1548
1557
  const queryKeys = target ? [.../* @__PURE__ */ new Set([...target.searchParams.keys()])].sort() : [];
@@ -1739,7 +1748,7 @@ async function loadRecentTargetMessages(registration, headers, target) {
1739
1748
  const historyHeaders = new Headers(headers);
1740
1749
  historyHeaders.delete("content-length");
1741
1750
  historyHeaders.delete("content-type");
1742
- const res = await fetch(historyUrl, { method: "GET", headers: historyHeaders });
1751
+ const res = await daemonFetch(historyUrl, { method: "GET", headers: historyHeaders });
1743
1752
  if (!res.ok) return [];
1744
1753
  const parsed = await res.json().catch(() => null);
1745
1754
  return Array.isArray(parsed?.messages) ? normalizeVisibleMessages(parsed.messages, target) : [];
@@ -1942,6 +1951,9 @@ var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1942
1951
  var powershellSingleQuote = (value) => `'${value.replace(/'/g, "''")}'`;
1943
1952
  var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
1944
1953
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
1954
+ var RAW_CREDENTIAL_ENV_DENYLIST = [
1955
+ "SLOCK_AGENT_CREDENTIAL_KEY"
1956
+ ];
1945
1957
  var cachedOpencliBinPath;
1946
1958
  function resolveOpencliBinPath() {
1947
1959
  if (cachedOpencliBinPath !== void 0) return cachedOpencliBinPath;
@@ -2123,8 +2135,9 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
2123
2135
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
2124
2136
  };
2125
2137
  delete spawnEnv.SLOCK_AGENT_TOKEN;
2126
- delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
2127
- delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
2138
+ for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
2139
+ delete spawnEnv[key];
2140
+ }
2128
2141
  delete spawnEnv.SLOCK_AGENT_PROXY_URL;
2129
2142
  delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN;
2130
2143
  delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN_FILE;
@@ -2148,6 +2161,118 @@ import path3 from "path";
2148
2161
  function normalizeExecOutput(raw) {
2149
2162
  return Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw ?? "");
2150
2163
  }
2164
+ var WINDOWS_ENVIRONMENT_SCRIPT = [
2165
+ "& {",
2166
+ " $result = [ordered]@{}",
2167
+ " foreach ($scope in @('Machine', 'User')) {",
2168
+ " $scopeEnv = [Environment]::GetEnvironmentVariables($scope)",
2169
+ " $scopeObj = [ordered]@{}",
2170
+ " foreach ($key in $scopeEnv.Keys) {",
2171
+ " $value = $scopeEnv[$key]",
2172
+ " if ($null -ne $value) { $scopeObj[$key] = [string]$value }",
2173
+ " }",
2174
+ " $result[$scope] = $scopeObj",
2175
+ " }",
2176
+ " $result | ConvertTo-Json -Compress -Depth 3",
2177
+ "}"
2178
+ ].join(" ");
2179
+ function normalizeProcessEnv(value) {
2180
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
2181
+ const env = {};
2182
+ for (const [key, rawValue] of Object.entries(value)) {
2183
+ if (rawValue !== void 0 && rawValue !== null) {
2184
+ env[key] = String(rawValue);
2185
+ }
2186
+ }
2187
+ return env;
2188
+ }
2189
+ function readWindowsMachineUserEnvironment(env, execFileSyncFn) {
2190
+ try {
2191
+ const output = normalizeExecOutput(execFileSyncFn("powershell.exe", [
2192
+ "-NoProfile",
2193
+ "-NonInteractive",
2194
+ "-Command",
2195
+ WINDOWS_ENVIRONMENT_SCRIPT
2196
+ ], {
2197
+ stdio: ["ignore", "pipe", "ignore"],
2198
+ env,
2199
+ timeout: 5e3
2200
+ }));
2201
+ const parsed = JSON.parse(output || "{}");
2202
+ return {
2203
+ machine: normalizeProcessEnv(parsed.Machine),
2204
+ user: normalizeProcessEnv(parsed.User)
2205
+ };
2206
+ } catch {
2207
+ return null;
2208
+ }
2209
+ }
2210
+ function findEnvKey(env, name) {
2211
+ if (!env) return null;
2212
+ const lowerName = name.toLowerCase();
2213
+ const keys = Object.keys(env);
2214
+ for (let index = keys.length - 1; index >= 0; index -= 1) {
2215
+ const key = keys[index];
2216
+ if (key.toLowerCase() === lowerName) return key;
2217
+ }
2218
+ return null;
2219
+ }
2220
+ function getEnvValue(env, name) {
2221
+ const key = findEnvKey(env, name);
2222
+ return key ? env?.[key] : void 0;
2223
+ }
2224
+ function setEnvValue(env, key, value) {
2225
+ const existingKey = findEnvKey(env, key);
2226
+ if (existingKey && existingKey !== key) {
2227
+ delete env[existingKey];
2228
+ }
2229
+ env[key] = value;
2230
+ }
2231
+ function mergeWindowsPathSegments(values) {
2232
+ const segments = [];
2233
+ const seen = /* @__PURE__ */ new Set();
2234
+ for (const value of values) {
2235
+ if (!value) continue;
2236
+ for (const rawSegment of value.split(";")) {
2237
+ const segment = rawSegment.trim();
2238
+ if (!segment) continue;
2239
+ const key = segment.toLowerCase();
2240
+ if (seen.has(key)) continue;
2241
+ seen.add(key);
2242
+ segments.push(segment);
2243
+ }
2244
+ }
2245
+ return segments.length > 0 ? segments.join(";") : void 0;
2246
+ }
2247
+ function mergeWindowsEnvironmentScopes(baseEnv, scopes) {
2248
+ const merged = {};
2249
+ const layers = [scopes.machine ?? {}, scopes.user ?? {}, baseEnv];
2250
+ for (const layer of layers) {
2251
+ for (const [key, value] of Object.entries(layer)) {
2252
+ if (value === void 0 || key.toLowerCase() === "path") continue;
2253
+ setEnvValue(merged, key, value);
2254
+ }
2255
+ }
2256
+ const pathKey = findEnvKey(baseEnv, "Path") ?? findEnvKey(scopes.machine, "Path") ?? findEnvKey(scopes.user, "Path") ?? "Path";
2257
+ const pathValue = mergeWindowsPathSegments([
2258
+ getEnvValue(baseEnv, "Path"),
2259
+ getEnvValue(scopes.machine, "Path"),
2260
+ getEnvValue(scopes.user, "Path")
2261
+ ]);
2262
+ if (pathValue) {
2263
+ merged[pathKey] = pathValue;
2264
+ }
2265
+ return merged;
2266
+ }
2267
+ function withWindowsUserEnvironment(env, deps = {}) {
2268
+ const platform = deps.platform ?? process.platform;
2269
+ if (platform !== "win32") return env;
2270
+ const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
2271
+ const reader = deps.windowsEnvironmentReaderFn ?? readWindowsMachineUserEnvironment;
2272
+ const scopes = reader(env, execFileSyncFn);
2273
+ if (!scopes) return env;
2274
+ return mergeWindowsEnvironmentScopes(env, scopes);
2275
+ }
2151
2276
  function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
2152
2277
  const script = "& {$cmd = Get-Command -Name $args[0] -ErrorAction Stop | Select-Object -First 1; if ($cmd.Path) { $cmd.Path } elseif ($cmd.Source) { $cmd.Source } elseif ($cmd.Definition) { $cmd.Definition } }";
2153
2278
  try {
@@ -2185,7 +2310,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
2185
2310
  }
2186
2311
  function resolveCommandOnPath(command, deps = {}) {
2187
2312
  const platform = deps.platform ?? process.platform;
2188
- const env = deps.env ?? process.env;
2313
+ const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
2189
2314
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
2190
2315
  const existsSyncFn = deps.existsSyncFn ?? existsSync2;
2191
2316
  if (platform === "win32") {
@@ -2211,7 +2336,7 @@ function firstExistingPath(candidates, deps = {}) {
2211
2336
  return null;
2212
2337
  }
2213
2338
  function readCommandVersion(command, args = [], deps = {}) {
2214
- const env = deps.env ?? process.env;
2339
+ const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
2215
2340
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
2216
2341
  try {
2217
2342
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -2488,6 +2613,7 @@ var ClaudeDriver = class {
2488
2613
  const detail = parts.join(" | ") || fallback;
2489
2614
  events.push({ kind: "error", message: detail });
2490
2615
  };
2616
+ const isProviderApiFailureText = (value, hasToolUse) => !hasToolUse && /^\s*API Error:/i.test(value) && (/\b(?:ECONNRESET|EPIPE|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(value) || /\bUnable to connect to API\b/i.test(value) || /\b(?:timed out|timeout)\b/i.test(value) || /\b5\d{2}\b/.test(value));
2491
2617
  switch (event.type) {
2492
2618
  case "system":
2493
2619
  if (event.subtype === "init" && event.session_id) {
@@ -2503,11 +2629,16 @@ var ClaudeDriver = class {
2503
2629
  case "assistant": {
2504
2630
  const content = event.message?.content;
2505
2631
  if (Array.isArray(content)) {
2632
+ const hasToolUse = content.some((block) => block?.type === "tool_use");
2506
2633
  for (const block of content) {
2507
2634
  if (block.type === "thinking" && block.thinking) {
2508
2635
  events.push({ kind: "thinking", text: block.thinking });
2509
2636
  } else if (block.type === "text" && block.text) {
2510
- events.push({ kind: "text", text: block.text });
2637
+ if (isProviderApiFailureText(block.text, hasToolUse)) {
2638
+ events.push({ kind: "error", message: block.text });
2639
+ } else {
2640
+ events.push({ kind: "text", text: block.text });
2641
+ }
2511
2642
  } else if (block.type === "tool_use") {
2512
2643
  events.push({ kind: "tool_call", name: block.name || "unknown_tool", input: block.input });
2513
2644
  }
@@ -2821,6 +2952,11 @@ var CodexDriver = class {
2821
2952
  sessionAnnounced = false;
2822
2953
  streamedAgentMessageIds = /* @__PURE__ */ new Set();
2823
2954
  streamedReasoningIds = /* @__PURE__ */ new Set();
2955
+ /**
2956
+ * Post-tool window where the app-server may not yet accept stdin steering.
2957
+ * Gate busy-mode delivery until turn/completed or next progress.
2958
+ */
2959
+ steeringGateActive = false;
2824
2960
  async spawn(ctx) {
2825
2961
  ensureGitRepoForCodex(ctx.workingDirectory);
2826
2962
  const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
@@ -2835,6 +2971,7 @@ var CodexDriver = class {
2835
2971
  this.sessionAnnounced = false;
2836
2972
  this.streamedAgentMessageIds.clear();
2837
2973
  this.streamedReasoningIds.clear();
2974
+ this.steeringGateActive = false;
2838
2975
  const args = ["app-server", "--listen", "stdio://"];
2839
2976
  args.push(...this.buildRuntimeActionsConfigArgs(ctx));
2840
2977
  const { command, args: spawnArgs } = resolveCodexSpawn(args);
@@ -2908,6 +3045,7 @@ var CodexDriver = class {
2908
3045
  if (typeof turnId === "string") {
2909
3046
  this.activeTurnId = turnId;
2910
3047
  }
3048
+ this.steeringGateActive = false;
2911
3049
  events.push({ kind: "thinking", text: "" });
2912
3050
  break;
2913
3051
  }
@@ -2918,6 +3056,7 @@ var CodexDriver = class {
2918
3056
  this.streamedAgentMessageIds.add(itemId);
2919
3057
  }
2920
3058
  if (typeof delta === "string" && delta.length > 0) {
3059
+ this.steeringGateActive = false;
2921
3060
  events.push({ kind: "text", text: delta });
2922
3061
  }
2923
3062
  break;
@@ -2930,6 +3069,7 @@ var CodexDriver = class {
2930
3069
  this.streamedReasoningIds.add(itemId);
2931
3070
  }
2932
3071
  if (typeof delta === "string" && delta.length > 0) {
3072
+ this.steeringGateActive = false;
2933
3073
  events.push({ kind: "thinking", text: delta });
2934
3074
  }
2935
3075
  break;
@@ -2946,6 +3086,7 @@ var CodexDriver = class {
2946
3086
  if (isCompleted && typeof item.id === "string" && !this.streamedReasoningIds.has(item.id)) {
2947
3087
  const text = joinReasoningText(item);
2948
3088
  if (text) {
3089
+ this.steeringGateActive = false;
2949
3090
  events.push({ kind: "thinking", text });
2950
3091
  }
2951
3092
  }
@@ -2955,6 +3096,7 @@ var CodexDriver = class {
2955
3096
  break;
2956
3097
  case "agentMessage":
2957
3098
  if (isCompleted && typeof item.id === "string" && !this.streamedAgentMessageIds.has(item.id) && typeof item.text === "string" && item.text.length > 0) {
3099
+ this.steeringGateActive = false;
2958
3100
  events.push({ kind: "text", text: item.text });
2959
3101
  }
2960
3102
  if (isCompleted && typeof item.id === "string") {
@@ -2967,6 +3109,7 @@ var CodexDriver = class {
2967
3109
  }
2968
3110
  if (isCompleted) {
2969
3111
  events.push({ kind: "tool_output", name: "shell" });
3112
+ this.steeringGateActive = true;
2970
3113
  }
2971
3114
  break;
2972
3115
  case "contextCompaction":
@@ -2996,17 +3139,24 @@ var CodexDriver = class {
2996
3139
  if (isCompleted) {
2997
3140
  const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
2998
3141
  events.push({ kind: "tool_output", name: toolName });
3142
+ this.steeringGateActive = true;
2999
3143
  }
3000
3144
  break;
3001
3145
  case "collabAgentToolCall":
3002
3146
  if (isStarted) {
3003
3147
  events.push({ kind: "tool_call", name: "collab_tool_call", input: { tool: item.tool, prompt: item.prompt } });
3004
3148
  }
3149
+ if (isCompleted) {
3150
+ this.steeringGateActive = true;
3151
+ }
3005
3152
  break;
3006
3153
  case "webSearch":
3007
3154
  if (isStarted) {
3008
3155
  events.push({ kind: "tool_call", name: "web_search", input: { query: item.query } });
3009
3156
  }
3157
+ if (isCompleted) {
3158
+ this.steeringGateActive = true;
3159
+ }
3010
3160
  break;
3011
3161
  }
3012
3162
  break;
@@ -3019,6 +3169,7 @@ var CodexDriver = class {
3019
3169
  this.activeTurnId = null;
3020
3170
  this.streamedAgentMessageIds.clear();
3021
3171
  this.streamedReasoningIds.clear();
3172
+ this.steeringGateActive = false;
3022
3173
  events.push({ kind: "turn_end", sessionId: this.threadId || void 0 });
3023
3174
  break;
3024
3175
  }
@@ -3038,7 +3189,7 @@ var CodexDriver = class {
3038
3189
  if (!this.threadId) return null;
3039
3190
  const mode = opts?.mode || "busy";
3040
3191
  if (mode === "busy") {
3041
- if (!this.activeTurnId) return null;
3192
+ if (!this.activeTurnId || this.steeringGateActive) return null;
3042
3193
  return JSON.stringify({
3043
3194
  jsonrpc: "2.0",
3044
3195
  id: this.nextRequestId(),
@@ -3452,8 +3603,9 @@ var CopilotDriver = class {
3452
3603
  import { spawn as spawn5, spawnSync } from "child_process";
3453
3604
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
3454
3605
  import path7 from "path";
3455
- async function buildCursorSpawnEnv(ctx) {
3456
- return (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
3606
+ async function buildCursorSpawnEnv(ctx, deps = {}) {
3607
+ const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
3608
+ return withWindowsUserEnvironment(spawnEnv, deps);
3457
3609
  }
3458
3610
  var CursorDriver = class {
3459
3611
  id = "cursor";
@@ -3637,9 +3789,16 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
3637
3789
  if (result.error || result.status !== 0) return null;
3638
3790
  return parseCursorModelsOutput(String(result.stdout || ""));
3639
3791
  }
3792
+ function buildCursorModelProbeEnv(deps = {}) {
3793
+ return withWindowsUserEnvironment({
3794
+ ...deps.env ?? process.env,
3795
+ FORCE_COLOR: "0",
3796
+ NO_COLOR: "1"
3797
+ }, deps);
3798
+ }
3640
3799
  function runCursorModelsCommand() {
3641
3800
  return spawnSync("cursor-agent", ["models"], {
3642
- env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
3801
+ env: buildCursorModelProbeEnv(),
3643
3802
  encoding: "utf8",
3644
3803
  timeout: 5e3
3645
3804
  });
@@ -4777,9 +4936,13 @@ function buildRuntimeErrorDiagnosticEnvelope(message) {
4777
4936
  const { value: excerpt, truncated } = truncateDiagnosticText(scrubbed, MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS);
4778
4937
  const httpStatus = extractHttpStatus(rawMessage);
4779
4938
  const runtimeErrorClass = classifyRuntimeError(rawMessage, httpStatus);
4939
+ const runtimeErrorReason = classifyRuntimeErrorReason(runtimeErrorClass);
4780
4940
  const runtimeErrorAction = classifyRuntimeErrorAction(rawMessage, runtimeErrorClass);
4781
4941
  const fingerprint = fingerprintRuntimeError(scrubbed);
4782
4942
  const spanAttrs = {
4943
+ turn_outcome: "failed",
4944
+ turn_subtype: "runtime_error",
4945
+ turn_reason: runtimeErrorReason,
4783
4946
  runtime_error_class: runtimeErrorClass,
4784
4947
  runtime_error_action: runtimeErrorAction,
4785
4948
  runtime_error_action_required: runtimeErrorAction !== "none",
@@ -4831,7 +4994,10 @@ function classifyRuntimeError(message, httpStatus) {
4831
4994
  return "ProviderApiError";
4832
4995
  }
4833
4996
  if (isRuntimeAuthActionRequiredText(message)) return "AuthError";
4834
- if (/\btimeout|timed out\b/i.test(message)) return "TimeoutError";
4997
+ if (/\b(?:ETIMEDOUT|timeout|timed out)\b/i.test(message)) return "TimeoutError";
4998
+ if (/\b(?:ECONNRESET|EPIPE|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(message) || /\bUnable to connect to API\b/i.test(message)) {
4999
+ return "ProviderConnectionError";
5000
+ }
4835
5001
  if (/stream closed before response\.completed|error decoding response body/i.test(message)) return "ProviderStreamError";
4836
5002
  if (/\brate.?limit|too many requests\b/i.test(message)) return "RateLimitError";
4837
5003
  if (/\bnot found\b/i.test(message)) return "NotFoundError";
@@ -4846,6 +5012,28 @@ function classifyRuntimeErrorAction(message, runtimeErrorClass) {
4846
5012
  function isRuntimeAuthActionRequiredText(text) {
4847
5013
  return RUNTIME_AUTH_ACTION_REQUIRED_PATTERNS.some((pattern) => pattern.test(text));
4848
5014
  }
5015
+ function classifyRuntimeErrorReason(runtimeErrorClass) {
5016
+ switch (runtimeErrorClass) {
5017
+ case "ProviderConnectionError":
5018
+ return "provider_connection_error";
5019
+ case "TimeoutError":
5020
+ return "provider_timeout";
5021
+ case "ProviderStreamError":
5022
+ return "provider_stream_error";
5023
+ case "RateLimitError":
5024
+ return "rate_limited";
5025
+ case "AuthError":
5026
+ return "auth_failed";
5027
+ case "NotFoundError":
5028
+ return "not_found";
5029
+ case "ProviderServerError":
5030
+ return "provider_server_error";
5031
+ case "ProviderApiError":
5032
+ return "provider_api_error";
5033
+ default:
5034
+ return "unclassified_runtime_error";
5035
+ }
5036
+ }
4849
5037
  function runtimeDisplayName(runtimeId) {
4850
5038
  switch (runtimeId) {
4851
5039
  case "antigravity":
@@ -5272,10 +5460,10 @@ For new channels, new agents, and adding members to an existing channel, post an
5272
5460
 
5273
5461
  - Use \`slock action prepare --target <onboarding-channel>\` and pipe an \`ActionCardAction\` JSON. Identity references are handles (\`@alice\` / \`@scout\` / \`#general\` \u2014 bare names work too), never UUIDs. Server resolves at prepare time.
5274
5462
  - \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
5275
- - \`{type: "agent:create", name, description?, draftHint?}\`
5463
+ - \`{type: "agent:create", name, description?, suggestedComputer?, requiredComputer?, draftHint?}\`
5276
5464
  - \`{type: "channel:add_member", channel: "#existing-channel", humans?: ["@alice"], agents?: ["@scout"], draftHint?}\` \u2014 at least one of humans / agents must be non-empty
5277
5465
  - The owner clicks the button on the card; the matching dialog opens **prefilled with your values** (editable, deselectable for add_member). They review, adjust, and submit; the action is committed under their identity.
5278
- - Technical fields the owner must pick themselves are NOT yours to prefill on \`agent:create\`: computer, runtime, model, reasoning effort. Stay on semantic intent (name + description).
5466
+ - Runtime / model / reasoning effort are NOT yours to prefill on \`agent:create\`. If the human request explicitly binds the agent to a computer, use a structured \`requiredComputer\` (or \`suggestedComputer\` for a soft preference) instead of burying the constraint in \`draftHint\`; otherwise stay on semantic intent (name + description).
5279
5467
  - For \`channel:add_member\`, only suggest people who are actually likely candidates (already in the server, relevant to the channel's topic). The owner will deselect anyone they don't want \u2014 make their default-yes list useful, not exhaustive.
5280
5468
  - Do not just describe or list copyable specs once action cards are available \u2014 the human input cost should land at "click the card, review, submit", not "copy this name into the dialog yourself".
5281
5469
  - Do not imply the resource has been created or members added until the card flips to "Done".
@@ -5528,7 +5716,7 @@ Do not copy these answers verbatim.
5528
5716
  - When the owner agrees to a new agent or channel, **post an action card** with \`slock action prepare\`. The card lives inline in chat; the owner clicks the action button, the matching create dialog opens prefilled with your values (editable), and the resource is created under their identity when they submit.
5529
5717
  - v1 supports three action types via \`slock action prepare --target '<channel>' <<'SLOCKACTION' { ... } SLOCKACTION\`:
5530
5718
  - \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
5531
- - \`{type: "agent:create", name, description?, draftHint?}\` \u2014 runtime / model / computer are the owner's call, not yours
5719
+ - \`{type: "agent:create", name, description?, suggestedComputer?, requiredComputer?, draftHint?}\` \u2014 runtime / model / reasoning effort are the owner's call. Use \`requiredComputer\` only when the owner explicitly says the new agent must run on that computer; use \`suggestedComputer\` for a soft preference.
5532
5720
  - \`{type: "channel:add_member", channel: "#existing-channel", humans?: ["@alice"], agents?: ["@scout"], draftHint?}\` \u2014 at least one of humans / agents must be non-empty. The owner clicks "Add Members" on the card; an AddMembers dialog opens with your suggested list (each row toggleable) and the owner submits to actually add them.
5533
5721
 
5534
5722
  - **Identity references are handles, not UUIDs.** Use \`@alice\` / \`@scout\` / \`#general\` (or bare \`alice\` / \`scout\` / \`general\`). The server resolves to UUIDs at prepare time. If a handle doesn't match a real human / agent / channel in this server you get a 422 INVALID_HANDLE error pointing at the field \u2014 fix the handle and retry. You should never see or write UUIDs in action card payloads.
@@ -5539,7 +5727,7 @@ Do not copy these answers verbatim.
5539
5727
 
5540
5728
  ### Guardrail
5541
5729
  - Do not imply you already created agents or channels unless the card state is \`executed\`.
5542
- - Do not prefill technical fields on \`agent:create\` (runtime / model / computer / reasoning effort). The owner picks those in the dialog.
5730
+ - Do not prefill runtime / model / reasoning effort on \`agent:create\`. Computer placement is only allowed as the structured \`suggestedComputer\` / \`requiredComputer\` field when the owner's request includes that placement; never rely on \`draftHint\` for a computer constraint.
5543
5731
  - If the action type the user wants is not yet supported (e.g. \`channel:add_member\`), say so plainly and offer the manual UI path; do not invent action types the schema does not accept.
5544
5732
  `;
5545
5733
  }
@@ -6886,7 +7074,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6886
7074
  }
6887
7075
  async requestManagedRunnerCredentialOnce(agentId, config) {
6888
7076
  const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.serverUrl);
6889
- const res = await fetch(url, {
7077
+ const res = await daemonFetch(url, {
6890
7078
  method: "POST",
6891
7079
  headers: {
6892
7080
  Authorization: `Bearer ${this.daemonApiKey}`,
@@ -6984,7 +7172,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6984
7172
  `/internal/computer/runners/${encodeURIComponent(agentId)}/credentials/${encodeURIComponent(credentialId)}`,
6985
7173
  this.serverUrl
6986
7174
  );
6987
- void fetch(url, {
7175
+ void daemonFetch(url, {
6988
7176
  method: "DELETE",
6989
7177
  headers: {
6990
7178
  Authorization: `Bearer ${this.daemonApiKey}`,
@@ -7065,14 +7253,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7065
7253
  });
7066
7254
  logger.info(`[Agent ${agentId}] Queued runtime profile ${kind} ${key} during startup`);
7067
7255
  }
7068
- splitRuntimeProfileControlBatch(messages) {
7069
- const controlMessages = messages.filter((message) => runtimeProfileNotificationFromMessage(message));
7070
- if (controlMessages.length === 0 || controlMessages.length === messages.length) {
7071
- return { nextMessages: messages, deferredMessages: [] };
7072
- }
7073
- const deferredMessages = messages.filter((message) => !runtimeProfileNotificationFromMessage(message));
7074
- return { nextMessages: controlMessages, deferredMessages };
7075
- }
7076
7256
  containsOrdinaryInboxMessage(messages) {
7077
7257
  return messages.some((message) => !runtimeProfileNotificationFromMessage(message));
7078
7258
  }
@@ -8602,7 +8782,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8602
8782
  ...runtimeTraceCounterAttrs(ap)
8603
8783
  });
8604
8784
  this.endRuntimeTrace(ap, "error", {
8605
- outcome: "runtime-error",
8606
8785
  ...runtimeErrorDiagnostics.spanAttrs,
8607
8786
  ...runtimeTraceCounterAttrs(ap),
8608
8787
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
@@ -8775,22 +8954,6 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8775
8954
  return true;
8776
8955
  }
8777
8956
  }
8778
- const split = this.splitRuntimeProfileControlBatch(messages);
8779
- if (split.deferredMessages.length > 0) {
8780
- ap.inbox.unshift(...split.deferredMessages);
8781
- ap.pendingNotificationCount += split.deferredMessages.length;
8782
- messages = split.nextMessages;
8783
- this.recordDaemonTrace("daemon.agent.runtime_profile.split_batch", {
8784
- agentId,
8785
- launchId: ap.launchId || void 0,
8786
- runtime: ap.config.runtime,
8787
- mode,
8788
- delivered_control_messages_count: messages.length,
8789
- deferred_messages_count: split.deferredMessages.length,
8790
- inbox_count: ap.inbox.length,
8791
- pending_notification_count: ap.pendingNotificationCount
8792
- });
8793
- }
8794
8957
  const traceAttrs = {
8795
8958
  agentId,
8796
8959
  launchId: ap.launchId || void 0,
@@ -9474,7 +9637,7 @@ async function requestDaemonScopeAttestation({
9474
9637
  apiKey,
9475
9638
  scope,
9476
9639
  metadata,
9477
- fetchImpl = fetch,
9640
+ fetchImpl = daemonFetch,
9478
9641
  timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
9479
9642
  }) {
9480
9643
  const { response, data } = await executeJsonRequest(
@@ -9507,7 +9670,7 @@ async function createDirectUploadSession({
9507
9670
  createPath = "/api/uploads",
9508
9671
  body,
9509
9672
  attestationMetadata,
9510
- fetchImpl = fetch,
9673
+ fetchImpl = daemonFetch,
9511
9674
  timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
9512
9675
  }) {
9513
9676
  const capability = await requestDaemonScopeAttestation({
@@ -9549,7 +9712,7 @@ async function uploadWithSignedCapability({
9549
9712
  createBody,
9550
9713
  attestationMetadata,
9551
9714
  uploadBody,
9552
- fetchImpl = fetch,
9715
+ fetchImpl = daemonFetch,
9553
9716
  timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
9554
9717
  }) {
9555
9718
  const { capability, response: session } = await createDirectUploadSession({
@@ -10173,7 +10336,7 @@ var DaemonCore = class {
10173
10336
  }
10174
10337
  async requestRunnerCredentialOnce(agentId, config) {
10175
10338
  const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.options.serverUrl);
10176
- const res = await fetch(url, {
10339
+ const res = await daemonFetch(url, {
10177
10340
  method: "POST",
10178
10341
  headers: {
10179
10342
  "Authorization": `Bearer ${this.options.apiKey}`,