@botiverse/raft-daemon 0.61.1 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1288,6 +1288,9 @@ var SERVER_CAPABILITY_MATRIX = {
1288
1288
 
1289
1289
  // ../shared/src/index.ts
1290
1290
  var RUNTIME_CONFIG_VERSION = 1;
1291
+ var PI_BUILTIN_PROVIDER_ENV_KEYS = {
1292
+ deepseek: "DEEPSEEK_API_KEY"
1293
+ };
1291
1294
  var AGENT_ACTIVITIES = ["online", "thinking", "working", "error", "offline"];
1292
1295
  var isAgentActivity = makeIsMember(AGENT_ACTIVITIES);
1293
1296
  var VALID_ACTIVITIES = new Set(AGENT_ACTIVITIES);
@@ -1392,7 +1395,12 @@ function getDefaultModel(runtimeId) {
1392
1395
  return models?.[0]?.id ?? "sonnet";
1393
1396
  }
1394
1397
  var CONTROLLED_RUNTIME_ENV_KEYS = {
1395
- claude: ["ANTHROPIC_BASE_URL", "ANTHROPIC_API_KEY", "ANTHROPIC_CUSTOM_MODEL_OPTION"]
1398
+ claude: ["ANTHROPIC_BASE_URL", "ANTHROPIC_API_KEY", "ANTHROPIC_CUSTOM_MODEL_OPTION"],
1399
+ // Pi-runtime builtin-provider env vars (e.g. DEEPSEEK_API_KEY). Owned by
1400
+ // RuntimeProviderConfig.pi-builtin → runtimeConfigToLaunchFields, not by
1401
+ // user-supplied envVars: reading from PI_BUILTIN_PROVIDER_ENV_KEYS keeps
1402
+ // this list in sync as new providers are added.
1403
+ pi: Object.values(PI_BUILTIN_PROVIDER_ENV_KEYS)
1396
1404
  };
1397
1405
  function isPlainRecord(value) {
1398
1406
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -1426,15 +1434,24 @@ function modelConfigFromLegacy(runtime, model) {
1426
1434
  return isPresetRuntimeModel(runtime, model) ? { kind: "preset", id: model } : { kind: "custom", name: model };
1427
1435
  }
1428
1436
  function parseProviderConfig(runtime, value, legacyApiUrl, legacyApiKey) {
1429
- if (runtime !== "claude") return void 0;
1430
- if (!isPlainRecord(value)) return { kind: "default" };
1431
- if (value.kind === "custom" && typeof value.apiUrl === "string" && value.apiUrl.trim() && typeof value.apiKey === "string" && value.apiKey.trim()) {
1432
- return { kind: "custom", apiUrl: value.apiUrl.trim(), apiKey: value.apiKey.trim() };
1437
+ if (runtime === "claude") {
1438
+ if (!isPlainRecord(value)) return { kind: "default" };
1439
+ if (value.kind === "custom" && typeof value.apiUrl === "string" && value.apiUrl.trim() && typeof value.apiKey === "string" && value.apiKey.trim()) {
1440
+ return { kind: "custom", apiUrl: value.apiUrl.trim(), apiKey: value.apiKey.trim() };
1441
+ }
1442
+ if (value.kind === "custom" && legacyApiUrl?.trim() && legacyApiKey?.trim()) {
1443
+ return { kind: "custom", apiUrl: legacyApiUrl.trim(), apiKey: legacyApiKey.trim() };
1444
+ }
1445
+ return { kind: "default" };
1433
1446
  }
1434
- if (value.kind === "custom" && legacyApiUrl?.trim() && legacyApiKey?.trim()) {
1435
- return { kind: "custom", apiUrl: legacyApiUrl.trim(), apiKey: legacyApiKey.trim() };
1447
+ if (runtime === "pi") {
1448
+ if (!isPlainRecord(value)) return { kind: "default" };
1449
+ if (value.kind === "pi-builtin" && typeof value.providerId === "string" && value.providerId.trim() && typeof value.apiKey === "string" && value.apiKey.trim() && Object.prototype.hasOwnProperty.call(PI_BUILTIN_PROVIDER_ENV_KEYS, value.providerId.trim())) {
1450
+ return { kind: "pi-builtin", providerId: value.providerId.trim(), apiKey: value.apiKey.trim() };
1451
+ }
1452
+ return { kind: "default" };
1436
1453
  }
1437
- return { kind: "default" };
1454
+ return void 0;
1438
1455
  }
1439
1456
  function parseModelConfig(runtime, value, fallback, provider, legacyCustomModel) {
1440
1457
  if (!isPlainRecord(value)) return modelConfigFromLegacy(runtime, fallback);
@@ -1496,6 +1513,10 @@ function runtimeConfigToLaunchFields(config) {
1496
1513
  generatedEnvVars.ANTHROPIC_CUSTOM_MODEL_OPTION = normalized.model.name;
1497
1514
  }
1498
1515
  }
1516
+ if (normalized.runtime === "pi" && normalized.provider?.kind === "pi-builtin") {
1517
+ const envKey = PI_BUILTIN_PROVIDER_ENV_KEYS[normalized.provider.providerId];
1518
+ if (envKey) generatedEnvVars[envKey] = normalized.provider.apiKey;
1519
+ }
1499
1520
  const envVars = {
1500
1521
  ...normalized.envVars ?? {},
1501
1522
  ...generatedEnvVars
@@ -1512,7 +1533,7 @@ function runtimeConfigToLaunchFields(config) {
1512
1533
  var PLAN_CONFIG = {
1513
1534
  free: {
1514
1535
  displayName: "Free",
1515
- limits: { maxMachines: 2, maxAgents: 5, maxChannels: 5, messageHistoryDays: 30, includedAgents: 5 },
1536
+ limits: { maxMachines: -1, maxAgents: -1, maxChannels: -1, messageHistoryDays: 30, includedAgents: -1 },
1516
1537
  comingSoon: false,
1517
1538
  price: 0,
1518
1539
  extraAgentPrice: 0,
@@ -1532,13 +1553,20 @@ var PLAN_CONFIG = {
1532
1553
  comingSoon: false,
1533
1554
  price: 0,
1534
1555
  extraAgentPrice: 0
1556
+ },
1557
+ pro: {
1558
+ displayName: "Pro",
1559
+ limits: { maxMachines: -1, maxAgents: 10, maxChannels: -1, messageHistoryDays: -1, includedAgents: 10 },
1560
+ comingSoon: false,
1561
+ price: 20,
1562
+ extraAgentPrice: 0
1535
1563
  }
1536
1564
  };
1537
1565
  var DISPLAY_PLAN_CONFIG = {
1538
1566
  free: PLAN_CONFIG.free,
1539
1567
  pro: {
1540
1568
  displayName: "Pro",
1541
- limits: { maxMachines: -1, maxAgents: 10, maxChannels: -1, messageHistoryDays: -1, includedAgents: 10 },
1569
+ limits: { maxMachines: -1, maxAgents: -1, maxChannels: -1, messageHistoryDays: -1, includedAgents: -1 },
1542
1570
  comingSoon: false,
1543
1571
  price: 20,
1544
1572
  priceCadence: "/ seat pack / month",
@@ -1568,14 +1596,406 @@ var DISPLAY_PLAN_CONFIG = {
1568
1596
  ]
1569
1597
  }
1570
1598
  };
1599
+ var FREE_MONTHLY_FILE_UPLOAD_LIMIT_BYTES = 100 * 1024 * 1024;
1571
1600
 
1572
1601
  // src/agentProcessManager.ts
1573
- import { mkdirSync as mkdirSync5, readdirSync as readdirSync3, statSync, writeFileSync as writeFileSync4 } from "fs";
1574
- import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
1575
- import { createHash as createHash3 } from "crypto";
1602
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync, writeFileSync as writeFileSync4 } from "fs";
1603
+ import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2, lstat, realpath, open } from "fs/promises";
1604
+ import { createHash as createHash3, randomUUID as randomUUID5 } from "crypto";
1576
1605
  import path13 from "path";
1606
+ import { gzipSync } from "zlib";
1577
1607
  import os6 from "os";
1578
1608
 
1609
+ // src/proxy.ts
1610
+ import { HttpsProxyAgent } from "https-proxy-agent";
1611
+ import { ProxyAgent } from "undici";
1612
+ var fetchDispatcherCache = /* @__PURE__ */ new Map();
1613
+ function getFetchPreResponseTimeoutMs(env) {
1614
+ const parsed = Number.parseInt(env.SLOCK_DAEMON_FETCH_PRE_RESPONSE_TIMEOUT_MS || "", 10);
1615
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 3e4;
1616
+ }
1617
+ function getDefaultPort(protocol) {
1618
+ switch (protocol) {
1619
+ case "https:":
1620
+ case "wss:":
1621
+ return "443";
1622
+ case "http:":
1623
+ case "ws:":
1624
+ return "80";
1625
+ default:
1626
+ return "";
1627
+ }
1628
+ }
1629
+ function hostMatchesNoProxyEntry(hostname, ruleHost) {
1630
+ if (!ruleHost) return false;
1631
+ const normalizedRule = ruleHost.replace(/^\*\./, ".").replace(/^\./, "").toLowerCase();
1632
+ const normalizedHost = hostname.toLowerCase();
1633
+ return normalizedHost === normalizedRule || normalizedHost.endsWith(`.${normalizedRule}`);
1634
+ }
1635
+ function getProxyUrlForTarget(targetUrl, env) {
1636
+ const protocol = new URL(targetUrl).protocol;
1637
+ switch (protocol) {
1638
+ case "wss:":
1639
+ return env.WSS_PROXY || env.wss_proxy || env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
1640
+ case "ws:":
1641
+ return env.WS_PROXY || env.ws_proxy || env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
1642
+ case "https:":
1643
+ return env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
1644
+ case "http:":
1645
+ return env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
1646
+ default:
1647
+ return env.ALL_PROXY || env.all_proxy;
1648
+ }
1649
+ }
1650
+ function shouldBypassProxy(targetUrl, env) {
1651
+ const rawNoProxy = env.NO_PROXY || env.no_proxy;
1652
+ if (!rawNoProxy) return false;
1653
+ const url = new URL(targetUrl);
1654
+ const hostname = url.hostname.toLowerCase();
1655
+ const port = url.port || getDefaultPort(url.protocol);
1656
+ return rawNoProxy.split(",").map((entry) => entry.trim()).filter(Boolean).some((entry) => {
1657
+ if (entry === "*") return true;
1658
+ const [ruleHost, rulePort] = entry.split(":", 2);
1659
+ if (rulePort && rulePort !== port) return false;
1660
+ return hostMatchesNoProxyEntry(hostname, ruleHost);
1661
+ });
1662
+ }
1663
+ function buildWebSocketOptions(wsUrl, env) {
1664
+ const proxyUrl = getProxyUrlForTarget(wsUrl, env);
1665
+ if (!proxyUrl) return void 0;
1666
+ if (shouldBypassProxy(wsUrl, env)) return void 0;
1667
+ return {
1668
+ agent: new HttpsProxyAgent(proxyUrl)
1669
+ };
1670
+ }
1671
+ function resolveProxyUrl(targetUrl, env) {
1672
+ const proxyUrl = getProxyUrlForTarget(targetUrl, env);
1673
+ if (!proxyUrl) return void 0;
1674
+ if (shouldBypassProxy(targetUrl, env)) return void 0;
1675
+ return proxyUrl;
1676
+ }
1677
+ function buildFetchDispatcher(targetUrl, env) {
1678
+ const proxyUrl = resolveProxyUrl(targetUrl, env);
1679
+ if (!proxyUrl) return void 0;
1680
+ const cached = fetchDispatcherCache.get(proxyUrl);
1681
+ if (cached) return cached;
1682
+ const timeoutMs = getFetchPreResponseTimeoutMs(env);
1683
+ const dispatcher = new ProxyAgent({
1684
+ uri: proxyUrl,
1685
+ // All three are pre-response and body-agnostic (see getFetchPreResponseTimeoutMs):
1686
+ // headersTimeout = headers-hang leg; requestTls.timeout = CONNECT-tunnel
1687
+ // establish leg; connect.timeout = socket to the proxy itself (defensive).
1688
+ connect: { timeout: timeoutMs },
1689
+ requestTls: { timeout: timeoutMs },
1690
+ headersTimeout: timeoutMs
1691
+ });
1692
+ fetchDispatcherCache.set(proxyUrl, dispatcher);
1693
+ return dispatcher;
1694
+ }
1695
+ function evictFetchDispatcher(targetUrl, env) {
1696
+ const proxyUrl = resolveProxyUrl(targetUrl, env);
1697
+ if (!proxyUrl) return false;
1698
+ const cached = fetchDispatcherCache.get(proxyUrl);
1699
+ if (!cached) return false;
1700
+ fetchDispatcherCache.delete(proxyUrl);
1701
+ void Promise.resolve().then(() => cached.close()).catch(() => cached.destroy?.(new Error("evicted"))).catch(() => {
1702
+ });
1703
+ return true;
1704
+ }
1705
+
1706
+ // src/daemonFetch.ts
1707
+ function withDaemonFetchProxy(input, init = {}, env = process.env) {
1708
+ const dispatcher = buildFetchDispatcher(input.toString(), env);
1709
+ return dispatcher ? { ...init, dispatcher } : init;
1710
+ }
1711
+ async function daemonFetch(input, init, env = process.env) {
1712
+ try {
1713
+ return await fetch(input, withDaemonFetchProxy(input, init, env));
1714
+ } catch (err) {
1715
+ evictFetchDispatcher(input.toString(), env);
1716
+ throw err;
1717
+ }
1718
+ }
1719
+
1720
+ // src/attachmentFormatting.ts
1721
+ function attachmentDownloadHint(style = "slock_cli") {
1722
+ switch (style) {
1723
+ case "slock_cli":
1724
+ return "use `raft attachment view --id <attachmentId> --output <path>` to download";
1725
+ case "mcp_tool":
1726
+ return "use view_file to download";
1727
+ }
1728
+ }
1729
+ function formatAttachmentSuffix(attachments, style = "slock_cli") {
1730
+ if (!attachments?.length) return "";
1731
+ const attachmentList = attachments.map((attachment) => `${attachment.filename} (id:${attachment.id})`).join(", ");
1732
+ return ` [${attachments.length} attachment${attachments.length > 1 ? "s" : ""}: ${attachmentList} \u2014 ${attachmentDownloadHint(style)}]`;
1733
+ }
1734
+
1735
+ // src/logger.ts
1736
+ var listeners = /* @__PURE__ */ new Set();
1737
+ function timestamp() {
1738
+ const d = /* @__PURE__ */ new Date();
1739
+ const pad = (n) => String(n).padStart(2, "0");
1740
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1741
+ }
1742
+ function format(level, msg) {
1743
+ return `${timestamp()} [${level}] ${msg}`;
1744
+ }
1745
+ function emit(event) {
1746
+ for (const listener of listeners) {
1747
+ listener(event);
1748
+ }
1749
+ }
1750
+ function subscribeDaemonLogs(listener) {
1751
+ listeners.add(listener);
1752
+ return () => {
1753
+ listeners.delete(listener);
1754
+ };
1755
+ }
1756
+ var logger = {
1757
+ info(msg) {
1758
+ const line = format("INFO", msg);
1759
+ console.log(line);
1760
+ emit({ level: "INFO", line, message: msg });
1761
+ },
1762
+ warn(msg) {
1763
+ const line = format("WARN", msg);
1764
+ console.warn(line);
1765
+ emit({ level: "WARN", line, message: msg });
1766
+ },
1767
+ error(msg, err) {
1768
+ const line = format("ERROR", msg);
1769
+ if (err) {
1770
+ console.error(line, err);
1771
+ } else {
1772
+ console.error(line);
1773
+ }
1774
+ emit({ level: "ERROR", line, message: msg, error: err });
1775
+ }
1776
+ };
1777
+
1778
+ // src/chatBridgeRequest.ts
1779
+ var DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS = Number.parseInt(
1780
+ process.env.SLOCK_CHAT_BRIDGE_TOOL_TIMEOUT_MS || "",
1781
+ 10
1782
+ ) || 6e4;
1783
+ var ChatBridgeToolTimeoutError = class extends Error {
1784
+ toolName;
1785
+ target;
1786
+ timeoutMs;
1787
+ durationMs;
1788
+ constructor(toolName, target, timeoutMs, durationMs) {
1789
+ super(`${toolName} timed out after ${timeoutMs}ms${target ? ` (target: ${target})` : ""}`);
1790
+ this.name = "ChatBridgeToolTimeoutError";
1791
+ this.toolName = toolName;
1792
+ this.target = target;
1793
+ this.timeoutMs = timeoutMs;
1794
+ this.durationMs = durationMs;
1795
+ }
1796
+ };
1797
+ function describeError(err) {
1798
+ if (err instanceof Error) return `${err.name}: ${err.message}`;
1799
+ return String(err);
1800
+ }
1801
+ async function executeJsonRequest(url, init, {
1802
+ toolName,
1803
+ target = null,
1804
+ timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
1805
+ fetchImpl,
1806
+ now = () => Date.now(),
1807
+ warn = (message) => logger.warn(message)
1808
+ }) {
1809
+ const startedAt = now();
1810
+ const timeoutController = new AbortController();
1811
+ const signals = [timeoutController.signal];
1812
+ if (init.signal) signals.push(init.signal);
1813
+ const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
1814
+ const timeout = setTimeout(() => {
1815
+ timeoutController.abort();
1816
+ }, timeoutMs);
1817
+ timeout.unref?.();
1818
+ try {
1819
+ const response = await fetchImpl(url, { ...init, signal });
1820
+ const data = await response.json();
1821
+ return { response, data, durationMs: now() - startedAt };
1822
+ } catch (err) {
1823
+ const durationMs = now() - startedAt;
1824
+ if (timeoutController.signal.aborted && !init.signal?.aborted) {
1825
+ warn(
1826
+ `[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
1827
+ );
1828
+ throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
1829
+ }
1830
+ warn(
1831
+ `[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
1832
+ );
1833
+ throw err;
1834
+ } finally {
1835
+ clearTimeout(timeout);
1836
+ }
1837
+ }
1838
+ async function executeResponseRequest(url, init, {
1839
+ toolName,
1840
+ target = null,
1841
+ timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
1842
+ fetchImpl,
1843
+ now = () => Date.now(),
1844
+ warn = (message) => logger.warn(message)
1845
+ }) {
1846
+ const startedAt = now();
1847
+ const timeoutController = new AbortController();
1848
+ const signals = [timeoutController.signal];
1849
+ if (init.signal) signals.push(init.signal);
1850
+ const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
1851
+ const timeout = setTimeout(() => {
1852
+ timeoutController.abort();
1853
+ }, timeoutMs);
1854
+ timeout.unref?.();
1855
+ try {
1856
+ const response = await fetchImpl(url, { ...init, signal });
1857
+ return { response, durationMs: now() - startedAt };
1858
+ } catch (err) {
1859
+ const durationMs = now() - startedAt;
1860
+ if (timeoutController.signal.aborted && !init.signal?.aborted) {
1861
+ warn(
1862
+ `[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
1863
+ );
1864
+ throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
1865
+ }
1866
+ warn(
1867
+ `[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
1868
+ );
1869
+ throw err;
1870
+ } finally {
1871
+ clearTimeout(timeout);
1872
+ }
1873
+ }
1874
+
1875
+ // src/directUploadCapability.ts
1876
+ function joinUrl(base, path18) {
1877
+ return `${base.replace(/\/+$/, "")}${path18}`;
1878
+ }
1879
+ function jsonHeaders(apiKey) {
1880
+ return {
1881
+ "Content-Type": "application/json",
1882
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
1883
+ };
1884
+ }
1885
+ async function requestDaemonScopeAttestation({
1886
+ serverUrl,
1887
+ apiKey,
1888
+ scope,
1889
+ metadata,
1890
+ fetchImpl = daemonFetch,
1891
+ timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
1892
+ }) {
1893
+ const { response, data } = await executeJsonRequest(
1894
+ joinUrl(serverUrl, "/internal/machine/scope-attestation"),
1895
+ {
1896
+ method: "POST",
1897
+ headers: jsonHeaders(apiKey),
1898
+ body: JSON.stringify({
1899
+ scope,
1900
+ ...metadata ? { metadata } : {}
1901
+ })
1902
+ },
1903
+ {
1904
+ toolName: "daemon_direct_upload.scope_attestation",
1905
+ target: scope,
1906
+ timeoutMs,
1907
+ fetchImpl
1908
+ }
1909
+ );
1910
+ if (!response.ok) {
1911
+ throw new Error(`Failed to request daemon scope attestation (${response.status})`);
1912
+ }
1913
+ return data;
1914
+ }
1915
+ async function createDirectUploadSession({
1916
+ serverUrl,
1917
+ apiKey,
1918
+ workerUrl,
1919
+ scope,
1920
+ createPath = "/api/uploads",
1921
+ body,
1922
+ attestationMetadata,
1923
+ fetchImpl = daemonFetch,
1924
+ timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
1925
+ }) {
1926
+ const capability = await requestDaemonScopeAttestation({
1927
+ serverUrl,
1928
+ apiKey,
1929
+ scope,
1930
+ metadata: attestationMetadata,
1931
+ fetchImpl,
1932
+ timeoutMs
1933
+ });
1934
+ const { response, data } = await executeJsonRequest(
1935
+ joinUrl(workerUrl, createPath),
1936
+ {
1937
+ method: "POST",
1938
+ headers: jsonHeaders(),
1939
+ body: JSON.stringify({
1940
+ ...body,
1941
+ attestation: capability.attestation
1942
+ })
1943
+ },
1944
+ {
1945
+ toolName: "daemon_direct_upload.create",
1946
+ target: capability.audience,
1947
+ timeoutMs,
1948
+ fetchImpl
1949
+ }
1950
+ );
1951
+ if (!response.ok) {
1952
+ throw new Error(`Failed to create direct upload session (${response.status})`);
1953
+ }
1954
+ return { capability, response: data };
1955
+ }
1956
+ async function uploadWithSignedCapability({
1957
+ serverUrl,
1958
+ apiKey,
1959
+ workerUrl,
1960
+ scope,
1961
+ createPath = "/api/uploads",
1962
+ createBody,
1963
+ attestationMetadata,
1964
+ uploadBody,
1965
+ fetchImpl = daemonFetch,
1966
+ timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
1967
+ }) {
1968
+ const { capability, response: session } = await createDirectUploadSession({
1969
+ serverUrl,
1970
+ apiKey,
1971
+ workerUrl,
1972
+ scope,
1973
+ createPath,
1974
+ body: createBody,
1975
+ attestationMetadata,
1976
+ fetchImpl,
1977
+ timeoutMs
1978
+ });
1979
+ const { response: uploadResponse } = await executeResponseRequest(
1980
+ session.upload.url,
1981
+ {
1982
+ method: session.upload.method,
1983
+ headers: session.upload.headers ?? {},
1984
+ body: uploadBody
1985
+ },
1986
+ {
1987
+ toolName: "daemon_direct_upload.put",
1988
+ target: capability.audience,
1989
+ timeoutMs,
1990
+ fetchImpl
1991
+ }
1992
+ );
1993
+ if (!uploadResponse.ok) {
1994
+ throw new Error(`Failed to upload with signed capability (${uploadResponse.status})`);
1995
+ }
1996
+ return { capability, session, uploadResponse };
1997
+ }
1998
+
1579
1999
  // src/drivers/claude.ts
1580
2000
  import { spawn } from "child_process";
1581
2001
 
@@ -2170,6 +2590,9 @@ import { URL as URL2 } from "url";
2170
2590
  // src/apmStateMachine.ts
2171
2591
  import { createHash } from "crypto";
2172
2592
  var MAX_APM_GATED_STEERING_EVENTS = 12;
2593
+ function reviewStatePatch(state) {
2594
+ return state.reviewing !== void 0 ? { reviewing: state.reviewing } : {};
2595
+ }
2173
2596
  function createInitialApmGatedSteeringState() {
2174
2597
  return {
2175
2598
  isIdle: false,
@@ -2182,6 +2605,22 @@ function createInitialApmGatedSteeringState() {
2182
2605
  recentEvents: []
2183
2606
  };
2184
2607
  }
2608
+ function commitApmGatedSteeringDecisionState(nextState) {
2609
+ const committed = {
2610
+ isIdle: nextState.isIdle,
2611
+ expectedTerminationReason: nextState.expectedTerminationReason,
2612
+ phase: nextState.phase,
2613
+ outstandingToolUses: nextState.outstandingToolUses,
2614
+ compacting: nextState.compacting,
2615
+ toolBoundaryFlushDisabled: nextState.toolBoundaryFlushDisabled,
2616
+ lastFlushReason: nextState.lastFlushReason,
2617
+ recentEvents: [...nextState.recentEvents]
2618
+ };
2619
+ if (nextState.reviewing !== void 0) {
2620
+ committed.reviewing = nextState.reviewing;
2621
+ }
2622
+ return committed;
2623
+ }
2185
2624
  function reduceApmIdleState(state, input) {
2186
2625
  return {
2187
2626
  nextState: {
@@ -2199,6 +2638,7 @@ function reduceApmGatedToolUse(state, input) {
2199
2638
  phase: "tool_wait",
2200
2639
  outstandingToolUses: state.outstandingToolUses + 1,
2201
2640
  compacting: state.compacting,
2641
+ ...reviewStatePatch(state),
2202
2642
  toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
2203
2643
  lastFlushReason: state.lastFlushReason,
2204
2644
  recentEvents: state.recentEvents
@@ -2216,6 +2656,7 @@ function reduceApmGatedToolUse(state, input) {
2216
2656
  phase: "tool_boundary",
2217
2657
  outstandingToolUses,
2218
2658
  compacting: state.compacting,
2659
+ ...reviewStatePatch(state),
2219
2660
  toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
2220
2661
  lastFlushReason: state.lastFlushReason,
2221
2662
  recentEvents: state.recentEvents
@@ -2233,20 +2674,52 @@ function reduceApmGatedCompaction(state, input) {
2233
2674
  phase: "compacting",
2234
2675
  outstandingToolUses: state.outstandingToolUses,
2235
2676
  compacting: true,
2677
+ ...reviewStatePatch(state),
2678
+ toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
2679
+ lastFlushReason: state.lastFlushReason,
2680
+ recentEvents: state.recentEvents
2681
+ }
2682
+ };
2683
+ }
2684
+ if (input.kind === "compaction_interrupted") {
2685
+ return {
2686
+ nextState: {
2687
+ isIdle: false,
2688
+ expectedTerminationReason: state.expectedTerminationReason,
2689
+ phase: state.phase,
2690
+ outstandingToolUses: state.outstandingToolUses,
2691
+ compacting: false,
2692
+ ...reviewStatePatch(state),
2236
2693
  toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
2237
2694
  lastFlushReason: state.lastFlushReason,
2238
2695
  recentEvents: state.recentEvents
2239
2696
  }
2240
2697
  };
2241
2698
  }
2242
- if (input.kind === "compaction_interrupted") {
2699
+ return {
2700
+ nextState: {
2701
+ isIdle: false,
2702
+ expectedTerminationReason: state.expectedTerminationReason,
2703
+ phase: "assistant_continuation",
2704
+ outstandingToolUses: state.outstandingToolUses,
2705
+ compacting: false,
2706
+ ...reviewStatePatch(state),
2707
+ toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
2708
+ lastFlushReason: state.lastFlushReason,
2709
+ recentEvents: state.recentEvents
2710
+ }
2711
+ };
2712
+ }
2713
+ function reduceApmGatedReview(state, input) {
2714
+ if (input.kind === "review_started") {
2243
2715
  return {
2244
2716
  nextState: {
2245
2717
  isIdle: false,
2246
2718
  expectedTerminationReason: state.expectedTerminationReason,
2247
- phase: state.phase,
2719
+ phase: "reviewing",
2248
2720
  outstandingToolUses: state.outstandingToolUses,
2249
- compacting: false,
2721
+ compacting: state.compacting,
2722
+ reviewing: true,
2250
2723
  toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
2251
2724
  lastFlushReason: state.lastFlushReason,
2252
2725
  recentEvents: state.recentEvents
@@ -2259,7 +2732,8 @@ function reduceApmGatedCompaction(state, input) {
2259
2732
  expectedTerminationReason: state.expectedTerminationReason,
2260
2733
  phase: "assistant_continuation",
2261
2734
  outstandingToolUses: state.outstandingToolUses,
2262
- compacting: false,
2735
+ compacting: state.compacting,
2736
+ reviewing: false,
2263
2737
  toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
2264
2738
  lastFlushReason: state.lastFlushReason,
2265
2739
  recentEvents: state.recentEvents
@@ -2280,6 +2754,20 @@ function reduceApmGatedCompactionBoundaryFlush(_state, input) {
2280
2754
  }]
2281
2755
  };
2282
2756
  }
2757
+ function reduceApmGatedReviewBoundaryFlush(_state, input) {
2758
+ if (!input.hasSession || !input.supportsStdinNotification || input.inboxLength === 0) {
2759
+ return { effects: [] };
2760
+ }
2761
+ if (input.pendingNotificationCount === 0) return { effects: [] };
2762
+ return {
2763
+ effects: [{
2764
+ kind: "notify_stdin",
2765
+ reason: "review_finished",
2766
+ stdinMode: "busy",
2767
+ clauseId: "SMR-002"
2768
+ }]
2769
+ };
2770
+ }
2283
2771
  function reduceApmGatedTurnEnd(_state, input = {}) {
2284
2772
  const shouldDeliverQueuedMessages = Boolean(
2285
2773
  input.inboxLength && input.inboxLength > 0 && input.supportsStdinNotification && input.hasSession
@@ -2291,6 +2779,7 @@ function reduceApmGatedTurnEnd(_state, input = {}) {
2291
2779
  phase: "idle",
2292
2780
  outstandingToolUses: 0,
2293
2781
  compacting: false,
2782
+ ..._state.reviewing !== void 0 ? { reviewing: false } : {},
2294
2783
  toolBoundaryFlushDisabled: _state.toolBoundaryFlushDisabled,
2295
2784
  lastFlushReason: _state.lastFlushReason,
2296
2785
  recentEvents: _state.recentEvents
@@ -2312,6 +2801,7 @@ function reduceApmGatedError(state, input = {}) {
2312
2801
  phase: "error",
2313
2802
  outstandingToolUses: state.outstandingToolUses,
2314
2803
  compacting: false,
2804
+ ...state.reviewing !== void 0 ? { reviewing: false } : {},
2315
2805
  toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled || shouldDisableToolBoundaryFlush,
2316
2806
  lastFlushReason: state.lastFlushReason,
2317
2807
  recentEvents: state.recentEvents
@@ -2327,6 +2817,7 @@ function reduceApmGatedAssistantContinuation(state) {
2327
2817
  phase: "assistant_continuation",
2328
2818
  outstandingToolUses: state.outstandingToolUses,
2329
2819
  compacting: state.compacting,
2820
+ ...reviewStatePatch(state),
2330
2821
  toolBoundaryFlushDisabled: state.toolBoundaryFlushDisabled,
2331
2822
  lastFlushReason: state.lastFlushReason,
2332
2823
  recentEvents: state.recentEvents
@@ -2377,6 +2868,17 @@ function reduceApmStartupTimeoutTermination(state, input) {
2377
2868
  blockedReason: null
2378
2869
  };
2379
2870
  }
2871
+ function reduceApmStartupRequestErrorTermination(state) {
2872
+ return {
2873
+ nextState: {
2874
+ ...state,
2875
+ isIdle: false,
2876
+ expectedTerminationReason: "startup_request_error",
2877
+ phase: "error"
2878
+ },
2879
+ shouldTerminate: true
2880
+ };
2881
+ }
2380
2882
  function reduceApmGatedFlush(state, input) {
2381
2883
  return {
2382
2884
  nextState: {
@@ -2386,7 +2888,8 @@ function reduceApmGatedFlush(state, input) {
2386
2888
  };
2387
2889
  }
2388
2890
  function reduceApmGatedRecentEvent(state, input) {
2389
- const summary = `${input.event}:${state.phase}:tools=${state.outstandingToolUses}:compact=${state.compacting}`;
2891
+ const reviewSuffix = state.reviewing !== void 0 ? `:review=${state.reviewing === true}` : "";
2892
+ const summary = `${input.event}:${state.phase}:tools=${state.outstandingToolUses}:compact=${state.compacting}${reviewSuffix}`;
2390
2893
  return {
2391
2894
  nextState: {
2392
2895
  ...state,
@@ -2394,6 +2897,154 @@ function reduceApmGatedRecentEvent(state, input) {
2394
2897
  }
2395
2898
  };
2396
2899
  }
2900
+ function projectApmRuntimeTerminationTrace(input) {
2901
+ if (input.reason === "startup_timeout") {
2902
+ const attrs = {
2903
+ turn_outcome: "failed",
2904
+ turn_subtype: "runtime_stalled",
2905
+ turn_reason: "no_runtime_events",
2906
+ runtime_start_failure_kind: "runtime_start_timeout",
2907
+ timeout_ms: input.timeoutMs
2908
+ };
2909
+ return {
2910
+ reason: input.reason,
2911
+ runtimeEventName: "runtime.start.timeout",
2912
+ runtimeEventAttrs: attrs,
2913
+ runtimeSpanAttrs: attrs,
2914
+ processExitAttrs: {
2915
+ stop_source: "startup_timeout",
2916
+ expectedTerminationReason: "startup_timeout",
2917
+ timeout_ms: input.timeoutMs
2918
+ },
2919
+ runtimeStopReason: "startup_timeout"
2920
+ };
2921
+ }
2922
+ if (input.reason === "turn_end") {
2923
+ return {
2924
+ reason: input.reason,
2925
+ processExitAttrs: {
2926
+ stop_source: "turn_end",
2927
+ expectedTerminationReason: "turn_end"
2928
+ },
2929
+ runtimeStopReason: "turn_end"
2930
+ };
2931
+ }
2932
+ const eventAttrs = {
2933
+ turn_outcome: "failed",
2934
+ turn_subtype: "runtime_stalled",
2935
+ turn_reason: input.turnReason,
2936
+ pendingMessages: input.pendingMessages,
2937
+ recovery: input.recoveryAction
2938
+ };
2939
+ return {
2940
+ reason: input.reason,
2941
+ runtimeEventName: "runtime.progress.stalled",
2942
+ runtimeEventAttrs: eventAttrs,
2943
+ runtimeSpanAttrs: {
2944
+ ...eventAttrs,
2945
+ ageMs: input.staleForMs,
2946
+ lastActivity: input.lastActivity,
2947
+ lastActivityDetailPresent: input.lastActivityDetailPresent,
2948
+ lastActivityDetailKind: input.lastActivityDetailKind
2949
+ },
2950
+ processExitAttrs: {
2951
+ stop_source: "stalled_recovery",
2952
+ expectedTerminationReason: "stalled_recovery",
2953
+ queued_messages_count: input.pendingMessages
2954
+ },
2955
+ runtimeStopReason: "stalled_recovery"
2956
+ };
2957
+ }
2958
+ function projectApmRuntimeProgressStalledTrace(input) {
2959
+ const eventAttrs = {
2960
+ turn_outcome: "failed",
2961
+ turn_subtype: "runtime_stalled",
2962
+ turn_reason: input.turnReason
2963
+ };
2964
+ return {
2965
+ runtimeEventName: "runtime.progress.stalled",
2966
+ runtimeEventAttrs: eventAttrs,
2967
+ runtimeSpanAttrs: {
2968
+ ...eventAttrs,
2969
+ ageMs: input.staleForMs,
2970
+ lastActivity: input.lastActivity,
2971
+ lastActivityDetailPresent: input.lastActivityDetailPresent,
2972
+ lastActivityDetailKind: input.lastActivityDetailKind
2973
+ }
2974
+ };
2975
+ }
2976
+ function projectApmRuntimeStallDiagnostic(input) {
2977
+ const context = [];
2978
+ const lastActivityDetailKind = classifyApmRuntimeStallActivityDetail(input.lastActivityDetail);
2979
+ if (input.lastActivityDetail) {
2980
+ context.push(`after ${input.lastActivityDetail}`);
2981
+ }
2982
+ if (input.runtimeDescriptorBusyDelivery === "gated") {
2983
+ context.push(`phase=${input.gatedPhase}`);
2984
+ }
2985
+ if (input.outstandingToolUses > 0) {
2986
+ context.push(`tools=${input.outstandingToolUses}`);
2987
+ }
2988
+ if (input.compacting) {
2989
+ context.push("compacting");
2990
+ }
2991
+ if (input.reviewing) {
2992
+ context.push("reviewing");
2993
+ }
2994
+ if (input.inboxCount > 0) {
2995
+ context.push(`queued=${input.inboxCount}`);
2996
+ }
2997
+ const detail = [
2998
+ `Runtime stalled: no runtime events for ${input.staleForMinutes}m`,
2999
+ context.length > 0 ? ` (${context.join(", ")})` : ""
3000
+ ].join("");
3001
+ const turnReason = input.runtimeProgressLastEventKind === "tool_output" && input.outstandingToolUses === 0 ? "harness_post_tool_silent_wedge" : "no_runtime_events";
3002
+ return {
3003
+ detail,
3004
+ turnReason,
3005
+ lastActivityDetailPresent: Boolean(input.lastActivityDetail),
3006
+ lastActivityDetailKind,
3007
+ traceAttrs: {
3008
+ ageMs: input.staleForMs,
3009
+ staleForMinutes: input.staleForMinutes,
3010
+ lastActivity: input.lastActivity,
3011
+ lastActivityDetailPresent: Boolean(input.lastActivityDetail),
3012
+ lastActivityDetailKind,
3013
+ runtime: input.runtime,
3014
+ model: input.model,
3015
+ platform: input.platform,
3016
+ arch: input.arch,
3017
+ launchId: input.launchId || void 0,
3018
+ sessionIdPresent: input.sessionIdPresent,
3019
+ inboxCount: input.inboxCount,
3020
+ pendingNotificationCount: input.pendingNotificationCount,
3021
+ processPidPresent: input.processPidPresent,
3022
+ busyDeliveryMode: input.driverBusyDeliveryMode,
3023
+ supportsStdinNotification: input.supportsStdinNotification,
3024
+ gatedPhase: input.runtimeDescriptorBusyDelivery === "gated" ? input.gatedPhase : void 0,
3025
+ outstandingToolUses: input.outstandingToolUses,
3026
+ compacting: input.compacting,
3027
+ reviewing: input.reviewing === true ? true : void 0,
3028
+ recentStderrCount: input.recentStderrCount,
3029
+ recentStdoutCount: input.recentStdoutCount,
3030
+ ...input.runtimeTraceCounterAttrs ?? {}
3031
+ }
3032
+ };
3033
+ }
3034
+ function classifyApmRuntimeStallActivityDetail(detail) {
3035
+ if (!detail) return void 0;
3036
+ if (detail === "Message received") return "message_received";
3037
+ if (detail === "Starting\u2026") return "starting";
3038
+ if (detail === "Running command\u2026") return "running_command";
3039
+ if (detail === "Checking messages\u2026") return "checking_messages";
3040
+ if (detail === "Compacting context") return "compacting_context";
3041
+ if (detail === "Context compaction finished") return "compaction_finished";
3042
+ if (detail === "Context compaction still running; no finish event observed") return "compaction_stale";
3043
+ if (detail === "Idle" || detail === "Process idle") return "idle";
3044
+ if (detail.startsWith("Restarting stalled ") && detail.endsWith(" runtime for queued message")) return "stalled_recovery";
3045
+ if (detail.startsWith("Runtime stalled: no runtime events for ")) return "runtime_stalled";
3046
+ return "other";
3047
+ }
2397
3048
  function reduceApmGatedFlushReadiness(state, input) {
2398
3049
  if (!input.isGated) return { shouldNotify: false, blockedReason: "non_gated", effects: [] };
2399
3050
  if (!input.hasSession) return { shouldNotify: false, blockedReason: "missing_session", effects: [] };
@@ -2402,6 +3053,7 @@ function reduceApmGatedFlushReadiness(state, input) {
2402
3053
  return { shouldNotify: false, blockedReason: "tool_boundary_flush_disabled", effects: [] };
2403
3054
  }
2404
3055
  if (state.compacting) return { shouldNotify: false, blockedReason: "compacting", effects: [] };
3056
+ if (state.reviewing) return { shouldNotify: false, blockedReason: "reviewing", effects: [] };
2405
3057
  if (state.outstandingToolUses > 0) {
2406
3058
  return { shouldNotify: false, blockedReason: "outstanding_tool_uses", effects: [] };
2407
3059
  }
@@ -2894,162 +3546,8 @@ function stripUndefined(value) {
2894
3546
  for (const key of Object.keys(value)) {
2895
3547
  if (value[key] === void 0) delete value[key];
2896
3548
  }
2897
- return value;
2898
- }
2899
-
2900
- // src/proxy.ts
2901
- import { HttpsProxyAgent } from "https-proxy-agent";
2902
- import { ProxyAgent } from "undici";
2903
- var fetchDispatcherCache = /* @__PURE__ */ new Map();
2904
- function getFetchPreResponseTimeoutMs(env) {
2905
- const parsed = Number.parseInt(env.SLOCK_DAEMON_FETCH_PRE_RESPONSE_TIMEOUT_MS || "", 10);
2906
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 3e4;
2907
- }
2908
- function getDefaultPort(protocol) {
2909
- switch (protocol) {
2910
- case "https:":
2911
- case "wss:":
2912
- return "443";
2913
- case "http:":
2914
- case "ws:":
2915
- return "80";
2916
- default:
2917
- return "";
2918
- }
2919
- }
2920
- function hostMatchesNoProxyEntry(hostname, ruleHost) {
2921
- if (!ruleHost) return false;
2922
- const normalizedRule = ruleHost.replace(/^\*\./, ".").replace(/^\./, "").toLowerCase();
2923
- const normalizedHost = hostname.toLowerCase();
2924
- return normalizedHost === normalizedRule || normalizedHost.endsWith(`.${normalizedRule}`);
2925
- }
2926
- function getProxyUrlForTarget(targetUrl, env) {
2927
- const protocol = new URL(targetUrl).protocol;
2928
- switch (protocol) {
2929
- case "wss:":
2930
- return env.WSS_PROXY || env.wss_proxy || env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
2931
- case "ws:":
2932
- return env.WS_PROXY || env.ws_proxy || env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
2933
- case "https:":
2934
- return env.HTTPS_PROXY || env.https_proxy || env.ALL_PROXY || env.all_proxy;
2935
- case "http:":
2936
- return env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy;
2937
- default:
2938
- return env.ALL_PROXY || env.all_proxy;
2939
- }
2940
- }
2941
- function shouldBypassProxy(targetUrl, env) {
2942
- const rawNoProxy = env.NO_PROXY || env.no_proxy;
2943
- if (!rawNoProxy) return false;
2944
- const url = new URL(targetUrl);
2945
- const hostname = url.hostname.toLowerCase();
2946
- const port = url.port || getDefaultPort(url.protocol);
2947
- return rawNoProxy.split(",").map((entry) => entry.trim()).filter(Boolean).some((entry) => {
2948
- if (entry === "*") return true;
2949
- const [ruleHost, rulePort] = entry.split(":", 2);
2950
- if (rulePort && rulePort !== port) return false;
2951
- return hostMatchesNoProxyEntry(hostname, ruleHost);
2952
- });
2953
- }
2954
- function buildWebSocketOptions(wsUrl, env) {
2955
- const proxyUrl = getProxyUrlForTarget(wsUrl, env);
2956
- if (!proxyUrl) return void 0;
2957
- if (shouldBypassProxy(wsUrl, env)) return void 0;
2958
- return {
2959
- agent: new HttpsProxyAgent(proxyUrl)
2960
- };
2961
- }
2962
- function resolveProxyUrl(targetUrl, env) {
2963
- const proxyUrl = getProxyUrlForTarget(targetUrl, env);
2964
- if (!proxyUrl) return void 0;
2965
- if (shouldBypassProxy(targetUrl, env)) return void 0;
2966
- return proxyUrl;
2967
- }
2968
- function buildFetchDispatcher(targetUrl, env) {
2969
- const proxyUrl = resolveProxyUrl(targetUrl, env);
2970
- if (!proxyUrl) return void 0;
2971
- const cached = fetchDispatcherCache.get(proxyUrl);
2972
- if (cached) return cached;
2973
- const timeoutMs = getFetchPreResponseTimeoutMs(env);
2974
- const dispatcher = new ProxyAgent({
2975
- uri: proxyUrl,
2976
- // All three are pre-response and body-agnostic (see getFetchPreResponseTimeoutMs):
2977
- // headersTimeout = headers-hang leg; requestTls.timeout = CONNECT-tunnel
2978
- // establish leg; connect.timeout = socket to the proxy itself (defensive).
2979
- connect: { timeout: timeoutMs },
2980
- requestTls: { timeout: timeoutMs },
2981
- headersTimeout: timeoutMs
2982
- });
2983
- fetchDispatcherCache.set(proxyUrl, dispatcher);
2984
- return dispatcher;
2985
- }
2986
- function evictFetchDispatcher(targetUrl, env) {
2987
- const proxyUrl = resolveProxyUrl(targetUrl, env);
2988
- if (!proxyUrl) return false;
2989
- const cached = fetchDispatcherCache.get(proxyUrl);
2990
- if (!cached) return false;
2991
- fetchDispatcherCache.delete(proxyUrl);
2992
- void Promise.resolve().then(() => cached.close()).catch(() => cached.destroy?.(new Error("evicted"))).catch(() => {
2993
- });
2994
- return true;
2995
- }
2996
-
2997
- // src/daemonFetch.ts
2998
- function withDaemonFetchProxy(input, init = {}, env = process.env) {
2999
- const dispatcher = buildFetchDispatcher(input.toString(), env);
3000
- return dispatcher ? { ...init, dispatcher } : init;
3001
- }
3002
- async function daemonFetch(input, init, env = process.env) {
3003
- try {
3004
- return await fetch(input, withDaemonFetchProxy(input, init, env));
3005
- } catch (err) {
3006
- evictFetchDispatcher(input.toString(), env);
3007
- throw err;
3008
- }
3009
- }
3010
-
3011
- // src/logger.ts
3012
- var listeners = /* @__PURE__ */ new Set();
3013
- function timestamp() {
3014
- const d = /* @__PURE__ */ new Date();
3015
- const pad = (n) => String(n).padStart(2, "0");
3016
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
3017
- }
3018
- function format(level, msg) {
3019
- return `${timestamp()} [${level}] ${msg}`;
3020
- }
3021
- function emit(event) {
3022
- for (const listener of listeners) {
3023
- listener(event);
3024
- }
3025
- }
3026
- function subscribeDaemonLogs(listener) {
3027
- listeners.add(listener);
3028
- return () => {
3029
- listeners.delete(listener);
3030
- };
3031
- }
3032
- var logger = {
3033
- info(msg) {
3034
- const line = format("INFO", msg);
3035
- console.log(line);
3036
- emit({ level: "INFO", line, message: msg });
3037
- },
3038
- warn(msg) {
3039
- const line = format("WARN", msg);
3040
- console.warn(line);
3041
- emit({ level: "WARN", line, message: msg });
3042
- },
3043
- error(msg, err) {
3044
- const line = format("ERROR", msg);
3045
- if (err) {
3046
- console.error(line, err);
3047
- } else {
3048
- console.error(line);
3049
- }
3050
- emit({ level: "ERROR", line, message: msg, error: err });
3051
- }
3052
- };
3549
+ return value;
3550
+ }
3053
3551
 
3054
3552
  // src/agentCredentialProxy.ts
3055
3553
  var registrations = /* @__PURE__ */ new Map();
@@ -3869,7 +4367,8 @@ async function prepareCliTransport(ctx, extraEnv = {}, platform = process.platfo
3869
4367
  }
3870
4368
  const posixWrapper = path2.join(slockDir, "slock");
3871
4369
  const posixRaftWrapper = path2.join(slockDir, "raft");
3872
- const posixCredentialPrefix = agentCredentialProxy ? `SLOCK_AGENT_PROXY_URL=${shellSingleQuote(agentCredentialProxy.proxyUrl)} SLOCK_AGENT_PROXY_TOKEN_FILE=${shellSingleQuote(agentCredentialProxyTokenFile)} SLOCK_AGENT_ACTIVE_CAPABILITIES=${shellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)} ` : "";
4370
+ const posixIdentityPrefix = `SLOCK_AGENT_ID=${shellSingleQuote(ctx.agentId)} SLOCK_SERVER_URL=${shellSingleQuote(ctx.config.serverUrl)} `;
4371
+ const posixCredentialPrefix = posixIdentityPrefix + (agentCredentialProxy ? `SLOCK_AGENT_PROXY_URL=${shellSingleQuote(agentCredentialProxy.proxyUrl)} SLOCK_AGENT_PROXY_TOKEN_FILE=${shellSingleQuote(agentCredentialProxyTokenFile)} SLOCK_AGENT_ACTIVE_CAPABILITIES=${shellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)} ` : `SLOCK_AGENT_TOKEN_FILE=${shellSingleQuote(tokenFile)} `);
3873
4372
  const posixCliFallbackBlock = cliPath === "__cli" || cliFallbackCandidates.length === 0 ? "" : `if [ ! -e "$SLOCK_CLI" ]; then
3874
4373
  ${cliFallbackCandidates.map((candidate, i) => ` ${i === 0 ? "if" : "elif"} [ -e ${shellSingleQuote(candidate)} ]; then SLOCK_CLI=${shellSingleQuote(candidate)};`).join("\n")}
3875
4374
  fi
@@ -3885,10 +4384,14 @@ ${posixCliFallbackBlock}${posixCredentialPrefix}exec ${shellSingleQuote(process.
3885
4384
  if (platform === "win32") {
3886
4385
  const cmdWrapper = path2.join(slockDir, "slock.cmd");
3887
4386
  const cmdRaftWrapper = path2.join(slockDir, "raft.cmd");
3888
- const cmdCredentialLine = agentCredentialProxy ? `set "SLOCK_AGENT_PROXY_URL=${agentCredentialProxy.proxyUrl}"\r
4387
+ const cmdIdentityLines = `set "SLOCK_AGENT_ID=${ctx.agentId}"\r
4388
+ set "SLOCK_SERVER_URL=${ctx.config.serverUrl}"\r
4389
+ `;
4390
+ const cmdCredentialLine = cmdIdentityLines + (agentCredentialProxy ? `set "SLOCK_AGENT_PROXY_URL=${agentCredentialProxy.proxyUrl}"\r
3889
4391
  set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
3890
4392
  set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
3891
- ` : "";
4393
+ ` : `set "SLOCK_AGENT_TOKEN_FILE=${tokenFile}"\r
4394
+ `);
3892
4395
  const cmdCliFallbackLines = cliPath === "__cli" ? [] : cliFallbackCandidates.map((candidate) => `if not exist "%SLOCK_CLI%" set "SLOCK_CLI=${candidate}"`);
3893
4396
  const cmdBody = [
3894
4397
  "@echo off",
@@ -3908,11 +4411,20 @@ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
3908
4411
  writeFileSync(cmdRaftWrapper, cmdBody);
3909
4412
  const psWrapper = path2.join(slockDir, "slock.ps1");
3910
4413
  const psRaftWrapper = path2.join(slockDir, "raft.ps1");
3911
- const psCredentialLines = agentCredentialProxy ? [
3912
- `$env:SLOCK_AGENT_PROXY_URL=${powershellSingleQuote(agentCredentialProxy.proxyUrl)}`,
3913
- `$env:SLOCK_AGENT_PROXY_TOKEN_FILE=${powershellSingleQuote(agentCredentialProxyTokenFile)}`,
3914
- `$env:SLOCK_AGENT_ACTIVE_CAPABILITIES=${powershellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)}`
3915
- ] : [];
4414
+ const psIdentityLines = [
4415
+ `$env:SLOCK_AGENT_ID=${powershellSingleQuote(ctx.agentId)}`,
4416
+ `$env:SLOCK_SERVER_URL=${powershellSingleQuote(ctx.config.serverUrl)}`
4417
+ ];
4418
+ const psCredentialLines = [
4419
+ ...psIdentityLines,
4420
+ ...agentCredentialProxy ? [
4421
+ `$env:SLOCK_AGENT_PROXY_URL=${powershellSingleQuote(agentCredentialProxy.proxyUrl)}`,
4422
+ `$env:SLOCK_AGENT_PROXY_TOKEN_FILE=${powershellSingleQuote(agentCredentialProxyTokenFile)}`,
4423
+ `$env:SLOCK_AGENT_ACTIVE_CAPABILITIES=${powershellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)}`
4424
+ ] : [
4425
+ `$env:SLOCK_AGENT_TOKEN_FILE=${powershellSingleQuote(tokenFile)}`
4426
+ ]
4427
+ ];
3916
4428
  const psBody = [
3917
4429
  "$ErrorActionPreference = 'Stop'",
3918
4430
  "$utf8NoBom = [System.Text.UTF8Encoding]::new($false)",
@@ -4663,7 +5175,7 @@ var ClaudeDriver = class {
4663
5175
  };
4664
5176
 
4665
5177
  // src/drivers/codex.ts
4666
- import { spawn as spawn2, execFileSync as execFileSync2, execSync } from "child_process";
5178
+ import { spawn as spawn2, execFileSync as execFileSync2 } from "child_process";
4667
5179
  import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
4668
5180
  import os3 from "os";
4669
5181
  import path6 from "path";
@@ -4671,6 +5183,7 @@ import path6 from "path";
4671
5183
  // src/runtimeTurnState.ts
4672
5184
  var RuntimeTurnState = class {
4673
5185
  currentTurnId = null;
5186
+ pendingTurnId = null;
4674
5187
  /**
4675
5188
  * Post-tool window where the app-server may not yet accept stdin steering.
4676
5189
  * Gate busy-mode delivery until turn/completed or next progress.
@@ -4678,22 +5191,25 @@ var RuntimeTurnState = class {
4678
5191
  steeringGateActive = false;
4679
5192
  reset() {
4680
5193
  this.currentTurnId = null;
5194
+ this.pendingTurnId = null;
4681
5195
  this.steeringGateActive = false;
4682
5196
  }
4683
5197
  get activeTurnId() {
4684
5198
  return this.currentTurnId;
4685
5199
  }
4686
5200
  get canSteerBusy() {
4687
- return Boolean(this.currentTurnId && !this.steeringGateActive);
5201
+ return Boolean(this.currentTurnId && !this.pendingTurnId && !this.steeringGateActive);
4688
5202
  }
4689
5203
  markTurnStarted(turnId) {
4690
- if (turnId !== void 0 && turnId !== null) {
4691
- this.currentTurnId = turnId;
5204
+ const startedTurnId = turnId ?? this.pendingTurnId;
5205
+ if (startedTurnId) {
5206
+ this.currentTurnId = startedTurnId;
4692
5207
  }
5208
+ this.pendingTurnId = null;
4693
5209
  this.steeringGateActive = false;
4694
5210
  }
4695
- adoptTurnId(turnId) {
4696
- this.currentTurnId = turnId;
5211
+ noteTurnAccepted(turnId) {
5212
+ this.pendingTurnId = turnId;
4697
5213
  }
4698
5214
  markProgress() {
4699
5215
  this.steeringGateActive = false;
@@ -4703,6 +5219,7 @@ var RuntimeTurnState = class {
4703
5219
  }
4704
5220
  markTurnCompleted() {
4705
5221
  this.currentTurnId = null;
5222
+ this.pendingTurnId = null;
4706
5223
  this.steeringGateActive = false;
4707
5224
  }
4708
5225
  };
@@ -4797,27 +5314,79 @@ function getCodexNotificationErrorMessage(params) {
4797
5314
  }
4798
5315
  return null;
4799
5316
  }
4800
- function joinReasoningText(item) {
5317
+ function payloadBytes(value) {
5318
+ try {
5319
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
5320
+ } catch {
5321
+ return void 0;
5322
+ }
5323
+ }
5324
+ function codexNotificationProgressEvent(itemType, payload) {
5325
+ return {
5326
+ kind: "internal_progress",
5327
+ source: "codex_app_server_notification",
5328
+ itemType,
5329
+ payloadBytes: payload === void 0 ? void 0 : payloadBytes(payload)
5330
+ };
5331
+ }
5332
+ function boundedString(value, limit = 1e3) {
5333
+ if (typeof value !== "string") return void 0;
5334
+ const trimmed = value.trim();
5335
+ if (!trimmed) return void 0;
5336
+ if (trimmed.length <= limit) return trimmed;
5337
+ return `${trimmed.slice(0, limit - 1)}\u2026`;
5338
+ }
5339
+ function codexNotificationDiagnosticEvent(message) {
5340
+ const params = message.params ?? {};
5341
+ let diagnosticMessage;
5342
+ let details;
5343
+ switch (message.method) {
5344
+ case "configWarning":
5345
+ diagnosticMessage = boundedString(params.summary) ?? boundedString(params.details) ?? "Codex configuration warning";
5346
+ details = boundedString(params.details);
5347
+ break;
5348
+ case "warning":
5349
+ diagnosticMessage = boundedString(params.message) ?? "Codex warning";
5350
+ break;
5351
+ case "guardianWarning":
5352
+ diagnosticMessage = boundedString(params.message) ?? "Codex guardian warning";
5353
+ break;
5354
+ case "deprecationNotice":
5355
+ diagnosticMessage = boundedString(params.summary) ?? boundedString(params.details) ?? "Codex deprecation notice";
5356
+ details = boundedString(params.details);
5357
+ break;
5358
+ default:
5359
+ return null;
5360
+ }
5361
+ const sessionId = codexMessageThreadId(message);
5362
+ const path18 = boundedString(params.path);
5363
+ return {
5364
+ kind: "runtime_diagnostic",
5365
+ severity: "warning",
5366
+ source: "codex_app_server_notification",
5367
+ itemType: message.method,
5368
+ message: diagnosticMessage,
5369
+ ...details ? { details } : {},
5370
+ ...path18 ? { path: path18 } : {},
5371
+ ...params.range !== void 0 ? { range: params.range } : {},
5372
+ payloadBytes: payloadBytes(params),
5373
+ ...sessionId ? { sessionId } : {}
5374
+ };
5375
+ }
5376
+ function joinReasoningSummaryText(item) {
4801
5377
  const summary = Array.isArray(item.summary) ? item.summary.filter((entry) => typeof entry === "string") : [];
4802
- const content = Array.isArray(item.content) ? item.content.filter((entry) => typeof entry === "string") : [];
4803
- return [...summary, ...content].join("\n").trim();
5378
+ return summary.join("\n").trim();
4804
5379
  }
4805
5380
  function rawResponseItemProgressEvent(message) {
4806
5381
  if (message.method !== "rawResponseItem/completed") return null;
4807
5382
  const item = message.params?.item ?? message.params?.responseItem ?? message.params?.rawItem ?? message.params;
4808
5383
  if (!item || typeof item !== "object") return null;
4809
5384
  const itemType = typeof item.type === "string" ? item.type : void 0;
4810
- let payloadBytes;
4811
- try {
4812
- payloadBytes = Buffer.byteLength(JSON.stringify(item), "utf8");
4813
- } catch {
4814
- payloadBytes = void 0;
4815
- }
4816
5385
  return {
4817
5386
  kind: "internal_progress",
4818
5387
  source: "codex_raw_response_item",
4819
5388
  itemType,
4820
- payloadBytes
5389
+ payloadBytes: payloadBytes(item)
4821
5390
  };
4822
5391
  }
4823
5392
  function nonEmptyString2(value) {
@@ -4828,11 +5397,15 @@ function codexMcpToolName(item) {
4828
5397
  const server = nonEmptyString2(item.server);
4829
5398
  return server ? `mcp_${server}_${tool}` : `mcp_${tool}`;
4830
5399
  }
5400
+ function codexMessageThreadId(message) {
5401
+ return nonEmptyString2(message.params?.threadId) ?? nonEmptyString2(message.params?.thread?.id) ?? nonEmptyString2(message.params?.sessionId);
5402
+ }
4831
5403
  var CodexEventNormalizer = class {
4832
5404
  currentThreadId = null;
4833
5405
  sessionAnnounced = false;
4834
5406
  streamedAgentMessageIds = /* @__PURE__ */ new Set();
4835
5407
  streamedReasoningIds = /* @__PURE__ */ new Set();
5408
+ fileChangeToolCallCounts = /* @__PURE__ */ new Map();
4836
5409
  turnState = new RuntimeTurnState();
4837
5410
  reset(opts = {}) {
4838
5411
  this.currentThreadId = opts.threadId ?? null;
@@ -4840,6 +5413,7 @@ var CodexEventNormalizer = class {
4840
5413
  this.sessionAnnounced = false;
4841
5414
  this.streamedAgentMessageIds.clear();
4842
5415
  this.streamedReasoningIds.clear();
5416
+ this.fileChangeToolCallCounts.clear();
4843
5417
  }
4844
5418
  get threadId() {
4845
5419
  return this.currentThreadId;
@@ -4862,11 +5436,11 @@ var CodexEventNormalizer = class {
4862
5436
  }
4863
5437
  const turn = message.result.turn;
4864
5438
  if (turn && typeof turn.id === "string") {
4865
- this.turnState.adoptTurnId(turn.id);
5439
+ this.turnState.noteTurnAccepted(turn.id);
4866
5440
  return { events };
4867
5441
  }
4868
5442
  if (typeof message.result.turnId === "string") {
4869
- this.turnState.adoptTurnId(message.result.turnId);
5443
+ this.turnState.noteTurnAccepted(message.result.turnId);
4870
5444
  return { events };
4871
5445
  }
4872
5446
  }
@@ -4874,9 +5448,12 @@ var CodexEventNormalizer = class {
4874
5448
  events.push({ kind: "error", message: message.error.message || "Codex app-server request failed" });
4875
5449
  return { events };
4876
5450
  }
5451
+ if (this.isSecondaryThreadId(codexMessageThreadId(message))) {
5452
+ return { events };
5453
+ }
4877
5454
  const telemetry = parseCodexTelemetryEvent(message);
4878
5455
  if (telemetry) {
4879
- const telemetrySessionId = nonEmptyString2(message.params?.threadId) ?? nonEmptyString2(message.params?.thread?.id) ?? nonEmptyString2(message.params?.sessionId);
5456
+ const telemetrySessionId = codexMessageThreadId(message);
4880
5457
  const telemetryTurnId = nonEmptyString2(message.params?.turnId) ?? nonEmptyString2(message.params?.turn?.id);
4881
5458
  if (telemetrySessionId) {
4882
5459
  this.currentThreadId = telemetrySessionId;
@@ -4907,7 +5484,7 @@ var CodexEventNormalizer = class {
4907
5484
  const turnId = message.params?.turn?.id;
4908
5485
  this.turnState.markTurnStarted(typeof turnId === "string" ? turnId : null);
4909
5486
  events.push({ kind: "thinking", text: "" });
4910
- break;
5487
+ return { events, turnStarted: true };
4911
5488
  }
4912
5489
  case "item/agentMessage/delta": {
4913
5490
  const delta = message.params?.delta;
@@ -4921,8 +5498,7 @@ var CodexEventNormalizer = class {
4921
5498
  }
4922
5499
  break;
4923
5500
  }
4924
- case "item/reasoning/summaryTextDelta":
4925
- case "item/reasoning/textDelta": {
5501
+ case "item/reasoning/summaryTextDelta": {
4926
5502
  const delta = message.params?.delta;
4927
5503
  const itemId = message.params?.itemId;
4928
5504
  if (typeof itemId === "string") {
@@ -4934,6 +5510,41 @@ var CodexEventNormalizer = class {
4934
5510
  }
4935
5511
  break;
4936
5512
  }
5513
+ case "item/reasoning/textDelta": {
5514
+ const delta = message.params?.delta;
5515
+ if (typeof delta === "string" && delta.length > 0) {
5516
+ this.turnState.markProgress();
5517
+ events.push(codexNotificationProgressEvent("reasoning_text_delta", {
5518
+ itemId: message.params?.itemId,
5519
+ bytes: Buffer.byteLength(delta, "utf8")
5520
+ }));
5521
+ }
5522
+ break;
5523
+ }
5524
+ case "item/commandExecution/outputDelta":
5525
+ case "item/mcpToolCall/progress":
5526
+ case "item/plan/delta":
5527
+ case "turn/plan/updated":
5528
+ case "turn/diff/updated":
5529
+ case "item/fileChange/patchUpdated":
5530
+ case "item/fileChange/outputDelta":
5531
+ case "command/exec/outputDelta":
5532
+ case "process/outputDelta":
5533
+ case "process/exited": {
5534
+ this.turnState.markProgress();
5535
+ events.push(codexNotificationProgressEvent(message.method, message.params));
5536
+ break;
5537
+ }
5538
+ case "configWarning":
5539
+ case "warning":
5540
+ case "guardianWarning":
5541
+ case "deprecationNotice": {
5542
+ const diagnostic = codexNotificationDiagnosticEvent(message);
5543
+ if (diagnostic) {
5544
+ events.push(diagnostic);
5545
+ }
5546
+ break;
5547
+ }
4937
5548
  case "item/started":
4938
5549
  case "item/completed": {
4939
5550
  const item = message.params?.item;
@@ -4944,7 +5555,7 @@ var CodexEventNormalizer = class {
4944
5555
  switch (itemType) {
4945
5556
  case "reasoning":
4946
5557
  if (isCompleted && typeof item.id === "string" && !this.streamedReasoningIds.has(item.id)) {
4947
- const text = joinReasoningText(item);
5558
+ const text = joinReasoningSummaryText(item);
4948
5559
  if (text) {
4949
5560
  this.turnState.markProgress();
4950
5561
  events.push({ kind: "thinking", text });
@@ -4980,14 +5591,45 @@ var CodexEventNormalizer = class {
4980
5591
  events.push({ kind: "compaction_finished" });
4981
5592
  }
4982
5593
  break;
5594
+ case "enteredReviewMode":
5595
+ if (isStarted) {
5596
+ events.push({ kind: "review_started" });
5597
+ }
5598
+ break;
5599
+ case "exitedReviewMode":
5600
+ if (isCompleted) {
5601
+ events.push({ kind: "review_finished" });
5602
+ }
5603
+ break;
4983
5604
  case "fileChange":
4984
5605
  if (isStarted && Array.isArray(item.changes)) {
5606
+ let outputCount = 0;
4985
5607
  for (const change of item.changes) {
4986
5608
  events.push({
4987
5609
  kind: "tool_call",
4988
5610
  name: "file_change",
4989
5611
  input: { path: change?.path, kind: change?.kind }
4990
5612
  });
5613
+ outputCount += 1;
5614
+ }
5615
+ if (outputCount > 0 && typeof item.id === "string") {
5616
+ this.fileChangeToolCallCounts.set(item.id, outputCount);
5617
+ }
5618
+ }
5619
+ if (isCompleted) {
5620
+ let outputCount = 0;
5621
+ if (typeof item.id === "string") {
5622
+ outputCount = this.fileChangeToolCallCounts.get(item.id) ?? 0;
5623
+ this.fileChangeToolCallCounts.delete(item.id);
5624
+ }
5625
+ if (outputCount === 0 && Array.isArray(item.changes)) {
5626
+ outputCount = item.changes.length;
5627
+ }
5628
+ for (let index = 0; index < outputCount; index += 1) {
5629
+ events.push({ kind: "tool_output", name: "file_change" });
5630
+ }
5631
+ if (outputCount > 0) {
5632
+ this.turnState.markToolBoundary();
4991
5633
  }
4992
5634
  }
4993
5635
  break;
@@ -5007,6 +5649,7 @@ var CodexEventNormalizer = class {
5007
5649
  events.push({ kind: "tool_call", name: "collab_tool_call", input: { tool: item.tool, prompt: item.prompt } });
5008
5650
  }
5009
5651
  if (isCompleted) {
5652
+ events.push({ kind: "tool_output", name: "collab_tool_call" });
5010
5653
  this.turnState.markToolBoundary();
5011
5654
  }
5012
5655
  break;
@@ -5015,6 +5658,7 @@ var CodexEventNormalizer = class {
5015
5658
  events.push({ kind: "tool_call", name: "web_search", input: { query: item.query } });
5016
5659
  }
5017
5660
  if (isCompleted) {
5661
+ events.push({ kind: "tool_output", name: "web_search" });
5018
5662
  this.turnState.markToolBoundary();
5019
5663
  }
5020
5664
  break;
@@ -5023,24 +5667,37 @@ var CodexEventNormalizer = class {
5023
5667
  }
5024
5668
  case "turn/completed": {
5025
5669
  const turn = message.params?.turn;
5026
- if (turn?.status === "failed" && turn?.error?.message) {
5027
- events.push({ kind: "error", message: turn.error.message });
5670
+ if (turn?.status === "failed") {
5671
+ events.push({ kind: "error", message: turn.error?.message || "Codex turn failed" });
5672
+ }
5673
+ if (turn?.status === "interrupted") {
5674
+ const detail = typeof turn?.error?.message === "string" && turn.error.message.length > 0 ? `: ${turn.error.message}` : "";
5675
+ events.push({ kind: "error", message: `Codex turn interrupted${detail}` });
5028
5676
  }
5029
5677
  this.turnState.markTurnCompleted();
5030
5678
  this.streamedAgentMessageIds.clear();
5031
5679
  this.streamedReasoningIds.clear();
5680
+ this.fileChangeToolCallCounts.clear();
5032
5681
  events.push({ kind: "turn_end", sessionId: this.currentThreadId || void 0 });
5033
5682
  break;
5034
5683
  }
5035
5684
  case "error":
5036
- events.push({
5037
- kind: "error",
5038
- message: getCodexNotificationErrorMessage(message.params) || "Unknown Codex app-server error"
5039
- });
5685
+ if (message.params?.willRetry === true) {
5686
+ this.turnState.markProgress();
5687
+ events.push(codexNotificationProgressEvent("retryable_error", message.params));
5688
+ } else {
5689
+ events.push({
5690
+ kind: "error",
5691
+ message: getCodexNotificationErrorMessage(message.params) || "Unknown Codex app-server error"
5692
+ });
5693
+ }
5040
5694
  break;
5041
5695
  }
5042
5696
  return { events };
5043
5697
  }
5698
+ isSecondaryThreadId(threadId) {
5699
+ return Boolean(threadId && this.currentThreadId && threadId !== this.currentThreadId);
5700
+ }
5044
5701
  handleThreadReady(threadId, events) {
5045
5702
  this.currentThreadId = threadId;
5046
5703
  if (!this.sessionAnnounced) {
@@ -5052,20 +5709,6 @@ var CodexEventNormalizer = class {
5052
5709
  };
5053
5710
 
5054
5711
  // src/drivers/codex.ts
5055
- function ensureGitRepoForCodex(workingDirectory, deps = {}) {
5056
- const existsSyncFn = deps.existsSyncFn ?? existsSync5;
5057
- const execSyncFn = deps.execSyncFn ?? execSync;
5058
- const gitDir = path6.join(workingDirectory, ".git");
5059
- if (existsSyncFn(gitDir)) return;
5060
- execSyncFn("git init", { cwd: workingDirectory, stdio: "pipe" });
5061
- execSyncFn(
5062
- "git -c user.name=slock -c user.email=slock@local -c commit.gpgsign=false add -A && git -c user.name=slock -c user.email=slock@local -c commit.gpgsign=false commit --allow-empty -m 'init'",
5063
- {
5064
- cwd: workingDirectory,
5065
- stdio: "pipe"
5066
- }
5067
- );
5068
- }
5069
5712
  var CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
5070
5713
  function isWindowsSandboxRunner(commandPath) {
5071
5714
  return path6.basename(commandPath).toLowerCase().startsWith("codex-command-runner");
@@ -5144,27 +5787,68 @@ function probeCodex(deps = {}) {
5144
5787
  }
5145
5788
  function resolveCodexSpawn(commandArgs, deps = {}) {
5146
5789
  if ((deps.platform ?? process.platform) !== "win32") {
5147
- return { command: resolveCodexCommand(deps) ?? "codex", args: commandArgs };
5790
+ return { command: resolveCodexCommand(deps) ?? "codex", args: commandArgs, shell: false };
5148
5791
  }
5149
5792
  const codexEntry = resolveWindowsNpmCodexEntry(deps);
5150
5793
  if (codexEntry) {
5151
5794
  return {
5152
5795
  command: process.execPath,
5153
- args: [codexEntry, ...commandArgs]
5796
+ args: [codexEntry, ...commandArgs],
5797
+ shell: false
5154
5798
  };
5155
5799
  }
5156
5800
  const command = resolveCommandOnPath("codex", deps);
5157
5801
  if (command && !isWindowsSandboxRunner(command)) {
5158
- return { command, args: commandArgs };
5802
+ return { command, args: commandArgs, shell: requiresWindowsShell(command, deps.platform) };
5159
5803
  }
5160
5804
  const desktopEntry = resolveWindowsCodexDesktopEntry(deps);
5161
5805
  if (desktopEntry) {
5162
- return { command: desktopEntry, args: commandArgs };
5806
+ return { command: desktopEntry, args: commandArgs, shell: false };
5163
5807
  }
5164
5808
  throw new Error(
5165
5809
  "Cannot resolve Codex CLI entry point on Windows. Install Codex Desktop or install @openai/codex globally via npm (npm i -g @openai/codex). Ignoring .codex/.sandbox-bin/codex-command-runner because it is a sandbox helper, not the Codex CLI."
5166
5810
  );
5167
5811
  }
5812
+ function isCodexMissingRolloutError(message) {
5813
+ return /\bno\s+rollout\s+found\b/i.test(message) || /\bmissing\s+rollout\b/i.test(message) || /\brollout\b.*\b(not found|missing)\b/i.test(message) || /\bthread\b.*\b(not found|missing)\b/i.test(message) || /\bmissing\s+thread\b/i.test(message);
5814
+ }
5815
+ function classifyCodexResumeError(message) {
5816
+ if (isCodexMissingRolloutError(message)) {
5817
+ return {
5818
+ kind: "missing_rollout",
5819
+ resumeErrorClass: "missing_rollout",
5820
+ recoveryAction: "fallback_fresh_thread",
5821
+ telemetry: {
5822
+ kind: "telemetry",
5823
+ name: "recovery",
5824
+ source: "codex_resume_missing_rollout",
5825
+ attrs: {
5826
+ resume_error_class: "missing_rollout",
5827
+ recovery_action: "fallback_fresh_thread"
5828
+ }
5829
+ }
5830
+ };
5831
+ }
5832
+ if (/\b(no\s+permission|permission\s+denied|forbidden|unauthorized)\b/i.test(message)) {
5833
+ return {
5834
+ kind: "terminal_error",
5835
+ resumeErrorClass: "permission_denied"
5836
+ };
5837
+ }
5838
+ return {
5839
+ kind: "terminal_error",
5840
+ resumeErrorClass: "unknown"
5841
+ };
5842
+ }
5843
+ function hasJsonRpcField(message, field) {
5844
+ return Object.prototype.hasOwnProperty.call(message, field);
5845
+ }
5846
+ function isJsonRpcResponse(message) {
5847
+ return message.id !== void 0 && (hasJsonRpcField(message, "result") || hasJsonRpcField(message, "error"));
5848
+ }
5849
+ function isCodexServerRequest(message) {
5850
+ return message.id !== void 0 && typeof message.method === "string" && !isJsonRpcResponse(message);
5851
+ }
5168
5852
  var CodexDriver = class {
5169
5853
  id = "codex";
5170
5854
  lifecycle = {
@@ -5176,6 +5860,7 @@ var CodexDriver = class {
5176
5860
  chat: "slock_cli",
5177
5861
  runtimeControl: "none"
5178
5862
  };
5863
+ stdoutChannel = "structured_protocol";
5179
5864
  session = {
5180
5865
  recovery: "resume_or_fresh"
5181
5866
  };
@@ -5183,6 +5868,8 @@ var CodexDriver = class {
5183
5868
  detectedModelsVerifiedAs: "launchable",
5184
5869
  toLaunchSpec: (modelId) => ({ params: { model: modelId } })
5185
5870
  };
5871
+ startupReadiness = "initial_turn";
5872
+ requiresSessionInitForDelivery = true;
5186
5873
  supportsStdinNotification = true;
5187
5874
  busyDeliveryMode = "direct";
5188
5875
  supportsNativeStandingPrompt = true;
@@ -5229,25 +5916,32 @@ var CodexDriver = class {
5229
5916
  pendingInitialPrompt = null;
5230
5917
  initializeRequestId = null;
5231
5918
  pendingThreadRequest = null;
5919
+ pendingThreadRequestId = null;
5920
+ pendingThreadRequestMethod = null;
5921
+ pendingResumeFallbackParams = null;
5922
+ pendingInitialTurnRequestId = null;
5232
5923
  initialTurnStarted = false;
5233
5924
  normalizer = new CodexEventNormalizer();
5234
5925
  async spawn(ctx) {
5235
- ensureGitRepoForCodex(ctx.workingDirectory);
5236
5926
  const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
5237
5927
  this.process = null;
5238
5928
  this.requestId = 0;
5239
5929
  this.pendingInitialPrompt = ctx.prompt;
5240
5930
  this.initializeRequestId = null;
5241
5931
  this.pendingThreadRequest = null;
5932
+ this.pendingThreadRequestId = null;
5933
+ this.pendingThreadRequestMethod = null;
5934
+ this.pendingResumeFallbackParams = null;
5935
+ this.pendingInitialTurnRequestId = null;
5242
5936
  this.initialTurnStarted = false;
5243
- this.normalizer.reset({ threadId: ctx.config.sessionId || null });
5937
+ this.normalizer.reset();
5244
5938
  const args = ["app-server", "--listen", "stdio://"];
5245
- const { command, args: spawnArgs } = resolveCodexSpawn(args);
5939
+ const { command, args: spawnArgs, shell } = resolveCodexSpawn(args);
5246
5940
  const proc = spawn2(command, spawnArgs, {
5247
5941
  cwd: ctx.workingDirectory,
5248
5942
  stdio: ["pipe", "pipe", "pipe"],
5249
5943
  env: spawnEnv,
5250
- shell: false
5944
+ shell
5251
5945
  });
5252
5946
  this.process = proc;
5253
5947
  queueMicrotask(() => {
@@ -5265,24 +5959,77 @@ var CodexDriver = class {
5265
5959
  return [];
5266
5960
  }
5267
5961
  const events = [];
5268
- if (message.result) {
5962
+ if (isCodexServerRequest(message)) {
5963
+ this.sendErrorResponse(
5964
+ message.id,
5965
+ `Codex app-server request "${message.method}" is not supported by the non-interactive Slock daemon`
5966
+ );
5967
+ return events;
5968
+ }
5969
+ const isResponse = isJsonRpcResponse(message);
5970
+ if (isResponse && hasJsonRpcField(message, "result")) {
5269
5971
  if (message.id === this.initializeRequestId) {
5270
5972
  this.initializeRequestId = null;
5271
5973
  this.sendNotification("initialized", {});
5272
5974
  if (this.pendingThreadRequest) {
5273
- this.sendRequest(this.pendingThreadRequest.method, this.pendingThreadRequest.params);
5975
+ this.sendThreadRequest(this.pendingThreadRequest.method, this.pendingThreadRequest.params);
5274
5976
  this.pendingThreadRequest = null;
5275
5977
  }
5276
5978
  return events;
5277
5979
  }
5278
5980
  }
5279
- if (message.error && message.id === this.initializeRequestId) {
5981
+ if (isResponse && hasJsonRpcField(message, "error") && message.id === this.initializeRequestId) {
5280
5982
  this.initializeRequestId = null;
5281
5983
  this.pendingThreadRequest = null;
5282
- events.push({ kind: "error", message: message.error.message || "Codex app-server request failed" });
5984
+ this.pendingThreadRequestId = null;
5985
+ this.pendingThreadRequestMethod = null;
5986
+ this.pendingResumeFallbackParams = null;
5987
+ events.push({
5988
+ kind: "error",
5989
+ message: message.error?.message || "Codex app-server request failed",
5990
+ startupRequestMethod: "initialize"
5991
+ });
5992
+ return events;
5993
+ }
5994
+ if (isResponse && message.id === this.pendingThreadRequestId) {
5995
+ if (hasJsonRpcField(message, "error")) {
5996
+ const errorMessage = message.error?.message || "Codex app-server request failed";
5997
+ const requestMethod = this.pendingThreadRequestMethod;
5998
+ const resumeErrorClassification = requestMethod === "thread/resume" ? classifyCodexResumeError(errorMessage) : null;
5999
+ if (this.pendingResumeFallbackParams && resumeErrorClassification?.kind === "missing_rollout") {
6000
+ events.push(resumeErrorClassification.telemetry);
6001
+ this.sendThreadRequest("thread/start", this.pendingResumeFallbackParams);
6002
+ this.pendingResumeFallbackParams = null;
6003
+ return events;
6004
+ }
6005
+ this.pendingThreadRequestId = null;
6006
+ this.pendingThreadRequestMethod = null;
6007
+ this.pendingResumeFallbackParams = null;
6008
+ events.push(requestMethod ? { kind: "error", message: errorMessage, startupRequestMethod: requestMethod } : { kind: "error", message: errorMessage });
6009
+ return events;
6010
+ }
6011
+ this.pendingThreadRequestId = null;
6012
+ this.pendingThreadRequestMethod = null;
6013
+ this.pendingResumeFallbackParams = null;
6014
+ }
6015
+ if (isResponse && message.id === this.pendingInitialTurnRequestId) {
6016
+ this.pendingInitialTurnRequestId = null;
6017
+ if (hasJsonRpcField(message, "error")) {
6018
+ events.push({
6019
+ kind: "error",
6020
+ message: message.error?.message || "Codex app-server request failed",
6021
+ startupRequestMethod: "turn/start"
6022
+ });
6023
+ } else {
6024
+ this.pendingInitialPrompt = null;
6025
+ }
5283
6026
  return events;
5284
6027
  }
5285
6028
  const result = this.normalizer.normalizeMessage(message);
6029
+ if (result.turnStarted) {
6030
+ this.pendingInitialTurnRequestId = null;
6031
+ this.pendingInitialPrompt = null;
6032
+ }
5286
6033
  if (result.threadReady) {
5287
6034
  this.startInitialTurn();
5288
6035
  }
@@ -5291,10 +6038,7 @@ var CodexDriver = class {
5291
6038
  get currentSessionId() {
5292
6039
  return this.normalizer.threadId;
5293
6040
  }
5294
- encodeStdinMessage(text, sessionId, opts) {
5295
- if (!this.normalizer.threadId && sessionId) {
5296
- this.normalizer.adoptThreadId(sessionId);
5297
- }
6041
+ encodeStdinMessage(text, _sessionId, opts) {
5298
6042
  if (!this.normalizer.threadId) return null;
5299
6043
  const mode = opts?.mode || "busy";
5300
6044
  if (mode === "busy") {
@@ -5335,11 +6079,10 @@ var CodexDriver = class {
5335
6079
  return this.requestId;
5336
6080
  }
5337
6081
  startInitialTurn() {
5338
- if (this.initialTurnStarted || !this.pendingInitialPrompt || !this.normalizer.threadId) return;
6082
+ if (this.initialTurnStarted || this.pendingInitialTurnRequestId !== null || !this.pendingInitialPrompt || !this.normalizer.threadId) return;
5339
6083
  this.initialTurnStarted = true;
5340
6084
  const prompt = this.pendingInitialPrompt;
5341
- this.pendingInitialPrompt = null;
5342
- this.sendRequest("turn/start", {
6085
+ this.pendingInitialTurnRequestId = this.sendRequest("turn/start", {
5343
6086
  threadId: this.normalizer.threadId,
5344
6087
  input: [{ type: "text", text: prompt }]
5345
6088
  });
@@ -5354,6 +6097,27 @@ var CodexDriver = class {
5354
6097
  }) + "\n");
5355
6098
  return id;
5356
6099
  }
6100
+ sendErrorResponse(id, message) {
6101
+ this.process?.stdin?.write(JSON.stringify({
6102
+ jsonrpc: "2.0",
6103
+ id,
6104
+ error: {
6105
+ code: -32601,
6106
+ message
6107
+ }
6108
+ }) + "\n");
6109
+ }
6110
+ sendThreadRequest(method, params) {
6111
+ const id = this.sendRequest(method, params);
6112
+ this.pendingThreadRequestId = id;
6113
+ this.pendingThreadRequestMethod = method;
6114
+ this.pendingResumeFallbackParams = null;
6115
+ if (method === "thread/resume") {
6116
+ const { threadId: _threadId, ...freshParams } = params;
6117
+ this.pendingResumeFallbackParams = freshParams;
6118
+ }
6119
+ return id;
6120
+ }
5357
6121
  sendNotification(method, params) {
5358
6122
  this.process?.stdin?.write(JSON.stringify({
5359
6123
  jsonrpc: "2.0",
@@ -5376,7 +6140,7 @@ function detectCodexModels(home = os3.homedir()) {
5376
6140
  for (const entry of entries) {
5377
6141
  const slug = typeof entry?.slug === "string" ? entry.slug : null;
5378
6142
  if (!slug) continue;
5379
- if (entry?.visibility && entry.visibility !== "public") continue;
6143
+ if (entry?.visibility && entry.visibility !== "public" && entry.visibility !== "list") continue;
5380
6144
  if (entry?.supported_in_api === false) continue;
5381
6145
  const label = typeof entry?.display_name === "string" && entry.display_name.length > 0 ? entry.display_name : slug;
5382
6146
  models.push({ id: slug, label, verified: "launchable" });
@@ -6374,6 +7138,9 @@ function mapKimiSdkEventToParsedEvents(event, state) {
6374
7138
  var KIMI_SDK_RUNTIME_SESSION_DESCRIPTOR = {
6375
7139
  transport: "sdk",
6376
7140
  lifecycle: "sdk_session",
7141
+ stdout: {
7142
+ channel: "diagnostic"
7143
+ },
6377
7144
  input: {
6378
7145
  initial: "start",
6379
7146
  idle: "sdk_prompt",
@@ -7217,7 +7984,8 @@ var PI_SDK_COMPACTION_ENABLED = true;
7217
7984
  var PI_PROVIDER_LABELS = {
7218
7985
  google: "Google",
7219
7986
  openai: "OpenAI",
7220
- openrouter: "OpenRouter"
7987
+ openrouter: "OpenRouter",
7988
+ deepseek: "DeepSeek"
7221
7989
  };
7222
7990
  function createPiSdkEventMappingState(sessionId = null) {
7223
7991
  return {
@@ -7448,6 +8216,9 @@ function mapPiSdkEventToParsedEvents(event, state) {
7448
8216
  var PI_RUNTIME_SESSION_DESCRIPTOR = {
7449
8217
  transport: "sdk",
7450
8218
  lifecycle: "sdk_session",
8219
+ stdout: {
8220
+ channel: "diagnostic"
8221
+ },
7451
8222
  input: {
7452
8223
  initial: "start",
7453
8224
  idle: "sdk_prompt",
@@ -7838,6 +8609,7 @@ function delay(ms) {
7838
8609
 
7839
8610
  // src/drivers/runtimeSession.ts
7840
8611
  import { EventEmitter as EventEmitter3 } from "events";
8612
+ import { StringDecoder } from "string_decoder";
7841
8613
  function descriptorFromDriver(driver) {
7842
8614
  const lifecycle = driver.lifecycle.kind === "per_turn" ? "turn_based" : "persistent_stream";
7843
8615
  const idle = driver.supportsStdinNotification ? "stdin" : "unsupported";
@@ -7845,6 +8617,9 @@ function descriptorFromDriver(driver) {
7845
8617
  return {
7846
8618
  transport: "child_process",
7847
8619
  lifecycle,
8620
+ stdout: {
8621
+ channel: driver.stdoutChannel ?? "diagnostic"
8622
+ },
7848
8623
  input: {
7849
8624
  initial: "start",
7850
8625
  idle,
@@ -7869,6 +8644,7 @@ var ChildProcessRuntimeSession = class {
7869
8644
  process = null;
7870
8645
  started = false;
7871
8646
  stdoutBuffer = "";
8647
+ stdoutDecoder = new StringDecoder("utf8");
7872
8648
  requestedStopReason;
7873
8649
  get pid() {
7874
8650
  return this.process?.pid;
@@ -7931,7 +8707,7 @@ var ChildProcessRuntimeSession = class {
7931
8707
  }
7932
8708
  attachProcess(process2) {
7933
8709
  process2.stdout?.on("data", (chunk) => {
7934
- const chunkText = chunk.toString();
8710
+ const chunkText = this.stdoutDecoder.write(chunk);
7935
8711
  this.events.emit("stdout", chunkText);
7936
8712
  this.stdoutBuffer += chunkText;
7937
8713
  const lines = this.stdoutBuffer.split("\n");
@@ -8611,6 +9387,73 @@ function getMessageShortId(messageId2) {
8611
9387
  return messageId2.startsWith("thread-") ? messageId2.slice(7) : messageId2.slice(0, 8);
8612
9388
  }
8613
9389
  var RESPONSE_TARGET_HINT = "Reply in the channel or create/reply in a thread as appropriate; use each message's `target` and `msg` fields to choose the exact target.";
9390
+ var SESSION_TRANSCRIPT_MAX_BYTES = 10 * 1024 * 1024;
9391
+ function runtimeTier(runtime) {
9392
+ const tier1 = /* @__PURE__ */ new Set(["claude", "codex", "kimi-sdk", "kimi", "pi"]);
9393
+ if (tier1.has(runtime)) return "tier1";
9394
+ return "tier2_or_fallback";
9395
+ }
9396
+ function redactTranscript(text) {
9397
+ return text.replace(/sk_(?:agent|machine|computer)_[A-Za-z0-9_-]+/g, "sk_[redacted]").replace(/sap_[A-Za-z0-9_-]+/g, "sap_[redacted]").replace(/Bearer\s+[A-Za-z0-9_\-./+=]+/g, "Bearer [redacted]").replace(/["']?auth[_-]?token["']?\s*[:=]\s*["'][^"']+["']/gi, "[redacted]").replace(/https?:\/\/[^\s\"]+/g, "[url]");
9398
+ }
9399
+ function allowedTranscriptRootsForRuntime(runtime, homeDir, workspaceDir) {
9400
+ const roots = [workspaceDir];
9401
+ switch (runtime) {
9402
+ case "claude":
9403
+ roots.push(path13.join(homeDir, ".claude"));
9404
+ break;
9405
+ case "codex":
9406
+ roots.push(path13.join(homeDir, ".codex"));
9407
+ break;
9408
+ case "kimi":
9409
+ case "kimi-sdk":
9410
+ roots.push(path13.join(homeDir, ".kimi"));
9411
+ break;
9412
+ case "pi":
9413
+ roots.push(path13.join(homeDir, ".pi"), path13.join(homeDir, ".pi", "agent"));
9414
+ break;
9415
+ }
9416
+ return roots;
9417
+ }
9418
+ async function isPathWithinAllowedRoots(filePath, roots) {
9419
+ const real = await realpath(filePath).catch(() => null);
9420
+ if (!real) return false;
9421
+ for (const root of roots) {
9422
+ const realRoot = await realpath(root).catch(() => null);
9423
+ if (!realRoot) continue;
9424
+ const rel = path13.relative(realRoot, real);
9425
+ if (!rel.startsWith("..") && !path13.isAbsolute(rel)) return true;
9426
+ }
9427
+ return false;
9428
+ }
9429
+ async function readBoundedTranscriptFile(filePath, maxBytes) {
9430
+ const info = await lstat(filePath);
9431
+ if (info.isSymbolicLink()) {
9432
+ throw new Error("symbolic links are not allowed");
9433
+ }
9434
+ if (!info.isFile()) {
9435
+ throw new Error(`not a regular file: ${filePath}`);
9436
+ }
9437
+ const fd = await open(filePath, "r");
9438
+ try {
9439
+ const toRead = Math.min(info.size, maxBytes);
9440
+ const buf = Buffer.alloc(toRead);
9441
+ await fd.read(buf, 0, toRead, 0);
9442
+ const truncated = info.size > maxBytes;
9443
+ const text = buf.toString("utf8");
9444
+ return { text, sizeBytes: Buffer.byteLength(text, "utf8"), truncated };
9445
+ } finally {
9446
+ await fd.close();
9447
+ }
9448
+ }
9449
+ async function readAndRedact(filePath, maxBytes) {
9450
+ try {
9451
+ const { text, truncated } = await readBoundedTranscriptFile(filePath, maxBytes);
9452
+ return { text: redactTranscript(text), truncated };
9453
+ } catch {
9454
+ return null;
9455
+ }
9456
+ }
8614
9457
  function findSessionJsonl(root, predicate) {
8615
9458
  let visited = 0;
8616
9459
  const maxEntries = 1e4;
@@ -8638,6 +9481,53 @@ function findSessionJsonl(root, predicate) {
8638
9481
  };
8639
9482
  return visit(root, maxDepth);
8640
9483
  }
9484
+ function findKimiSdkSessionDir(sessionId, agentId, homeDir) {
9485
+ const indexPath = path13.join(homeDir, ".kimi", "session_index.jsonl");
9486
+ try {
9487
+ const index = readFileSync6(indexPath, "utf8");
9488
+ for (const line of index.split("\n")) {
9489
+ if (!line.trim()) continue;
9490
+ try {
9491
+ const entry = JSON.parse(line);
9492
+ if (entry.sessionId === sessionId && entry.sessionDir && existsSync9(entry.sessionDir)) {
9493
+ return entry.sessionDir;
9494
+ }
9495
+ } catch {
9496
+ }
9497
+ }
9498
+ } catch {
9499
+ }
9500
+ const sessionsRoot = path13.join(homeDir, ".kimi", "sessions");
9501
+ try {
9502
+ const prefix = agentId ? `wd_${agentId}_` : `wd_`;
9503
+ for (const entry of readdirSync3(sessionsRoot, { withFileTypes: true })) {
9504
+ if (!entry.isDirectory() || !entry.name.startsWith(prefix)) continue;
9505
+ const candidate = path13.join(sessionsRoot, entry.name, `session_${sessionId}`);
9506
+ if (existsSync9(candidate)) return candidate;
9507
+ }
9508
+ } catch {
9509
+ }
9510
+ return null;
9511
+ }
9512
+ function findPiSessionFile2(sessionId, workingDirectory, homeDir) {
9513
+ if (workingDirectory) {
9514
+ const piSessionsDir = path13.join(workingDirectory, ".pi-sessions");
9515
+ try {
9516
+ const files = readdirSync3(piSessionsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => ({ name: e.name, path: path13.join(piSessionsDir, e.name), stat: statSync(path13.join(piSessionsDir, e.name)) })).filter((f) => f.stat.isFile() && f.name.includes(sessionId)).sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs);
9517
+ if (files[0]) return files[0].path;
9518
+ } catch {
9519
+ }
9520
+ }
9521
+ const legacyRoots = [
9522
+ path13.join(homeDir, ".pi", "agent"),
9523
+ path13.join(homeDir, ".pi")
9524
+ ];
9525
+ for (const root of legacyRoots) {
9526
+ const found = findSessionJsonl(root, (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId));
9527
+ if (found) return found;
9528
+ }
9529
+ return null;
9530
+ }
8641
9531
  function safeSessionFilename(value) {
8642
9532
  const normalized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
8643
9533
  return normalized || "unknown-session";
@@ -8677,20 +9567,30 @@ function ensureRuntimeHomeDir(config, defaultHomeDir, workspacePath) {
8677
9567
  }
8678
9568
  return defaultHomeDir;
8679
9569
  }
8680
- function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
8681
- const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
8682
- if (directPath) {
8683
- try {
8684
- if (statSync(directPath).isFile()) {
8685
- return { label: sessionId, path: directPath, runtime, reachable: true };
8686
- }
8687
- } catch {
8688
- }
9570
+ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir, opts) {
9571
+ let resolvedPath = null;
9572
+ let lookupMethod = "none";
9573
+ if (runtime === "claude") {
9574
+ lookupMethod = "claude_jsonl";
9575
+ resolvedPath = findSessionJsonl(path13.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`);
9576
+ } else if (runtime === "codex") {
9577
+ lookupMethod = "codex_jsonl";
9578
+ resolvedPath = findSessionJsonl(path13.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId));
9579
+ } else if (runtime === "kimi-sdk" || runtime === "kimi") {
9580
+ lookupMethod = "kimi_sdk_index";
9581
+ resolvedPath = findKimiSdkSessionDir(sessionId, opts?.agentId, homeDir);
9582
+ } else if (runtime === "pi") {
9583
+ lookupMethod = "pi_jsonl";
9584
+ resolvedPath = findPiSessionFile2(sessionId, opts?.workingDirectory, homeDir);
8689
9585
  }
8690
- const resolvedPath = runtime === "claude" ? findSessionJsonl(path13.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path13.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
8691
9586
  if (!resolvedPath && fallbackDir) {
8692
9587
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
8693
- if (fallback) return fallback;
9588
+ if (fallback) {
9589
+ return {
9590
+ ...fallback,
9591
+ reason: `${fallback.reason}; attempted_lookup=${lookupMethod}`
9592
+ };
9593
+ }
8694
9594
  }
8695
9595
  const ref = {
8696
9596
  label: sessionId,
@@ -8699,7 +9599,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
8699
9599
  reachable: Boolean(resolvedPath)
8700
9600
  };
8701
9601
  if (!resolvedPath) {
8702
- ref.reason = "session file path not found";
9602
+ ref.reason = `session file path not found; attempted_lookup=${lookupMethod}`;
8703
9603
  }
8704
9604
  return ref;
8705
9605
  }
@@ -8768,7 +9668,7 @@ function dynamicReplyInstruction() {
8768
9668
  function dynamicClaimInstruction() {
8769
9669
  return "claim the relevant task with `raft task claim`";
8770
9670
  }
8771
- function formatIncomingMessage(message) {
9671
+ function formatIncomingMessage(message, options = {}) {
8772
9672
  const threadJoinPrefix = message.thread_join_context ? [
8773
9673
  `[Slock thread context: you were added to a new thread via @mention.]`,
8774
9674
  `parent: ${message.thread_join_context.parent_target}`,
@@ -8786,13 +9686,19 @@ function formatIncomingMessage(message) {
8786
9686
  const msgId = message.message_id ? getMessageShortId(message.message_id) : "-";
8787
9687
  const time = message.timestamp ? toLocalTime(message.timestamp) : "-";
8788
9688
  const senderType = formatVisibleActorType(message.sender_type);
8789
- const attachSuffix = message.attachments?.length ? ` [${message.attachments.length} attachment${message.attachments.length > 1 ? "s" : ""}: ${message.attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} \u2014 use view_file to download]` : "";
9689
+ const attachSuffix = formatAttachmentSuffix(message.attachments, options.attachmentHint);
8790
9690
  const taskSuffix = message.task_status ? ` [task #${message.task_number} status=${message.task_status}${message.task_assignee_id ? ` assignee=${formatTaskAssigneeType(message.task_assignee_type)}:${message.task_assignee_id}` : ""}]` : "";
8791
9691
  const lineageSuffix = formatProducerFactLineageBracket(message.producerFactId);
8792
9692
  const body = `[target=${target} msg=${msgId} time=${time}${senderType}] ${formatSenderHandle(message)}: ${message.content}${attachSuffix}${taskSuffix}${lineageSuffix}`;
8793
9693
  return threadJoinPrefix ? `${threadJoinPrefix}
8794
9694
  ${body}` : body;
8795
9695
  }
9696
+ function incomingMessageFormatOptionsForDriver(driver) {
9697
+ switch (driver.communication.chat) {
9698
+ case "slock_cli":
9699
+ return { attachmentHint: "slock_cli" };
9700
+ }
9701
+ }
8796
9702
  function formatRuntimeProfileControlPrompt(messages) {
8797
9703
  const controls = messages.map((message) => ({
8798
9704
  message,
@@ -9263,7 +10169,6 @@ function cleanupAgentCredentialProxy(agentId, launchId) {
9263
10169
  unregisterAgentCredentialProxyForLaunch({ agentId, launchId });
9264
10170
  }
9265
10171
  function stripManagedRunnerCredential(config) {
9266
- if (!config.agentCredentialKey && !config.agentCredentialId) return config;
9267
10172
  const { agentCredentialKey: _agentCredentialKey, agentCredentialId: _agentCredentialId, ...rest } = config;
9268
10173
  return rest;
9269
10174
  }
@@ -9345,6 +10250,68 @@ function summarizeCrash(code, signal) {
9345
10250
  if (typeof code === "number") return `exit code ${code}`;
9346
10251
  return "unknown exit";
9347
10252
  }
10253
+ function closeSatisfiedTurnBoundary(ap, expectedTermination) {
10254
+ if (expectedTermination) return true;
10255
+ if (ap.runtime.descriptor.turnBoundary === "process_exit") return true;
10256
+ return !ap.runtimeTraceSpan;
10257
+ }
10258
+ function isStartupRequestErrorEvent(event) {
10259
+ return event.kind === "error" && !!event.startupRequestMethod;
10260
+ }
10261
+ function runtimeDiagnosticTitle(event) {
10262
+ switch (event.itemType) {
10263
+ case "configWarning":
10264
+ return "Codex config warning";
10265
+ case "guardianWarning":
10266
+ return "Codex guardian warning";
10267
+ case "deprecationNotice":
10268
+ return "Codex deprecation notice";
10269
+ default:
10270
+ return "Codex warning";
10271
+ }
10272
+ }
10273
+ function summarizeDiagnosticRange(range) {
10274
+ if (range === void 0 || range === null) return null;
10275
+ try {
10276
+ const json = JSON.stringify(range);
10277
+ if (!json) return null;
10278
+ return json.length <= 300 ? json : `${json.slice(0, 299)}\u2026`;
10279
+ } catch {
10280
+ return null;
10281
+ }
10282
+ }
10283
+ function runtimeDiagnosticTrajectoryEntry(event) {
10284
+ const lines = [event.message];
10285
+ if (event.details && event.details !== event.message) {
10286
+ lines.push(event.details);
10287
+ }
10288
+ if (event.path) {
10289
+ lines.push(`Path: ${event.path}`);
10290
+ }
10291
+ const range = summarizeDiagnosticRange(event.range);
10292
+ if (range) {
10293
+ lines.push(`Range: ${range}`);
10294
+ }
10295
+ return {
10296
+ kind: "system",
10297
+ title: runtimeDiagnosticTitle(event),
10298
+ text: lines.join("\n")
10299
+ };
10300
+ }
10301
+ function runtimeDiagnosticTraceAttrs(event) {
10302
+ return {
10303
+ kind: event.kind,
10304
+ severity: event.severity,
10305
+ source: event.source,
10306
+ itemType: event.itemType,
10307
+ payloadBytes: event.payloadBytes,
10308
+ message_present: Boolean(event.message),
10309
+ details_present: Boolean(event.details),
10310
+ path_present: Boolean(event.path),
10311
+ range_present: event.range !== void 0,
10312
+ session_id_present: Boolean(event.sessionId)
10313
+ };
10314
+ }
9348
10315
  function currentErrorCandidates(ap) {
9349
10316
  return [
9350
10317
  ap.runtimeErrorSinceProgress ? ap.lastRuntimeError : null,
@@ -9429,79 +10396,39 @@ function isPiReplayRejectedByProvider(text) {
9429
10396
  return /Cannot continue from message role:\s*assistant/i.test(text);
9430
10397
  }
9431
10398
  function resumeSessionRuntimeLabel(runtimeId) {
10399
+ if (runtimeId === "codex") return "Codex";
9432
10400
  if (runtimeId === "opencode") return "OpenCode";
9433
10401
  if (runtimeId === "gemini") return "Gemini";
9434
10402
  if (runtimeId === "pi") return "Pi";
9435
10403
  return "Claude";
9436
10404
  }
9437
- function classifyActivityDetailForTrace(detail) {
9438
- if (!detail) return void 0;
9439
- if (detail === "Message received") return "message_received";
9440
- if (detail === "Starting\u2026") return "starting";
9441
- if (detail === "Running command\u2026") return "running_command";
9442
- if (detail === "Checking messages\u2026") return "checking_messages";
9443
- if (detail === "Compacting context") return "compacting_context";
9444
- if (detail === "Context compaction finished") return "compaction_finished";
9445
- if (detail === "Context compaction still running; no finish event observed") return "compaction_stale";
9446
- if (detail === "Idle" || detail === "Process idle") return "idle";
9447
- if (detail.startsWith("Restarting stalled ") && detail.endsWith(" runtime for queued message")) return "stalled_recovery";
9448
- if (detail.startsWith("Runtime stalled: no runtime events for ")) return "runtime_stalled";
9449
- return "other";
9450
- }
9451
10405
  function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
9452
- const context = [];
9453
- if (ap.lastActivityDetail) {
9454
- context.push(`after ${ap.lastActivityDetail}`);
9455
- }
9456
- if (ap.runtime.descriptor.busyDelivery === "gated") {
9457
- context.push(`phase=${ap.gatedSteering.phase}`);
9458
- }
9459
- if (ap.gatedSteering.outstandingToolUses > 0) {
9460
- context.push(`tools=${ap.gatedSteering.outstandingToolUses}`);
9461
- }
9462
- if (ap.gatedSteering.compacting) {
9463
- context.push("compacting");
9464
- }
9465
- if (ap.inbox.length > 0) {
9466
- context.push(`queued=${ap.inbox.length}`);
9467
- }
9468
- const detail = [
9469
- `Runtime stalled: no runtime events for ${staleForMinutes}m`,
9470
- context.length > 0 ? ` (${context.join(", ")})` : ""
9471
- ].join("");
9472
- return {
9473
- detail,
9474
- traceAttrs: {
9475
- ageMs: staleForMs,
9476
- staleForMinutes,
9477
- lastActivity: ap.lastActivity,
9478
- lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
9479
- lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
9480
- runtime: ap.config.runtime,
9481
- model: ap.config.model,
9482
- platform: process.platform,
9483
- arch: process.arch,
9484
- launchId: ap.launchId || void 0,
9485
- sessionIdPresent: Boolean(ap.sessionId),
9486
- inboxCount: ap.inbox.length,
9487
- pendingNotificationCount: ap.notifications.pendingCount,
9488
- processPidPresent: typeof ap.runtime.pid === "number",
9489
- busyDeliveryMode: ap.driver.busyDeliveryMode,
9490
- supportsStdinNotification: ap.driver.supportsStdinNotification,
9491
- gatedPhase: ap.runtime.descriptor.busyDelivery === "gated" ? ap.gatedSteering.phase : void 0,
9492
- outstandingToolUses: ap.gatedSteering.outstandingToolUses,
9493
- compacting: ap.gatedSteering.compacting,
9494
- recentStderrCount: ap.recentStderr.length,
9495
- recentStdoutCount: ap.recentStdout.length,
9496
- ...runtimeTraceCounterAttrs(ap)
9497
- }
9498
- };
9499
- }
9500
- function classifyRuntimeStallReason(ap) {
9501
- if (ap.runtimeProgress.lastEventKind === "tool_output" && ap.gatedSteering.outstandingToolUses === 0) {
9502
- return "harness_post_tool_silent_wedge";
9503
- }
9504
- return "no_runtime_events";
10406
+ return projectApmRuntimeStallDiagnostic({
10407
+ staleForMs,
10408
+ staleForMinutes,
10409
+ lastActivity: ap.lastActivity,
10410
+ lastActivityDetail: ap.lastActivityDetail,
10411
+ runtimeDescriptorBusyDelivery: ap.runtime.descriptor.busyDelivery,
10412
+ runtimeProgressLastEventKind: ap.runtimeProgress.lastEventKind,
10413
+ runtime: ap.config.runtime,
10414
+ model: ap.config.model,
10415
+ platform: process.platform,
10416
+ arch: process.arch,
10417
+ launchId: ap.launchId,
10418
+ sessionIdPresent: Boolean(ap.sessionId),
10419
+ inboxCount: ap.inbox.length,
10420
+ pendingNotificationCount: ap.notifications.pendingCount,
10421
+ processPidPresent: typeof ap.runtime.pid === "number",
10422
+ driverBusyDeliveryMode: ap.driver.busyDeliveryMode,
10423
+ supportsStdinNotification: ap.driver.supportsStdinNotification,
10424
+ gatedPhase: ap.gatedSteering.phase,
10425
+ outstandingToolUses: ap.gatedSteering.outstandingToolUses,
10426
+ compacting: ap.gatedSteering.compacting,
10427
+ reviewing: ap.gatedSteering.reviewing === true ? true : void 0,
10428
+ recentStderrCount: ap.recentStderr.length,
10429
+ recentStdoutCount: ap.recentStdout.length,
10430
+ runtimeTraceCounterAttrs: runtimeTraceCounterAttrs(ap)
10431
+ });
9505
10432
  }
9506
10433
  function bucketBytes(value) {
9507
10434
  const bytes = typeof value === "string" ? Buffer.byteLength(value, "utf8") : Math.max(0, Math.floor(value ?? 0));
@@ -9730,6 +10657,8 @@ var AgentProcessManager = class _AgentProcessManager {
9730
10657
  agentSpawnFailBackoff = /* @__PURE__ */ new Map();
9731
10658
  daemonVersion;
9732
10659
  computerVersion;
10660
+ workerUrl;
10661
+ fetchImpl;
9733
10662
  constructor(sendToServer, daemonApiKey, opts) {
9734
10663
  this.slockCliPath = opts.slockCliPath ?? "";
9735
10664
  this.sendToServer = sendToServer;
@@ -9742,6 +10671,8 @@ var AgentProcessManager = class _AgentProcessManager {
9742
10671
  this.tracer = opts.tracer ?? noopTracer;
9743
10672
  this.daemonVersion = opts.daemonVersion?.trim() || null;
9744
10673
  this.computerVersion = opts.computerVersion?.trim() || null;
10674
+ this.workerUrl = opts.workerUrl?.trim() || null;
10675
+ this.fetchImpl = opts.fetchImpl ?? daemonFetch;
9745
10676
  this.stdinNotificationRetryMs = Math.max(
9746
10677
  0,
9747
10678
  Math.floor(opts.stdinNotificationRetryMs ?? STDIN_NOTIFICATION_RETRY_DELAY_MS)
@@ -9863,13 +10794,17 @@ var AgentProcessManager = class _AgentProcessManager {
9863
10794
  this.sendStdinNotification(agentId);
9864
10795
  }, delayMs);
9865
10796
  }
10797
+ isApmIdle(ap) {
10798
+ return ap.gatedSteering.isIdle;
10799
+ }
9866
10800
  flushPendingDirectStdinNotificationOnRuntimeProgress(agentId, ap, source) {
9867
10801
  if (ap.notifications.pendingCount === 0) return false;
9868
- if (ap.isIdle) return false;
10802
+ if (this.isApmIdle(ap)) return false;
9869
10803
  if (!ap.sessionId) return false;
9870
10804
  if (!ap.driver.supportsStdinNotification) return false;
9871
10805
  if (ap.runtime.descriptor.busyDelivery !== "direct") return false;
9872
10806
  if (ap.gatedSteering.compacting) return false;
10807
+ if (ap.gatedSteering.reviewing) return false;
9873
10808
  this.recordDaemonTrace("daemon.agent.stdin_notification.retry_signal", {
9874
10809
  agentId,
9875
10810
  runtime: ap.config.runtime,
@@ -9975,8 +10910,9 @@ var AgentProcessManager = class _AgentProcessManager {
9975
10910
  queueDeliveryForRuntimeErrorBackoff(agentId, ap, message) {
9976
10911
  const remainingMs = this.runtimeErrorDeliveryBackoffRemainingMs(ap);
9977
10912
  if (remainingMs <= 0) return false;
10913
+ const isIdle = this.isApmIdle(ap);
9978
10914
  ap.inbox.push(message);
9979
- if (!ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
10915
+ if (!isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
9980
10916
  ap.notifications.add();
9981
10917
  }
9982
10918
  const scheduled = this.scheduleRuntimeErrorDeliveryBackoffFlush(agentId, ap);
@@ -9987,7 +10923,7 @@ var AgentProcessManager = class _AgentProcessManager {
9987
10923
  runtime: ap.config.runtime,
9988
10924
  session_id_present: Boolean(ap.sessionId),
9989
10925
  launchId: ap.launchId || void 0,
9990
- is_idle: ap.isIdle,
10926
+ is_idle: isIdle,
9991
10927
  inbox_count: ap.inbox.length,
9992
10928
  pending_notification_count: ap.notifications.pendingCount,
9993
10929
  runtime_error_backoff_remaining_ms: remainingMs,
@@ -10007,7 +10943,7 @@ var AgentProcessManager = class _AgentProcessManager {
10007
10943
  }
10008
10944
  if (ap.inbox.length === 0) return false;
10009
10945
  const reason = ap.runtimeErrorDeliveryBackoff.reason || "runtime_error_backoff";
10010
- if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
10946
+ if (this.isApmIdle(ap) && ap.driver.supportsStdinNotification && ap.sessionId) {
10011
10947
  ap.notifications.pruneContributedToPending(ap.inbox, ap.sessionId);
10012
10948
  const messages = ap.notifications.filterUncontributedMessages(ap.inbox, ap.sessionId);
10013
10949
  ap.notifications.clearPending();
@@ -10299,6 +11235,23 @@ var AgentProcessManager = class _AgentProcessManager {
10299
11235
  clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0
10300
11236
  });
10301
11237
  }
11238
+ recordRuntimeDiagnosticActivity(agentId, ap, event) {
11239
+ this.sendToServer({
11240
+ type: "agent:activity",
11241
+ agentId,
11242
+ activity: ap.lastActivity || "online",
11243
+ detail: ap.lastActivityDetail || "",
11244
+ entries: [runtimeDiagnosticTrajectoryEntry(event)],
11245
+ launchId: ap.launchId || void 0,
11246
+ clientSeq: this.nextActivityClientSeq(agentId)
11247
+ });
11248
+ this.recordDaemonTrace("daemon.runtime.diagnostic", {
11249
+ agentId,
11250
+ launchId: ap.launchId || void 0,
11251
+ runtime: ap.config.runtime,
11252
+ ...runtimeDiagnosticTraceAttrs(event)
11253
+ });
11254
+ }
10302
11255
  recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
10303
11256
  const span = this.tracer.startSpan(name, {
10304
11257
  parent: parseTraceparent(parentTraceparent),
@@ -10364,6 +11317,13 @@ var AgentProcessManager = class _AgentProcessManager {
10364
11317
  session_id_present: Boolean(sessionId)
10365
11318
  });
10366
11319
  }
11320
+ initialAgentProcessSessionId(driver, config) {
11321
+ if (driver.requiresSessionInitForDelivery) return null;
11322
+ return config.sessionId || null;
11323
+ }
11324
+ restartSafeSessionId(ap) {
11325
+ return ap.sessionId || (ap.driver.requiresSessionInitForDelivery ? ap.config.sessionId || null : null);
11326
+ }
10367
11327
  sameWakeMessage(left, right) {
10368
11328
  if (!left || !right) return left === right;
10369
11329
  if (left.message_id && right.message_id) return left.message_id === right.message_id;
@@ -10408,16 +11368,18 @@ var AgentProcessManager = class _AgentProcessManager {
10408
11368
  }
10409
11369
  const previousLaunchId = ap.launchId;
10410
11370
  const nextLaunchId = start.launchId || ap.launchId || null;
10411
- const nextSessionId = ap.sessionId || start.config.sessionId || null;
11371
+ const requiresSessionInit = ap.driver.requiresSessionInitForDelivery === true;
11372
+ const nextSessionId = ap.sessionId || (requiresSessionInit ? null : start.config.sessionId || null);
11373
+ const nextConfigSessionId = nextSessionId || (requiresSessionInit ? start.config.sessionId || null : ap.config.sessionId || start.config.sessionId || null);
10412
11374
  ap.launchId = nextLaunchId;
10413
11375
  ap.sessionId = nextSessionId;
10414
11376
  ap.config = {
10415
11377
  ...start.config,
10416
- sessionId: nextSessionId
11378
+ sessionId: nextConfigSessionId
10417
11379
  };
10418
11380
  this.idleAgentConfigs.set(agentId, {
10419
- config: { ...stripManagedRunnerCredential(ap.config), sessionId: nextSessionId },
10420
- sessionId: nextSessionId,
11381
+ config: this.buildRestartSafeConfig(ap.config, nextConfigSessionId),
11382
+ sessionId: nextConfigSessionId,
10421
11383
  launchId: nextLaunchId
10422
11384
  });
10423
11385
  this.recordStartRebind(agentId, start, reason, previousLaunchId, nextLaunchId, nextSessionId);
@@ -10623,9 +11585,53 @@ var AgentProcessManager = class _AgentProcessManager {
10623
11585
  return;
10624
11586
  }
10625
11587
  this.agentsStarting.add(agentId);
10626
- this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient));
10627
11588
  let agentProcess = null;
11589
+ let pendingStartRebind;
11590
+ const originalLaunchId = launchId || null;
10628
11591
  try {
11592
+ const agentDataDir = path13.join(this.dataDir, agentId);
11593
+ await mkdir(agentDataDir, { recursive: true });
11594
+ const initialRuntimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
11595
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
11596
+ try {
11597
+ await access(memoryMdPath);
11598
+ } catch {
11599
+ const initialMemoryMd = buildInitialMemoryMd(initialRuntimeConfig);
11600
+ await writeFile(memoryMdPath, initialMemoryMd);
11601
+ }
11602
+ const notesDir = path13.join(agentDataDir, "notes");
11603
+ await mkdir(notesDir, { recursive: true });
11604
+ if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
11605
+ const seedFiles = buildOnboardingSeedFiles();
11606
+ for (const { relativePath, content } of seedFiles) {
11607
+ const fullPath = path13.join(agentDataDir, relativePath);
11608
+ try {
11609
+ await access(fullPath);
11610
+ } catch {
11611
+ await mkdir(path13.dirname(fullPath), { recursive: true });
11612
+ await writeFile(fullPath, content);
11613
+ }
11614
+ }
11615
+ }
11616
+ pendingStartRebind = this.pendingStartRebinds.get(agentId);
11617
+ if (pendingStartRebind) {
11618
+ this.pendingStartRebinds.delete(agentId);
11619
+ const previousWakeMessage = wakeMessage;
11620
+ if (previousWakeMessage && pendingStartRebind.wakeMessage && !this.sameWakeMessage(previousWakeMessage, pendingStartRebind.wakeMessage)) {
11621
+ const pending = this.startingInboxes.get(agentId) || [];
11622
+ pending.push(previousWakeMessage);
11623
+ this.startingInboxes.set(agentId, pending);
11624
+ }
11625
+ config = pendingStartRebind.config;
11626
+ unreadSummary = pendingStartRebind.unreadSummary;
11627
+ resumePrompt = pendingStartRebind.resumePrompt;
11628
+ launchId = pendingStartRebind.launchId || launchId;
11629
+ if (pendingStartRebind.wakeMessage) {
11630
+ wakeMessage = pendingStartRebind.wakeMessage;
11631
+ wakeMessageTransient = pendingStartRebind.wakeMessageTransient === true;
11632
+ }
11633
+ }
11634
+ this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient));
10629
11635
  const driver = this.driverResolver(config.runtime || "claude");
10630
11636
  const legacyWakeRuntimeProfile = wakeMessage ? runtimeProfileNotificationFromMessage(wakeMessage) : null;
10631
11637
  if (legacyWakeRuntimeProfile?.kind === "migration") {
@@ -10638,8 +11644,6 @@ var AgentProcessManager = class _AgentProcessManager {
10638
11644
  );
10639
11645
  wakeMessage = void 0;
10640
11646
  }
10641
- const agentDataDir = path13.join(this.dataDir, agentId);
10642
- await mkdir(agentDataDir, { recursive: true });
10643
11647
  let runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
10644
11648
  const legacyRuntimeProfileControl = runtimeConfig.runtimeProfileControl?.kind === "migration" ? runtimeConfig.runtimeProfileControl : null;
10645
11649
  if (legacyRuntimeProfileControl) {
@@ -10652,27 +11656,6 @@ var AgentProcessManager = class _AgentProcessManager {
10652
11656
  );
10653
11657
  runtimeConfig = { ...runtimeConfig, runtimeProfileControl: null };
10654
11658
  }
10655
- const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
10656
- try {
10657
- await access(memoryMdPath);
10658
- } catch {
10659
- const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
10660
- await writeFile(memoryMdPath, initialMemoryMd);
10661
- }
10662
- const notesDir = path13.join(agentDataDir, "notes");
10663
- await mkdir(notesDir, { recursive: true });
10664
- if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
10665
- const seedFiles = buildOnboardingSeedFiles();
10666
- for (const { relativePath, content } of seedFiles) {
10667
- const fullPath = path13.join(agentDataDir, relativePath);
10668
- try {
10669
- await access(fullPath);
10670
- } catch {
10671
- await mkdir(path13.dirname(fullPath), { recursive: true });
10672
- await writeFile(fullPath, content);
10673
- }
10674
- }
10675
- }
10676
11659
  const isResume = !!runtimeConfig.sessionId;
10677
11660
  const standingPrompt = driver.buildSystemPrompt(runtimeConfig, agentId);
10678
11661
  let prompt;
@@ -10692,7 +11675,7 @@ var AgentProcessManager = class _AgentProcessManager {
10692
11675
  if (transientWakeMessage) {
10693
11676
  prompt = `System notice received:
10694
11677
 
10695
- ${formatIncomingMessage(wakeMessage)}
11678
+ ${formatIncomingMessage(wakeMessage, incomingMessageFormatOptionsForDriver(driver))}
10696
11679
 
10697
11680
  Respond as appropriate. Complete all your work before stopping.
10698
11681
  ${RESPONSE_TARGET_HINT}`;
@@ -10746,20 +11729,15 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
10746
11729
  sessionIdPresent: isResume,
10747
11730
  nativeStandingPrompt: Boolean(driver.supportsNativeStandingPrompt)
10748
11731
  });
10749
- const effectiveConfig = await this.buildSpawnConfig(agentId, runtimeConfig);
10750
- const pendingStartRebind = this.pendingStartRebinds.get(agentId);
10751
- if (pendingStartRebind) {
10752
- this.pendingStartRebinds.delete(agentId);
10753
- }
10754
- const effectiveLaunchId = pendingStartRebind?.launchId || launchId || null;
10755
- const canDeferEmptyStart = driver.deferSpawnUntilMessage === true && !wakeMessage && !pendingStartRebind?.wakeMessage && !runtimeConfig.runtimeProfileControl && (!unreadSummary || Object.keys(unreadSummary).length === 0);
11732
+ const effectiveLaunchId = launchId || null;
11733
+ const canDeferEmptyStart = driver.deferSpawnUntilMessage === true && !wakeMessage && !runtimeConfig.runtimeProfileControl && (!unreadSummary || Object.keys(unreadSummary).length === 0);
10756
11734
  if (canDeferEmptyStart) {
10757
11735
  const pendingMessages = this.startingInboxes.get(agentId) || [];
10758
11736
  this.startingInboxes.delete(agentId);
10759
11737
  this.agentsStarting.delete(agentId);
10760
11738
  this.idleAgentConfigs.set(agentId, {
10761
- config: effectiveConfig,
10762
- sessionId: effectiveConfig.sessionId || null,
11739
+ config: this.buildRestartSafeConfig(runtimeConfig, runtimeConfig.sessionId || null),
11740
+ sessionId: runtimeConfig.sessionId || null,
10763
11741
  launchId: effectiveLaunchId
10764
11742
  });
10765
11743
  this.sendAgentStatus(agentId, "active", effectiveLaunchId);
@@ -10775,6 +11753,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
10775
11753
  }
10776
11754
  return;
10777
11755
  }
11756
+ const effectiveConfig = await this.buildSpawnConfig(agentId, runtimeConfig);
10778
11757
  const runtimeContext = {
10779
11758
  agentId,
10780
11759
  config: effectiveConfig,
@@ -10789,20 +11768,28 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
10789
11768
  tracer: this.tracer
10790
11769
  };
10791
11770
  const runtime = driver.createSession?.(runtimeContext) ?? createChildProcessRuntimeSession(driver, runtimeContext);
11771
+ const liveProcessConfig = {
11772
+ ...runtimeConfig,
11773
+ serverUrl: effectiveConfig.serverUrl,
11774
+ agentCredentialKey: effectiveConfig.agentCredentialKey,
11775
+ agentCredentialId: effectiveConfig.agentCredentialId
11776
+ };
11777
+ const initialSessionId = this.initialAgentProcessSessionId(driver, liveProcessConfig);
11778
+ const restartSessionId = initialSessionId || (driver.requiresSessionInitForDelivery ? liveProcessConfig.sessionId || null : null);
10792
11779
  agentProcess = {
10793
11780
  runtime,
10794
11781
  driver,
10795
11782
  inbox: wakeMessageDeliveredAsInboxUpdate && wakeMessage ? [wakeMessage, ...startingInboxMessages] : startingInboxMessages,
10796
- config: runtimeConfig,
10797
- sessionId: runtimeConfig.sessionId || null,
11783
+ config: liveProcessConfig,
11784
+ sessionId: initialSessionId,
10798
11785
  launchId: effectiveLaunchId,
10799
11786
  startupWakeMessage: wakeMessage,
10800
11787
  startupUnreadSummary: unreadSummary,
10801
11788
  startupResumePrompt: resumePrompt,
10802
- isIdle: false,
10803
11789
  notifications: new RuntimeNotificationState(),
10804
11790
  activityHeartbeat: null,
10805
11791
  startupTimeoutTimer: null,
11792
+ startupReadinessSatisfied: false,
10806
11793
  compactionWatchdog: null,
10807
11794
  compactionStartedAt: null,
10808
11795
  runtimeProgress: new RuntimeProgressState(Date.now()),
@@ -10820,21 +11807,20 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
10820
11807
  spawnError: null,
10821
11808
  exitCode: null,
10822
11809
  exitSignal: null,
10823
- expectedTerminationReason: null,
10824
11810
  stalledRecoverySigtermTimer: null,
10825
- runtimeProfileTurnControl: runtimeConfig.runtimeProfileControl ? runtimeProfileTurnControl(runtimeConfig.runtimeProfileControl.kind, runtimeConfig.runtimeProfileControl.key, "agent_config") : null,
11811
+ runtimeProfileTurnControl: liveProcessConfig.runtimeProfileControl ? runtimeProfileTurnControl(liveProcessConfig.runtimeProfileControl.kind, liveProcessConfig.runtimeProfileControl.key, "agent_config") : null,
10826
11812
  pendingTrajectory: null,
10827
11813
  gatedSteering: createGatedSteeringState()
10828
11814
  };
10829
11815
  this.startingInboxes.delete(agentId);
10830
11816
  this.agents.set(agentId, agentProcess);
10831
11817
  this.idleAgentConfigs.set(agentId, {
10832
- config: { ...effectiveConfig, sessionId: effectiveConfig.sessionId || null },
10833
- sessionId: effectiveConfig.sessionId || null,
11818
+ config: this.buildRestartSafeConfig(runtimeConfig, restartSessionId),
11819
+ sessionId: restartSessionId,
10834
11820
  launchId: effectiveLaunchId
10835
11821
  });
10836
11822
  if (pendingStartRebind) {
10837
- this.recordStartRebind(agentId, pendingStartRebind, "startup_registered", launchId || null, effectiveLaunchId, agentProcess.sessionId);
11823
+ this.recordStartRebind(agentId, pendingStartRebind, "startup_registered", originalLaunchId, effectiveLaunchId, agentProcess.sessionId);
10838
11824
  }
10839
11825
  this.startRuntimeTrace(agentId, agentProcess, "spawn", wakeMessage ? [wakeMessage] : void 0, runtimeInputTraceAttrs);
10840
11826
  this.agentsStarting.delete(agentId);
@@ -10848,6 +11834,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
10848
11834
  this.ackInjectedRuntimeProfileMessages(agentId, [wakeMessage], agentProcess.launchId);
10849
11835
  }
10850
11836
  runtime.on("stdout", (chunkText) => {
11837
+ if (runtime.descriptor.stdout.channel === "structured_protocol") return;
10851
11838
  const current = this.agents.get(agentId);
10852
11839
  if (current) {
10853
11840
  current.recentStdout = pushRecentStdout(current.recentStdout, chunkText);
@@ -10935,17 +11922,24 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
10935
11922
  this.clearStalledRecoverySigtermWatchdog(ap);
10936
11923
  const finalCode = ap.exitCode ?? code;
10937
11924
  const finalSignal = ap.exitSignal ?? signal;
10938
- const startupTimeoutTermination = ap.expectedTerminationReason === "startup_timeout";
10939
- const expectedTermination = Boolean(ap.expectedTerminationReason);
11925
+ const expectedTerminationReason = ap.gatedSteering.expectedTerminationReason;
11926
+ const startupTimeoutTermination = expectedTerminationReason === "startup_timeout";
11927
+ const startupRequestErrorTermination = expectedTerminationReason === "startup_request_error";
11928
+ const expectedTermination = Boolean(expectedTerminationReason);
10940
11929
  const stickyTerminalFailureDetail = classifyStickyTerminalFailure(ap);
10941
- const processEndedCleanly = !stickyTerminalFailureDetail && !startupTimeoutTermination && (finalCode === 0 || expectedTermination && !ap.lastRuntimeError);
11930
+ const turnBoundarySatisfied = closeSatisfiedTurnBoundary(ap, expectedTermination);
11931
+ const closeBeforeTurnBoundary = !turnBoundarySatisfied;
11932
+ const processEndedCleanly = !stickyTerminalFailureDetail && !startupTimeoutTermination && !startupRequestErrorTermination && (finalCode === 0 && turnBoundarySatisfied || expectedTermination && !ap.lastRuntimeError);
10942
11933
  const terminalFailureDetail = processEndedCleanly ? null : stickyTerminalFailureDetail ?? classifyTerminalFailure(ap);
10943
11934
  const resumeRecoveryReason = resumeSessionRecoveryReason(ap);
10944
11935
  const shouldColdStartResumeSession = resumeRecoveryReason !== null;
10945
11936
  const summary = summarizeCrash(finalCode, finalSignal);
10946
11937
  this.endRuntimeTrace(ap, processEndedCleanly ? "ok" : "error", {
10947
11938
  outcome: processEndedCleanly ? "process-exit" : "process-crash",
10948
- expectedTerminationReason: ap.expectedTerminationReason || void 0,
11939
+ expectedTerminationReason: expectedTerminationReason || void 0,
11940
+ runtime_turn_boundary: ap.runtime.descriptor.turnBoundary,
11941
+ runtime_turn_boundary_satisfied: turnBoundarySatisfied,
11942
+ runtime_exit_before_turn_boundary: closeBeforeTurnBoundary || void 0,
10949
11943
  exitCode: finalCode,
10950
11944
  exitSignal: finalSignal,
10951
11945
  ...runtimeTraceCounterAttrs(ap),
@@ -10958,7 +11952,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
10958
11952
  if (shouldColdStartResumeSession) {
10959
11953
  const staleSessionId = ap.sessionId;
10960
11954
  const runtimeLabel = resumeSessionRuntimeLabel(ap.driver.id);
10961
- const restartConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: null };
11955
+ const restartConfig = this.buildRestartSafeConfig(ap.config, null);
10962
11956
  const reasonText = resumeRecoveryReason === "provider_replay_rejected" ? "was rejected by the provider during replay" : "is unavailable locally";
10963
11957
  const activityText = resumeRecoveryReason === "provider_replay_rejected" ? `Stored ${runtimeLabel} session replay rejected; cold-starting a new session\u2026` : `Stored ${runtimeLabel} session missing; cold-starting a new session\u2026`;
10964
11958
  logger.warn(
@@ -10989,7 +11983,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
10989
11983
  }
10990
11984
  if (processEndedCleanly) {
10991
11985
  let queuedWakeMessage;
10992
- if (!ap.driver.supportsStdinNotification || ap.expectedTerminationReason === "stalled_recovery") {
11986
+ if (!ap.driver.supportsStdinNotification || expectedTerminationReason === "stalled_recovery") {
10993
11987
  while (ap.inbox.length > 0) {
10994
11988
  const candidate = ap.inbox.shift();
10995
11989
  if (this.shouldDeferWakeMessage(agentId, ap.driver, candidate)) continue;
@@ -11000,7 +11994,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11000
11994
  const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
11001
11995
  if (queuedWakeMessage) {
11002
11996
  logger.info(`[Agent ${agentId}] Turn completed; restarting immediately for queued message`);
11003
- const nextConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId };
11997
+ const nextConfig = this.buildRestartSafeConfig(ap.config, ap.sessionId);
11004
11998
  this.idleAgentConfigs.set(agentId, {
11005
11999
  config: nextConfig,
11006
12000
  sessionId: ap.sessionId,
@@ -11023,7 +12017,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11023
12017
  return;
11024
12018
  }
11025
12019
  this.idleAgentConfigs.set(agentId, {
11026
- config: { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId },
12020
+ config: this.buildRestartSafeConfig(ap.config, ap.sessionId),
11027
12021
  sessionId: ap.sessionId,
11028
12022
  launchId: ap.launchId
11029
12023
  });
@@ -11035,7 +12029,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11035
12029
  const reason = formatCrashReason(finalCode, finalSignal, ap);
11036
12030
  if (terminalFailureDetail && isProviderStreamFailureText(terminalFailureDetail.detail)) {
11037
12031
  this.idleAgentConfigs.set(agentId, {
11038
- config: { ...ap.config, sessionId: ap.sessionId },
12032
+ config: this.buildRestartSafeConfig(ap.config, ap.sessionId),
11039
12033
  sessionId: ap.sessionId,
11040
12034
  launchId: ap.launchId
11041
12035
  });
@@ -11044,13 +12038,16 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11044
12038
  } else if (startupTimeoutTermination) {
11045
12039
  this.cacheStartupTimeoutRetryConfig(agentId, ap);
11046
12040
  logger.warn(`[Agent ${agentId}] Startup timeout cleanup completed (${reason})`);
12041
+ } else if (startupRequestErrorTermination) {
12042
+ this.idleAgentConfigs.delete(agentId);
12043
+ logger.warn(`[Agent ${agentId}] Startup request failure cleanup completed (${reason})`);
11047
12044
  } else {
11048
12045
  this.idleAgentConfigs.delete(agentId);
11049
12046
  logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
11050
12047
  this.sendAgentStatus(agentId, "inactive", ap.launchId);
11051
12048
  }
11052
12049
  if (terminalFailureDetail) {
11053
- if (!startupTimeoutTermination) {
12050
+ if (!startupTimeoutTermination && !startupRequestErrorTermination) {
11054
12051
  this.broadcastActivity(
11055
12052
  agentId,
11056
12053
  "error",
@@ -11059,7 +12056,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11059
12056
  ap.launchId
11060
12057
  );
11061
12058
  }
11062
- } else {
12059
+ } else if (!startupRequestErrorTermination) {
11063
12060
  this.broadcastActivity(agentId, "offline", `Crashed (${summary})`, [], ap.launchId);
11064
12061
  }
11065
12062
  }
@@ -11093,12 +12090,6 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11093
12090
  if (pendingStartRebind && agentProcess.sessionId) {
11094
12091
  this.sendToServer({ type: "agent:session", agentId, sessionId: agentProcess.sessionId, launchId: agentProcess.launchId || void 0 });
11095
12092
  }
11096
- if (pendingStartRebind?.wakeMessage) {
11097
- const accepted = this.deliverMessage(agentId, pendingStartRebind.wakeMessage, { transient: pendingStartRebind.wakeMessageTransient === true });
11098
- if (accepted instanceof Promise) {
11099
- accepted.catch((err) => logger.error(`[Agent ${agentId}] Failed to deliver wake message after startup rebind`, err));
11100
- }
11101
- }
11102
12093
  this.broadcastActivity(agentId, "working", "Starting\u2026");
11103
12094
  this.startRuntimeStartupTimeout(agentId, agentProcess);
11104
12095
  } catch (err) {
@@ -11184,13 +12175,11 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11184
12175
  });
11185
12176
  }
11186
12177
  cacheStartupTimeoutRetryConfig(agentId, ap) {
11187
- const retryConfig = {
11188
- ...stripManagedRunnerCredential(ap.config),
11189
- sessionId: ap.sessionId
11190
- };
12178
+ const retrySessionId = this.restartSafeSessionId(ap);
12179
+ const retryConfig = this.buildRestartSafeConfig(ap.config, retrySessionId);
11191
12180
  this.idleAgentConfigs.set(agentId, {
11192
12181
  config: retryConfig,
11193
- sessionId: ap.sessionId,
12182
+ sessionId: retrySessionId,
11194
12183
  launchId: ap.launchId
11195
12184
  });
11196
12185
  this.recordDaemonTrace("daemon.agent.startup_timeout.retry_config_cached", {
@@ -11200,6 +12189,14 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11200
12189
  session_id_present: Boolean(ap.sessionId)
11201
12190
  });
11202
12191
  }
12192
+ buildRestartSafeConfig(config, sessionId) {
12193
+ const stripped = stripManagedRunnerCredential(config);
12194
+ return {
12195
+ ...stripped,
12196
+ serverUrl: this.serverUrl,
12197
+ sessionId
12198
+ };
12199
+ }
11203
12200
  async buildSpawnConfig(agentId, config) {
11204
12201
  const baseConfig = config.serverUrl === this.serverUrl ? config : { ...config, serverUrl: this.serverUrl };
11205
12202
  const runnerConfig = await this.ensureManagedRunnerCredential(agentId, baseConfig);
@@ -11647,6 +12644,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11647
12644
  this.broadcastActivity(agentId, "offline", "Process unavailable; restart required");
11648
12645
  return false;
11649
12646
  }
12647
+ const isIdle = this.isApmIdle(ap);
11650
12648
  if (!transientDelivery && this.shouldDeferWakeMessage(agentId, ap.driver, message)) {
11651
12649
  this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
11652
12650
  outcome: "deferred_wake_message",
@@ -11655,7 +12653,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11655
12653
  runtime: ap.config.runtime,
11656
12654
  session_id_present: Boolean(ap.sessionId),
11657
12655
  launchId: ap.launchId || void 0,
11658
- is_idle: ap.isIdle,
12656
+ is_idle: isIdle,
11659
12657
  inbox_count: ap.inbox.length
11660
12658
  }));
11661
12659
  return true;
@@ -11670,7 +12668,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11670
12668
  runtime: ap.config.runtime,
11671
12669
  session_id_present: Boolean(ap.sessionId),
11672
12670
  launchId: ap.launchId || void 0,
11673
- is_idle: ap.isIdle,
12671
+ is_idle: isIdle,
11674
12672
  inbox_count: ap.inbox.length
11675
12673
  }));
11676
12674
  return true;
@@ -11687,7 +12685,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11687
12685
  runtime: ap.config.runtime,
11688
12686
  session_id_present: Boolean(ap.sessionId),
11689
12687
  launchId: ap.launchId || void 0,
11690
- is_idle: ap.isIdle,
12688
+ is_idle: isIdle,
11691
12689
  inbox_count: ap.inbox.length
11692
12690
  }));
11693
12691
  } else {
@@ -11699,7 +12697,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11699
12697
  runtime: ap.config.runtime,
11700
12698
  session_id_present: Boolean(ap.sessionId),
11701
12699
  launchId: ap.launchId || void 0,
11702
- is_idle: ap.isIdle,
12700
+ is_idle: isIdle,
11703
12701
  inbox_count: ap.inbox.length
11704
12702
  }));
11705
12703
  this.sendAgentStatus(agentId, "inactive", ap.launchId);
@@ -11710,7 +12708,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11710
12708
  if (!transientDelivery && this.queueDeliveryForRuntimeErrorBackoff(agentId, ap, message)) {
11711
12709
  return true;
11712
12710
  }
11713
- if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
12711
+ if (isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
11714
12712
  if (transientDelivery) {
11715
12713
  this.commitApmIdleState(agentId, ap, false);
11716
12714
  this.startRuntimeTrace(agentId, ap, "stdin-idle-delivery", [message]);
@@ -11747,7 +12745,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11747
12745
  runtime: ap.config.runtime,
11748
12746
  session_id_present: true,
11749
12747
  launchId: ap.launchId || void 0,
11750
- is_idle: ap.isIdle,
12748
+ is_idle: isIdle,
11751
12749
  inbox_count: ap.inbox.length,
11752
12750
  pending_notification_count: ap.notifications.pendingCount
11753
12751
  }));
@@ -11786,7 +12784,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11786
12784
  runtime: ap.config.runtime,
11787
12785
  session_id_present: Boolean(ap.sessionId),
11788
12786
  launchId: ap.launchId || void 0,
11789
- is_idle: ap.isIdle,
12787
+ is_idle: isIdle,
11790
12788
  inbox_count: ap.inbox.length
11791
12789
  }));
11792
12790
  return true;
@@ -11856,6 +12854,34 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11856
12854
  }));
11857
12855
  return true;
11858
12856
  }
12857
+ if (ap.gatedSteering.reviewing) {
12858
+ ap.notifications.add();
12859
+ ap.notifications.clearTimer();
12860
+ if (ap.runtime.descriptor.busyDelivery === "gated") {
12861
+ this.recordGatedSteeringEvent(agentId, ap, "buffer", {
12862
+ reason: "review_boundary",
12863
+ pendingMessages: ap.inbox.length
12864
+ });
12865
+ }
12866
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.review_boundary.delivery_buffered", {
12867
+ pendingNotificationCount: ap.notifications.pendingCount,
12868
+ pendingMessages: ap.inbox.length,
12869
+ busyDeliveryMode: ap.driver.busyDeliveryMode
12870
+ });
12871
+ this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
12872
+ outcome: "queued_review_boundary",
12873
+ accepted: true,
12874
+ process_present: true,
12875
+ runtime: ap.config.runtime,
12876
+ session_id_present: true,
12877
+ launchId: ap.launchId || void 0,
12878
+ inbox_count: ap.inbox.length,
12879
+ pending_notification_count: ap.notifications.pendingCount,
12880
+ busy_delivery_mode: ap.driver.busyDeliveryMode,
12881
+ notification_timer_present: false
12882
+ }));
12883
+ return true;
12884
+ }
11859
12885
  if (ap.runtime.descriptor.busyDelivery === "gated") {
11860
12886
  ap.notifications.add();
11861
12887
  if (!ap.notifications.hasTimer) {
@@ -11971,7 +12997,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
11971
12997
  path: workspacePath,
11972
12998
  reachable: true
11973
12999
  },
11974
- sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, runtimeHomeDir, workspacePath) : null
13000
+ sessionRef: sessionId ? resolveRuntimeSessionRef(config.runtime, sessionId, runtimeHomeDir, workspacePath, { agentId, workingDirectory: workspacePath }) : null
11975
13001
  }
11976
13002
  };
11977
13003
  }
@@ -12040,7 +13066,8 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
12040
13066
  traceparent: formatTraceparent(span.context)
12041
13067
  };
12042
13068
  const ap = this.agents.get(agentId);
12043
- if (ap && !(ap.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) && !(ap.sessionId && ap.runtime.descriptor.busyDelivery === "direct")) {
13069
+ const isIdle = ap ? this.isApmIdle(ap) : false;
13070
+ if (ap && !(ap.sessionId && ap.driver.supportsStdinNotification && isIdle) && !(ap.sessionId && ap.runtime.descriptor.busyDelivery === "direct")) {
12044
13071
  this.enqueueRuntimeProfileNotification(agentId, ap, message, kind, key);
12045
13072
  span.end("ok", {
12046
13073
  attrs: {
@@ -12054,7 +13081,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
12054
13081
  });
12055
13082
  return true;
12056
13083
  }
12057
- if (ap?.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) {
13084
+ if (ap?.sessionId && ap.driver.supportsStdinNotification && isIdle) {
12058
13085
  this.commitApmIdleState(agentId, ap, false);
12059
13086
  this.startRuntimeTrace(agentId, ap, "runtime-profile", [message]);
12060
13087
  const written = this.deliverMessagesViaStdin(agentId, ap, [message], "idle");
@@ -12305,6 +13332,183 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
12305
13332
  workspace: [".codex/skills", ".agents/skills"]
12306
13333
  }
12307
13334
  };
13335
+ async getSessionTranscript(agentId) {
13336
+ const agent = this.agents.get(agentId);
13337
+ const idle = this.idleAgentConfigs.get(agentId);
13338
+ const config = agent?.config ?? idle?.config ?? null;
13339
+ const actualSessionId = agent?.sessionId || idle?.sessionId || null;
13340
+ if (!config) {
13341
+ return {
13342
+ runtime: "unknown",
13343
+ sessionId: actualSessionId || "unknown",
13344
+ reachable: false,
13345
+ path: null,
13346
+ fallbackReason: "agent not found or no config",
13347
+ transcript: null,
13348
+ sizeBytes: 0,
13349
+ truncated: false,
13350
+ redacted: false,
13351
+ tier: "unknown"
13352
+ };
13353
+ }
13354
+ const runtime = config.runtime;
13355
+ const workspaceDir = path13.join(this.dataDir, agentId);
13356
+ const homeDir = ensureRuntimeHomeDir(config, this.runtimeSessionHomeDir, workspaceDir);
13357
+ if (!actualSessionId) {
13358
+ return {
13359
+ runtime,
13360
+ sessionId: "unknown",
13361
+ reachable: false,
13362
+ path: null,
13363
+ fallbackReason: "no session id available",
13364
+ transcript: null,
13365
+ sizeBytes: 0,
13366
+ truncated: false,
13367
+ redacted: false,
13368
+ tier: runtimeTier(runtime)
13369
+ };
13370
+ }
13371
+ const ref = resolveRuntimeSessionRef(runtime, actualSessionId, homeDir, workspaceDir, {
13372
+ agentId,
13373
+ workingDirectory: workspaceDir
13374
+ });
13375
+ const tier = runtimeTier(runtime);
13376
+ const span = this.tracer.startSpan("daemon.session_transcript.read", {
13377
+ surface: "daemon",
13378
+ kind: "internal",
13379
+ attrs: {
13380
+ agentId,
13381
+ runtime,
13382
+ sessionId: actualSessionId,
13383
+ reachable: ref.reachable ?? false,
13384
+ tier,
13385
+ lookup_method: ref.reason?.includes("attempted_lookup=") ? ref.reason.split("attempted_lookup=")[1]?.split(";")[0] : "unknown"
13386
+ }
13387
+ });
13388
+ let transcript = null;
13389
+ let sizeBytes = 0;
13390
+ let truncated = false;
13391
+ let redacted = false;
13392
+ if (ref.reachable && ref.path) {
13393
+ try {
13394
+ const resolved = path13.resolve(ref.path);
13395
+ const allowedRoots = allowedTranscriptRootsForRuntime(runtime, homeDir, workspaceDir);
13396
+ if (!await isPathWithinAllowedRoots(resolved, allowedRoots)) {
13397
+ throw new Error("resolved session path is outside allowed runtime directories");
13398
+ }
13399
+ let targetPath = resolved;
13400
+ const info = await lstat(resolved);
13401
+ if (info.isSymbolicLink()) {
13402
+ throw new Error("symbolic links are not allowed");
13403
+ }
13404
+ if (info.isDirectory()) {
13405
+ targetPath = path13.join(resolved, "state.json");
13406
+ }
13407
+ if (!await isPathWithinAllowedRoots(targetPath, allowedRoots)) {
13408
+ throw new Error("resolved session state path is outside allowed runtime directories");
13409
+ }
13410
+ const redactedResult = await readAndRedact(targetPath, SESSION_TRANSCRIPT_MAX_BYTES);
13411
+ if (redactedResult !== null) {
13412
+ transcript = redactedResult.text;
13413
+ sizeBytes = Buffer.byteLength(transcript, "utf8");
13414
+ redacted = true;
13415
+ truncated = redactedResult.truncated;
13416
+ }
13417
+ span.end("ok", { attrs: { transcript_present: Boolean(transcript), size_bytes: sizeBytes, truncated, redacted } });
13418
+ } catch (err) {
13419
+ span.end("error", { attrs: { error_class: err instanceof Error ? err.name : "Error" } });
13420
+ }
13421
+ } else {
13422
+ span.end("ok", { attrs: { transcript_present: false, reason: ref.reason } });
13423
+ }
13424
+ return {
13425
+ runtime,
13426
+ sessionId: actualSessionId,
13427
+ reachable: ref.reachable ?? false,
13428
+ path: ref.path ?? null,
13429
+ fallbackReason: ref.reason ?? void 0,
13430
+ transcript,
13431
+ sizeBytes,
13432
+ truncated,
13433
+ redacted,
13434
+ tier
13435
+ };
13436
+ }
13437
+ /**
13438
+ * Collect the agent's current session transcript and upload it as a trace
13439
+ * bundle linked to a feedback report. The transcript is read using only the
13440
+ * sessionId bound to the agent; no caller-supplied session id is accepted.
13441
+ */
13442
+ async collectFeedbackTranscript(agentId, feedbackReportId) {
13443
+ const transcriptResult = await this.getSessionTranscript(agentId);
13444
+ if (!transcriptResult.reachable || !transcriptResult.transcript) {
13445
+ return {
13446
+ reachable: false,
13447
+ fallbackReason: transcriptResult.fallbackReason ?? "transcript not reachable"
13448
+ };
13449
+ }
13450
+ if (!this.workerUrl) {
13451
+ return {
13452
+ reachable: true,
13453
+ fallbackReason: "daemon worker URL is not configured"
13454
+ };
13455
+ }
13456
+ const span = this.tracer.startSpan("daemon.feedback_transcript.upload", {
13457
+ surface: "daemon",
13458
+ kind: "producer",
13459
+ attrs: {
13460
+ agentId,
13461
+ feedbackReportId,
13462
+ runtime: transcriptResult.runtime,
13463
+ sessionId: transcriptResult.sessionId,
13464
+ transcript_size_bytes: transcriptResult.sizeBytes
13465
+ }
13466
+ });
13467
+ try {
13468
+ const raw = Buffer.from(transcriptResult.transcript, "utf8");
13469
+ const gzipped = gzipSync(raw);
13470
+ const bundleSha256 = createHash3("sha256").update(gzipped).digest("hex");
13471
+ const bundleSizeBytes = gzipped.byteLength;
13472
+ const bundleId = randomUUID5();
13473
+ const uploadResult = await uploadWithSignedCapability({
13474
+ serverUrl: this.serverUrl,
13475
+ apiKey: this.daemonApiKey,
13476
+ workerUrl: this.workerUrl,
13477
+ scope: "daemon-trace-bundle:create",
13478
+ createPath: "/api/trace-bundles",
13479
+ createBody: {
13480
+ bundleSha256,
13481
+ bundleSizeBytes
13482
+ },
13483
+ attestationMetadata: {
13484
+ bundleId,
13485
+ bundleSha256,
13486
+ bundleSizeBytes,
13487
+ bundleContentType: "application/json",
13488
+ bundleContentEncoding: "gzip",
13489
+ feedbackReportId,
13490
+ agentId
13491
+ },
13492
+ uploadBody: new Blob([new Uint8Array(gzipped)], { type: "application/json" }),
13493
+ fetchImpl: this.fetchImpl
13494
+ });
13495
+ const traceBundleId = typeof uploadResult.session.id === "string" ? uploadResult.session.id : bundleId;
13496
+ logger.info(`[FeedbackTranscript] uploaded for report=${feedbackReportId} agent=${agentId} traceBundleId=${traceBundleId} size=${bundleSizeBytes}`);
13497
+ span.end("ok", { attrs: { traceBundleId, bundleSizeBytes } });
13498
+ return {
13499
+ reachable: true,
13500
+ traceBundleId
13501
+ };
13502
+ } catch (err) {
13503
+ const message = err instanceof Error ? err.message : String(err);
13504
+ logger.warn(`[FeedbackTranscript] upload failed for report=${feedbackReportId} agent=${agentId}: ${message}`);
13505
+ span.end("error", { attrs: { error_class: err instanceof Error ? err.name : "Error", error_message: message } });
13506
+ return {
13507
+ reachable: true,
13508
+ error: message
13509
+ };
13510
+ }
13511
+ }
12308
13512
  async listSkills(agentId, runtimeHint) {
12309
13513
  const agent = this.agents.get(agentId);
12310
13514
  const idle = this.idleAgentConfigs.get(agentId);
@@ -12415,6 +13619,17 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
12415
13619
  launchId: launchIdOverride || ap?.launchId || void 0,
12416
13620
  clientSeq: ap ? this.nextActivityClientSeq(agentId) : void 0
12417
13621
  });
13622
+ this.recordDaemonTrace("daemon.agent.activity.produced", {
13623
+ agentId,
13624
+ activity,
13625
+ detail_present: Boolean(detail),
13626
+ entry_kinds: entries.map((e) => e.kind).join(","),
13627
+ ap_present: Boolean(ap),
13628
+ launch_id_present: Boolean(launchIdOverride || ap?.launchId),
13629
+ client_seq_present: ap ? true : false,
13630
+ session_id_present: Boolean(ap?.sessionId),
13631
+ runtime: ap?.config.runtime
13632
+ });
12418
13633
  if (ap) {
12419
13634
  ap.lastActivity = activity;
12420
13635
  ap.lastActivityDetail = detail;
@@ -12494,7 +13709,15 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
12494
13709
  }
12495
13710
  queueTrajectoryText(agentId, kind, text) {
12496
13711
  const ap = this.agents.get(agentId);
12497
- if (!ap) return;
13712
+ if (!ap) {
13713
+ this.recordDaemonTrace("daemon.agent.activity.skipped", {
13714
+ agentId,
13715
+ event_kind: kind,
13716
+ reason: "agent_process_missing",
13717
+ text_length: text.length
13718
+ });
13719
+ return;
13720
+ }
12498
13721
  if (!text) {
12499
13722
  this.broadcastActivity(agentId, "thinking", "");
12500
13723
  return;
@@ -12541,7 +13764,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
12541
13764
  ap.stalledRecoverySigtermTimer = setTimeout(() => {
12542
13765
  ap.stalledRecoverySigtermTimer = null;
12543
13766
  const current = this.agents.get(agentId);
12544
- if (!current || current !== ap || current.runtime !== runtimeAtSignal || current.expectedTerminationReason !== "stalled_recovery") {
13767
+ if (!current || current !== ap || current.runtime !== runtimeAtSignal || current.gatedSteering.expectedTerminationReason !== "stalled_recovery") {
12545
13768
  return;
12546
13769
  }
12547
13770
  this.mergeRuntimeExitTraceAttrs(runtimeAtSignal, {
@@ -12617,6 +13840,25 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
12617
13840
  const reduction = reduceApmGatedCompaction(ap.gatedSteering, { kind: "compaction_interrupted" });
12618
13841
  this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState);
12619
13842
  }
13843
+ flushReviewBoundaryMessages(agentId, ap) {
13844
+ const reduction = reduceApmGatedReviewBoundaryFlush(ap.gatedSteering, {
13845
+ hasSession: Boolean(ap.sessionId),
13846
+ supportsStdinNotification: ap.driver.supportsStdinNotification,
13847
+ inboxLength: ap.inbox.length,
13848
+ pendingNotificationCount: ap.notifications.pendingCount
13849
+ });
13850
+ for (const effect of reduction.effects) {
13851
+ this.executeApmGatedSteeringEffect(agentId, ap, effect);
13852
+ }
13853
+ }
13854
+ interruptReviewIfActive(agentId) {
13855
+ const ap = this.agents.get(agentId);
13856
+ if (!ap?.gatedSteering.reviewing) return;
13857
+ const reduction = reduceApmGatedReview(ap.gatedSteering, { kind: "review_finished" });
13858
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, {
13859
+ event: "review_interrupted"
13860
+ });
13861
+ }
12620
13862
  messagesTraceAttrs(messages) {
12621
13863
  if (!messages || messages.length === 0) return {};
12622
13864
  const first = messages[0];
@@ -12768,28 +14010,20 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
12768
14010
  phase: ap.gatedSteering.phase,
12769
14011
  outstandingToolUses: ap.gatedSteering.outstandingToolUses,
12770
14012
  compacting: ap.gatedSteering.compacting,
14013
+ reviewing: ap.gatedSteering.reviewing === true ? true : void 0,
12771
14014
  toolBoundaryFlushDisabled: ap.gatedSteering.toolBoundaryFlushDisabled,
12772
14015
  pendingMessages: ap.inbox.length,
12773
14016
  ...attrs
12774
14017
  });
12775
14018
  }
12776
14019
  commitGatedSteeringDecisionState(agentId, ap, nextState, phaseEventAttrs) {
12777
- ap.gatedSteering.phase = nextState.phase;
12778
- ap.gatedSteering.outstandingToolUses = nextState.outstandingToolUses;
12779
- ap.gatedSteering.compacting = nextState.compacting;
12780
- ap.gatedSteering.toolBoundaryFlushDisabled = nextState.toolBoundaryFlushDisabled;
12781
- ap.gatedSteering.lastFlushReason = nextState.lastFlushReason;
12782
- ap.gatedSteering.recentEvents = nextState.recentEvents;
12783
- ap.gatedSteering.isIdle = nextState.isIdle;
12784
- ap.gatedSteering.expectedTerminationReason = nextState.expectedTerminationReason;
12785
- ap.isIdle = nextState.isIdle;
12786
- ap.expectedTerminationReason = nextState.expectedTerminationReason;
14020
+ ap.gatedSteering = commitApmGatedSteeringDecisionState(nextState);
12787
14021
  if (phaseEventAttrs) {
12788
14022
  this.recordGatedSteeringEvent(agentId, ap, "phase", phaseEventAttrs);
12789
14023
  }
12790
14024
  }
12791
- commitApmIdleState(agentId, ap, isIdle) {
12792
- const reduction = reduceApmIdleState(ap.gatedSteering, { isIdle });
14025
+ commitApmIdleState(agentId, ap, nextIsIdle) {
14026
+ const reduction = reduceApmIdleState(ap.gatedSteering, { isIdle: nextIsIdle });
12793
14027
  this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState);
12794
14028
  }
12795
14029
  notifyGatedSteeringBoundary(agentId, ap, reason) {
@@ -12932,11 +14166,17 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
12932
14166
  clearTimeout(ap.startupTimeoutTimer);
12933
14167
  ap.startupTimeoutTimer = null;
12934
14168
  }
14169
+ runtimeStartupReadinessSatisfiedByEvent(ap, event) {
14170
+ if ((ap.driver.startupReadiness ?? "first_event") !== "initial_turn") {
14171
+ return true;
14172
+ }
14173
+ return event.kind !== "session_init" && event.kind !== "internal_progress" && event.kind !== "runtime_diagnostic";
14174
+ }
12935
14175
  handleRuntimeStartupTimeout(agentId, ap, timeoutMs) {
12936
14176
  const current = this.agents.get(agentId);
12937
14177
  if (current !== ap) return;
12938
14178
  const reduction = reduceApmStartupTimeoutTermination(ap.gatedSteering, {
12939
- hasRuntimeProgressEvent: Boolean(ap.runtimeProgress.lastEventKind)
14179
+ hasRuntimeProgressEvent: ap.startupReadinessSatisfied
12940
14180
  });
12941
14181
  if (!reduction.shouldTerminate) {
12942
14182
  this.clearRuntimeStartupTimeout(ap);
@@ -12951,37 +14191,86 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
12951
14191
  ap.runtimeProgress.markStale();
12952
14192
  const staleForMs = Math.max(timeoutMs, ap.runtimeProgress.ageMs());
12953
14193
  const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, Math.max(1, Math.floor(staleForMs / 6e4)));
12954
- this.recordRuntimeTraceEvent(agentId, ap, "runtime.start.timeout", {
12955
- turn_outcome: "failed",
12956
- turn_subtype: "runtime_stalled",
12957
- turn_reason: "no_runtime_events",
12958
- runtime_start_failure_kind: "runtime_start_timeout",
12959
- timeout_ms: timeoutMs,
14194
+ const projection = projectApmRuntimeTerminationTrace({
14195
+ reason: "startup_timeout",
14196
+ timeoutMs
14197
+ });
14198
+ this.recordRuntimeTraceEvent(agentId, ap, projection.runtimeEventName, {
14199
+ ...projection.runtimeEventAttrs,
12960
14200
  ...diagnostic.traceAttrs
12961
14201
  });
12962
14202
  this.endRuntimeTrace(ap, "error", {
12963
- turn_outcome: "failed",
12964
- turn_subtype: "runtime_stalled",
12965
- turn_reason: "no_runtime_events",
12966
- runtime_start_failure_kind: "runtime_start_timeout",
12967
- timeout_ms: timeoutMs,
14203
+ ...projection.runtimeSpanAttrs,
12968
14204
  ...runtimeTraceCounterAttrs(ap),
12969
14205
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
12970
14206
  });
12971
- logger.warn(`[Agent ${agentId}] ${ap.driver.id} did not emit a startup runtime event within ${timeoutMs}ms; terminating process`);
14207
+ logger.warn(`[Agent ${agentId}] ${ap.driver.id} did not reach startup readiness within ${timeoutMs}ms; terminating process`);
12972
14208
  this.broadcastActivity(agentId, "error", detail, [{ kind: "text", text: `Error: ${detail}` }], ap.launchId);
12973
14209
  this.sendAgentStatus(agentId, "inactive", ap.launchId);
12974
14210
  this.cacheStartupTimeoutRetryConfig(agentId, ap);
14211
+ try {
14212
+ this.runtimeExitTraceAttrs.set(ap.runtime, projection.processExitAttrs);
14213
+ void ap.runtime.stop({ signal: "SIGTERM", reason: projection.runtimeStopReason });
14214
+ } catch (err) {
14215
+ const reason = err instanceof Error ? err.message : String(err);
14216
+ logger.warn(`[Agent ${agentId}] Failed to terminate startup-timed-out ${ap.driver.id} process: ${reason}`);
14217
+ }
14218
+ }
14219
+ handleRuntimeStartupRequestError(agentId, ap, event) {
14220
+ const current = this.agents.get(agentId);
14221
+ if (current !== ap) return;
14222
+ this.clearRuntimeStartupTimeout(ap);
14223
+ this.interruptCompactionIfActive(agentId);
14224
+ this.interruptReviewIfActive(agentId);
14225
+ this.flushPendingTrajectory(agentId);
14226
+ const reduction = reduceApmStartupRequestErrorTermination(ap.gatedSteering);
14227
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "startup_request_error" });
14228
+ ap.lastRuntimeError = event.message;
14229
+ ap.runtimeErrorSinceProgress = true;
14230
+ ap.runtimeProgress.markStale();
14231
+ ap.notifications.clearPending();
14232
+ ap.notifications.clearTimer();
14233
+ const diagnostics = buildRuntimeErrorDiagnosticEnvelope(event.message);
14234
+ const visibleErrorMessage = diagnostics.spanAttrs.runtime_error_action_required === true ? formatRuntimeLoginRequiredMessage(ap.driver.id) : event.message;
14235
+ const failureAttrs = {
14236
+ turn_outcome: "failed",
14237
+ turn_subtype: "runtime_start_failed",
14238
+ turn_reason: "startup_request_error",
14239
+ runtime_start_failure_kind: "startup_request_error",
14240
+ startup_request_method: event.startupRequestMethod,
14241
+ ...diagnostics.eventAttrs
14242
+ };
14243
+ this.noteRuntimeTraceCounter(ap, event);
14244
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.event.received", {
14245
+ kind: event.kind,
14246
+ startup_request_method: event.startupRequestMethod
14247
+ });
14248
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.start.request_failed", failureAttrs);
14249
+ this.endRuntimeTrace(ap, "error", {
14250
+ ...failureAttrs,
14251
+ ...diagnostics.spanAttrs,
14252
+ ...runtimeTraceCounterAttrs(ap),
14253
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
14254
+ });
14255
+ logger.warn(
14256
+ `[Agent ${agentId}] ${ap.driver.id} startup request ${event.startupRequestMethod} failed; terminating unusable runtime process`
14257
+ );
14258
+ this.broadcastActivity(agentId, "error", visibleErrorMessage, [
14259
+ { kind: "text", text: `Error: ${visibleErrorMessage}` }
14260
+ ], ap.launchId);
14261
+ this.sendAgentStatus(agentId, "inactive", ap.launchId);
14262
+ this.idleAgentConfigs.delete(agentId);
12975
14263
  try {
12976
14264
  this.runtimeExitTraceAttrs.set(ap.runtime, {
12977
- stop_source: "startup_timeout",
12978
- expectedTerminationReason: "startup_timeout",
12979
- timeout_ms: timeoutMs
14265
+ stop_source: "startup_request_error",
14266
+ expectedTerminationReason: "startup_request_error",
14267
+ startup_request_method: event.startupRequestMethod,
14268
+ runtime_error_class: diagnostics.spanAttrs.runtime_error_class
12980
14269
  });
12981
- void ap.runtime.stop({ signal: "SIGTERM", reason: "startup_timeout" });
14270
+ void ap.runtime.stop({ signal: "SIGTERM", reason: "startup_request_error" });
12982
14271
  } catch (err) {
12983
14272
  const reason = err instanceof Error ? err.message : String(err);
12984
- logger.warn(`[Agent ${agentId}] Failed to terminate startup-timed-out ${ap.driver.id} process: ${reason}`);
14273
+ logger.warn(`[Agent ${agentId}] Failed to terminate startup-failed ${ap.driver.id} process: ${reason}`);
12985
14274
  }
12986
14275
  }
12987
14276
  isThinkingBlockMutationError(message) {
@@ -12995,21 +14284,19 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
12995
14284
  ap.runtimeProgress.markStale();
12996
14285
  const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
12997
14286
  const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes);
12998
- const turnReason = classifyRuntimeStallReason(ap);
12999
- this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.stalled", {
13000
- turn_outcome: "failed",
13001
- turn_subtype: "runtime_stalled",
13002
- turn_reason: turnReason,
14287
+ const projection = projectApmRuntimeProgressStalledTrace({
14288
+ turnReason: diagnostic.turnReason,
14289
+ staleForMs,
14290
+ lastActivity: ap.lastActivity,
14291
+ lastActivityDetailPresent: diagnostic.lastActivityDetailPresent,
14292
+ lastActivityDetailKind: diagnostic.lastActivityDetailKind
14293
+ });
14294
+ this.recordRuntimeTraceEvent(agentId, ap, projection.runtimeEventName, {
14295
+ ...projection.runtimeEventAttrs,
13003
14296
  ...diagnostic.traceAttrs
13004
14297
  });
13005
14298
  this.endRuntimeTrace(ap, "error", {
13006
- turn_outcome: "failed",
13007
- turn_subtype: "runtime_stalled",
13008
- turn_reason: turnReason,
13009
- ageMs: staleForMs,
13010
- lastActivity: ap.lastActivity,
13011
- lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
13012
- lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
14299
+ ...projection.runtimeSpanAttrs,
13013
14300
  ...runtimeTraceCounterAttrs(ap),
13014
14301
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
13015
14302
  });
@@ -13036,25 +14323,22 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
13036
14323
  const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
13037
14324
  ap.runtimeProgress.markStale();
13038
14325
  const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes);
13039
- const turnReason = classifyRuntimeStallReason(ap);
13040
- this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.stalled", {
13041
- turn_outcome: "failed",
13042
- turn_subtype: "runtime_stalled",
13043
- turn_reason: turnReason,
13044
- ...diagnostic.traceAttrs,
14326
+ const projection = projectApmRuntimeTerminationTrace({
14327
+ reason: "stalled_recovery",
14328
+ turnReason: diagnostic.turnReason,
14329
+ staleForMs,
14330
+ lastActivity: ap.lastActivity,
14331
+ lastActivityDetailPresent: diagnostic.lastActivityDetailPresent,
14332
+ lastActivityDetailKind: diagnostic.lastActivityDetailKind,
13045
14333
  pendingMessages: ap.inbox.length,
13046
- recovery: "terminate_for_queued_message"
14334
+ recoveryAction: "terminate_for_queued_message"
14335
+ });
14336
+ this.recordRuntimeTraceEvent(agentId, ap, projection.runtimeEventName, {
14337
+ ...projection.runtimeEventAttrs,
14338
+ ...diagnostic.traceAttrs
13047
14339
  });
13048
14340
  this.endRuntimeTrace(ap, "error", {
13049
- turn_outcome: "failed",
13050
- turn_subtype: "runtime_stalled",
13051
- turn_reason: turnReason,
13052
- ageMs: staleForMs,
13053
- lastActivity: ap.lastActivity,
13054
- lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
13055
- lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
13056
- pendingMessages: ap.inbox.length,
13057
- recovery: "terminate_for_queued_message",
14341
+ ...projection.runtimeSpanAttrs,
13058
14342
  ...runtimeTraceCounterAttrs(ap),
13059
14343
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
13060
14344
  });
@@ -13064,13 +14348,9 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
13064
14348
  );
13065
14349
  this.broadcastActivity(agentId, "working", `Restarting stalled ${runtimeLabel} runtime for queued message`);
13066
14350
  try {
13067
- this.runtimeExitTraceAttrs.set(ap.runtime, {
13068
- stop_source: "stalled_recovery",
13069
- expectedTerminationReason: "stalled_recovery",
13070
- queued_messages_count: ap.inbox.length
13071
- });
14351
+ this.runtimeExitTraceAttrs.set(ap.runtime, projection.processExitAttrs);
13072
14352
  this.startStalledRecoverySigtermWatchdog(agentId, ap, runtimeLabel, ap.inbox.length, staleForMs);
13073
- void ap.runtime.stop({ signal: "SIGTERM", reason: "stalled_recovery" });
14353
+ void ap.runtime.stop({ signal: "SIGTERM", reason: projection.runtimeStopReason });
13074
14354
  } catch (err) {
13075
14355
  this.clearStalledRecoverySigtermWatchdog(ap);
13076
14356
  const reason = err instanceof Error ? err.message : String(err);
@@ -13086,16 +14366,23 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
13086
14366
  if (ap) this.recordRuntimeTelemetry(agentId, ap, event);
13087
14367
  return;
13088
14368
  }
14369
+ if (ap && isStartupRequestErrorEvent(event)) {
14370
+ this.handleRuntimeStartupRequestError(agentId, ap, event);
14371
+ return;
14372
+ }
13089
14373
  if (ap) {
13090
14374
  const wasStalled = ap.runtimeProgress.isStale;
13091
- this.clearRuntimeStartupTimeout(ap);
14375
+ if (this.runtimeStartupReadinessSatisfiedByEvent(ap, event)) {
14376
+ ap.startupReadinessSatisfied = true;
14377
+ this.clearRuntimeStartupTimeout(ap);
14378
+ }
13092
14379
  this.noteRuntimeTraceCounter(ap, event);
13093
14380
  const eventAttrs = event.kind === "internal_progress" ? {
13094
14381
  kind: event.kind,
13095
14382
  source: event.source,
13096
14383
  itemType: event.itemType,
13097
14384
  payloadBytes: event.payloadBytes
13098
- } : { kind: event.kind };
14385
+ } : event.kind === "runtime_diagnostic" ? runtimeDiagnosticTraceAttrs(event) : { kind: event.kind };
13099
14386
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.event.received", eventAttrs);
13100
14387
  if (wasStalled) {
13101
14388
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.observed", { afterStall: true });
@@ -13116,7 +14403,19 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
13116
14403
  });
13117
14404
  return;
13118
14405
  }
14406
+ if (event.kind === "runtime_diagnostic") {
14407
+ this.noteRuntimeProgress(ap, event.kind);
14408
+ this.clearRuntimeErrorDeliveryBackoffAfterProgress(agentId, ap, event.kind);
14409
+ this.recordRuntimeDiagnosticActivity(agentId, ap, event);
14410
+ return;
14411
+ }
13119
14412
  this.noteRuntimeProgress(ap, event.kind);
14413
+ } else if (event.kind !== "internal_progress") {
14414
+ this.recordDaemonTrace("daemon.agent.event.received_without_process", {
14415
+ agentId,
14416
+ event_kind: event.kind,
14417
+ runtime: driver.id
14418
+ });
13120
14419
  }
13121
14420
  switch (event.kind) {
13122
14421
  case "session_init":
@@ -13206,6 +14505,25 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
13206
14505
  this.flushCompactionBoundaryMessages(agentId, ap);
13207
14506
  }
13208
14507
  break;
14508
+ case "review_started":
14509
+ this.flushPendingTrajectory(agentId);
14510
+ if (ap) this.recordRuntimeTraceEvent(agentId, ap, "runtime.review_mode.started");
14511
+ this.broadcastActivity(agentId, "working", "Reviewing changes");
14512
+ if (ap) {
14513
+ const reduction = reduceApmGatedReview(ap.gatedSteering, { kind: "review_started" });
14514
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "review_started" });
14515
+ }
14516
+ break;
14517
+ case "review_finished":
14518
+ this.flushPendingTrajectory(agentId);
14519
+ if (ap) this.recordRuntimeTraceEvent(agentId, ap, "runtime.review_mode.finished");
14520
+ this.broadcastActivity(agentId, "working", "Review finished");
14521
+ if (ap) {
14522
+ const reduction = reduceApmGatedReview(ap.gatedSteering, { kind: "review_finished" });
14523
+ this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "review_finished" });
14524
+ this.flushReviewBoundaryMessages(agentId, ap);
14525
+ }
14526
+ break;
13209
14527
  case "turn_end":
13210
14528
  if (ap) this.recordRuntimeTraceEvent(agentId, ap, "runtime.turn.completed");
13211
14529
  this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from turn end)", {
@@ -13257,12 +14575,10 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
13257
14575
  });
13258
14576
  if (ap.driver.terminateProcessOnTurnEnd) {
13259
14577
  logger.info(`[Agent ${agentId}] Turn completed; terminating ${ap.driver.id} process`);
14578
+ const projection = projectApmRuntimeTerminationTrace({ reason: "turn_end" });
13260
14579
  try {
13261
- this.runtimeExitTraceAttrs.set(ap.runtime, {
13262
- stop_source: "turn_end",
13263
- expectedTerminationReason: "turn_end"
13264
- });
13265
- void ap.runtime.stop({ signal: "SIGTERM", reason: "turn_end" });
14580
+ this.runtimeExitTraceAttrs.set(ap.runtime, projection.processExitAttrs);
14581
+ void ap.runtime.stop({ signal: "SIGTERM", reason: projection.runtimeStopReason });
13266
14582
  } catch (err) {
13267
14583
  const reason = err instanceof Error ? err.message : String(err);
13268
14584
  logger.warn(`[Agent ${agentId}] Failed to terminate ${ap.driver.id} after turn_end: ${reason}`);
@@ -13276,6 +14592,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
13276
14592
  break;
13277
14593
  case "error": {
13278
14594
  this.interruptCompactionIfActive(agentId);
14595
+ this.interruptReviewIfActive(agentId);
13279
14596
  this.flushPendingTrajectory(agentId);
13280
14597
  if (ap) {
13281
14598
  ap.lastRuntimeError = event.message;
@@ -13459,7 +14776,7 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
13459
14776
  if (!ap) return false;
13460
14777
  const count = ap.notifications.takePendingAndClearTimer();
13461
14778
  if (count === 0) return false;
13462
- if (ap.isIdle) return false;
14779
+ if (this.isApmIdle(ap)) return false;
13463
14780
  if (!ap.sessionId) return false;
13464
14781
  const runtimeErrorBackoffRemainingMs = this.runtimeErrorDeliveryBackoffRemainingMs(ap);
13465
14782
  if (runtimeErrorBackoffRemainingMs > 0) {
@@ -13494,6 +14811,18 @@ Use ${communicationCommand("read_history")} to catch up on the channels listed a
13494
14811
  );
13495
14812
  return false;
13496
14813
  }
14814
+ if (ap.gatedSteering.reviewing) {
14815
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.review_boundary.delivery_suppressed", {
14816
+ pendingNotificationCount: count,
14817
+ pendingMessages: ap.inbox.length,
14818
+ busyDeliveryMode: ap.driver.busyDeliveryMode
14819
+ });
14820
+ ap.notifications.add(count);
14821
+ logger.info(
14822
+ `[Agent ${agentId}] Suppressing stdin delivery until review mode finishes; pending=${ap.inbox.length}`
14823
+ );
14824
+ return false;
14825
+ }
13497
14826
  const inboxCount = ap.inbox.length;
13498
14827
  if (inboxCount === 0) return false;
13499
14828
  ap.notifications.pruneContributedToPending(ap.inbox, ap.sessionId);
@@ -13761,12 +15090,12 @@ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
13761
15090
  const traceSource = options.transient ? `stdin_${mode}_transient_delivery` : `stdin_${mode}_delivery`;
13762
15091
  const prompt = formatRuntimeProfileControlPrompt(messages) ?? (messages.length === 1 ? `New message received:
13763
15092
 
13764
- ${formatIncomingMessage(messages[0])}
15093
+ ${formatIncomingMessage(messages[0], incomingMessageFormatOptionsForDriver(ap.driver))}
13765
15094
 
13766
15095
  Respond as appropriate. Complete all your work before stopping.
13767
15096
  ${RESPONSE_TARGET_HINT}` : `New messages received:
13768
15097
 
13769
- ${messages.map((message) => formatIncomingMessage(message)).join("\n")}
15098
+ ${messages.map((message) => formatIncomingMessage(message, incomingMessageFormatOptionsForDriver(ap.driver))).join("\n")}
13770
15099
 
13771
15100
  Respond as appropriate. Complete all your work before stopping.
13772
15101
  ${RESPONSE_TARGET_HINT}`);
@@ -14211,8 +15540,8 @@ var ReminderCache = class {
14211
15540
  };
14212
15541
 
14213
15542
  // src/machineLock.ts
14214
- import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
14215
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
15543
+ import { createHash as createHash4, randomUUID as randomUUID6 } from "crypto";
15544
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync7, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
14216
15545
  import os7 from "os";
14217
15546
  import path14 from "path";
14218
15547
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
@@ -14240,7 +15569,7 @@ function ownerPath(lockDir) {
14240
15569
  }
14241
15570
  function readOwner(lockDir) {
14242
15571
  try {
14243
- return JSON.parse(readFileSync6(ownerPath(lockDir), "utf8"));
15572
+ return JSON.parse(readFileSync7(ownerPath(lockDir), "utf8"));
14244
15573
  } catch {
14245
15574
  return null;
14246
15575
  }
@@ -14268,7 +15597,7 @@ function acquireDaemonMachineLock(options) {
14268
15597
  const lockId = getDaemonMachineLockId(options.apiKey);
14269
15598
  const machineDir = path14.join(rootDir, lockId);
14270
15599
  const lockDir = path14.join(machineDir, "daemon.lock");
14271
- const token = randomUUID5();
15600
+ const token = randomUUID6();
14272
15601
  mkdirSync6(machineDir, { recursive: true });
14273
15602
  for (let attempt = 0; attempt < 2; attempt += 1) {
14274
15603
  try {
@@ -14498,232 +15827,11 @@ function isDiagnosticErrorAttr(key) {
14498
15827
  }
14499
15828
 
14500
15829
  // src/traceBundleUpload.ts
14501
- import { createHash as createHash6, randomUUID as randomUUID6 } from "crypto";
14502
- import { gzipSync } from "zlib";
15830
+ import { createHash as createHash6, randomUUID as randomUUID7 } from "crypto";
15831
+ import { gzipSync as gzipSync2 } from "zlib";
14503
15832
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
14504
15833
  import path16 from "path";
14505
15834
 
14506
- // src/chatBridgeRequest.ts
14507
- var DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS = Number.parseInt(
14508
- process.env.SLOCK_CHAT_BRIDGE_TOOL_TIMEOUT_MS || "",
14509
- 10
14510
- ) || 6e4;
14511
- var ChatBridgeToolTimeoutError = class extends Error {
14512
- toolName;
14513
- target;
14514
- timeoutMs;
14515
- durationMs;
14516
- constructor(toolName, target, timeoutMs, durationMs) {
14517
- super(`${toolName} timed out after ${timeoutMs}ms${target ? ` (target: ${target})` : ""}`);
14518
- this.name = "ChatBridgeToolTimeoutError";
14519
- this.toolName = toolName;
14520
- this.target = target;
14521
- this.timeoutMs = timeoutMs;
14522
- this.durationMs = durationMs;
14523
- }
14524
- };
14525
- function describeError(err) {
14526
- if (err instanceof Error) return `${err.name}: ${err.message}`;
14527
- return String(err);
14528
- }
14529
- async function executeJsonRequest(url, init, {
14530
- toolName,
14531
- target = null,
14532
- timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
14533
- fetchImpl,
14534
- now = () => Date.now(),
14535
- warn = (message) => logger.warn(message)
14536
- }) {
14537
- const startedAt = now();
14538
- const timeoutController = new AbortController();
14539
- const signals = [timeoutController.signal];
14540
- if (init.signal) signals.push(init.signal);
14541
- const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
14542
- const timeout = setTimeout(() => {
14543
- timeoutController.abort();
14544
- }, timeoutMs);
14545
- timeout.unref?.();
14546
- try {
14547
- const response = await fetchImpl(url, { ...init, signal });
14548
- const data = await response.json();
14549
- return { response, data, durationMs: now() - startedAt };
14550
- } catch (err) {
14551
- const durationMs = now() - startedAt;
14552
- if (timeoutController.signal.aborted && !init.signal?.aborted) {
14553
- warn(
14554
- `[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
14555
- );
14556
- throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
14557
- }
14558
- warn(
14559
- `[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
14560
- );
14561
- throw err;
14562
- } finally {
14563
- clearTimeout(timeout);
14564
- }
14565
- }
14566
- async function executeResponseRequest(url, init, {
14567
- toolName,
14568
- target = null,
14569
- timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
14570
- fetchImpl,
14571
- now = () => Date.now(),
14572
- warn = (message) => logger.warn(message)
14573
- }) {
14574
- const startedAt = now();
14575
- const timeoutController = new AbortController();
14576
- const signals = [timeoutController.signal];
14577
- if (init.signal) signals.push(init.signal);
14578
- const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
14579
- const timeout = setTimeout(() => {
14580
- timeoutController.abort();
14581
- }, timeoutMs);
14582
- timeout.unref?.();
14583
- try {
14584
- const response = await fetchImpl(url, { ...init, signal });
14585
- return { response, durationMs: now() - startedAt };
14586
- } catch (err) {
14587
- const durationMs = now() - startedAt;
14588
- if (timeoutController.signal.aborted && !init.signal?.aborted) {
14589
- warn(
14590
- `[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
14591
- );
14592
- throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
14593
- }
14594
- warn(
14595
- `[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
14596
- );
14597
- throw err;
14598
- } finally {
14599
- clearTimeout(timeout);
14600
- }
14601
- }
14602
-
14603
- // src/directUploadCapability.ts
14604
- function joinUrl(base, path18) {
14605
- return `${base.replace(/\/+$/, "")}${path18}`;
14606
- }
14607
- function jsonHeaders(apiKey) {
14608
- return {
14609
- "Content-Type": "application/json",
14610
- ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
14611
- };
14612
- }
14613
- async function requestDaemonScopeAttestation({
14614
- serverUrl,
14615
- apiKey,
14616
- scope,
14617
- metadata,
14618
- fetchImpl = daemonFetch,
14619
- timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
14620
- }) {
14621
- const { response, data } = await executeJsonRequest(
14622
- joinUrl(serverUrl, "/internal/machine/scope-attestation"),
14623
- {
14624
- method: "POST",
14625
- headers: jsonHeaders(apiKey),
14626
- body: JSON.stringify({
14627
- scope,
14628
- ...metadata ? { metadata } : {}
14629
- })
14630
- },
14631
- {
14632
- toolName: "daemon_direct_upload.scope_attestation",
14633
- target: scope,
14634
- timeoutMs,
14635
- fetchImpl
14636
- }
14637
- );
14638
- if (!response.ok) {
14639
- throw new Error(`Failed to request daemon scope attestation (${response.status})`);
14640
- }
14641
- return data;
14642
- }
14643
- async function createDirectUploadSession({
14644
- serverUrl,
14645
- apiKey,
14646
- workerUrl,
14647
- scope,
14648
- createPath = "/api/uploads",
14649
- body,
14650
- attestationMetadata,
14651
- fetchImpl = daemonFetch,
14652
- timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
14653
- }) {
14654
- const capability = await requestDaemonScopeAttestation({
14655
- serverUrl,
14656
- apiKey,
14657
- scope,
14658
- metadata: attestationMetadata,
14659
- fetchImpl,
14660
- timeoutMs
14661
- });
14662
- const { response, data } = await executeJsonRequest(
14663
- joinUrl(workerUrl, createPath),
14664
- {
14665
- method: "POST",
14666
- headers: jsonHeaders(),
14667
- body: JSON.stringify({
14668
- ...body,
14669
- attestation: capability.attestation
14670
- })
14671
- },
14672
- {
14673
- toolName: "daemon_direct_upload.create",
14674
- target: capability.audience,
14675
- timeoutMs,
14676
- fetchImpl
14677
- }
14678
- );
14679
- if (!response.ok) {
14680
- throw new Error(`Failed to create direct upload session (${response.status})`);
14681
- }
14682
- return { capability, response: data };
14683
- }
14684
- async function uploadWithSignedCapability({
14685
- serverUrl,
14686
- apiKey,
14687
- workerUrl,
14688
- scope,
14689
- createPath = "/api/uploads",
14690
- createBody,
14691
- attestationMetadata,
14692
- uploadBody,
14693
- fetchImpl = daemonFetch,
14694
- timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS
14695
- }) {
14696
- const { capability, response: session } = await createDirectUploadSession({
14697
- serverUrl,
14698
- apiKey,
14699
- workerUrl,
14700
- scope,
14701
- createPath,
14702
- body: createBody,
14703
- attestationMetadata,
14704
- fetchImpl,
14705
- timeoutMs
14706
- });
14707
- const { response: uploadResponse } = await executeResponseRequest(
14708
- session.upload.url,
14709
- {
14710
- method: session.upload.method,
14711
- headers: session.upload.headers ?? {},
14712
- body: uploadBody
14713
- },
14714
- {
14715
- toolName: "daemon_direct_upload.put",
14716
- target: capability.audience,
14717
- timeoutMs,
14718
- fetchImpl
14719
- }
14720
- );
14721
- if (!uploadResponse.ok) {
14722
- throw new Error(`Failed to upload with signed capability (${uploadResponse.status})`);
14723
- }
14724
- return { capability, session, uploadResponse };
14725
- }
14726
-
14727
15835
  // src/traceJitter.ts
14728
15836
  import { createHash as createHash5 } from "crypto";
14729
15837
  var INITIAL_UPLOAD_DELAY_SPAN_MS = 3e4;
@@ -14864,9 +15972,9 @@ var DaemonTraceBundleUploader = class {
14864
15972
  span?.end("cancelled", { attrs: { outcome: "empty" } });
14865
15973
  return false;
14866
15974
  }
14867
- const gzipped = gzipSync(raw);
15975
+ const gzipped = gzipSync2(raw);
14868
15976
  const bundleSha256 = sha256Hex(gzipped);
14869
- const bundleId = randomUUID6();
15977
+ const bundleId = randomUUID7();
14870
15978
  await uploadWithSignedCapability({
14871
15979
  serverUrl: this.options.serverUrl,
14872
15980
  apiKey: this.options.apiKey,
@@ -15006,6 +16114,15 @@ var DAEMON_CORE_TRACE_ATTR_CONTRACTS = {
15006
16114
  },
15007
16115
  "daemon.connection.local_disconnect_observed": {
15008
16116
  spanAttrs: ["running_agents_count", "idle_agents_count"]
16117
+ },
16118
+ "daemon.agent.activity.produced": {
16119
+ spanAttrs: ["agentId", "activity", "detail_present", "entry_kinds", "ap_present", "launch_id_present", "client_seq_present", "session_id_present", "runtime"]
16120
+ },
16121
+ "daemon.agent.activity.skipped": {
16122
+ spanAttrs: ["agentId", "event_kind", "reason", "text_length"]
16123
+ },
16124
+ "daemon.agent.event.received_without_process": {
16125
+ spanAttrs: ["agentId", "event_kind", "runtime"]
15009
16126
  }
15010
16127
  };
15011
16128
  var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
@@ -15083,7 +16200,7 @@ function resolveSlockCliPathOrEmpty(moduleUrl = import.meta.url) {
15083
16200
  }
15084
16201
  async function runBundledSlockCli(argv) {
15085
16202
  process.argv = [process.execPath, "slock", ...argv];
15086
- await import("./dist-KVBO6CH7.js");
16203
+ await import("./dist-DSRBN3VD.js");
15087
16204
  }
15088
16205
  function detectRuntimes(tracer = noopTracer) {
15089
16206
  const ids = [];
@@ -15193,6 +16310,10 @@ function summarizeIncomingMessage(msg) {
15193
16310
  return `(agent=${msg.agentId}, path=${msg.path})`;
15194
16311
  case "agent:skills:list":
15195
16312
  return `(agent=${msg.agentId}, runtime=${msg.runtime || "auto"})`;
16313
+ case "agent:diagnostic:session_transcript":
16314
+ return `(agent=${msg.agentId})`;
16315
+ case "agent:diagnostic:feedback_transcript":
16316
+ return `(agent=${msg.agentId}, feedbackReportId=${msg.feedbackReportId})`;
15196
16317
  case "agent:activity_probe":
15197
16318
  return `(agent=${msg.agentId}, probe=${msg.probeId.slice(0, 8)}, purpose=${msg.purpose})`;
15198
16319
  case "machine:workspace:delete":
@@ -15251,6 +16372,7 @@ var DaemonCore = class {
15251
16372
  });
15252
16373
  let connection;
15253
16374
  this.agentsDataDir = options.dataDir ?? resolveSlockHomePath("agents", this.slockHome);
16375
+ const traceUploadDisabled = process.env.SLOCK_DAEMON_TRACE_UPLOAD_DISABLED === "1";
15254
16376
  const agentManagerOptions = {
15255
16377
  dataDir: this.agentsDataDir,
15256
16378
  serverUrl: options.serverUrl,
@@ -15258,7 +16380,8 @@ var DaemonCore = class {
15258
16380
  slockCliPath: this.slockCliPath,
15259
16381
  tracer: this.tracer,
15260
16382
  daemonVersion: this.daemonVersion,
15261
- computerVersion: this.computerVersion
16383
+ computerVersion: this.computerVersion,
16384
+ workerUrl: traceUploadDisabled ? void 0 : process.env.SLOCK_DAEMON_TRACE_UPLOAD_URL || DEFAULT_TRACE_UPLOAD_URL
15262
16385
  };
15263
16386
  this.agentManager = options.agentManagerFactory ? options.agentManagerFactory((msg) => connection.send(msg), options.apiKey, agentManagerOptions) : new AgentProcessManager((msg) => connection.send(msg), options.apiKey, agentManagerOptions);
15264
16387
  const connectionFactory = options.connectionFactory ?? ((connOptions) => new DaemonConnection(connOptions));
@@ -15687,6 +16810,49 @@ var DaemonCore = class {
15687
16810
  this.connection.send({ type: "agent:skills:list_result", agentId: msg.agentId, global: [], workspace: [] });
15688
16811
  });
15689
16812
  break;
16813
+ case "agent:diagnostic:session_transcript":
16814
+ this.agentManager.getSessionTranscript(msg.agentId).then((result) => {
16815
+ this.connection.send({ type: "agent:diagnostic:session_transcript_result", agentId: msg.agentId, requestId: msg.requestId, ...result });
16816
+ }).catch((err) => {
16817
+ logger.error(`[Daemon] Failed to get session transcript for ${msg.agentId}`, err);
16818
+ this.connection.send({
16819
+ type: "agent:diagnostic:session_transcript_result",
16820
+ agentId: msg.agentId,
16821
+ requestId: msg.requestId,
16822
+ runtime: "unknown",
16823
+ sessionId: "unknown",
16824
+ reachable: false,
16825
+ path: null,
16826
+ transcript: null,
16827
+ sizeBytes: 0,
16828
+ truncated: false,
16829
+ redacted: false,
16830
+ tier: "unknown",
16831
+ error: err instanceof Error ? err.message : String(err)
16832
+ });
16833
+ });
16834
+ break;
16835
+ case "agent:diagnostic:feedback_transcript":
16836
+ this.agentManager.collectFeedbackTranscript(msg.agentId, msg.feedbackReportId).then((result) => {
16837
+ this.connection.send({
16838
+ type: "agent:diagnostic:feedback_transcript_result",
16839
+ agentId: msg.agentId,
16840
+ feedbackReportId: msg.feedbackReportId,
16841
+ requestId: msg.requestId,
16842
+ ...result
16843
+ });
16844
+ }).catch((err) => {
16845
+ logger.error(`[Daemon] Failed to collect feedback transcript for ${msg.agentId}`, err);
16846
+ this.connection.send({
16847
+ type: "agent:diagnostic:feedback_transcript_result",
16848
+ agentId: msg.agentId,
16849
+ feedbackReportId: msg.feedbackReportId,
16850
+ requestId: msg.requestId,
16851
+ reachable: false,
16852
+ error: err instanceof Error ? err.message : String(err)
16853
+ });
16854
+ });
16855
+ break;
15690
16856
  case "agent:activity_probe":
15691
16857
  this.agentManager.respondToActivityProbe(msg.agentId, msg.probeId);
15692
16858
  break;