@alook/daemon 0.1.29 → 0.1.31

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.
package/dist/cli/index.js CHANGED
@@ -98,7 +98,7 @@ var init_nanoid = () => {};
98
98
  // src/cli/index.ts
99
99
  import { Command, CommanderError } from "commander";
100
100
  import { realpathSync as realpathSync2 } from "node:fs";
101
- import { randomUUID as randomUUID10 } from "node:crypto";
101
+ import { randomUUID as randomUUID11 } from "node:crypto";
102
102
  import { pathToFileURL as pathToFileURL2 } from "node:url";
103
103
 
104
104
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
@@ -16762,6 +16762,10 @@ var OwnerDiagnosticReportSchema = exports_external.discriminatedUnion("status",
16762
16762
  // ../shared/src/community-cli-contract.ts
16763
16763
  var DM_SERVER = ".dm";
16764
16764
  var CONTROL_HEARTBEAT_CAPABILITY = "control-heartbeat-v1";
16765
+ var AgentInterruptRequestSchema = exports_external.strictObject({
16766
+ type: exports_external.literal("agent:interrupt"),
16767
+ agentId: exports_external.string().min(1).max(128)
16768
+ });
16765
16769
  function parseRef(ref) {
16766
16770
  if (!ref.startsWith("/"))
16767
16771
  throw new Error(`ref must start with "/": ${ref}`);
@@ -16853,6 +16857,7 @@ var HostCommandSchema = exports_external.discriminatedUnion("type", [
16853
16857
  type: exports_external.literal("agent:stop"),
16854
16858
  agentId: exports_external.string().min(1)
16855
16859
  }),
16860
+ AgentInterruptRequestSchema,
16856
16861
  exports_external.object({
16857
16862
  type: exports_external.literal("agent:reset"),
16858
16863
  agentId: exports_external.string().min(1),
@@ -16920,7 +16925,6 @@ var MAX_FOLDER_NAME_LENGTH = 100;
16920
16925
  var MAX_PROFILE_NAME_LENGTH = 100;
16921
16926
  var MAX_PROFILE_ABOUT_LENGTH = 1000;
16922
16927
  var MAX_MESSAGE_CONTENT_LENGTH = 4000;
16923
- var MAX_EMOJI_BYTES = 32;
16924
16928
  var MAX_ATTACHMENTS_PER_MESSAGE = 10;
16925
16929
  var MAX_ATTACHMENT_SIZE_BYTES = 25 * 1024 * 1024;
16926
16930
  var MAX_ATTACHMENT_THUMBNAIL_EDGE_PX = 1024;
@@ -17830,17 +17834,18 @@ var CommunityAgentNapRequestSchema = exports_external.object({
17830
17834
  message: "handoff is required"
17831
17835
  })
17832
17836
  });
17833
- var CommunityAgentReactAddRequestSchema = exports_external.object({
17837
+ var CommunityAgentMessagePropertyRequestSchema = exports_external.object({
17834
17838
  channel: exports_external.string().min(1),
17835
17839
  seq: CommunityAgentPositiveSeqSchema,
17836
- emoji: exports_external.string().min(1)
17840
+ property: exports_external.unknown()
17837
17841
  });
17838
17842
  var CommunityAgentFriendRequestSchema = exports_external.object({
17839
17843
  username: exports_external.string().min(1)
17840
17844
  });
17841
17845
  var CommunityAgentListFriendsSchema = exports_external.object({});
17842
17846
  var AuditLogCliInvocationPayloadSchema = exports_external.object({
17843
- subcommand: exports_external.string().min(1)
17847
+ subcommand: exports_external.string().min(1),
17848
+ propertyType: exports_external.enum(["emoji", "tag", "mark"]).optional()
17844
17849
  });
17845
17850
  var AuditLogToolCallPayloadSchema = exports_external.object({
17846
17851
  name: exports_external.string().min(1),
@@ -17851,6 +17856,9 @@ var AuditLogThinkingPayloadSchema = exports_external.object({
17851
17856
  truncated: exports_external.boolean(),
17852
17857
  chars: exports_external.number().int().nonnegative()
17853
17858
  });
17859
+ var AuditLogTurnInterruptPayloadSchema = exports_external.object({
17860
+ status: exports_external.literal("accepted")
17861
+ });
17854
17862
  var AuditLogWakeTriggerPayloadSchema = exports_external.object({
17855
17863
  messageId: exports_external.string().min(1),
17856
17864
  channel: exports_external.string().min(1),
@@ -17883,6 +17891,7 @@ var BotAuditEventSchema = exports_external.discriminatedUnion("kind", [
17883
17891
  exports_external.object({ kind: exports_external.literal("cli_invocation"), payload: AuditLogCliInvocationPayloadSchema }),
17884
17892
  exports_external.object({ kind: exports_external.literal("tool_call"), payload: AuditLogToolCallPayloadSchema }),
17885
17893
  exports_external.object({ kind: exports_external.literal("thinking"), payload: AuditLogThinkingPayloadSchema }),
17894
+ exports_external.object({ kind: exports_external.literal("turn_interrupt"), payload: AuditLogTurnInterruptPayloadSchema }),
17886
17895
  exports_external.object({ kind: exports_external.literal("wake_trigger"), payload: AuditLogWakeTriggerPayloadSchema }),
17887
17896
  exports_external.object({ kind: exports_external.literal("session_reset"), payload: AuditLogSessionResetPayloadSchema }),
17888
17897
  exports_external.object({ kind: exports_external.literal("nap"), payload: AuditLogNapPayloadSchema }),
@@ -17894,6 +17903,7 @@ var BotAuditEventKindSchema = exports_external.enum([
17894
17903
  "cli_invocation",
17895
17904
  "tool_call",
17896
17905
  "thinking",
17906
+ "turn_interrupt",
17897
17907
  "wake_trigger",
17898
17908
  "session_reset",
17899
17909
  "nap",
@@ -18850,7 +18860,7 @@ var communityBotAuditEventSchema = exports_external.strictObject({
18850
18860
  type: exports_external.literal("community:bot.audit_event"),
18851
18861
  botId: string4,
18852
18862
  id: string4,
18853
- kind: exports_external.enum(["cli_invocation", "tool_call", "thinking", "wake_trigger", "session_reset", "nap", "model_changed", "provider_changed", "error"]),
18863
+ kind: exports_external.enum(["cli_invocation", "tool_call", "thinking", "turn_interrupt", "wake_trigger", "session_reset", "nap", "model_changed", "provider_changed", "error"]),
18854
18864
  payload: exports_external.unknown(),
18855
18865
  sessionId: nullableString.optional(),
18856
18866
  launchId: nullableString.optional(),
@@ -19136,6 +19146,14 @@ function proxyServerApiFromEnv(prefix = "ALOOK", env = process.env) {
19136
19146
  function createProxyServerApi(config2) {
19137
19147
  const fetchImpl = config2.fetchImpl ?? fetch;
19138
19148
  const base = config2.proxyUrl.replace(/\/+$/, "");
19149
+ const propertyAuditQuery = (property) => {
19150
+ if (!property || typeof property !== "object" || Array.isArray(property))
19151
+ return "";
19152
+ const type = property.type;
19153
+ if (type !== "emoji" && type !== "tag" && type !== "mark")
19154
+ return "";
19155
+ return `?type=${encodeURIComponent(type)}`;
19156
+ };
19139
19157
  async function parseJsonResponse(res, method) {
19140
19158
  let text2;
19141
19159
  try {
@@ -19253,17 +19271,37 @@ function createProxyServerApi(config2) {
19253
19271
  });
19254
19272
  return parseJsonResponse(res, "resolve");
19255
19273
  }
19256
- async function callReactAdd(req) {
19257
- const res = await fetchImpl(`${base}/api/community/messages/${REF_PLACEHOLDER_ID}/reactions/${encodeURIComponent(req.emoji)}`, {
19274
+ async function callMessagePropertySet(req) {
19275
+ const res = await fetchImpl(`${base}/api/community/messages/${REF_PLACEHOLDER_ID}/properties${propertyAuditQuery(req.property)}`, {
19258
19276
  method: "PUT",
19259
19277
  headers: {
19260
19278
  "content-type": "application/json",
19261
19279
  authorization: `Bearer ${config2.voucher}`
19262
19280
  },
19263
- body: JSON.stringify({ channel: req.channel, seq: req.seq })
19281
+ body: JSON.stringify(req)
19282
+ });
19283
+ return parseJsonResponse(res, "messagePropertySet");
19284
+ }
19285
+ async function callMessagePropertyList(req) {
19286
+ const q = new URLSearchParams;
19287
+ q.set("ref", req.channel);
19288
+ q.set("seq", String(req.seq));
19289
+ const res = await fetchImpl(`${base}/api/community/messages/${REF_PLACEHOLDER_ID}/properties?${q.toString()}`, {
19290
+ method: "GET",
19291
+ headers: { authorization: `Bearer ${config2.voucher}` }
19292
+ });
19293
+ return parseJsonResponse(res, "messagePropertyList");
19294
+ }
19295
+ async function callMessagePropertyRemove(req) {
19296
+ const res = await fetchImpl(`${base}/api/community/messages/${REF_PLACEHOLDER_ID}/properties${propertyAuditQuery(req.property)}`, {
19297
+ method: "DELETE",
19298
+ headers: {
19299
+ "content-type": "application/json",
19300
+ authorization: `Bearer ${config2.voucher}`
19301
+ },
19302
+ body: JSON.stringify(req)
19264
19303
  });
19265
- const body = await parseJsonResponse(res, "reactAdd");
19266
- return body?.duplicate ? { ok: true, duplicate: true } : { ok: true };
19304
+ return parseJsonResponse(res, "messagePropertyRemove");
19267
19305
  }
19268
19306
  async function callListMembers(req) {
19269
19307
  const q = new URLSearchParams;
@@ -19315,28 +19353,6 @@ function createProxyServerApi(config2) {
19315
19353
  });
19316
19354
  return parseJsonResponse(res, "inboxPull");
19317
19355
  }
19318
- async function callMarkSet(req) {
19319
- const res = await fetchImpl(`${base}/api/community/messages/resolve/marks`, {
19320
- method: "PUT",
19321
- headers: {
19322
- "content-type": "application/json",
19323
- authorization: `Bearer ${config2.voucher}`
19324
- },
19325
- body: JSON.stringify(req)
19326
- });
19327
- await parseJsonResponse(res, "markSet");
19328
- }
19329
- async function callMarkRemove(req) {
19330
- const res = await fetchImpl(`${base}/api/community/messages/resolve/marks`, {
19331
- method: "DELETE",
19332
- headers: {
19333
- "content-type": "application/json",
19334
- authorization: `Bearer ${config2.voucher}`
19335
- },
19336
- body: JSON.stringify(req)
19337
- });
19338
- await parseJsonResponse(res, "markRemove");
19339
- }
19340
19356
  async function callListMarks() {
19341
19357
  const res = await fetchImpl(`${base}/api/community/users/me/marks`, {
19342
19358
  method: "GET",
@@ -19493,11 +19509,32 @@ function createProxyServerApi(config2) {
19493
19509
  }
19494
19510
  return { server: projectServer(body.server) };
19495
19511
  }
19512
+ async function callLeaveChannel(req) {
19513
+ const parsed = parseRef(req.channel);
19514
+ const scope = parsed.threadRootSeq === undefined ? "channel" : "thread";
19515
+ const collection = scope === "thread" ? "participants" : "members";
19516
+ const res = await fetchImpl(`${base}/api/community/channels/${REF_PLACEHOLDER_ID}/${collection}/self?ref=${encodeURIComponent(req.channel)}`, {
19517
+ method: "DELETE",
19518
+ headers: { authorization: `Bearer ${config2.voucher}` }
19519
+ });
19520
+ await parseJsonResponse(res, "leaveChannel");
19521
+ return { left: req.channel, scope };
19522
+ }
19523
+ async function callLeaveServer(req) {
19524
+ const res = await fetchImpl(`${base}/api/community/servers/${REF_PLACEHOLDER_ID}/leave?server=${encodeURIComponent(req.server)}`, {
19525
+ method: "POST",
19526
+ headers: { authorization: `Bearer ${config2.voucher}` }
19527
+ });
19528
+ await parseJsonResponse(res, "leaveServer");
19529
+ return { left: req.server, scope: "server" };
19530
+ }
19496
19531
  return {
19497
19532
  listServers: (_r) => callListServers(),
19498
19533
  joinServer: callJoinServer,
19534
+ leaveServer: callLeaveServer,
19499
19535
  listChannels: callListChannels,
19500
19536
  channelMember: callChannelMember,
19537
+ leaveChannel: callLeaveChannel,
19501
19538
  inboxPull: callInboxPull,
19502
19539
  inboxSnapshot: (_r) => callInboxSnapshot(),
19503
19540
  ack: callAck,
@@ -19508,9 +19545,9 @@ function createProxyServerApi(config2) {
19508
19545
  attachmentUpload: callUpload,
19509
19546
  attachmentDownload: callDownload,
19510
19547
  updateProfile: callUpdateProfile,
19511
- reactAdd: callReactAdd,
19512
- markSet: callMarkSet,
19513
- markRemove: callMarkRemove,
19548
+ messagePropertySet: callMessagePropertySet,
19549
+ messagePropertyList: callMessagePropertyList,
19550
+ messagePropertyRemove: callMessagePropertyRemove,
19514
19551
  listMarks: (_r) => callListMarks(),
19515
19552
  friendRequest: callFriendRequest,
19516
19553
  listFriends: (_r) => callListFriends(),
@@ -19557,6 +19594,9 @@ function createDefaultAgentDriverHost(options = {}) {
19557
19594
  };
19558
19595
  }
19559
19596
 
19597
+ // agent-driver/dist/adapters/claude/index.js
19598
+ import { randomUUID as randomUUID5 } from "node:crypto";
19599
+
19560
19600
  // agent-driver/dist/controller/process-host.js
19561
19601
  import { EventEmitter } from "events";
19562
19602
 
@@ -20136,6 +20176,7 @@ class ProcessLane {
20136
20176
  admissionSequence = 0;
20137
20177
  activeTerminalReceipt;
20138
20178
  stopPromise;
20179
+ pendingInterrupt;
20139
20180
  pendingPromptAdmission;
20140
20181
  constructor(driver, ctx, opts = {}) {
20141
20182
  this.driver = driver;
@@ -20175,12 +20216,14 @@ class ProcessLane {
20175
20216
  try {
20176
20217
  spawned = await this.driver.spawn(launchCtx);
20177
20218
  } catch (error51) {
20219
+ this.settlePendingInterrupt(false);
20178
20220
  this.settlePendingPrompt({ ok: false, reason: "runtime_error", error: String(error51) });
20179
20221
  throw error51;
20180
20222
  }
20181
20223
  const { process: proc } = spawned;
20182
20224
  this.process = proc;
20183
20225
  this.attachProcess(proc);
20226
+ this.dispatchPendingInterrupt(proc);
20184
20227
  this.armPendingPromptTimeout();
20185
20228
  if (this.driver.execution.lifetime === "session" && !this.writableStdin(proc)) {
20186
20229
  const rejection = this.stdinUnavailableAdmission();
@@ -20279,6 +20322,7 @@ class ProcessLane {
20279
20322
  pending.resolve(admission);
20280
20323
  }
20281
20324
  stop(opts = {}) {
20325
+ this.settlePendingInterrupt(false);
20282
20326
  this.settlePendingPrompt({ ok: false, reason: "closed", error: "runtime lane stopped before command admission" });
20283
20327
  if (!this.process)
20284
20328
  return Promise.resolve();
@@ -20297,11 +20341,39 @@ class ProcessLane {
20297
20341
  proc.kill(opts?.signal ?? "SIGTERM");
20298
20342
  }
20299
20343
  }
20300
- async interrupt() {
20344
+ async interrupt(input) {
20301
20345
  const proc = this.process;
20302
- if (!proc || this.closed)
20346
+ if (!proc) {
20347
+ if (!this.started)
20348
+ return false;
20349
+ if (this.pendingInterrupt)
20350
+ return this.pendingInterrupt.promise;
20351
+ let resolve;
20352
+ let reject;
20353
+ const promise2 = new Promise((res, rej) => {
20354
+ resolve = res;
20355
+ reject = rej;
20356
+ });
20357
+ this.pendingInterrupt = { input, promise: promise2, resolve, reject };
20358
+ return promise2;
20359
+ }
20360
+ if (this.closed)
20303
20361
  return false;
20304
- return proc.kill("SIGINT");
20362
+ return this.driver.interrupt(input, proc);
20363
+ }
20364
+ dispatchPendingInterrupt(proc) {
20365
+ const pending = this.pendingInterrupt;
20366
+ if (!pending)
20367
+ return;
20368
+ this.pendingInterrupt = undefined;
20369
+ this.driver.interrupt(pending.input, proc).then(pending.resolve, pending.reject);
20370
+ }
20371
+ settlePendingInterrupt(accepted) {
20372
+ const pending = this.pendingInterrupt;
20373
+ if (!pending)
20374
+ return;
20375
+ this.pendingInterrupt = undefined;
20376
+ pending.resolve(accepted);
20305
20377
  }
20306
20378
  updateSettings(input) {
20307
20379
  return this.driver.updateSettings?.(input) ?? Promise.resolve({ status: "unsupported" });
@@ -20638,6 +20710,13 @@ function tryParseJsonLine(line) {
20638
20710
 
20639
20711
  // agent-driver/dist/adapters/claude/normalizer.js
20640
20712
  var API_ERROR_RE = /API Error:.*(?:Connection error|\b[45]\d{2}\b)/i;
20713
+ var CLAUDE_RESULT_ERROR_CODES = new Map([
20714
+ ["error_max_turns", "claude.error_max_turns"],
20715
+ ["error_during_execution", "claude.error_during_execution"],
20716
+ ["error_max_budget_usd", "claude.error_max_budget_usd"],
20717
+ ["error_max_structured_output_retries", "claude.error_max_structured_output_retries"]
20718
+ ]);
20719
+ var CLAUDE_INTERRUPT_TERMINAL_REASONS = new Set(["aborted_streaming", "aborted_tools"]);
20641
20720
  var CLAUDE_USAGE_COMPONENTS = [
20642
20721
  "inputTokens",
20643
20722
  "outputTokens",
@@ -20731,9 +20810,9 @@ class ClaudeEventNormalizer {
20731
20810
  out.push({ kind: "assistant_reasoning_completed", text: block.thinking ?? "" });
20732
20811
  } else if (block?.type === "text") {
20733
20812
  const text2 = block.text ?? "";
20734
- if (API_ERROR_RE.test(text2))
20735
- out.push({ kind: "error", message: text2 });
20736
- else
20813
+ if (API_ERROR_RE.test(text2)) {
20814
+ out.push({ kind: "error", code: "claude.api_error", message: text2 });
20815
+ } else
20737
20816
  completedText.push(text2);
20738
20817
  } else if (block?.type === "tool_use") {
20739
20818
  out.push({ kind: "tool_call", name: block.name ?? "unknown_tool", input: block.input });
@@ -20759,14 +20838,19 @@ class ClaudeEventNormalizer {
20759
20838
  }
20760
20839
  handleResult(event, out) {
20761
20840
  const rawOwner = typeof event.user_message_uuid === "string" && event.user_message_uuid.length > 0 ? event.user_message_uuid : null;
20762
- const turnOwner = rawOwner ? this.turnProtocol?.claimResult(rawOwner) ?? (this.turnProtocol ? null : `claude:${rawOwner}`) : null;
20841
+ const interruptedTerminal = Boolean(this.turnProtocol && !rawOwner && event.subtype === "error_during_execution" && CLAUDE_INTERRUPT_TERMINAL_REASONS.has(event.terminal_reason));
20842
+ const turnOwner = rawOwner ? this.turnProtocol?.claimResult(rawOwner) ?? (this.turnProtocol ? null : `claude:${rawOwner}`) : interruptedTerminal ? this.turnProtocol.claimInterruptedResult() : null;
20763
20843
  if (this.turnProtocol && !turnOwner)
20764
20844
  return;
20765
20845
  const usage = this.buildUsageTelemetry(event, rawOwner);
20766
20846
  if (usage)
20767
20847
  out.push(usage);
20768
- if (event.is_error || event.subtype === "error_during_execution") {
20769
- out.push({ kind: "error", message: String(event.result ?? "Claude runtime error") });
20848
+ if (!interruptedTerminal && (event.is_error || event.subtype === "error_during_execution")) {
20849
+ out.push({
20850
+ kind: "error",
20851
+ code: CLAUDE_RESULT_ERROR_CODES.get(event.subtype) ?? "claude.result_error",
20852
+ message: String(event.result ?? "Claude runtime error")
20853
+ });
20770
20854
  }
20771
20855
  out.push({
20772
20856
  kind: "turn_end",
@@ -21044,6 +21128,7 @@ class ClaudeTurnProtocol {
21044
21128
  segmentOwnerUuid = null;
21045
21129
  delivered = new Map;
21046
21130
  awaitingFollowOn = false;
21131
+ interruptRequested = false;
21047
21132
  finalized = false;
21048
21133
  beginTurn() {
21049
21134
  const uuid3 = randomUUID4();
@@ -21052,6 +21137,7 @@ class ClaudeTurnProtocol {
21052
21137
  this.delivered.clear();
21053
21138
  this.delivered.set(uuid3, { acknowledged: false, followOn: false });
21054
21139
  this.awaitingFollowOn = false;
21140
+ this.interruptRequested = false;
21055
21141
  this.finalized = false;
21056
21142
  return this.receipt(uuid3);
21057
21143
  }
@@ -21078,6 +21164,13 @@ class ClaudeTurnProtocol {
21078
21164
  return false;
21079
21165
  return this.delivered.get(this.rootUuid)?.acknowledged === true;
21080
21166
  }
21167
+ hasActiveTurn() {
21168
+ return this.rootUuid !== null && !this.finalized;
21169
+ }
21170
+ noteInterruptRequested() {
21171
+ if (this.hasActiveTurn())
21172
+ this.interruptRequested = true;
21173
+ }
21081
21174
  claimResult(userMessageUuid) {
21082
21175
  if (this.finalized || !this.rootUuid || userMessageUuid !== this.segmentOwnerUuid)
21083
21176
  return null;
@@ -21094,6 +21187,19 @@ class ClaudeTurnProtocol {
21094
21187
  this.finalized = true;
21095
21188
  return this.receipt(this.rootUuid);
21096
21189
  }
21190
+ claimInterruptedResult() {
21191
+ if (this.finalized || !this.rootUuid || !this.interruptRequested)
21192
+ return null;
21193
+ const unacknowledged = [...this.delivered.values()].filter((input) => !input.acknowledged);
21194
+ if (unacknowledged.length > 0) {
21195
+ this.awaitingFollowOn = true;
21196
+ for (const input of unacknowledged)
21197
+ input.followOn = true;
21198
+ return null;
21199
+ }
21200
+ this.finalized = true;
21201
+ return this.receipt(this.rootUuid);
21202
+ }
21097
21203
  receipt(uuid3) {
21098
21204
  return `claude:${uuid3}`;
21099
21205
  }
@@ -21119,7 +21225,9 @@ class ClaudeDriver {
21119
21225
  };
21120
21226
  turnProtocol = new ClaudeTurnProtocol;
21121
21227
  eventNormalizer = new ClaudeEventNormalizer(this.turnProtocol);
21228
+ pendingInterrupt;
21122
21229
  beginTurn() {
21230
+ this.pendingInterrupt = undefined;
21123
21231
  const receipt = this.turnProtocol.beginTurn();
21124
21232
  this.eventNormalizer.beginTurn();
21125
21233
  return receipt;
@@ -21158,11 +21266,45 @@ class ClaudeDriver {
21158
21266
  return { process: proc };
21159
21267
  }
21160
21268
  normalizeLine(line) {
21161
- return this.eventNormalizer.normalizeLine(line);
21269
+ const events = this.eventNormalizer.normalizeLine(line);
21270
+ this.flushPendingInterrupt();
21271
+ return events;
21162
21272
  }
21163
21273
  get currentSessionId() {
21164
21274
  return this.eventNormalizer.currentSessionId;
21165
21275
  }
21276
+ async interrupt(input, process3) {
21277
+ const stdin = process3.stdin;
21278
+ if (!this.turnProtocol.hasActiveTurn() || !stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false)
21279
+ return false;
21280
+ this.turnProtocol.noteInterruptRequested();
21281
+ if (!this.turnProtocol.acceptsTurnWork()) {
21282
+ this.pendingInterrupt ??= { input, process: process3 };
21283
+ return true;
21284
+ }
21285
+ this.writeInterrupt(input, process3);
21286
+ return true;
21287
+ }
21288
+ writeInterrupt(input, process3) {
21289
+ process3.stdin.write(JSON.stringify({
21290
+ type: "control_request",
21291
+ request_id: input.requestId ?? randomUUID5(),
21292
+ request: { subtype: "interrupt" }
21293
+ }) + `
21294
+ `);
21295
+ }
21296
+ flushPendingInterrupt() {
21297
+ const pending = this.pendingInterrupt;
21298
+ if (!pending || !this.turnProtocol.acceptsTurnWork())
21299
+ return;
21300
+ this.pendingInterrupt = undefined;
21301
+ const stdin = pending.process.stdin;
21302
+ if (!stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false)
21303
+ return;
21304
+ try {
21305
+ this.writeInterrupt(pending.input, pending.process);
21306
+ } catch {}
21307
+ }
21166
21308
  encodeMessage(text2, sessionId, opts) {
21167
21309
  const uuid3 = opts?.mode === "idle" ? this.turnProtocol.rootInputUuid() : this.turnProtocol.steeringInputUuid();
21168
21310
  return JSON.stringify({
@@ -21294,6 +21436,35 @@ import { randomBytes } from "node:crypto";
21294
21436
  var codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
21295
21437
  var codexQuotaSourceGeneration = 0;
21296
21438
  var codexAccountFingerprint = null;
21439
+ var CODEX_ERROR_INFO_CODES = new Set([
21440
+ "contextWindowExceeded",
21441
+ "sessionBudgetExceeded",
21442
+ "usageLimitExceeded",
21443
+ "serverOverloaded",
21444
+ "cyberPolicy",
21445
+ "misalignmentPolicyViolation",
21446
+ "internalServerError",
21447
+ "unauthorized",
21448
+ "badRequest",
21449
+ "threadRollbackFailed",
21450
+ "sandboxError",
21451
+ "other",
21452
+ "httpConnectionFailed",
21453
+ "responseStreamConnectionFailed",
21454
+ "responseStreamDisconnected",
21455
+ "responseTooManyFailedAttempts",
21456
+ "activeTurnNotSteerable"
21457
+ ]);
21458
+ function snakeCase(value) {
21459
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
21460
+ }
21461
+ function codexErrorInfoCode(value, fallback) {
21462
+ const label = typeof value === "string" ? value : value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value)[0] : undefined;
21463
+ return label && CODEX_ERROR_INFO_CODES.has(label) ? `codex.${snakeCase(label)}` : fallback;
21464
+ }
21465
+ function codexRpcErrorCode(value) {
21466
+ return typeof value === "number" && Number.isSafeInteger(value) ? `codex.rpc.${value}` : "codex.rpc_error";
21467
+ }
21297
21468
  function rotateCodexQuotaSource() {
21298
21469
  codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
21299
21470
  codexQuotaSourceGeneration += 1;
@@ -21451,7 +21622,11 @@ class CodexEventNormalizer {
21451
21622
  return this.replaceQuotaSnapshots(msg.result ?? {});
21452
21623
  }
21453
21624
  if (msg?.error && msg.id !== undefined) {
21454
- return [{ kind: "error", message: msg.error?.message ?? "Codex RPC error" }];
21625
+ return [{
21626
+ kind: "error",
21627
+ code: codexRpcErrorCode(msg.error?.code),
21628
+ message: msg.error?.message ?? "Codex RPC error"
21629
+ }];
21455
21630
  }
21456
21631
  if (msg?.result?.thread?.id) {
21457
21632
  return this.adoptAndInit(msg.result.thread.id);
@@ -21520,20 +21695,26 @@ class CodexEventNormalizer {
21520
21695
  return [];
21521
21696
  this.releaseUsageForTurn(params.threadId, params.turn.id);
21522
21697
  if (params.turn.status === "failed") {
21698
+ const error51 = params.turn.error;
21523
21699
  return [
21524
- { kind: "error", message: "Codex turn failed" },
21700
+ {
21701
+ kind: "error",
21702
+ code: codexErrorInfoCode(error51?.codexErrorInfo, "codex.turn_failed"),
21703
+ message: error51?.message ?? "Codex turn failed"
21704
+ },
21525
21705
  { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }
21526
21706
  ];
21527
21707
  }
21528
- if (params.turn.status === "interrupted") {
21529
- return [{ kind: "error", message: "Codex turn interrupted" }, { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
21530
- }
21531
21708
  return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
21532
21709
  case "error":
21533
21710
  if (params?.willRetry === true) {
21534
21711
  return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
21535
21712
  }
21536
- return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
21713
+ return [{
21714
+ kind: "error",
21715
+ code: codexErrorInfoCode(params?.error?.codexErrorInfo, "codex.error"),
21716
+ message: params?.error?.message ?? params?.message ?? "Codex error"
21717
+ }];
21537
21718
  case "thread/tokenUsage/updated":
21538
21719
  return [];
21539
21720
  case "account/rateLimits/updated":
@@ -21630,42 +21811,46 @@ class CodexEventNormalizer {
21630
21811
  }
21631
21812
  handleItemStarted(params) {
21632
21813
  const t = params?.item?.type ?? params?.type;
21814
+ const callId = this.itemCallId(params);
21815
+ const identity = callId ? { callId } : {};
21633
21816
  switch (t) {
21634
21817
  case "commandExecution":
21635
- return [{ kind: "tool_call", name: "shell", input: params?.item }];
21818
+ return [{ kind: "tool_call", ...identity, name: "shell", input: params?.item }];
21636
21819
  case "contextCompaction":
21637
21820
  return [{ kind: "compaction_started" }];
21638
21821
  case "enteredReviewMode":
21639
21822
  return [{ kind: "review_started" }];
21640
21823
  case "fileChange":
21641
- return [{ kind: "tool_call", name: "file_change", input: normalizeFileChangeInput(params?.item) }];
21824
+ return [{ kind: "tool_call", ...identity, name: "file_change", input: normalizeFileChangeInput(params?.item) }];
21642
21825
  case "mcpToolCall":
21643
- return [{ kind: "tool_call", name: `mcp_${params?.item?.name ?? "tool"}`, input: params?.item }];
21826
+ return [{ kind: "tool_call", ...identity, name: `mcp_${params?.item?.tool ?? params?.item?.name ?? "tool"}`, input: params?.item }];
21644
21827
  case "webSearch":
21645
- return [{ kind: "tool_call", name: "web_search", input: params?.item }];
21828
+ return [{ kind: "tool_call", ...identity, name: "web_search", input: params?.item }];
21646
21829
  case "collabAgentToolCall":
21647
- return [{ kind: "tool_call", name: "collab_tool_call", input: params?.item }];
21830
+ return [{ kind: "tool_call", ...identity, name: "collab_tool_call", input: params?.item }];
21648
21831
  default:
21649
21832
  return [];
21650
21833
  }
21651
21834
  }
21652
21835
  handleItemCompleted(params) {
21653
21836
  const t = params?.item?.type ?? params?.type;
21837
+ const callId = this.itemCallId(params);
21838
+ const identity = callId ? { callId } : {};
21654
21839
  switch (t) {
21655
21840
  case "commandExecution":
21656
- return [{ kind: "tool_output", name: "shell" }];
21841
+ return [{ kind: "tool_output", ...identity, name: "shell" }];
21657
21842
  case "contextCompaction":
21658
21843
  return [{ kind: "compaction_finished" }];
21659
21844
  case "exitedReviewMode":
21660
21845
  return [{ kind: "review_finished" }];
21661
21846
  case "fileChange":
21662
- return [{ kind: "tool_output", name: "file_change" }];
21847
+ return [{ kind: "tool_output", ...identity, name: "file_change" }];
21663
21848
  case "mcpToolCall":
21664
- return [{ kind: "tool_output", name: `mcp_${params?.item?.name ?? "tool"}` }];
21849
+ return [{ kind: "tool_output", ...identity, name: `mcp_${params?.item?.tool ?? params?.item?.name ?? "tool"}` }];
21665
21850
  case "webSearch":
21666
- return [{ kind: "tool_output", name: "web_search" }];
21851
+ return [{ kind: "tool_output", ...identity, name: "web_search" }];
21667
21852
  case "collabAgentToolCall":
21668
- return [{ kind: "tool_output", name: "collab_tool_call" }];
21853
+ return [{ kind: "tool_output", ...identity, name: "collab_tool_call" }];
21669
21854
  case "agentMessage":
21670
21855
  return [{ kind: "assistant_message_completed", text: params?.item?.text ?? "" }];
21671
21856
  case "reasoning":
@@ -21674,6 +21859,10 @@ class CodexEventNormalizer {
21674
21859
  return [];
21675
21860
  }
21676
21861
  }
21862
+ itemCallId(params) {
21863
+ const value = params?.item?.id ?? params?.itemId;
21864
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
21865
+ }
21677
21866
  }
21678
21867
 
21679
21868
  // agent-driver/dist/adapters/codex/home.js
@@ -22144,6 +22333,16 @@ class CodexDriver {
22144
22333
  get currentSessionId() {
22145
22334
  return this.eventNormalizer.currentSessionId;
22146
22335
  }
22336
+ async interrupt(_input, process3) {
22337
+ const threadId = this.eventNormalizer.currentSessionId;
22338
+ const turnId = this.eventNormalizer.currentTurnId;
22339
+ const stdin = process3.stdin;
22340
+ if (!threadId || !turnId || !stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false)
22341
+ return false;
22342
+ stdin.write(jsonRpcRequest("turn/interrupt", { threadId, turnId }, this.nextRequestId()) + `
22343
+ `);
22344
+ return true;
22345
+ }
22147
22346
  buildTurnStart(threadId, text2) {
22148
22347
  return jsonRpcRequest("turn/start", { threadId, input: [{ type: "text", text: text2 }] }, this.nextRequestId());
22149
22348
  }
@@ -22643,7 +22842,7 @@ class CursorAcpLane {
22643
22842
  return;
22644
22843
  const result = record3(value);
22645
22844
  if (!result || typeof result.stopReason !== "string" || !PROMPT_STOP_REASONS.has(result.stopReason)) {
22646
- this.failPrompt(prompt, new Error("Cursor ACP prompt response did not contain a supported stopReason"));
22845
+ this.failPrompt(prompt, new Error("Cursor ACP prompt response did not contain a supported stopReason"), "cursor.invalid_stop_reason");
22647
22846
  return;
22648
22847
  }
22649
22848
  const terminalOwner = this.terminalOwner ?? prompt.receipt;
@@ -22656,7 +22855,7 @@ class CursorAcpLane {
22656
22855
  turnOwner: terminalOwner
22657
22856
  });
22658
22857
  }
22659
- failPrompt(prompt, error51) {
22858
+ failPrompt(prompt, error51, code = "cursor.prompt_failed") {
22660
22859
  if (this.currentPromptRequestId !== prompt.requestId)
22661
22860
  return;
22662
22861
  const terminalOwner = this.terminalOwner ?? prompt.receipt;
@@ -22665,6 +22864,7 @@ class CursorAcpLane {
22665
22864
  this.openToolCalls.clear();
22666
22865
  this.events.emit("runtime_event", {
22667
22866
  kind: "error",
22867
+ code,
22668
22868
  message: error51 instanceof Error ? error51.message : String(error51)
22669
22869
  });
22670
22870
  this.events.emit("runtime_event", {
@@ -22818,9 +23018,10 @@ class CursorAcpLane {
22818
23018
  this.pending.delete(id);
22819
23019
  if (message2.error !== undefined) {
22820
23020
  const payload = record3(message2.error);
22821
- this.failPrompt(pending.prompt, new CursorAcpRpcError(pending.method, typeof payload?.code === "number" ? payload.code : undefined, rpcErrorMessage(message2.error)));
23021
+ const rpcCode = typeof payload?.code === "number" && Number.isSafeInteger(payload.code) ? payload.code : undefined;
23022
+ this.failPrompt(pending.prompt, new CursorAcpRpcError(pending.method, rpcCode, rpcErrorMessage(message2.error)), rpcCode === undefined ? "cursor.rpc_error" : `cursor.rpc.${rpcCode}`);
22822
23023
  } else if (!("result" in message2)) {
22823
- this.failPrompt(pending.prompt, new Error("Cursor ACP response omitted result"));
23024
+ this.failPrompt(pending.prompt, new Error("Cursor ACP response omitted result"), "cursor.invalid_response");
22824
23025
  } else {
22825
23026
  this.completePrompt(pending.prompt, message2.result);
22826
23027
  }
@@ -23105,7 +23306,11 @@ function parseModelRef(model) {
23105
23306
  function messageFromError(value) {
23106
23307
  const payload = record4(value);
23107
23308
  const message2 = typeof payload?.message === "string" && payload.message.trim() ? payload.message : undefined;
23108
- return message2 ? "OpenCode turn failed" : "OpenCode reported an inconsistent turn outcome";
23309
+ return message2 ?? "OpenCode reported an inconsistent turn outcome";
23310
+ }
23311
+ function codeFromError(value) {
23312
+ const payload = record4(value);
23313
+ return payload?.type === "unknown" ? "opencode.unknown" : "opencode.step_failed";
23109
23314
  }
23110
23315
 
23111
23316
  class OpenCodeServiceLane {
@@ -23773,7 +23978,10 @@ class OpenCodeServiceLane {
23773
23978
  root.outcomes.push({
23774
23979
  seq,
23775
23980
  ok: successful,
23776
- ...!successful ? { message: "OpenCode reported an unsupported final step outcome" } : {}
23981
+ ...!successful ? {
23982
+ code: "opencode.unsupported_finish",
23983
+ message: "OpenCode reported an unsupported final step outcome"
23984
+ } : {}
23777
23985
  });
23778
23986
  }
23779
23987
  const tokens = record4(data.tokens);
@@ -23802,7 +24010,12 @@ class OpenCodeServiceLane {
23802
24010
  break;
23803
24011
  }
23804
24012
  case "session.next.step.failed":
23805
- root.outcomes.push({ seq, ok: false, message: messageFromError(data.error) });
24013
+ root.outcomes.push({
24014
+ seq,
24015
+ ok: false,
24016
+ code: codeFromError(data.error),
24017
+ message: messageFromError(data.error)
24018
+ });
23806
24019
  break;
23807
24020
  default:
23808
24021
  break;
@@ -23982,11 +24195,11 @@ class OpenCodeServiceLane {
23982
24195
  return;
23983
24196
  const outcome = root.outcomes.filter((item) => item.seq > maxAdmission).sort((left, right) => left.seq - right.seq).at(-1);
23984
24197
  if (!root.interrupted && !outcome) {
23985
- this.settleRoot(root, false, "OpenCode drain settled without a durable turn outcome");
24198
+ this.settleRoot(root, false, "OpenCode drain settled without a durable turn outcome", "opencode.missing_outcome");
23986
24199
  return;
23987
24200
  }
23988
24201
  if (outcome && !outcome.ok) {
23989
- this.settleRoot(root, false, outcome.message ?? "OpenCode turn failed");
24202
+ this.settleRoot(root, false, outcome.message ?? "OpenCode turn failed", outcome.code ?? "opencode.turn_failed");
23990
24203
  return;
23991
24204
  }
23992
24205
  this.settleRoot(root, true);
@@ -24012,7 +24225,7 @@ class OpenCodeServiceLane {
24012
24225
  barrierStillCurrent(root, identity, generation) {
24013
24226
  return this.activeRoot === root && root.generation === generation && root.pendingAdmissions === 0 && !root.interruptPending && this.isIdentity(identity) && this.identityMatches();
24014
24227
  }
24015
- settleRoot(root, success2, message2) {
24228
+ settleRoot(root, success2, message2, code = "opencode.turn_failed") {
24016
24229
  if (this.activeRoot !== root)
24017
24230
  return;
24018
24231
  this.activeRoot = null;
@@ -24020,6 +24233,7 @@ class OpenCodeServiceLane {
24020
24233
  if (!success2) {
24021
24234
  this.events.emit("runtime_event", {
24022
24235
  kind: "error",
24236
+ code,
24023
24237
  message: message2 ?? "OpenCode turn failed"
24024
24238
  });
24025
24239
  }
@@ -24535,7 +24749,11 @@ function mapPiSdkEvent(event, sessionId, state) {
24535
24749
  return [{ kind: "assistant_message_completed", text: d.content ?? "" }];
24536
24750
  }
24537
24751
  case "error":
24538
- return [{ kind: "error", message: d.error?.errorMessage ?? "Pi error" }];
24752
+ return [{
24753
+ kind: "error",
24754
+ code: "pi.message_error",
24755
+ message: d.error?.errorMessage ?? "Pi error"
24756
+ }];
24539
24757
  default:
24540
24758
  return [];
24541
24759
  }
@@ -25123,7 +25341,9 @@ class LogicalAgentSession {
25123
25341
  interruptedTurnId;
25124
25342
  processTurnEnded = false;
25125
25343
  stopRequestId;
25126
- outstandingToolUses = 0;
25344
+ anonymousOutstandingToolUses = 0;
25345
+ outstandingToolCallIds = new Set;
25346
+ toolLifecycleMismatchCount = 0;
25127
25347
  compacting = false;
25128
25348
  reviewing = false;
25129
25349
  steeringDeliveries = new Set;
@@ -25313,6 +25533,9 @@ class LogicalAgentSession {
25313
25533
  queueDwellCount: this.metricValue(this.queueDwellCount),
25314
25534
  queueDwellTotalMs: this.metricValue(this.queueDwellTotalMs),
25315
25535
  sseReconnectCount: this.metricValue(this.sseReconnectCount),
25536
+ outstandingToolUses: this.outstandingToolUseCount(),
25537
+ anonymousOutstandingToolUses: this.metricValue(this.anonymousOutstandingToolUses),
25538
+ toolLifecycleMismatchCount: this.metricValue(this.toolLifecycleMismatchCount),
25316
25539
  resumeOutcome: this.resumeOutcome,
25317
25540
  terminalOwnerKind: this.adapter.execution.terminalOwnership
25318
25541
  }
@@ -25632,15 +25855,36 @@ class LogicalAgentSession {
25632
25855
  }
25633
25856
  return;
25634
25857
  case "tool_call":
25635
- this.outstandingToolUses += 1;
25636
- if (turnId)
25637
- this.emit({ type: "tool_started", turnId, name: event.name, input: jsonValue(event.input) });
25858
+ if (!turnId) {
25859
+ this.toolLifecycleMismatchCount += 1;
25860
+ return;
25861
+ }
25862
+ if (!this.startToolUse(event.callId))
25863
+ return;
25864
+ const startedCallId = this.toolCallId(event.callId);
25865
+ this.emit({
25866
+ type: "tool_started",
25867
+ turnId,
25868
+ ...startedCallId ? { callId: startedCallId } : {},
25869
+ name: event.name,
25870
+ input: jsonValue(event.input)
25871
+ });
25638
25872
  return;
25639
25873
  case "tool_output":
25640
- this.outstandingToolUses = Math.max(0, this.outstandingToolUses - 1);
25641
- if (turnId)
25642
- this.emit({ type: "tool_finished", turnId, name: event.name });
25643
- if (this.outstandingToolUses === 0 && !this.toolBoundaryFlushDisabled)
25874
+ if (!turnId) {
25875
+ this.toolLifecycleMismatchCount += 1;
25876
+ return;
25877
+ }
25878
+ if (!this.finishToolUse(event.callId))
25879
+ return;
25880
+ const finishedCallId = this.toolCallId(event.callId);
25881
+ this.emit({
25882
+ type: "tool_finished",
25883
+ turnId,
25884
+ ...finishedCallId ? { callId: finishedCallId } : {},
25885
+ name: event.name
25886
+ });
25887
+ if (this.outstandingToolUseCount() === 0 && !this.toolBoundaryFlushDisabled)
25644
25888
  this.flushSafeBoundaryQueue();
25645
25889
  return;
25646
25890
  case "compaction_started":
@@ -25696,7 +25940,7 @@ class LogicalAgentSession {
25696
25940
  }
25697
25941
  case "error":
25698
25942
  this.toolBoundaryFlushDisabled = true;
25699
- this.turnError = driverError("process", "runtime_error", event.message, true);
25943
+ this.turnError = driverError("process", stableErrorCode(event.code, "runtime_error"), event.message, true);
25700
25944
  this.emit({
25701
25945
  type: "diagnostic",
25702
25946
  turnId,
@@ -25783,7 +26027,7 @@ class LogicalAgentSession {
25783
26027
  this.activeTurn = undefined;
25784
26028
  this.turnError = undefined;
25785
26029
  this.interruptedTurnId = undefined;
25786
- this.outstandingToolUses = 0;
26030
+ this.clearOutstandingToolUses();
25787
26031
  this.compacting = false;
25788
26032
  this.reviewing = false;
25789
26033
  this.toolBoundaryFlushDisabled = false;
@@ -25807,7 +26051,7 @@ class LogicalAgentSession {
25807
26051
  return this.safeBoundaryFlush;
25808
26052
  }
25809
26053
  canFlushSafeBoundaryQueue() {
25810
- return this.state === "working" && this.behavior.midTurnDelivery === "safe_boundary_queue" && this.outstandingToolUses === 0 && !this.compacting && !this.reviewing && this.activeTurn !== undefined && this.lane !== null && this.queued.length > 0;
26054
+ return this.state === "working" && this.behavior.midTurnDelivery === "safe_boundary_queue" && this.outstandingToolUseCount() === 0 && !this.compacting && !this.reviewing && this.activeTurn !== undefined && this.lane !== null && this.queued.length > 0;
25811
26055
  }
25812
26056
  async drainSafeBoundaryQueue() {
25813
26057
  while (this.canFlushSafeBoundaryQueue()) {
@@ -25988,7 +26232,7 @@ class LogicalAgentSession {
25988
26232
  return "compacting";
25989
26233
  if (this.reviewing)
25990
26234
  return "reviewing";
25991
- if (this.outstandingToolUses > 0)
26235
+ if (this.outstandingToolUseCount() > 0)
25992
26236
  return "tool_wait";
25993
26237
  if (this.activeTurn)
25994
26238
  return "working";
@@ -25998,6 +26242,46 @@ class LogicalAgentSession {
25998
26242
  const now = this.host.now();
25999
26243
  return Number.isFinite(now) && now >= 0 ? now : 0;
26000
26244
  }
26245
+ outstandingToolUseCount() {
26246
+ return this.anonymousOutstandingToolUses + this.outstandingToolCallIds.size;
26247
+ }
26248
+ startToolUse(rawCallId) {
26249
+ const callId = this.toolCallId(rawCallId);
26250
+ if (!callId) {
26251
+ this.anonymousOutstandingToolUses += 1;
26252
+ return true;
26253
+ }
26254
+ if (this.outstandingToolCallIds.has(callId)) {
26255
+ this.toolLifecycleMismatchCount += 1;
26256
+ return false;
26257
+ }
26258
+ this.outstandingToolCallIds.add(callId);
26259
+ return true;
26260
+ }
26261
+ finishToolUse(rawCallId) {
26262
+ const callId = this.toolCallId(rawCallId);
26263
+ if (!callId) {
26264
+ if (this.anonymousOutstandingToolUses === 0) {
26265
+ this.toolLifecycleMismatchCount += 1;
26266
+ return false;
26267
+ }
26268
+ this.anonymousOutstandingToolUses -= 1;
26269
+ return true;
26270
+ }
26271
+ if (!this.outstandingToolCallIds.delete(callId)) {
26272
+ this.toolLifecycleMismatchCount += 1;
26273
+ return false;
26274
+ }
26275
+ return true;
26276
+ }
26277
+ clearOutstandingToolUses() {
26278
+ this.anonymousOutstandingToolUses = 0;
26279
+ this.outstandingToolCallIds.clear();
26280
+ }
26281
+ toolCallId(rawCallId) {
26282
+ const callId = rawCallId?.trim();
26283
+ return callId && callId.length <= 1024 ? callId : undefined;
26284
+ }
26001
26285
  metricValue(value) {
26002
26286
  return Number.isFinite(value) && value >= 0 ? value : 0;
26003
26287
  }
@@ -27636,10 +27920,11 @@ var DEFAULT_CAPABILITY_RESOLVER = (method, pathname) => {
27636
27920
  const isMessagesDoor = /\/channels\/[^/]+\/messages(\/|$|\?)/.test(pathname);
27637
27921
  if (isMessagesDoor)
27638
27922
  return method === "GET" ? "read" : "send";
27923
+ if (/\/messages\/[^/]+\/properties(\/|$|\?)/.test(pathname)) {
27924
+ return method === "GET" ? "read" : "send";
27925
+ }
27639
27926
  if (/\/messages\/[^/]+\/reactions\//.test(pathname))
27640
27927
  return "send";
27641
- if ((method === "PUT" || method === "DELETE") && /\/messages\/[^/]+\/marks(\/|$|\?)/.test(pathname))
27642
- return "send";
27643
27928
  if (method === "GET" && /\/users\/me\/marks(\/|$|\?)/.test(pathname))
27644
27929
  return "read";
27645
27930
  if (method === "GET" && /\/messages\/[^/]+(\?|$)/.test(pathname))
@@ -27680,6 +27965,12 @@ function isCanonicalChannelScope(channel2) {
27680
27965
  return false;
27681
27966
  }
27682
27967
  }
27968
+ function propertyAuditMetadata(method, pathname, requestUrl) {
27969
+ if (method !== "PUT" && method !== "DELETE" || !/^\/api\/community\/messages\/[^/]+\/properties$/.test(pathname))
27970
+ return;
27971
+ const type = new URL2(requestUrl, "http://placeholder").searchParams.get("type");
27972
+ return type === "emoji" || type === "tag" || type === "mark" ? { propertyType: type } : undefined;
27973
+ }
27683
27974
  function parseLocalMessageReminderBody(body, agentId) {
27684
27975
  let value;
27685
27976
  try {
@@ -27818,7 +28109,12 @@ async function startCredentialProxy(broker, options = {}) {
27818
28109
  }
27819
28110
  if (onProxyRequest) {
27820
28111
  try {
27821
- onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
28112
+ const method = req.method ?? "GET";
28113
+ const metadata = propertyAuditMetadata(method, pathname, req.url ?? pathname);
28114
+ if (metadata)
28115
+ onProxyRequest(reg.agentId, method, pathname, metadata);
28116
+ else
28117
+ onProxyRequest(reg.agentId, method, pathname);
27822
28118
  } catch {}
27823
28119
  }
27824
28120
  const outHeaders = { ...req.headers };
@@ -28101,6 +28397,7 @@ function reduceManager(state, event) {
28101
28397
  if ((lease.state === "active" || lease.state === "suspect_active") && lease.outstandingToolUses !== undefined) {
28102
28398
  const unblockedLease = { ...lease };
28103
28399
  delete unblockedLease.outstandingToolUses;
28400
+ delete unblockedLease.outstandingToolCallIds;
28104
28401
  a.execution.lease = unblockedLease;
28105
28402
  }
28106
28403
  });
@@ -28361,26 +28658,40 @@ function onTurnToolLifecycle(state, event, lifecycle) {
28361
28658
  const matchesTerminal = lifecycle === "started" && current.state === "none" && current.lastTerminal !== null && sameIdentity(current.lastTerminal.identity, identity);
28362
28659
  if (!matchesActive && !matchesTerminal)
28363
28660
  return { state, effects: [] };
28364
- if (lifecycle === "finished" && matchesActive && (current.outstandingToolUses ?? 0) === 0) {
28365
- return { state, effects: [] };
28366
- }
28367
28661
  return mutate(state, event.agentId, (agent2) => {
28368
28662
  if (!matchesSession(agent2, event.sessionInstanceId))
28369
28663
  return;
28370
28664
  const lease = agent2.execution.lease;
28665
+ const callId = normalizedToolCallId(event.callId);
28371
28666
  if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
28372
- const outstandingToolUses = (lease.outstandingToolUses ?? 0) + (lifecycle === "started" ? 1 : -1);
28667
+ const identified = new Set(lease.outstandingToolCallIds ?? []);
28668
+ const currentCount = lease.outstandingToolUses ?? 0;
28669
+ const anonymousCount = Math.max(0, currentCount - identified.size);
28670
+ if (lifecycle === "started") {
28671
+ if (callId && identified.has(callId))
28672
+ return;
28673
+ if (callId)
28674
+ identified.add(callId);
28675
+ } else if (callId) {
28676
+ if (!identified.delete(callId))
28677
+ return;
28678
+ } else if (anonymousCount === 0) {
28679
+ return;
28680
+ }
28681
+ const outstandingToolUses = currentCount + (lifecycle === "started" ? 1 : -1);
28373
28682
  if (outstandingToolUses > 0) {
28374
28683
  agent2.execution.lease = {
28375
28684
  ...lease,
28376
28685
  lastWorkAt: event.nowMs,
28377
28686
  nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
28378
28687
  recoveryExtensionsUsed: 0,
28379
- outstandingToolUses
28688
+ outstandingToolUses,
28689
+ ...identified.size > 0 ? { outstandingToolCallIds: [...identified] } : {}
28380
28690
  };
28381
28691
  } else {
28382
28692
  const unblockedLease = { ...lease };
28383
28693
  delete unblockedLease.outstandingToolUses;
28694
+ delete unblockedLease.outstandingToolCallIds;
28384
28695
  agent2.execution.lease = {
28385
28696
  ...unblockedLease,
28386
28697
  lastWorkAt: event.nowMs,
@@ -28399,6 +28710,7 @@ function onTurnToolLifecycle(state, event, lifecycle) {
28399
28710
  nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
28400
28711
  recoveryExtensionsUsed: 0,
28401
28712
  outstandingToolUses: 1,
28713
+ ...callId ? { outstandingToolCallIds: [callId] } : {},
28402
28714
  reason: "work_after_terminal"
28403
28715
  };
28404
28716
  }
@@ -28562,6 +28874,10 @@ function identityOf(event) {
28562
28874
  function sameIdentity(left, right) {
28563
28875
  return left.sessionInstanceId === right.sessionInstanceId && left.turnId === right.turnId;
28564
28876
  }
28877
+ function normalizedToolCallId(rawCallId) {
28878
+ const callId = rawCallId?.trim();
28879
+ return callId && callId.length <= 1024 ? callId : undefined;
28880
+ }
28565
28881
  function leaseIsWorking(lease) {
28566
28882
  return lease.state === "active" || lease.state === "suspect_active";
28567
28883
  }
@@ -28783,13 +29099,13 @@ function cliCommandsSection() {
28783
29099
  "",
28784
29100
  "### Messaging",
28785
29101
  "",
28786
- `1. \`${CLI} inbox pull\` — fetch unread messages; \`--no-ack\` peeks without advancing.`,
29102
+ `1. \`${CLI} inbox pull\` — fetch unread messages and advance their read waterlines.`,
28787
29103
  `2. \`${CLI} message send --target <ref> --remind-after <0|Nm|Nh> --stdin\` — send to a ` + `channel, DM, or thread. ` + `The body is required through \`--stdin\` and limited to 1 KiB of UTF-8. ` + `Attach uploaded files with \`--attachment <id>\` (repeatable, order matters). ` + `\`--remind-after\` accepts \`0\`, or a whole-number duration from \`1m\` to \`24h\`.`,
28788
29104
  `3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a file; ` + `returns an id stable across pending→persisted.`,
28789
29105
  `4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download any ` + `attachment you can see (or your own pending uploads).`,
28790
- `5. \`${CLI} message emoji --target <ref> --emoji <e>\` — react with a single emoji. ` + `Works on channel messages (\`/<server>/<channel>#N\`), DM messages ` + `(\`/.dm/<peer>#N\`), and thread-reply messages (\`/<server>/<channel>/#N#M\`).`,
28791
- `6. \`${CLI} message mark set --target <full-message-ref>\` — persist a message as outstanding work.`,
28792
- `7. \`${CLI} message mark remove --target <full-message-ref>\` — clear a completed message mark.`,
29106
+ `5. \`${CLI} message property set --target <full-message-ref> --json <json>\` — add a supported property value to a message.`,
29107
+ `6. \`${CLI} message property list --target <full-message-ref>\` — list the message's current properties and supported capabilities.`,
29108
+ `7. \`${CLI} message property remove --target <full-message-ref> --json <json>\` — remove a supported property value from a message.`,
28793
29109
  `8. \`${CLI} message mark list\` — list every currently visible marked message with its full content.`,
28794
29110
  "",
28795
29111
  "### Servers",
@@ -28797,12 +29113,14 @@ function cliCommandsSection() {
28797
29113
  `1. \`${CLI} server list\` — list your servers.`,
28798
29114
  `2. \`${CLI} server member --server <name#discriminator>\` — list a server's members.`,
28799
29115
  `3. \`${CLI} server join --invite <link>\` — join via invite link or token.`,
29116
+ `4. \`${CLI} server leave --server <name#NNNN>\` — leave a server you belong to. A server owner cannot leave.`,
28800
29117
  "",
28801
29118
  "### Channels",
28802
29119
  "",
28803
29120
  `1. \`${CLI} channel list --server <name#discriminator>\` — list top-level channels, grouped by ` + `category; each is marked \`public\` or \`private\`.`,
28804
29121
  `2. \`${CLI} channel history --channel <ref>\` — read a channel's or thread's past messages ` + `(the context you weren't awake for). Page with \`--before N\` / \`--after N\` (seq N as ` + "anchor), `--around N` to center on a message, `--limit N` for page size.",
28805
29122
  `3. \`${CLI} channel member --channel <ref>\` — roster of a channel or thread. A private ` + `channel/forum returns its concrete member list; a public one returns a hint pointing at ` + `\`${CLI} server member\` (its audience is the whole server).`,
29123
+ `4. \`${CLI} channel leave --channel <ref>\` — leave a thread or private channel/forum. Public channels require leaving their server; DMs cannot be left.`,
28806
29124
  "",
28807
29125
  "### Friends",
28808
29126
  "",
@@ -28855,6 +29173,15 @@ function messagingSection() {
28855
29173
  "Reply where the message came from. Post results in the channel that owns the topic. " + "When uncertain, read history (below) or DM the relevant people.",
28856
29174
  "Write every message body in the stdin/heredoc block; never place it directly on the command line.",
28857
29175
  "",
29176
+ "### Message properties",
29177
+ "",
29178
+ `Message properties are capabilities determined by the target message's channel type. \`${CLI} ` + "message property list --target <full-message-ref>` returns its current values and supported `capabilities`.",
29179
+ "",
29180
+ `- \`emoji\` works on text-channel, DM, and thread messages. Set or remove your own reaction with ` + `\`{"type":"emoji","value":"\uD83D\uDC4D"}\`; \`list\` shows everyone who reacted. Add: ` + `\`${CLI} message property set --target <full-message-ref> --json '{"type":"emoji","value":"\uD83D\uDC4D"}'\`.`,
29181
+ `- \`tag\` works on forum opener messages. Set or remove named tags with ` + `\`{"type":"tag","value":["bug","ready"]}\`. A forum reply is a thread message, so it ` + `supports \`emoji\`, not \`tag\`. Add: \`${CLI} message property set --target ` + `<full-message-ref> --json '{"type":"tag","value":["bug"]}'\`.`,
29182
+ `- \`mark\` works on every message. Mark outstanding work with \`{"type":"mark","value":true}\`: ` + `\`${CLI} message property set --target <full-message-ref> --json ` + `'{"type":"mark","value":true}'\`. Clear it with the same JSON through ` + `\`message property remove\`. Use \`${CLI} message mark list\` for the one-shot global view ` + `of all currently visible marked messages.`,
29183
+ "- `archived` is a reserved tag meaning the forum post is complete. Setting it hides the " + "post from the normal forum feed; removing it reopens the post.",
29184
+ "",
28858
29185
  "### Context refs",
28859
29186
  "",
28860
29187
  "Path-style refs:",
@@ -28967,6 +29294,7 @@ function criticalRulesSection() {
28967
29294
  `- **\`${CLI}\` is the only way to communicate.** Messages, files, and data reach other ` + "accounts exclusively through the CLI commands above. Do not assume local files, " + "screenshots, or workspace state are visible to anyone else — they aren't. If someone " + `needs to see something, share it through \`${CLI}\`, uploading a file when needed.`,
28968
29295
  "- **Never expose tokens, keys, or secrets.** Redact credential-like strings from tool output " + "before sharing.",
28969
29296
  "- **Match the sender's language.** Reply in the language they wrote in.",
29297
+ `- **Leaving a scope is destructive.** Use \`${CLI} channel leave\` or \`${CLI} server leave\` only after your owner explicitly asks you to leave that exact scope.`,
28970
29298
  "- **Channel alignment**: you can't send to a channel with unread messages. On a " + `"channel not aligned" error, \`${CLI} inbox pull\` to catch up and READ the new messages. ` + "Judge if your message is still needed or overlaps with what just landed. Adjust or skip; " + "don't mechanically resend."
28971
29299
  ].join(`
28972
29300
  `);
@@ -28979,7 +29307,7 @@ function executionModelSection() {
28979
29307
  "",
28980
29308
  "On wake, restore durable context from `memory.md` and the context timeline, then pull your inbox. " + "Follow *Outstanding work marks* below before taking new work.",
28981
29309
  "",
28982
- "`inbox pull` advances your read waterline by default — pulled messages won't come back in a " + "future pull. A task-bearing message stays durable through its server mark even after the " + "read waterline advances."
29310
+ "`inbox pull` advances your read waterline — pulled messages won't come back in a " + "future pull. A task-bearing message stays durable through its server mark even after the " + "read waterline advances."
28983
29311
  ].join(`
28984
29312
  `);
28985
29313
  }
@@ -29051,9 +29379,9 @@ function workspaceMemorySection(config2) {
29051
29379
  "",
29052
29380
  "### Outstanding work marks",
29053
29381
  "",
29054
- "Marks are the durable work queue. If a message requires work beyond an immediate reply, " + `acknowledge it, then run \`${CLI} message mark set --target <full-message-ref>\` before ` + "starting or queuing. Do not mark a message you answer immediately.",
29382
+ "Marks are the durable work queue. If a message requires work beyond an immediate reply, " + `acknowledge it, then run \`${CLI} message property set --target <full-message-ref> ` + `--json '{"type":"mark","value":true}'\` before ` + "starting or queuing. Do not mark a message you answer immediately.",
29055
29383
  "",
29056
- "Keep it marked while the work is active, queued, or blocked; report blockers where the task " + `came from. Run \`${CLI} message mark remove --target <full-message-ref>\` only after ` + "sending the result, or when the request is cancelled or superseded and nothing remains.",
29384
+ "Keep it marked while the work is active, queued, or blocked; report blockers where the task " + `came from. Run \`${CLI} message property remove --target <full-message-ref> ` + `--json '{"type":"mark","value":true}'\` only after ` + "sending the result, or when the request is cancelled or superseded and nothing remains.",
29057
29385
  "",
29058
29386
  "If `inbox pull` returns a `markedReminder`, run " + `\`${CLI} message mark list\` before taking new work. Do not copy marked tasks into local files.`
29059
29387
  ].join(`
@@ -29106,7 +29434,7 @@ function toLocalISO(iso) {
29106
29434
  }
29107
29435
 
29108
29436
  // src/manager/managerRuntime.ts
29109
- import { randomUUID as randomUUID5 } from "node:crypto";
29437
+ import { randomUUID as randomUUID6 } from "node:crypto";
29110
29438
  var SESSION_STOP_GRACE_MS2 = 2000;
29111
29439
  function normalizeTerminationCause(value) {
29112
29440
  return value === "runtime_error" || value === "killed_stalled" ? value : "other";
@@ -29343,7 +29671,7 @@ class AgentProcessManager {
29343
29671
  liveBackendIds = new Map;
29344
29672
  activeSpawnState = new Map;
29345
29673
  publishedAgentActivity = new Map;
29346
- traceProcessNonce = randomUUID5();
29674
+ traceProcessNonce = randomUUID6();
29347
29675
  nextSpawnOrdinal = 1;
29348
29676
  nextDaemonTurnOrdinal = 1;
29349
29677
  nextDeliveryOrdinal = 1;
@@ -29619,6 +29947,24 @@ class AgentProcessManager {
29619
29947
  if (this.sessions.get(agentId) === session2)
29620
29948
  this.sessions.delete(agentId);
29621
29949
  }
29950
+ async interrupt(agentId) {
29951
+ const session2 = this.sessions.get(agentId);
29952
+ if (!session2)
29953
+ return;
29954
+ const result = await session2.interrupt({ requestId: randomUUID6(), reason: "owner_request" });
29955
+ if (result.status !== "accepted")
29956
+ return;
29957
+ this.log.info("agent turn interrupt accepted", {
29958
+ agentId,
29959
+ sessionInstanceId: session2.sessionInstanceId,
29960
+ turnId: result.turnId
29961
+ });
29962
+ try {
29963
+ this.opts.onBotAuditEvent?.(agentId, { kind: "turn_interrupt", payload: { status: "accepted" } }, this.auditContext(agentId));
29964
+ } catch (err) {
29965
+ this.log.debug("audit emit failed (turn interrupt)", { agentId, err: String(err) });
29966
+ }
29967
+ }
29622
29968
  async stopAll() {
29623
29969
  if (this.tickTimer) {
29624
29970
  clearInterval(this.tickTimer);
@@ -30116,7 +30462,7 @@ ${this.opts.wakePromptFooter}` : text2;
30116
30462
  case "reset_idle_session": {
30117
30463
  const spawnState = this.activeSpawnState.get(effect.agentId);
30118
30464
  const completion = {
30119
- eventId: `bae_${randomUUID5()}`,
30465
+ eventId: `bae_${randomUUID6()}`,
30120
30466
  occurredAt: new Date(this.now()).toISOString()
30121
30467
  };
30122
30468
  const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId, completion);
@@ -30511,11 +30857,12 @@ ${this.opts.wakePromptFooter}` : text2;
30511
30857
  emitErrorAudit(agentId, scope, code, rawMessage) {
30512
30858
  if (!this.opts.onBotAuditEvent)
30513
30859
  return;
30860
+ const stableCode = /^[A-Za-z0-9_.-]{1,100}$/.test(code) ? code : "runtime_error";
30514
30861
  const message2 = scrubRuntimeErrorDiagnosticText(rawMessage).slice(0, AUDIT_ERROR_MESSAGE_MAX_LEN);
30515
30862
  try {
30516
30863
  this.opts.onBotAuditEvent(agentId, {
30517
30864
  kind: "error",
30518
- payload: { scope, code, message: message2, model: this.currentModelFor(agentId) }
30865
+ payload: { scope, code: stableCode, message: message2, model: this.currentModelFor(agentId) }
30519
30866
  }, {
30520
30867
  sessionId: this.liveSessions.get(agentId) ?? null,
30521
30868
  launchId: this.launchIds.get(agentId) ?? null
@@ -30540,18 +30887,18 @@ ${this.opts.wakePromptFooter}` : text2;
30540
30887
  return;
30541
30888
  }
30542
30889
  const sessionSuperseded = owner.superseded;
30543
- const errorMessage2 = event.type === "session_failed" ? event.error.message : event.type === "turn_completed" && event.result.outcome === "failed" ? event.result.error.message : undefined;
30544
- if (errorMessage2 !== undefined && !sessionSuperseded) {
30545
- this.emitErrorAudit(agentId, "runtime", "runtime_error", errorMessage2);
30890
+ const runtimeError = event.type === "session_failed" ? event.error : event.type === "turn_completed" && event.result.outcome === "failed" ? event.result.error : undefined;
30891
+ if (runtimeError !== undefined && !sessionSuperseded) {
30892
+ this.emitErrorAudit(agentId, "runtime", runtimeError.code, runtimeError.message);
30546
30893
  if (this.nonCleanEndMarker.get(agentId)?.cause !== "killed_stalled") {
30547
- this.nonCleanEndMarker.set(agentId, { cause: "runtime_error", detail: errorMessage2 });
30894
+ this.nonCleanEndMarker.set(agentId, { cause: "runtime_error", detail: runtimeError.message });
30548
30895
  }
30549
30896
  const agent2 = this.state.agents[agentId];
30550
30897
  if (agent2?.resetting && agent2.resettingSince !== null && this.now() - agent2.resettingSince >= this.state.resetStuckThresholdMs) {
30551
30898
  this.log.warn("runtime error during a stuck reset window", {
30552
30899
  agentId,
30553
30900
  resettingForMs: this.now() - agent2.resettingSince,
30554
- message: errorMessage2
30901
+ message: runtimeError.message
30555
30902
  });
30556
30903
  }
30557
30904
  }
@@ -30646,9 +30993,9 @@ ${this.opts.wakePromptFooter}` : text2;
30646
30993
  case "assistant_message_completed":
30647
30994
  return { type: "turn_work", turnId: event.turnId };
30648
30995
  case "tool_started":
30649
- return { type: "turn_tool_started", turnId: event.turnId };
30996
+ return { type: "turn_tool_started", turnId: event.turnId, callId: event.callId };
30650
30997
  case "tool_finished":
30651
- return { type: "turn_tool_finished", turnId: event.turnId };
30998
+ return { type: "turn_tool_finished", turnId: event.turnId, callId: event.callId };
30652
30999
  case "compaction_started":
30653
31000
  case "compaction_finished":
30654
31001
  case "review_started":
@@ -31101,6 +31448,10 @@ class AgentRouter {
31101
31448
  }
31102
31449
  break;
31103
31450
  }
31451
+ case "agent:interrupt": {
31452
+ await this.opts.manager.interrupt(cmd.agentId);
31453
+ break;
31454
+ }
31104
31455
  case "agent:stop":
31105
31456
  this.log.info("agent:stop received", { agentId: cmd.agentId });
31106
31457
  try {
@@ -32543,7 +32894,7 @@ class MessageReminderScheduler {
32543
32894
  }
32544
32895
 
32545
32896
  // src/manager/agentDriverHost.ts
32546
- import { randomUUID as randomUUID6 } from "node:crypto";
32897
+ import { randomUUID as randomUUID7 } from "node:crypto";
32547
32898
  import { homedir as homedir4 } from "node:os";
32548
32899
  import { join as join13 } from "node:path";
32549
32900
 
@@ -32699,7 +33050,7 @@ function createDaemonAgentDriverHost(ctx, onRawLine) {
32699
33050
  onRawLine?.(event.text);
32700
33051
  },
32701
33052
  now: () => Date.now(),
32702
- createId: () => randomUUID6()
33053
+ createId: () => randomUUID7()
32703
33054
  };
32704
33055
  }
32705
33056
 
@@ -32778,7 +33129,7 @@ class DaemonSelfSleepScheduler {
32778
33129
  // src/telemetry/dailyTokenUsage.ts
32779
33130
  import { chmod, mkdir, open, readFile as readFile2, rename, rm } from "node:fs/promises";
32780
33131
  import { dirname as dirname6, join as join14 } from "node:path";
32781
- import { randomUUID as randomUUID7 } from "node:crypto";
33132
+ import { randomUUID as randomUUID8 } from "node:crypto";
32782
33133
  var TOKEN_USAGE_WINDOW_DAYS = 30;
32783
33134
  var TOKEN_USAGE_RECOVERY_DAYS = 2;
32784
33135
  var TOKEN_USAGE_RETENTION_DAYS = TOKEN_USAGE_WINDOW_DAYS + TOKEN_USAGE_RECOVERY_DAYS;
@@ -32949,7 +33300,7 @@ class DailyTokenUsageStore {
32949
33300
  async persist() {
32950
33301
  const directory = dirname6(this.filePath);
32951
33302
  await mkdir(directory, { recursive: true, mode: 448 });
32952
- const temporary = `${this.filePath}.${randomUUID7()}.tmp`;
33303
+ const temporary = `${this.filePath}.${randomUUID8()}.tmp`;
32953
33304
  try {
32954
33305
  const file2 = await open(temporary, "wx", 384);
32955
33306
  try {
@@ -33031,15 +33382,17 @@ function createRuntimeRawLineTap(args) {
33031
33382
  }
33032
33383
  function deriveAuditLogSubcommand(pathname, method) {
33033
33384
  const canonical = pathname.split("?")[0] ?? pathname;
33034
- if (/^\/api\/community\/messages\/[^/]+\/reactions\//.test(canonical))
33035
- return "reactAdd";
33036
- if (/^\/api\/community\/messages\/[^/]+\/marks$/.test(canonical)) {
33385
+ if (/^\/api\/community\/messages\/[^/]+\/properties$/.test(canonical)) {
33386
+ if (method === "GET")
33387
+ return "messagePropertyList";
33037
33388
  if (method === "PUT")
33038
- return "markSet";
33389
+ return "messagePropertySet";
33039
33390
  if (method === "DELETE")
33040
- return "markRemove";
33391
+ return "messagePropertyRemove";
33041
33392
  return null;
33042
33393
  }
33394
+ if (/^\/api\/community\/messages\/[^/]+\/reactions\//.test(canonical))
33395
+ return "reactAdd";
33043
33396
  if (method === "GET" && /^\/api\/community\/users\/me\/marks$/.test(canonical))
33044
33397
  return "markList";
33045
33398
  if (/^\/api\/community\/channels\/[^/]+\/messages\/seq\//.test(canonical))
@@ -33205,14 +33558,14 @@ async function createDaemon(opts) {
33205
33558
  log2.warn("inbox pull timeline observation failed", { agentId, reason, contentEncoding });
33206
33559
  },
33207
33560
  onMessageReminderArm: (input) => reminderSchedulerRef?.arm(input) ?? { armed: false, reason: "reminder_scheduler_unavailable" },
33208
- onProxyRequest: (agentId, method, pathname) => {
33561
+ onProxyRequest: (agentId, method, pathname, metadata) => {
33209
33562
  const subcommand = deriveAuditLogSubcommand(pathname, method);
33210
33563
  if (!subcommand)
33211
33564
  return;
33212
33565
  const context = managerRef?.auditContext(agentId);
33213
33566
  emitBotAuditEvent(agentId, {
33214
33567
  kind: "cli_invocation",
33215
- payload: { subcommand }
33568
+ payload: { subcommand, ...metadata ? { propertyType: metadata.propertyType } : {} }
33216
33569
  }, context);
33217
33570
  emitImplicitTypingStopOnSend({
33218
33571
  subcommand,
@@ -34134,10 +34487,17 @@ function projectFsm(row, targetAgentId) {
34134
34487
  "queueDwellTotalMs",
34135
34488
  "sseReconnectCount"
34136
34489
  ];
34137
- const hasMetrics = metricKeys.some((key) => (key in row) || row[key] !== undefined) || "resumeOutcome" in row || "terminalOwnerKind" in row;
34490
+ const toolMetricKeys = [
34491
+ "outstandingToolUses",
34492
+ "anonymousOutstandingToolUses",
34493
+ "toolLifecycleMismatchCount"
34494
+ ];
34495
+ const hasMetrics = metricKeys.some((key) => (key in row) || row[key] !== undefined) || toolMetricKeys.some((key) => (key in row) || row[key] !== undefined) || "resumeOutcome" in row || "terminalOwnerKind" in row;
34138
34496
  if (hasMetrics) {
34139
34497
  if (!metricKeys.every((key) => copyFiniteNumber(row, output, key)))
34140
34498
  return null;
34499
+ if (!toolMetricKeys.every((key) => copyInteger(row, output, key, { optional: true })))
34500
+ return null;
34141
34501
  const resumeOutcome = enumValue(row.resumeOutcome, RESUME_OUTCOMES);
34142
34502
  const terminalOwnerKind = enumValue(row.terminalOwnerKind, TERMINAL_OWNER_KINDS);
34143
34503
  if (!resumeOutcome || !terminalOwnerKind)
@@ -36849,7 +37209,7 @@ ${MESSAGE_SEND_STDIN_POLICY}`);
36849
37209
  }
36850
37210
  replyToSeq = n;
36851
37211
  }
36852
- const nonce = randomUUID10();
37212
+ const nonce = randomUUID11();
36853
37213
  const res = await sendWithRetry(api2, {
36854
37214
  agentId: agent2,
36855
37215
  channel: channel2,
@@ -36878,22 +37238,43 @@ ${MESSAGE_SEND_STDIN_POLICY}`);
36878
37238
  return { sent, reminder: { armed: false, reason: "local reminder request failed" } };
36879
37239
  }
36880
37240
  }
36881
- async function cmdMessageEmoji(opts) {
37241
+ function parseMessagePropertyJson(command, raw) {
37242
+ if (typeof raw !== "string")
37243
+ throw new CliError(`${command}: --json <json> is required`);
37244
+ try {
37245
+ return JSON.parse(raw);
37246
+ } catch {
37247
+ throw new CliError(`${command}: --json must be valid JSON`);
37248
+ }
37249
+ }
37250
+ async function cmdMessagePropertySet(opts) {
36882
37251
  const api2 = getApi();
36883
37252
  const target2 = opts.target;
36884
- const emoji3 = opts.emoji;
36885
37253
  if (!target2)
36886
- throw new CliError("message emoji: --target <ref> is required (e.g. /demo#1234/general#42)");
36887
- if (!emoji3)
36888
- throw new CliError("message emoji: --emoji <string> is required");
36889
- const { channel: channel2, seq } = parseMessageTarget("message emoji", target2);
36890
- if (Buffer.byteLength(emoji3, "utf8") > MAX_EMOJI_BYTES) {
36891
- const err = new CliError("emoji is too long");
36892
- err.hint = "use a single emoji, not a phrase";
36893
- throw err;
36894
- }
36895
- const res = await api2.reactAdd({ channel: channel2, seq, emoji: emoji3 });
36896
- return { target: target2, emoji: emoji3, duplicate: res.duplicate === true };
37254
+ throw new CliError("message property set: --target <ref> is required");
37255
+ const request = parseMessageTarget("message property set", target2);
37256
+ const property = parseMessagePropertyJson("message property set", opts.json);
37257
+ const result = await withTransientMutationRetry(() => api2.messagePropertySet({ ...request, property }));
37258
+ return { target: target2, ...result };
37259
+ }
37260
+ async function cmdMessagePropertyList(opts) {
37261
+ const api2 = getApi();
37262
+ const target2 = opts.target;
37263
+ if (!target2)
37264
+ throw new CliError("message property list: --target <ref> is required");
37265
+ const request = parseMessageTarget("message property list", target2);
37266
+ const result = await api2.messagePropertyList(request);
37267
+ return { target: target2, ...result };
37268
+ }
37269
+ async function cmdMessagePropertyRemove(opts) {
37270
+ const api2 = getApi();
37271
+ const target2 = opts.target;
37272
+ if (!target2)
37273
+ throw new CliError("message property remove: --target <ref> is required");
37274
+ const request = parseMessageTarget("message property remove", target2);
37275
+ const property = parseMessagePropertyJson("message property remove", opts.json);
37276
+ const result = await withTransientMutationRetry(() => api2.messagePropertyRemove({ ...request, property }));
37277
+ return { target: target2, ...result };
36897
37278
  }
36898
37279
  function parseMessageTarget(command, target2) {
36899
37280
  let parsed;
@@ -36910,24 +37291,6 @@ function parseMessageTarget(command, target2) {
36910
37291
  const channel2 = parsed.threadRootSeq !== undefined ? `/${parsed.server}/${parsed.channel}/#${parsed.threadRootSeq}` : `/${parsed.server}/${parsed.channel}`;
36911
37292
  return { channel: channel2, seq: parsed.seq };
36912
37293
  }
36913
- async function cmdMessageMarkSet(opts) {
36914
- const api2 = getApi();
36915
- const target2 = opts.target;
36916
- if (!target2)
36917
- throw new CliError("message mark set: --target <ref> is required");
36918
- const request = parseMessageTarget("message mark set", target2);
36919
- await withTransientMutationRetry(() => api2.markSet(request));
36920
- return { target: target2, marked: true };
36921
- }
36922
- async function cmdMessageMarkRemove(opts) {
36923
- const api2 = getApi();
36924
- const target2 = opts.target;
36925
- if (!target2)
36926
- throw new CliError("message mark remove: --target <ref> is required");
36927
- const request = parseMessageTarget("message mark remove", target2);
36928
- await withTransientMutationRetry(() => api2.markRemove(request));
36929
- return { target: target2, marked: false };
36930
- }
36931
37294
  async function cmdMessageMarkList(opts) {
36932
37295
  const api2 = getApi();
36933
37296
  const { marked } = await api2.listMarks({ agentId: agentId(opts) });
@@ -37057,7 +37420,7 @@ async function cmdInboxPull(opts) {
37057
37420
  let acked = 0;
37058
37421
  let ackError;
37059
37422
  let failed;
37060
- if (opts.ack !== false && messages.length > 0) {
37423
+ if (messages.length > 0) {
37061
37424
  const latest = new Map;
37062
37425
  for (const m of messages) {
37063
37426
  const seqN = Number(m.seq.replace("#", ""));
@@ -37125,6 +37488,14 @@ async function cmdServerJoin(opts) {
37125
37488
  const { server } = await api2.joinServer({ agentId: agent2, invite: token });
37126
37489
  return { server };
37127
37490
  }
37491
+ async function cmdServerLeave(opts) {
37492
+ const api2 = getApi();
37493
+ const agent2 = agentId(opts);
37494
+ const server = opts.server;
37495
+ if (!server)
37496
+ throw new CliError("server leave: --server <name#NNNN> is required");
37497
+ return await api2.leaveServer({ agentId: agent2, server });
37498
+ }
37128
37499
  async function cmdChannelList(opts) {
37129
37500
  const api2 = getApi();
37130
37501
  const agent2 = agentId(opts);
@@ -37141,6 +37512,26 @@ async function cmdChannelMember(opts) {
37141
37512
  throw new CliError("channel member: --channel <ref> is required");
37142
37513
  return await api2.channelMember({ agentId: agent2, channel: channel2 });
37143
37514
  }
37515
+ async function cmdChannelLeave(opts) {
37516
+ const api2 = getApi();
37517
+ const agent2 = agentId(opts);
37518
+ const channel2 = opts.channel;
37519
+ if (!channel2)
37520
+ throw new CliError("channel leave: --channel <ref> is required");
37521
+ let parsed;
37522
+ try {
37523
+ parsed = parseRef(channel2);
37524
+ } catch {
37525
+ throw new CliError("channel leave: malformed channel ref");
37526
+ }
37527
+ if (parsed.server === DM_SERVER) {
37528
+ throw new CliError("channel leave: DMs cannot be left");
37529
+ }
37530
+ if (parsed.seq !== undefined) {
37531
+ throw new CliError("channel leave: message refs cannot be left; use a channel or thread-root ref");
37532
+ }
37533
+ return await api2.leaveChannel({ agentId: agent2, channel: channel2 });
37534
+ }
37144
37535
  async function cmdChannelHistory(opts) {
37145
37536
  const api2 = getApi();
37146
37537
  const agent2 = agentId(opts);
@@ -37198,22 +37589,22 @@ function buildProgram(stdin) {
37198
37589
  const result = await cmdMessageSend({ ...globalOpts, ...localOpts }, stdin);
37199
37590
  printEnvelope({ success: result });
37200
37591
  });
37201
- message2.command("emoji").description("react to a message with a single emoji").requiredOption("--target <ref>", "message ref (path-style, e.g. /demo#1234/general#42 or /.dm/peer#0007#42)").requiredOption("--emoji <string>", "single emoji character").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37202
- const localOpts = this.opts();
37203
- const globalOpts = program.opts();
37204
- const result = await cmdMessageEmoji({ ...globalOpts, ...localOpts });
37592
+ const property = message2.command("property").description("message property operations").exitOverride();
37593
+ property.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37594
+ property.command("set").description("add a supported property value to a message").requiredOption("--target <ref>", "full message ref").requiredOption("--json <json>", "property JSON").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37595
+ const result = await cmdMessagePropertySet({ ...program.opts(), ...this.opts() });
37205
37596
  printEnvelope({ success: result });
37206
37597
  });
37207
- const mark = message2.command("mark").description("durable message mark operations").exitOverride();
37208
- mark.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37209
- mark.command("set").description("mark a message as outstanding work").requiredOption("--target <ref>", "full message ref").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37210
- const result = await cmdMessageMarkSet({ ...program.opts(), ...this.opts() });
37598
+ property.command("list").description("list current properties and supported capabilities").requiredOption("--target <ref>", "full message ref").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37599
+ const result = await cmdMessagePropertyList({ ...program.opts(), ...this.opts() });
37211
37600
  printEnvelope({ success: result });
37212
37601
  });
37213
- mark.command("remove").description("remove an outstanding-work mark").requiredOption("--target <ref>", "full message ref").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37214
- const result = await cmdMessageMarkRemove({ ...program.opts(), ...this.opts() });
37602
+ property.command("remove").description("remove a supported property value from a message").requiredOption("--target <ref>", "full message ref").requiredOption("--json <json>", "property JSON").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37603
+ const result = await cmdMessagePropertyRemove({ ...program.opts(), ...this.opts() });
37215
37604
  printEnvelope({ success: result });
37216
37605
  });
37606
+ const mark = message2.command("mark").description("global durable-mark queries").exitOverride();
37607
+ mark.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37217
37608
  mark.command("list").description("list all currently visible marked messages").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37218
37609
  const result = await cmdMessageMarkList({ ...program.opts(), ...this.opts() });
37219
37610
  printEnvelope({ success: result });
@@ -37234,7 +37625,7 @@ function buildProgram(stdin) {
37234
37625
  });
37235
37626
  const inbox = program.command("inbox").description("inbox operations").exitOverride();
37236
37627
  inbox.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37237
- inbox.command("pull").description("fetch unread messages from all channels").option("--max <n>", "max messages to return").option("--no-ack", "do not advance read waterlines (peek only)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37628
+ inbox.command("pull").description("fetch unread messages from all channels").option("--max <n>", "max messages to return").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37238
37629
  const localOpts = this.opts();
37239
37630
  const globalOpts = program.opts();
37240
37631
  const result = await cmdInboxPull({ ...globalOpts, ...localOpts });
@@ -37260,6 +37651,12 @@ function buildProgram(stdin) {
37260
37651
  const result = await cmdServerJoin({ ...globalOpts, ...localOpts });
37261
37652
  printEnvelope({ success: result });
37262
37653
  });
37654
+ server.command("leave").description("leave a server you belong to").option("--server <handle>", "server name#NNNN handle (from `server list`)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37655
+ const localOpts = this.opts();
37656
+ const globalOpts = program.opts();
37657
+ const result = await cmdServerLeave({ ...globalOpts, ...localOpts });
37658
+ printEnvelope({ success: result });
37659
+ });
37263
37660
  const channel2 = program.command("channel").description("channel operations").exitOverride();
37264
37661
  channel2.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37265
37662
  channel2.command("list").description("list top-level channels visible to this agent in one server").option("--server <handle>", "server name#discriminator handle (from `server list`)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
@@ -37280,6 +37677,12 @@ function buildProgram(stdin) {
37280
37677
  const result = await cmdChannelMember({ ...globalOpts, ...localOpts });
37281
37678
  printEnvelope({ success: result });
37282
37679
  });
37680
+ channel2.command("leave").description("leave a thread or private channel/forum").option("--channel <ref>", "channel/thread ref (path-style)").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37681
+ const localOpts = this.opts();
37682
+ const globalOpts = program.opts();
37683
+ const result = await cmdChannelLeave({ ...globalOpts, ...localOpts });
37684
+ printEnvelope({ success: result });
37685
+ });
37283
37686
  const friend = program.command("friend").description("friend operations").exitOverride();
37284
37687
  friend.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37285
37688
  friend.command("request").description("send a friend request to a user by handle (owner-approval required)").option("--username <name#0042>", "the target's global handle, e.g. Alice#0042").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {