@slock-ai/daemon 0.55.7-play.20260602150229 → 0.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -939,10 +939,12 @@ var actionCardActionSchema = z.discriminatedUnion("type", [
939
939
  ]);
940
940
 
941
941
  // ../shared/src/agentInbox.ts
942
- function formatAgentInboxDelta(rows) {
943
- if (rows.length === 0) return "Inbox update: no pending targets";
942
+ function formatAgentInboxDelta(rows, options = {}) {
943
+ const totalPendingMessages = options.totalPendingMessages;
944
+ const header = typeof totalPendingMessages === "number" ? `Inbox update: ${totalPendingMessages} unread message${totalPendingMessages === 1 ? "" : "s"} total; ${rows.length === 0 ? "no" : rows.length} changed target${rows.length === 1 ? "" : "s"}` : `Inbox update: ${rows.length} changed target${rows.length === 1 ? "" : "s"}`;
945
+ if (rows.length === 0) return typeof totalPendingMessages === "number" ? header : "Inbox update: no pending targets";
944
946
  return [
945
- `Inbox update: ${rows.length} changed target${rows.length === 1 ? "" : "s"}`,
947
+ header,
946
948
  ...rows.map((row) => `${row.target} ${formatAgentInboxRowDetails(row)}`)
947
949
  ].join("\n");
948
950
  }
@@ -1305,6 +1307,7 @@ function buildPrompt(config, variant, opts) {
1305
1307
  const sendCmd = isCli ? "`slock message send`" : `\`${t("send_message")}\``;
1306
1308
  const readCmd = isCli ? "`slock message read`" : `\`${t("read_history")}\``;
1307
1309
  const checkCmd = isCli ? "`slock message check`" : `\`${t("check_messages")}\``;
1310
+ const inboxCheckCmd = isCli ? "`slock inbox check`" : checkCmd;
1308
1311
  const taskClaimCmd = isCli ? "`slock task claim`" : `\`${t("claim_tasks")}\``;
1309
1312
  const taskCreateCmd = isCli ? "`slock task create`" : `\`${t("create_tasks")}\``;
1310
1313
  const taskUpdateCmd = isCli ? "`slock task update`" : `\`${t("update_task_status")}\``;
@@ -1785,14 +1788,14 @@ You may develop a specialized role over time through your interactions. Embrace
1785
1788
 
1786
1789
  ## Message Notifications
1787
1790
 
1788
- While you are working, the daemon may write a batched inbox-count notification into your current turn.
1791
+ While you are working, the daemon may write a batched, content-free inbox update into your current turn.
1789
1792
 
1790
1793
  How to handle these:
1791
- - Treat the notification as a signal that new Slock messages are waiting; it does not include the message content.
1792
- - Call ${checkCmd} at the next safe breakpoint to materialize the pending messages before taking side-effect actions that depend on current context.
1793
- - If the new message is higher priority, pivot after reading it. If not, continue your current work.`;
1794
+ - Treat the notification as a non-urgent signal that new Slock messages are waiting; it does not include the message content and does not require an immediate interruption.
1795
+ - Keep working until a natural breakpoint. If you then choose to inspect pending targets, call ${inboxCheckCmd}; use ${checkCmd} / ${readCmd} when you choose to inspect message content.
1796
+ - If a message you explicitly read is higher priority, pivot to it. If not, continue your current work.`;
1794
1797
  } else {
1795
- const notifyExample = isCli ? `\`[Slock inbox notice: You have N pending inbox message(s). Call slock message check to read them when you're ready.]\`` : `\`[Slock inbox notice: You have N pending inbox message(s). Call ${t("check_messages")} to read them when you're ready.]\``;
1798
+ const notifyExample = isCli ? `\`[Slock inbox notice:\\nInbox update: N unread messages total; M changed targets\\n#channel pending: K messages ...]\`` : `\`[Slock inbox notice:\\nInbox update: N unread messages total; M changed targets\\n#channel pending: K messages ...]\``;
1796
1799
  prompt += `
1797
1800
 
1798
1801
  ## Message Notifications
@@ -1802,9 +1805,9 @@ While you are busy (executing tools, thinking, etc.), new messages may arrive. W
1802
1805
  ${notifyExample}
1803
1806
 
1804
1807
  How to handle these:
1805
- - Call ${checkCmd} to check for new messages. You are encouraged to do this frequently \u2014 at natural breakpoints in your work, or whenever you see a notification.
1806
- - If the new message is higher priority, you may pivot to it. If not, continue your current work.
1807
- - ${checkCmd} returns instantly with any pending messages (or "no new messages"). It is always safe to call.`;
1808
+ - The notice is target-scoped and content-free. Its header may show total unread/pending count, while detail rows list the targets changed by this update; it never includes message bodies.
1809
+ - Do not interrupt the current step just because a notice arrived. At a natural breakpoint, call ${inboxCheckCmd} if you choose to inspect pending targets, or use ${checkCmd} / ${readCmd} when you choose to inspect message content.
1810
+ - If a message you explicitly read is higher priority, you may pivot to it. If not, continue your current work.`;
1808
1811
  }
1809
1812
  }
1810
1813
  if (config.description) {
@@ -1858,19 +1861,6 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1858
1861
  return candidates.filter((candidate) => existsSync(candidate.path));
1859
1862
  }
1860
1863
 
1861
- // src/authEnv.ts
1862
- var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
1863
- var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
1864
- function scrubDaemonAuthEnv(env) {
1865
- delete env[DAEMON_API_KEY_ENV];
1866
- return env;
1867
- }
1868
- function scrubDaemonChildEnv(env) {
1869
- delete env[DAEMON_API_KEY_ENV];
1870
- delete env[SLOCK_AGENT_TOKEN_ENV];
1871
- return env;
1872
- }
1873
-
1874
1864
  // src/agentCredentialProxy.ts
1875
1865
  import { randomBytes } from "crypto";
1876
1866
  import http from "http";
@@ -3026,9 +3016,7 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
3026
3016
  var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
3027
3017
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
3028
3018
  var RAW_CREDENTIAL_ENV_DENYLIST = [
3029
- "SLOCK_AGENT_TOKEN",
3030
- "SLOCK_AGENT_CREDENTIAL_KEY",
3031
- "SLOCK_AGENT_CREDENTIAL_KEY_FILE"
3019
+ "SLOCK_AGENT_CREDENTIAL_KEY"
3032
3020
  ];
3033
3021
  var cachedOpencliBinPath;
3034
3022
  function resolveOpencliBinPath() {
@@ -3243,7 +3231,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
3243
3231
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
3244
3232
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
3245
3233
  };
3246
- scrubDaemonChildEnv(spawnEnv);
3234
+ delete spawnEnv.SLOCK_AGENT_TOKEN;
3247
3235
  for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
3248
3236
  delete spawnEnv[key];
3249
3237
  }
@@ -3672,7 +3660,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
3672
3660
  }
3673
3661
  function resolveCommandOnPath(command, deps = {}) {
3674
3662
  const platform = deps.platform ?? process.platform;
3675
- const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
3663
+ const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
3676
3664
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
3677
3665
  const existsSyncFn = deps.existsSyncFn ?? existsSync2;
3678
3666
  if (platform === "win32") {
@@ -3698,7 +3686,7 @@ function firstExistingPath(candidates, deps = {}) {
3698
3686
  return null;
3699
3687
  }
3700
3688
  function readCommandVersion(command, args = [], deps = {}) {
3701
- const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
3689
+ const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
3702
3690
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
3703
3691
  try {
3704
3692
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -5161,11 +5149,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
5161
5149
  return parseCursorModelsOutput(String(result.stdout || ""));
5162
5150
  }
5163
5151
  function buildCursorModelProbeEnv(deps = {}) {
5164
- return scrubDaemonChildEnv(withWindowsUserEnvironment({
5152
+ return withWindowsUserEnvironment({
5165
5153
  ...deps.env ?? process.env,
5166
5154
  FORCE_COLOR: "0",
5167
5155
  NO_COLOR: "1"
5168
- }, deps));
5156
+ }, deps);
5169
5157
  }
5170
5158
  function runCursorModelsCommand() {
5171
5159
  return spawnSync("cursor-agent", ["models"], {
@@ -5221,7 +5209,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
5221
5209
  }
5222
5210
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
5223
5211
  const existsSyncFn = deps.existsSyncFn ?? existsSync5;
5224
- const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
5212
+ const env = deps.env ?? process.env;
5225
5213
  const winPath = path8.win32;
5226
5214
  let geminiEntry = null;
5227
5215
  try {
@@ -5393,16 +5381,13 @@ var GeminiDriver = class {
5393
5381
  // src/drivers/kimi.ts
5394
5382
  import { randomUUID as randomUUID2 } from "crypto";
5395
5383
  import { spawn as spawn7 } from "child_process";
5396
- import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
5384
+ import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
5397
5385
  import os3 from "os";
5398
5386
  import path9 from "path";
5399
5387
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
5400
5388
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
5401
5389
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
5402
5390
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
5403
- var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
5404
- var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
5405
- var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
5406
5391
  function parseToolArguments(raw) {
5407
5392
  if (typeof raw !== "string") return raw;
5408
5393
  try {
@@ -5411,73 +5396,6 @@ function parseToolArguments(raw) {
5411
5396
  return raw;
5412
5397
  }
5413
5398
  }
5414
- function readKimiConfigSource(home = os3.homedir(), env = process.env) {
5415
- const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5416
- if (inlineConfig && inlineConfig.trim()) {
5417
- return {
5418
- raw: inlineConfig,
5419
- explicitPath: null,
5420
- sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
5421
- };
5422
- }
5423
- const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
5424
- const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
5425
- try {
5426
- return {
5427
- raw: readFileSync3(configPath, "utf8"),
5428
- explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5429
- sourcePath: configPath
5430
- };
5431
- } catch {
5432
- return {
5433
- raw: null,
5434
- explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5435
- sourcePath: configPath
5436
- };
5437
- }
5438
- }
5439
- function buildKimiSpawnEnv(env = process.env) {
5440
- const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
5441
- delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5442
- delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
5443
- return scrubDaemonChildEnv(spawnEnv);
5444
- }
5445
- function buildKimiEffectiveEnv(ctx, overrideEnv) {
5446
- return {
5447
- ...process.env,
5448
- ...ctx.config.envVars || {},
5449
- ...overrideEnv || {}
5450
- };
5451
- }
5452
- function buildKimiLaunchOptions(ctx, opts = {}) {
5453
- const env = buildKimiEffectiveEnv(ctx, opts.env);
5454
- const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
5455
- const args = [];
5456
- let configFilePath = null;
5457
- let configContent = null;
5458
- if (source.explicitPath) {
5459
- configFilePath = source.explicitPath;
5460
- } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
5461
- configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
5462
- configContent = source.raw;
5463
- if (opts.writeGeneratedConfig !== false) {
5464
- writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
5465
- chmodSync(configFilePath, 384);
5466
- }
5467
- }
5468
- if (configFilePath) {
5469
- args.push("--config-file", configFilePath);
5470
- }
5471
- if (ctx.config.model && ctx.config.model !== "default") {
5472
- args.push("--model", ctx.config.model);
5473
- }
5474
- return {
5475
- args,
5476
- env: buildKimiSpawnEnv(env),
5477
- configFilePath,
5478
- configContent
5479
- };
5480
- }
5481
5399
  function resolveKimiSpawn(commandArgs, deps = {}) {
5482
5400
  return {
5483
5401
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -5501,25 +5419,7 @@ var KimiDriver = class {
5501
5419
  };
5502
5420
  model = {
5503
5421
  detectedModelsVerifiedAs: "launchable",
5504
- toLaunchSpec: (modelId, ctx, opts) => {
5505
- if (!ctx) return { args: ["--model", modelId] };
5506
- const launchCtx = {
5507
- ...ctx,
5508
- config: {
5509
- ...ctx.config,
5510
- model: modelId
5511
- }
5512
- };
5513
- const launch = buildKimiLaunchOptions(launchCtx, {
5514
- home: opts?.home,
5515
- writeGeneratedConfig: false
5516
- });
5517
- return {
5518
- args: launch.args,
5519
- env: launch.env,
5520
- configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
5521
- };
5522
- }
5422
+ toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
5523
5423
  };
5524
5424
  supportsStdinNotification = true;
5525
5425
  mcpToolPrefix = "";
@@ -5573,7 +5473,6 @@ var KimiDriver = class {
5573
5473
  }
5574
5474
  }
5575
5475
  }), "utf8");
5576
- const launch = buildKimiLaunchOptions(ctx);
5577
5476
  const args = [
5578
5477
  "--wire",
5579
5478
  "--yolo",
@@ -5582,16 +5481,15 @@ var KimiDriver = class {
5582
5481
  "--mcp-config-file",
5583
5482
  mcpConfigPath,
5584
5483
  "--session",
5585
- this.sessionId,
5586
- ...launch.args
5484
+ this.sessionId
5587
5485
  ];
5588
5486
  const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
5589
5487
  if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
5590
5488
  args.push("--model", launchRuntimeFields.model);
5591
5489
  }
5592
5490
  const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
5593
- const spawnTarget = resolveKimiSpawn(args);
5594
- const proc = spawn7(spawnTarget.command, spawnTarget.args, {
5491
+ const launch = resolveKimiSpawn(args);
5492
+ const proc = spawn7(launch.command, launch.args, {
5595
5493
  cwd: ctx.workingDirectory,
5596
5494
  stdio: ["pipe", "pipe", "pipe"],
5597
5495
  env: spawnEnv,
@@ -5599,7 +5497,7 @@ var KimiDriver = class {
5599
5497
  // and has an 8191-character command-line limit. Kimi's official
5600
5498
  // installer/uv entrypoint is an executable, so launch it directly and
5601
5499
  // keep prompts on stdin / files instead of routing through cmd.exe.
5602
- shell: spawnTarget.shell
5500
+ shell: launch.shell
5603
5501
  });
5604
5502
  proc.stdin?.write(JSON.stringify({
5605
5503
  jsonrpc: "2.0",
@@ -5713,9 +5611,14 @@ var KimiDriver = class {
5713
5611
  return detectKimiModels();
5714
5612
  }
5715
5613
  };
5716
- function detectKimiModels(home = os3.homedir(), opts = {}) {
5717
- const raw = readKimiConfigSource(home, opts.env).raw;
5718
- if (raw === null) return null;
5614
+ function detectKimiModels(home = os3.homedir()) {
5615
+ const configPath = path9.join(home, ".kimi", "config.toml");
5616
+ let raw;
5617
+ try {
5618
+ raw = readFileSync3(configPath, "utf8");
5619
+ } catch {
5620
+ return null;
5621
+ }
5719
5622
  const models = [];
5720
5623
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
5721
5624
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -5979,7 +5882,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
5979
5882
  const platform = deps.platform ?? process.platform;
5980
5883
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
5981
5884
  const result = spawnSyncFn("opencode", ["models"], {
5982
- env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
5885
+ env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
5983
5886
  encoding: "utf8",
5984
5887
  timeout: 5e3,
5985
5888
  shell: platform === "win32"
@@ -6242,7 +6145,6 @@ var OpenCodeDriver = class {
6242
6145
  import { randomUUID as randomUUID3 } from "crypto";
6243
6146
  import { EventEmitter } from "events";
6244
6147
  import { mkdirSync as mkdirSync4, readdirSync } from "fs";
6245
- import { PassThrough, Writable } from "stream";
6246
6148
  import path11 from "path";
6247
6149
  import {
6248
6150
  AuthStorage,
@@ -6261,6 +6163,13 @@ var PI_PROVIDER_LABELS = {
6261
6163
  openai: "OpenAI",
6262
6164
  openrouter: "OpenRouter"
6263
6165
  };
6166
+ function createPiSdkEventMappingState(sessionId = null) {
6167
+ return {
6168
+ sessionId,
6169
+ sessionAnnounced: false,
6170
+ sawTextDelta: false
6171
+ };
6172
+ }
6264
6173
  function buildPiSessionDir(workingDirectory) {
6265
6174
  return path11.join(workingDirectory, PI_SESSION_DIR);
6266
6175
  }
@@ -6286,12 +6195,6 @@ function findPiSessionFile(sessionDir, sessionId) {
6286
6195
  const match = entries.find((entry) => entry.endsWith(suffix));
6287
6196
  return match ? path11.join(sessionDir, match) : null;
6288
6197
  }
6289
- function piSdkEventToJsonLine(event) {
6290
- if (event.type === "agent_end") {
6291
- return JSON.stringify({ type: "agent_end" });
6292
- }
6293
- return JSON.stringify(event);
6294
- }
6295
6198
  function detectPiModelsFromRegistry(modelRegistry) {
6296
6199
  const models = [];
6297
6200
  const seen = /* @__PURE__ */ new Set();
@@ -6341,114 +6244,309 @@ function piErrorMessage(error) {
6341
6244
  }
6342
6245
  return "Unknown Pi error";
6343
6246
  }
6344
- var PiSdkProcess = class extends EventEmitter {
6345
- constructor(session) {
6346
- super();
6347
- this.session = session;
6348
- this.stdin = new Writable({
6349
- write: (chunk, _encoding, callback) => {
6350
- this.handleInput(String(chunk)).then(
6351
- () => callback(),
6352
- (error) => {
6353
- this.writeError(error);
6354
- callback();
6355
- }
6356
- );
6247
+ function pushSessionInitIfNeeded(state, events) {
6248
+ if (!state.sessionAnnounced && state.sessionId) {
6249
+ events.push({ kind: "session_init", sessionId: state.sessionId });
6250
+ state.sessionAnnounced = true;
6251
+ }
6252
+ }
6253
+ function mapPiAssistantMessageEvent(assistantEvent, state) {
6254
+ switch (assistantEvent.type) {
6255
+ case "thinking_delta":
6256
+ return typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0 ? [{ kind: "thinking", text: assistantEvent.delta }] : [];
6257
+ case "text_delta":
6258
+ if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
6259
+ state.sawTextDelta = true;
6260
+ return [{ kind: "text", text: assistantEvent.delta }];
6357
6261
  }
6358
- });
6359
- this.session.subscribe((event) => {
6360
- this.writeStdout(piSdkEventToJsonLine(event));
6361
- });
6262
+ return [];
6263
+ case "text_end":
6264
+ return !state.sawTextDelta && typeof assistantEvent.content === "string" && assistantEvent.content.length > 0 ? [{ kind: "text", text: assistantEvent.content }] : [];
6265
+ case "error":
6266
+ return [{ kind: "error", message: piErrorMessage(assistantEvent.error.errorMessage || assistantEvent.error) }];
6267
+ case "thinking_start":
6268
+ case "thinking_end":
6269
+ case "text_start":
6270
+ case "toolcall_start":
6271
+ case "toolcall_delta":
6272
+ case "toolcall_end":
6273
+ case "start":
6274
+ case "done":
6275
+ return [];
6276
+ default: {
6277
+ const _exhaustive = assistantEvent;
6278
+ return _exhaustive;
6279
+ }
6280
+ }
6281
+ }
6282
+ function mapPiSdkEventToParsedEvents(event, state) {
6283
+ const events = [];
6284
+ pushSessionInitIfNeeded(state, events);
6285
+ switch (event.type) {
6286
+ case "agent_start":
6287
+ case "turn_start":
6288
+ case "turn_end":
6289
+ case "message_end":
6290
+ case "tool_execution_update":
6291
+ case "queue_update":
6292
+ case "session_info_changed":
6293
+ case "thinking_level_changed":
6294
+ case "auto_retry_start":
6295
+ case "auto_retry_end":
6296
+ return events;
6297
+ case "message_start":
6298
+ if (event.message.role === "assistant") {
6299
+ state.sawTextDelta = false;
6300
+ }
6301
+ return events;
6302
+ case "message_update":
6303
+ events.push(...mapPiAssistantMessageEvent(event.assistantMessageEvent, state));
6304
+ return events;
6305
+ case "tool_execution_start":
6306
+ events.push({
6307
+ kind: "tool_call",
6308
+ name: event.toolName || "unknown_tool",
6309
+ input: event.args ?? {}
6310
+ });
6311
+ return events;
6312
+ case "tool_execution_end":
6313
+ events.push({ kind: "tool_output", name: event.toolName || "unknown_tool" });
6314
+ return events;
6315
+ case "compaction_start":
6316
+ events.push({ kind: "compaction_started" });
6317
+ return events;
6318
+ case "compaction_end":
6319
+ events.push({ kind: "compaction_finished" });
6320
+ return events;
6321
+ case "agent_end":
6322
+ events.push({ kind: "turn_end", sessionId: state.sessionId || void 0 });
6323
+ return events;
6324
+ default: {
6325
+ const _exhaustive = event;
6326
+ return _exhaustive;
6327
+ }
6362
6328
  }
6363
- stdout = new PassThrough();
6364
- stderr = new PassThrough();
6365
- stdin;
6366
- pid = void 0;
6367
- exitCode = null;
6368
- signalCode = null;
6369
- killed = false;
6370
- buffer = "";
6371
- closed = false;
6372
- kill(signal = "SIGTERM") {
6373
- if (this.closed) return false;
6374
- this.killed = true;
6375
- this.signalCode = typeof signal === "string" ? signal : null;
6376
- void this.shutdown(null, this.signalCode);
6377
- return true;
6329
+ }
6330
+ var PI_RUNTIME_SESSION_DESCRIPTOR = {
6331
+ transport: "sdk",
6332
+ lifecycle: "sdk_session",
6333
+ input: {
6334
+ initial: "start",
6335
+ idle: "sdk_prompt",
6336
+ busy: "sdk_steer"
6337
+ },
6338
+ readiness: "sdk_ready",
6339
+ turnBoundary: "sdk_event",
6340
+ startPolicy: "immediate",
6341
+ inFlightWake: "steer",
6342
+ busyDelivery: "direct",
6343
+ postTurn: "keep_alive"
6344
+ };
6345
+ async function createPiAgentSessionForContext(ctx, sessionId) {
6346
+ const sessionDir = buildPiSessionDir(ctx.workingDirectory);
6347
+ mkdirSync4(sessionDir, { recursive: true });
6348
+ const spawnEnv = await buildPiSpawnEnv(ctx);
6349
+ const agentDir = spawnEnv.PI_CODING_AGENT_DIR || getAgentDir();
6350
+ const authStorage = AuthStorage.create(path11.join(agentDir, "auth.json"));
6351
+ const modelRegistry = ModelRegistry.create(authStorage, path11.join(agentDir, "models.json"));
6352
+ const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
6353
+ const model = resolvePiModelFromRegistry(launchRuntimeFields.model, modelRegistry);
6354
+ if (launchRuntimeFields.model && launchRuntimeFields.model !== "default" && !model) {
6355
+ throw new Error(`Pi model not found: ${launchRuntimeFields.model}`);
6356
+ }
6357
+ const settingsManager = SettingsManager.inMemory({ compaction: { enabled: false } });
6358
+ const resourceLoader = new DefaultResourceLoader({
6359
+ cwd: ctx.workingDirectory,
6360
+ agentDir,
6361
+ settingsManager,
6362
+ systemPromptOverride: () => ctx.standingPrompt
6363
+ });
6364
+ await resourceLoader.reload();
6365
+ const existingSessionFile = ctx.config.sessionId ? findPiSessionFile(sessionDir, ctx.config.sessionId) : null;
6366
+ const sessionManager = existingSessionFile ? SessionManager.open(existingSessionFile, sessionDir, ctx.workingDirectory) : SessionManager.create(ctx.workingDirectory, sessionDir, { id: sessionId });
6367
+ const { session } = await createAgentSession({
6368
+ cwd: ctx.workingDirectory,
6369
+ agentDir,
6370
+ model,
6371
+ thinkingLevel: launchRuntimeFields.reasoningEffort,
6372
+ authStorage,
6373
+ modelRegistry,
6374
+ resourceLoader,
6375
+ customTools: [
6376
+ createBashTool(ctx.workingDirectory, {
6377
+ spawnHook: (spawnContext) => ({
6378
+ ...spawnContext,
6379
+ env: {
6380
+ ...spawnContext.env,
6381
+ ...spawnEnv
6382
+ }
6383
+ })
6384
+ })
6385
+ ],
6386
+ sessionManager,
6387
+ settingsManager
6388
+ });
6389
+ return session;
6390
+ }
6391
+ var PiSdkRuntimeSession = class {
6392
+ constructor(ctx, setCurrentSessionId, sessionFactory = createPiAgentSessionForContext) {
6393
+ this.ctx = ctx;
6394
+ this.setCurrentSessionId = setCurrentSessionId;
6395
+ this.sessionFactory = sessionFactory;
6396
+ this.mappingState = createPiSdkEventMappingState(ctx.config.sessionId || null);
6397
+ }
6398
+ descriptor = PI_RUNTIME_SESSION_DESCRIPTOR;
6399
+ events = new EventEmitter();
6400
+ mappingState;
6401
+ session = null;
6402
+ unsubscribe = null;
6403
+ started = false;
6404
+ didClose = false;
6405
+ requestedStopReason;
6406
+ exitInfo = null;
6407
+ get pid() {
6408
+ return void 0;
6378
6409
  }
6379
- ref() {
6380
- return this;
6410
+ get currentSessionId() {
6411
+ return this.mappingState.sessionId;
6381
6412
  }
6382
- unref() {
6383
- return this;
6413
+ get exitCode() {
6414
+ return this.exitInfo?.code ?? null;
6384
6415
  }
6385
- async handleInput(chunk) {
6386
- this.buffer += chunk;
6387
- const lines = this.buffer.split("\n");
6388
- this.buffer = lines.pop() || "";
6389
- for (const line of lines) {
6390
- if (!line.trim() || this.closed) continue;
6391
- await this.handleCommand(line);
6392
- }
6416
+ get signalCode() {
6417
+ return this.exitInfo?.signal ?? null;
6393
6418
  }
6394
- async handleCommand(raw) {
6395
- let command;
6396
- try {
6397
- command = JSON.parse(raw);
6398
- } catch (error) {
6399
- this.writeError(error);
6400
- return;
6419
+ get closed() {
6420
+ return this.didClose;
6421
+ }
6422
+ on(event, cb) {
6423
+ this.events.on(event, cb);
6424
+ }
6425
+ async start(input) {
6426
+ if (this.started) {
6427
+ return { ok: false, reason: "runtime_error", error: "runtime session already started" };
6401
6428
  }
6402
- const id = typeof command.id === "string" ? command.id : void 0;
6403
- try {
6404
- if (command.type === "prompt") {
6405
- await this.session.prompt(String(command.message ?? ""));
6406
- this.writeStdout(JSON.stringify({ id, type: "response", command: "prompt", success: true }));
6407
- } else if (command.type === "steer") {
6408
- await this.session.steer(String(command.message ?? ""));
6409
- this.writeStdout(JSON.stringify({ id, type: "response", command: "steer", success: true }));
6410
- } else {
6411
- throw new Error(`Unsupported Pi SDK command: ${command.type || "unknown"}`);
6429
+ if (this.didClose) return { ok: false, reason: "closed" };
6430
+ this.started = true;
6431
+ const sessionId = input.sessionId || this.ctx.config.sessionId || randomUUID3();
6432
+ this.mappingState.sessionId = sessionId;
6433
+ this.setCurrentSessionId(sessionId);
6434
+ const session = await this.sessionFactory({
6435
+ ...this.ctx,
6436
+ config: {
6437
+ ...this.ctx.config,
6438
+ sessionId
6412
6439
  }
6413
- } catch (error) {
6414
- this.writeStdout(JSON.stringify({
6415
- id,
6416
- type: "response",
6417
- command: command.type || "unknown",
6418
- success: false,
6419
- error: piErrorMessage(error)
6420
- }));
6440
+ }, sessionId);
6441
+ this.session = session;
6442
+ this.mappingState.sessionId = session.sessionId;
6443
+ this.setCurrentSessionId(session.sessionId);
6444
+ this.unsubscribe = session.subscribe((event) => {
6445
+ for (const parsed of mapPiSdkEventToParsedEvents(event, this.mappingState)) {
6446
+ this.events.emit("runtime_event", parsed);
6447
+ }
6448
+ });
6449
+ this.emitSessionInit();
6450
+ this.launchPrompt(input.text);
6451
+ return { ok: true, acceptedAs: "prompt" };
6452
+ }
6453
+ send(input) {
6454
+ if (this.didClose) return { ok: false, reason: "closed" };
6455
+ const session = this.session;
6456
+ if (!session) return { ok: false, reason: "closed" };
6457
+ if (input.mode === "busy") {
6458
+ this.deferSdkCall(() => session.steer(input.text));
6459
+ return { ok: true, acceptedAs: "steer" };
6460
+ }
6461
+ if (session.isStreaming) {
6462
+ return { ok: false, reason: "busy_rejected", error: "Pi session is still streaming" };
6463
+ }
6464
+ this.launchPrompt(input.text);
6465
+ return { ok: true, acceptedAs: "prompt" };
6466
+ }
6467
+ async stop(opts) {
6468
+ if (this.didClose) return;
6469
+ this.requestedStopReason = opts?.reason;
6470
+ const signal = opts?.signal ?? "SIGTERM";
6471
+ const session = this.session;
6472
+ if (session?.isStreaming) {
6473
+ try {
6474
+ await session.abort();
6475
+ } catch (error) {
6476
+ this.events.emit("stderr", piErrorMessage(error));
6477
+ }
6478
+ }
6479
+ await this.disposeSession();
6480
+ this.emitExitAndClose(null, signal);
6481
+ }
6482
+ async dispose() {
6483
+ if (this.didClose) return;
6484
+ await this.disposeSession();
6485
+ this.emitExitAndClose(0, null);
6486
+ }
6487
+ emitSessionInit() {
6488
+ const sessionId = this.mappingState.sessionId;
6489
+ if (!sessionId || this.mappingState.sessionAnnounced) return;
6490
+ this.mappingState.sessionAnnounced = true;
6491
+ this.events.emit("runtime_event", { kind: "session_init", sessionId });
6492
+ }
6493
+ launchPrompt(text) {
6494
+ const session = this.session;
6495
+ if (!session) {
6496
+ this.events.emit("runtime_event", {
6497
+ kind: "error",
6498
+ message: "Pi SDK session is not started"
6499
+ });
6500
+ return;
6421
6501
  }
6502
+ this.deferSdkCall(() => session.prompt(text));
6422
6503
  }
6423
- writeStdout(line) {
6424
- if (this.closed) return;
6425
- this.stdout.write(line + "\n");
6426
- }
6427
- writeError(error) {
6428
- if (this.closed) return;
6429
- this.stderr.write(piErrorMessage(error) + "\n");
6504
+ deferSdkCall(invoke) {
6505
+ setImmediate(() => {
6506
+ if (this.didClose) return;
6507
+ try {
6508
+ void invoke().catch((error) => {
6509
+ if (this.didClose) return;
6510
+ this.events.emit("runtime_event", {
6511
+ kind: "error",
6512
+ message: piErrorMessage(error)
6513
+ });
6514
+ });
6515
+ } catch (error) {
6516
+ if (this.didClose) return;
6517
+ this.events.emit("runtime_event", {
6518
+ kind: "error",
6519
+ message: piErrorMessage(error)
6520
+ });
6521
+ }
6522
+ });
6430
6523
  }
6431
- async shutdown(code, signal) {
6432
- if (this.closed) return;
6433
- this.closed = true;
6434
- this.exitCode = code;
6435
- this.signalCode = signal;
6524
+ async disposeSession() {
6525
+ const unsubscribe = this.unsubscribe;
6526
+ this.unsubscribe = null;
6436
6527
  try {
6437
- if (this.session.isStreaming) {
6438
- await this.session.abort();
6439
- }
6440
- } catch (error) {
6441
- this.stderr.write(piErrorMessage(error) + "\n");
6528
+ unsubscribe?.();
6529
+ } catch {
6442
6530
  }
6531
+ const session = this.session;
6532
+ this.session = null;
6443
6533
  try {
6444
- this.session.dispose();
6445
- } catch {
6534
+ session?.dispose();
6535
+ } catch (error) {
6536
+ this.events.emit("stderr", piErrorMessage(error));
6446
6537
  }
6447
- this.stdin.destroy();
6448
- this.stdout.end();
6449
- this.stderr.end();
6450
- this.emit("exit", code, signal);
6451
- this.emit("close", code, signal);
6538
+ }
6539
+ emitExitAndClose(code, signal) {
6540
+ if (this.didClose) return;
6541
+ this.didClose = true;
6542
+ const info = {
6543
+ code,
6544
+ signal,
6545
+ reason: this.requestedStopReason ? "requested" : "runtime_exit"
6546
+ };
6547
+ this.exitInfo = info;
6548
+ this.events.emit("exit", info);
6549
+ this.events.emit("close", info);
6452
6550
  }
6453
6551
  };
6454
6552
  var PiDriver = class {
@@ -6475,10 +6573,9 @@ var PiDriver = class {
6475
6573
  busyDeliveryMode = "direct";
6476
6574
  usesSlockCliForCommunication = true;
6477
6575
  sessionId = null;
6478
- sessionAnnounced = false;
6479
- sawTextDelta = false;
6480
- requestId = 0;
6481
- process = null;
6576
+ get currentSessionId() {
6577
+ return this.sessionId;
6578
+ }
6482
6579
  probe() {
6483
6580
  return {
6484
6581
  available: true,
@@ -6488,144 +6585,20 @@ var PiDriver = class {
6488
6585
  async detectModels() {
6489
6586
  return detectPiModels();
6490
6587
  }
6491
- async spawn(ctx) {
6492
- this.sessionId = ctx.config.sessionId || randomUUID3();
6493
- this.sessionAnnounced = false;
6494
- this.sawTextDelta = false;
6495
- this.requestId = 0;
6496
- const sessionDir = buildPiSessionDir(ctx.workingDirectory);
6497
- mkdirSync4(sessionDir, { recursive: true });
6498
- const spawnEnv = await buildPiSpawnEnv(ctx);
6499
- const agentDir = spawnEnv.PI_CODING_AGENT_DIR || getAgentDir();
6500
- const authStorage = AuthStorage.create(path11.join(agentDir, "auth.json"));
6501
- const modelRegistry = ModelRegistry.create(authStorage, path11.join(agentDir, "models.json"));
6502
- const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
6503
- const model = resolvePiModelFromRegistry(launchRuntimeFields.model, modelRegistry);
6504
- if (launchRuntimeFields.model && launchRuntimeFields.model !== "default" && !model) {
6505
- throw new Error(`Pi model not found: ${launchRuntimeFields.model}`);
6506
- }
6507
- const settingsManager = SettingsManager.inMemory({ compaction: { enabled: false } });
6508
- const resourceLoader = new DefaultResourceLoader({
6509
- cwd: ctx.workingDirectory,
6510
- agentDir,
6511
- settingsManager,
6512
- systemPromptOverride: () => ctx.standingPrompt
6513
- });
6514
- await resourceLoader.reload();
6515
- const existingSessionFile = ctx.config.sessionId ? findPiSessionFile(sessionDir, ctx.config.sessionId) : null;
6516
- const sessionManager = existingSessionFile ? SessionManager.open(existingSessionFile, sessionDir, ctx.workingDirectory) : SessionManager.create(ctx.workingDirectory, sessionDir, { id: this.sessionId });
6517
- const { session } = await createAgentSession({
6518
- cwd: ctx.workingDirectory,
6519
- agentDir,
6520
- model,
6521
- thinkingLevel: launchRuntimeFields.reasoningEffort,
6522
- authStorage,
6523
- modelRegistry,
6524
- resourceLoader,
6525
- customTools: [
6526
- createBashTool(ctx.workingDirectory, {
6527
- spawnHook: (spawnContext) => ({
6528
- ...spawnContext,
6529
- env: {
6530
- ...spawnContext.env,
6531
- ...spawnEnv
6532
- }
6533
- })
6534
- })
6535
- ],
6536
- sessionManager,
6537
- settingsManager
6538
- });
6539
- this.sessionId = session.sessionId;
6540
- const proc = new PiSdkProcess(session);
6541
- this.process = proc;
6542
- setImmediate(() => {
6543
- if (this.process === proc && !proc.killed) {
6544
- this.sendRpcCommand("prompt", { message: ctx.prompt });
6545
- }
6588
+ createSession(ctx) {
6589
+ this.sessionId = ctx.config.sessionId || null;
6590
+ return new PiSdkRuntimeSession(ctx, (sessionId) => {
6591
+ this.sessionId = sessionId;
6546
6592
  });
6547
- return { process: proc };
6548
6593
  }
6549
- parseLine(line) {
6550
- let event;
6551
- try {
6552
- event = JSON.parse(line);
6553
- } catch {
6554
- return [];
6555
- }
6556
- const events = [];
6557
- if (event.type === "session" && event.id) {
6558
- this.sessionId = event.id;
6559
- if (!this.sessionAnnounced) {
6560
- this.sessionAnnounced = true;
6561
- events.push({ kind: "session_init", sessionId: event.id });
6562
- }
6563
- return events;
6564
- }
6565
- if (!this.sessionAnnounced && this.sessionId) {
6566
- events.push({ kind: "session_init", sessionId: this.sessionId });
6567
- this.sessionAnnounced = true;
6568
- }
6569
- if (event.type === "response") {
6570
- if (event.data?.sessionId && event.data.sessionId !== this.sessionId) {
6571
- this.sessionId = event.data.sessionId;
6572
- }
6573
- if (event.success === false) {
6574
- events.push({ kind: "error", message: piErrorMessage(event.error) });
6575
- }
6576
- return events;
6577
- }
6578
- if (event.type === "message_start" && event.message?.role === "assistant") {
6579
- this.sawTextDelta = false;
6580
- return events;
6581
- }
6582
- const assistantEvent = event.assistantMessageEvent;
6583
- if (event.type === "message_update" && assistantEvent) {
6584
- switch (assistantEvent.type) {
6585
- case "thinking_delta":
6586
- if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
6587
- events.push({ kind: "thinking", text: assistantEvent.delta });
6588
- }
6589
- break;
6590
- case "text_delta":
6591
- if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
6592
- this.sawTextDelta = true;
6593
- events.push({ kind: "text", text: assistantEvent.delta });
6594
- }
6595
- break;
6596
- case "thinking_start":
6597
- case "text_start":
6598
- break;
6599
- case "tool_use":
6600
- case "tool_call":
6601
- case "tool_start":
6602
- events.push({
6603
- kind: "tool_call",
6604
- name: assistantEvent.name || assistantEvent.toolName || "unknown_tool",
6605
- input: assistantEvent.input ?? assistantEvent.parameters ?? {}
6606
- });
6607
- break;
6608
- case "text_end":
6609
- if (!this.sawTextDelta && typeof assistantEvent.content === "string" && assistantEvent.content.length > 0) {
6610
- events.push({ kind: "text", text: assistantEvent.content });
6611
- }
6612
- break;
6613
- }
6614
- return events;
6615
- }
6616
- if (event.type === "agent_end") {
6617
- events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
6618
- } else if (event.type === "error") {
6619
- events.push({ kind: "error", message: piErrorMessage(event.error ?? event.message) });
6620
- }
6621
- return events;
6594
+ async spawn(_ctx) {
6595
+ throw new Error("PiDriver uses a native RuntimeSession; child-process spawn is unsupported");
6622
6596
  }
6623
- encodeStdinMessage(text, _sessionId, opts) {
6624
- return JSON.stringify({
6625
- id: this.nextRequestId(),
6626
- type: opts?.mode === "idle" ? "prompt" : "steer",
6627
- message: text
6628
- });
6597
+ parseLine(_line) {
6598
+ return [];
6599
+ }
6600
+ encodeStdinMessage(_text, _sessionId, _opts) {
6601
+ return null;
6629
6602
  }
6630
6603
  buildSystemPrompt(config, _agentId) {
6631
6604
  return buildCliTransportSystemPrompt(config, {
@@ -6638,18 +6611,141 @@ var PiDriver = class {
6638
6611
  messageNotificationStyle: "direct"
6639
6612
  });
6640
6613
  }
6641
- nextRequestId() {
6642
- this.requestId += 1;
6643
- return String(this.requestId);
6614
+ };
6615
+
6616
+ // src/drivers/runtimeSession.ts
6617
+ import { EventEmitter as EventEmitter2 } from "events";
6618
+ function descriptorFromDriver(driver) {
6619
+ const lifecycle = driver.lifecycle.kind === "per_turn" ? "turn_based" : "persistent_stream";
6620
+ const idle = driver.supportsStdinNotification ? "stdin" : "unsupported";
6621
+ const busy = driver.supportsStdinNotification ? "stdin_steer" : "unsupported";
6622
+ return {
6623
+ transport: "child_process",
6624
+ lifecycle,
6625
+ input: {
6626
+ initial: "start",
6627
+ idle,
6628
+ busy
6629
+ },
6630
+ readiness: "spawned",
6631
+ turnBoundary: driver.lifecycle.kind === "per_turn" ? "process_exit" : "parsed_event",
6632
+ startPolicy: driver.lifecycle.kind === "per_turn" ? driver.lifecycle.start : "immediate",
6633
+ inFlightWake: driver.lifecycle.inFlightWake,
6634
+ busyDelivery: driver.busyDeliveryMode,
6635
+ postTurn: driver.terminateProcessOnTurnEnd ? "terminate_process" : driver.endStdinOnTurnEnd ? "close_stdin" : "keep_alive"
6636
+ };
6637
+ }
6638
+ var ChildProcessRuntimeSession = class {
6639
+ constructor(driver, ctx) {
6640
+ this.driver = driver;
6641
+ this.ctx = ctx;
6642
+ this.descriptor = descriptorFromDriver(driver);
6644
6643
  }
6645
- sendRpcCommand(type, params) {
6646
- this.process?.stdin?.write(JSON.stringify({
6647
- id: this.nextRequestId(),
6648
- type,
6649
- ...params
6650
- }) + "\n");
6644
+ descriptor;
6645
+ events = new EventEmitter2();
6646
+ process = null;
6647
+ started = false;
6648
+ stdoutBuffer = "";
6649
+ requestedStopReason;
6650
+ get pid() {
6651
+ return this.process?.pid;
6652
+ }
6653
+ get currentSessionId() {
6654
+ return this.driver.currentSessionId;
6655
+ }
6656
+ get exitCode() {
6657
+ return this.process?.exitCode ?? null;
6658
+ }
6659
+ get signalCode() {
6660
+ return this.process?.signalCode ?? null;
6661
+ }
6662
+ get closed() {
6663
+ return this.process ? this.process.exitCode != null || this.process.signalCode != null : false;
6664
+ }
6665
+ on(event, cb) {
6666
+ this.events.on(event, cb);
6667
+ }
6668
+ async start(input) {
6669
+ if (this.started) {
6670
+ return { ok: false, reason: "runtime_error", error: "runtime session already started" };
6671
+ }
6672
+ this.started = true;
6673
+ const launchCtx = {
6674
+ ...this.ctx,
6675
+ prompt: input.text,
6676
+ config: {
6677
+ ...this.ctx.config,
6678
+ sessionId: input.sessionId ?? this.ctx.config.sessionId
6679
+ }
6680
+ };
6681
+ const { process: process2 } = await this.driver.spawn(launchCtx);
6682
+ this.process = process2;
6683
+ this.attachProcess(process2);
6684
+ return { ok: true, acceptedAs: "prompt" };
6685
+ }
6686
+ send(input) {
6687
+ const proc = this.process;
6688
+ if (!proc || this.closed) return { ok: false, reason: "closed" };
6689
+ const encoded = this.driver.encodeStdinMessage(input.text, input.sessionId ?? null, { mode: input.mode });
6690
+ if (!encoded) return { ok: false, reason: "unsupported" };
6691
+ proc.stdin?.write(encoded + "\n");
6692
+ return { ok: true, acceptedAs: input.mode === "busy" ? "steer" : "prompt" };
6693
+ }
6694
+ async stop(opts) {
6695
+ const proc = this.process;
6696
+ if (!proc || this.closed) return;
6697
+ this.requestedStopReason = opts?.reason;
6698
+ proc.kill(opts?.signal ?? "SIGTERM");
6699
+ if (!opts?.forceAfterMs) return;
6700
+ setTimeout(() => {
6701
+ if (!this.closed) {
6702
+ try {
6703
+ proc.kill("SIGKILL");
6704
+ } catch {
6705
+ }
6706
+ }
6707
+ }, opts.forceAfterMs).unref?.();
6708
+ }
6709
+ attachProcess(process2) {
6710
+ process2.stdout?.on("data", (chunk) => {
6711
+ const chunkText = chunk.toString();
6712
+ this.events.emit("stdout", chunkText);
6713
+ this.stdoutBuffer += chunkText;
6714
+ const lines = this.stdoutBuffer.split("\n");
6715
+ this.stdoutBuffer = lines.pop() || "";
6716
+ for (const line of lines) {
6717
+ if (!line.trim()) continue;
6718
+ for (const event of this.driver.parseLine(line)) {
6719
+ this.events.emit("runtime_event", event);
6720
+ }
6721
+ }
6722
+ });
6723
+ process2.stderr?.on("data", (chunk) => {
6724
+ const text = chunk.toString().trim();
6725
+ if (text) this.events.emit("stderr", text);
6726
+ });
6727
+ process2.on("error", (err) => {
6728
+ this.events.emit("error", err);
6729
+ });
6730
+ process2.on("exit", (code, signal) => {
6731
+ this.events.emit("exit", {
6732
+ code,
6733
+ signal,
6734
+ reason: this.requestedStopReason ? "requested" : "runtime_exit"
6735
+ });
6736
+ });
6737
+ process2.on("close", (code, signal) => {
6738
+ this.events.emit("close", {
6739
+ code,
6740
+ signal,
6741
+ reason: this.requestedStopReason ? "requested" : "runtime_exit"
6742
+ });
6743
+ });
6651
6744
  }
6652
6745
  };
6746
+ function createChildProcessRuntimeSession(driver, ctx) {
6747
+ return new ChildProcessRuntimeSession(driver, ctx);
6748
+ }
6653
6749
 
6654
6750
  // src/drivers/index.ts
6655
6751
  var driverFactories = {
@@ -6786,7 +6882,9 @@ import { createHash as createHash2 } from "crypto";
6786
6882
  var MAX_RUNTIME_ERROR_MESSAGE_EXCERPT_CHARS = 4096;
6787
6883
  var RUNTIME_AUTH_ACTION_REQUIRED_PATTERNS = [
6788
6884
  /access token could not be refreshed/i,
6885
+ /\btoken_(?:revoked|invalidated)\b/i,
6789
6886
  /refresh token was already used/i,
6887
+ /access token.*invalidated/i,
6790
6888
  /authentication token has been invalidated/i,
6791
6889
  /logged out or signed in to another account/i,
6792
6890
  /not logged in/i,
@@ -6868,6 +6966,9 @@ function classifyRuntimeError(message, httpStatus) {
6868
6966
  return "ProviderApiError";
6869
6967
  }
6870
6968
  if (isRuntimeAuthActionRequiredText(message)) return "AuthError";
6969
+ if (/\bmodel\b.*\bnot supported\b/i.test(message) || /\bunsupported\b.*\bmodel\b/i.test(message) || /\bmodel\b.*\bnot available\b/i.test(message)) {
6970
+ return "ModelConfigError";
6971
+ }
6871
6972
  if (/\b(?:ETIMEDOUT|timeout|timed out)\b/i.test(message)) return "TimeoutError";
6872
6973
  if (/\b(?:ECONNRESET|EPIPE|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(message) || /\bUnable to connect to API\b/i.test(message)) {
6873
6974
  return "ProviderConnectionError";
@@ -6900,6 +7001,8 @@ function classifyRuntimeErrorReason(runtimeErrorClass) {
6900
7001
  return "auth_failed";
6901
7002
  case "NotFoundError":
6902
7003
  return "not_found";
7004
+ case "ModelConfigError":
7005
+ return "model_config_error";
6903
7006
  case "ProviderServerError":
6904
7007
  return "provider_server_error";
6905
7008
  case "ProviderApiError":
@@ -7043,6 +7146,15 @@ var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS = 3;
7043
7146
  function assertNeverApmEffect(effect) {
7044
7147
  throw new Error(`Unhandled APM gated steering effect: ${String(effect)}`);
7045
7148
  }
7149
+ function requireImmediateRuntimeSendResult(result, context) {
7150
+ if (typeof result.then === "function") {
7151
+ throw new Error(`RuntimeSession.send returned async result in synchronous APM path: ${context}`);
7152
+ }
7153
+ return result;
7154
+ }
7155
+ function runtimeSendFailureOutcome(result) {
7156
+ return !result.ok && result.reason === "unsupported" ? "encode_failed" : "send_failed";
7157
+ }
7046
7158
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS = 250;
7047
7159
  var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
7048
7160
  var WORKSPACE_IMAGE_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
@@ -7901,7 +8013,7 @@ function classifyTerminalFailure(ap) {
7901
8013
  for (const text of candidates) {
7902
8014
  const diagnostics = buildRuntimeErrorDiagnosticEnvelope(text);
7903
8015
  const lower = text.toLowerCase();
7904
- if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found") || diagnostics.spanAttrs.runtime_error_action_required === true || isProviderStreamFailureText(text) || isRuntimeStartTimeoutText(text)) {
8016
+ if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found") || lower.includes("model is not supported") || lower.includes("unsupported model") || /\bmodel\b.*\bnot supported\b/i.test(text) || diagnostics.spanAttrs.runtime_error_action_required === true || isProviderStreamFailureText(text) || isRuntimeStartTimeoutText(text)) {
7905
8017
  const actionRequired = diagnostics.spanAttrs.runtime_error_action_required === true;
7906
8018
  return {
7907
8019
  detail: actionRequired ? formatRuntimeLoginRequiredMessage(ap.driver.id) : text,
@@ -7911,6 +8023,16 @@ function classifyTerminalFailure(ap) {
7911
8023
  }
7912
8024
  return null;
7913
8025
  }
8026
+ function classifyStickyTerminalFailure(ap) {
8027
+ const terminalFailure = classifyTerminalFailure(ap);
8028
+ if (!terminalFailure) return null;
8029
+ if (terminalFailure.actionRequired) return terminalFailure;
8030
+ if (/\bmodel\b.*\bnot supported\b/i.test(terminalFailure.detail)) return terminalFailure;
8031
+ if (/\bunsupported\b.*\bmodel\b/i.test(terminalFailure.detail)) return terminalFailure;
8032
+ if (isProviderStreamFailureText(terminalFailure.detail)) return null;
8033
+ if (isRuntimeStartTimeoutText(terminalFailure.detail)) return null;
8034
+ return null;
8035
+ }
7914
8036
  function isProviderStreamFailureText(text) {
7915
8037
  return /stream closed before response\.completed|error decoding response body/i.test(text);
7916
8038
  }
@@ -7980,7 +8102,7 @@ function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
7980
8102
  if (ap.lastActivityDetail) {
7981
8103
  context.push(`after ${ap.lastActivityDetail}`);
7982
8104
  }
7983
- if (ap.driver.busyDeliveryMode === "gated") {
8105
+ if (ap.runtime.descriptor.busyDelivery === "gated") {
7984
8106
  context.push(`phase=${ap.gatedSteering.phase}`);
7985
8107
  }
7986
8108
  if (ap.gatedSteering.outstandingToolUses > 0) {
@@ -8012,10 +8134,10 @@ function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
8012
8134
  sessionIdPresent: Boolean(ap.sessionId),
8013
8135
  inboxCount: ap.inbox.length,
8014
8136
  pendingNotificationCount: ap.notifications.pendingCount,
8015
- processPidPresent: typeof ap.process.pid === "number",
8137
+ processPidPresent: typeof ap.runtime.pid === "number",
8016
8138
  busyDeliveryMode: ap.driver.busyDeliveryMode,
8017
8139
  supportsStdinNotification: ap.driver.supportsStdinNotification,
8018
- gatedPhase: ap.driver.busyDeliveryMode === "gated" ? ap.gatedSteering.phase : void 0,
8140
+ gatedPhase: ap.runtime.descriptor.busyDelivery === "gated" ? ap.gatedSteering.phase : void 0,
8019
8141
  outstandingToolUses: ap.gatedSteering.outstandingToolUses,
8020
8142
  compacting: ap.gatedSteering.compacting,
8021
8143
  recentStderrCount: ap.recentStderr.length,
@@ -8158,8 +8280,10 @@ var RUNTIME_TELEMETRY_RESERVED_ATTR_KEYS = /* @__PURE__ */ new Set([
8158
8280
  "runtimeResultId",
8159
8281
  "daemonVersion",
8160
8282
  "daemon_version",
8283
+ "daemon_version_present",
8161
8284
  "computerVersion",
8162
- "computer_version"
8285
+ "computer_version",
8286
+ "computer_version_present"
8163
8287
  ]);
8164
8288
  function sanitizeRuntimeTelemetryPayloadAttrs(attrs) {
8165
8289
  const sanitized = {};
@@ -8170,17 +8294,17 @@ function sanitizeRuntimeTelemetryPayloadAttrs(attrs) {
8170
8294
  return sanitized;
8171
8295
  }
8172
8296
  function getMessageDeliveryText(driver) {
8173
- return driver.supportsStdinNotification ? "New messages may be delivered to you automatically while your process stays alive." : "The daemon will automatically restart you when new messages arrive.";
8297
+ return driver.supportsStdinNotification ? "New inbox updates may be delivered to you automatically while your process stays alive." : "The daemon will automatically restart you with an inbox update when new messages arrive.";
8174
8298
  }
8175
8299
  function getBusyDeliveryNote(driver) {
8176
8300
  if (!driver.supportsStdinNotification) return "";
8177
8301
  if (driver.busyDeliveryMode === "direct") {
8178
- return "\n\nNote: While you are busy, the daemon may write batched inbox-count notifications into your active turn. Call check_messages to read the pending messages before context-sensitive side effects.";
8302
+ return "\n\nNote: While you are busy, the daemon may write batched content-free inbox updates into your active turn. Finish your current step, then check the inbox or read messages at a natural breakpoint.";
8179
8303
  }
8180
8304
  if (driver.busyDeliveryMode === "gated") {
8181
- return "\n\nNote: While you are busy, the daemon may write batched inbox-count notifications into your active turn at runtime-observed safe boundaries. Call check_messages to read the pending messages before context-sensitive side effects.";
8305
+ return "\n\nNote: While you are busy, the daemon may write batched content-free inbox updates into your active turn at runtime-observed safe boundaries. Finish your current step, then check the inbox or read messages at a natural breakpoint.";
8182
8306
  }
8183
- return "\n\nNote: While you are busy, you may receive [Slock inbox notice: ...] messages. Finish your current step, then call check_messages to check for messages.";
8307
+ return "\n\nNote: While you are busy, you may receive [Slock inbox notice: ...] messages. Finish your current step, then check the inbox or read messages at a natural breakpoint.";
8184
8308
  }
8185
8309
  var NATIVE_STANDING_PROMPT_STARTUP_INPUT = (
8186
8310
  // Claude Code 2.1.114 treats "follow your system prompt" style user turns as
@@ -8216,9 +8340,11 @@ var AgentProcessManager = class _AgentProcessManager {
8216
8340
  stdinNotificationRetryMs;
8217
8341
  cliTransportTraceDir = null;
8218
8342
  deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
8219
- processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
8343
+ runtimeExitTraceAttrs = /* @__PURE__ */ new WeakMap();
8220
8344
  agentVisibleBoundaries = /* @__PURE__ */ new Map();
8221
8345
  agentVisibleMessageIds = /* @__PURE__ */ new Map();
8346
+ daemonVersion;
8347
+ computerVersion;
8222
8348
  constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
8223
8349
  this.chatBridgePath = chatBridgePath;
8224
8350
  this.slockCliPath = opts.slockCliPath ?? "";
@@ -8230,6 +8356,8 @@ var AgentProcessManager = class _AgentProcessManager {
8230
8356
  this.driverResolver = opts.driverResolver || getDriver;
8231
8357
  this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
8232
8358
  this.tracer = opts.tracer ?? noopTracer;
8359
+ this.daemonVersion = opts.daemonVersion?.trim() || null;
8360
+ this.computerVersion = opts.computerVersion?.trim() || null;
8233
8361
  this.stdinNotificationRetryMs = Math.max(
8234
8362
  0,
8235
8363
  Math.floor(opts.stdinNotificationRetryMs ?? STDIN_NOTIFICATION_RETRY_DELAY_MS)
@@ -8470,7 +8598,7 @@ var AgentProcessManager = class _AgentProcessManager {
8470
8598
  });
8471
8599
  span.end(status);
8472
8600
  }
8473
- startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
8601
+ startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient = false) {
8474
8602
  return {
8475
8603
  agentId,
8476
8604
  launchId,
@@ -8479,6 +8607,7 @@ var AgentProcessManager = class _AgentProcessManager {
8479
8607
  session_id_present: Boolean(config.sessionId),
8480
8608
  launch_id_present: Boolean(launchId),
8481
8609
  wake_message_present: Boolean(wakeMessage),
8610
+ wake_message_transient: Boolean(wakeMessage && wakeMessageTransient),
8482
8611
  unread_channels_count: unreadSummary ? Object.keys(unreadSummary).length : 0,
8483
8612
  resume_prompt_present: Boolean(resumePrompt),
8484
8613
  queue_depth: this.agentStartQueue.length,
@@ -8490,6 +8619,9 @@ var AgentProcessManager = class _AgentProcessManager {
8490
8619
  getDeliveryTraceContext(message) {
8491
8620
  return this.deliveryTraceContexts.get(message) ?? {};
8492
8621
  }
8622
+ isTransientDelivery(message) {
8623
+ return this.getDeliveryTraceContext(message).transient === true;
8624
+ }
8493
8625
  deliveryTraceAttrs(agentId, message, attrs = {}) {
8494
8626
  const context = this.getDeliveryTraceContext(message);
8495
8627
  const deliveryCorrelationId = context.deliveryId ?? message.message_id;
@@ -8501,14 +8633,15 @@ var AgentProcessManager = class _AgentProcessManager {
8501
8633
  sender_type: message.sender_type,
8502
8634
  messageId: message.message_id,
8503
8635
  message_id_present: Boolean(message.message_id),
8636
+ transient_delivery: context.transient === true,
8504
8637
  ...attrs
8505
8638
  };
8506
8639
  }
8507
- async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
8508
- this.recordDaemonTrace("daemon.agent.start.requested", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
8640
+ async startAgent(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient = false) {
8641
+ this.recordDaemonTrace("daemon.agent.start.requested", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient));
8509
8642
  if (this.agents.has(agentId)) {
8510
8643
  this.recordDaemonTrace("daemon.agent.start.ignored", {
8511
- ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
8644
+ ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
8512
8645
  reason: "already_running"
8513
8646
  });
8514
8647
  logger.info(`[Agent ${agentId}] Start ignored (already running)`);
@@ -8516,7 +8649,7 @@ var AgentProcessManager = class _AgentProcessManager {
8516
8649
  }
8517
8650
  if (this.agentsStarting.has(agentId)) {
8518
8651
  this.recordDaemonTrace("daemon.agent.start.ignored", {
8519
- ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
8652
+ ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
8520
8653
  reason: "already_starting"
8521
8654
  });
8522
8655
  logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
@@ -8524,7 +8657,7 @@ var AgentProcessManager = class _AgentProcessManager {
8524
8657
  }
8525
8658
  if (this.queuedAgentStarts.has(agentId)) {
8526
8659
  this.recordDaemonTrace("daemon.agent.start.ignored", {
8527
- ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
8660
+ ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
8528
8661
  reason: "already_queued"
8529
8662
  });
8530
8663
  logger.info(`[Agent ${agentId}] Start ignored (startup already queued)`);
@@ -8535,6 +8668,7 @@ var AgentProcessManager = class _AgentProcessManager {
8535
8668
  agentId,
8536
8669
  config,
8537
8670
  wakeMessage,
8671
+ wakeMessageTransient,
8538
8672
  unreadSummary,
8539
8673
  resumePrompt,
8540
8674
  launchId,
@@ -8543,7 +8677,7 @@ var AgentProcessManager = class _AgentProcessManager {
8543
8677
  };
8544
8678
  this.agentStartQueue.push(item);
8545
8679
  this.queuedAgentStarts.set(agentId, item);
8546
- this.recordDaemonTrace("daemon.agent.start.queued", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
8680
+ this.recordDaemonTrace("daemon.agent.start.queued", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient));
8547
8681
  logger.info(
8548
8682
  `[Agent ${agentId}] Start queued (queue=${this.agentStartQueue.length}, active=${this.activeAgentStartCount}, max=${this.maxConcurrentAgentStarts}, interval=${this.agentStartIntervalMs}ms)`
8549
8683
  );
@@ -8560,7 +8694,7 @@ var AgentProcessManager = class _AgentProcessManager {
8560
8694
  const waitMs = shouldRateLimit ? Math.max(0, this.agentStartIntervalMs - elapsed) : 0;
8561
8695
  if (waitMs > 0) {
8562
8696
  this.recordDaemonTrace("daemon.agent.start.rate_limited", {
8563
- ...this.startQueueTraceAttrs(next.agentId, next.config, next.wakeMessage, next.unreadSummary, next.resumePrompt, next.launchId),
8697
+ ...this.startQueueTraceAttrs(next.agentId, next.config, next.wakeMessage, next.unreadSummary, next.resumePrompt, next.launchId, next.wakeMessageTransient),
8564
8698
  wait_ms: waitMs
8565
8699
  });
8566
8700
  this.agentStartPumpTimer = setTimeout(() => {
@@ -8573,7 +8707,7 @@ var AgentProcessManager = class _AgentProcessManager {
8573
8707
  if (!item) return;
8574
8708
  if (this.queuedAgentStarts.get(item.agentId) !== item) {
8575
8709
  this.recordDaemonTrace("daemon.agent.start.skipped", {
8576
- ...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
8710
+ ...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId, item.wakeMessageTransient),
8577
8711
  reason: "stale_queue_item"
8578
8712
  });
8579
8713
  this.pumpAgentStartQueue();
@@ -8582,7 +8716,7 @@ var AgentProcessManager = class _AgentProcessManager {
8582
8716
  this.queuedAgentStarts.delete(item.agentId);
8583
8717
  if (this.agents.has(item.agentId) || this.agentsStarting.has(item.agentId)) {
8584
8718
  this.recordDaemonTrace("daemon.agent.start.skipped", {
8585
- ...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
8719
+ ...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId, item.wakeMessageTransient),
8586
8720
  reason: "already_running_or_starting"
8587
8721
  });
8588
8722
  logger.info(`[Agent ${item.agentId}] Queued start skipped (already running or starting)`);
@@ -8596,14 +8730,15 @@ var AgentProcessManager = class _AgentProcessManager {
8596
8730
  logger.info(
8597
8731
  `[Agent ${item.agentId}] Dequeued start (remaining=${this.agentStartQueue.length}, active=${this.activeAgentStartCount})`
8598
8732
  );
8599
- this.recordDaemonTrace("daemon.agent.start.dequeued", this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId));
8733
+ this.recordDaemonTrace("daemon.agent.start.dequeued", this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId, item.wakeMessageTransient));
8600
8734
  this.startAgentNow(
8601
8735
  item.agentId,
8602
8736
  item.config,
8603
8737
  item.wakeMessage,
8604
8738
  item.unreadSummary,
8605
8739
  item.resumePrompt,
8606
- item.launchId
8740
+ item.launchId,
8741
+ item.wakeMessageTransient ?? false
8607
8742
  ).then(() => {
8608
8743
  this.releaseAgentStartSlot(item.agentId, "spawn attempted");
8609
8744
  item.resolve();
@@ -8638,7 +8773,7 @@ var AgentProcessManager = class _AgentProcessManager {
8638
8773
  this.agentStartPumpTimer = null;
8639
8774
  }
8640
8775
  this.recordDaemonTrace("daemon.agent.start.cancelled", {
8641
- ...this.startQueueTraceAttrs(agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
8776
+ ...this.startQueueTraceAttrs(agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId, item.wakeMessageTransient),
8642
8777
  reason
8643
8778
  }, "cancelled");
8644
8779
  logger.info(`[Agent ${agentId}] Queued start cancelled (${reason})`);
@@ -8649,7 +8784,7 @@ var AgentProcessManager = class _AgentProcessManager {
8649
8784
  for (const item of this.agentStartQueue) {
8650
8785
  if (this.queuedAgentStarts.get(item.agentId) === item) {
8651
8786
  this.recordDaemonTrace("daemon.agent.start.cancelled", {
8652
- ...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId),
8787
+ ...this.startQueueTraceAttrs(item.agentId, item.config, item.wakeMessage, item.unreadSummary, item.resumePrompt, item.launchId, item.wakeMessageTransient),
8653
8788
  reason
8654
8789
  }, "cancelled");
8655
8790
  logger.info(`[Agent ${item.agentId}] Queued start cancelled (${reason})`);
@@ -8664,10 +8799,10 @@ var AgentProcessManager = class _AgentProcessManager {
8664
8799
  this.agentStartPumpTimer = null;
8665
8800
  }
8666
8801
  }
8667
- async startAgentNow(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId) {
8802
+ async startAgentNow(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient = false) {
8668
8803
  if (this.agents.has(agentId)) {
8669
8804
  this.recordDaemonTrace("daemon.agent.spawn.skipped", {
8670
- ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
8805
+ ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
8671
8806
  reason: "already_running"
8672
8807
  });
8673
8808
  logger.info(`[Agent ${agentId}] Start ignored (already running)`);
@@ -8675,14 +8810,15 @@ var AgentProcessManager = class _AgentProcessManager {
8675
8810
  }
8676
8811
  if (this.agentsStarting.has(agentId)) {
8677
8812
  this.recordDaemonTrace("daemon.agent.spawn.skipped", {
8678
- ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
8813
+ ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
8679
8814
  reason: "already_starting"
8680
8815
  });
8681
8816
  logger.info(`[Agent ${agentId}] Start ignored (startup in progress)`);
8682
8817
  return;
8683
8818
  }
8684
8819
  this.agentsStarting.add(agentId);
8685
- this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
8820
+ this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient));
8821
+ let agentProcess = null;
8686
8822
  try {
8687
8823
  const driver = this.driverResolver(config.runtime || "claude");
8688
8824
  const legacyWakeRuntimeProfile = wakeMessage ? runtimeProfileNotificationFromMessage(wakeMessage) : null;
@@ -8735,6 +8871,8 @@ var AgentProcessManager = class _AgentProcessManager {
8735
8871
  const standingPrompt = driver.buildSystemPrompt(runtimeConfig, agentId);
8736
8872
  let prompt;
8737
8873
  let promptSource;
8874
+ let wakeMessageDeliveredAsInboxUpdate = false;
8875
+ const startingInboxMessages = this.startingInboxes.get(agentId) || [];
8738
8876
  if (runtimeConfig.runtimeProfileControl && !wakeMessage) {
8739
8877
  prompt = driver.supportsNativeStandingPrompt ? NATIVE_STANDING_PROMPT_STARTUP_INPUT : formatRuntimeProfileControlStartupInput(runtimeConfig.runtimeProfileControl, driver);
8740
8878
  promptSource = "runtime_profile_control";
@@ -8743,14 +8881,24 @@ var AgentProcessManager = class _AgentProcessManager {
8743
8881
  prompt += getBusyDeliveryNote(driver);
8744
8882
  promptSource = "resume_prompt";
8745
8883
  } else if (wakeMessage) {
8746
- const runtimeProfileControlPrompt = formatRuntimeProfileControlPrompt([wakeMessage]);
8747
- const channelLabel = formatChannelLabel(wakeMessage);
8748
- prompt = runtimeProfileControlPrompt ?? `New message received:
8749
-
8750
- ${formatIncomingMessage(wakeMessage, driver)}`;
8751
- promptSource = runtimeProfileControlPrompt ? "runtime_profile_control_message" : "wake_message";
8752
- if (!runtimeProfileControlPrompt && unreadSummary && Object.keys(unreadSummary).length > 0) {
8753
- const otherUnread = Object.entries(unreadSummary).filter(([key]) => key !== channelLabel);
8884
+ const transientWakeMessage = wakeMessageTransient === true;
8885
+ const runtimeProfileControlPrompt = transientWakeMessage ? null : formatRuntimeProfileControlPrompt([wakeMessage]);
8886
+ if (transientWakeMessage) {
8887
+ prompt = `System notice received:
8888
+
8889
+ ${formatIncomingMessage(wakeMessage, driver)}
8890
+
8891
+ Respond as appropriate. Complete all your work before stopping.
8892
+ ${RESPONSE_TARGET_HINT}`;
8893
+ } else if (runtimeProfileControlPrompt) {
8894
+ prompt = runtimeProfileControlPrompt;
8895
+ } else {
8896
+ wakeMessageDeliveredAsInboxUpdate = true;
8897
+ prompt = this.formatInboxUpdateRuntimeInput([wakeMessage, ...startingInboxMessages], driver);
8898
+ }
8899
+ promptSource = transientWakeMessage ? "transient_wake_message" : runtimeProfileControlPrompt ? "runtime_profile_control_message" : "wake_inbox_update";
8900
+ if (!transientWakeMessage && !runtimeProfileControlPrompt && unreadSummary && Object.keys(unreadSummary).length > 0) {
8901
+ const otherUnread = Object.entries(unreadSummary);
8754
8902
  if (otherUnread.length > 0) {
8755
8903
  prompt += `
8756
8904
 
@@ -8761,18 +8909,9 @@ You also have unread messages in other channels:`;
8761
8909
  }
8762
8910
  prompt += `
8763
8911
 
8764
- Use ${communicationCommand(driver, "read_history")} to catch up, or respond to the message above first.`;
8912
+ Use the inbox/read commands at a natural breakpoint if you choose to inspect those targets.`;
8765
8913
  }
8766
8914
  }
8767
- if (!runtimeProfileControlPrompt) {
8768
- prompt += `
8769
-
8770
- Respond as appropriate \u2014 ${dynamicReplyInstruction(driver)}, or take action as needed. Complete ALL your work before stopping.
8771
- ${RESPONSE_TARGET_HINT}
8772
-
8773
- IMPORTANT: If the message requires multi-step work (e.g. research, code changes, testing), complete ALL steps before stopping. Sending a progress update does NOT mean your task is done \u2014 only stop when you have NO more work to do. ${getMessageDeliveryText(driver)}`;
8774
- prompt += getBusyDeliveryNote(driver);
8775
- }
8776
8915
  } else if (isResume && unreadSummary && Object.keys(unreadSummary).length > 0) {
8777
8916
  prompt = `You have unread messages from while you were offline:`;
8778
8917
  for (const [ch, count] of Object.entries(unreadSummary)) {
@@ -8815,7 +8954,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8815
8954
  this.sendAgentStatus(agentId, "active", launchId || null);
8816
8955
  this.broadcastActivity(agentId, "online", "Process idle");
8817
8956
  this.recordDaemonTrace("daemon.agent.spawn.deferred", {
8818
- ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId),
8957
+ ...this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
8819
8958
  pending_messages_count: pendingMessages.length,
8820
8959
  reason: "defer_until_concrete_message"
8821
8960
  });
@@ -8825,7 +8964,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8825
8964
  }
