@alook/daemon 0.1.29 → 0.1.30

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),
@@ -19136,6 +19141,14 @@ function proxyServerApiFromEnv(prefix = "ALOOK", env = process.env) {
19136
19141
  function createProxyServerApi(config2) {
19137
19142
  const fetchImpl = config2.fetchImpl ?? fetch;
19138
19143
  const base = config2.proxyUrl.replace(/\/+$/, "");
19144
+ const propertyAuditQuery = (property) => {
19145
+ if (!property || typeof property !== "object" || Array.isArray(property))
19146
+ return "";
19147
+ const type = property.type;
19148
+ if (type !== "emoji" && type !== "tag" && type !== "mark")
19149
+ return "";
19150
+ return `?type=${encodeURIComponent(type)}`;
19151
+ };
19139
19152
  async function parseJsonResponse(res, method) {
19140
19153
  let text2;
19141
19154
  try {
@@ -19253,17 +19266,37 @@ function createProxyServerApi(config2) {
19253
19266
  });
19254
19267
  return parseJsonResponse(res, "resolve");
19255
19268
  }
19256
- async function callReactAdd(req) {
19257
- const res = await fetchImpl(`${base}/api/community/messages/${REF_PLACEHOLDER_ID}/reactions/${encodeURIComponent(req.emoji)}`, {
19269
+ async function callMessagePropertySet(req) {
19270
+ const res = await fetchImpl(`${base}/api/community/messages/${REF_PLACEHOLDER_ID}/properties${propertyAuditQuery(req.property)}`, {
19258
19271
  method: "PUT",
19259
19272
  headers: {
19260
19273
  "content-type": "application/json",
19261
19274
  authorization: `Bearer ${config2.voucher}`
19262
19275
  },
19263
- body: JSON.stringify({ channel: req.channel, seq: req.seq })
19276
+ body: JSON.stringify(req)
19264
19277
  });
19265
- const body = await parseJsonResponse(res, "reactAdd");
19266
- return body?.duplicate ? { ok: true, duplicate: true } : { ok: true };
19278
+ return parseJsonResponse(res, "messagePropertySet");
19279
+ }
19280
+ async function callMessagePropertyList(req) {
19281
+ const q = new URLSearchParams;
19282
+ q.set("ref", req.channel);
19283
+ q.set("seq", String(req.seq));
19284
+ const res = await fetchImpl(`${base}/api/community/messages/${REF_PLACEHOLDER_ID}/properties?${q.toString()}`, {
19285
+ method: "GET",
19286
+ headers: { authorization: `Bearer ${config2.voucher}` }
19287
+ });
19288
+ return parseJsonResponse(res, "messagePropertyList");
19289
+ }
19290
+ async function callMessagePropertyRemove(req) {
19291
+ const res = await fetchImpl(`${base}/api/community/messages/${REF_PLACEHOLDER_ID}/properties${propertyAuditQuery(req.property)}`, {
19292
+ method: "DELETE",
19293
+ headers: {
19294
+ "content-type": "application/json",
19295
+ authorization: `Bearer ${config2.voucher}`
19296
+ },
19297
+ body: JSON.stringify(req)
19298
+ });
19299
+ return parseJsonResponse(res, "messagePropertyRemove");
19267
19300
  }
19268
19301
  async function callListMembers(req) {
19269
19302
  const q = new URLSearchParams;
@@ -19315,28 +19348,6 @@ function createProxyServerApi(config2) {
19315
19348
  });
19316
19349
  return parseJsonResponse(res, "inboxPull");
19317
19350
  }
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
19351
  async function callListMarks() {
19341
19352
  const res = await fetchImpl(`${base}/api/community/users/me/marks`, {
19342
19353
  method: "GET",
@@ -19493,11 +19504,32 @@ function createProxyServerApi(config2) {
19493
19504
  }
19494
19505
  return { server: projectServer(body.server) };
19495
19506
  }
19507
+ async function callLeaveChannel(req) {
19508
+ const parsed = parseRef(req.channel);
19509
+ const scope = parsed.threadRootSeq === undefined ? "channel" : "thread";
19510
+ const collection = scope === "thread" ? "participants" : "members";
19511
+ const res = await fetchImpl(`${base}/api/community/channels/${REF_PLACEHOLDER_ID}/${collection}/self?ref=${encodeURIComponent(req.channel)}`, {
19512
+ method: "DELETE",
19513
+ headers: { authorization: `Bearer ${config2.voucher}` }
19514
+ });
19515
+ await parseJsonResponse(res, "leaveChannel");
19516
+ return { left: req.channel, scope };
19517
+ }
19518
+ async function callLeaveServer(req) {
19519
+ const res = await fetchImpl(`${base}/api/community/servers/${REF_PLACEHOLDER_ID}/leave?server=${encodeURIComponent(req.server)}`, {
19520
+ method: "POST",
19521
+ headers: { authorization: `Bearer ${config2.voucher}` }
19522
+ });
19523
+ await parseJsonResponse(res, "leaveServer");
19524
+ return { left: req.server, scope: "server" };
19525
+ }
19496
19526
  return {
19497
19527
  listServers: (_r) => callListServers(),
19498
19528
  joinServer: callJoinServer,
19529
+ leaveServer: callLeaveServer,
19499
19530
  listChannels: callListChannels,
19500
19531
  channelMember: callChannelMember,
19532
+ leaveChannel: callLeaveChannel,
19501
19533
  inboxPull: callInboxPull,
19502
19534
  inboxSnapshot: (_r) => callInboxSnapshot(),
19503
19535
  ack: callAck,
@@ -19508,9 +19540,9 @@ function createProxyServerApi(config2) {
19508
19540
  attachmentUpload: callUpload,
19509
19541
  attachmentDownload: callDownload,
19510
19542
  updateProfile: callUpdateProfile,
19511
- reactAdd: callReactAdd,
19512
- markSet: callMarkSet,
19513
- markRemove: callMarkRemove,
19543
+ messagePropertySet: callMessagePropertySet,
19544
+ messagePropertyList: callMessagePropertyList,
19545
+ messagePropertyRemove: callMessagePropertyRemove,
19514
19546
  listMarks: (_r) => callListMarks(),
19515
19547
  friendRequest: callFriendRequest,
19516
19548
  listFriends: (_r) => callListFriends(),
@@ -19557,6 +19589,9 @@ function createDefaultAgentDriverHost(options = {}) {
19557
19589
  };
19558
19590
  }
19559
19591
 
19592
+ // agent-driver/dist/adapters/claude/index.js
19593
+ import { randomUUID as randomUUID5 } from "node:crypto";
19594
+
19560
19595
  // agent-driver/dist/controller/process-host.js
19561
19596
  import { EventEmitter } from "events";
19562
19597
 
@@ -20136,6 +20171,7 @@ class ProcessLane {
20136
20171
  admissionSequence = 0;
20137
20172
  activeTerminalReceipt;
20138
20173
  stopPromise;
20174
+ pendingInterrupt;
20139
20175
  pendingPromptAdmission;
20140
20176
  constructor(driver, ctx, opts = {}) {
20141
20177
  this.driver = driver;
@@ -20175,12 +20211,14 @@ class ProcessLane {
20175
20211
  try {
20176
20212
  spawned = await this.driver.spawn(launchCtx);
20177
20213
  } catch (error51) {
20214
+ this.settlePendingInterrupt(false);
20178
20215
  this.settlePendingPrompt({ ok: false, reason: "runtime_error", error: String(error51) });
20179
20216
  throw error51;
20180
20217
  }
20181
20218
  const { process: proc } = spawned;
20182
20219
  this.process = proc;
20183
20220
  this.attachProcess(proc);
20221
+ this.dispatchPendingInterrupt(proc);
20184
20222
  this.armPendingPromptTimeout();
20185
20223
  if (this.driver.execution.lifetime === "session" && !this.writableStdin(proc)) {
20186
20224
  const rejection = this.stdinUnavailableAdmission();
@@ -20279,6 +20317,7 @@ class ProcessLane {
20279
20317
  pending.resolve(admission);
20280
20318
  }
20281
20319
  stop(opts = {}) {
20320
+ this.settlePendingInterrupt(false);
20282
20321
  this.settlePendingPrompt({ ok: false, reason: "closed", error: "runtime lane stopped before command admission" });
20283
20322
  if (!this.process)
20284
20323
  return Promise.resolve();
@@ -20297,11 +20336,39 @@ class ProcessLane {
20297
20336
  proc.kill(opts?.signal ?? "SIGTERM");
20298
20337
  }
20299
20338
  }
20300
- async interrupt() {
20339
+ async interrupt(input) {
20301
20340
  const proc = this.process;
20302
- if (!proc || this.closed)
20341
+ if (!proc) {
20342
+ if (!this.started)
20343
+ return false;
20344
+ if (this.pendingInterrupt)
20345
+ return this.pendingInterrupt.promise;
20346
+ let resolve;
20347
+ let reject;
20348
+ const promise2 = new Promise((res, rej) => {
20349
+ resolve = res;
20350
+ reject = rej;
20351
+ });
20352
+ this.pendingInterrupt = { input, promise: promise2, resolve, reject };
20353
+ return promise2;
20354
+ }
20355
+ if (this.closed)
20303
20356
  return false;
20304
- return proc.kill("SIGINT");
20357
+ return this.driver.interrupt(input, proc);
20358
+ }
20359
+ dispatchPendingInterrupt(proc) {
20360
+ const pending = this.pendingInterrupt;
20361
+ if (!pending)
20362
+ return;
20363
+ this.pendingInterrupt = undefined;
20364
+ this.driver.interrupt(pending.input, proc).then(pending.resolve, pending.reject);
20365
+ }
20366
+ settlePendingInterrupt(accepted) {
20367
+ const pending = this.pendingInterrupt;
20368
+ if (!pending)
20369
+ return;
20370
+ this.pendingInterrupt = undefined;
20371
+ pending.resolve(accepted);
20305
20372
  }
20306
20373
  updateSettings(input) {
20307
20374
  return this.driver.updateSettings?.(input) ?? Promise.resolve({ status: "unsupported" });
@@ -20638,6 +20705,13 @@ function tryParseJsonLine(line) {
20638
20705
 
20639
20706
  // agent-driver/dist/adapters/claude/normalizer.js
20640
20707
  var API_ERROR_RE = /API Error:.*(?:Connection error|\b[45]\d{2}\b)/i;
20708
+ var CLAUDE_RESULT_ERROR_CODES = new Map([
20709
+ ["error_max_turns", "claude.error_max_turns"],
20710
+ ["error_during_execution", "claude.error_during_execution"],
20711
+ ["error_max_budget_usd", "claude.error_max_budget_usd"],
20712
+ ["error_max_structured_output_retries", "claude.error_max_structured_output_retries"]
20713
+ ]);
20714
+ var CLAUDE_INTERRUPT_TERMINAL_REASONS = new Set(["aborted_streaming", "aborted_tools"]);
20641
20715
  var CLAUDE_USAGE_COMPONENTS = [
20642
20716
  "inputTokens",
20643
20717
  "outputTokens",
@@ -20731,9 +20805,9 @@ class ClaudeEventNormalizer {
20731
20805
  out.push({ kind: "assistant_reasoning_completed", text: block.thinking ?? "" });
20732
20806
  } else if (block?.type === "text") {
20733
20807
  const text2 = block.text ?? "";
20734
- if (API_ERROR_RE.test(text2))
20735
- out.push({ kind: "error", message: text2 });
20736
- else
20808
+ if (API_ERROR_RE.test(text2)) {
20809
+ out.push({ kind: "error", code: "claude.api_error", message: text2 });
20810
+ } else
20737
20811
  completedText.push(text2);
20738
20812
  } else if (block?.type === "tool_use") {
20739
20813
  out.push({ kind: "tool_call", name: block.name ?? "unknown_tool", input: block.input });
@@ -20759,14 +20833,19 @@ class ClaudeEventNormalizer {
20759
20833
  }
20760
20834
  handleResult(event, out) {
20761
20835
  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;
20836
+ const interruptedTerminal = Boolean(this.turnProtocol && !rawOwner && event.subtype === "error_during_execution" && CLAUDE_INTERRUPT_TERMINAL_REASONS.has(event.terminal_reason));
20837
+ const turnOwner = rawOwner ? this.turnProtocol?.claimResult(rawOwner) ?? (this.turnProtocol ? null : `claude:${rawOwner}`) : interruptedTerminal ? this.turnProtocol.claimInterruptedResult() : null;
20763
20838
  if (this.turnProtocol && !turnOwner)
20764
20839
  return;
20765
20840
  const usage = this.buildUsageTelemetry(event, rawOwner);
20766
20841
  if (usage)
20767
20842
  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") });
20843
+ if (!interruptedTerminal && (event.is_error || event.subtype === "error_during_execution")) {
20844
+ out.push({
20845
+ kind: "error",
20846
+ code: CLAUDE_RESULT_ERROR_CODES.get(event.subtype) ?? "claude.result_error",
20847
+ message: String(event.result ?? "Claude runtime error")
20848
+ });
20770
20849
  }
20771
20850
  out.push({
20772
20851
  kind: "turn_end",
@@ -21044,6 +21123,7 @@ class ClaudeTurnProtocol {
21044
21123
  segmentOwnerUuid = null;
21045
21124
  delivered = new Map;
21046
21125
  awaitingFollowOn = false;
21126
+ interruptRequested = false;
21047
21127
  finalized = false;
21048
21128
  beginTurn() {
21049
21129
  const uuid3 = randomUUID4();
@@ -21052,6 +21132,7 @@ class ClaudeTurnProtocol {
21052
21132
  this.delivered.clear();
21053
21133
  this.delivered.set(uuid3, { acknowledged: false, followOn: false });
21054
21134
  this.awaitingFollowOn = false;
21135
+ this.interruptRequested = false;
21055
21136
  this.finalized = false;
21056
21137
  return this.receipt(uuid3);
21057
21138
  }
@@ -21078,6 +21159,13 @@ class ClaudeTurnProtocol {
21078
21159
  return false;
21079
21160
  return this.delivered.get(this.rootUuid)?.acknowledged === true;
21080
21161
  }
21162
+ hasActiveTurn() {
21163
+ return this.rootUuid !== null && !this.finalized;
21164
+ }
21165
+ noteInterruptRequested() {
21166
+ if (this.hasActiveTurn())
21167
+ this.interruptRequested = true;
21168
+ }
21081
21169
  claimResult(userMessageUuid) {
21082
21170
  if (this.finalized || !this.rootUuid || userMessageUuid !== this.segmentOwnerUuid)
21083
21171
  return null;
@@ -21094,6 +21182,19 @@ class ClaudeTurnProtocol {
21094
21182
  this.finalized = true;
21095
21183
  return this.receipt(this.rootUuid);
21096
21184
  }
21185
+ claimInterruptedResult() {
21186
+ if (this.finalized || !this.rootUuid || !this.interruptRequested)
21187
+ return null;
21188
+ const unacknowledged = [...this.delivered.values()].filter((input) => !input.acknowledged);
21189
+ if (unacknowledged.length > 0) {
21190
+ this.awaitingFollowOn = true;
21191
+ for (const input of unacknowledged)
21192
+ input.followOn = true;
21193
+ return null;
21194
+ }
21195
+ this.finalized = true;
21196
+ return this.receipt(this.rootUuid);
21197
+ }
21097
21198
  receipt(uuid3) {
21098
21199
  return `claude:${uuid3}`;
21099
21200
  }
@@ -21119,7 +21220,9 @@ class ClaudeDriver {
21119
21220
  };
21120
21221
  turnProtocol = new ClaudeTurnProtocol;
21121
21222
  eventNormalizer = new ClaudeEventNormalizer(this.turnProtocol);
21223
+ pendingInterrupt;
21122
21224
  beginTurn() {
21225
+ this.pendingInterrupt = undefined;
21123
21226
  const receipt = this.turnProtocol.beginTurn();
21124
21227
  this.eventNormalizer.beginTurn();
21125
21228
  return receipt;
@@ -21158,11 +21261,45 @@ class ClaudeDriver {
21158
21261
  return { process: proc };
21159
21262
  }
21160
21263
  normalizeLine(line) {
21161
- return this.eventNormalizer.normalizeLine(line);
21264
+ const events = this.eventNormalizer.normalizeLine(line);
21265
+ this.flushPendingInterrupt();
21266
+ return events;
21162
21267
  }
21163
21268
  get currentSessionId() {
21164
21269
  return this.eventNormalizer.currentSessionId;
21165
21270
  }
21271
+ async interrupt(input, process3) {
21272
+ const stdin = process3.stdin;
21273
+ if (!this.turnProtocol.hasActiveTurn() || !stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false)
21274
+ return false;
21275
+ this.turnProtocol.noteInterruptRequested();
21276
+ if (!this.turnProtocol.acceptsTurnWork()) {
21277
+ this.pendingInterrupt ??= { input, process: process3 };
21278
+ return true;
21279
+ }
21280
+ this.writeInterrupt(input, process3);
21281
+ return true;
21282
+ }
21283
+ writeInterrupt(input, process3) {
21284
+ process3.stdin.write(JSON.stringify({
21285
+ type: "control_request",
21286
+ request_id: input.requestId ?? randomUUID5(),
21287
+ request: { subtype: "interrupt" }
21288
+ }) + `
21289
+ `);
21290
+ }
21291
+ flushPendingInterrupt() {
21292
+ const pending = this.pendingInterrupt;
21293
+ if (!pending || !this.turnProtocol.acceptsTurnWork())
21294
+ return;
21295
+ this.pendingInterrupt = undefined;
21296
+ const stdin = pending.process.stdin;
21297
+ if (!stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false)
21298
+ return;
21299
+ try {
21300
+ this.writeInterrupt(pending.input, pending.process);
21301
+ } catch {}
21302
+ }
21166
21303
  encodeMessage(text2, sessionId, opts) {
21167
21304
  const uuid3 = opts?.mode === "idle" ? this.turnProtocol.rootInputUuid() : this.turnProtocol.steeringInputUuid();
21168
21305
  return JSON.stringify({
@@ -21294,6 +21431,35 @@ import { randomBytes } from "node:crypto";
21294
21431
  var codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
21295
21432
  var codexQuotaSourceGeneration = 0;
21296
21433
  var codexAccountFingerprint = null;
21434
+ var CODEX_ERROR_INFO_CODES = new Set([
21435
+ "contextWindowExceeded",
21436
+ "sessionBudgetExceeded",
21437
+ "usageLimitExceeded",
21438
+ "serverOverloaded",
21439
+ "cyberPolicy",
21440
+ "misalignmentPolicyViolation",
21441
+ "internalServerError",
21442
+ "unauthorized",
21443
+ "badRequest",
21444
+ "threadRollbackFailed",
21445
+ "sandboxError",
21446
+ "other",
21447
+ "httpConnectionFailed",
21448
+ "responseStreamConnectionFailed",
21449
+ "responseStreamDisconnected",
21450
+ "responseTooManyFailedAttempts",
21451
+ "activeTurnNotSteerable"
21452
+ ]);
21453
+ function snakeCase(value) {
21454
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
21455
+ }
21456
+ function codexErrorInfoCode(value, fallback) {
21457
+ const label = typeof value === "string" ? value : value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value)[0] : undefined;
21458
+ return label && CODEX_ERROR_INFO_CODES.has(label) ? `codex.${snakeCase(label)}` : fallback;
21459
+ }
21460
+ function codexRpcErrorCode(value) {
21461
+ return typeof value === "number" && Number.isSafeInteger(value) ? `codex.rpc.${value}` : "codex.rpc_error";
21462
+ }
21297
21463
  function rotateCodexQuotaSource() {
21298
21464
  codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
21299
21465
  codexQuotaSourceGeneration += 1;
@@ -21451,7 +21617,11 @@ class CodexEventNormalizer {
21451
21617
  return this.replaceQuotaSnapshots(msg.result ?? {});
21452
21618
  }
21453
21619
  if (msg?.error && msg.id !== undefined) {
21454
- return [{ kind: "error", message: msg.error?.message ?? "Codex RPC error" }];
21620
+ return [{
21621
+ kind: "error",
21622
+ code: codexRpcErrorCode(msg.error?.code),
21623
+ message: msg.error?.message ?? "Codex RPC error"
21624
+ }];
21455
21625
  }
21456
21626
  if (msg?.result?.thread?.id) {
21457
21627
  return this.adoptAndInit(msg.result.thread.id);
@@ -21520,20 +21690,26 @@ class CodexEventNormalizer {
21520
21690
  return [];
21521
21691
  this.releaseUsageForTurn(params.threadId, params.turn.id);
21522
21692
  if (params.turn.status === "failed") {
21693
+ const error51 = params.turn.error;
21523
21694
  return [
21524
- { kind: "error", message: "Codex turn failed" },
21695
+ {
21696
+ kind: "error",
21697
+ code: codexErrorInfoCode(error51?.codexErrorInfo, "codex.turn_failed"),
21698
+ message: error51?.message ?? "Codex turn failed"
21699
+ },
21525
21700
  { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }
21526
21701
  ];
21527
21702
  }
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
21703
  return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
21532
21704
  case "error":
21533
21705
  if (params?.willRetry === true) {
21534
21706
  return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
21535
21707
  }
21536
- return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
21708
+ return [{
21709
+ kind: "error",
21710
+ code: codexErrorInfoCode(params?.error?.codexErrorInfo, "codex.error"),
21711
+ message: params?.error?.message ?? params?.message ?? "Codex error"
21712
+ }];
21537
21713
  case "thread/tokenUsage/updated":
21538
21714
  return [];
21539
21715
  case "account/rateLimits/updated":
@@ -21630,42 +21806,46 @@ class CodexEventNormalizer {
21630
21806
  }
21631
21807
  handleItemStarted(params) {
21632
21808
  const t = params?.item?.type ?? params?.type;
21809
+ const callId = this.itemCallId(params);
21810
+ const identity = callId ? { callId } : {};
21633
21811
  switch (t) {
21634
21812
  case "commandExecution":
21635
- return [{ kind: "tool_call", name: "shell", input: params?.item }];
21813
+ return [{ kind: "tool_call", ...identity, name: "shell", input: params?.item }];
21636
21814
  case "contextCompaction":
21637
21815
  return [{ kind: "compaction_started" }];
21638
21816
  case "enteredReviewMode":
21639
21817
  return [{ kind: "review_started" }];
21640
21818
  case "fileChange":
21641
- return [{ kind: "tool_call", name: "file_change", input: normalizeFileChangeInput(params?.item) }];
21819
+ return [{ kind: "tool_call", ...identity, name: "file_change", input: normalizeFileChangeInput(params?.item) }];
21642
21820
  case "mcpToolCall":
21643
- return [{ kind: "tool_call", name: `mcp_${params?.item?.name ?? "tool"}`, input: params?.item }];
21821
+ return [{ kind: "tool_call", ...identity, name: `mcp_${params?.item?.tool ?? params?.item?.name ?? "tool"}`, input: params?.item }];
21644
21822
  case "webSearch":
21645
- return [{ kind: "tool_call", name: "web_search", input: params?.item }];
21823
+ return [{ kind: "tool_call", ...identity, name: "web_search", input: params?.item }];
21646
21824
  case "collabAgentToolCall":
21647
- return [{ kind: "tool_call", name: "collab_tool_call", input: params?.item }];
21825
+ return [{ kind: "tool_call", ...identity, name: "collab_tool_call", input: params?.item }];
21648
21826
  default:
21649
21827
  return [];
21650
21828
  }
21651
21829
  }
21652
21830
  handleItemCompleted(params) {
21653
21831
  const t = params?.item?.type ?? params?.type;
21832
+ const callId = this.itemCallId(params);
21833
+ const identity = callId ? { callId } : {};
21654
21834
  switch (t) {
21655
21835
  case "commandExecution":
21656
- return [{ kind: "tool_output", name: "shell" }];
21836
+ return [{ kind: "tool_output", ...identity, name: "shell" }];
21657
21837
  case "contextCompaction":
21658
21838
  return [{ kind: "compaction_finished" }];
21659
21839
  case "exitedReviewMode":
21660
21840
  return [{ kind: "review_finished" }];
21661
21841
  case "fileChange":
21662
- return [{ kind: "tool_output", name: "file_change" }];
21842
+ return [{ kind: "tool_output", ...identity, name: "file_change" }];
21663
21843
  case "mcpToolCall":
21664
- return [{ kind: "tool_output", name: `mcp_${params?.item?.name ?? "tool"}` }];
21844
+ return [{ kind: "tool_output", ...identity, name: `mcp_${params?.item?.tool ?? params?.item?.name ?? "tool"}` }];
21665
21845
  case "webSearch":
21666
- return [{ kind: "tool_output", name: "web_search" }];
21846
+ return [{ kind: "tool_output", ...identity, name: "web_search" }];
21667
21847
  case "collabAgentToolCall":
21668
- return [{ kind: "tool_output", name: "collab_tool_call" }];
21848
+ return [{ kind: "tool_output", ...identity, name: "collab_tool_call" }];
21669
21849
  case "agentMessage":
21670
21850
  return [{ kind: "assistant_message_completed", text: params?.item?.text ?? "" }];
21671
21851
  case "reasoning":
@@ -21674,6 +21854,10 @@ class CodexEventNormalizer {
21674
21854
  return [];
21675
21855
  }
21676
21856
  }
21857
+ itemCallId(params) {
21858
+ const value = params?.item?.id ?? params?.itemId;
21859
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
21860
+ }
21677
21861
  }
21678
21862
 
21679
21863
  // agent-driver/dist/adapters/codex/home.js
@@ -22144,6 +22328,16 @@ class CodexDriver {
22144
22328
  get currentSessionId() {
22145
22329
  return this.eventNormalizer.currentSessionId;
22146
22330
  }
22331
+ async interrupt(_input, process3) {
22332
+ const threadId = this.eventNormalizer.currentSessionId;
22333
+ const turnId = this.eventNormalizer.currentTurnId;
22334
+ const stdin = process3.stdin;
22335
+ if (!threadId || !turnId || !stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false)
22336
+ return false;
22337
+ stdin.write(jsonRpcRequest("turn/interrupt", { threadId, turnId }, this.nextRequestId()) + `
22338
+ `);
22339
+ return true;
22340
+ }
22147
22341
  buildTurnStart(threadId, text2) {
22148
22342
  return jsonRpcRequest("turn/start", { threadId, input: [{ type: "text", text: text2 }] }, this.nextRequestId());
22149
22343
  }
@@ -22643,7 +22837,7 @@ class CursorAcpLane {
22643
22837
  return;
22644
22838
  const result = record3(value);
22645
22839
  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"));
22840
+ this.failPrompt(prompt, new Error("Cursor ACP prompt response did not contain a supported stopReason"), "cursor.invalid_stop_reason");
22647
22841
  return;
22648
22842
  }
22649
22843
  const terminalOwner = this.terminalOwner ?? prompt.receipt;
@@ -22656,7 +22850,7 @@ class CursorAcpLane {
22656
22850
  turnOwner: terminalOwner
22657
22851
  });
22658
22852
  }
22659
- failPrompt(prompt, error51) {
22853
+ failPrompt(prompt, error51, code = "cursor.prompt_failed") {
22660
22854
  if (this.currentPromptRequestId !== prompt.requestId)
22661
22855
  return;
22662
22856
  const terminalOwner = this.terminalOwner ?? prompt.receipt;
@@ -22665,6 +22859,7 @@ class CursorAcpLane {
22665
22859
  this.openToolCalls.clear();
22666
22860
  this.events.emit("runtime_event", {
22667
22861
  kind: "error",
22862
+ code,
22668
22863
  message: error51 instanceof Error ? error51.message : String(error51)
22669
22864
  });
22670
22865
  this.events.emit("runtime_event", {
@@ -22818,9 +23013,10 @@ class CursorAcpLane {
22818
23013
  this.pending.delete(id);
22819
23014
  if (message2.error !== undefined) {
22820
23015
  const payload = record3(message2.error);
22821
- this.failPrompt(pending.prompt, new CursorAcpRpcError(pending.method, typeof payload?.code === "number" ? payload.code : undefined, rpcErrorMessage(message2.error)));
23016
+ const rpcCode = typeof payload?.code === "number" && Number.isSafeInteger(payload.code) ? payload.code : undefined;
23017
+ this.failPrompt(pending.prompt, new CursorAcpRpcError(pending.method, rpcCode, rpcErrorMessage(message2.error)), rpcCode === undefined ? "cursor.rpc_error" : `cursor.rpc.${rpcCode}`);
22822
23018
  } else if (!("result" in message2)) {
22823
- this.failPrompt(pending.prompt, new Error("Cursor ACP response omitted result"));
23019
+ this.failPrompt(pending.prompt, new Error("Cursor ACP response omitted result"), "cursor.invalid_response");
22824
23020
  } else {
22825
23021
  this.completePrompt(pending.prompt, message2.result);
22826
23022
  }
@@ -23105,7 +23301,11 @@ function parseModelRef(model) {
23105
23301
  function messageFromError(value) {
23106
23302
  const payload = record4(value);
23107
23303
  const message2 = typeof payload?.message === "string" && payload.message.trim() ? payload.message : undefined;
23108
- return message2 ? "OpenCode turn failed" : "OpenCode reported an inconsistent turn outcome";
23304
+ return message2 ?? "OpenCode reported an inconsistent turn outcome";
23305
+ }
23306
+ function codeFromError(value) {
23307
+ const payload = record4(value);
23308
+ return payload?.type === "unknown" ? "opencode.unknown" : "opencode.step_failed";
23109
23309
  }
23110
23310
 
23111
23311
  class OpenCodeServiceLane {
@@ -23773,7 +23973,10 @@ class OpenCodeServiceLane {
23773
23973
  root.outcomes.push({
23774
23974
  seq,
23775
23975
  ok: successful,
23776
- ...!successful ? { message: "OpenCode reported an unsupported final step outcome" } : {}
23976
+ ...!successful ? {
23977
+ code: "opencode.unsupported_finish",
23978
+ message: "OpenCode reported an unsupported final step outcome"
23979
+ } : {}
23777
23980
  });
23778
23981
  }
23779
23982
  const tokens = record4(data.tokens);
@@ -23802,7 +24005,12 @@ class OpenCodeServiceLane {
23802
24005
  break;
23803
24006
  }
23804
24007
  case "session.next.step.failed":
23805
- root.outcomes.push({ seq, ok: false, message: messageFromError(data.error) });
24008
+ root.outcomes.push({
24009
+ seq,
24010
+ ok: false,
24011
+ code: codeFromError(data.error),
24012
+ message: messageFromError(data.error)
24013
+ });
23806
24014
  break;
23807
24015
  default:
23808
24016
  break;
@@ -23982,11 +24190,11 @@ class OpenCodeServiceLane {
23982
24190
  return;
23983
24191
  const outcome = root.outcomes.filter((item) => item.seq > maxAdmission).sort((left, right) => left.seq - right.seq).at(-1);
23984
24192
  if (!root.interrupted && !outcome) {
23985
- this.settleRoot(root, false, "OpenCode drain settled without a durable turn outcome");
24193
+ this.settleRoot(root, false, "OpenCode drain settled without a durable turn outcome", "opencode.missing_outcome");
23986
24194
  return;
23987
24195
  }
23988
24196
  if (outcome && !outcome.ok) {
23989
- this.settleRoot(root, false, outcome.message ?? "OpenCode turn failed");
24197
+ this.settleRoot(root, false, outcome.message ?? "OpenCode turn failed", outcome.code ?? "opencode.turn_failed");
23990
24198
  return;
23991
24199
  }
23992
24200
  this.settleRoot(root, true);
@@ -24012,7 +24220,7 @@ class OpenCodeServiceLane {
24012
24220
  barrierStillCurrent(root, identity, generation) {
24013
24221
  return this.activeRoot === root && root.generation === generation && root.pendingAdmissions === 0 && !root.interruptPending && this.isIdentity(identity) && this.identityMatches();
24014
24222
  }
24015
- settleRoot(root, success2, message2) {
24223
+ settleRoot(root, success2, message2, code = "opencode.turn_failed") {
24016
24224
  if (this.activeRoot !== root)
24017
24225
  return;
24018
24226
  this.activeRoot = null;
@@ -24020,6 +24228,7 @@ class OpenCodeServiceLane {
24020
24228
  if (!success2) {
24021
24229
  this.events.emit("runtime_event", {
24022
24230
  kind: "error",
24231
+ code,
24023
24232
  message: message2 ?? "OpenCode turn failed"
24024
24233
  });
24025
24234
  }
@@ -24535,7 +24744,11 @@ function mapPiSdkEvent(event, sessionId, state) {
24535
24744
  return [{ kind: "assistant_message_completed", text: d.content ?? "" }];
24536
24745
  }
24537
24746
  case "error":
24538
- return [{ kind: "error", message: d.error?.errorMessage ?? "Pi error" }];
24747
+ return [{
24748
+ kind: "error",
24749
+ code: "pi.message_error",
24750
+ message: d.error?.errorMessage ?? "Pi error"
24751
+ }];
24539
24752
  default:
24540
24753
  return [];
24541
24754
  }
@@ -25123,7 +25336,9 @@ class LogicalAgentSession {
25123
25336
  interruptedTurnId;
25124
25337
  processTurnEnded = false;
25125
25338
  stopRequestId;
25126
- outstandingToolUses = 0;
25339
+ anonymousOutstandingToolUses = 0;
25340
+ outstandingToolCallIds = new Set;
25341
+ toolLifecycleMismatchCount = 0;
25127
25342
  compacting = false;
25128
25343
  reviewing = false;
25129
25344
  steeringDeliveries = new Set;
@@ -25313,6 +25528,9 @@ class LogicalAgentSession {
25313
25528
  queueDwellCount: this.metricValue(this.queueDwellCount),
25314
25529
  queueDwellTotalMs: this.metricValue(this.queueDwellTotalMs),
25315
25530
  sseReconnectCount: this.metricValue(this.sseReconnectCount),
25531
+ outstandingToolUses: this.outstandingToolUseCount(),
25532
+ anonymousOutstandingToolUses: this.metricValue(this.anonymousOutstandingToolUses),
25533
+ toolLifecycleMismatchCount: this.metricValue(this.toolLifecycleMismatchCount),
25316
25534
  resumeOutcome: this.resumeOutcome,
25317
25535
  terminalOwnerKind: this.adapter.execution.terminalOwnership
25318
25536
  }
@@ -25632,15 +25850,36 @@ class LogicalAgentSession {
25632
25850
  }
25633
25851
  return;
25634
25852
  case "tool_call":
25635
- this.outstandingToolUses += 1;
25636
- if (turnId)
25637
- this.emit({ type: "tool_started", turnId, name: event.name, input: jsonValue(event.input) });
25853
+ if (!turnId) {
25854
+ this.toolLifecycleMismatchCount += 1;
25855
+ return;
25856
+ }
25857
+ if (!this.startToolUse(event.callId))
25858
+ return;
25859
+ const startedCallId = this.toolCallId(event.callId);
25860
+ this.emit({
25861
+ type: "tool_started",
25862
+ turnId,
25863
+ ...startedCallId ? { callId: startedCallId } : {},
25864
+ name: event.name,
25865
+ input: jsonValue(event.input)
25866
+ });
25638
25867
  return;
25639
25868
  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)
25869
+ if (!turnId) {
25870
+ this.toolLifecycleMismatchCount += 1;
25871
+ return;
25872
+ }
25873
+ if (!this.finishToolUse(event.callId))
25874
+ return;
25875
+ const finishedCallId = this.toolCallId(event.callId);
25876
+ this.emit({
25877
+ type: "tool_finished",
25878
+ turnId,
25879
+ ...finishedCallId ? { callId: finishedCallId } : {},
25880
+ name: event.name
25881
+ });
25882
+ if (this.outstandingToolUseCount() === 0 && !this.toolBoundaryFlushDisabled)
25644
25883
  this.flushSafeBoundaryQueue();
25645
25884
  return;
25646
25885
  case "compaction_started":
@@ -25696,7 +25935,7 @@ class LogicalAgentSession {
25696
25935
  }
25697
25936
  case "error":
25698
25937
  this.toolBoundaryFlushDisabled = true;
25699
- this.turnError = driverError("process", "runtime_error", event.message, true);
25938
+ this.turnError = driverError("process", stableErrorCode(event.code, "runtime_error"), event.message, true);
25700
25939
  this.emit({
25701
25940
  type: "diagnostic",
25702
25941
  turnId,
@@ -25783,7 +26022,7 @@ class LogicalAgentSession {
25783
26022
  this.activeTurn = undefined;
25784
26023
  this.turnError = undefined;
25785
26024
  this.interruptedTurnId = undefined;
25786
- this.outstandingToolUses = 0;
26025
+ this.clearOutstandingToolUses();
25787
26026
  this.compacting = false;
25788
26027
  this.reviewing = false;
25789
26028
  this.toolBoundaryFlushDisabled = false;
@@ -25807,7 +26046,7 @@ class LogicalAgentSession {
25807
26046
  return this.safeBoundaryFlush;
25808
26047
  }
25809
26048
  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;
26049
+ 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
26050
  }
25812
26051
  async drainSafeBoundaryQueue() {
25813
26052
  while (this.canFlushSafeBoundaryQueue()) {
@@ -25988,7 +26227,7 @@ class LogicalAgentSession {
25988
26227
  return "compacting";
25989
26228
  if (this.reviewing)
25990
26229
  return "reviewing";
25991
- if (this.outstandingToolUses > 0)
26230
+ if (this.outstandingToolUseCount() > 0)
25992
26231
  return "tool_wait";
25993
26232
  if (this.activeTurn)
25994
26233
  return "working";
@@ -25998,6 +26237,46 @@ class LogicalAgentSession {
25998
26237
  const now = this.host.now();
25999
26238
  return Number.isFinite(now) && now >= 0 ? now : 0;
26000
26239
  }
26240
+ outstandingToolUseCount() {
26241
+ return this.anonymousOutstandingToolUses + this.outstandingToolCallIds.size;
26242
+ }
26243
+ startToolUse(rawCallId) {
26244
+ const callId = this.toolCallId(rawCallId);
26245
+ if (!callId) {
26246
+ this.anonymousOutstandingToolUses += 1;
26247
+ return true;
26248
+ }
26249
+ if (this.outstandingToolCallIds.has(callId)) {
26250
+ this.toolLifecycleMismatchCount += 1;
26251
+ return false;
26252
+ }
26253
+ this.outstandingToolCallIds.add(callId);
26254
+ return true;
26255
+ }
26256
+ finishToolUse(rawCallId) {
26257
+ const callId = this.toolCallId(rawCallId);
26258
+ if (!callId) {
26259
+ if (this.anonymousOutstandingToolUses === 0) {
26260
+ this.toolLifecycleMismatchCount += 1;
26261
+ return false;
26262
+ }
26263
+ this.anonymousOutstandingToolUses -= 1;
26264
+ return true;
26265
+ }
26266
+ if (!this.outstandingToolCallIds.delete(callId)) {
26267
+ this.toolLifecycleMismatchCount += 1;
26268
+ return false;
26269
+ }
26270
+ return true;
26271
+ }
26272
+ clearOutstandingToolUses() {
26273
+ this.anonymousOutstandingToolUses = 0;
26274
+ this.outstandingToolCallIds.clear();
26275
+ }
26276
+ toolCallId(rawCallId) {
26277
+ const callId = rawCallId?.trim();
26278
+ return callId && callId.length <= 1024 ? callId : undefined;
26279
+ }
26001
26280
  metricValue(value) {
26002
26281
  return Number.isFinite(value) && value >= 0 ? value : 0;
26003
26282
  }
@@ -27636,10 +27915,11 @@ var DEFAULT_CAPABILITY_RESOLVER = (method, pathname) => {
27636
27915
  const isMessagesDoor = /\/channels\/[^/]+\/messages(\/|$|\?)/.test(pathname);
27637
27916
  if (isMessagesDoor)
27638
27917
  return method === "GET" ? "read" : "send";
27918
+ if (/\/messages\/[^/]+\/properties(\/|$|\?)/.test(pathname)) {
27919
+ return method === "GET" ? "read" : "send";
27920
+ }
27639
27921
  if (/\/messages\/[^/]+\/reactions\//.test(pathname))
27640
27922
  return "send";
27641
- if ((method === "PUT" || method === "DELETE") && /\/messages\/[^/]+\/marks(\/|$|\?)/.test(pathname))
27642
- return "send";
27643
27923
  if (method === "GET" && /\/users\/me\/marks(\/|$|\?)/.test(pathname))
27644
27924
  return "read";
27645
27925
  if (method === "GET" && /\/messages\/[^/]+(\?|$)/.test(pathname))
@@ -27680,6 +27960,12 @@ function isCanonicalChannelScope(channel2) {
27680
27960
  return false;
27681
27961
  }
27682
27962
  }
27963
+ function propertyAuditMetadata(method, pathname, requestUrl) {
27964
+ if (method !== "PUT" && method !== "DELETE" || !/^\/api\/community\/messages\/[^/]+\/properties$/.test(pathname))
27965
+ return;
27966
+ const type = new URL2(requestUrl, "http://placeholder").searchParams.get("type");
27967
+ return type === "emoji" || type === "tag" || type === "mark" ? { propertyType: type } : undefined;
27968
+ }
27683
27969
  function parseLocalMessageReminderBody(body, agentId) {
27684
27970
  let value;
27685
27971
  try {
@@ -27818,7 +28104,12 @@ async function startCredentialProxy(broker, options = {}) {
27818
28104
  }
27819
28105
  if (onProxyRequest) {
27820
28106
  try {
27821
- onProxyRequest(reg.agentId, req.method ?? "GET", pathname);
28107
+ const method = req.method ?? "GET";
28108
+ const metadata = propertyAuditMetadata(method, pathname, req.url ?? pathname);
28109
+ if (metadata)
28110
+ onProxyRequest(reg.agentId, method, pathname, metadata);
28111
+ else
28112
+ onProxyRequest(reg.agentId, method, pathname);
27822
28113
  } catch {}
27823
28114
  }
27824
28115
  const outHeaders = { ...req.headers };
@@ -28101,6 +28392,7 @@ function reduceManager(state, event) {
28101
28392
  if ((lease.state === "active" || lease.state === "suspect_active") && lease.outstandingToolUses !== undefined) {
28102
28393
  const unblockedLease = { ...lease };
28103
28394
  delete unblockedLease.outstandingToolUses;
28395
+ delete unblockedLease.outstandingToolCallIds;
28104
28396
  a.execution.lease = unblockedLease;
28105
28397
  }
28106
28398
  });
@@ -28361,26 +28653,40 @@ function onTurnToolLifecycle(state, event, lifecycle) {
28361
28653
  const matchesTerminal = lifecycle === "started" && current.state === "none" && current.lastTerminal !== null && sameIdentity(current.lastTerminal.identity, identity);
28362
28654
  if (!matchesActive && !matchesTerminal)
28363
28655
  return { state, effects: [] };
28364
- if (lifecycle === "finished" && matchesActive && (current.outstandingToolUses ?? 0) === 0) {
28365
- return { state, effects: [] };
28366
- }
28367
28656
  return mutate(state, event.agentId, (agent2) => {
28368
28657
  if (!matchesSession(agent2, event.sessionInstanceId))
28369
28658
  return;
28370
28659
  const lease = agent2.execution.lease;
28660
+ const callId = normalizedToolCallId(event.callId);
28371
28661
  if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
28372
- const outstandingToolUses = (lease.outstandingToolUses ?? 0) + (lifecycle === "started" ? 1 : -1);
28662
+ const identified = new Set(lease.outstandingToolCallIds ?? []);
28663
+ const currentCount = lease.outstandingToolUses ?? 0;
28664
+ const anonymousCount = Math.max(0, currentCount - identified.size);
28665
+ if (lifecycle === "started") {
28666
+ if (callId && identified.has(callId))
28667
+ return;
28668
+ if (callId)
28669
+ identified.add(callId);
28670
+ } else if (callId) {
28671
+ if (!identified.delete(callId))
28672
+ return;
28673
+ } else if (anonymousCount === 0) {
28674
+ return;
28675
+ }
28676
+ const outstandingToolUses = currentCount + (lifecycle === "started" ? 1 : -1);
28373
28677
  if (outstandingToolUses > 0) {
28374
28678
  agent2.execution.lease = {
28375
28679
  ...lease,
28376
28680
  lastWorkAt: event.nowMs,
28377
28681
  nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
28378
28682
  recoveryExtensionsUsed: 0,
28379
- outstandingToolUses
28683
+ outstandingToolUses,
28684
+ ...identified.size > 0 ? { outstandingToolCallIds: [...identified] } : {}
28380
28685
  };
28381
28686
  } else {
28382
28687
  const unblockedLease = { ...lease };
28383
28688
  delete unblockedLease.outstandingToolUses;
28689
+ delete unblockedLease.outstandingToolCallIds;
28384
28690
  agent2.execution.lease = {
28385
28691
  ...unblockedLease,
28386
28692
  lastWorkAt: event.nowMs,
@@ -28399,6 +28705,7 @@ function onTurnToolLifecycle(state, event, lifecycle) {
28399
28705
  nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
28400
28706
  recoveryExtensionsUsed: 0,
28401
28707
  outstandingToolUses: 1,
28708
+ ...callId ? { outstandingToolCallIds: [callId] } : {},
28402
28709
  reason: "work_after_terminal"
28403
28710
  };
28404
28711
  }
@@ -28562,6 +28869,10 @@ function identityOf(event) {
28562
28869
  function sameIdentity(left, right) {
28563
28870
  return left.sessionInstanceId === right.sessionInstanceId && left.turnId === right.turnId;
28564
28871
  }
28872
+ function normalizedToolCallId(rawCallId) {
28873
+ const callId = rawCallId?.trim();
28874
+ return callId && callId.length <= 1024 ? callId : undefined;
28875
+ }
28565
28876
  function leaseIsWorking(lease) {
28566
28877
  return lease.state === "active" || lease.state === "suspect_active";
28567
28878
  }
@@ -28783,13 +29094,13 @@ function cliCommandsSection() {
28783
29094
  "",
28784
29095
  "### Messaging",
28785
29096
  "",
28786
- `1. \`${CLI} inbox pull\` — fetch unread messages; \`--no-ack\` peeks without advancing.`,
29097
+ `1. \`${CLI} inbox pull\` — fetch unread messages and advance their read waterlines.`,
28787
29098
  `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
29099
  `3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a file; ` + `returns an id stable across pending→persisted.`,
28789
29100
  `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.`,
29101
+ `5. \`${CLI} message property set --target <full-message-ref> --json <json>\` — add a supported property value to a message.`,
29102
+ `6. \`${CLI} message property list --target <full-message-ref>\` — list the message's current properties and supported capabilities.`,
29103
+ `7. \`${CLI} message property remove --target <full-message-ref> --json <json>\` — remove a supported property value from a message.`,
28793
29104
  `8. \`${CLI} message mark list\` — list every currently visible marked message with its full content.`,
28794
29105
  "",
28795
29106
  "### Servers",
@@ -28797,12 +29108,14 @@ function cliCommandsSection() {
28797
29108
  `1. \`${CLI} server list\` — list your servers.`,
28798
29109
  `2. \`${CLI} server member --server <name#discriminator>\` — list a server's members.`,
28799
29110
  `3. \`${CLI} server join --invite <link>\` — join via invite link or token.`,
29111
+ `4. \`${CLI} server leave --server <name#NNNN>\` — leave a server you belong to. A server owner cannot leave.`,
28800
29112
  "",
28801
29113
  "### Channels",
28802
29114
  "",
28803
29115
  `1. \`${CLI} channel list --server <name#discriminator>\` — list top-level channels, grouped by ` + `category; each is marked \`public\` or \`private\`.`,
28804
29116
  `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
29117
  `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).`,
29118
+ `4. \`${CLI} channel leave --channel <ref>\` — leave a thread or private channel/forum. Public channels require leaving their server; DMs cannot be left.`,
28806
29119
  "",
28807
29120
  "### Friends",
28808
29121
  "",
@@ -28855,6 +29168,15 @@ function messagingSection() {
28855
29168
  "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
29169
  "Write every message body in the stdin/heredoc block; never place it directly on the command line.",
28857
29170
  "",
29171
+ "### Message properties",
29172
+ "",
29173
+ `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`.",
29174
+ "",
29175
+ `- \`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"}'\`.`,
29176
+ `- \`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"]}'\`.`,
29177
+ `- \`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.`,
29178
+ "- `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.",
29179
+ "",
28858
29180
  "### Context refs",
28859
29181
  "",
28860
29182
  "Path-style refs:",
@@ -28967,6 +29289,7 @@ function criticalRulesSection() {
28967
29289
  `- **\`${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
29290
  "- **Never expose tokens, keys, or secrets.** Redact credential-like strings from tool output " + "before sharing.",
28969
29291
  "- **Match the sender's language.** Reply in the language they wrote in.",
29292
+ `- **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
29293
  "- **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
29294
  ].join(`
28972
29295
  `);
@@ -28979,7 +29302,7 @@ function executionModelSection() {
28979
29302
  "",
28980
29303
  "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
29304
  "",
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."
29305
+ "`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
29306
  ].join(`
28984
29307
  `);
28985
29308
  }
@@ -29051,9 +29374,9 @@ function workspaceMemorySection(config2) {
29051
29374
  "",
29052
29375
  "### Outstanding work marks",
29053
29376
  "",
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.",
29377
+ "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
29378
  "",
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.",
29379
+ "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
29380
  "",
29058
29381
  "If `inbox pull` returns a `markedReminder`, run " + `\`${CLI} message mark list\` before taking new work. Do not copy marked tasks into local files.`
29059
29382
  ].join(`
@@ -29106,7 +29429,7 @@ function toLocalISO(iso) {
29106
29429
  }
29107
29430
 
29108
29431
  // src/manager/managerRuntime.ts
29109
- import { randomUUID as randomUUID5 } from "node:crypto";
29432
+ import { randomUUID as randomUUID6 } from "node:crypto";
29110
29433
  var SESSION_STOP_GRACE_MS2 = 2000;
29111
29434
  function normalizeTerminationCause(value) {
29112
29435
  return value === "runtime_error" || value === "killed_stalled" ? value : "other";
@@ -29343,7 +29666,7 @@ class AgentProcessManager {
29343
29666
  liveBackendIds = new Map;
29344
29667
  activeSpawnState = new Map;
29345
29668
  publishedAgentActivity = new Map;
29346
- traceProcessNonce = randomUUID5();
29669
+ traceProcessNonce = randomUUID6();
29347
29670
  nextSpawnOrdinal = 1;
29348
29671
  nextDaemonTurnOrdinal = 1;
29349
29672
  nextDeliveryOrdinal = 1;
@@ -29619,6 +29942,12 @@ class AgentProcessManager {
29619
29942
  if (this.sessions.get(agentId) === session2)
29620
29943
  this.sessions.delete(agentId);
29621
29944
  }
29945
+ async interrupt(agentId) {
29946
+ const session2 = this.sessions.get(agentId);
29947
+ if (!session2)
29948
+ return;
29949
+ await session2.interrupt({ requestId: randomUUID6(), reason: "owner_request" });
29950
+ }
29622
29951
  async stopAll() {
29623
29952
  if (this.tickTimer) {
29624
29953
  clearInterval(this.tickTimer);
@@ -30116,7 +30445,7 @@ ${this.opts.wakePromptFooter}` : text2;
30116
30445
  case "reset_idle_session": {
30117
30446
  const spawnState = this.activeSpawnState.get(effect.agentId);
30118
30447
  const completion = {
30119
- eventId: `bae_${randomUUID5()}`,
30448
+ eventId: `bae_${randomUUID6()}`,
30120
30449
  occurredAt: new Date(this.now()).toISOString()
30121
30450
  };
30122
30451
  const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId, completion);
@@ -30511,11 +30840,12 @@ ${this.opts.wakePromptFooter}` : text2;
30511
30840
  emitErrorAudit(agentId, scope, code, rawMessage) {
30512
30841
  if (!this.opts.onBotAuditEvent)
30513
30842
  return;
30843
+ const stableCode = /^[A-Za-z0-9_.-]{1,100}$/.test(code) ? code : "runtime_error";
30514
30844
  const message2 = scrubRuntimeErrorDiagnosticText(rawMessage).slice(0, AUDIT_ERROR_MESSAGE_MAX_LEN);
30515
30845
  try {
30516
30846
  this.opts.onBotAuditEvent(agentId, {
30517
30847
  kind: "error",
30518
- payload: { scope, code, message: message2, model: this.currentModelFor(agentId) }
30848
+ payload: { scope, code: stableCode, message: message2, model: this.currentModelFor(agentId) }
30519
30849
  }, {
30520
30850
  sessionId: this.liveSessions.get(agentId) ?? null,
30521
30851
  launchId: this.launchIds.get(agentId) ?? null
@@ -30540,18 +30870,18 @@ ${this.opts.wakePromptFooter}` : text2;
30540
30870
  return;
30541
30871
  }
30542
30872
  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);
30873
+ const runtimeError = event.type === "session_failed" ? event.error : event.type === "turn_completed" && event.result.outcome === "failed" ? event.result.error : undefined;
30874
+ if (runtimeError !== undefined && !sessionSuperseded) {
30875
+ this.emitErrorAudit(agentId, "runtime", runtimeError.code, runtimeError.message);
30546
30876
  if (this.nonCleanEndMarker.get(agentId)?.cause !== "killed_stalled") {
30547
- this.nonCleanEndMarker.set(agentId, { cause: "runtime_error", detail: errorMessage2 });
30877
+ this.nonCleanEndMarker.set(agentId, { cause: "runtime_error", detail: runtimeError.message });
30548
30878
  }
30549
30879
  const agent2 = this.state.agents[agentId];
30550
30880
  if (agent2?.resetting && agent2.resettingSince !== null && this.now() - agent2.resettingSince >= this.state.resetStuckThresholdMs) {
30551
30881
  this.log.warn("runtime error during a stuck reset window", {
30552
30882
  agentId,
30553
30883
  resettingForMs: this.now() - agent2.resettingSince,
30554
- message: errorMessage2
30884
+ message: runtimeError.message
30555
30885
  });
30556
30886
  }
30557
30887
  }
@@ -30646,9 +30976,9 @@ ${this.opts.wakePromptFooter}` : text2;
30646
30976
  case "assistant_message_completed":
30647
30977
  return { type: "turn_work", turnId: event.turnId };
30648
30978
  case "tool_started":
30649
- return { type: "turn_tool_started", turnId: event.turnId };
30979
+ return { type: "turn_tool_started", turnId: event.turnId, callId: event.callId };
30650
30980
  case "tool_finished":
30651
- return { type: "turn_tool_finished", turnId: event.turnId };
30981
+ return { type: "turn_tool_finished", turnId: event.turnId, callId: event.callId };
30652
30982
  case "compaction_started":
30653
30983
  case "compaction_finished":
30654
30984
  case "review_started":
@@ -31101,6 +31431,10 @@ class AgentRouter {
31101
31431
  }
31102
31432
  break;
31103
31433
  }
31434
+ case "agent:interrupt": {
31435
+ await this.opts.manager.interrupt(cmd.agentId);
31436
+ break;
31437
+ }
31104
31438
  case "agent:stop":
31105
31439
  this.log.info("agent:stop received", { agentId: cmd.agentId });
31106
31440
  try {
@@ -32543,7 +32877,7 @@ class MessageReminderScheduler {
32543
32877
  }
32544
32878
 
32545
32879
  // src/manager/agentDriverHost.ts
32546
- import { randomUUID as randomUUID6 } from "node:crypto";
32880
+ import { randomUUID as randomUUID7 } from "node:crypto";
32547
32881
  import { homedir as homedir4 } from "node:os";
32548
32882
  import { join as join13 } from "node:path";
32549
32883
 
@@ -32699,7 +33033,7 @@ function createDaemonAgentDriverHost(ctx, onRawLine) {
32699
33033
  onRawLine?.(event.text);
32700
33034
  },
32701
33035
  now: () => Date.now(),
32702
- createId: () => randomUUID6()
33036
+ createId: () => randomUUID7()
32703
33037
  };
32704
33038
  }
32705
33039
 
@@ -32778,7 +33112,7 @@ class DaemonSelfSleepScheduler {
32778
33112
  // src/telemetry/dailyTokenUsage.ts
32779
33113
  import { chmod, mkdir, open, readFile as readFile2, rename, rm } from "node:fs/promises";
32780
33114
  import { dirname as dirname6, join as join14 } from "node:path";
32781
- import { randomUUID as randomUUID7 } from "node:crypto";
33115
+ import { randomUUID as randomUUID8 } from "node:crypto";
32782
33116
  var TOKEN_USAGE_WINDOW_DAYS = 30;
32783
33117
  var TOKEN_USAGE_RECOVERY_DAYS = 2;
32784
33118
  var TOKEN_USAGE_RETENTION_DAYS = TOKEN_USAGE_WINDOW_DAYS + TOKEN_USAGE_RECOVERY_DAYS;
@@ -32949,7 +33283,7 @@ class DailyTokenUsageStore {
32949
33283
  async persist() {
32950
33284
  const directory = dirname6(this.filePath);
32951
33285
  await mkdir(directory, { recursive: true, mode: 448 });
32952
- const temporary = `${this.filePath}.${randomUUID7()}.tmp`;
33286
+ const temporary = `${this.filePath}.${randomUUID8()}.tmp`;
32953
33287
  try {
32954
33288
  const file2 = await open(temporary, "wx", 384);
32955
33289
  try {
@@ -33031,15 +33365,17 @@ function createRuntimeRawLineTap(args) {
33031
33365
  }
33032
33366
  function deriveAuditLogSubcommand(pathname, method) {
33033
33367
  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)) {
33368
+ if (/^\/api\/community\/messages\/[^/]+\/properties$/.test(canonical)) {
33369
+ if (method === "GET")
33370
+ return "messagePropertyList";
33037
33371
  if (method === "PUT")
33038
- return "markSet";
33372
+ return "messagePropertySet";
33039
33373
  if (method === "DELETE")
33040
- return "markRemove";
33374
+ return "messagePropertyRemove";
33041
33375
  return null;
33042
33376
  }
33377
+ if (/^\/api\/community\/messages\/[^/]+\/reactions\//.test(canonical))
33378
+ return "reactAdd";
33043
33379
  if (method === "GET" && /^\/api\/community\/users\/me\/marks$/.test(canonical))
33044
33380
  return "markList";
33045
33381
  if (/^\/api\/community\/channels\/[^/]+\/messages\/seq\//.test(canonical))
@@ -33205,14 +33541,14 @@ async function createDaemon(opts) {
33205
33541
  log2.warn("inbox pull timeline observation failed", { agentId, reason, contentEncoding });
33206
33542
  },
33207
33543
  onMessageReminderArm: (input) => reminderSchedulerRef?.arm(input) ?? { armed: false, reason: "reminder_scheduler_unavailable" },
33208
- onProxyRequest: (agentId, method, pathname) => {
33544
+ onProxyRequest: (agentId, method, pathname, metadata) => {
33209
33545
  const subcommand = deriveAuditLogSubcommand(pathname, method);
33210
33546
  if (!subcommand)
33211
33547
  return;
33212
33548
  const context = managerRef?.auditContext(agentId);
33213
33549
  emitBotAuditEvent(agentId, {
33214
33550
  kind: "cli_invocation",
33215
- payload: { subcommand }
33551
+ payload: { subcommand, ...metadata ? { propertyType: metadata.propertyType } : {} }
33216
33552
  }, context);
33217
33553
  emitImplicitTypingStopOnSend({
33218
33554
  subcommand,
@@ -34134,10 +34470,17 @@ function projectFsm(row, targetAgentId) {
34134
34470
  "queueDwellTotalMs",
34135
34471
  "sseReconnectCount"
34136
34472
  ];
34137
- const hasMetrics = metricKeys.some((key) => (key in row) || row[key] !== undefined) || "resumeOutcome" in row || "terminalOwnerKind" in row;
34473
+ const toolMetricKeys = [
34474
+ "outstandingToolUses",
34475
+ "anonymousOutstandingToolUses",
34476
+ "toolLifecycleMismatchCount"
34477
+ ];
34478
+ 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
34479
  if (hasMetrics) {
34139
34480
  if (!metricKeys.every((key) => copyFiniteNumber(row, output, key)))
34140
34481
  return null;
34482
+ if (!toolMetricKeys.every((key) => copyInteger(row, output, key, { optional: true })))
34483
+ return null;
34141
34484
  const resumeOutcome = enumValue(row.resumeOutcome, RESUME_OUTCOMES);
34142
34485
  const terminalOwnerKind = enumValue(row.terminalOwnerKind, TERMINAL_OWNER_KINDS);
34143
34486
  if (!resumeOutcome || !terminalOwnerKind)
@@ -36849,7 +37192,7 @@ ${MESSAGE_SEND_STDIN_POLICY}`);
36849
37192
  }
36850
37193
  replyToSeq = n;
36851
37194
  }
36852
- const nonce = randomUUID10();
37195
+ const nonce = randomUUID11();
36853
37196
  const res = await sendWithRetry(api2, {
36854
37197
  agentId: agent2,
36855
37198
  channel: channel2,
@@ -36878,22 +37221,43 @@ ${MESSAGE_SEND_STDIN_POLICY}`);
36878
37221
  return { sent, reminder: { armed: false, reason: "local reminder request failed" } };
36879
37222
  }
36880
37223
  }
36881
- async function cmdMessageEmoji(opts) {
37224
+ function parseMessagePropertyJson(command, raw) {
37225
+ if (typeof raw !== "string")
37226
+ throw new CliError(`${command}: --json <json> is required`);
37227
+ try {
37228
+ return JSON.parse(raw);
37229
+ } catch {
37230
+ throw new CliError(`${command}: --json must be valid JSON`);
37231
+ }
37232
+ }
37233
+ async function cmdMessagePropertySet(opts) {
36882
37234
  const api2 = getApi();
36883
37235
  const target2 = opts.target;
36884
- const emoji3 = opts.emoji;
36885
37236
  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 };
37237
+ throw new CliError("message property set: --target <ref> is required");
37238
+ const request = parseMessageTarget("message property set", target2);
37239
+ const property = parseMessagePropertyJson("message property set", opts.json);
37240
+ const result = await withTransientMutationRetry(() => api2.messagePropertySet({ ...request, property }));
37241
+ return { target: target2, ...result };
37242
+ }
37243
+ async function cmdMessagePropertyList(opts) {
37244
+ const api2 = getApi();
37245
+ const target2 = opts.target;
37246
+ if (!target2)
37247
+ throw new CliError("message property list: --target <ref> is required");
37248
+ const request = parseMessageTarget("message property list", target2);
37249
+ const result = await api2.messagePropertyList(request);
37250
+ return { target: target2, ...result };
37251
+ }
37252
+ async function cmdMessagePropertyRemove(opts) {
37253
+ const api2 = getApi();
37254
+ const target2 = opts.target;
37255
+ if (!target2)
37256
+ throw new CliError("message property remove: --target <ref> is required");
37257
+ const request = parseMessageTarget("message property remove", target2);
37258
+ const property = parseMessagePropertyJson("message property remove", opts.json);
37259
+ const result = await withTransientMutationRetry(() => api2.messagePropertyRemove({ ...request, property }));
37260
+ return { target: target2, ...result };
36897
37261
  }
36898
37262
  function parseMessageTarget(command, target2) {
36899
37263
  let parsed;
@@ -36910,24 +37274,6 @@ function parseMessageTarget(command, target2) {
36910
37274
  const channel2 = parsed.threadRootSeq !== undefined ? `/${parsed.server}/${parsed.channel}/#${parsed.threadRootSeq}` : `/${parsed.server}/${parsed.channel}`;
36911
37275
  return { channel: channel2, seq: parsed.seq };
36912
37276
  }
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
37277
  async function cmdMessageMarkList(opts) {
36932
37278
  const api2 = getApi();
36933
37279
  const { marked } = await api2.listMarks({ agentId: agentId(opts) });
@@ -37057,7 +37403,7 @@ async function cmdInboxPull(opts) {
37057
37403
  let acked = 0;
37058
37404
  let ackError;
37059
37405
  let failed;
37060
- if (opts.ack !== false && messages.length > 0) {
37406
+ if (messages.length > 0) {
37061
37407
  const latest = new Map;
37062
37408
  for (const m of messages) {
37063
37409
  const seqN = Number(m.seq.replace("#", ""));
@@ -37125,6 +37471,14 @@ async function cmdServerJoin(opts) {
37125
37471
  const { server } = await api2.joinServer({ agentId: agent2, invite: token });
37126
37472
  return { server };
37127
37473
  }
37474
+ async function cmdServerLeave(opts) {
37475
+ const api2 = getApi();
37476
+ const agent2 = agentId(opts);
37477
+ const server = opts.server;
37478
+ if (!server)
37479
+ throw new CliError("server leave: --server <name#NNNN> is required");
37480
+ return await api2.leaveServer({ agentId: agent2, server });
37481
+ }
37128
37482
  async function cmdChannelList(opts) {
37129
37483
  const api2 = getApi();
37130
37484
  const agent2 = agentId(opts);
@@ -37141,6 +37495,26 @@ async function cmdChannelMember(opts) {
37141
37495
  throw new CliError("channel member: --channel <ref> is required");
37142
37496
  return await api2.channelMember({ agentId: agent2, channel: channel2 });
37143
37497
  }
37498
+ async function cmdChannelLeave(opts) {
37499
+ const api2 = getApi();
37500
+ const agent2 = agentId(opts);
37501
+ const channel2 = opts.channel;
37502
+ if (!channel2)
37503
+ throw new CliError("channel leave: --channel <ref> is required");
37504
+ let parsed;
37505
+ try {
37506
+ parsed = parseRef(channel2);
37507
+ } catch {
37508
+ throw new CliError("channel leave: malformed channel ref");
37509
+ }
37510
+ if (parsed.server === DM_SERVER) {
37511
+ throw new CliError("channel leave: DMs cannot be left");
37512
+ }
37513
+ if (parsed.seq !== undefined) {
37514
+ throw new CliError("channel leave: message refs cannot be left; use a channel or thread-root ref");
37515
+ }
37516
+ return await api2.leaveChannel({ agentId: agent2, channel: channel2 });
37517
+ }
37144
37518
  async function cmdChannelHistory(opts) {
37145
37519
  const api2 = getApi();
37146
37520
  const agent2 = agentId(opts);
@@ -37198,22 +37572,22 @@ function buildProgram(stdin) {
37198
37572
  const result = await cmdMessageSend({ ...globalOpts, ...localOpts }, stdin);
37199
37573
  printEnvelope({ success: result });
37200
37574
  });
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 });
37575
+ const property = message2.command("property").description("message property operations").exitOverride();
37576
+ property.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37577
+ 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() {
37578
+ const result = await cmdMessagePropertySet({ ...program.opts(), ...this.opts() });
37205
37579
  printEnvelope({ success: result });
37206
37580
  });
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() });
37581
+ property.command("list").description("list current properties and supported capabilities").requiredOption("--target <ref>", "full message ref").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37582
+ const result = await cmdMessagePropertyList({ ...program.opts(), ...this.opts() });
37211
37583
  printEnvelope({ success: result });
37212
37584
  });
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() });
37585
+ 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() {
37586
+ const result = await cmdMessagePropertyRemove({ ...program.opts(), ...this.opts() });
37215
37587
  printEnvelope({ success: result });
37216
37588
  });
37589
+ const mark = message2.command("mark").description("global durable-mark queries").exitOverride();
37590
+ mark.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37217
37591
  mark.command("list").description("list all currently visible marked messages").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37218
37592
  const result = await cmdMessageMarkList({ ...program.opts(), ...this.opts() });
37219
37593
  printEnvelope({ success: result });
@@ -37234,7 +37608,7 @@ function buildProgram(stdin) {
37234
37608
  });
37235
37609
  const inbox = program.command("inbox").description("inbox operations").exitOverride();
37236
37610
  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() {
37611
+ 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
37612
  const localOpts = this.opts();
37239
37613
  const globalOpts = program.opts();
37240
37614
  const result = await cmdInboxPull({ ...globalOpts, ...localOpts });
@@ -37260,6 +37634,12 @@ function buildProgram(stdin) {
37260
37634
  const result = await cmdServerJoin({ ...globalOpts, ...localOpts });
37261
37635
  printEnvelope({ success: result });
37262
37636
  });
37637
+ 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() {
37638
+ const localOpts = this.opts();
37639
+ const globalOpts = program.opts();
37640
+ const result = await cmdServerLeave({ ...globalOpts, ...localOpts });
37641
+ printEnvelope({ success: result });
37642
+ });
37263
37643
  const channel2 = program.command("channel").description("channel operations").exitOverride();
37264
37644
  channel2.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37265
37645
  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 +37660,12 @@ function buildProgram(stdin) {
37280
37660
  const result = await cmdChannelMember({ ...globalOpts, ...localOpts });
37281
37661
  printEnvelope({ success: result });
37282
37662
  });
37663
+ 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() {
37664
+ const localOpts = this.opts();
37665
+ const globalOpts = program.opts();
37666
+ const result = await cmdChannelLeave({ ...globalOpts, ...localOpts });
37667
+ printEnvelope({ success: result });
37668
+ });
37283
37669
  const friend = program.command("friend").description("friend operations").exitOverride();
37284
37670
  friend.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37285
37671
  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() {