@canonmsg/agent-sdk 12.1.0 → 12.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -109,13 +109,13 @@ import {
109
109
  readMembershipChangeMetadata,
110
110
  resolveAllowedUploadMimeType,
111
111
  sanitizeDownloadFileName
112
- } from "./chunk-7N6OPPW4.js";
112
+ } from "./chunk-J63YVAKA.js";
113
113
 
114
114
  // src/media.ts
115
115
  import { createWriteStream as createWriteStream2 } from "node:fs";
116
116
  import { randomUUID as randomUUID8 } from "node:crypto";
117
117
  import { mkdir as mkdir3, open as open4, rename as rename2, stat as stat2, unlink as unlink2 } from "node:fs/promises";
118
- import { basename, dirname as dirname3, extname, join as join8 } from "node:path";
118
+ import { basename, dirname as dirname3, extname, join as join7 } from "node:path";
119
119
  import { Readable, Transform as Transform2 } from "node:stream";
120
120
  import { pipeline as pipeline2 } from "node:stream/promises";
121
121
 
@@ -1876,9 +1876,6 @@ var DEFAULT_BASE_URL = DEFAULT_CANON_RUNTIME_ENDPOINTS.apiBaseUrl;
1876
1876
 
1877
1877
  // ../core/src/client.ts
1878
1878
  import { createHash as createHash6, randomUUID as randomUUID2 } from "node:crypto";
1879
- import { mkdtemp, writeFile, rm } from "node:fs/promises";
1880
- import { tmpdir } from "node:os";
1881
- import { join as join5 } from "node:path";
1882
1879
 
1883
1880
  // ../endpoint/src/store.ts
1884
1881
  function copyJson(value) {
@@ -2374,10 +2371,11 @@ function createEndpointQueryObserver(options) {
2374
2371
  const intervalMs = Math.max(250, options.intervalMs ?? 3e3);
2375
2372
  function schedule(entry) {
2376
2373
  if (closed || entry.listeners.size === 0) return;
2374
+ const delay = entry.retryAt === void 0 ? options.getPollInterval?.() ?? intervalMs : entry.retryAt - Date.now();
2377
2375
  entry.timer = setTimeout(() => {
2378
2376
  entry.timer = void 0;
2379
2377
  void refresh(entry);
2380
- }, Math.max(250, options.getPollInterval?.() ?? intervalMs));
2378
+ }, Math.max(250, delay));
2381
2379
  }
2382
2380
  __name(schedule, "schedule");