8826
8965
  return;
8827
8966
  }
8828
- const { process: proc } = await driver.spawn({
8967
+ const runtimeContext = {
8829
8968
  agentId,
8830
8969
  config: effectiveConfig,
8831
8970
  standingPrompt,
@@ -8837,17 +8976,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8837
8976
  launchId: launchId || null,
8838
8977
  agentCredentialProxyInboxCoordinator: this.createAgentProxyInboxCoordinator(agentId),
8839
8978
  cliTransportTraceDir: this.cliTransportTraceDir
8840
- });
8841
- this.recordDaemonTrace("daemon.agent.spawn.created", {
8842
- ...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, launchId),
8843
- detached: false,
8844
- new_session: false,
8845
- process_pid_present: typeof proc.pid === "number"
8846
- });
8847
- const agentProcess = {
8848
- process: proc,
8979
+ };
8980
+ const runtime = driver.createSession?.(runtimeContext) ?? createChildProcessRuntimeSession(driver, runtimeContext);
8981
+ agentProcess = {
8982
+ runtime,
8849
8983
  driver,
8850
- inbox: this.startingInboxes.get(agentId) || [],
8984
+ inbox: wakeMessageDeliveredAsInboxUpdate && wakeMessage ? [wakeMessage, ...startingInboxMessages] : startingInboxMessages,
8851
8985
  config: runtimeConfig,
8852
8986
  sessionId: runtimeConfig.sessionId || null,
8853
8987
  launchId: launchId || null,
@@ -8890,30 +9024,22 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8890
9024
  if (runtimeConfig.runtimeProfileControl) {
8891
9025
  this.ackInjectedRuntimeProfileControl(agentId, runtimeConfig.runtimeProfileControl, agentProcess.launchId);
8892
9026
  }
8893
- if (wakeMessage) {
9027
+ if (wakeMessageDeliveredAsInboxUpdate) {
9028
+ this.recordInboxUpdateProjection(agentId, agentProcess, agentProcess.inbox, "spawn_wake_inbox_update", "wake", prompt);
9029
+ } else if (wakeMessage && !wakeMessageTransient) {
8894
9030
  this.consumeVisibleMessages(agentId, { messages: [wakeMessage], source: "spawn_wake_message" });
8895
9031
  this.ackInjectedRuntimeProfileMessages(agentId, [wakeMessage], agentProcess.launchId);
8896
9032
  }
8897
- let buffer = "";
8898
- proc.stdout?.on("data", (chunk) => {
8899
- const chunkText = chunk.toString();
9033
+ runtime.on("stdout", (chunkText) => {
8900
9034
  const current = this.agents.get(agentId);
8901
9035
  if (current) {
8902
9036
  current.recentStdout = pushRecentStdout(current.recentStdout, chunkText);
8903
9037
  }
8904
- buffer += chunkText;
8905
- const lines = buffer.split("\n");
8906
- buffer = lines.pop() || "";
8907
- for (const line of lines) {
8908
- if (!line.trim()) continue;
8909
- const events = driver.parseLine(line);
8910
- for (const event of events) {
8911
- this.handleParsedEvent(agentId, event, driver);
8912
- }
8913
- }
8914
9038
  });
8915
- proc.stderr?.on("data", (chunk) => {
8916
- const text = chunk.toString().trim();
9039
+ runtime.on("runtime_event", (event) => {
9040
+ this.handleParsedEvent(agentId, event, driver);
9041
+ });
9042
+ runtime.on("stderr", (text) => {
8917
9043
  if (!text) return;
8918
9044
  const current = this.agents.get(agentId);
8919
9045
  if (driver.id === "codex" && isCodexProviderReconnectLog(text)) {
@@ -8938,7 +9064,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8938
9064
  }
8939
9065
  logger.error(`[Agent ${agentId} stderr]: ${text}`);
8940
9066
  });
8941
- proc.on("error", (err) => {
9067
+ runtime.on("error", (err) => {
8942
9068
  const current = this.agents.get(agentId);
8943
9069
  if (current) {
8944
9070
  current.spawnError = err.message;
@@ -8953,9 +9079,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8953
9079
  }, "error");
8954
9080
  logger.error(`[Agent ${agentId}] Process error: ${err.message}`);
8955
9081
  });
8956
- proc.on("exit", (code, signal) => {
9082
+ runtime.on("exit", ({ code, signal }) => {
8957
9083
  const current = this.agents.get(agentId);
8958
- if (current && current.process === proc) {
9084
+ if (current && current.runtime === runtime) {
8959
9085
  current.exitCode = code;
8960
9086
  current.exitSignal = signal;
8961
9087
  }
@@ -8970,14 +9096,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8970
9096
  runtime_trace_active: Boolean(current?.runtimeTraceSpan),
8971
9097
  inbox_count: current?.inbox.length ?? 0,
8972
9098
  pending_notification_count: current?.notifications.pendingCount ?? 0,
8973
- ...this.processExitTraceAttrs.get(proc)
9099
+ ...this.runtimeExitTraceAttrs.get(runtime)
8974
9100
  });
8975
9101
  logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
8976
9102
  });
8977
- proc.on("close", (code, signal) => {
9103
+ runtime.on("close", ({ code, signal }) => {
8978
9104
  if (this.agents.has(agentId)) {
8979
9105
  const ap = this.agents.get(agentId);
8980
- if (ap.process !== proc) return;
9106
+ if (ap.runtime !== runtime) return;
8981
9107
  ap.notifications.clearTimer();
8982
9108
  if (ap.pendingTrajectory?.timer) {
8983
9109
  clearTimeout(ap.pendingTrajectory.timer);
@@ -8991,8 +9117,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8991
9117
  const finalSignal = ap.exitSignal ?? signal;
8992
9118
  const startupTimeoutTermination = ap.expectedTerminationReason === "startup_timeout";
8993
9119
  const expectedTermination = Boolean(ap.expectedTerminationReason);
8994
- const processEndedCleanly = finalCode === 0 || expectedTermination && !ap.lastRuntimeError;
8995
- const terminalFailureDetail = processEndedCleanly ? null : classifyTerminalFailure(ap);
9120
+ const stickyTerminalFailureDetail = classifyStickyTerminalFailure(ap);
9121
+ const processEndedCleanly = !stickyTerminalFailureDetail && (finalCode === 0 || expectedTermination && !ap.lastRuntimeError);
9122
+ const terminalFailureDetail = processEndedCleanly ? null : stickyTerminalFailureDetail ?? classifyTerminalFailure(ap);
8996
9123
  const resumeRecoveryReason = resumeSessionRecoveryReason(ap);
8997
9124
  const shouldColdStartResumeSession = resumeRecoveryReason !== null;
8998
9125
  const summary = summarizeCrash(finalCode, finalSignal);
@@ -9117,14 +9244,54 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9117
9244
  }
9118
9245
  }
9119
9246
  });
9247
+ const startResult = await runtime.start({ text: prompt, sessionId: effectiveConfig.sessionId || null });
9248
+ if (!startResult.ok) {
9249
+ throw new Error(`Runtime session failed to start: ${startResult.reason}${startResult.error ? ` (${startResult.error})` : ""}`);
9250
+ }
9251
+ this.recordDaemonTrace("daemon.agent.spawn.created", {
9252
+ ...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, launchId, wakeMessageTransient),
9253
+ detached: false,
9254
+ new_session: false,
9255
+ process_pid_present: typeof runtime.pid === "number"
9256
+ });
9120
9257
  this.sendAgentStatus(agentId, "active", launchId || null);
9121
9258
  this.broadcastActivity(agentId, "working", "Starting\u2026");
9122
9259
  this.startRuntimeStartupTimeout(agentId, agentProcess);
9123
9260
  } catch (err) {
9124
9261
  this.agentsStarting.delete(agentId);
9262
+ this.cleanupFailedRuntimeStart(agentId, agentProcess, err);
9125
9263
  throw err;
9126
9264
  }
9127
9265
  }
9266
+ cleanupFailedRuntimeStart(agentId, ap, err) {
9267
+ if (!ap) return;
9268
+ if (this.agents.get(agentId) !== ap) return;
9269
+ ap.notifications.clearTimer();
9270
+ if (ap.pendingTrajectory?.timer) {
9271
+ clearTimeout(ap.pendingTrajectory.timer);
9272
+ ap.pendingTrajectory.timer = null;
9273
+ }
9274
+ if (ap.activityHeartbeat) {
9275
+ clearInterval(ap.activityHeartbeat);
9276
+ ap.activityHeartbeat = null;
9277
+ }
9278
+ if (ap.compactionWatchdog) {
9279
+ clearTimeout(ap.compactionWatchdog);
9280
+ ap.compactionWatchdog = null;
9281
+ }
9282
+ this.clearRuntimeStartupTimeout(ap);
9283
+ this.clearStalledRecoverySigtermWatchdog(ap);
9284
+ this.endRuntimeTrace(ap, "error", {
9285
+ outcome: "runtime-start-failed",
9286
+ failure_detail: err instanceof Error ? err.message : String(err),
9287
+ ...runtimeTraceCounterAttrs(ap),
9288
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
9289
+ });
9290
+ cleanupAgentCredentialProxy(agentId, ap.launchId);
9291
+ this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
9292
+ this.agents.delete(agentId);
9293
+ this.idleAgentConfigs.delete(agentId);
9294
+ }
9128
9295
  async buildSpawnConfig(agentId, config) {
9129
9296
  const baseConfig = config.serverUrl === this.serverUrl ? config : { ...config, serverUrl: this.serverUrl };
9130
9297
  const runnerConfig = await this.ensureManagedRunnerCredential(agentId, baseConfig);
@@ -9293,7 +9460,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9293
9460
  ap.inbox.push(message);
9294
9461
  if (ap.driver.supportsStdinNotification && ap.sessionId) {
9295
9462
  ap.notifications.add();
9296
- if (ap.driver.busyDeliveryMode === "gated") {
9463
+ if (ap.runtime.descriptor.busyDelivery === "gated") {
9297
9464
  this.recordGatedSteeringEvent(agentId, ap, "buffer", {
9298
9465
  reason: "runtime_profile",
9299
9466
  kind,
@@ -9360,12 +9527,16 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9360
9527
  cleanupAgentCredentialProxy(agentId, ap.launchId);
9361
9528
  this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
9362
9529
  this.agents.delete(agentId);
9363
- this.processExitTraceAttrs.set(ap.process, {
9530
+ this.runtimeExitTraceAttrs.set(ap.runtime, {
9364
9531
  stop_source: silent ? "daemon_internal" : "explicit_request",
9365
9532
  stop_wait_requested: wait,
9366
9533
  stop_silent: silent
9367
9534
  });
9368
- ap.process.kill("SIGTERM");
9535
+ await ap.runtime.stop({
9536
+ signal: "SIGTERM",
9537
+ forceAfterMs: wait ? 5e3 : void 0,
9538
+ reason: silent ? "daemon_internal" : "explicit_request"
9539
+ });
9369
9540
  if (!silent) {
9370
9541
  this.sendRuntimeProfileReportFor(agentId, ap.config, ap.sessionId, ap.launchId);
9371
9542
  this.sendAgentStatus(agentId, "inactive", ap.launchId);
@@ -9374,22 +9545,18 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9374
9545
  }
9375
9546
  if (wait) {
9376
9547
  await new Promise((resolve) => {
9377
- const forceKillTimer = setTimeout(() => {
9548
+ const timeoutTimer = setTimeout(() => {
9378
9549
  if (!silent) {
9379
9550
  logger.warn(`[Agent ${agentId}] Stop timed out; force killing`);
9380
9551
  }
9381
- try {
9382
- ap.process.kill("SIGKILL");
9383
- } catch {
9384
- }
9385
9552
  resolve();
9386
9553
  }, 5e3);
9387
- ap.process.on("exit", () => {
9388
- clearTimeout(forceKillTimer);
9554
+ ap.runtime.on("exit", () => {
9555
+ clearTimeout(timeoutTimer);
9389
9556
  resolve();
9390
9557
  });
9391
- if (ap.process.exitCode !== null || ap.process.signalCode !== null) {
9392
- clearTimeout(forceKillTimer);
9558
+ if (ap.runtime.closed) {
9559
+ clearTimeout(timeoutTimer);
9393
9560
  resolve();
9394
9561
  }
9395
9562
  });
@@ -9399,9 +9566,24 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9399
9566
  if (traceContext.deliveryId) {
9400
9567
  this.deliveryTraceContexts.set(message, traceContext);
9401
9568
  }
9569
+ if (traceContext.transient) {
9570
+ this.deliveryTraceContexts.set(message, traceContext);
9571
+ }
9572
+ const transientDelivery = this.isTransientDelivery(message);
9402
9573
  const ap = this.agents.get(agentId);
9403
9574
  if (!ap) {
9404
9575
  if (this.agentsStarting.has(agentId) || this.queuedAgentStarts.has(agentId)) {
9576
+ if (transientDelivery) {
9577
+ const queuedStart2 = this.queuedAgentStarts.get(agentId);
9578
+ this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
9579
+ outcome: "transient_dropped_during_start",
9580
+ accepted: true,
9581
+ process_present: false,
9582
+ startup_pending: true,
9583
+ launchId: queuedStart2?.launchId
9584
+ }));
9585
+ return true;
9586
+ }
9405
9587
  const queuedStart = this.queuedAgentStarts.get(agentId);
9406
9588
  const pending = this.startingInboxes.get(agentId) || [];
9407
9589
  pending.push(message);
@@ -9419,7 +9601,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9419
9601
  const cached = this.idleAgentConfigs.get(agentId);
9420
9602
  if (cached) {
9421
9603
  const driver = this.driverResolver(cached.config.runtime || "claude");
9422
- if (this.shouldDeferWakeMessage(agentId, driver, message)) {
9604
+ if (!transientDelivery && this.shouldDeferWakeMessage(agentId, driver, message)) {
9423
9605
  this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
9424
9606
  outcome: "deferred_wake_message",
9425
9607
  accepted: true,
@@ -9442,7 +9624,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9442
9624
  session_id_present: Boolean(cached.sessionId),
9443
9625
  launchId: cached.launchId || void 0
9444
9626
  }));
9445
- return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
9627
+ return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0, transientDelivery).then(() => true, (err) => {
9446
9628
  logger.error(`[Agent ${agentId}] Failed to auto-restart`, err);
9447
9629
  if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "idle_auto_restart")) {
9448
9630
  return false;
@@ -9452,6 +9634,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9452
9634
  });
9453
9635
  }
9454
9636
  logger.warn(`[Agent ${agentId}] Delivery received but no running process or cached idle config exists`);
9637
+ if (transientDelivery) {
9638
+ this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
9639
+ outcome: "transient_dropped_no_process",
9640
+ accepted: true,
9641
+ process_present: false,
9642
+ cached_idle_config_present: false
9643
+ }));
9644
+ return true;
9645
+ }
9455
9646
  this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
9456
9647
  outcome: "rejected_no_process",
9457
9648
  accepted: false,
@@ -9462,7 +9653,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9462
9653
  this.broadcastActivity(agentId, "offline", "Process unavailable; restart required");
9463
9654
  return false;
9464
9655
  }
9465
- if (this.shouldDeferWakeMessage(agentId, ap.driver, message)) {
9656
+ if (!transientDelivery && this.shouldDeferWakeMessage(agentId, ap.driver, message)) {
9466
9657
  this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
9467
9658
  outcome: "deferred_wake_message",
9468
9659
  accepted: true,
@@ -9475,13 +9666,73 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9475
9666
  }));
9476
9667
  return true;
9477
9668
  }
9669
+ const stickyTerminalFailure = classifyStickyTerminalFailure(ap);
9670
+ if (stickyTerminalFailure) {
9671
+ if (transientDelivery) {
9672
+ this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
9673
+ outcome: "transient_dropped_terminal_runtime_error",
9674
+ accepted: true,
9675
+ process_present: true,
9676
+ runtime: ap.config.runtime,
9677
+ session_id_present: Boolean(ap.sessionId),
9678
+ launchId: ap.launchId || void 0,
9679
+ is_idle: ap.isIdle,
9680
+ inbox_count: ap.inbox.length
9681
+ }));
9682
+ return true;
9683
+ }
9684
+ ap.inbox.push(message);
9685
+ this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
9686
+ outcome: "queued_terminal_runtime_error",
9687
+ accepted: true,
9688
+ process_present: true,
9689
+ runtime: ap.config.runtime,
9690
+ session_id_present: Boolean(ap.sessionId),
9691
+ launchId: ap.launchId || void 0,
9692
+ is_idle: ap.isIdle,
9693
+ inbox_count: ap.inbox.length
9694
+ }));
9695
+ this.sendAgentStatus(agentId, "inactive", ap.launchId);
9696
+ this.broadcastActivity(agentId, "error", stickyTerminalFailure.detail);
9697
+ return true;
9698
+ }
9478
9699
  if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
9479
- const nextMessages = ap.inbox.splice(0, ap.inbox.length);
9480
- nextMessages.push(message);
9700
+ if (transientDelivery) {
9701
+ this.commitApmIdleState(agentId, ap, false);
9702
+ this.startRuntimeTrace(agentId, ap, "stdin-idle-delivery", [message]);
9703
+ this.broadcastActivity(agentId, "working", "Message received");
9704
+ const stdinAccepted2 = this.deliverMessagesViaStdin(
9705
+ agentId,
9706
+ ap,
9707
+ [message],
9708
+ "idle",
9709
+ { transient: true }
9710
+ );
9711
+ this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
9712
+ outcome: "stdin_idle_transient_delivery",
9713
+ accepted: true,
9714
+ process_present: true,
9715
+ runtime: ap.config.runtime,
9716
+ session_id_present: true,
9717
+ launchId: ap.launchId || void 0,
9718
+ stdin_delivery_accepted: stdinAccepted2,
9719
+ delivered_messages_count: 1,
9720
+ inbox_count: ap.inbox.length
9721
+ }));
9722
+ return true;
9723
+ }
9724
+ ap.inbox.push(message);
9725
+ const nextMessages = [...ap.inbox];
9481
9726
  this.commitApmIdleState(agentId, ap, false);
9482
- this.startRuntimeTrace(agentId, ap, "stdin-idle-delivery", nextMessages);
9727
+ this.startRuntimeTrace(agentId, ap, "stdin-idle-delivery", [message]);
9483
9728
  this.broadcastActivity(agentId, "working", "Message received");
9484
- const stdinAccepted = this.deliverMessagesViaStdin(agentId, ap, nextMessages, "idle");
9729
+ const stdinAccepted = this.deliverInboxUpdateViaStdin(
9730
+ agentId,
9731
+ ap,
9732
+ [message],
9733
+ "idle",
9734
+ "stdin_idle_delivery"
9735
+ );
9485
9736
  this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
9486
9737
  outcome: "stdin_idle_delivery",
9487
9738
  accepted: true,
@@ -9494,6 +9745,19 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9494
9745
  }));
9495
9746
  return true;
9496
9747
  }
9748
+ if (transientDelivery) {
9749
+ this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
9750
+ outcome: "transient_dropped_busy",
9751
+ accepted: true,
9752
+ process_present: true,
9753
+ runtime: ap.config.runtime,
9754
+ session_id_present: Boolean(ap.sessionId),
9755
+ launchId: ap.launchId || void 0,
9756
+ is_idle: ap.isIdle,
9757
+ inbox_count: ap.inbox.length
9758
+ }));
9759
+ return true;
9760
+ }
9497
9761
  ap.inbox.push(message);
9498
9762
  if (this.recoverStaleProcessForQueuedMessageIfNeeded(agentId, ap)) {
9499
9763
  this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
@@ -9534,7 +9798,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9534
9798
  if (ap.gatedSteering.compacting) {
9535
9799
  ap.notifications.add();
9536
9800
  ap.notifications.clearTimer();
9537
- if (ap.driver.busyDeliveryMode === "gated") {
9801
+ if (ap.runtime.descriptor.busyDelivery === "gated") {
9538
9802
  this.recordGatedSteeringEvent(agentId, ap, "buffer", {
9539
9803
  reason: "compaction_boundary",
9540
9804
  pendingMessages: ap.inbox.length
@@ -9559,7 +9823,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9559
9823
  }));
9560
9824
  return true;
9561
9825
  }
9562
- if (ap.driver.busyDeliveryMode === "gated") {
9826
+ if (ap.runtime.descriptor.busyDelivery === "gated") {
9563
9827
  ap.notifications.add();
9564
9828
  if (!ap.notifications.hasTimer) {
9565
9829
  this.scheduleStdinNotification(agentId, ap, STDIN_NOTIFICATION_INITIAL_DELAY_MS);
@@ -9742,7 +10006,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9742
10006
  traceparent: formatTraceparent(span.context)
9743
10007
  };
9744
10008
  const ap = this.agents.get(agentId);
9745
- if (ap && !(ap.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) && !(ap.sessionId && ap.driver.busyDeliveryMode === "direct")) {
10009
+ if (ap && !(ap.sessionId && ap.driver.supportsStdinNotification && ap.isIdle) && !(ap.sessionId && ap.runtime.descriptor.busyDelivery === "direct")) {
9746
10010
  this.enqueueRuntimeProfileNotification(agentId, ap, message, kind, key);
9747
10011
  span.end("ok", {
9748
10012
  attrs: {
@@ -9772,7 +10036,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9772
10036
  });
9773
10037
  return written;
9774
10038
  }
9775
- if (ap?.sessionId && ap.driver.busyDeliveryMode === "direct") {
10039
+ if (ap?.sessionId && ap.runtime.descriptor.busyDelivery === "direct") {
9776
10040
  const written = this.deliverMessagesViaStdin(agentId, ap, [message], "busy");
9777
10041
  span.end(written ? "ok" : "error", {
9778
10042
  attrs: {
@@ -10195,23 +10459,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10195
10459
  clearTimeout(ap.stalledRecoverySigtermTimer);
10196
10460
  ap.stalledRecoverySigtermTimer = null;
10197
10461
  }
10198
- mergeProcessExitTraceAttrs(proc, attrs) {
10199
- this.processExitTraceAttrs.set(proc, {
10200
- ...this.processExitTraceAttrs.get(proc) ?? {},
10462
+ mergeRuntimeExitTraceAttrs(runtime, attrs) {
10463
+ this.runtimeExitTraceAttrs.set(runtime, {
10464
+ ...this.runtimeExitTraceAttrs.get(runtime) ?? {},
10201
10465
  ...attrs
10202
10466
  });
10203
10467
  }
10204
10468
  startStalledRecoverySigtermWatchdog(agentId, ap, runtimeLabel, queuedMessagesAtSignal, staleForMs) {
10205
10469
  this.clearStalledRecoverySigtermWatchdog(ap);
10206
10470
  const timeoutMs = stalledRecoverySigtermTimeoutMs();
10207
- const processAtSignal = ap.process;
10471
+ const runtimeAtSignal = ap.runtime;
10208
10472
  ap.stalledRecoverySigtermTimer = setTimeout(() => {
10209
10473
  ap.stalledRecoverySigtermTimer = null;
10210
10474
  const current = this.agents.get(agentId);
10211
- if (!current || current !== ap || current.process !== processAtSignal || current.expectedTerminationReason !== "stalled_recovery") {
10475
+ if (!current || current !== ap || current.runtime !== runtimeAtSignal || current.expectedTerminationReason !== "stalled_recovery") {
10212
10476
  return;
10213
10477
  }
10214
- this.mergeProcessExitTraceAttrs(processAtSignal, {
10478
+ this.mergeRuntimeExitTraceAttrs(runtimeAtSignal, {
10215
10479
  stalled_recovery_sigterm_timeout: true,
10216
10480
  stalled_recovery_sigterm_timeout_ms: timeoutMs
10217
10481
  });
@@ -10225,7 +10489,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10225
10489
  queued_messages_at_signal: queuedMessagesAtSignal,
10226
10490
  stale_age_ms_at_signal: staleForMs,
10227
10491
  timeout_ms: timeoutMs,
10228
- process_pid_present: typeof processAtSignal.pid === "number",
10492
+ process_pid_present: typeof runtimeAtSignal.pid === "number",
10229
10493
  session_id_present: Boolean(current.sessionId),
10230
10494
  supports_stdin_notification: current.driver.supportsStdinNotification,
10231
10495
  busy_delivery_mode: current.driver.busyDeliveryMode
@@ -10234,7 +10498,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10234
10498
  `[Agent ${agentId}] Stalled ${runtimeLabel} runtime did not exit after SIGTERM within ${timeoutMs}ms; force killing`
10235
10499
  );
10236
10500
  try {
10237
- processAtSignal.kill("SIGKILL");
10501
+ void runtimeAtSignal.stop({ signal: "SIGKILL", reason: "stalled_recovery_sigterm_timeout" });
10238
10502
  } catch (err) {
10239
10503
  const reason = err instanceof Error ? err.message : String(err);
10240
10504
  this.recordDaemonTrace("daemon.agent.stalled_recovery.sigkill_failed", {
@@ -10420,7 +10684,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10420
10684
  ap.runtimeProgress.noteRuntimeEvent(eventKind);
10421
10685
  }
10422
10686
  recordGatedSteeringEvent(agentId, ap, event, attrs = {}) {
10423
- if (ap.driver.busyDeliveryMode !== "gated") return;
10687
+ if (ap.runtime.descriptor.busyDelivery !== "gated") return;
10424
10688
  const reduction = reduceApmGatedRecentEvent(ap.gatedSteering, { event });
10425
10689
  this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState);
10426
10690
  this.recordRuntimeTraceEvent(agentId, ap, `runtime.gated_steering.${event}`, {
@@ -10455,7 +10719,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10455
10719
  }
10456
10720
  notifyGatedSteeringBoundary(agentId, ap, reason) {
10457
10721
  const readiness = reduceApmGatedFlushReadiness(ap.gatedSteering, {
10458
- isGated: ap.driver.busyDeliveryMode === "gated",
10722
+ isGated: ap.runtime.descriptor.busyDelivery === "gated",
10459
10723
  hasSession: Boolean(ap.sessionId),
10460
10724
  inboxLength: ap.inbox.length,
10461
10725
  reason
@@ -10496,7 +10760,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10496
10760
  return written;
10497
10761
  }
10498
10762
  case "deliver_stdin": {
10499
- const messages = ap.inbox.splice(0, ap.inbox.length);
10763
+ const messages = [...ap.inbox];
10500
10764
  ap.notifications.clear();
10501
10765
  if (messages.length === 0) {
10502
10766
  this.recordApmGatedSteeringEffectTrace(agentId, ap, effect, {
@@ -10505,7 +10769,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10505
10769
  });
10506
10770
  return true;
10507
10771
  }
10508
- if (ap.driver.busyDeliveryMode === "gated") {
10772
+ if (ap.runtime.descriptor.busyDelivery === "gated") {
10509
10773
  const flushReduction = reduceApmGatedFlush(ap.gatedSteering, { reason: effect.reason });
10510
10774
  this.commitGatedSteeringDecisionState(agentId, ap, flushReduction.nextState);
10511
10775
  this.recordGatedSteeringEvent(agentId, ap, "flush", {
@@ -10514,7 +10778,22 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10514
10778
  });
10515
10779
  }
10516
10780
  this.broadcastActivity(agentId, "working", "Message received");
10517
- const accepted = this.deliverMessagesViaStdin(agentId, ap, messages, effect.stdinMode);
10781
+ const runtimeProfileMessages = messages.filter((message) => runtimeProfileNotificationFromMessage(message));
10782
+ const ordinaryMessages = messages.filter((message) => !runtimeProfileNotificationFromMessage(message));
10783
+ let accepted = true;
10784
+ if (runtimeProfileMessages.length > 0) {
10785
+ ap.inbox.splice(0, ap.inbox.length, ...ordinaryMessages);
10786
+ accepted = this.deliverMessagesViaStdin(agentId, ap, runtimeProfileMessages, effect.stdinMode);
10787
+ }
10788
+ if (ordinaryMessages.length > 0) {
10789
+ accepted = this.deliverInboxUpdateViaStdin(
10790
+ agentId,
10791
+ ap,
10792
+ ordinaryMessages,
10793
+ effect.stdinMode,
10794
+ `stdin_${effect.stdinMode}_delivery`
10795
+ ) && accepted;
10796
+ }
10518
10797
  this.recordApmGatedSteeringEffectTrace(agentId, ap, effect, {
10519
10798
  outcome: accepted ? "written" : "not_written",
10520
10799
  delivered_messages_count: messages.length
@@ -10592,12 +10871,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10592
10871
  this.sendAgentStatus(agentId, "inactive", ap.launchId);
10593
10872
  this.idleAgentConfigs.delete(agentId);
10594
10873
  try {
10595
- this.processExitTraceAttrs.set(ap.process, {
10874
+ this.runtimeExitTraceAttrs.set(ap.runtime, {
10596
10875
  stop_source: "startup_timeout",
10597
10876
  expectedTerminationReason: "startup_timeout",
10598
10877
  timeout_ms: timeoutMs
10599
10878
  });
10600
- ap.process.kill("SIGTERM");
10879
+ void ap.runtime.stop({ signal: "SIGTERM", reason: "startup_timeout" });
10601
10880
  } catch (err) {
10602
10881
  const reason = err instanceof Error ? err.message : String(err);
10603
10882
  logger.warn(`[Agent ${agentId}] Failed to terminate startup-timed-out ${ap.driver.id} process: ${reason}`);
@@ -10683,13 +10962,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10683
10962
  );
10684
10963
  this.broadcastActivity(agentId, "working", `Restarting stalled ${runtimeLabel} runtime for queued message`);
10685
10964
  try {
10686
- this.processExitTraceAttrs.set(ap.process, {
10965
+ this.runtimeExitTraceAttrs.set(ap.runtime, {
10687
10966
  stop_source: "stalled_recovery",
10688
10967
  expectedTerminationReason: "stalled_recovery",
10689
10968
  queued_messages_count: ap.inbox.length
10690
10969
  });
10691
10970
  this.startStalledRecoverySigtermWatchdog(agentId, ap, runtimeLabel, ap.inbox.length, staleForMs);
10692
- ap.process.kill("SIGTERM");
10971
+ void ap.runtime.stop({ signal: "SIGTERM", reason: "stalled_recovery" });
10693
10972
  } catch (err) {
10694
10973
  this.clearStalledRecoverySigtermWatchdog(ap);
10695
10974
  const reason = err instanceof Error ? err.message : String(err);
@@ -10826,39 +11105,50 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10826
11105
  this.flushPendingTrajectory(agentId);
10827
11106
  if (ap) {
10828
11107
  if (event.sessionId) ap.sessionId = event.sessionId;
11108
+ const stickyTerminalFailure = classifyStickyTerminalFailure(ap);
10829
11109
  const reduction = reduceApmGatedTurnEnd(ap.gatedSteering, {
10830
- inboxLength: ap.inbox.length,
11110
+ inboxLength: stickyTerminalFailure ? 0 : ap.inbox.length,
10831
11111
  supportsStdinNotification: ap.driver.supportsStdinNotification,
10832
11112
  hasSession: Boolean(ap.sessionId),
10833
11113
  terminateProcessOnTurnEnd: ap.driver.terminateProcessOnTurnEnd === true
10834
11114
  });
10835
11115
  this.commitGatedSteeringDecisionState(agentId, ap, reduction.nextState, { event: "turn_end" });
10836
11116
  const deliverStdinEffect = reduction.effects.find((effect) => effect.kind === "deliver_stdin");
11117
+ if (stickyTerminalFailure) {
11118
+ this.sendAgentStatus(agentId, "inactive", ap.launchId);
11119
+ }
10837
11120
  if (deliverStdinEffect) {
10838
11121
  if (!this.executeApmGatedSteeringEffect(agentId, ap, deliverStdinEffect)) {
10839
11122
  this.commitApmIdleState(agentId, ap, true);
10840
- this.broadcastActivity(agentId, "online", "Idle");
11123
+ if (stickyTerminalFailure) {
11124
+ this.broadcastActivity(agentId, "error", stickyTerminalFailure.detail);
11125
+ } else {
11126
+ this.broadcastActivity(agentId, "online", "Idle");
11127
+ }
10841
11128
  }
10842
11129
  } else {
10843
- if (ap.lastRuntimeError) {
11130
+ if (stickyTerminalFailure) {
11131
+ this.broadcastActivity(agentId, "error", stickyTerminalFailure.detail);
11132
+ } else if (ap.lastRuntimeError) {
10844
11133
  this.broadcastActivity(agentId, "error", ap.lastRuntimeError);
10845
11134
  } else {
10846
11135
  this.broadcastActivity(agentId, "online", "Idle");
10847
11136
  }
10848
11137
  }
10849
- this.endRuntimeTrace(ap, "ok", {
10850
- outcome: "turn-completed",
11138
+ this.endRuntimeTrace(ap, stickyTerminalFailure ? "error" : "ok", {
11139
+ outcome: stickyTerminalFailure ? "runtime-error-with-turn-end" : "turn-completed",
11140
+ ...stickyTerminalFailure ? { terminalRuntimeError: true } : {},
10851
11141
  ...runtimeTraceCounterAttrs(ap),
10852
- ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "turn_end")
11142
+ ...this.finalizeRuntimeProfileTurnControl(agentId, ap, stickyTerminalFailure ? "runtime_error" : "turn_end")
10853
11143
  });
10854
11144
  if (ap.driver.terminateProcessOnTurnEnd) {
10855
11145
  logger.info(`[Agent ${agentId}] Turn completed; terminating ${ap.driver.id} process`);
10856
11146
  try {
10857
- this.processExitTraceAttrs.set(ap.process, {
11147
+ this.runtimeExitTraceAttrs.set(ap.runtime, {
10858
11148
  stop_source: "turn_end",
10859
11149
  expectedTerminationReason: "turn_end"
10860
11150
  });
10861
- ap.process.kill("SIGTERM");
11151
+ void ap.runtime.stop({ signal: "SIGTERM", reason: "turn_end" });
10862
11152
  } catch (err) {
10863
11153
  const reason = err instanceof Error ? err.message : String(err);
10864
11154
  logger.warn(`[Agent ${agentId}] Failed to terminate ${ap.driver.id} after turn_end: ${reason}`);
@@ -10880,7 +11170,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10880
11170
  if (runtimeErrorDiagnostics.spanAttrs.runtime_error_action_required === true) {
10881
11171
  visibleErrorMessage = formatRuntimeLoginRequiredMessage(ap.driver.id);
10882
11172
  }
10883
- const shouldDisableToolBoundaryFlush = ap.driver.busyDeliveryMode === "gated" && this.isThinkingBlockMutationError(event.message);
11173
+ const shouldDisableToolBoundaryFlush = ap.runtime.descriptor.busyDelivery === "gated" && this.isThinkingBlockMutationError(event.message);
10884
11174
  const terminalFailure = classifyTerminalFailure(ap);
10885
11175
  const reduction = reduceApmGatedError(ap.gatedSteering, {
10886
11176
  disableToolBoundaryFlush: shouldDisableToolBoundaryFlush,
@@ -10907,18 +11197,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10907
11197
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
10908
11198
  });
10909
11199
  if (ap.driver.supportsStdinNotification && terminalFailure) {
11200
+ const stickyTerminalFailure = classifyStickyTerminalFailure(ap);
10910
11201
  if (terminalFailure.actionRequired) {
10911
11202
  logger.warn(`[Agent ${agentId}] ${ap.driver.id} auth requires user action; terminating runtime process`);
10912
11203
  try {
10913
- this.processExitTraceAttrs.set(ap.process, {
11204
+ this.runtimeExitTraceAttrs.set(ap.runtime, {
10914
11205
  stop_source: "runtime_auth_error",
10915
11206
  runtime_error_class: "AuthError"
10916
11207
  });
10917
- ap.process.kill("SIGTERM");
11208
+ void ap.runtime.stop({ signal: "SIGTERM", reason: "runtime_auth_error" });
10918
11209
  } catch (err) {
10919
11210
  const reason = err instanceof Error ? err.message : String(err);
10920
11211
  logger.warn(`[Agent ${agentId}] Failed to terminate ${ap.driver.id} after auth error: ${reason}`);
10921
11212
  }
11213
+ } else if (stickyTerminalFailure) {
11214
+ ap.notifications.clear();
11215
+ this.sendAgentStatus(agentId, "inactive", ap.launchId);
11216
+ logger.warn(`[Agent ${agentId}] ${ap.driver.id} terminal runtime error requires explicit recovery`);
10922
11217
  } else {
10923
11218
  ap.notifications.clear();
10924
11219
  logger.info(`[Agent ${agentId}] Marked ${ap.driver.id} wakeable after terminal runtime error`);
@@ -10935,8 +11230,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10935
11230
  recordRuntimeTelemetry(agentId, ap, event) {
10936
11231
  const sessionId = ap.driver.currentSessionId ?? event.sessionId;
10937
11232
  const payloadAttrs = sanitizeRuntimeTelemetryPayloadAttrs(event.attrs);
11233
+ const versionAttrs = this.runtimeTelemetryVersionAttrs();
10938
11234
  const telemetryAttrs = {
10939
11235
  ...payloadAttrs,
11236
+ ...versionAttrs,
10940
11237
  ...event.source ? { source: event.source } : {},
10941
11238
  ...event.usageKind ? { usageKind: event.usageKind } : {},
10942
11239
  ...sessionId ? { sessionId } : {},
@@ -10954,6 +11251,24 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10954
11251
  ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, telemetryAttrs);
10955
11252
  this.recordDaemonTrace(`daemon.runtime.telemetry.${event.name}`, attrs);
10956
11253
  }
11254
+ runtimeTelemetryVersionAttrs() {
11255
+ return {
11256
+ ...this.daemonVersion ? {
11257
+ daemonVersion: this.daemonVersion,
11258
+ daemon_version: this.daemonVersion,
11259
+ daemon_version_present: true
11260
+ } : {
11261
+ daemon_version_present: false
11262
+ },
11263
+ ...this.computerVersion ? {
11264
+ computerVersion: this.computerVersion,
11265
+ computer_version: this.computerVersion,
11266
+ computer_version_present: true
11267
+ } : {
11268
+ computer_version_present: false
11269
+ }
11270
+ };
11271
+ }
10957
11272
  sendAgentStatus(agentId, status, launchId) {
10958
11273
  this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
10959
11274
  }
@@ -11025,9 +11340,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
11025
11340
  }
11026
11341
  const inboxCount = ap.inbox.length;
11027
11342
  if (inboxCount === 0) return false;
11028
- const inboxRows = projectAgentInboxSnapshot(ap.inbox);
11343
+ const changedMessages = ap.inbox.slice(Math.max(0, ap.inbox.length - count));
11344
+ const inboxRows = projectAgentInboxSnapshot(changedMessages);
11029
11345
  const notification = `[Slock inbox notice:
11030
- ${formatAgentInboxDelta(inboxRows)}]`;
11346
+ ${formatAgentInboxDelta(inboxRows, { totalPendingMessages: inboxCount })}]`;
11031
11347
  const notificationByteCount = Buffer.byteLength(notification, "utf8");
11032
11348
  const projectionAttrs = this.inboxProjectionTraceAttrs(inboxRows, inboxCount);
11033
11349
  this.recordDaemonTrace("daemon.agent.inbox_projection.delta", {
@@ -11036,9 +11352,11 @@ ${formatAgentInboxDelta(inboxRows)}]`;
11036
11352
  ...projectionAttrs
11037
11353
  });
11038
11354
  logger.info(`[Agent ${agentId}] Sending stdin inbox update: ${inboxRows.length} changed target(s), ${inboxCount} pending message(s)`);
11039
- const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId, { mode: "busy" });
11040
- if (encoded) {
11041
- ap.process.stdin?.write(encoded + "\n");
11355
+ const sendResult = requireImmediateRuntimeSendResult(
11356
+ ap.runtime.send({ mode: "busy", text: notification, sessionId: ap.sessionId }),
11357
+ "busy inbox notification"
11358
+ );
11359
+ if (sendResult.ok) {
11042
11360
  this.recordDaemonTrace("daemon.agent.inbox_update.pushed", {
11043
11361
  agentId,
11044
11362
  runtime: ap.config.runtime,
@@ -11063,14 +11381,17 @@ ${formatAgentInboxDelta(inboxRows)}]`;
11063
11381
  return true;
11064
11382
  } else {
11065
11383
  ap.notifications.add(count);
11066
- const retryScheduled = ap.driver.busyDeliveryMode === "direct" ? this.scheduleStdinNotification(agentId, ap, this.stdinNotificationRetryMs) : false;
11384
+ const retryScheduled = ap.runtime.descriptor.busyDelivery === "direct" ? this.scheduleStdinNotification(agentId, ap, this.stdinNotificationRetryMs) : false;
11385
+ const outcome = runtimeSendFailureOutcome(sendResult);
11067
11386
  this.recordDaemonTrace("daemon.agent.stdin_notification", {
11068
11387
  agentId,
11069
11388
  runtime: ap.config.runtime,
11070
11389
  model: ap.config.model,
11071
11390
  launchId: ap.launchId || void 0,
11072
- outcome: "encode_failed",
11391
+ outcome,
11073
11392
  mode: "busy",
11393
+ failure_reason: sendResult.reason,
11394
+ failure_error: sendResult.error,
11074
11395
  pending_notification_count: count,
11075
11396
  retry_scheduled: retryScheduled,
11076
11397
  notification_timer_present: ap.notifications.hasTimer,
@@ -11081,8 +11402,110 @@ ${formatAgentInboxDelta(inboxRows)}]`;
11081
11402
  return false;
11082
11403
  }
11083
11404
  }
11405
+ formatInboxUpdateRuntimeInput(messages, driver, totalPendingMessages = messages.length) {
11406
+ const rows = projectAgentInboxSnapshot(messages);
11407
+ return [
11408
+ "[Slock inbox notice:",
11409
+ formatAgentInboxDelta(rows, { totalPendingMessages }),
11410
+ "]"
11411
+ ].join("\n");
11412
+ }
11413
+ recordInboxUpdateProjection(agentId, ap, messages, source, mode, renderedInput, totalPendingMessages = messages.length) {
11414
+ const rows = projectAgentInboxSnapshot(messages);
11415
+ const projectionAttrs = this.inboxProjectionTraceAttrs(rows, totalPendingMessages);
11416
+ this.recordDaemonTrace("daemon.agent.inbox_projection.delta", {
11417
+ agentId,
11418
+ source,
11419
+ ...projectionAttrs
11420
+ });
11421
+ this.recordDaemonTrace("daemon.agent.inbox_update.pushed", {
11422
+ agentId,
11423
+ runtime: ap.config.runtime,
11424
+ model: ap.config.model,
11425
+ launchId: ap.launchId || void 0,
11426
+ mode,
11427
+ notification_byte_count: Buffer.byteLength(renderedInput, "utf8"),
11428
+ cursors_advanced: "none",
11429
+ ...projectionAttrs
11430
+ });
11431
+ return projectionAttrs;
11432
+ }
11433
+ deliverInboxUpdateViaStdin(agentId, ap, messages, mode, source) {
11434
+ if (messages.length === 0) return true;
11435
+ const prompt = this.formatInboxUpdateRuntimeInput(messages, ap.driver, ap.inbox.length);
11436
+ const projectionAttrs = this.recordInboxUpdateProjection(agentId, ap, messages, source, mode, prompt, ap.inbox.length);
11437
+ const inputTraceAttrs = buildRuntimeInputTraceAttrs({
11438
+ source,
11439
+ prompt,
11440
+ messages,
11441
+ sessionIdPresent: Boolean(ap.sessionId),
11442
+ nativeStandingPrompt: Boolean(ap.driver.supportsNativeStandingPrompt)
11443
+ });
11444
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.input.prepared", inputTraceAttrs);
11445
+ const sendResult = requireImmediateRuntimeSendResult(
11446
+ ap.runtime.send({ mode, text: prompt, sessionId: ap.sessionId }),
11447
+ `${mode} inbox update`
11448
+ );
11449
+ if (!sendResult.ok) {
11450
+ if (mode === "idle") {
11451
+ this.commitApmIdleState(agentId, ap, true);
11452
+ }
11453
+ logger.warn(
11454
+ `[Agent ${agentId}] Failed to encode ${mode} inbox update; ${messages.length === 1 ? "message remains" : "messages remain"} pending`
11455
+ );
11456
+ this.recordDaemonTrace("daemon.agent.stdin_delivery", {
11457
+ agentId,
11458
+ launchId: ap.launchId || void 0,
11459
+ runtime: ap.config.runtime,
11460
+ model: ap.config.model,
11461
+ mode,
11462
+ messages_count: messages.length,
11463
+ session_id_present: Boolean(ap.sessionId),
11464
+ inbox_count: ap.inbox.length,
11465
+ pending_notification_count: ap.notifications.pendingCount,
11466
+ busy_delivery_mode: ap.driver.busyDeliveryMode,
11467
+ supports_stdin_notification: ap.driver.supportsStdinNotification,
11468
+ ...this.messagesTraceAttrs(messages),
11469
+ ...inputTraceAttrs,
11470
+ ...projectionAttrs,
11471
+ outcome: runtimeSendFailureOutcome(sendResult),
11472
+ failure_reason: sendResult.reason,
11473
+ failure_error: sendResult.error,
11474
+ requeued_messages_count: 0,
11475
+ cursors_advanced: "none"
11476
+ }, "error");
11477
+ return false;
11478
+ }
11479
+ const senders = [...new Set(messages.map((message) => `@${message.sender_name}`))].join(", ");
11480
+ logger.info(
11481
+ `[Agent ${agentId}] Delivering ${mode} inbox update for ${messages.length === 1 ? "message" : `${messages.length} messages`} from ${senders}`
11482
+ );
11483
+ if (this.containsOrdinaryInboxMessage(messages)) {
11484
+ ap.lastRuntimeError = null;
11485
+ }
11486
+ this.recordDaemonTrace("daemon.agent.stdin_delivery", {
11487
+ agentId,
11488
+ launchId: ap.launchId || void 0,
11489
+ runtime: ap.config.runtime,
11490
+ model: ap.config.model,
11491
+ mode,
11492
+ messages_count: messages.length,
11493
+ session_id_present: Boolean(ap.sessionId),
11494
+ inbox_count: ap.inbox.length,
11495
+ pending_notification_count: ap.notifications.pendingCount,
11496
+ busy_delivery_mode: ap.driver.busyDeliveryMode,
11497
+ supports_stdin_notification: ap.driver.supportsStdinNotification,
11498
+ ...this.messagesTraceAttrs(messages),
11499
+ ...inputTraceAttrs,
11500
+ ...projectionAttrs,
11501
+ outcome: "written",
11502
+ stdin_write_attempted: true,
11503
+ cursors_advanced: "none"
11504
+ });
11505
+ return true;
11506
+ }
11084
11507
  /** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
11085
- deliverMessagesViaStdin(agentId, ap, messages, mode) {
11508
+ deliverMessagesViaStdin(agentId, ap, messages, mode, options = {}) {
11086
11509
  if (messages.length === 0) return true;
11087
11510
  const runtimeProfileMigrationMessages = messages.filter((message) => runtimeProfileNotificationFromMessage(message)?.kind === "migration");
11088
11511
  if (runtimeProfileMigrationMessages.length > 0) {
@@ -11120,8 +11543,10 @@ ${formatAgentInboxDelta(inboxRows)}]`;
11120
11543
  pending_notification_count: ap.notifications.pendingCount,
11121
11544
  busy_delivery_mode: ap.driver.busyDeliveryMode,
11122
11545
  supports_stdin_notification: ap.driver.supportsStdinNotification,
11546
+ transient_delivery: options.transient === true,
11123
11547
  ...this.messagesTraceAttrs(messages)
11124
11548
  };
11549
+ const traceSource = options.transient ? `stdin_${mode}_transient_delivery` : `stdin_${mode}_delivery`;
11125
11550
  const prompt = formatRuntimeProfileControlPrompt(messages) ?? (messages.length === 1 ? `New message received:
11126
11551
 
11127
11552
  ${formatIncomingMessage(messages[0], ap.driver)}
@@ -11134,16 +11559,21 @@ ${messages.map((message) => formatIncomingMessage(message, ap.driver)).join("\n"
11134
11559
  Respond as appropriate. Complete all your work before stopping.
11135
11560
  ${RESPONSE_TARGET_HINT}`);
11136
11561
  const inputTraceAttrs = buildRuntimeInputTraceAttrs({
11137
- source: `stdin_${mode}_delivery`,
11562
+ source: traceSource,
11138
11563
  prompt,
11139
11564
  messages,
11140
11565
  sessionIdPresent: Boolean(ap.sessionId),
11141
11566
  nativeStandingPrompt: Boolean(ap.driver.supportsNativeStandingPrompt)
11142
11567
  });
11143
11568
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.input.prepared", inputTraceAttrs);
11144
- const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId, { mode });
11145
- if (!encoded) {
11146
- ap.inbox.unshift(...messages);
11569
+ const sendResult = requireImmediateRuntimeSendResult(
11570
+ ap.runtime.send({ mode, text: prompt, sessionId: ap.sessionId }),
11571
+ `${mode} message delivery`
11572
+ );
11573
+ if (!sendResult.ok) {
11574
+ if (!options.transient) {
11575
+ ap.inbox.unshift(...messages);
11576
+ }
11147
11577
  if (mode === "idle") {
11148
11578
  this.commitApmIdleState(agentId, ap, true);
11149
11579
  }
@@ -11153,12 +11583,16 @@ ${RESPONSE_TARGET_HINT}`);
11153
11583
  this.recordDaemonTrace("daemon.agent.stdin_delivery", {
11154
11584
  ...traceAttrs,
11155
11585
  ...inputTraceAttrs,
11156
- outcome: "encode_failed",
11157
- requeued_messages_count: messages.length
11586
+ outcome: runtimeSendFailureOutcome(sendResult),
11587
+ failure_reason: sendResult.reason,
11588
+ failure_error: sendResult.error,
11589
+ requeued_messages_count: options.transient ? 0 : messages.length
11158
11590
  }, "error");
11159
11591
  return false;
11160
11592
  }
11161
- this.consumeVisibleMessages(agentId, { messages, source: `stdin_${mode}_delivery` });
11593
+ if (!options.transient) {
11594
+ this.consumeVisibleMessages(agentId, { messages, source: traceSource });
11595
+ }
11162
11596
  const senders = [...new Set(messages.map((message) => `@${message.sender_name}`))].join(", ");
11163
11597
  logger.info(
11164
11598
  `[Agent ${agentId}] Delivering ${mode} ${messages.length === 1 ? "message" : `${messages.length} messages`} via stdin from ${senders}`
@@ -11166,7 +11600,6 @@ ${RESPONSE_TARGET_HINT}`);
11166
11600
  if (this.containsOrdinaryInboxMessage(messages)) {
11167
11601
  ap.lastRuntimeError = null;
11168
11602
  }
11169
- ap.process.stdin?.write(encoded + "\n");
11170
11603
  this.ackInjectedRuntimeProfileMessages(agentId, messages, ap.launchId);
11171
11604
  this.recordDaemonTrace("daemon.agent.stdin_delivery", {
11172
11605
  ...traceAttrs,
@@ -12134,7 +12567,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
12134
12567
  var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
12135
12568
  var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
12136
12569
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
12137
- var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
12570
+ var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
12138
12571
  var RunnerCredentialMintError2 = class extends Error {
12139
12572
  code;
12140
12573
  retryable;
@@ -12170,9 +12603,9 @@ function runnerCredentialErrorDetail2(error) {
12170
12603
  async function waitForRunnerCredentialRetry2() {
12171
12604
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
12172
12605
  }
12173
- function parseDaemonCliArgs(args, env = {}) {
12606
+ function parseDaemonCliArgs(args) {
12174
12607
  let serverUrl = "";
12175
- let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
12608
+ let apiKey = "";
12176
12609
  for (let i = 0; i < args.length; i++) {
12177
12610
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
12178
12611
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -12373,7 +12806,9 @@ var DaemonCore = class {
12373
12806
  serverUrl: options.serverUrl,
12374
12807
  defaultAgentEnvVarsProvider: options.defaultAgentEnvVarsProvider,
12375
12808
  slockCliPath: this.slockCliPath,
12376
- tracer: this.tracer
12809
+ tracer: this.tracer,
12810
+ daemonVersion: this.daemonVersion,
12811
+ computerVersion: this.computerVersion
12377
12812
  };
12378
12813
  this.agentManager = options.agentManagerFactory ? options.agentManagerFactory(this.chatBridgePath, (msg) => connection.send(msg), options.apiKey, agentManagerOptions) : new AgentProcessManager(this.chatBridgePath, (msg) => connection.send(msg), options.apiKey, agentManagerOptions);
12379
12814
  const connectionFactory = options.connectionFactory ?? ((connOptions) => new DaemonConnection(connOptions));
@@ -12624,7 +13059,8 @@ var DaemonCore = class {
12624
13059
  msg.wakeMessage,
12625
13060
  msg.unreadSummary,
12626
13061
  msg.resumePrompt,
12627
- msg.launchId
13062
+ msg.launchId,
13063
+ msg.wakeMessageTransient ?? false
12628
13064
  );
12629
13065
  }
12630
13066
  handleMessage(msg) {
@@ -12675,7 +13111,10 @@ var DaemonCore = class {
12675
13111
  logger.info(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
12676
13112
  try {
12677
13113
  span.addEvent("daemon.receive", { seq: msg.seq, deliveryId: msg.deliveryId });
12678
- const acceptedOrPromise = this.agentManager.deliverMessage(msg.agentId, msg.message, { deliveryId: msg.deliveryId });
13114
+ const acceptedOrPromise = this.agentManager.deliverMessage(msg.agentId, msg.message, {
13115
+ deliveryId: msg.deliveryId,
13116
+ transient: msg.transient ?? false
13117
+ });
12679
13118
  Promise.resolve(acceptedOrPromise).then((accepted) => {
12680
13119
  span.addEvent("daemon.deliver_to_agent_manager", { accepted });
12681
13120
  if (!accepted) {
@@ -12939,8 +13378,6 @@ var DaemonCore = class {
12939
13378
  };
12940
13379
 
12941
13380
  export {
12942
- DAEMON_API_KEY_ENV,
12943
- scrubDaemonAuthEnv,
12944
13381
  resolveWorkspaceDirectoryPath,
12945
13382
  scanWorkspaceDirectories,
12946
13383
  deleteWorkspaceDirectory,