@alook/daemon 0.1.28 → 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;
@@ -16934,6 +16938,7 @@ var ALLOWED_ICON_MIME_TYPES = [
16934
16938
  ];
16935
16939
  var MAX_ICON_SOURCE_FILE_SIZE_BYTES = 15 * 1024 * 1024;
16936
16940
  // ../shared/src/provider-telemetry.ts
16941
+ var DAILY_TOKEN_USAGE_WINDOW_DAYS = 30;
16937
16942
  var safeToken = exports_external.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
16938
16943
  var boundedText = exports_external.string().min(1).refine((value) => new TextEncoder().encode(value).length <= 64, { message: "must be at most 64 UTF-8 bytes" });
16939
16944
  var DailyUsageMetricSchema = safeToken.nullable();
@@ -17665,7 +17670,7 @@ var AgentActivityMessageSchema = exports_external.object({
17665
17670
  state: exports_external.enum(["idle", "starting", "running", "stopping"]),
17666
17671
  usageTimeZone: exports_external.string().min(1).max(128).optional(),
17667
17672
  usageDay: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
17668
- dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(7).optional(),
17673
+ dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(DAILY_TOKEN_USAGE_WINDOW_DAYS).optional(),
17669
17674
  quota: ProviderQuotaSnapshotSchema.optional()
17670
17675
  });
17671
17676
  var AgentTypingMessageSchema = exports_external.object({
@@ -17829,17 +17834,18 @@ var CommunityAgentNapRequestSchema = exports_external.object({
17829
17834
  message: "handoff is required"
17830
17835
  })
17831
17836
  });
17832
- var CommunityAgentReactAddRequestSchema = exports_external.object({
17837
+ var CommunityAgentMessagePropertyRequestSchema = exports_external.object({
17833
17838
  channel: exports_external.string().min(1),
17834
17839
  seq: CommunityAgentPositiveSeqSchema,
17835
- emoji: exports_external.string().min(1)
17840
+ property: exports_external.unknown()
17836
17841
  });
17837
17842
  var CommunityAgentFriendRequestSchema = exports_external.object({
17838
17843
  username: exports_external.string().min(1)
17839
17844
  });
17840
17845
  var CommunityAgentListFriendsSchema = exports_external.object({});
17841
17846
  var AuditLogCliInvocationPayloadSchema = exports_external.object({
17842
- subcommand: exports_external.string().min(1)
17847
+ subcommand: exports_external.string().min(1),
17848
+ propertyType: exports_external.enum(["emoji", "tag", "mark"]).optional()
17843
17849
  });
17844
17850
  var AuditLogToolCallPayloadSchema = exports_external.object({
17845
17851
  name: exports_external.string().min(1),
@@ -19135,6 +19141,14 @@ function proxyServerApiFromEnv(prefix = "ALOOK", env = process.env) {
19135
19141
  function createProxyServerApi(config2) {
19136
19142
  const fetchImpl = config2.fetchImpl ?? fetch;
19137
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
+ };
19138
19152
  async function parseJsonResponse(res, method) {
19139
19153
  let text2;
19140
19154
  try {
@@ -19252,17 +19266,37 @@ function createProxyServerApi(config2) {
19252
19266
  });
19253
19267
  return parseJsonResponse(res, "resolve");
19254
19268
  }
19255
- async function callReactAdd(req) {
19256
- 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)}`, {
19257
19271
  method: "PUT",
19258
19272
  headers: {
19259
19273
  "content-type": "application/json",
19260
19274
  authorization: `Bearer ${config2.voucher}`
19261
19275
  },
19262
- body: JSON.stringify({ channel: req.channel, seq: req.seq })
19276
+ body: JSON.stringify(req)
19277
+ });
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)
19263
19298
  });
19264
- const body = await parseJsonResponse(res, "reactAdd");
19265
- return body?.duplicate ? { ok: true, duplicate: true } : { ok: true };
19299
+ return parseJsonResponse(res, "messagePropertyRemove");
19266
19300
  }
19267
19301
  async function callListMembers(req) {
19268
19302
  const q = new URLSearchParams;
@@ -19314,28 +19348,6 @@ function createProxyServerApi(config2) {
19314
19348
  });
19315
19349
  return parseJsonResponse(res, "inboxPull");
19316
19350
  }
19317
- async function callMarkSet(req) {
19318
- const res = await fetchImpl(`${base}/api/community/messages/resolve/marks`, {
19319
- method: "PUT",
19320
- headers: {
19321
- "content-type": "application/json",
19322
- authorization: `Bearer ${config2.voucher}`
19323
- },
19324
- body: JSON.stringify(req)
19325
- });
19326
- await parseJsonResponse(res, "markSet");
19327
- }
19328
- async function callMarkRemove(req) {
19329
- const res = await fetchImpl(`${base}/api/community/messages/resolve/marks`, {
19330
- method: "DELETE",
19331
- headers: {
19332
- "content-type": "application/json",
19333
- authorization: `Bearer ${config2.voucher}`
19334
- },
19335
- body: JSON.stringify(req)
19336
- });
19337
- await parseJsonResponse(res, "markRemove");
19338
- }
19339
19351
  async function callListMarks() {
19340
19352
  const res = await fetchImpl(`${base}/api/community/users/me/marks`, {
19341
19353
  method: "GET",
@@ -19492,11 +19504,32 @@ function createProxyServerApi(config2) {
19492
19504
  }
19493
19505
  return { server: projectServer(body.server) };
19494
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
+ }
19495
19526
  return {
19496
19527
  listServers: (_r) => callListServers(),
19497
19528
  joinServer: callJoinServer,
19529
+ leaveServer: callLeaveServer,
19498
19530
  listChannels: callListChannels,
19499
19531
  channelMember: callChannelMember,
19532
+ leaveChannel: callLeaveChannel,
19500
19533
  inboxPull: callInboxPull,
19501
19534
  inboxSnapshot: (_r) => callInboxSnapshot(),
19502
19535
  ack: callAck,
@@ -19507,9 +19540,9 @@ function createProxyServerApi(config2) {
19507
19540
  attachmentUpload: callUpload,
19508
19541
  attachmentDownload: callDownload,
19509
19542
  updateProfile: callUpdateProfile,
19510
- reactAdd: callReactAdd,
19511
- markSet: callMarkSet,
19512
- markRemove: callMarkRemove,
19543
+ messagePropertySet: callMessagePropertySet,
19544
+ messagePropertyList: callMessagePropertyList,
19545
+ messagePropertyRemove: callMessagePropertyRemove,
19513
19546
  listMarks: (_r) => callListMarks(),
19514
19547
  friendRequest: callFriendRequest,
19515
19548
  listFriends: (_r) => callListFriends(),
@@ -19556,6 +19589,9 @@ function createDefaultAgentDriverHost(options = {}) {
19556
19589
  };
19557
19590
  }
19558
19591
 
19592
+ // agent-driver/dist/adapters/claude/index.js
19593
+ import { randomUUID as randomUUID5 } from "node:crypto";
19594
+
19559
19595
  // agent-driver/dist/controller/process-host.js
19560
19596
  import { EventEmitter } from "events";
19561
19597
 
@@ -20135,6 +20171,7 @@ class ProcessLane {
20135
20171
  admissionSequence = 0;
20136
20172
  activeTerminalReceipt;
20137
20173
  stopPromise;
20174
+ pendingInterrupt;
20138
20175
  pendingPromptAdmission;
20139
20176
  constructor(driver, ctx, opts = {}) {
20140
20177
  this.driver = driver;
@@ -20174,12 +20211,14 @@ class ProcessLane {
20174
20211
  try {
20175
20212
  spawned = await this.driver.spawn(launchCtx);
20176
20213
  } catch (error51) {
20214
+ this.settlePendingInterrupt(false);
20177
20215
  this.settlePendingPrompt({ ok: false, reason: "runtime_error", error: String(error51) });
20178
20216
  throw error51;
20179
20217
  }
20180
20218
  const { process: proc } = spawned;
20181
20219
  this.process = proc;
20182
20220
  this.attachProcess(proc);
20221
+ this.dispatchPendingInterrupt(proc);
20183
20222
  this.armPendingPromptTimeout();
20184
20223
  if (this.driver.execution.lifetime === "session" && !this.writableStdin(proc)) {
20185
20224
  const rejection = this.stdinUnavailableAdmission();
@@ -20278,6 +20317,7 @@ class ProcessLane {
20278
20317
  pending.resolve(admission);
20279
20318
  }
20280
20319
  stop(opts = {}) {
20320
+ this.settlePendingInterrupt(false);
20281
20321
  this.settlePendingPrompt({ ok: false, reason: "closed", error: "runtime lane stopped before command admission" });
20282
20322
  if (!this.process)
20283
20323
  return Promise.resolve();
@@ -20296,11 +20336,39 @@ class ProcessLane {
20296
20336
  proc.kill(opts?.signal ?? "SIGTERM");
20297
20337
  }
20298
20338
  }
20299
- async interrupt() {
20339
+ async interrupt(input) {
20300
20340
  const proc = this.process;
20301
- 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)
20302
20356
  return false;
20303
- 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);
20304
20372
  }
20305
20373
  updateSettings(input) {
20306
20374
  return this.driver.updateSettings?.(input) ?? Promise.resolve({ status: "unsupported" });
@@ -20637,11 +20705,40 @@ function tryParseJsonLine(line) {
20637
20705
 
20638
20706
  // agent-driver/dist/adapters/claude/normalizer.js
20639
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"]);
20715
+ var CLAUDE_USAGE_COMPONENTS = [
20716
+ "inputTokens",
20717
+ "outputTokens",
20718
+ "cacheReadInputTokens",
20719
+ "cacheCreationInputTokens"
20720
+ ];
20721
+ function safeCount(value) {
20722
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
20723
+ }
20724
+ function safeMoney(value) {
20725
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
20726
+ }
20727
+ function safeAdd(left, right) {
20728
+ const total = left + right;
20729
+ return Number.isSafeInteger(total) ? total : null;
20730
+ }
20731
+ function equalCost(left, right) {
20732
+ return Math.abs(left - right) <= Math.max(0.000000000001, Math.abs(right) * 0.000000001);
20733
+ }
20640
20734
 
20641
20735
  class ClaudeEventNormalizer {
20642
20736
  turnProtocol;
20643
20737
  currentSession = null;
20644
20738
  usageProjector = new SettledUsageProjector;
20739
+ claudeUsageSession = null;
20740
+ claudeModelUsageHighWater = new Map;
20741
+ claudeUsageMode = "unknown";
20645
20742
  constructor(turnProtocol) {
20646
20743
  this.turnProtocol = turnProtocol;
20647
20744
  }
@@ -20708,9 +20805,9 @@ class ClaudeEventNormalizer {
20708
20805
  out.push({ kind: "assistant_reasoning_completed", text: block.thinking ?? "" });
20709
20806
  } else if (block?.type === "text") {
20710
20807
  const text2 = block.text ?? "";
20711
- if (API_ERROR_RE.test(text2))
20712
- out.push({ kind: "error", message: text2 });
20713
- else
20808
+ if (API_ERROR_RE.test(text2)) {
20809
+ out.push({ kind: "error", code: "claude.api_error", message: text2 });
20810
+ } else
20714
20811
  completedText.push(text2);
20715
20812
  } else if (block?.type === "tool_use") {
20716
20813
  out.push({ kind: "tool_call", name: block.name ?? "unknown_tool", input: block.input });
@@ -20736,14 +20833,19 @@ class ClaudeEventNormalizer {
20736
20833
  }
20737
20834
  handleResult(event, out) {
20738
20835
  const rawOwner = typeof event.user_message_uuid === "string" && event.user_message_uuid.length > 0 ? event.user_message_uuid : null;
20739
- 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;
20740
20838
  if (this.turnProtocol && !turnOwner)
20741
20839
  return;
20742
20840
  const usage = this.buildUsageTelemetry(event, rawOwner);
20743
20841
  if (usage)
20744
20842
  out.push(usage);
20745
- if (event.is_error || event.subtype === "error_during_execution") {
20746
- 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
+ });
20747
20849
  }
20748
20850
  out.push({
20749
20851
  kind: "turn_end",
@@ -20755,14 +20857,39 @@ class ClaudeEventNormalizer {
20755
20857
  return this.turnProtocol?.acceptsTurnWork() ?? true;
20756
20858
  }
20757
20859
  buildUsageTelemetry(event, rootRequestId) {
20758
- const u = event?.usage;
20759
- if (!u)
20760
- return null;
20761
20860
  const backendSessionId = event.session_id ?? this.currentSession;
20762
20861
  if (typeof backendSessionId !== "string" || !backendSessionId)
20763
20862
  return null;
20764
20863
  const providerRecordId = rootRequestId ?? (typeof event.request_id === "string" ? event.request_id : "invocation-result");
20765
- return this.usageProjector.project({
20864
+ const cumulative = this.prepareClaudeModelUsage(event, backendSessionId);
20865
+ if (cumulative === null)
20866
+ return null;
20867
+ if (cumulative) {
20868
+ const hasDelta = cumulative.input > 0 || cumulative.output > 0 || cumulative.cacheRead > 0 || cumulative.cacheWrite > 0;
20869
+ if (!hasDelta) {
20870
+ this.commitClaudeModelUsage(backendSessionId, cumulative);
20871
+ return null;
20872
+ }
20873
+ const projected2 = this.usageProjector.project({
20874
+ runtime: "claude",
20875
+ backendSessionId,
20876
+ providerRecordId,
20877
+ source: "claude_result_model_usage",
20878
+ input: cumulative.input,
20879
+ output: cumulative.output,
20880
+ cacheRead: cumulative.cacheRead,
20881
+ cacheWrite: cumulative.cacheWrite,
20882
+ inputIncludesCache: false,
20883
+ outputIncludesReasoning: true
20884
+ });
20885
+ if (projected2)
20886
+ this.commitClaudeModelUsage(backendSessionId, cumulative);
20887
+ return projected2;
20888
+ }
20889
+ const u = event?.usage;
20890
+ if (!u)
20891
+ return null;
20892
+ const projected = this.usageProjector.project({
20766
20893
  runtime: "claude",
20767
20894
  backendSessionId,
20768
20895
  providerRecordId,
@@ -20774,6 +20901,102 @@ class ClaudeEventNormalizer {
20774
20901
  inputIncludesCache: false,
20775
20902
  outputIncludesReasoning: true
20776
20903
  });
20904
+ if (projected)
20905
+ this.claudeUsageMode = "legacy";
20906
+ return projected;
20907
+ }
20908
+ prepareClaudeModelUsage(event, backendSessionId) {
20909
+ if (this.claudeUsageMode === "invalid")
20910
+ return null;
20911
+ if (!Object.hasOwn(event ?? {}, "modelUsage")) {
20912
+ if (this.claudeUsageMode !== "model_usage")
20913
+ return;
20914
+ return event?.usage ? this.lockClaudeUsageMode() : null;
20915
+ }
20916
+ if (this.claudeUsageMode === "legacy")
20917
+ return this.lockClaudeUsageMode();
20918
+ if (this.claudeUsageMode === "unknown")
20919
+ this.claudeUsageMode = "model_usage";
20920
+ this.claudeUsageSession ??= backendSessionId;
20921
+ if (this.claudeUsageSession !== null && this.claudeUsageSession !== backendSessionId) {
20922
+ return this.lockClaudeUsageMode();
20923
+ }
20924
+ if (!event.modelUsage || typeof event.modelUsage !== "object" || Array.isArray(event.modelUsage)) {
20925
+ return null;
20926
+ }
20927
+ const entries = Object.entries(event.modelUsage);
20928
+ if (entries.length === 0)
20929
+ return null;
20930
+ const highWater = new Map;
20931
+ let input = 0;
20932
+ let output = 0;
20933
+ let cacheRead = 0;
20934
+ let cacheWrite = 0;
20935
+ let snapshotCost = 0;
20936
+ for (const [model, rawValue] of entries) {
20937
+ if (!model.trim() || !rawValue || typeof rawValue !== "object" || Array.isArray(rawValue)) {
20938
+ return null;
20939
+ }
20940
+ const raw = rawValue;
20941
+ const current = {
20942
+ inputTokens: safeCount(raw.inputTokens),
20943
+ outputTokens: safeCount(raw.outputTokens),
20944
+ cacheReadInputTokens: safeCount(raw.cacheReadInputTokens),
20945
+ cacheCreationInputTokens: safeCount(raw.cacheCreationInputTokens),
20946
+ costUSD: safeMoney(raw.costUSD)
20947
+ };
20948
+ if (Object.values(current).some((value) => value === null)) {
20949
+ return null;
20950
+ }
20951
+ const snapshot = current;
20952
+ const prior = this.claudeModelUsageHighWater.get(model) ?? {
20953
+ inputTokens: 0,
20954
+ outputTokens: 0,
20955
+ cacheReadInputTokens: 0,
20956
+ cacheCreationInputTokens: 0,
20957
+ costUSD: 0
20958
+ };
20959
+ for (const component of CLAUDE_USAGE_COMPONENTS) {
20960
+ if (snapshot[component] < prior[component])
20961
+ return null;
20962
+ }
20963
+ if (snapshot.costUSD < prior.costUSD)
20964
+ return null;
20965
+ const nextInput = safeAdd(input, snapshot.inputTokens - prior.inputTokens);
20966
+ const nextOutput = safeAdd(output, snapshot.outputTokens - prior.outputTokens);
20967
+ const nextCacheRead = safeAdd(cacheRead, snapshot.cacheReadInputTokens - prior.cacheReadInputTokens);
20968
+ const nextCacheWrite = safeAdd(cacheWrite, snapshot.cacheCreationInputTokens - prior.cacheCreationInputTokens);
20969
+ if (nextInput === null || nextOutput === null || nextCacheRead === null || nextCacheWrite === null) {
20970
+ return null;
20971
+ }
20972
+ input = nextInput;
20973
+ output = nextOutput;
20974
+ cacheRead = nextCacheRead;
20975
+ cacheWrite = nextCacheWrite;
20976
+ snapshotCost += snapshot.costUSD;
20977
+ if (!Number.isFinite(snapshotCost))
20978
+ return null;
20979
+ highWater.set(model, snapshot);
20980
+ }
20981
+ for (const model of this.claudeModelUsageHighWater.keys()) {
20982
+ if (!highWater.has(model))
20983
+ return null;
20984
+ }
20985
+ if (Object.hasOwn(event, "total_cost_usd")) {
20986
+ const totalCost = safeMoney(event.total_cost_usd);
20987
+ if (totalCost === null || !equalCost(snapshotCost, totalCost))
20988
+ return null;
20989
+ }
20990
+ return { highWater, input, output, cacheRead, cacheWrite };
20991
+ }
20992
+ commitClaudeModelUsage(backendSessionId, projection) {
20993
+ this.claudeUsageSession ??= backendSessionId;
20994
+ this.claudeModelUsageHighWater = projection.highWater;
20995
+ this.claudeUsageMode = "model_usage";
20996
+ }
20997
+ lockClaudeUsageMode() {
20998
+ this.claudeUsageMode = "invalid";
20999
+ return null;
20777
21000
  }
20778
21001
  }
20779
21002
 
@@ -20900,6 +21123,7 @@ class ClaudeTurnProtocol {
20900
21123
  segmentOwnerUuid = null;
20901
21124
  delivered = new Map;
20902
21125
  awaitingFollowOn = false;
21126
+ interruptRequested = false;
20903
21127
  finalized = false;
20904
21128
  beginTurn() {
20905
21129
  const uuid3 = randomUUID4();
@@ -20908,6 +21132,7 @@ class ClaudeTurnProtocol {
20908
21132
  this.delivered.clear();
20909
21133
  this.delivered.set(uuid3, { acknowledged: false, followOn: false });
20910
21134
  this.awaitingFollowOn = false;
21135
+ this.interruptRequested = false;
20911
21136
  this.finalized = false;
20912
21137
  return this.receipt(uuid3);
20913
21138
  }
@@ -20934,6 +21159,13 @@ class ClaudeTurnProtocol {
20934
21159
  return false;
20935
21160
  return this.delivered.get(this.rootUuid)?.acknowledged === true;
20936
21161
  }
21162
+ hasActiveTurn() {
21163
+ return this.rootUuid !== null && !this.finalized;
21164
+ }
21165
+ noteInterruptRequested() {
21166
+ if (this.hasActiveTurn())
21167
+ this.interruptRequested = true;
21168
+ }
20937
21169
  claimResult(userMessageUuid) {
20938
21170
  if (this.finalized || !this.rootUuid || userMessageUuid !== this.segmentOwnerUuid)
20939
21171
  return null;
@@ -20950,6 +21182,19 @@ class ClaudeTurnProtocol {
20950
21182
  this.finalized = true;
20951
21183
  return this.receipt(this.rootUuid);
20952
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
+ }
20953
21198
  receipt(uuid3) {
20954
21199
  return `claude:${uuid3}`;
20955
21200
  }
@@ -20975,7 +21220,9 @@ class ClaudeDriver {
20975
21220
  };
20976
21221
  turnProtocol = new ClaudeTurnProtocol;
20977
21222
  eventNormalizer = new ClaudeEventNormalizer(this.turnProtocol);
21223
+ pendingInterrupt;
20978
21224
  beginTurn() {
21225
+ this.pendingInterrupt = undefined;
20979
21226
  const receipt = this.turnProtocol.beginTurn();
20980
21227
  this.eventNormalizer.beginTurn();
20981
21228
  return receipt;
@@ -21014,11 +21261,45 @@ class ClaudeDriver {
21014
21261
  return { process: proc };
21015
21262
  }
21016
21263
  normalizeLine(line) {
21017
- return this.eventNormalizer.normalizeLine(line);
21264
+ const events = this.eventNormalizer.normalizeLine(line);
21265
+ this.flushPendingInterrupt();
21266
+ return events;
21018
21267
  }
21019
21268
  get currentSessionId() {
21020
21269
  return this.eventNormalizer.currentSessionId;
21021
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
+ }
21022
21303
  encodeMessage(text2, sessionId, opts) {
21023
21304
  const uuid3 = opts?.mode === "idle" ? this.turnProtocol.rootInputUuid() : this.turnProtocol.steeringInputUuid();
21024
21305
  return JSON.stringify({
@@ -21150,6 +21431,35 @@ import { randomBytes } from "node:crypto";
21150
21431
  var codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
21151
21432
  var codexQuotaSourceGeneration = 0;
21152
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
+ }
21153
21463
  function rotateCodexQuotaSource() {
21154
21464
  codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
21155
21465
  codexQuotaSourceGeneration += 1;
@@ -21307,7 +21617,11 @@ class CodexEventNormalizer {
21307
21617
  return this.replaceQuotaSnapshots(msg.result ?? {});
21308
21618
  }
21309
21619
  if (msg?.error && msg.id !== undefined) {
21310
- 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
+ }];
21311
21625
  }
21312
21626
  if (msg?.result?.thread?.id) {
21313
21627
  return this.adoptAndInit(msg.result.thread.id);
@@ -21376,20 +21690,26 @@ class CodexEventNormalizer {
21376
21690
  return [];
21377
21691
  this.releaseUsageForTurn(params.threadId, params.turn.id);
21378
21692
  if (params.turn.status === "failed") {
21693
+ const error51 = params.turn.error;
21379
21694
  return [
21380
- { 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
+ },
21381
21700
  { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }
21382
21701
  ];
21383
21702
  }
21384
- if (params.turn.status === "interrupted") {
21385
- return [{ kind: "error", message: "Codex turn interrupted" }, { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
21386
- }
21387
21703
  return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
21388
21704
  case "error":
21389
21705
  if (params?.willRetry === true) {
21390
21706
  return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
21391
21707
  }
21392
- 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
+ }];
21393
21713
  case "thread/tokenUsage/updated":
21394
21714
  return [];
21395
21715
  case "account/rateLimits/updated":
@@ -21486,42 +21806,46 @@ class CodexEventNormalizer {
21486
21806
  }
21487
21807
  handleItemStarted(params) {
21488
21808
  const t = params?.item?.type ?? params?.type;
21809
+ const callId = this.itemCallId(params);
21810
+ const identity = callId ? { callId } : {};
21489
21811
  switch (t) {
21490
21812
  case "commandExecution":
21491
- return [{ kind: "tool_call", name: "shell", input: params?.item }];
21813
+ return [{ kind: "tool_call", ...identity, name: "shell", input: params?.item }];
21492
21814
  case "contextCompaction":
21493
21815
  return [{ kind: "compaction_started" }];
21494
21816
  case "enteredReviewMode":
21495
21817
  return [{ kind: "review_started" }];
21496
21818
  case "fileChange":
21497
- return [{ kind: "tool_call", name: "file_change", input: normalizeFileChangeInput(params?.item) }];
21819
+ return [{ kind: "tool_call", ...identity, name: "file_change", input: normalizeFileChangeInput(params?.item) }];
21498
21820
  case "mcpToolCall":
21499
- 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 }];
21500
21822
  case "webSearch":
21501
- return [{ kind: "tool_call", name: "web_search", input: params?.item }];
21823
+ return [{ kind: "tool_call", ...identity, name: "web_search", input: params?.item }];
21502
21824
  case "collabAgentToolCall":
21503
- return [{ kind: "tool_call", name: "collab_tool_call", input: params?.item }];
21825
+ return [{ kind: "tool_call", ...identity, name: "collab_tool_call", input: params?.item }];
21504
21826
  default:
21505
21827
  return [];
21506
21828
  }
21507
21829
  }
21508
21830
  handleItemCompleted(params) {
21509
21831
  const t = params?.item?.type ?? params?.type;
21832
+ const callId = this.itemCallId(params);
21833
+ const identity = callId ? { callId } : {};
21510
21834
  switch (t) {
21511
21835
  case "commandExecution":
21512
- return [{ kind: "tool_output", name: "shell" }];
21836
+ return [{ kind: "tool_output", ...identity, name: "shell" }];
21513
21837
  case "contextCompaction":
21514
21838
  return [{ kind: "compaction_finished" }];
21515
21839
  case "exitedReviewMode":
21516
21840
  return [{ kind: "review_finished" }];
21517
21841
  case "fileChange":
21518
- return [{ kind: "tool_output", name: "file_change" }];
21842
+ return [{ kind: "tool_output", ...identity, name: "file_change" }];
21519
21843
  case "mcpToolCall":
21520
- 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"}` }];
21521
21845
  case "webSearch":
21522
- return [{ kind: "tool_output", name: "web_search" }];
21846
+ return [{ kind: "tool_output", ...identity, name: "web_search" }];
21523
21847
  case "collabAgentToolCall":
21524
- return [{ kind: "tool_output", name: "collab_tool_call" }];
21848
+ return [{ kind: "tool_output", ...identity, name: "collab_tool_call" }];
21525
21849
  case "agentMessage":
21526
21850
  return [{ kind: "assistant_message_completed", text: params?.item?.text ?? "" }];
21527
21851
  case "reasoning":
@@ -21530,6 +21854,10 @@ class CodexEventNormalizer {
21530
21854
  return [];
21531
21855
  }
21532
21856
  }
21857
+ itemCallId(params) {
21858
+ const value = params?.item?.id ?? params?.itemId;
21859
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
21860
+ }
21533
21861
  }
21534
21862
 
21535
21863
  // agent-driver/dist/adapters/codex/home.js
@@ -22000,6 +22328,16 @@ class CodexDriver {
22000
22328
  get currentSessionId() {
22001
22329
  return this.eventNormalizer.currentSessionId;
22002
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
+ }
22003
22341
  buildTurnStart(threadId, text2) {
22004
22342
  return jsonRpcRequest("turn/start", { threadId, input: [{ type: "text", text: text2 }] }, this.nextRequestId());
22005
22343
  }
@@ -22255,7 +22593,8 @@ class CursorAcpLane {
22255
22593
  started = false;
22256
22594
  ready = false;
22257
22595
  sessionId = null;
22258
- activePrompt = null;
22596
+ currentPromptRequestId = null;
22597
+ terminalOwner = null;
22259
22598
  requestedStopReason;
22260
22599
  spawnPromise;
22261
22600
  stopPromise;
@@ -22327,16 +22666,19 @@ class CursorAcpLane {
22327
22666
  async send(input) {
22328
22667
  if (!this.ready || !this.process || this.isClosed())
22329
22668
  return { ok: false, reason: "closed" };
22330
- if (input.mode !== "idle") {
22331
- return { ok: false, reason: "unsupported", error: "Cursor ACP does not support mid-turn steering" };
22669
+ if (input.mode === "busy") {
22670
+ if (this.currentPromptRequestId === null) {
22671
+ return { ok: false, reason: "runtime_busy", error: "Cursor ACP has no active prompt to steer" };
22672
+ }
22673
+ return this.admitPrompt(input.text, "steer");
22332
22674
  }
22333
- if (this.activePrompt) {
22675
+ if (this.currentPromptRequestId !== null) {
22334
22676
  return { ok: false, reason: "runtime_busy", error: "Cursor ACP prompt is still active" };
22335
22677
  }
22336
22678
  return this.admitPrompt(input.text);
22337
22679
  }
22338
22680
  async interrupt(_input) {
22339
- if (!this.ready || !this.sessionId || !this.activePrompt || this.isClosed())
22681
+ if (!this.ready || !this.sessionId || this.currentPromptRequestId === null || this.isClosed())
22340
22682
  return false;
22341
22683
  this.write({ jsonrpc: "2.0", method: "session/cancel", params: { sessionId: this.sessionId } });
22342
22684
  return true;
@@ -22344,7 +22686,8 @@ class CursorAcpLane {
22344
22686
  stop(input = {}) {
22345
22687
  this.requestedStopReason ??= input.reason ?? "requested_stop";
22346
22688
  this.ready = false;
22347
- this.activePrompt = null;
22689
+ this.currentPromptRequestId = null;
22690
+ this.terminalOwner = null;
22348
22691
  this.openToolCalls.clear();
22349
22692
  this.rejectAllPending(new Error("Cursor ACP lane stopped"));
22350
22693
  this.stopPromise ??= this.stopPhysicalOnce(input);
@@ -22450,19 +22793,22 @@ class CursorAcpLane {
22450
22793
  throw new CursorAcpIncompatibleError("Cursor ACP did not confirm the exact configured model");
22451
22794
  }
22452
22795
  }
22453
- admitPrompt(text2) {
22796
+ admitPrompt(text2, delivery = "prompt") {
22454
22797
  if (!this.sessionId)
22455
22798
  return { ok: false, reason: "not_ready", error: "Cursor ACP session is not ready" };
22456
- if (this.activePrompt)
22457
- return { ok: false, reason: "runtime_busy", error: "Cursor ACP prompt is still active" };
22458
22799
  const requestId = ++this.requestSequence;
22459
- const active = {
22800
+ const prompt = {
22460
22801
  requestId,
22461
22802
  receipt: `cursor:acp:${requestId}`
22462
22803
  };
22804
+ const previousPromptRequestId = this.currentPromptRequestId;
22805
+ const previousTerminalOwner = this.terminalOwner;
22806
+ const previousOpenToolCalls = new Set(this.openToolCalls);
22807
+ if (delivery === "prompt")
22808
+ this.terminalOwner = prompt.receipt;
22809
+ this.currentPromptRequestId = requestId;
22463
22810
  this.openToolCalls.clear();
22464
- this.activePrompt = active;
22465
- this.pending.set(requestId, { kind: "prompt", method: "session/prompt", active });
22811
+ this.pending.set(requestId, { kind: "prompt", method: "session/prompt", prompt });
22466
22812
  try {
22467
22813
  this.write({
22468
22814
  jsonrpc: "2.0",
@@ -22475,41 +22821,51 @@ class CursorAcpLane {
22475
22821
  });
22476
22822
  } catch (error51) {
22477
22823
  this.pending.delete(requestId);
22478
- if (this.activePrompt?.requestId === requestId)
22479
- this.activePrompt = null;
22824
+ if (this.currentPromptRequestId === requestId) {
22825
+ this.currentPromptRequestId = previousPromptRequestId;
22826
+ this.terminalOwner = previousTerminalOwner;
22827
+ this.openToolCalls.clear();
22828
+ for (const toolCallId of previousOpenToolCalls)
22829
+ this.openToolCalls.add(toolCallId);
22830
+ }
22480
22831
  throw error51;
22481
22832
  }
22482
- return { ok: true, acceptedAs: "prompt", receipt: active.receipt };
22833
+ return { ok: true, acceptedAs: delivery, receipt: prompt.receipt };
22483
22834
  }
22484
- completePrompt(active, value) {
22485
- if (this.activePrompt?.requestId !== active.requestId)
22835
+ completePrompt(prompt, value) {
22836
+ if (this.currentPromptRequestId !== prompt.requestId)
22486
22837
  return;
22487
22838
  const result = record3(value);
22488
22839
  if (!result || typeof result.stopReason !== "string" || !PROMPT_STOP_REASONS.has(result.stopReason)) {
22489
- this.failPrompt(active, 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");
22490
22841
  return;
22491
22842
  }
22492
- this.activePrompt = null;
22843
+ const terminalOwner = this.terminalOwner ?? prompt.receipt;
22844
+ this.currentPromptRequestId = null;
22845
+ this.terminalOwner = null;
22493
22846
  this.openToolCalls.clear();
22494
22847
  this.events.emit("runtime_event", {
22495
22848
  kind: "turn_end",
22496
22849
  sessionId: this.sessionId ?? undefined,
22497
- turnOwner: active.receipt
22850
+ turnOwner: terminalOwner
22498
22851
  });
22499
22852
  }
22500
- failPrompt(active, error51) {
22501
- if (this.activePrompt?.requestId !== active.requestId)
22853
+ failPrompt(prompt, error51, code = "cursor.prompt_failed") {
22854
+ if (this.currentPromptRequestId !== prompt.requestId)
22502
22855
  return;
22503
- this.activePrompt = null;
22856
+ const terminalOwner = this.terminalOwner ?? prompt.receipt;
22857
+ this.currentPromptRequestId = null;
22858
+ this.terminalOwner = null;
22504
22859
  this.openToolCalls.clear();
22505
22860
  this.events.emit("runtime_event", {
22506
22861
  kind: "error",
22862
+ code,
22507
22863
  message: error51 instanceof Error ? error51.message : String(error51)
22508
22864
  });
22509
22865
  this.events.emit("runtime_event", {
22510
22866
  kind: "turn_end",
22511
22867
  sessionId: this.sessionId ?? undefined,
22512
- turnOwner: active.receipt
22868
+ turnOwner: terminalOwner
22513
22869
  });
22514
22870
  }
22515
22871
  call(method, params) {
@@ -22595,7 +22951,8 @@ class CursorAcpLane {
22595
22951
  }
22596
22952
  this.processTerminal = true;
22597
22953
  this.ready = false;
22598
- this.activePrompt = null;
22954
+ this.currentPromptRequestId = null;
22955
+ this.terminalOwner = null;
22599
22956
  this.openToolCalls.clear();
22600
22957
  this.stopPromise ??= this.stopPhysicalOnce({ reason: "runtime_error", forceAfterMs: 0 });
22601
22958
  this.stopPromise.catch(() => {});
@@ -22613,7 +22970,8 @@ class CursorAcpLane {
22613
22970
  return;
22614
22971
  this.processTerminal = true;
22615
22972
  this.ready = false;
22616
- this.activePrompt = null;
22973
+ this.currentPromptRequestId = null;
22974
+ this.terminalOwner = null;
22617
22975
  this.openToolCalls.clear();
22618
22976
  this.rejectAllPending(new Error("Cursor ACP process exited"));
22619
22977
  if (this.suppressExit || !this.processActivated)
@@ -22655,11 +23013,12 @@ class CursorAcpLane {
22655
23013
  this.pending.delete(id);
22656
23014
  if (message2.error !== undefined) {
22657
23015
  const payload = record3(message2.error);
22658
- this.failPrompt(pending.active, 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}`);
22659
23018
  } else if (!("result" in message2)) {
22660
- this.failPrompt(pending.active, new Error("Cursor ACP response omitted result"));
23019
+ this.failPrompt(pending.prompt, new Error("Cursor ACP response omitted result"), "cursor.invalid_response");
22661
23020
  } else {
22662
- this.completePrompt(pending.active, message2.result);
23021
+ this.completePrompt(pending.prompt, message2.result);
22663
23022
  }
22664
23023
  return;
22665
23024
  }
@@ -22700,7 +23059,7 @@ class CursorAcpLane {
22700
23059
  const sameSession = payload?.sessionId === this.sessionId;
22701
23060
  const options = Array.isArray(payload?.options) ? payload.options.map(record3).filter(Boolean) : [];
22702
23061
  const allowOnce = options.find((option) => option.kind === "allow_once" && typeof option.optionId === "string" && option.optionId.trim().length > 0);
22703
- if (!this.ready || !this.activePrompt || !sameSession || !allowOnce) {
23062
+ if (!this.ready || this.currentPromptRequestId === null || !sameSession || !allowOnce) {
22704
23063
  this.write({ jsonrpc: "2.0", id, result: { outcome: { outcome: "cancelled" } } });
22705
23064
  this.diagnostic("error", "Cursor ACP permission request was not allowed for the active prompt");
22706
23065
  return;
@@ -22724,7 +23083,7 @@ class CursorAcpLane {
22724
23083
  this.diagnostic("warning", "Cursor ACP emitted an update for a different session");
22725
23084
  return;
22726
23085
  }
22727
- if (!this.ready || !this.activePrompt) {
23086
+ if (!this.ready || this.currentPromptRequestId === null) {
22728
23087
  this.diagnostic("warning", "Cursor ACP emitted a session update without an active prompt");
22729
23088
  return;
22730
23089
  }
@@ -22942,7 +23301,11 @@ function parseModelRef(model) {
22942
23301
  function messageFromError(value) {
22943
23302
  const payload = record4(value);
22944
23303
  const message2 = typeof payload?.message === "string" && payload.message.trim() ? payload.message : undefined;
22945
- 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";
22946
23309
  }
22947
23310
 
22948
23311
  class OpenCodeServiceLane {
@@ -23610,7 +23973,10 @@ class OpenCodeServiceLane {
23610
23973
  root.outcomes.push({
23611
23974
  seq,
23612
23975
  ok: successful,
23613
- ...!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
+ } : {}
23614
23980
  });
23615
23981
  }
23616
23982
  const tokens = record4(data.tokens);
@@ -23639,7 +24005,12 @@ class OpenCodeServiceLane {
23639
24005
  break;
23640
24006
  }
23641
24007
  case "session.next.step.failed":
23642
- 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
+ });
23643
24014
  break;
23644
24015
  default:
23645
24016
  break;
@@ -23819,11 +24190,11 @@ class OpenCodeServiceLane {
23819
24190
  return;
23820
24191
  const outcome = root.outcomes.filter((item) => item.seq > maxAdmission).sort((left, right) => left.seq - right.seq).at(-1);
23821
24192
  if (!root.interrupted && !outcome) {
23822
- 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");
23823
24194
  return;
23824
24195
  }
23825
24196
  if (outcome && !outcome.ok) {
23826
- this.settleRoot(root, false, outcome.message ?? "OpenCode turn failed");
24197
+ this.settleRoot(root, false, outcome.message ?? "OpenCode turn failed", outcome.code ?? "opencode.turn_failed");
23827
24198
  return;
23828
24199
  }
23829
24200
  this.settleRoot(root, true);
@@ -23849,7 +24220,7 @@ class OpenCodeServiceLane {
23849
24220
  barrierStillCurrent(root, identity, generation) {
23850
24221
  return this.activeRoot === root && root.generation === generation && root.pendingAdmissions === 0 && !root.interruptPending && this.isIdentity(identity) && this.identityMatches();
23851
24222
  }
23852
- settleRoot(root, success2, message2) {
24223
+ settleRoot(root, success2, message2, code = "opencode.turn_failed") {
23853
24224
  if (this.activeRoot !== root)
23854
24225
  return;
23855
24226
  this.activeRoot = null;
@@ -23857,6 +24228,7 @@ class OpenCodeServiceLane {
23857
24228
  if (!success2) {
23858
24229
  this.events.emit("runtime_event", {
23859
24230
  kind: "error",
24231
+ code,
23860
24232
  message: message2 ?? "OpenCode turn failed"
23861
24233
  });
23862
24234
  }
@@ -24372,7 +24744,11 @@ function mapPiSdkEvent(event, sessionId, state) {
24372
24744
  return [{ kind: "assistant_message_completed", text: d.content ?? "" }];
24373
24745
  }
24374
24746
  case "error":
24375
- 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
+ }];
24376
24752
  default:
24377
24753
  return [];
24378
24754
  }
@@ -24652,7 +25028,7 @@ var capabilities = {
24652
25028
  commandOverride: true,
24653
25029
  resume: "by_id",
24654
25030
  sessionLifetime: "persistent",
24655
- midTurnDelivery: "next_turn_queue",
25031
+ midTurnDelivery: "steer",
24656
25032
  interrupt: true
24657
25033
  },
24658
25034
  opencode: {
@@ -24960,7 +25336,9 @@ class LogicalAgentSession {
24960
25336
  interruptedTurnId;
24961
25337
  processTurnEnded = false;
24962
25338
  stopRequestId;
24963
- outstandingToolUses = 0;
25339
+ anonymousOutstandingToolUses = 0;
25340
+ outstandingToolCallIds = new Set;
25341
+ toolLifecycleMismatchCount = 0;
24964
25342
  compacting = false;
24965
25343
  reviewing = false;
24966
25344
  steeringDeliveries = new Set;
@@ -25150,6 +25528,9 @@ class LogicalAgentSession {
25150
25528
  queueDwellCount: this.metricValue(this.queueDwellCount),
25151
25529
  queueDwellTotalMs: this.metricValue(this.queueDwellTotalMs),
25152
25530
  sseReconnectCount: this.metricValue(this.sseReconnectCount),
25531
+ outstandingToolUses: this.outstandingToolUseCount(),
25532
+ anonymousOutstandingToolUses: this.metricValue(this.anonymousOutstandingToolUses),
25533
+ toolLifecycleMismatchCount: this.metricValue(this.toolLifecycleMismatchCount),
25153
25534
  resumeOutcome: this.resumeOutcome,
25154
25535
  terminalOwnerKind: this.adapter.execution.terminalOwnership
25155
25536
  }
@@ -25469,15 +25850,36 @@ class LogicalAgentSession {
25469
25850
  }
25470
25851
  return;
25471
25852
  case "tool_call":
25472
- this.outstandingToolUses += 1;
25473
- if (turnId)
25474
- 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
+ });
25475
25867
  return;
25476
25868
  case "tool_output":
25477
- this.outstandingToolUses = Math.max(0, this.outstandingToolUses - 1);
25478
- if (turnId)
25479
- this.emit({ type: "tool_finished", turnId, name: event.name });
25480
- 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)
25481
25883
  this.flushSafeBoundaryQueue();
25482
25884
  return;
25483
25885
  case "compaction_started":
@@ -25533,7 +25935,7 @@ class LogicalAgentSession {
25533
25935
  }
25534
25936
  case "error":
25535
25937
  this.toolBoundaryFlushDisabled = true;
25536
- this.turnError = driverError("process", "runtime_error", event.message, true);
25938
+ this.turnError = driverError("process", stableErrorCode(event.code, "runtime_error"), event.message, true);
25537
25939
  this.emit({
25538
25940
  type: "diagnostic",
25539
25941
  turnId,
@@ -25620,7 +26022,7 @@ class LogicalAgentSession {
25620
26022
  this.activeTurn = undefined;
25621
26023
  this.turnError = undefined;
25622
26024
  this.interruptedTurnId = undefined;
25623
- this.outstandingToolUses = 0;
26025
+ this.clearOutstandingToolUses();
25624
26026
  this.compacting = false;
25625
26027
  this.reviewing = false;
25626
26028
  this.toolBoundaryFlushDisabled = false;
@@ -25644,7 +26046,7 @@ class LogicalAgentSession {
25644
26046
  return this.safeBoundaryFlush;
25645
26047
  }
25646
26048
  canFlushSafeBoundaryQueue() {
25647
- 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;
25648
26050
  }
25649
26051
  async drainSafeBoundaryQueue() {
25650
26052
  while (this.canFlushSafeBoundaryQueue()) {
@@ -25825,7 +26227,7 @@ class LogicalAgentSession {
25825
26227
  return "compacting";
25826
26228
  if (this.reviewing)
25827
26229
  return "reviewing";
25828
- if (this.outstandingToolUses > 0)
26230
+ if (this.outstandingToolUseCount() > 0)
25829
26231
  return "tool_wait";
25830
26232
  if (this.activeTurn)
25831
26233
  return "working";
@@ -25835,6 +26237,46 @@ class LogicalAgentSession {
25835
26237
  const now = this.host.now();
25836
26238
  return Number.isFinite(now) && now >= 0 ? now : 0;
25837
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
+ }
25838
26280
  metricValue(value) {
25839
26281
  return Number.isFinite(value) && value >= 0 ? value : 0;
25840
26282
  }
@@ -27473,10 +27915,11 @@ var DEFAULT_CAPABILITY_RESOLVER = (method, pathname) => {
27473
27915
  const isMessagesDoor = /\/channels\/[^/]+\/messages(\/|$|\?)/.test(pathname);
27474
27916
  if (isMessagesDoor)
27475
27917
  return method === "GET" ? "read" : "send";
27918
+ if (/\/messages\/[^/]+\/properties(\/|$|\?)/.test(pathname)) {
27919
+ return method === "GET" ? "read" : "send";
27920
+ }
27476
27921
  if (/\/messages\/[^/]+\/reactions\//.test(pathname))
27477
27922
  return "send";
27478
- if ((method === "PUT" || method === "DELETE") && /\/messages\/[^/]+\/marks(\/|$|\?)/.test(pathname))
27479
- return "send";
27480
27923
  if (method === "GET" && /\/users\/me\/marks(\/|$|\?)/.test(pathname))
27481
27924
  return "read";
27482
27925
  if (method === "GET" && /\/messages\/[^/]+(\?|$)/.test(pathname))
@@ -27517,6 +27960,12 @@ function isCanonicalChannelScope(channel2) {
27517
27960
  return false;
27518
27961
  }
27519
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
+ }
27520
27969
  function parseLocalMessageReminderBody(body, agentId) {
27521
27970
  let value;
27522
27971
  try {
@@ -27655,7 +28104,12 @@ async function startCredentialProxy(broker, options = {}) {
27655
28104
  }
27656
28105
  if (onProxyRequest) {
27657
28106
  try {
27658
- 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);
27659
28113
  } catch {}
27660
28114
  }
27661
28115
  const outHeaders = { ...req.headers };
@@ -27938,6 +28392,7 @@ function reduceManager(state, event) {
27938
28392
  if ((lease.state === "active" || lease.state === "suspect_active") && lease.outstandingToolUses !== undefined) {
27939
28393
  const unblockedLease = { ...lease };
27940
28394
  delete unblockedLease.outstandingToolUses;
28395
+ delete unblockedLease.outstandingToolCallIds;
27941
28396
  a.execution.lease = unblockedLease;
27942
28397
  }
27943
28398
  });
@@ -28198,26 +28653,40 @@ function onTurnToolLifecycle(state, event, lifecycle) {
28198
28653
  const matchesTerminal = lifecycle === "started" && current.state === "none" && current.lastTerminal !== null && sameIdentity(current.lastTerminal.identity, identity);
28199
28654
  if (!matchesActive && !matchesTerminal)
28200
28655
  return { state, effects: [] };
28201
- if (lifecycle === "finished" && matchesActive && (current.outstandingToolUses ?? 0) === 0) {
28202
- return { state, effects: [] };
28203
- }
28204
28656
  return mutate(state, event.agentId, (agent2) => {
28205
28657
  if (!matchesSession(agent2, event.sessionInstanceId))
28206
28658
  return;
28207
28659
  const lease = agent2.execution.lease;
28660
+ const callId = normalizedToolCallId(event.callId);
28208
28661
  if ((lease.state === "active" || lease.state === "suspect_active") && sameIdentity(lease.identity, identity)) {
28209
- 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);
28210
28677
  if (outstandingToolUses > 0) {
28211
28678
  agent2.execution.lease = {
28212
28679
  ...lease,
28213
28680
  lastWorkAt: event.nowMs,
28214
28681
  nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
28215
28682
  recoveryExtensionsUsed: 0,
28216
- outstandingToolUses
28683
+ outstandingToolUses,
28684
+ ...identified.size > 0 ? { outstandingToolCallIds: [...identified] } : {}
28217
28685
  };
28218
28686
  } else {
28219
28687
  const unblockedLease = { ...lease };
28220
28688
  delete unblockedLease.outstandingToolUses;
28689
+ delete unblockedLease.outstandingToolCallIds;
28221
28690
  agent2.execution.lease = {
28222
28691
  ...unblockedLease,
28223
28692
  lastWorkAt: event.nowMs,
@@ -28236,6 +28705,7 @@ function onTurnToolLifecycle(state, event, lifecycle) {
28236
28705
  nativeDeadlineAt: event.nowMs + agent2.turnSilence.normalBudgetMs,
28237
28706
  recoveryExtensionsUsed: 0,
28238
28707
  outstandingToolUses: 1,
28708
+ ...callId ? { outstandingToolCallIds: [callId] } : {},
28239
28709
  reason: "work_after_terminal"
28240
28710
  };
28241
28711
  }
@@ -28399,6 +28869,10 @@ function identityOf(event) {
28399
28869
  function sameIdentity(left, right) {
28400
28870
  return left.sessionInstanceId === right.sessionInstanceId && left.turnId === right.turnId;
28401
28871
  }
28872
+ function normalizedToolCallId(rawCallId) {
28873
+ const callId = rawCallId?.trim();
28874
+ return callId && callId.length <= 1024 ? callId : undefined;
28875
+ }
28402
28876
  function leaseIsWorking(lease) {
28403
28877
  return lease.state === "active" || lease.state === "suspect_active";
28404
28878
  }
@@ -28620,13 +29094,13 @@ function cliCommandsSection() {
28620
29094
  "",
28621
29095
  "### Messaging",
28622
29096
  "",
28623
- `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.`,
28624
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\`.`,
28625
29099
  `3. \`${CLI} message attachment upload --target <ref> --file <path>\` — upload a file; ` + `returns an id stable across pending→persisted.`,
28626
29100
  `4. \`${CLI} message attachment download --id <id> [--out <path>]\` — download any ` + `attachment you can see (or your own pending uploads).`,
28627
- `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\`).`,
28628
- `6. \`${CLI} message mark set --target <full-message-ref>\` — persist a message as outstanding work.`,
28629
- `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.`,
28630
29104
  `8. \`${CLI} message mark list\` — list every currently visible marked message with its full content.`,
28631
29105
  "",
28632
29106
  "### Servers",
@@ -28634,12 +29108,14 @@ function cliCommandsSection() {
28634
29108
  `1. \`${CLI} server list\` — list your servers.`,
28635
29109
  `2. \`${CLI} server member --server <name#discriminator>\` — list a server's members.`,
28636
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.`,
28637
29112
  "",
28638
29113
  "### Channels",
28639
29114
  "",
28640
29115
  `1. \`${CLI} channel list --server <name#discriminator>\` — list top-level channels, grouped by ` + `category; each is marked \`public\` or \`private\`.`,
28641
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.",
28642
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.`,
28643
29119
  "",
28644
29120
  "### Friends",
28645
29121
  "",
@@ -28692,6 +29168,15 @@ function messagingSection() {
28692
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.",
28693
29169
  "Write every message body in the stdin/heredoc block; never place it directly on the command line.",
28694
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
+ "",
28695
29180
  "### Context refs",
28696
29181
  "",
28697
29182
  "Path-style refs:",
@@ -28804,6 +29289,7 @@ function criticalRulesSection() {
28804
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.`,
28805
29290
  "- **Never expose tokens, keys, or secrets.** Redact credential-like strings from tool output " + "before sharing.",
28806
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.`,
28807
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."
28808
29294
  ].join(`
28809
29295
  `);
@@ -28816,7 +29302,7 @@ function executionModelSection() {
28816
29302
  "",
28817
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.",
28818
29304
  "",
28819
- "`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."
28820
29306
  ].join(`
28821
29307
  `);
28822
29308
  }
@@ -28888,9 +29374,9 @@ function workspaceMemorySection(config2) {
28888
29374
  "",
28889
29375
  "### Outstanding work marks",
28890
29376
  "",
28891
- "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.",
28892
29378
  "",
28893
- "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.",
28894
29380
  "",
28895
29381
  "If `inbox pull` returns a `markedReminder`, run " + `\`${CLI} message mark list\` before taking new work. Do not copy marked tasks into local files.`
28896
29382
  ].join(`
@@ -28943,7 +29429,7 @@ function toLocalISO(iso) {
28943
29429
  }
28944
29430
 
28945
29431
  // src/manager/managerRuntime.ts
28946
- import { randomUUID as randomUUID5 } from "node:crypto";
29432
+ import { randomUUID as randomUUID6 } from "node:crypto";
28947
29433
  var SESSION_STOP_GRACE_MS2 = 2000;
28948
29434
  function normalizeTerminationCause(value) {
28949
29435
  return value === "runtime_error" || value === "killed_stalled" ? value : "other";
@@ -29180,7 +29666,7 @@ class AgentProcessManager {
29180
29666
  liveBackendIds = new Map;
29181
29667
  activeSpawnState = new Map;
29182
29668
  publishedAgentActivity = new Map;
29183
- traceProcessNonce = randomUUID5();
29669
+ traceProcessNonce = randomUUID6();
29184
29670
  nextSpawnOrdinal = 1;
29185
29671
  nextDaemonTurnOrdinal = 1;
29186
29672
  nextDeliveryOrdinal = 1;
@@ -29456,6 +29942,12 @@ class AgentProcessManager {
29456
29942
  if (this.sessions.get(agentId) === session2)
29457
29943
  this.sessions.delete(agentId);
29458
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
+ }
29459
29951
  async stopAll() {
29460
29952
  if (this.tickTimer) {
29461
29953
  clearInterval(this.tickTimer);
@@ -29953,7 +30445,7 @@ ${this.opts.wakePromptFooter}` : text2;
29953
30445
  case "reset_idle_session": {
29954
30446
  const spawnState = this.activeSpawnState.get(effect.agentId);
29955
30447
  const completion = {
29956
- eventId: `bae_${randomUUID5()}`,
30448
+ eventId: `bae_${randomUUID6()}`,
29957
30449
  occurredAt: new Date(this.now()).toISOString()
29958
30450
  };
29959
30451
  const persisted = this.forgetSession(effect.agentId, "reset_session", effect.sessionId, completion);
@@ -30348,11 +30840,12 @@ ${this.opts.wakePromptFooter}` : text2;
30348
30840
  emitErrorAudit(agentId, scope, code, rawMessage) {
30349
30841
  if (!this.opts.onBotAuditEvent)
30350
30842
  return;
30843
+ const stableCode = /^[A-Za-z0-9_.-]{1,100}$/.test(code) ? code : "runtime_error";
30351
30844
  const message2 = scrubRuntimeErrorDiagnosticText(rawMessage).slice(0, AUDIT_ERROR_MESSAGE_MAX_LEN);
30352
30845
  try {
30353
30846
  this.opts.onBotAuditEvent(agentId, {
30354
30847
  kind: "error",
30355
- payload: { scope, code, message: message2, model: this.currentModelFor(agentId) }
30848
+ payload: { scope, code: stableCode, message: message2, model: this.currentModelFor(agentId) }
30356
30849
  }, {
30357
30850
  sessionId: this.liveSessions.get(agentId) ?? null,
30358
30851
  launchId: this.launchIds.get(agentId) ?? null
@@ -30377,18 +30870,18 @@ ${this.opts.wakePromptFooter}` : text2;
30377
30870
  return;
30378
30871
  }
30379
30872
  const sessionSuperseded = owner.superseded;
30380
- const errorMessage2 = event.type === "session_failed" ? event.error.message : event.type === "turn_completed" && event.result.outcome === "failed" ? event.result.error.message : undefined;
30381
- if (errorMessage2 !== undefined && !sessionSuperseded) {
30382
- 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);
30383
30876
  if (this.nonCleanEndMarker.get(agentId)?.cause !== "killed_stalled") {
30384
- this.nonCleanEndMarker.set(agentId, { cause: "runtime_error", detail: errorMessage2 });
30877
+ this.nonCleanEndMarker.set(agentId, { cause: "runtime_error", detail: runtimeError.message });
30385
30878
  }
30386
30879
  const agent2 = this.state.agents[agentId];
30387
30880
  if (agent2?.resetting && agent2.resettingSince !== null && this.now() - agent2.resettingSince >= this.state.resetStuckThresholdMs) {
30388
30881
  this.log.warn("runtime error during a stuck reset window", {
30389
30882
  agentId,
30390
30883
  resettingForMs: this.now() - agent2.resettingSince,
30391
- message: errorMessage2
30884
+ message: runtimeError.message
30392
30885
  });
30393
30886
  }
30394
30887
  }
@@ -30483,9 +30976,9 @@ ${this.opts.wakePromptFooter}` : text2;
30483
30976
  case "assistant_message_completed":
30484
30977
  return { type: "turn_work", turnId: event.turnId };
30485
30978
  case "tool_started":
30486
- return { type: "turn_tool_started", turnId: event.turnId };
30979
+ return { type: "turn_tool_started", turnId: event.turnId, callId: event.callId };
30487
30980
  case "tool_finished":
30488
- return { type: "turn_tool_finished", turnId: event.turnId };
30981
+ return { type: "turn_tool_finished", turnId: event.turnId, callId: event.callId };
30489
30982
  case "compaction_started":
30490
30983
  case "compaction_finished":
30491
30984
  case "review_started":
@@ -30938,6 +31431,10 @@ class AgentRouter {
30938
31431
  }
30939
31432
  break;
30940
31433
  }
31434
+ case "agent:interrupt": {
31435
+ await this.opts.manager.interrupt(cmd.agentId);
31436
+ break;
31437
+ }
30941
31438
  case "agent:stop":
30942
31439
  this.log.info("agent:stop received", { agentId: cmd.agentId });
30943
31440
  try {
@@ -32380,7 +32877,7 @@ class MessageReminderScheduler {
32380
32877
  }
32381
32878
 
32382
32879
  // src/manager/agentDriverHost.ts
32383
- import { randomUUID as randomUUID6 } from "node:crypto";
32880
+ import { randomUUID as randomUUID7 } from "node:crypto";
32384
32881
  import { homedir as homedir4 } from "node:os";
32385
32882
  import { join as join13 } from "node:path";
32386
32883
 
@@ -32536,7 +33033,7 @@ function createDaemonAgentDriverHost(ctx, onRawLine) {
32536
33033
  onRawLine?.(event.text);
32537
33034
  },
32538
33035
  now: () => Date.now(),
32539
- createId: () => randomUUID6()
33036
+ createId: () => randomUUID7()
32540
33037
  };
32541
33038
  }
32542
33039
 
@@ -32615,13 +33112,16 @@ class DaemonSelfSleepScheduler {
32615
33112
  // src/telemetry/dailyTokenUsage.ts
32616
33113
  import { chmod, mkdir, open, readFile as readFile2, rename, rm } from "node:fs/promises";
32617
33114
  import { dirname as dirname6, join as join14 } from "node:path";
32618
- import { randomUUID as randomUUID7 } from "node:crypto";
33115
+ import { randomUUID as randomUUID8 } from "node:crypto";
33116
+ var TOKEN_USAGE_WINDOW_DAYS = 30;
33117
+ var TOKEN_USAGE_RECOVERY_DAYS = 2;
33118
+ var TOKEN_USAGE_RETENTION_DAYS = TOKEN_USAGE_WINDOW_DAYS + TOKEN_USAGE_RECOVERY_DAYS;
32619
33119
  function oldestRetainedDay(at, timeZone) {
32620
33120
  const today = dayKeyInTimeZone(at, timeZone);
32621
- return calendarDayKeyDaysAgo(today, 8);
33121
+ return calendarDayKeyDaysAgo(today, TOKEN_USAGE_RETENTION_DAYS - 1);
32622
33122
  }
32623
33123
  function oldestVisibleDay(today) {
32624
- return calendarDayKeyDaysAgo(today, 6);
33124
+ return calendarDayKeyDaysAgo(today, TOKEN_USAGE_WINDOW_DAYS - 1);
32625
33125
  }
32626
33126
  function isMetric(value) {
32627
33127
  return value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
@@ -32783,7 +33283,7 @@ class DailyTokenUsageStore {
32783
33283
  async persist() {
32784
33284
  const directory = dirname6(this.filePath);
32785
33285
  await mkdir(directory, { recursive: true, mode: 448 });
32786
- const temporary = `${this.filePath}.${randomUUID7()}.tmp`;
33286
+ const temporary = `${this.filePath}.${randomUUID8()}.tmp`;
32787
33287
  try {
32788
33288
  const file2 = await open(temporary, "wx", 384);
32789
33289
  try {
@@ -32865,15 +33365,17 @@ function createRuntimeRawLineTap(args) {
32865
33365
  }
32866
33366
  function deriveAuditLogSubcommand(pathname, method) {
32867
33367
  const canonical = pathname.split("?")[0] ?? pathname;
32868
- if (/^\/api\/community\/messages\/[^/]+\/reactions\//.test(canonical))
32869
- return "reactAdd";
32870
- if (/^\/api\/community\/messages\/[^/]+\/marks$/.test(canonical)) {
33368
+ if (/^\/api\/community\/messages\/[^/]+\/properties$/.test(canonical)) {
33369
+ if (method === "GET")
33370
+ return "messagePropertyList";
32871
33371
  if (method === "PUT")
32872
- return "markSet";
33372
+ return "messagePropertySet";
32873
33373
  if (method === "DELETE")
32874
- return "markRemove";
33374
+ return "messagePropertyRemove";
32875
33375
  return null;
32876
33376
  }
33377
+ if (/^\/api\/community\/messages\/[^/]+\/reactions\//.test(canonical))
33378
+ return "reactAdd";
32877
33379
  if (method === "GET" && /^\/api\/community\/users\/me\/marks$/.test(canonical))
32878
33380
  return "markList";
32879
33381
  if (/^\/api\/community\/channels\/[^/]+\/messages\/seq\//.test(canonical))
@@ -33039,14 +33541,14 @@ async function createDaemon(opts) {
33039
33541
  log2.warn("inbox pull timeline observation failed", { agentId, reason, contentEncoding });
33040
33542
  },
33041
33543
  onMessageReminderArm: (input) => reminderSchedulerRef?.arm(input) ?? { armed: false, reason: "reminder_scheduler_unavailable" },
33042
- onProxyRequest: (agentId, method, pathname) => {
33544
+ onProxyRequest: (agentId, method, pathname, metadata) => {
33043
33545
  const subcommand = deriveAuditLogSubcommand(pathname, method);
33044
33546
  if (!subcommand)
33045
33547
  return;
33046
33548
  const context = managerRef?.auditContext(agentId);
33047
33549
  emitBotAuditEvent(agentId, {
33048
33550
  kind: "cli_invocation",
33049
- payload: { subcommand }
33551
+ payload: { subcommand, ...metadata ? { propertyType: metadata.propertyType } : {} }
33050
33552
  }, context);
33051
33553
  emitImplicitTypingStopOnSend({
33052
33554
  subcommand,
@@ -33968,10 +34470,17 @@ function projectFsm(row, targetAgentId) {
33968
34470
  "queueDwellTotalMs",
33969
34471
  "sseReconnectCount"
33970
34472
  ];
33971
- 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;
33972
34479
  if (hasMetrics) {
33973
34480
  if (!metricKeys.every((key) => copyFiniteNumber(row, output, key)))
33974
34481
  return null;
34482
+ if (!toolMetricKeys.every((key) => copyInteger(row, output, key, { optional: true })))
34483
+ return null;
33975
34484
  const resumeOutcome = enumValue(row.resumeOutcome, RESUME_OUTCOMES);
33976
34485
  const terminalOwnerKind = enumValue(row.terminalOwnerKind, TERMINAL_OWNER_KINDS);
33977
34486
  if (!resumeOutcome || !terminalOwnerKind)
@@ -36683,7 +37192,7 @@ ${MESSAGE_SEND_STDIN_POLICY}`);
36683
37192
  }
36684
37193
  replyToSeq = n;
36685
37194
  }
36686
- const nonce = randomUUID10();
37195
+ const nonce = randomUUID11();
36687
37196
  const res = await sendWithRetry(api2, {
36688
37197
  agentId: agent2,
36689
37198
  channel: channel2,
@@ -36712,22 +37221,43 @@ ${MESSAGE_SEND_STDIN_POLICY}`);
36712
37221
  return { sent, reminder: { armed: false, reason: "local reminder request failed" } };
36713
37222
  }
36714
37223
  }
36715
- 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) {
36716
37234
  const api2 = getApi();
36717
37235
  const target2 = opts.target;
36718
- const emoji3 = opts.emoji;
36719
37236
  if (!target2)
36720
- throw new CliError("message emoji: --target <ref> is required (e.g. /demo#1234/general#42)");
36721
- if (!emoji3)
36722
- throw new CliError("message emoji: --emoji <string> is required");
36723
- const { channel: channel2, seq } = parseMessageTarget("message emoji", target2);
36724
- if (Buffer.byteLength(emoji3, "utf8") > MAX_EMOJI_BYTES) {
36725
- const err = new CliError("emoji is too long");
36726
- err.hint = "use a single emoji, not a phrase";
36727
- throw err;
36728
- }
36729
- const res = await api2.reactAdd({ channel: channel2, seq, emoji: emoji3 });
36730
- 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 };
36731
37261
  }
36732
37262
  function parseMessageTarget(command, target2) {
36733
37263
  let parsed;
@@ -36744,24 +37274,6 @@ function parseMessageTarget(command, target2) {
36744
37274
  const channel2 = parsed.threadRootSeq !== undefined ? `/${parsed.server}/${parsed.channel}/#${parsed.threadRootSeq}` : `/${parsed.server}/${parsed.channel}`;
36745
37275
  return { channel: channel2, seq: parsed.seq };
36746
37276
  }
36747
- async function cmdMessageMarkSet(opts) {
36748
- const api2 = getApi();
36749
- const target2 = opts.target;
36750
- if (!target2)
36751
- throw new CliError("message mark set: --target <ref> is required");
36752
- const request = parseMessageTarget("message mark set", target2);
36753
- await withTransientMutationRetry(() => api2.markSet(request));
36754
- return { target: target2, marked: true };
36755
- }
36756
- async function cmdMessageMarkRemove(opts) {
36757
- const api2 = getApi();
36758
- const target2 = opts.target;
36759
- if (!target2)
36760
- throw new CliError("message mark remove: --target <ref> is required");
36761
- const request = parseMessageTarget("message mark remove", target2);
36762
- await withTransientMutationRetry(() => api2.markRemove(request));
36763
- return { target: target2, marked: false };
36764
- }
36765
37277
  async function cmdMessageMarkList(opts) {
36766
37278
  const api2 = getApi();
36767
37279
  const { marked } = await api2.listMarks({ agentId: agentId(opts) });
@@ -36891,7 +37403,7 @@ async function cmdInboxPull(opts) {
36891
37403
  let acked = 0;
36892
37404
  let ackError;
36893
37405
  let failed;
36894
- if (opts.ack !== false && messages.length > 0) {
37406
+ if (messages.length > 0) {
36895
37407
  const latest = new Map;
36896
37408
  for (const m of messages) {
36897
37409
  const seqN = Number(m.seq.replace("#", ""));
@@ -36959,6 +37471,14 @@ async function cmdServerJoin(opts) {
36959
37471
  const { server } = await api2.joinServer({ agentId: agent2, invite: token });
36960
37472
  return { server };
36961
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
+ }
36962
37482
  async function cmdChannelList(opts) {
36963
37483
  const api2 = getApi();
36964
37484
  const agent2 = agentId(opts);
@@ -36975,6 +37495,26 @@ async function cmdChannelMember(opts) {
36975
37495
  throw new CliError("channel member: --channel <ref> is required");
36976
37496
  return await api2.channelMember({ agentId: agent2, channel: channel2 });
36977
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
+ }
36978
37518
  async function cmdChannelHistory(opts) {
36979
37519
  const api2 = getApi();
36980
37520
  const agent2 = agentId(opts);
@@ -37032,22 +37572,22 @@ function buildProgram(stdin) {
37032
37572
  const result = await cmdMessageSend({ ...globalOpts, ...localOpts }, stdin);
37033
37573
  printEnvelope({ success: result });
37034
37574
  });
37035
- 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() {
37036
- const localOpts = this.opts();
37037
- const globalOpts = program.opts();
37038
- 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() });
37039
37579
  printEnvelope({ success: result });
37040
37580
  });
37041
- const mark = message2.command("mark").description("durable message mark operations").exitOverride();
37042
- mark.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37043
- mark.command("set").description("mark a message as outstanding work").requiredOption("--target <ref>", "full message ref").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37044
- 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() });
37045
37583
  printEnvelope({ success: result });
37046
37584
  });
37047
- mark.command("remove").description("remove an outstanding-work mark").requiredOption("--target <ref>", "full message ref").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37048
- 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() });
37049
37587
  printEnvelope({ success: result });
37050
37588
  });
37589
+ const mark = message2.command("mark").description("global durable-mark queries").exitOverride();
37590
+ mark.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37051
37591
  mark.command("list").description("list all currently visible marked messages").exitOverride().configureOutput({ writeOut: () => {}, writeErr: () => {} }).action(async function() {
37052
37592
  const result = await cmdMessageMarkList({ ...program.opts(), ...this.opts() });
37053
37593
  printEnvelope({ success: result });
@@ -37068,7 +37608,7 @@ function buildProgram(stdin) {
37068
37608
  });
37069
37609
  const inbox = program.command("inbox").description("inbox operations").exitOverride();
37070
37610
  inbox.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37071
- 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() {
37072
37612
  const localOpts = this.opts();
37073
37613
  const globalOpts = program.opts();
37074
37614
  const result = await cmdInboxPull({ ...globalOpts, ...localOpts });
@@ -37094,6 +37634,12 @@ function buildProgram(stdin) {
37094
37634
  const result = await cmdServerJoin({ ...globalOpts, ...localOpts });
37095
37635
  printEnvelope({ success: result });
37096
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
+ });
37097
37643
  const channel2 = program.command("channel").description("channel operations").exitOverride();
37098
37644
  channel2.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37099
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() {
@@ -37114,6 +37660,12 @@ function buildProgram(stdin) {
37114
37660
  const result = await cmdChannelMember({ ...globalOpts, ...localOpts });
37115
37661
  printEnvelope({ success: result });
37116
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
+ });
37117
37669
  const friend = program.command("friend").description("friend operations").exitOverride();
37118
37670
  friend.configureOutput({ writeOut: () => {}, writeErr: () => {} });
37119
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() {