2383
2381
  async function refresh(entry) {
@@ -2388,11 +2386,17 @@ function createEndpointQueryObserver(options) {
2388
2386
  }
2389
2387
  if (entry.timer) clearTimeout(entry.timer);
2390
2388
  entry.timer = void 0;
2389
+ if (entry.retryAt !== void 0 && entry.retryAt > Date.now()) {
2390
+ schedule(entry);
2391
+ return;
2392
+ }
2393
+ delete entry.retryAt;
2391
2394
  entry.running = true;
2392
2395
  entry.dirty = false;
2393
2396
  try {
2394
2397
  const value = await options.query(entry.query, entry.parameters);
2395
2398
  if (closed || entry.listeners.size === 0) return;
2399
+ entry.retryAttempts = 0;
2396
2400
  if (!entry.dirty) {
2397
2401
  const fingerprint = canonicalEndpointJson(value);
2398
2402
  if (!entry.hasValue || entry.hadError || fingerprint !== entry.fingerprint) {
@@ -2412,6 +2416,13 @@ function createEndpointQueryObserver(options) {
2412
2416
  } catch (error) {
2413
2417
  entry.hadError = true;
2414
2418
  entry.lastError = error;
2419
+ if (error instanceof EndpointError && error.retryable) {
2420
+ const retryAfter = Number.isFinite(error.retryAfterMs) ? Math.max(0, Math.min(3e5, error.retryAfterMs)) : 0;
2421
+ const fastDelay = entry.retryAttempts < 3 ? 1e3 * 2 ** entry.retryAttempts++ : void 0;
2422
+ if (fastDelay !== void 0 || retryAfter > 0) {
2423
+ entry.retryAt = Date.now() + Math.max(retryAfter, fastDelay ?? options.getPollInterval?.() ?? intervalMs);
2424
+ }
2425
+ }
2415
2426
  if (!closed) for (const listener of entry.listeners) {
2416
2427
  try {
2417
2428
  listener.error?.(error);
@@ -2431,7 +2442,7 @@ function createEndpointQueryObserver(options) {
2431
2442
  const key = canonicalEndpointJson({ query, parameters });
2432
2443
  let entry = entries2.get(key);
2433
2444
  if (!entry) {
2434
- entry = { query, parameters, listeners: /* @__PURE__ */ new Set(), running: false, dirty: false, hasValue: false, hadError: false };
2445
+ entry = { query, parameters, listeners: /* @__PURE__ */ new Set(), running: false, dirty: false, hasValue: false, hadError: false, retryAttempts: 0 };
2435
2446
  entries2.set(key, entry);
2436
2447
  }
2437
2448
  const observer = { value: listener, error: onError };
@@ -6638,6 +6649,16 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
6638
6649
  const current = envelope(state.protocolState, init.binding, conversationId);
6639
6650
  const observed = authority(await tx.get(authorityKey(conversationId)), conversationId);
6640
6651
  const conversation = await tx.get(`conversation/${conversationId}`);
6652
+ const roomDependencies = conversation ? Object.fromEntries([
6653
+ "membershipRevision",
6654
+ "memberIds",
6655
+ "mls",
6656
+ "mlsRequired",
6657
+ "mlsProtocol",
6658
+ "mlsRemoval",
6659
+ "mlsIdentities",
6660
+ "mlsInviteeId"
6661
+ ].map((key) => [key, conversation[key] ?? null])) : null;
6641
6662
  const cursor = await tx.get(`cursor/${conversationId}`);
6642
6663
  const protocol = await tx.get(mlsDevTextProtocolKey(conversationId));
6643
6664
  if (protocol !== void 0 && !isMlsDevTextProtocol(protocol) || current && current.protocol !== protocol) fail6("MLS_DEV_TEXT_PROTOCOL_UNSUPPORTED");
@@ -6647,7 +6668,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
6647
6668
  return { device, envelope: current, authority: observed, feedOrdinal, ...protocol ? { protocol } : {}, fingerprint: canonicalEndpointJson({
6648
6669
  device: device.fingerprint,
6649
6670
  authority: observed ?? null,
6650
- conversation: conversation ?? null,
6671
+ conversation: roomDependencies,
6651
6672
  cursor: cursor ?? null,
6652
6673
  protocol: protocol ?? null,
6653
6674
  feedOrdinal
@@ -10011,7 +10032,7 @@ async function acquireAgentEndpoint(input) {
10011
10032
  }
10012
10033
  const requestedMls = input.options.mlsDev;
10013
10034
  const mls = requestedMls ? await (async () => {
10014
- const crypto2 = await import("./mls-crypto-LMNQZOS6.js");
10035
+ const crypto2 = await import("./mls-crypto-ITVGUEP7.js");
10015
10036
  const configuration = crypto2.parseMlsDevClientConfiguration(requestedMls.configuration);
10016
10037
  if (configuration.environmentId !== environmentId || baseUrl !== CANON_RUNTIME_ENDPOINT_DEFAULTS[configuration.environmentId]?.apiBaseUrl || configuration.principalIds && !configuration.principalIds.includes(input.principalId) || !Number.isSafeInteger(input.ownershipVersion) || input.ownershipVersion < 0) throw new Error("MLS_DEV_AGENT_CONFIGURATION_BINDING_MISMATCH");
10017
10038
  const custody = requestedMls.custody;
@@ -10312,45 +10333,6 @@ var CanonClient = class {
10312
10333
  }
10313
10334
  return result;
10314
10335
  }
10315
- /**
10316
- * List conversations. Called with no options this is the historical request —
10317
- * no query string, every conversation the agent belongs to, most recent
10318
- * first. Passing `limit`/`before` opts into paging; use
10319
- * `getConversationsPage` when you need the cursor back.
10320
- */
10321
- async getConversations(options) {
10322
- if (options) return (await this.fetchConversationsPage(options)).conversations;
10323
- const conversations = [];
10324
- const seen = /* @__PURE__ */ new Set();
10325
- let before;
10326
- do {
10327
- const page = await this.fetchConversationsPage({ limit: 100, ...before ? { before } : {} });
10328
- conversations.push(...page.conversations);
10329
- if (page.nextBefore && seen.has(page.nextBefore)) throw new Error("Canon conversation cursor did not advance.");
10330
- before = page.nextBefore ?? void 0;
10331
- if (before) seen.add(before);
10332
- } while (before);
10333
- return conversations;
10334
- }
10335
- /**
10336
- * One page of conversations plus the cursor to continue from. Pages are cut
10337
- * in conversation-id order; keep calling with `before: page.nextBefore` until
10338
- * `nextBefore` is null. Do not stop on an empty page and do not derive the
10339
- * cursor from the returned array — the array is recency-sorted within the
10340
- * page and hidden conversations are filtered after the page is read.
10341
- */
10342
- async getConversationsPage(options) {
10343
- return this.fetchConversationsPage(options);
10344
- }
10345
- async fetchConversationsPage(options) {
10346
- const data = await this.endpointQuery("list_conversations", { ...options });
10347
- return {
10348
- conversations: data.conversations,
10349
- // Absent on the unpaginated shape, and on any Canon deployment that
10350
- // predates conversation paging — both mean "no further pages".
10351
- nextBefore: typeof data.nextBefore === "string" ? data.nextBefore : null
10352
- };
10353
- }
10354
10336
  /** Targeted membership read; session hosts must not poll the entire account. */
10355
10337
  async getConversation(conversationId) {
10356
10338
  return await (await this.getEndpoint()).getConversation(conversationId);
@@ -10363,15 +10345,6 @@ var CanonClient = class {
10363
10345
  const data = await this.endpointQuery("list_messages", { conversationId, limit, includeBehavior: true, ...before ? { before } : {} });
10364
10346
  return { ...data, messages: data.messages.map(normalizeMessage) };
10365
10347
  }
10366
- /** Read one explicitly referenced message without extending ordinary history. */
10367
- async getMessage(conversationId, messageId) {
10368
- const data = await this.endpointQuery("get_messages", {
10369
- conversationId,
10370
- messageIds: [messageId]
10371
- });
10372
- const message = data.messages.find((candidate) => candidate.id === messageId);
10373
- return message ? normalizeMessage(message) : null;
10374
- }
10375
10348
  async sendMessage(conversationId, text, options, delivery) {
10376
10349
  return this.endpointAction(
10377
10350
  "send_message",
@@ -10394,78 +10367,27 @@ var CanonClient = class {
10394
10367
  }
10395
10368
  /** Core's chunker records all ordered message intents before publishing any part. */
10396
10369
  async prepareMessageGroup(conversationId, messageId, parts, delivery) {
10397
- const id2 = delivery?.operationId ?? stableOperationId(delivery?.proactive ? "send_proactive_group" : "send_group", conversationId, messageId);
10370
+ const id2 = delivery?.operationId ?? stableOperationId("send_group", conversationId, messageId);
10398
10371
  try {
10399
10372
  const endpoint = await this.getEndpoint();
10400
10373
  const group = await endpoint.registerMessageGroup({ id: id2, ...delivery?.replacesOperationId ? { replacesOperationId: delivery.replacesOperationId } : {}, children: parts.map((part, index) => ({
10401
- operationId: delivery?.operationId ? `${delivery.operationId.slice(0, 160 - `-part-${index + 1}`.length)}-part-${index + 1}` : delivery?.proactive ? stableOperationId("verb_execute", "send_to", conversationId, part.options.messageId) : stableOperationId("send_message", conversationId, part.options.messageId),
10402
- intent: delivery?.proactive ? { action: "verb_execute", body: { ...projectVerbIntentToWire("send_to", { targetConversationId: conversationId, text: part.text, messageOptions: part.options }, { now: Date.now() }) } } : { action: "send_message", body: { conversationId, text: part.text, ...part.options } }
10374
+ operationId: delivery?.operationId ? `${delivery.operationId.slice(0, 160 - `-part-${index + 1}`.length)}-part-${index + 1}` : stableOperationId("send_message", conversationId, part.options.messageId),
10375
+ intent: { action: "send_message", body: { conversationId, text: part.text, ...part.options } }
10403
10376
  })) }, { signal: delivery?.signal });
10404
10377
  return { id: id2, sendPart: /* @__PURE__ */ __name(async (index, options) => {
10405
10378
  const operationId = group.childOperationIds?.[index];
10406
10379
  if (!operationId) throw new Error("Unknown message group part");
10407
10380
  if (options?.signal?.aborted) throw new CanonApiError(409, JSON.stringify({ code: "OPERATION_PENDING", message: "Reply delivery was interrupted" }));
10408
10381
  await endpoint.retry(operationId);
10409
- return delivery?.proactive ? this.waitForProactiveMessage(operationId, options) : this.waitForOperation(operationId, options);
10382
+ return this.waitForOperation(operationId, options);
10410
10383
  }, "sendPart") };
10411
10384
  } catch (error) {
10412
10385
  throw asCanonApiError(error);
10413
10386
  }
10414
10387
  }
10415
- /** Publish a native-formatted ordered plan through the same endpoint engine. */
10416
- async sendMessageGroup(conversationId, messageId, parts, delivery) {
10417
- const root = messageId ?? randomUUID2();
10418
- const prepared = parts.map((part, index) => ({ ...part, options: {
10419
- ...part.options,
10420
- messageId: part.options.messageId ?? `${root.slice(0, 160 - `-part-${index + 1}`.length)}-part-${index + 1}`
10421
- } }));
10422
- const group = await this.prepareMessageGroup(conversationId, root, prepared, delivery);
10423
- try {
10424
- for (let index = 0; index < prepared.length; index++) await group.sendPart(index);
10425
- return await this.waitForOperation(group.id);
10426
- } catch (error) {
10427
- if (error instanceof CanonApiError) error.operationId = group.id;
10428
- throw error;
10429
- }
10430
- }
10431
10388
  async cancelMessageGroup(id2) {
10432
10389
  await (await this.getEndpoint()).cancelMessageGroup(id2);
10433
10390
  }
10434
- /**
10435
- * Send outside an active inbound turn through the canonical policy boundary.
10436
- * Harness reply paths should keep using sendMessage; schedulers, notifications,
10437
- * and other detached work must use this method.
10438
- */
10439
- async sendProactiveMessage(conversationId, text, options, delivery) {
10440
- const result = await this.sendTo({
10441
- targetConversationId: conversationId,
10442
- text,
10443
- ...options ? { messageOptions: options } : {}
10444
- }, delivery);
10445
- if (result.status !== "messaged" || !result.messageId) {
10446
- const reason = "reason" in result ? result.reason : result.status;
10447
- throw new CanonApiError(
10448
- 403,
10449
- `Canon proactive send was not admitted: ${reason}`
10450
- );
10451
- }
10452
- return {
10453
- conversationId: result.conversationId,
10454
- messageId: result.messageId
10455
- };
10456
- }
10457
- /** Reconcile an existing publication without rebuilding or resubmitting its body. */
10458
- async waitForProactiveMessage(operationId, options) {
10459
- if (await (await this.getEndpoint()).getMessageGroup(operationId)) return this.waitForOperation(operationId, options);
10460
- const response = await this.waitForOperation(operationId, options);
10461
- const value = unwrapJsonVerbResponse(response, "send_to");
10462
- if (!isSendToResult(value)) throw new CanonApiError(502, "Canon returned a malformed send_to result");
10463
- if (value.status !== "messaged" || !value.messageId) {
10464
- const reason = "reason" in value ? value.reason : value.status;
10465
- throw new CanonApiError(403, `Canon proactive send was not admitted: ${reason}`);
10466
- }
10467
- return { conversationId: value.conversationId, messageId: value.messageId };
10468
- }
10469
10391
  /**
10470
10392
  * Execute a canonical verb over the wire contract (canon.verb-wire.v1).
10471
10393
  * Bindings build the request with projectVerbIntentToWire; the endpoint
@@ -10703,98 +10625,9 @@ var CanonClient = class {
10703
10625
  }
10704
10626
  return result.status === "unavailable" && result.reason === "opening-text-required" ? void 0 : result;
10705
10627
  }
10706
- /** Create a group. Direct conversations must go through `communicate`. */
10707
- async createGroup(options) {
10708
- return this.endpointAction("create_conversation", { ...options });
10709
- }
10710
- async createMemoStream(options) {
10711
- return this.endpointPublish("create_memo_stream", { streamId: randomUUID2(), ...options });
10712
- }
10713
- async appendMemoStreamChunk(conversationId, streamId, options) {
10714
- return this.endpointPublish("append_memo_chunk", { conversationId, streamId, ...options });
10715
- }
10716
- async finalizeMemoStream(conversationId, streamId, messageId) {
10717
- await this.endpointAction("finalize_memo_stream", { conversationId, streamId, messageId });
10718
- }
10719
- async abortMemoStream(conversationId, streamId) {
10720
- await this.endpointAction("abort_memo_stream", { conversationId, streamId });
10721
- }
10722
- async createVoiceSession(options) {
10723
- return this.endpointAction("create_voice_session", { ...options });
10724
- }
10725
- async getVoiceSessionState(conversationId, sessionId) {
10726
- return this.endpointQuery("get_voice_session", { conversationId, sessionId });
10727
- }
10728
- async declineVoiceSession(conversationId, sessionId) {
10729
- await this.endpointAction("decline_voice_session", { conversationId, sessionId });
10730
- }
10731
- async joinVoiceSession(conversationId, sessionId, options) {
10732
- return this.endpointAction("join_voice_session", { conversationId, sessionId, ...options ?? {} });
10733
- }
10734
- async endVoiceSession(conversationId, sessionId) {
10735
- await this.endpointAction("end_voice_session", { conversationId, sessionId });
10736
- }
10737
- async listContacts() {
10738
- const data = await this.endpointQuery("list_contacts");
10739
- return data.contacts;
10740
- }
10741
- async getContact(contactId) {
10742
- try {
10743
- return await this.endpointQuery("get_contact", { contactId });
10744
- } catch (error) {
10745
- if (error instanceof CanonApiError && error.status === 404) return null;
10746
- throw error;
10747
- }
10748
- }
10749
- async deleteContact(contactId) {
10750
- await this.endpointAction("remove_contact", { contactId });
10751
- }
10752
10628
  async blockUser(userId) {
10753
10629
  await this.endpointAction("set_block", { userId, blocked: true });
10754
10630
  }
10755
- async unblockUser(userId) {
10756
- await this.endpointAction("set_block", { userId, blocked: false });
10757
- }
10758
- async resolveAdmission(target) {
10759
- const body = typeof target === "string" ? { targetUserId: target } : target;
10760
- return this.endpointQuery("resolve_admission", { ...body });
10761
- }
10762
- async uploadMedia(conversationId, data, mimeType, fileName, options) {
10763
- const bytes = Buffer.from(data, "base64");
10764
- if (!bytes.length || bytes.length > 100 * 1024 * 1024) throw new CanonApiError(400, "Canon media must contain from 1 byte to 100 MiB");
10765
- const directory = await mkdtemp(join5(tmpdir(), "canon-upload-"));
10766
- const source = join5(directory, "bytes");
10767
- try {
10768
- await writeFile(source, bytes, { mode: 384 });
10769
- const media = await this.getFileMediaPipeline();
10770
- const [attachment] = await media.prepare({
10771
- conversationId,
10772
- attachmentId: options?.attachmentId ?? randomUUID2(),
10773
- files: [{ source, descriptor: { mimeType, sizeBytes: bytes.length, ...fileName ? { fileName } : {} } }]
10774
- });
10775
- if (typeof attachment?.url !== "string") throw new CanonApiError(502, "Canon returned an invalid prepared attachment");
10776
- return { url: attachment.url, attachment };
10777
- } catch (error) {
10778
- throw asCanonApiError(error);
10779
- } finally {
10780
- await rm(directory, { recursive: true, force: true });
10781
- }
10782
- }
10783
- /**
10784
- * Start a private direct-to-Storage upload. The returned upload URL is a
10785
- * short-lived bearer capability; send exactly `sizeBytes` raw bytes to it,
10786
- * then call `finalizeResumableMediaUpload`.
10787
- */
10788
- async createResumableMediaUpload(conversationId, sizeBytes, mimeType, fileName) {
10789
- return this.endpointAction("create_upload", { conversationId, sizeBytes, mimeType, ...fileName ? { fileName } : {} });
10790
- }
10791
- /** Promote validated pending bytes into a canonical Canon attachment. */
10792
- async finalizeResumableMediaUpload(uploadId) {
10793
- return this.endpointAction("finalize_upload", { uploadId });
10794
- }
10795
- async updateTopic(conversationId, topic) {
10796
- await this.endpointAction("update_conversation", { conversationId, topic });
10797
- }
10798
10631
  async deleteMessage(conversationId, messageId) {
10799
10632
  await this.endpointAction("delete_message", { conversationId, messageId });
10800
10633
  }
@@ -10804,25 +10637,9 @@ var CanonClient = class {
10804
10637
  async markAsRead(conversationId) {
10805
10638
  await this.endpointAction("mark_read", { conversationId });
10806
10639
  }
10807
- async leaveConversation(conversationId) {
10808
- await this.endpointAction("leave_conversation", { conversationId });
10809
- }
10810
10640
  async react(conversationId, messageId, emoji) {
10811
10641
  await this.endpointAction("react", { conversationId, messageId, emoji });
10812
10642
  }
10813
- async updateConversationName(conversationId, name) {
10814
- await this.endpointAction("update_conversation", { conversationId, name });
10815
- }
10816
- async addMember(conversationId, userId) {
10817
- const result = await this.endpointAction("add_member", { conversationId, userId });
10818
- if (!result || typeof result !== "object" || !("status" in result) || result.status !== "added" && !(result.status === "pending" && "requestId" in result && typeof result.requestId === "string" && result.requestId)) {
10819
- throw new CanonApiError(502, "Canon returned a malformed add_member result");
10820
- }
10821
- return result;
10822
- }
10823
- async removeMember(conversationId, userId) {
10824
- await this.endpointAction("remove_member", { conversationId, userId });
10825
- }
10826
10643
  async setStreaming(options) {
10827
10644
  await this.endpointPublish("publish_streaming", { ...options });
10828
10645
  }
@@ -11256,7 +11073,7 @@ __name(resumeRegistration, "resumeRegistration");
11256
11073
  import { existsSync, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync } from "node:fs";
11257
11074
  import { closeSync as closeSync2, openSync as openSync2 } from "node:fs";
11258
11075
  import { randomUUID as randomUUID5 } from "node:crypto";
11259
- import { join as join6, resolve as resolve4 } from "node:path";
11076
+ import { join as join5, resolve as resolve4 } from "node:path";
11260
11077
  import { homedir as homedir2 } from "node:os";
11261
11078
 
11262
11079
  // ../core/src/process-lock.ts
@@ -11297,15 +11114,15 @@ function claimStaleLockReaper(lockPath) {
11297
11114
  __name(claimStaleLockReaper, "claimStaleLockReaper");
11298
11115
 
11299
11116
  // ../core/src/agent-profiles.ts
11300
- var CANON_DIR = process.env.CANON_HOME ? resolve4(process.env.CANON_HOME) : join6(homedir2(), ".canon");
11301
- var AGENTS_PATH = join6(CANON_DIR, "agents.json");
11302
- var LOCKS_DIR = join6(CANON_DIR, "locks");
11303
- var PENDING_REGISTRATIONS_PATH = join6(CANON_DIR, "pending-registrations.json");
11117
+ var CANON_DIR = process.env.CANON_HOME ? resolve4(process.env.CANON_HOME) : join5(homedir2(), ".canon");
11118
+ var AGENTS_PATH = join5(CANON_DIR, "agents.json");
11119
+ var LOCKS_DIR = join5(CANON_DIR, "locks");
11120
+ var PENDING_REGISTRATIONS_PATH = join5(CANON_DIR, "pending-registrations.json");
11304
11121
  var AGENTS_JSON_BOOTSTRAP_ENV = "CANON_AGENTS_JSON_BOOTSTRAP";
11305
11122
  function canonPaths(home) {
11306
11123
  if (!home) return { dir: CANON_DIR, agents: AGENTS_PATH, locks: LOCKS_DIR };
11307
11124
  const dir = resolve4(home);
11308
- return { dir, agents: join6(dir, "agents.json"), locks: join6(dir, "locks") };
11125
+ return { dir, agents: join5(dir, "agents.json"), locks: join5(dir, "locks") };
11309
11126
  }
11310
11127
  __name(canonPaths, "canonPaths");
11311
11128
  function normalizeAgentProfile(name, value, source) {
@@ -11452,12 +11269,31 @@ function isProcessAlive(pid) {
11452
11269
  }
11453
11270
  }
11454
11271
  __name(isProcessAlive, "isProcessAlive");
11272
+ function processIdentity(pid) {
11273
+ if (process.platform !== "linux") return void 0;
11274
+ try {
11275
+ const bootId = readFileSync2("/proc/sys/kernel/random/boot_id", "utf8").trim();
11276
+ const stat3 = readFileSync2(`/proc/${pid}/stat`, "utf8");
11277
+ const startTicks = stat3.slice(stat3.lastIndexOf(")") + 2).split(/\s+/)[19];
11278
+ if (!/^[a-f0-9-]{36}$/.test(bootId) || !startTicks || !/^\d+$/.test(startTicks)) return void 0;
11279
+ return { bootId, startTicks };
11280
+ } catch {
11281
+ return void 0;
11282
+ }
11283
+ }
11284
+ __name(processIdentity, "processIdentity");
11285
+ function sameProcess(owner) {
11286
+ if (!owner.processIdentity) return true;
11287
+ const current = processIdentity(owner.pid);
11288
+ return !current || current.bootId === owner.processIdentity.bootId && current.startTicks === owner.processIdentity.startTicks;
11289
+ }
11290
+ __name(sameProcess, "sameProcess");
11455
11291
  function isProfileLocked(profile, home) {
11456
- const lockPath = join6(canonPaths(home).locks, `${profile}.lock`);
11292
+ const lockPath = join5(canonPaths(home).locks, `${profile}.lock`);
11457
11293
  try {
11458
11294
  const lock = parseLock(readFileSync2(lockPath, "utf-8"));
11459
11295
  const pid = lock?.pid;
11460
- if (pid && isProcessAlive(pid)) return { locked: true, pid };
11296
+ if (pid && isProcessAlive(pid) && sameProcess(lock)) return { locked: true, pid };
11461
11297
  } catch {
11462
11298
  }
11463
11299
  return { locked: false };
@@ -11468,7 +11304,8 @@ function parseLock(raw) {
11468
11304
  if (!trimmed) return null;
11469
11305
  try {
11470
11306
  const data = JSON.parse(trimmed);
11471
- return typeof data.pid === "number" ? { pid: data.pid, token: typeof data.token === "string" ? data.token : void 0 } : null;
11307
+ if (data.processIdentity !== void 0 && (!data.processIdentity || typeof data.processIdentity.bootId !== "string" || !/^[a-f0-9-]{36}$/.test(data.processIdentity.bootId) || typeof data.processIdentity.startTicks !== "string" || !/^\d+$/.test(data.processIdentity.startTicks))) return null;
11308
+ return typeof data.pid === "number" ? { pid: data.pid, token: typeof data.token === "string" ? data.token : void 0, processIdentity: data.processIdentity } : null;
11472
11309
  } catch {
11473
11310
  return null;
11474
11311
  }
@@ -11477,12 +11314,13 @@ __name(parseLock, "parseLock");
11477
11314
  function acquireLock(profile, home) {
11478
11315
  const locks = canonPaths(home).locks;
11479
11316
  mkdirSync(locks, { recursive: true });
11480
- const lockPath = join6(locks, `${profile}.lock`);
11317
+ const lockPath = join5(locks, `${profile}.lock`);
11481
11318
  const token = randomUUID5();
11482
11319
  const payload = JSON.stringify({
11483
11320
  pid: process.pid,
11484
11321
  token,
11485
- acquiredAt: (/* @__PURE__ */ new Date()).toISOString()
11322
+ acquiredAt: (/* @__PURE__ */ new Date()).toISOString(),
11323
+ processIdentity: processIdentity(process.pid)
11486
11324
  });
11487
11325
  for (let attempt = 0; attempt < 2; attempt += 1) {
11488
11326
  try {
@@ -11514,7 +11352,7 @@ function acquireLock(profile, home) {
11514
11352
  }
11515
11353
  try {
11516
11354
  process.kill(owner.pid, 0);
11517
- throw new Error(`Agent "${profile}" is in use by another session (PID ${owner.pid})`);
11355
+ if (sameProcess(owner)) throw new Error(`Agent "${profile}" is in use by another session (PID ${owner.pid})`);
11518
11356
  } catch (probeError) {
11519
11357
  if (probeError.code !== "ESRCH") throw probeError;
11520
11358
  }
@@ -11528,7 +11366,7 @@ function acquireLock(profile, home) {
11528
11366
  }
11529
11367
  __name(acquireLock, "acquireLock");
11530
11368
  function releaseLock(profile, token, home) {
11531
- const lockPath = join6(canonPaths(home).locks, `${profile}.lock`);
11369
+ const lockPath = join5(canonPaths(home).locks, `${profile}.lock`);
11532
11370
  if (token) {
11533
11371
  try {
11534
11372
  const lock = parseLock(readFileSync2(lockPath, "utf-8"));
@@ -11548,13 +11386,6 @@ function normalizePendingRegistration(name, value) {
11548
11386
  throw new Error(`${PENDING_REGISTRATIONS_PATH} entry "${name}" must be an object`);
11549
11387
  }
11550
11388
  const pending = { ...value };
11551
- if (typeof pending.localRegistrationId === "string" && pending.localRegistrationId.startsWith("hermes-")) {
11552
- const now = (/* @__PURE__ */ new Date()).toISOString();
11553
- pending.profile ??= name;
11554
- pending.clientType ??= "hermes";
11555
- pending.createdAt ??= typeof pending.submittedAt === "string" ? pending.submittedAt : now;
11556
- pending.updatedAt ??= pending.createdAt;
11557
- }
11558
11389
  const requiredStrings = [
11559
11390
  "profile",
11560
11391
  "localRegistrationId",
@@ -12695,7 +12526,7 @@ var DEFAULT_TURN_VERBOSITY_BY_CONVERSATION_TYPE = {
12695
12526
  };
12696
12527
  function normalizeTurnVerbosityConversationType(value) {
12697
12528
  const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
12698
- if (normalized === "direct" || normalized === "dm") return "direct";
12529
+ if (normalized === "direct") return "direct";
12699
12530
  if (normalized === "group") return "group";
12700
12531
  return "unknown";
12701
12532
  }
@@ -13519,11 +13350,11 @@ import {
13519
13350
  unlinkSync as unlinkSync3,
13520
13351
  writeFileSync as writeFileSync3
13521
13352
  } from "node:fs";
13522
- import { join as join7 } from "node:path";
13353
+ import { join as join6 } from "node:path";
13523
13354
  function createCompletedOutputJournal(directory, scope5, options) {
13524
13355
  const label = options.label;
13525
- const folder = join7(directory, createHash7("sha256").update(JSON.stringify(scope5)).digest("hex"));
13526
- const filename = /* @__PURE__ */ __name((turnId) => join7(folder, `${createHash7("sha256").update(turnId).digest("hex")}.json`), "filename");
13356
+ const folder = join6(directory, createHash7("sha256").update(JSON.stringify(scope5)).digest("hex"));
13357
+ const filename = /* @__PURE__ */ __name((turnId) => join6(folder, `${createHash7("sha256").update(turnId).digest("hex")}.json`), "filename");
13527
13358
  function syncDirectory() {
13528
13359
  const fd = openSync3(folder, "r");
13529
13360
  try {
@@ -13549,7 +13380,7 @@ function createCompletedOutputJournal(directory, scope5, options) {
13549
13380
  } catch (error) {
13550
13381
  if (error.code !== "ESRCH") continue;
13551
13382
  }
13552
- const path = join7(folder, name);
13383
+ const path = join6(folder, name);
13553
13384
  let record7;
13554
13385
  try {
13555
13386
  record7 = JSON.parse(readFileSync4(path, "utf8"));
@@ -13559,7 +13390,7 @@ function createCompletedOutputJournal(directory, scope5, options) {
13559
13390
  throw new Error(`Incomplete ${label} output write removed; reconcile its native transcript`);
13560
13391
  }
13561
13392
  const destination = filename(record7.turnId);
13562
- if (destination !== join7(folder, `${match[1]}.json`)) {
13393
+ if (destination !== join6(folder, `${match[1]}.json`)) {
13563
13394
  throw new Error(`Completed ${label} output temporary file identity mismatch`);
13564
13395
  }
13565
13396
  if (existsSync2(destination)) {
@@ -13605,9 +13436,9 @@ function createCompletedOutputJournal(directory, scope5, options) {
13605
13436
  if (!existsSync2(folder)) return [];
13606
13437
  recoverTemporaryFiles();
13607
13438
  return readdirSync(folder).filter((name) => name.endsWith(".json")).map((name) => {
13608
- const record7 = JSON.parse(readFileSync4(join7(folder, name), "utf8"));
13439
+ const record7 = JSON.parse(readFileSync4(join6(folder, name), "utf8"));
13609
13440
  validate(record7);
13610
- if (filename(record7.turnId) !== join7(folder, name)) throw new Error(`Completed ${label} output identity mismatch`);
13441
+ if (filename(record7.turnId) !== join6(folder, name)) throw new Error(`Completed ${label} output identity mismatch`);
13611
13442
  return record7;
13612
13443
  }).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
13613
13444
  },
@@ -13672,7 +13503,7 @@ var ANTHROPIC_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
13672
13503
  "image/gif",
13673
13504
  "image/webp"
13674
13505
  ]);
13675
- var DEFAULT_MEDIA_CACHE_DIR = join8(CANON_DIR, "media-cache");
13506
+ var DEFAULT_MEDIA_CACHE_DIR = join7(CANON_DIR, "media-cache");
13676
13507
  var DEFAULT_MEDIA_MATERIALIZATION_BYTES = 10 * 1024 * 1024;
13677
13508
  var MAX_CANON_MEDIA_BYTES = 100 * 1024 * 1024;
13678
13509
  var MAX_ANTHROPIC_IMAGE_RAW_BYTES = 7 * 1024 * 1024;
@@ -13740,7 +13571,7 @@ function inferExtension(input) {
13740
13571
  }
13741
13572
  __name(inferExtension, "inferExtension");
13742
13573
  function conversationMediaCacheDir(input) {
13743
- return join8(
13574
+ return join7(
13744
13575
  input.rootDir ?? DEFAULT_MEDIA_CACHE_DIR,
13745
13576
  sanitizeSegment(input.agentId, "agent"),
13746
13577
  sanitizeSegment(input.conversationId, "conversation")
@@ -13756,7 +13587,7 @@ function buildCachePath(input) {
13756
13587
  const fallbackName = `${input.attachment.kind}-${input.index}${extension}`;
13757
13588
  const preferredName = input.attachment.fileName ? sanitizeDownloadFileName(input.attachment.fileName, fallbackName) : fallbackName;
13758
13589
  const fileName = extname(preferredName) ? preferredName : `${preferredName}${extension}`;
13759
- return join8(
13590
+ return join7(
13760
13591
  conversationMediaCacheDir({ agentId: input.agentId, conversationId: input.conversationId, rootDir }),
13761
13592
  sanitizeSegment(input.messageId, "message"),
13762
13593
  `${String(input.index).padStart(2, "0")}-${fileName}`
@@ -366,16 +366,12 @@ function installation(value) {
366
366
  return value;
367
367
  }
368
368
  __name(installation, "installation");
369
+ function isMlsPrincipalId(value) {
370
+ return typeof value === "string" && value.length > 0 && value.length <= 512 && !/[\u0000-\u001f\u007f-\u009f]/.test(value) && !/[\ud800-\udfff]/.test(value.replace(/[\ud800-\udbff][\udc00-\udfff]/g, "")) && utf8.encode(value).length <= 512;
371
+ }
372
+ __name(isMlsPrincipalId, "isMlsPrincipalId");
369
373
  function principalId(value) {
370
- if (typeof value !== "string" || value.length < 1 || value.length > 512 || /[\u0000-\u001f\u007f-\u009f]/.test(value)) invalid2();
371
- for (let index = 0; index < value.length; index++) {
372
- const unit = value.charCodeAt(index);
373
- if (unit >= 55296 && unit <= 56319) {
374
- const next = value.charCodeAt(++index);
375
- if (!(next >= 56320 && next <= 57343)) invalid2();
376
- } else if (unit >= 56320 && unit <= 57343) invalid2();
377
- }
378
- if (utf8.encode(value).length > 512) invalid2();
374
+ if (!isMlsPrincipalId(value)) invalid2();
379
375
  return value;
380
376
  }
381
377
  __name(principalId, "principalId");
@@ -2324,9 +2320,7 @@ var VERB_LIMITS = {
2324
2320
  externalSourceLabelChars: 80,
2325
2321
  /**
2326
2322
  * Contact-request note. Values longer than this are truncated by senders
2327
- * (slice(0,497)+'...') — the truncation is currently triplicated in
2328
- * functions/src/api/sendContextualMessage.ts, packages/core/src/reach-out.ts
2329
- * and the hermes plugin; this constant is the canonical figure.
2323
+ * (slice(0,497)+'...'); this constant is the canonical figure.
2330
2324
  */
2331
2325
  contactRequestNoteChars: 500,
2332
2326
  /** Group membership cap incl. creator (functions/src/utils/conversations.ts MAX_GROUP_MEMBERS). */
@@ -2747,7 +2741,7 @@ var send_to_input = {
2747
2741
  ]
2748
2742
  };
2749
2743
  var send_to_result = {
2750
- description: "Canonical outcome vocabulary. Legacy binding drift documented in docs/design/verb-bindings.md (hermes 'opened' -> messaged without messageId; hermes 'denied' -> blocked).",
2744
+ description: "Canonical outcome vocabulary.",
2751
2745
  oneOf: [
2752
2746
  {
2753
2747
  type: "object",
@@ -2832,7 +2826,7 @@ var request_input_input = {
2832
2826
  responseUserId: {
2833
2827
  type: "string",
2834
2828
  pattern: VERB_ID_PATTERNS.runtimeId,
2835
- description: "Must be a human conversation member. Trust-critical bindings (hermes) derive this from turn provenance and ignore model-supplied values."
2829
+ description: "Must be a human conversation member. Trust-critical bindings derive this from turn provenance and ignore model-supplied values."
2836
2830
  },
2837
2831
  native: REF("native"),
2838
2832
  turnId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
@@ -5148,9 +5142,12 @@ function isMlsPrincipalAlias(value) {
5148
5142
  return typeof value === "string" && ALIAS_PATTERN.test(value);
5149
5143
  }
5150
5144
  __name(isMlsPrincipalAlias, "isMlsPrincipalAlias");
5145
+ function isMlsAccountId(value) {
5146
+ return isMlsPrincipalId(value) && !value.includes("/") && value !== "." && value !== "..";
5147
+ }
5148
+ __name(isMlsAccountId, "isMlsAccountId");
5151
5149
  function accountId(value) {
5152
- if (typeof value !== "string" || value.length < 1 || /[\u0000-\u001f\u007f-\u009f/]/.test(value) || value === "." || value === ".." || utf89.encode(value).length > 512 || /[\ud800-\udfff]/.test(value.replace(/[\ud800-\udbff][\udc00-\udfff]/g, ""))) invalid15();
5153
- if (ALIAS_PATTERN.test(value)) invalid15();
5150
+ if (!isMlsAccountId(value) || ALIAS_PATTERN.test(value)) invalid15();
5154
5151
  return value;
5155
5152
  }
5156
5153
  __name(accountId, "accountId");