@canonmsg/agent-sdk 13.2.1 → 13.3.0

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.
@@ -1,22 +1,6 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
- // ../backend-contracts/src/clientFloor.ts
5
- var CANON_CLIENT_HEADER = "X-Canon-Client";
6
- var CLIENT_UPDATE_REQUIRED = "CLIENT_UPDATE_REQUIRED";
7
- var CANON_CLIENT_KINDS = ["app-ios", "app-android", "web", "runner", "hermes", "core", "connector"];
8
- var VERSION = /^(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})$/;
9
- var isKind = /* @__PURE__ */ __name((value) => CANON_CLIENT_KINDS.includes(value), "isKind");
10
- function isCanonClientVersion(value) {
11
- return typeof value === "string" && VERSION.test(value);
12
- }
13
- __name(isCanonClientVersion, "isCanonClientVersion");
14
- function formatCanonClientHeader(client) {
15
- if (!isKind(client.kind) || !isCanonClientVersion(client.version)) throw new Error("Invalid Canon client identity");
16
- return `${client.kind}/${client.version}`;
17
- }
18
- __name(formatCanonClientHeader, "formatCanonClientHeader");
19
-
20
4
  // ../backend-contracts/src/media.ts
21
5
  function sanitizeDownloadFileName(value, fallback = "attachment") {
22
6
  const baseName = typeof value === "string" ? value.trim().split(/[\\/]/).filter(Boolean).pop() ?? "" : "";
@@ -726,6 +710,7 @@ __name(parseMlsDevGroupFeedResult, "parseMlsDevGroupFeedResult");
726
710
  // ../backend-contracts/src/mlsDevKeyPackages.ts
727
711
  var MLS_DEV_KEY_PACKAGES_MAX_BYTES = 32768;
728
712
  var MLS_DEV_KEY_PACKAGE_TLS_MAX_BYTES = 16384;
713
+ var MLS_DEV_KEY_PACKAGE_CLAIM_LEASE_MS = 2 * 6e4;
729
714
  var MlsDevKeyPackagesValidationError = class extends Error {
730
715
  static {
731
716
  __name(this, "MlsDevKeyPackagesValidationError");
@@ -2097,1761 +2082,1089 @@ function isMlsDevReceiptClockSkew(value) {
2097
2082
  }
2098
2083
  __name(isMlsDevReceiptClockSkew, "isMlsDevReceiptClockSkew");
2099
2084
 
2100
- // ../backend-contracts/src/mlsDevCredentialReconciliation.ts
2101
- var MLS_DEV_CREDENTIAL_RECONCILIATION_CHALLENGE_TTL_MS = 3e5;
2102
- var MLS_DEV_CREDENTIAL_RECONCILIATION_MAX_BYTES = 8192;
2103
- var MlsDevCredentialReconciliationValidationError = class extends Error {
2104
- static {
2105
- __name(this, "MlsDevCredentialReconciliationValidationError");
2085
+ // ../backend-contracts/src/verbContract.ts
2086
+ var CANON_VERBS_SCHEMA_ID = "https://canonmsg.com/schemas/canon.verbs.v1.json";
2087
+ var VERB_ID_PATTERNS = {
2088
+ runtimeId: "^[A-Za-z0-9_.:-]{1,160}$",
2089
+ cardId: "^[A-Za-z0-9_.:-]{1,80}$",
2090
+ actionId: "^[A-Za-z0-9_.:-]{1,80}$",
2091
+ questionId: "^[A-Za-z0-9_.:-]{1,120}$",
2092
+ sessionRuleToolPattern: "^[\\w.*:-]{1,128}$",
2093
+ runtimeCorrelationValue: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$",
2094
+ runtimeMethod: "^[A-Za-z0-9_][A-Za-z0-9_./:\\-]{0,255}$",
2095
+ runtimeHandleKey: "^[A-Za-z0-9_.:-]{1,80}$",
2096
+ /** UUID issued by Canon's resumable-media session endpoint. */
2097
+ resumableUploadId: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
2098
+ };
2099
+ var VERB_LIMITS = {
2100
+ /** Message text — UTF-8 bytes (functions/src/api/sendMessage.ts MAX_MESSAGE_TEXT_BYTES). */
2101
+ messageTextBytes: 4096,
2102
+ /**
2103
+ * Serialized metadata JSON length in UTF-16 code units — the server checks
2104
+ * JSON.stringify(metadata).length, not bytes (sendMessage.ts:716-724 inline;
2105
+ * parseBody.ts maxJsonBytes despite the name).
2106
+ */
2107
+ messageMetadataJsonChars: 4096,
2108
+ /** Attachments per message (sendMessage.ts MAX_MESSAGE_ATTACHMENTS). */
2109
+ messageAttachments: 10,
2110
+ /** Client-supplied messageId — chars and UTF-8 bytes (sendMessage.ts). */
2111
+ messageIdChars: 160,
2112
+ messageIdBytes: 256,
2113
+ /** Self-context note (functions/src/utils/selfContexts.ts SELF_CONTEXT_CONTEXT_LIMIT). */
2114
+ selfContextChars: 1e3,
2115
+ /** Short private description of an external working context. */
2116
+ externalSourceLabelChars: 80,
2117
+ /**
2118
+ * Contact-request note. Values longer than this are truncated by senders
2119
+ * (slice(0,497)+'...'); this constant is the canonical figure.
2120
+ */
2121
+ contactRequestNoteChars: 500,
2122
+ /** Group membership cap incl. creator (functions/src/utils/conversations.ts MAX_GROUP_MEMBERS). */
2123
+ groupMembers: 50,
2124
+ /** Runtime input (functions/src/api/interactionInput.ts). */
2125
+ inputTitleChars: 160,
2126
+ inputPromptChars: 4e3,
2127
+ inputChoices: 12,
2128
+ inputChoiceLabelChars: 120,
2129
+ inputChoiceValueChars: 200,
2130
+ inputChoiceDescriptionChars: 300,
2131
+ inputQuestions: 12,
2132
+ inputQuestionChars: 1e3,
2133
+ inputQuestionHeaderChars: 120,
2134
+ inputSecretNameChars: 160,
2135
+ inputAnswerChars: 8192,
2136
+ /** Approval (functions/src/api/interactionApproval.ts). */
2137
+ toolNameChars: 128,
2138
+ toolSummaryChars: 1e3,
2139
+ approvalDetails: 8,
2140
+ approvalDetailLabelChars: 80,
2141
+ approvalDetailValueChars: 500,
2142
+ diffFiles: 100,
2143
+ diffPathChars: 1024,
2144
+ diffFileBytes: 24 * 1024,
2145
+ diffTotalBytes: 96 * 1024,
2146
+ /** Card envelope acceptance (functions/src/api/interactionCard.ts — server caps;
2147
+ * authoring caps in @canonmsg/rich-cards are stricter: title 120, fallback 500, blocks 24). */
2148
+ cardEnvelopeBytes: 32 * 1024,
2149
+ cardServerTitleChars: 200,
2150
+ cardServerFallbackTextChars: 2e3,
2151
+ cardServerBlocks: 64,
2152
+ /** Response-values caps enforced on submit (callable respondToInteraction.ts
2153
+ * MAX_VALUES_BYTES/MAX_VALUES_DEPTH; interactionCard.ts re-checks bytes on
2154
+ * consume via MAX_REPLY_BYTES). */
2155
+ cardValuesBytes: 8 * 1024,
2156
+ cardValuesDepth: 8,
2157
+ /** Native correlation metadata (functions/src/api/interactionKinds.ts normalizeNative). */
2158
+ nativeKeys: 24,
2159
+ nativeValueChars: 256,
2160
+ nativeHandles: 16,
2161
+ /** Deadlines (functions/src/utils/runtimeRequestHelpers.ts). */
2162
+ minTimeoutMs: 1e3,
2163
+ /** input/card/plan ceiling (30 minutes). */
2164
+ maxTimeoutMs: 30 * 60 * 1e3,
2165
+ /** approval-only ceiling (72 hours — owner ruling 2026-07-10). */
2166
+ maxApprovalTimeoutMs: 72 * 60 * 60 * 1e3,
2167
+ /** turnId / runtimeId fields on interaction creators. */
2168
+ turnIdChars: 128,
2169
+ /** Reaction key (functions/src/api/reactToMessage.ts normalizeReactionKey). */
2170
+ reactionEmojiChars: 64,
2171
+ /**
2172
+ * Group name authoring cap (enforced on rename via updateNameServer; group
2173
+ * CREATE does not length-check today — treat as the authoring contract).
2174
+ */
2175
+ groupNameChars: 100,
2176
+ /**
2177
+ * `no_reply.reason` authoring cap. There is no server enforcement beyond the
2178
+ * intent schema — the handler logs the reason's presence and nothing else —
2179
+ * so this constant is the canonical figure.
2180
+ */
2181
+ noReplyReasonChars: 500
2182
+ };
2183
+ var SELF_CONTEXT_TYPE = "cross_session";
2184
+ var CANON_VERB_NAMES = [
2185
+ "send_to",
2186
+ "request_input",
2187
+ "request_approval",
2188
+ "check_approval",
2189
+ "send_card",
2190
+ "request_card",
2191
+ "share_contact",
2192
+ "react",
2193
+ "forward",
2194
+ "create_group",
2195
+ "add_member",
2196
+ "remove_member",
2197
+ "leave_conversation",
2198
+ "list_contacts",
2199
+ "list_conversations",
2200
+ "no_reply"
2201
+ ];
2202
+ var VERB_NATIVE_METADATA_KEYS = [
2203
+ "runtime",
2204
+ "method",
2205
+ "requestId",
2206
+ "provider",
2207
+ "origin",
2208
+ "surface",
2209
+ "threadId",
2210
+ "turnId",
2211
+ "runId",
2212
+ "itemId",
2213
+ "toolCallId",
2214
+ "approvalId",
2215
+ "pluginId",
2216
+ "sessionKey",
2217
+ "nodeId",
2218
+ "handles"
2219
+ ];
2220
+ var VERB_NATIVE_STRING_KEYS = VERB_NATIVE_METADATA_KEYS.filter(
2221
+ (key) => key !== "handles"
2222
+ );
2223
+ var RUNTIME_CORRELATION_VALUE_PATTERN = new RegExp(
2224
+ VERB_ID_PATTERNS.runtimeCorrelationValue
2225
+ );
2226
+ var RUNTIME_METHOD_PATTERN = new RegExp(VERB_ID_PATTERNS.runtimeMethod);
2227
+ var RUNTIME_HANDLE_KEY_PATTERN = new RegExp(VERB_ID_PATTERNS.runtimeHandleKey);
2228
+ var NO_REPLY_ACK_NOTE = "Acknowledged \u2014 nothing was posted to the conversation.";
2229
+
2230
+ // ../backend-contracts/src/verbSchemas.ts
2231
+ var REF = /* @__PURE__ */ __name((def) => ({ $ref: `#/$defs/${def}` }), "REF");
2232
+ var selfContextDef = {
2233
+ type: "object",
2234
+ description: "Private note-to-self attached to a cross-conversation send. Visible only to the sending agent (surfaced back as provenance.activeSelfContext); never shown to recipients. Enforced by functions/src/utils/selfContexts.ts.",
2235
+ required: ["type", "context"],
2236
+ additionalProperties: false,
2237
+ properties: {
2238
+ type: { const: SELF_CONTEXT_TYPE },
2239
+ context: {
2240
+ type: "string",
2241
+ minLength: 1,
2242
+ maxLength: VERB_LIMITS.selfContextChars,
2243
+ description: "What future-you needs to know about this transfer."
2244
+ }
2106
2245
  }
2107
- code = "INVALID_MLS_DEV_CREDENTIAL_RECONCILIATION";
2108
- constructor() {
2109
- super("INVALID_MLS_DEV_CREDENTIAL_RECONCILIATION");
2110
- this.name = "MlsDevCredentialReconciliationValidationError";
2246
+ };
2247
+ var sessionSelectionDef = {
2248
+ description: "How to pick the conversation when targeting a user whose chats are session-shaped (agent targets). Canon default for user-targeted sends: continue_or_create.",
2249
+ oneOf: [
2250
+ {
2251
+ type: "object",
2252
+ required: ["mode"],
2253
+ additionalProperties: false,
2254
+ properties: { mode: { enum: ["new", "continue_latest", "continue_or_create"] } }
2255
+ },
2256
+ {
2257
+ type: "object",
2258
+ required: ["mode", "conversationId"],
2259
+ additionalProperties: false,
2260
+ properties: {
2261
+ mode: { const: "specific" },
2262
+ conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId }
2263
+ }
2264
+ }
2265
+ ]
2266
+ };
2267
+ var mediaAttachmentDef = {
2268
+ type: "object",
2269
+ required: ["kind", "url"],
2270
+ additionalProperties: false,
2271
+ properties: {
2272
+ kind: { enum: ["image", "audio", "video", "file"] },
2273
+ url: {
2274
+ type: "string",
2275
+ minLength: 1,
2276
+ description: "Must come from a canonical Canon upload/finalization response or the GIF picker \u2014 server allowlist, fail-closed (functions/src/utils/mediaUploadSafety.ts)."
2277
+ },
2278
+ uploadId: {
2279
+ type: "string",
2280
+ pattern: VERB_ID_PATTERNS.resumableUploadId,
2281
+ description: "Server-issued resumable identity. Preserve it from finalization so the durable message transaction can validate and retain initially temporary media."
2282
+ },
2283
+ mimeType: { type: "string" },
2284
+ fileName: { type: "string" },
2285
+ sizeBytes: { type: "number", minimum: 0 },
2286
+ width: { type: "number", minimum: 0 },
2287
+ height: { type: "number", minimum: 0 },
2288
+ durationMs: { type: "number", minimum: 0 },
2289
+ thumbnailUrl: { type: "string", minLength: 1 },
2290
+ processingStatus: { enum: ["processing", "ready", "failed"] },
2291
+ processingErrorCode: {
2292
+ enum: ["invalid_video", "video_limits_exceeded", "video_processing_failed"]
2293
+ }
2111
2294
  }
2112
2295
  };
2113
- var utf86 = new TextEncoder();
2114
- var domain2 = utf86.encode("canon.mls.credential-reconciliation-proof.v1\0");
2115
- function invalid10() {
2116
- throw new MlsDevCredentialReconciliationValidationError();
2117
- }
2118
- __name(invalid10, "invalid");
2119
- function boundary9(read) {
2120
- try {
2121
- return read();
2122
- } catch {
2123
- return invalid10();
2296
+ var turnMetadataDef = {
2297
+ type: "object",
2298
+ description: `Free-form metadata envelope; JSON.stringify length <= ${VERB_LIMITS.messageMetadataJsonChars} UTF-16 code units (functions/src/api/sendMessage.ts checks string length, not bytes). Well-known turn-protocol keys below (authoritative type: turnProtocol.ts TurnMetadata, which also carries requestedTurnMode); receipt metadata.type values are server-owned and rejected.`,
2299
+ additionalProperties: true,
2300
+ properties: {
2301
+ turnId: { type: ["string", "null"] },
2302
+ turnSemantics: { enum: ["progress", "turn_complete", "control"] },
2303
+ deliveryIntent: {
2304
+ enum: ["queue", "interrupt", "interleave", "stop"],
2305
+ description: "What the recipient runtime should do if the message lands mid-turn. Default queue."
2306
+ },
2307
+ replyBehavior: { enum: ["allow_auto_reply", "suppress_auto_reply"] }
2124
2308
  }
2125
- }
2126
- __name(boundary9, "boundary");
2127
- function bounded7(value) {
2128
- if (utf86.encode(JSON.stringify(value)).length > MLS_DEV_CREDENTIAL_RECONCILIATION_MAX_BYTES) invalid10();
2129
- return Object.freeze(value);
2130
- }
2131
- __name(bounded7, "bounded");
2132
- function encoded6(value, bytes) {
2133
- decodeCanonicalMlsBase64Url(value, bytes);
2134
- return value;
2135
- }
2136
- __name(encoded6, "encoded");
2137
- function parseMlsDevCredentialReconciliationBegin(value) {
2138
- return boundary9(() => {
2139
- const input = readMlsDataRecord(value, ["version", "deviceId", "requestId", "expectedCredentialGeneration"]);
2140
- return bounded7({
2141
- version: requireMlsLiteral(input.version, 1),
2142
- deviceId: requireMlsUuid(input.deviceId),
2143
- requestId: requireMlsUuid(input.requestId),
2144
- expectedCredentialGeneration: requireMlsInteger(input.expectedCredentialGeneration)
2145
- });
2146
- });
2147
- }
2148
- __name(parseMlsDevCredentialReconciliationBegin, "parseMlsDevCredentialReconciliationBegin");
2149
- function parseMlsDevCredentialReconciliationChallenge(value) {
2150
- return boundary9(() => {
2151
- const input = readMlsDataRecord(value, [
2152
- "version",
2153
- "challengeId",
2154
- "requestId",
2155
- "nonce",
2156
- "device",
2157
- "expectedCredentialGeneration",
2158
- "issuedAtMs",
2159
- "expiresAtMs"
2160
- ]);
2161
- const device = parseMlsDevEnrollmentResult({ version: 1, authorization: "registration_only", device: input.device }).device;
2162
- const issuedAtMs = requireMlsInteger(input.issuedAtMs), expiresAtMs = requireMlsInteger(input.expiresAtMs);
2163
- if (issuedAtMs < device.registeredAtMs || expiresAtMs - issuedAtMs !== MLS_DEV_CREDENTIAL_RECONCILIATION_CHALLENGE_TTL_MS) invalid10();
2164
- return bounded7({
2165
- version: requireMlsLiteral(input.version, 1),
2166
- challengeId: requireMlsUuid(input.challengeId),
2167
- requestId: requireMlsUuid(input.requestId),
2168
- nonce: encoded6(input.nonce, 32),
2169
- device,
2170
- expectedCredentialGeneration: requireMlsInteger(input.expectedCredentialGeneration),
2171
- issuedAtMs,
2172
- expiresAtMs
2173
- });
2174
- });
2175
- }
2176
- __name(parseMlsDevCredentialReconciliationChallenge, "parseMlsDevCredentialReconciliationChallenge");
2177
- function parseMlsDevCredentialReconciliationComplete(value) {
2178
- return boundary9(() => {
2179
- const input = readMlsDataRecord(value, ["version", "challengeId", "proof"]);
2180
- return bounded7({ version: requireMlsLiteral(input.version, 1), challengeId: requireMlsUuid(input.challengeId), proof: encoded6(input.proof, 64) });
2181
- });
2182
- }
2183
- __name(parseMlsDevCredentialReconciliationComplete, "parseMlsDevCredentialReconciliationComplete");
2184
- function parseMlsDevCredentialReconciliationReceipt(value) {
2185
- return boundary9(() => {
2186
- const input = readMlsDataRecord(value, ["version", "authorization", "challenge", "observedCredentialGeneration", "observedAtMs"]);
2187
- const challenge = parseMlsDevCredentialReconciliationChallenge(input.challenge);
2188
- const observedCredentialGeneration = requireMlsInteger(input.observedCredentialGeneration), observedAtMs = requireMlsInteger(input.observedAtMs);
2189
- if (observedCredentialGeneration < challenge.expectedCredentialGeneration || observedAtMs < challenge.issuedAtMs || observedAtMs >= challenge.expiresAtMs) invalid10();
2190
- return bounded7({
2191
- version: requireMlsLiteral(input.version, 1),
2192
- authorization: requireMlsLiteral(input.authorization, "credential_generation_observation_only"),
2193
- challenge,
2194
- observedCredentialGeneration,
2195
- observedAtMs
2196
- });
2197
- });
2198
- }
2199
- __name(parseMlsDevCredentialReconciliationReceipt, "parseMlsDevCredentialReconciliationReceipt");
2200
-
2201
- // ../backend-contracts/src/mlsDevDeviceCustody.ts
2202
- var MLS_DEV_DEVICE_CONTEXT_MAX_BYTES = 2048;
2203
- var MLS_DEV_DEVICE_COMMAND_MAX_BYTES = 16384;
2204
- var MLS_DEV_DEVICE_WRAPPED_STATE_MAX_BYTES = 6144;
2205
- var MlsDevDeviceCustodyValidationError = class extends Error {
2206
- static {
2207
- __name(this, "MlsDevDeviceCustodyValidationError");
2309
+ };
2310
+ var messageIdDef = {
2311
+ type: "string",
2312
+ pattern: VERB_ID_PATTERNS.runtimeId,
2313
+ not: {
2314
+ pattern: "^(\\.{1,2}|__.*__)$",
2315
+ description: "Reserved forms the server 400s: '.', '..', and __x__ ids."
2316
+ },
2317
+ description: `Client idempotency key (<= ${VERB_LIMITS.messageIdChars} chars / ${VERB_LIMITS.messageIdBytes} UTF-8 bytes; no slashes, control chars, '.', '..', __x__). Same id + identical payload replays idempotently; different payload -> 409 MESSAGE_ID_EXISTS.`
2318
+ };
2319
+ var messageOptionsDef = {
2320
+ type: "object",
2321
+ description: "Message composition options (mirrors POST /messages/send). Deliberately absent: contact_card contentType (use share_contact) and forwarded/forwardedFrom (forwarding has its own verb \u2014 use forward, not send_to).",
2322
+ additionalProperties: false,
2323
+ properties: {
2324
+ messageId: REF("messageId"),
2325
+ contentType: {
2326
+ enum: ["text", "image", "audio", "video", "file"],
2327
+ description: "Derived from attachments when omitted. contact_card is deliberately excluded here \u2014 use the share_contact verb."
2328
+ },
2329
+ attachments: {
2330
+ type: "array",
2331
+ maxItems: VERB_LIMITS.messageAttachments,
2332
+ items: REF("mediaAttachment")
2333
+ },
2334
+ mentions: {
2335
+ type: "array",
2336
+ items: { type: "string" },
2337
+ description: "Member userIds; every id must be a conversation member (400 otherwise)."
2338
+ },
2339
+ replyTo: { type: "string", description: "Message id being replied to (not existence-validated)." },
2340
+ replyToPosition: { type: "integer" },
2341
+ metadata: REF("turnMetadata")
2208
2342
  }
2209
- code = "INVALID_MLS_DEV_DEVICE_CUSTODY";
2210
- constructor() {
2211
- super("INVALID_MLS_DEV_DEVICE_CUSTODY");
2212
- this.name = "MlsDevDeviceCustodyValidationError";
2343
+ };
2344
+ var inputChoiceDef = {
2345
+ type: "object",
2346
+ required: ["label"],
2347
+ additionalProperties: false,
2348
+ properties: {
2349
+ label: { type: "string", minLength: 1, maxLength: VERB_LIMITS.inputChoiceLabelChars },
2350
+ value: { type: "string", maxLength: VERB_LIMITS.inputChoiceValueChars },
2351
+ description: { type: "string", maxLength: VERB_LIMITS.inputChoiceDescriptionChars }
2213
2352
  }
2214
2353
  };
2215
- var utf87 = new TextEncoder();
2216
- function invalid11() {
2217
- throw new MlsDevDeviceCustodyValidationError();
2218
- }
2219
- __name(invalid11, "invalid");
2220
- function boundary10(read) {
2221
- try {
2222
- return read();
2223
- } catch {
2224
- return invalid11();
2354
+ var inputQuestionDef = {
2355
+ type: "object",
2356
+ required: ["id", "question"],
2357
+ additionalProperties: false,
2358
+ properties: {
2359
+ id: { type: "string", pattern: VERB_ID_PATTERNS.questionId },
2360
+ question: { type: "string", minLength: 1, maxLength: VERB_LIMITS.inputQuestionChars },
2361
+ header: { type: "string", maxLength: VERB_LIMITS.inputQuestionHeaderChars },
2362
+ choices: { type: "array", maxItems: VERB_LIMITS.inputChoices, items: REF("inputChoice") },
2363
+ allowOther: { type: "boolean" },
2364
+ isSecret: {
2365
+ type: "boolean",
2366
+ description: "Forces owner-only routing for the whole request."
2367
+ },
2368
+ multiSelect: { type: "boolean" }
2225
2369
  }
2226
- }
2227
- __name(boundary10, "boundary");
2228
- function bounded8(value, limit2) {
2229
- if (utf87.encode(JSON.stringify(value)).length > limit2) invalid11();
2230
- return Object.freeze(value);
2231
- }
2232
- __name(bounded8, "bounded");
2233
- function binding2(input) {
2234
- return {
2235
- version: requireMlsLiteral(input.version, 1),
2236
- environmentId: requireMlsEnvironmentId(input.environmentId),
2237
- principal: parseMlsDevEnrollmentPrincipal(input.principal),
2238
- installationId: parseMlsDevInstallationId(input.installationId)
2239
- };
2240
- }
2241
- __name(binding2, "binding");
2242
- function parseMlsDevDeviceBinding(value) {
2243
- return boundary10(() => bounded8(binding2(readMlsDataRecord(value, ["version", "environmentId", "principal", "installationId"])), MLS_DEV_DEVICE_CONTEXT_MAX_BYTES));
2244
- }
2245
- __name(parseMlsDevDeviceBinding, "parseMlsDevDeviceBinding");
2246
- function parseMlsDevDeviceContext(value) {
2247
- return boundary10(() => {
2248
- const input = readMlsDataRecord(value, ["version", "environmentId", "principal", "installationId", "revision"]);
2249
- const revision2 = requireMlsInteger(input.revision);
2250
- if (revision2 >= Number.MAX_SAFE_INTEGER) invalid11();
2251
- return bounded8({ ...binding2(input), revision: revision2 }, MLS_DEV_DEVICE_CONTEXT_MAX_BYTES);
2252
- });
2253
- }
2254
- __name(parseMlsDevDeviceContext, "parseMlsDevDeviceContext");
2255
- function parseMlsDevDeviceCommand(value) {
2256
- return boundary10(() => {
2257
- if (value === null || typeof value !== "object") invalid11();
2258
- const descriptor = Object.getOwnPropertyDescriptor(value, "op");
2259
- if (!descriptor || !Object.hasOwn(descriptor, "value")) invalid11();
2260
- if (descriptor.value === "create") {
2261
- const input2 = readMlsDataRecord(value, ["op"]);
2262
- return Object.freeze({ op: requireMlsLiteral(input2.op, "create") });
2263
- }
2264
- if (descriptor.value === "enrollment_proof") {
2265
- const input2 = readMlsDataRecord(value, ["op", "challenge"]);
2266
- return bounded8({
2267
- op: requireMlsLiteral(input2.op, "enrollment_proof"),
2268
- challenge: parseMlsDevEnrollmentChallenge(input2.challenge)
2269
- }, MLS_DEV_DEVICE_COMMAND_MAX_BYTES);
2270
- }
2271
- if (descriptor.value === "credential_reconciliation_proof") {
2272
- const input2 = readMlsDataRecord(value, ["op", "challenge", "expectedCredentialGeneration"]);
2273
- const challenge2 = parseMlsDevCredentialReconciliationChallenge(input2.challenge);
2274
- const expectedCredentialGeneration2 = requireMlsInteger(input2.expectedCredentialGeneration);
2275
- if (challenge2.expectedCredentialGeneration !== expectedCredentialGeneration2) invalid11();
2276
- return bounded8({
2277
- op: requireMlsLiteral(input2.op, "credential_reconciliation_proof"),
2278
- challenge: challenge2,
2279
- expectedCredentialGeneration: expectedCredentialGeneration2
2280
- }, MLS_DEV_DEVICE_COMMAND_MAX_BYTES);
2281
- }
2282
- if (descriptor.value !== "credential_proof") invalid11();
2283
- const input = readMlsDataRecord(value, ["op", "challenge", "registrationResult", "expectedCredentialGeneration"]);
2284
- const challenge = parseMlsDevCredentialChallenge(input.challenge);
2285
- const registrationResult = parseMlsDevEnrollmentResult(input.registrationResult);
2286
- const expectedCredentialGeneration = requireMlsInteger(input.expectedCredentialGeneration);
2287
- if (expectedCredentialGeneration >= Number.MAX_SAFE_INTEGER || challenge.credential.claims.generation !== expectedCredentialGeneration + 1 || JSON.stringify(challenge.credential.claims.device) !== JSON.stringify(registrationResult.device)) invalid11();
2288
- return bounded8({
2289
- op: requireMlsLiteral(input.op, "credential_proof"),
2290
- challenge,
2291
- registrationResult,
2292
- expectedCredentialGeneration
2293
- }, MLS_DEV_DEVICE_COMMAND_MAX_BYTES);
2294
- });
2295
- }
2296
- __name(parseMlsDevDeviceCommand, "parseMlsDevDeviceCommand");
2297
- function encoded7(value, bytes) {
2298
- decodeCanonicalMlsBase64Url(value, bytes);
2299
- return value;
2300
- }
2301
- __name(encoded7, "encoded");
2302
- function wrapped(value) {
2303
- if (typeof value !== "string" || value.length > MLS_DEV_DEVICE_WRAPPED_STATE_MAX_BYTES || !value.startsWith("v1:")) invalid11();
2304
- const body = value.slice(3);
2305
- if (body.length < 40 || body.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(body)) invalid11();
2306
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2307
- if (body.endsWith("==") && (alphabet.indexOf(body.at(-3)) & 15) !== 0) invalid11();
2308
- if (!body.endsWith("==") && body.endsWith("=") && (alphabet.indexOf(body.at(-2)) & 3) !== 0) invalid11();
2309
- return value;
2310
- }
2311
- __name(wrapped, "wrapped");
2312
- function parseMlsDevDevicePreparation(value, op) {
2313
- return boundary10(() => {
2314
- if (!["create", "enrollment_proof", "credential_proof", "credential_reconciliation_proof"].includes(op)) invalid11();
2315
- const input = readMlsDataRecord(value, ["wrappedState", "payload"]);
2316
- const payload = readMlsDataRecord(input.payload, op === "create" ? ["signaturePublicKey"] : ["signaturePublicKey", "proof"]);
2317
- return bounded8({ wrappedState: wrapped(input.wrappedState), payload: Object.freeze({
2318
- signaturePublicKey: encoded7(payload.signaturePublicKey, 32),
2319
- ...op === "create" ? {} : { proof: encoded7(payload.proof, 64) }
2320
- }) }, MLS_DEV_DEVICE_COMMAND_MAX_BYTES);
2321
- });
2322
- }
2323
- __name(parseMlsDevDevicePreparation, "parseMlsDevDevicePreparation");
2324
-
2325
- // ../backend-contracts/src/upload-mime-types.ts
2326
- var MIME_TYPE_RE = /^[a-z0-9.+-]+\/[a-z0-9.+-]+$/i;
2327
- var SAFE_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
2328
- "image/jpeg",
2329
- "image/png",
2330
- "image/webp",
2331
- "image/gif"
2332
- ]);
2333
- var SAFE_AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
2334
- "audio/mp4",
2335
- "audio/x-m4a",
2336
- "audio/mpeg",
2337
- "audio/webm",
2338
- "audio/ogg",
2339
- "audio/wav",
2340
- // ADTS AAC — the future Android streamable-memo lane; harmless standalone.
2341
- "audio/aac"
2342
- ]);
2343
- var SAFE_VIDEO_MIME_TYPES = /* @__PURE__ */ new Set([
2344
- "video/mp4",
2345
- "video/quicktime",
2346
- "video/webm",
2347
- "video/x-m4v"
2348
- ]);
2349
- var SAFE_FILE_MIME_TYPES = /* @__PURE__ */ new Set([
2350
- "application/pdf",
2351
- "text/plain",
2352
- "text/markdown",
2353
- "text/x-markdown",
2354
- "text/csv",
2355
- "application/csv",
2356
- "application/json",
2357
- "application/zip",
2358
- "application/vnd.ms-excel",
2359
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
2360
- ]);
2361
- var SAFE_FILE_MIME_BY_EXTENSION = {
2362
- csv: "text/csv",
2363
- json: "application/json",
2364
- md: "text/markdown",
2365
- markdown: "text/markdown",
2366
- pdf: "application/pdf",
2367
- txt: "text/plain",
2368
- xls: "application/vnd.ms-excel",
2369
- xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
2370
- zip: "application/zip"
2371
- };
2372
- var SAFE_VIDEO_MIME_BY_EXTENSION = {
2373
- m4v: "video/x-m4v",
2374
- mov: "video/quicktime",
2375
- mp4: "video/mp4",
2376
- webm: "video/webm"
2377
- };
2378
- var SAFE_AUDIO_MIME_BY_EXTENSION = {
2379
- aac: "audio/aac",
2380
- m4a: "audio/x-m4a",
2381
- mp3: "audio/mpeg",
2382
- oga: "audio/ogg",
2383
- ogg: "audio/ogg",
2384
- wav: "audio/wav"
2385
2370
  };
2386
- function normalizeMimeType(value) {
2387
- if (typeof value !== "string") return null;
2388
- const normalized = value.split(";")[0].trim().toLowerCase();
2389
- if (!MIME_TYPE_RE.test(normalized)) return null;
2390
- if (normalized === "image/jpg") return "image/jpeg";
2391
- return normalized;
2392
- }
2393
- __name(normalizeMimeType, "normalizeMimeType");
2394
- function inferSafeFileMimeTypeFromName(value) {
2395
- if (typeof value !== "string") return null;
2396
- const extension = value.trim().split(/[\\/]/).pop()?.split(".").pop()?.toLowerCase() ?? "";
2397
- return SAFE_FILE_MIME_BY_EXTENSION[extension] ?? null;
2398
- }
2399
- __name(inferSafeFileMimeTypeFromName, "inferSafeFileMimeTypeFromName");
2400
- function inferSafeVideoMimeTypeFromName(value) {
2401
- if (typeof value !== "string") return null;
2402
- const extension = value.trim().split(/[\\/]/).pop()?.split(".").pop()?.toLowerCase() ?? "";
2403
- return SAFE_VIDEO_MIME_BY_EXTENSION[extension] ?? null;
2404
- }
2405
- __name(inferSafeVideoMimeTypeFromName, "inferSafeVideoMimeTypeFromName");
2406
- function inferSafeAudioMimeTypeFromName(value) {
2407
- if (typeof value !== "string") return null;
2408
- const extension = value.trim().split(/[\\/]/).pop()?.split(".").pop()?.toLowerCase() ?? "";
2409
- return SAFE_AUDIO_MIME_BY_EXTENSION[extension] ?? null;
2410
- }
2411
- __name(inferSafeAudioMimeTypeFromName, "inferSafeAudioMimeTypeFromName");
2412
- function shouldTrustSafeFileName(mimeType) {
2413
- return !mimeType || mimeType === "application/octet-stream" || mimeType === "application/vnd.ms-excel" || mimeType === "text/plain";
2414
- }
2415
- __name(shouldTrustSafeFileName, "shouldTrustSafeFileName");
2416
- function classifyAllowedUploadMimeType(value) {
2417
- const mimeType = normalizeMimeType(value);
2418
- if (!mimeType) return null;
2419
- if (SAFE_IMAGE_MIME_TYPES.has(mimeType)) return "image";
2420
- if (SAFE_AUDIO_MIME_TYPES.has(mimeType)) return "audio";
2421
- if (SAFE_VIDEO_MIME_TYPES.has(mimeType)) return "video";
2422
- if (SAFE_FILE_MIME_TYPES.has(mimeType)) return "file";
2423
- return null;
2424
- }
2425
- __name(classifyAllowedUploadMimeType, "classifyAllowedUploadMimeType");
2426
- function resolveAllowedUploadMimeType(input) {
2427
- const normalizedMimeType = normalizeMimeType(input.mimeType);
2428
- const hasExplicitMimeType = typeof input.mimeType === "string" && input.mimeType.trim().length > 0;
2429
- const inferredVideoMimeType = inferSafeVideoMimeTypeFromName(input.fileName);
2430
- const inferredAudioMimeType = inferSafeAudioMimeTypeFromName(input.fileName);
2431
- const inferredFileMimeType = inferSafeFileMimeTypeFromName(input.fileName);
2432
- if (hasExplicitMimeType && !normalizedMimeType) {
2433
- return null;
2434
- }
2435
- if (inferredVideoMimeType && (!normalizedMimeType || normalizedMimeType === "application/octet-stream")) {
2436
- return inferredVideoMimeType;
2371
+ var approvalDetailDef = {
2372
+ type: "object",
2373
+ required: ["label", "value"],
2374
+ additionalProperties: false,
2375
+ properties: {
2376
+ label: { type: "string", minLength: 1, maxLength: VERB_LIMITS.approvalDetailLabelChars },
2377
+ value: { type: "string", minLength: 1, maxLength: VERB_LIMITS.approvalDetailValueChars },
2378
+ monospace: { type: "boolean" }
2437
2379
  }
2438
- if (inferredAudioMimeType && (!normalizedMimeType || normalizedMimeType === "application/octet-stream")) {
2439
- return inferredAudioMimeType;
2380
+ };
2381
+ var unifiedDiffDef = {
2382
+ type: "object",
2383
+ description: `Unified diff for file-change approvals. Server sanitizes: sensitive paths suppressed, secret tokens redacted, per-file clip ${VERB_LIMITS.diffFileBytes} bytes, total ${VERB_LIMITS.diffTotalBytes} bytes (functions/src/api/interactionApproval.ts; primitives in backend-contracts diffRedaction.ts).`,
2384
+ required: ["files"],
2385
+ additionalProperties: false,
2386
+ properties: {
2387
+ files: {
2388
+ type: "array",
2389
+ maxItems: VERB_LIMITS.diffFiles,
2390
+ items: {
2391
+ type: "object",
2392
+ required: ["path", "status"],
2393
+ additionalProperties: false,
2394
+ properties: {
2395
+ path: { type: "string", minLength: 1, maxLength: VERB_LIMITS.diffPathChars },
2396
+ status: { enum: ["modified", "created", "deleted", "renamed"] },
2397
+ oldPath: { type: "string", maxLength: VERB_LIMITS.diffPathChars },
2398
+ additions: { type: "integer", minimum: 0 },
2399
+ deletions: { type: "integer", minimum: 0 },
2400
+ diff: { type: "string" },
2401
+ suppressed: { type: "boolean" }
2402
+ }
2403
+ }
2404
+ },
2405
+ truncated: { type: "boolean" }
2440
2406
  }
2441
- if (inferredFileMimeType && shouldTrustSafeFileName(normalizedMimeType)) {
2442
- return inferredFileMimeType;
2407
+ };
2408
+ var sessionRuleDef = {
2409
+ type: "object",
2410
+ description: "Standing approval rule. Owner-only: the server forces allowSessionRule=false for non-owner responders.",
2411
+ required: ["type"],
2412
+ additionalProperties: false,
2413
+ properties: {
2414
+ type: { enum: ["approve-all", "approve-tool", "deny-tool"] },
2415
+ toolPattern: { type: "string", pattern: VERB_ID_PATTERNS.sessionRuleToolPattern },
2416
+ expiresAt: { type: ["string", "null"], description: "ISO timestamp or null." }
2417
+ },
2418
+ if: { properties: { type: { enum: ["approve-tool", "deny-tool"] } }, required: ["type"] },
2419
+ then: { required: ["toolPattern"], properties: { toolPattern: true } }
2420
+ };
2421
+ var runtimeCardDef = {
2422
+ type: "object",
2423
+ description: `ENVELOPE-ONLY validation of a canon.card.v1 document: block structures are NOT checked here. Full document contract: @canonmsg/rich-cards RUNTIME_CARD_JSON_SCHEMA_V1 ($id https://canonmsg.com/schemas/canon.card.v1.json) \u2014 authoring caps title 120 / fallbackText 500 / blocks 24. Server envelope acceptance (interactionCard.ts): <= ${VERB_LIMITS.cardEnvelopeBytes} bytes serialized, title <= ${VERB_LIMITS.cardServerTitleChars}, fallbackText <= ${VERB_LIMITS.cardServerFallbackTextChars}, blocks <= ${VERB_LIMITS.cardServerBlocks}; no secret-bearing top-level keys.`,
2424
+ required: ["schema", "title", "fallbackText", "blocks"],
2425
+ additionalProperties: true,
2426
+ properties: {
2427
+ schema: { const: "canon.card.v1" },
2428
+ cardId: { type: "string", pattern: VERB_ID_PATTERNS.cardId },
2429
+ title: { type: "string", minLength: 1, maxLength: VERB_LIMITS.cardServerTitleChars },
2430
+ fallbackText: { type: "string", minLength: 1, maxLength: VERB_LIMITS.cardServerFallbackTextChars },
2431
+ blocks: { type: "array", minItems: 1, maxItems: VERB_LIMITS.cardServerBlocks }
2443
2432
  }
2444
- if (normalizedMimeType && classifyAllowedUploadMimeType(normalizedMimeType)) {
2445
- return normalizedMimeType;
2433
+ };
2434
+ var runtimeCorrelationValueDef = {
2435
+ type: "string",
2436
+ pattern: VERB_ID_PATTERNS.runtimeCorrelationValue,
2437
+ maxLength: VERB_LIMITS.nativeValueChars
2438
+ };
2439
+ var runtimeMethodDef = {
2440
+ type: "string",
2441
+ pattern: VERB_ID_PATTERNS.runtimeMethod,
2442
+ maxLength: VERB_LIMITS.nativeValueChars
2443
+ };
2444
+ var VERB_NATIVE_METADATA_JSON_SCHEMA = {
2445
+ type: "object",
2446
+ description: "Content-free runtime correlation ids. Commands, paths, prompts, summaries, and model labels are not permitted in this public envelope.",
2447
+ maxProperties: VERB_LIMITS.nativeKeys,
2448
+ additionalProperties: false,
2449
+ properties: {
2450
+ runtime: runtimeCorrelationValueDef,
2451
+ method: runtimeMethodDef,
2452
+ requestId: runtimeCorrelationValueDef,
2453
+ provider: runtimeCorrelationValueDef,
2454
+ origin: runtimeCorrelationValueDef,
2455
+ surface: runtimeCorrelationValueDef,
2456
+ threadId: runtimeCorrelationValueDef,
2457
+ turnId: runtimeCorrelationValueDef,
2458
+ runId: runtimeCorrelationValueDef,
2459
+ itemId: runtimeCorrelationValueDef,
2460
+ toolCallId: runtimeCorrelationValueDef,
2461
+ approvalId: runtimeCorrelationValueDef,
2462
+ pluginId: runtimeCorrelationValueDef,
2463
+ sessionKey: runtimeCorrelationValueDef,
2464
+ nodeId: runtimeCorrelationValueDef,
2465
+ handles: {
2466
+ type: "object",
2467
+ maxProperties: VERB_LIMITS.nativeHandles,
2468
+ propertyNames: { pattern: VERB_ID_PATTERNS.runtimeHandleKey },
2469
+ additionalProperties: runtimeCorrelationValueDef
2470
+ }
2446
2471
  }
2447
- return normalizedMimeType ? null : inferredFileMimeType;
2448
- }
2449
- __name(resolveAllowedUploadMimeType, "resolveAllowedUploadMimeType");
2450
-
2451
- // ../backend-contracts/src/messageText.ts
2452
- var HUMAN_MESSAGE_TEXT_MAX_BYTES = 32 * 1024;
2453
- var UTF8_ENCODER = new TextEncoder();
2454
-
2455
- // ../backend-contracts/src/runtimeCardFields.ts
2456
- var RUNTIME_CARD_FIELD_TYPES = [
2457
- "text",
2458
- "textarea",
2459
- "select",
2460
- "multiSelect",
2461
- "boolean",
2462
- "date",
2463
- "number",
2464
- "currency",
2465
- "searchSelect",
2466
- "lineItems"
2467
- ];
2468
- var RUNTIME_CARD_LINE_ITEM_COLUMN_TYPES = [
2469
- "text",
2470
- "number",
2471
- "currency",
2472
- "date",
2473
- "select"
2474
- ];
2475
-
2476
- // ../backend-contracts/src/membershipChange.ts
2477
- function isRecord(value) {
2478
- return typeof value === "object" && value !== null && !Array.isArray(value);
2479
- }
2480
- __name(isRecord, "isRecord");
2481
- function isMembershipChangeMessage(message) {
2482
- return isRecord(message.metadata) && message.metadata.type === "membership_change";
2483
- }
2484
- __name(isMembershipChangeMessage, "isMembershipChangeMessage");
2485
- function isNameOrId(value) {
2486
- return value === null || typeof value === "string" && value.trim().length > 0;
2487
- }
2488
- __name(isNameOrId, "isNameOrId");
2489
- function readMembershipChangeMetadata(metadata) {
2490
- if (!isRecord(metadata) || metadata.type !== "membership_change" || metadata.action !== "added" && metadata.action !== "removed" && metadata.action !== "left" || !isNameOrId(metadata.actorId) || !isNameOrId(metadata.actorName) || !isNameOrId(metadata.memberId) || typeof metadata.memberName !== "string" || !metadata.memberName.trim() || !Number.isSafeInteger(metadata.membershipRevision) || Number(metadata.membershipRevision) < 1 || metadata.turnSemantics !== "control" || metadata.replyBehavior !== "suppress_auto_reply") {
2491
- return null;
2472
+ };
2473
+ var timeoutFields = /* @__PURE__ */ __name((maxMs, ceilingNote) => ({
2474
+ timeoutMs: {
2475
+ type: "integer",
2476
+ minimum: VERB_LIMITS.minTimeoutMs,
2477
+ maximum: maxMs,
2478
+ description: `Relative deadline in ms (server ceiling ${ceilingNote}). expiresAt wins when both are given.`
2479
+ },
2480
+ expiresAt: {
2481
+ type: "integer",
2482
+ minimum: 0,
2483
+ description: "Absolute epoch-ms deadline. The REST interaction creators REQUIRE a deadline (input interactions also require input subtype kind): bindings must fill defaults and convert timeoutMs into expiresAt before calling the endpoint."
2492
2484
  }
2493
- return {
2494
- type: "membership_change",
2495
- action: metadata.action,
2496
- actorId: metadata.actorId,
2497
- actorName: metadata.actorName,
2498
- memberId: metadata.memberId,
2499
- memberName: metadata.memberName,
2500
- membershipRevision: metadata.membershipRevision,
2501
- turnSemantics: "control",
2502
- replyBehavior: "suppress_auto_reply"
2503
- };
2504
- }
2505
- __name(readMembershipChangeMetadata, "readMembershipChangeMetadata");
2506
- function formatMembershipChangeNotice(metadata, currentUserId) {
2507
- const isActor = !!currentUserId && metadata.actorId === currentUserId;
2508
- const isMember = !!currentUserId && metadata.memberId === currentUserId;
2509
- if (metadata.action === "left") {
2510
- return `${isMember ? "You" : metadata.memberName} left`;
2511
- }
2512
- const actor = isActor ? "You" : metadata.actorName ?? "A member";
2513
- const member2 = isMember ? "you" : metadata.memberName;
2514
- return `${actor} ${metadata.action} ${member2}`;
2515
- }
2516
- __name(formatMembershipChangeNotice, "formatMembershipChangeNotice");
2517
-
2518
- // ../backend-contracts/src/turnProtocol.ts
2519
- var ACTIVE_TURN_STALE_THRESHOLD_MS = 30 * 60 * 1e3;
2520
- var WAITING_INPUT_STALE_THRESHOLD_MS = 12 * 60 * 60 * 1e3;
2521
- var TURN_SEMANTICS = [
2522
- "progress",
2523
- "turn_complete",
2524
- "control"
2525
- ];
2526
- var DELIVERY_INTENTS = [
2527
- "queue",
2528
- "interrupt",
2529
- "interleave",
2530
- "stop"
2531
- ];
2532
- function isRecord2(value) {
2533
- return typeof value === "object" && value !== null && !Array.isArray(value);
2534
- }
2535
- __name(isRecord2, "isRecord");
2536
- function normalizeTurnMetadata(metadata) {
2537
- if (!isRecord2(metadata)) return null;
2538
- const turnId = typeof metadata.turnId === "string" && metadata.turnId.trim() ? metadata.turnId.trim() : null;
2539
- const turnSemantics = TURN_SEMANTICS.includes(metadata.turnSemantics) ? metadata.turnSemantics : void 0;
2540
- const deliveryIntent = DELIVERY_INTENTS.includes(metadata.deliveryIntent) ? metadata.deliveryIntent : void 0;
2541
- const replyBehavior = metadata.replyBehavior === "allow_auto_reply" || metadata.replyBehavior === "suppress_auto_reply" ? metadata.replyBehavior : void 0;
2542
- const inboundDisposition = metadata.inboundDisposition === "queued" || metadata.inboundDisposition === "accepted_now" || metadata.inboundDisposition === "interleaved" || metadata.inboundDisposition === "trigger_suppressed" || metadata.inboundDisposition === "rejected" || metadata.inboundDisposition === "cancelled" ? metadata.inboundDisposition : void 0;
2543
- const requestedTurnMode = typeof metadata.requestedTurnMode === "string" && /^[A-Za-z0-9_.:-]{1,80}$/.test(metadata.requestedTurnMode.trim()) ? metadata.requestedTurnMode.trim() : void 0;
2544
- if (!turnId && !turnSemantics && !deliveryIntent && !replyBehavior && !inboundDisposition && !requestedTurnMode) {
2545
- return null;
2546
- }
2547
- return {
2548
- ...turnId ? { turnId } : {},
2549
- ...turnSemantics ? { turnSemantics } : {},
2550
- ...deliveryIntent ? { deliveryIntent } : {},
2551
- ...replyBehavior ? { replyBehavior } : {},
2552
- ...inboundDisposition ? { inboundDisposition } : {},
2553
- ...requestedTurnMode ? { requestedTurnMode } : {}
2554
- };
2555
- }
2556
- __name(normalizeTurnMetadata, "normalizeTurnMetadata");
2557
- function resolveTurnMessageSemantics(input) {
2558
- if (isMembershipChangeMessage(input)) return "control";
2559
- const turnMetadata = normalizeTurnMetadata(input.metadata);
2560
- if (turnMetadata?.turnSemantics) return turnMetadata.turnSemantics;
2561
- if (input.senderType === "human") return "turn_complete";
2562
- return "progress";
2563
- }
2564
- __name(resolveTurnMessageSemantics, "resolveTurnMessageSemantics");
2565
- function shouldTriggerAgentTurn(input) {
2566
- if (isMembershipChangeMessage(input)) {
2567
- return { allow: false, semantics: "control", reason: "membership notices do not trigger agent replies" };
2568
- }
2569
- const semantics = resolveTurnMessageSemantics(input);
2570
- const turnMetadata = normalizeTurnMetadata(input.metadata);
2571
- if (turnMetadata?.replyBehavior === "suppress_auto_reply") {
2572
- return {
2573
- allow: false,
2574
- semantics,
2575
- reason: "metadata explicitly suppresses auto-reply"
2576
- };
2577
- }
2578
- if (input.senderType === "human") {
2579
- return {
2580
- allow: true,
2581
- semantics,
2582
- reason: "human messages always remain triggerable"
2583
- };
2584
- }
2585
- if (semantics === "progress") {
2586
- return {
2587
- allow: false,
2588
- semantics,
2589
- reason: turnMetadata?.turnSemantics === "progress" ? "non-final agent progress does not trigger other agents" : "agent messages require explicit turn_complete semantics"
2590
- };
2591
- }
2592
- return {
2593
- allow: true,
2594
- semantics,
2595
- reason: "agent message is treated as turn-complete"
2596
- };
2597
- }
2598
- __name(shouldTriggerAgentTurn, "shouldTriggerAgentTurn");
2599
-
2600
- // ../backend-contracts/src/agentBehaviorPolicy.ts
2601
- var PARTICIPATION_HISTORY_FETCH_LIMIT = 50;
2602
- var DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS = 4;
2603
- var DEFAULT_DIRECT_MAX_CONSECUTIVE_AGENT_TURNS = 4;
2604
- function defaultMaxConsecutiveAgentTurns(conversationType) {
2605
- if (conversationType === "group") {
2606
- return DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS;
2607
- }
2608
- if (conversationType === "direct") {
2609
- return DEFAULT_DIRECT_MAX_CONSECUTIVE_AGENT_TURNS;
2610
- }
2611
- return null;
2612
- }
2613
- __name(defaultMaxConsecutiveAgentTurns, "defaultMaxConsecutiveAgentTurns");
2614
- var DEFAULT_POLICY = {
2615
- participation: {
2616
- style: "natural",
2617
- allowAgentToAgent: true,
2618
- allowHumanToAgent: true,
2619
- allowLongRunningCollaboration: true,
2620
- requireMentionForGroupReplies: false,
2621
- maxConsecutiveAgentTurns: null
2485
+ }), "timeoutFields");
2486
+ var send_to_input = {
2487
+ type: "object",
2488
+ description: "Message another conversation or user (admission-aware), optionally carrying a private self-context. Exactly one of targetConversationId / targetUserId / canonContactId. canonContactId is NOT a wire field: bindings resolve it via POST /admission/resolve to a targetUserId first. Projections: selfContext sends -> POST /messages/send-contextual (which requires selfContext and exactly one Canon conversation or external source); known-conversation sends without selfContext -> POST /messages/send; plain user sends -> admission resolve + create + send (the core reachOut composite). User targets resolve admission first: open targets get the message, approval-required targets get a contact request (send deferred and auto-fulfilled on approval), closed targets surface as status 'unavailable' with reason closed.",
2489
+ additionalProperties: false,
2490
+ properties: {
2491
+ targetConversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
2492
+ targetUserId: { type: "string", minLength: 1 },
2493
+ canonContactId: { type: "string", minLength: 1 },
2494
+ text: {
2495
+ type: "string",
2496
+ maxLength: VERB_LIMITS.messageTextBytes,
2497
+ description: `<= ${VERB_LIMITS.messageTextBytes} UTF-8 bytes. Required unless messageOptions.attachments carries the content.`
2498
+ },
2499
+ sourceConversationId: {
2500
+ type: "string",
2501
+ pattern: VERB_ID_PATTERNS.runtimeId,
2502
+ description: "Canon source; the agent must be a member. Exclusive with externalSource."
2503
+ },
2504
+ externalSource: {
2505
+ type: "object",
2506
+ required: ["kind", "integration"],
2507
+ additionalProperties: false,
2508
+ properties: {
2509
+ kind: { const: "external" },
2510
+ integration: { const: "codex" },
2511
+ label: { type: "string", minLength: 1, maxLength: VERB_LIMITS.externalSourceLabelChars }
2512
+ }
2513
+ },
2514
+ selfContext: REF("selfContext"),
2515
+ requestMessage: {
2516
+ type: "string",
2517
+ maxLength: VERB_LIMITS.contactRequestNoteChars,
2518
+ description: `Contact-request note when admission requires approval; defaults to text; senders truncate to ${VERB_LIMITS.contactRequestNoteChars} chars.`
2519
+ },
2520
+ sessionSelection: REF("sessionSelection"),
2521
+ messageOptions: REF("messageOptions")
2622
2522
  },
2623
- instructions: [],
2624
- source: {
2625
- hasAgentDefault: false,
2626
- hasConversationOverride: false
2627
- }
2523
+ oneOf: [
2524
+ { required: ["targetConversationId"], properties: { targetConversationId: true } },
2525
+ { required: ["targetUserId"], properties: { targetUserId: true } },
2526
+ { required: ["canonContactId"], properties: { canonContactId: true } }
2527
+ ],
2528
+ allOf: [
2529
+ {
2530
+ if: { required: ["selfContext"], properties: { selfContext: true } },
2531
+ then: { oneOf: [
2532
+ { required: ["sourceConversationId"], properties: { sourceConversationId: true } },
2533
+ { required: ["externalSource"], properties: { externalSource: true } }
2534
+ ] }
2535
+ },
2536
+ { if: { required: ["externalSource"], properties: { externalSource: true } }, then: { required: ["selfContext"], properties: { selfContext: true } } }
2537
+ ]
2628
2538
  };
2629
- function coalesceBoolean(overrideValue, fallbackValue, defaultValue) {
2630
- if (typeof overrideValue === "boolean") return overrideValue;
2631
- if (typeof fallbackValue === "boolean") return fallbackValue;
2632
- return defaultValue;
2633
- }
2634
- __name(coalesceBoolean, "coalesceBoolean");
2635
- function normalizeAgentBehaviorInstructions(value) {
2636
- if (typeof value !== "string") return null;
2637
- const trimmed = value.trim();
2638
- return trimmed.length > 0 ? trimmed : null;
2639
- }
2640
- __name(normalizeAgentBehaviorInstructions, "normalizeAgentBehaviorInstructions");
2641
- function resolveAgentBehaviorPolicy(params) {
2642
- const agentDefault = params?.agentDefault ?? null;
2643
- const conversationOverride = params?.conversationOverride ?? null;
2644
- const defaultInstructions = normalizeAgentBehaviorInstructions(agentDefault?.instructions);
2645
- const overrideInstructions = normalizeAgentBehaviorInstructions(conversationOverride?.instructions);
2646
- return {
2647
- participation: {
2648
- style: conversationOverride?.participationStyle ?? agentDefault?.participationStyle ?? DEFAULT_POLICY.participation.style,
2649
- allowAgentToAgent: coalesceBoolean(
2650
- conversationOverride?.allowAgentToAgent,
2651
- agentDefault?.allowAgentToAgent,
2652
- DEFAULT_POLICY.participation.allowAgentToAgent
2653
- ),
2654
- allowHumanToAgent: DEFAULT_POLICY.participation.allowHumanToAgent,
2655
- allowLongRunningCollaboration: coalesceBoolean(
2656
- conversationOverride?.allowLongRunningCollaboration,
2657
- agentDefault?.allowLongRunningCollaboration,
2658
- DEFAULT_POLICY.participation.allowLongRunningCollaboration
2659
- ),
2660
- requireMentionForGroupReplies: coalesceBoolean(
2661
- conversationOverride?.requireMentionForGroupReplies,
2662
- agentDefault?.requireMentionForGroupReplies,
2663
- DEFAULT_POLICY.participation.requireMentionForGroupReplies
2664
- ),
2665
- maxConsecutiveAgentTurns: conversationOverride?.maxConsecutiveAgentTurns !== void 0 ? conversationOverride.maxConsecutiveAgentTurns ?? null : agentDefault?.maxConsecutiveAgentTurns !== void 0 ? agentDefault.maxConsecutiveAgentTurns ?? null : defaultMaxConsecutiveAgentTurns(params?.conversationType)
2539
+ var send_to_result = {
2540
+ description: "Canonical outcome vocabulary.",
2541
+ oneOf: [
2542
+ {
2543
+ type: "object",
2544
+ required: ["status", "conversationId"],
2545
+ additionalProperties: true,
2546
+ properties: {
2547
+ status: { const: "messaged" },
2548
+ conversationId: { type: "string" },
2549
+ messageId: { type: "string" },
2550
+ selfContextId: { type: "string" },
2551
+ created: { type: "boolean" },
2552
+ reused: { type: "boolean" },
2553
+ sessionSelection: { type: "string" }
2554
+ }
2666
2555
  },
2667
- instructions: [
2668
- ...defaultInstructions ? [defaultInstructions] : [],
2669
- ...overrideInstructions ? [overrideInstructions] : []
2670
- ],
2671
- source: {
2672
- hasAgentDefault: agentDefault != null,
2673
- hasConversationOverride: conversationOverride != null
2674
- }
2675
- };
2676
- }
2677
- __name(resolveAgentBehaviorPolicy, "resolveAgentBehaviorPolicy");
2678
- function toHistorySenderType(message) {
2679
- if (isMembershipChangeMessage(message)) return null;
2680
- if (message.senderType !== "ai_agent") {
2681
- return "human";
2682
- }
2683
- return shouldTriggerAgentTurn({
2684
- senderType: "ai_agent",
2685
- metadata: message.metadata
2686
- }).allow ? "ai_agent" : null;
2687
- }
2688
- __name(toHistorySenderType, "toHistorySenderType");
2689
- function buildParticipationHistorySnapshotFromSenderTypes(recentSenderTypes) {
2690
- let consecutiveAgentTurns = 0;
2691
- for (const senderType of recentSenderTypes) {
2692
- if (senderType !== "ai_agent") break;
2693
- consecutiveAgentTurns += 1;
2694
- }
2695
- return {
2696
- recentSenderTypes,
2697
- recentHumanCount: recentSenderTypes.filter((senderType) => senderType === "human").length,
2698
- recentAgentCount: recentSenderTypes.filter((senderType) => senderType === "ai_agent").length,
2699
- consecutiveAgentTurns,
2700
- currentAgentStreakStartedByHuman: consecutiveAgentTurns > 0 && recentSenderTypes[consecutiveAgentTurns] === "human"
2701
- };
2702
- }
2703
- __name(buildParticipationHistorySnapshotFromSenderTypes, "buildParticipationHistorySnapshotFromSenderTypes");
2704
- function buildParticipationHistorySnapshot(messages, agentId) {
2705
- void agentId;
2706
- const recentSenderTypes = messages.flatMap((message) => {
2707
- const senderType = toHistorySenderType(message);
2708
- return senderType ? [senderType] : [];
2709
- });
2710
- return buildParticipationHistorySnapshotFromSenderTypes(recentSenderTypes);
2711
- }
2712
- __name(buildParticipationHistorySnapshot, "buildParticipationHistorySnapshot");
2713
- function evaluateParticipationPolicy(policy, input) {
2714
- const resolved = policy ?? resolveAgentBehaviorPolicy({ conversationType: input.conversationType });
2715
- const participation = resolved.participation;
2716
- const priorAgentTurns = Math.max(input.consecutiveAgentTurns ?? 0, 0);
2717
- const agentTurnsWithCurrent = priorAgentTurns + (input.senderType === "ai_agent" ? 1 : 0);
2718
- const consecutiveAgentTurns = Math.max(
2719
- priorAgentTurns,
2720
- input.senderType === "ai_agent" ? 1 : 0
2721
- );
2722
- const currentAgentStreakStartedByHuman = input.currentAgentStreakStartedByHuman === true;
2723
- if (input.conversationType === "group" && participation.requireMentionForGroupReplies && !input.mentionedAgent) {
2724
- return {
2725
- allow: false,
2726
- reasonCode: "group_mention_required",
2727
- reason: "group replies require a direct mention"
2728
- };
2729
- }
2730
- if (input.isOwner) {
2731
- return {
2732
- allow: true,
2733
- reasonCode: "owner_sender",
2734
- reason: "owner messages pass through outside mention-required group turns"
2735
- };
2736
- }
2737
- if (input.senderType !== "ai_agent") {
2738
- return { allow: true, reasonCode: "human_sender", reason: "latest sender is human" };
2739
- }
2740
- if (!participation.allowAgentToAgent) {
2741
- return {
2742
- allow: false,
2743
- reasonCode: "agent_to_agent_disabled",
2744
- reason: "agent-to-agent participation is disabled by policy"
2745
- };
2746
- }
2747
- if (!participation.allowLongRunningCollaboration && (consecutiveAgentTurns > 1 || !currentAgentStreakStartedByHuman)) {
2748
- return {
2749
- allow: false,
2750
- reasonCode: "human_reset_required",
2751
- reason: "a fresh human steer is required before continuing agent collaboration"
2752
- };
2556
+ {
2557
+ type: "object",
2558
+ required: ["status", "conversationId", "messageId", "openingText"],
2559
+ additionalProperties: true,
2560
+ description: "The room is encrypted, so Canon stored no opening text: the sender sends it encrypted under messageId.",
2561
+ properties: {
2562
+ status: { const: "opened" },
2563
+ conversationId: { type: "string" },
2564
+ messageId: { type: "string" },
2565
+ openingText: { const: "deferred" },
2566
+ created: { type: "boolean" },
2567
+ reused: { type: "boolean" }
2568
+ }
2569
+ },
2570
+ {
2571
+ type: "object",
2572
+ required: ["status", "requestId"],
2573
+ additionalProperties: true,
2574
+ properties: {
2575
+ status: { enum: ["requested", "pending"] },
2576
+ requestId: { type: ["string", "null"] },
2577
+ deferredIntentId: {
2578
+ type: ["string", "null"],
2579
+ description: "Present when the send is deferred until the contact request resolves."
2580
+ },
2581
+ conversationId: { type: "string" },
2582
+ messageId: { type: "string" },
2583
+ openingText: {
2584
+ const: "deferred",
2585
+ description: "The room is encrypted, so Canon stored no opening text: the sender sends it encrypted under messageId."
2586
+ }
2587
+ }
2588
+ },
2589
+ {
2590
+ type: "object",
2591
+ required: ["status", "reason"],
2592
+ additionalProperties: true,
2593
+ properties: {
2594
+ status: { enum: ["setup_required", "no_session", "blocked", "unavailable"] },
2595
+ reason: { type: "string" }
2596
+ }
2597
+ }
2598
+ ]
2599
+ };
2600
+ var request_input_input = {
2601
+ type: "object",
2602
+ description: `Ask a human a structured question mid-turn (HITL input card). sudo/secret kinds, sensitive:true, or any isSecret question force owner-only routing server-side; bindings must not let the model redirect the responder for those. Ceiling ${VERB_LIMITS.maxTimeoutMs / 6e4} minutes.`,
2603
+ additionalProperties: false,
2604
+ properties: {
2605
+ conversationId: {
2606
+ type: "string",
2607
+ pattern: VERB_ID_PATTERNS.runtimeId,
2608
+ description: "Bindings default to the active conversation."
2609
+ },
2610
+ inputId: {
2611
+ type: "string",
2612
+ pattern: VERB_ID_PATTERNS.runtimeId,
2613
+ description: "Durable single-use id (reuse -> 409); generated when omitted."
2614
+ },
2615
+ kind: { enum: ["clarify", "sudo", "secret"], default: "clarify" },
2616
+ title: { type: "string", maxLength: VERB_LIMITS.inputTitleChars },
2617
+ prompt: { type: "string", maxLength: VERB_LIMITS.inputPromptChars },
2618
+ choices: { type: "array", maxItems: VERB_LIMITS.inputChoices, items: REF("inputChoice") },
2619
+ questions: { type: "array", maxItems: VERB_LIMITS.inputQuestions, items: REF("inputQuestion") },
2620
+ secretName: { type: "string", maxLength: VERB_LIMITS.inputSecretNameChars },
2621
+ sensitive: { type: "boolean" },
2622
+ responseUserId: {
2623
+ type: "string",
2624
+ pattern: VERB_ID_PATTERNS.runtimeId,
2625
+ description: "Must be a human conversation member. Trust-critical bindings derive this from turn provenance and ignore model-supplied values."
2626
+ },
2627
+ native: REF("native"),
2628
+ turnId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
2629
+ ...timeoutFields(VERB_LIMITS.maxTimeoutMs, "30 minutes")
2753
2630
  }
2754
- if (typeof participation.maxConsecutiveAgentTurns === "number" && participation.maxConsecutiveAgentTurns >= 0 && agentTurnsWithCurrent >= participation.maxConsecutiveAgentTurns) {
2755
- return {
2756
- allow: false,
2757
- reasonCode: "agent_turn_limit_reached",
2758
- reason: "maximum consecutive agent turns reached"
2759
- };
2631
+ };
2632
+ var request_input_result = {
2633
+ oneOf: [
2634
+ {
2635
+ type: "object",
2636
+ required: ["status", "inputId", "value"],
2637
+ additionalProperties: true,
2638
+ properties: {
2639
+ status: { const: "submitted" },
2640
+ inputId: { type: "string" },
2641
+ value: { type: "string", description: "Free-text answer ('' when structured answers were used)." },
2642
+ answers: {
2643
+ type: "object",
2644
+ description: "Structured answers keyed by question id.",
2645
+ additionalProperties: {
2646
+ type: "object",
2647
+ required: ["answers"],
2648
+ properties: { answers: { type: "array", items: { type: "string" } } }
2649
+ }
2650
+ }
2651
+ }
2652
+ },
2653
+ {
2654
+ type: "object",
2655
+ required: ["status", "inputId"],
2656
+ additionalProperties: true,
2657
+ properties: {
2658
+ status: { enum: ["cancelled", "timeout"] },
2659
+ inputId: { type: "string" }
2660
+ }
2661
+ }
2662
+ ]
2663
+ };
2664
+ var request_approval_input = {
2665
+ type: "object",
2666
+ description: `Ask a human to allow/deny an action. mode 'blocking' (default) waits inside the verb call; mode 'detached' returns pending immediately \u2014 fetch the decision later with check_approval. Approval deadline ceiling is ${VERB_LIMITS.maxApprovalTimeoutMs / 36e5} hours (other kinds: 30 minutes). Timeouts fail closed to deny.`,
2667
+ required: ["toolName", "toolSummary"],
2668
+ additionalProperties: false,
2669
+ properties: {
2670
+ conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
2671
+ approvalId: {
2672
+ type: "string",
2673
+ pattern: VERB_ID_PATTERNS.runtimeId,
2674
+ description: "Durable single-use id; server-generated when omitted."
2675
+ },
2676
+ toolName: { type: "string", minLength: 1, maxLength: VERB_LIMITS.toolNameChars },
2677
+ toolSummary: {
2678
+ type: "string",
2679
+ minLength: 1,
2680
+ maxLength: VERB_LIMITS.toolSummaryChars,
2681
+ description: "Pre-redact secrets \u2014 this renders to humans."
2682
+ },
2683
+ mode: { enum: ["blocking", "detached"], default: "blocking" },
2684
+ riskLevel: {
2685
+ enum: ["normal", "destructive"],
2686
+ description: "Server accepts only these values; others are silently dropped, not rejected."
2687
+ },
2688
+ risk: {
2689
+ enum: ["low", "normal", "high", "destructive"],
2690
+ description: "Advisory; server stores any string <= 64 chars today."
2691
+ },
2692
+ category: {
2693
+ enum: ["command", "file", "network", "browser", "mcp", "plugin", "canon", "tool"],
2694
+ description: "Advisory; server stores any string <= 64 chars today."
2695
+ },
2696
+ details: { type: "array", maxItems: VERB_LIMITS.approvalDetails, items: REF("approvalDetail") },
2697
+ diff: REF("unifiedDiff"),
2698
+ native: REF("native"),
2699
+ runtimeId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
2700
+ turnId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
2701
+ responseUserId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
2702
+ allowSessionRule: {
2703
+ type: "boolean",
2704
+ description: "Server forces false when the responder is not the agent owner."
2705
+ },
2706
+ ...timeoutFields(VERB_LIMITS.maxApprovalTimeoutMs, "72 hours")
2760
2707
  }
2761
- return {
2762
- allow: true,
2763
- reasonCode: input.conversationType === "group" ? "group_agent_allowed" : "direct_agent_allowed",
2764
- reason: participation.requireMentionForGroupReplies ? "policy allows this directly mentioned group reply" : "agent-to-agent participation allowed by policy"
2765
- };
2766
- }
2767
- __name(evaluateParticipationPolicy, "evaluateParticipationPolicy");
2768
-
2769
- // ../backend-contracts/src/agentDirectory.ts
2770
- var MAX_DISCOVER_AGENTS_QUERY_LENGTH = 64;
2771
-
2772
- // ../backend-contracts/src/verbContract.ts
2773
- var CANON_VERBS_SCHEMA_ID = "https://canonmsg.com/schemas/canon.verbs.v1.json";
2774
- var VERB_ID_PATTERNS = {
2775
- runtimeId: "^[A-Za-z0-9_.:-]{1,160}$",
2776
- cardId: "^[A-Za-z0-9_.:-]{1,80}$",
2777
- actionId: "^[A-Za-z0-9_.:-]{1,80}$",
2778
- questionId: "^[A-Za-z0-9_.:-]{1,120}$",
2779
- sessionRuleToolPattern: "^[\\w.*:-]{1,128}$",
2780
- runtimeCorrelationValue: "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$",
2781
- runtimeMethod: "^[A-Za-z0-9_][A-Za-z0-9_./:\\-]{0,255}$",
2782
- runtimeHandleKey: "^[A-Za-z0-9_.:-]{1,80}$",
2783
- /** UUID issued by Canon's resumable-media session endpoint. */
2784
- resumableUploadId: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
2785
2708
  };
2786
- var VERB_LIMITS = {
2787
- /** Message text — UTF-8 bytes (functions/src/api/sendMessage.ts MAX_MESSAGE_TEXT_BYTES). */
2788
- messageTextBytes: 4096,
2789
- /**
2790
- * Serialized metadata JSON length in UTF-16 code units — the server checks
2791
- * JSON.stringify(metadata).length, not bytes (sendMessage.ts:716-724 inline;
2792
- * parseBody.ts maxJsonBytes despite the name).
2793
- */
2794
- messageMetadataJsonChars: 4096,
2795
- /** Attachments per message (sendMessage.ts MAX_MESSAGE_ATTACHMENTS). */
2796
- messageAttachments: 10,
2797
- /** Client-supplied messageId — chars and UTF-8 bytes (sendMessage.ts). */
2798
- messageIdChars: 160,
2799
- messageIdBytes: 256,
2800
- /** Self-context note (functions/src/utils/selfContexts.ts SELF_CONTEXT_CONTEXT_LIMIT). */
2801
- selfContextChars: 1e3,
2802
- /** Short private description of an external working context. */
2803
- externalSourceLabelChars: 80,
2804
- /**
2805
- * Contact-request note. Values longer than this are truncated by senders
2806
- * (slice(0,497)+'...'); this constant is the canonical figure.
2807
- */
2808
- contactRequestNoteChars: 500,
2809
- /** Group membership cap incl. creator (functions/src/utils/conversations.ts MAX_GROUP_MEMBERS). */
2810
- groupMembers: 50,
2811
- /** Runtime input (functions/src/api/interactionInput.ts). */
2812
- inputTitleChars: 160,
2813
- inputPromptChars: 4e3,
2814
- inputChoices: 12,
2815
- inputChoiceLabelChars: 120,
2816
- inputChoiceValueChars: 200,
2817
- inputChoiceDescriptionChars: 300,
2818
- inputQuestions: 12,
2819
- inputQuestionChars: 1e3,
2820
- inputQuestionHeaderChars: 120,
2821
- inputSecretNameChars: 160,
2822
- inputAnswerChars: 8192,
2823
- /** Approval (functions/src/api/interactionApproval.ts). */
2824
- toolNameChars: 128,
2825
- toolSummaryChars: 1e3,
2826
- approvalDetails: 8,
2827
- approvalDetailLabelChars: 80,
2828
- approvalDetailValueChars: 500,
2829
- diffFiles: 100,
2830
- diffPathChars: 1024,
2831
- diffFileBytes: 24 * 1024,
2832
- diffTotalBytes: 96 * 1024,
2833
- /** Card envelope acceptance (functions/src/api/interactionCard.ts — server caps;
2834
- * authoring caps in @canonmsg/rich-cards are stricter: title 120, fallback 500, blocks 24). */
2835
- cardEnvelopeBytes: 32 * 1024,
2836
- cardServerTitleChars: 200,
2837
- cardServerFallbackTextChars: 2e3,
2838
- cardServerBlocks: 64,
2839
- /** Response-values caps enforced on submit (callable respondToInteraction.ts
2840
- * MAX_VALUES_BYTES/MAX_VALUES_DEPTH; interactionCard.ts re-checks bytes on
2841
- * consume via MAX_REPLY_BYTES). */
2842
- cardValuesBytes: 8 * 1024,
2843
- cardValuesDepth: 8,
2844
- /** Native correlation metadata (functions/src/api/interactionKinds.ts normalizeNative). */
2845
- nativeKeys: 24,
2846
- nativeValueChars: 256,
2847
- nativeHandles: 16,
2848
- /** Deadlines (functions/src/utils/runtimeRequestHelpers.ts). */
2849
- minTimeoutMs: 1e3,
2850
- /** input/card/plan ceiling (30 minutes). */
2851
- maxTimeoutMs: 30 * 60 * 1e3,
2852
- /** approval-only ceiling (72 hours — owner ruling 2026-07-10). */
2853
- maxApprovalTimeoutMs: 72 * 60 * 60 * 1e3,
2854
- /** turnId / runtimeId fields on interaction creators. */
2855
- turnIdChars: 128,
2856
- /** Reaction key (functions/src/api/reactToMessage.ts normalizeReactionKey). */
2857
- reactionEmojiChars: 64,
2858
- /**
2859
- * Group name authoring cap (enforced on rename via updateNameServer; group
2860
- * CREATE does not length-check today — treat as the authoring contract).
2861
- */
2862
- groupNameChars: 100,
2863
- /**
2864
- * `no_reply.reason` authoring cap. There is no server enforcement beyond the
2865
- * intent schema — the handler logs the reason's presence and nothing else —
2866
- * so this constant is the canonical figure.
2867
- */
2868
- noReplyReasonChars: 500
2709
+ var request_approval_result = {
2710
+ oneOf: [
2711
+ {
2712
+ type: "object",
2713
+ required: ["status", "approvalId"],
2714
+ additionalProperties: true,
2715
+ properties: {
2716
+ status: { const: "allow" },
2717
+ approvalId: { type: "string" },
2718
+ sessionRule: REF("sessionRule"),
2719
+ respondedBy: { type: "string", description: "Canon-authenticated responder userId." }
2720
+ }
2721
+ },
2722
+ {
2723
+ type: "object",
2724
+ required: ["status", "approvalId"],
2725
+ additionalProperties: true,
2726
+ properties: {
2727
+ status: { const: "deny" },
2728
+ approvalId: { type: "string" },
2729
+ sessionRule: REF("sessionRule"),
2730
+ respondedBy: { type: "string" }
2731
+ }
2732
+ },
2733
+ {
2734
+ type: "object",
2735
+ required: ["status", "approvalId"],
2736
+ additionalProperties: true,
2737
+ properties: { status: { const: "timeout" }, approvalId: { type: "string" } }
2738
+ },
2739
+ {
2740
+ type: "object",
2741
+ description: "Detached acceptance \u2014 the decision arrives via check_approval.",
2742
+ required: ["status", "approvalId", "expiresAt"],
2743
+ additionalProperties: true,
2744
+ properties: {
2745
+ status: { const: "pending" },
2746
+ approvalId: { type: "string" },
2747
+ conversationId: { type: "string" },
2748
+ expiresAt: { type: "integer" },
2749
+ responseUserId: { type: "string" }
2750
+ }
2751
+ }
2752
+ ]
2869
2753
  };
2870
- var SELF_CONTEXT_TYPE = "cross_session";
2871
- var CANON_VERB_NAMES = [
2872
- "send_to",
2873
- "request_input",
2874
- "request_approval",
2875
- "check_approval",
2876
- "send_card",
2877
- "request_card",
2878
- "share_contact",
2879
- "react",
2880
- "forward",
2881
- "create_group",
2882
- "add_member",
2883
- "remove_member",
2884
- "leave_conversation",
2885
- "list_contacts",
2886
- "list_conversations",
2887
- "no_reply"
2888
- ];
2889
- var VERB_NATIVE_METADATA_KEYS = [
2890
- "runtime",
2891
- "method",
2892
- "requestId",
2893
- "provider",
2894
- "origin",
2895
- "surface",
2896
- "threadId",
2897
- "turnId",
2898
- "runId",
2899
- "itemId",
2900
- "toolCallId",
2901
- "approvalId",
2902
- "pluginId",
2903
- "sessionKey",
2904
- "nodeId",
2905
- "handles"
2906
- ];
2907
- var VERB_NATIVE_STRING_KEYS = VERB_NATIVE_METADATA_KEYS.filter(
2908
- (key) => key !== "handles"
2909
- );
2910
- var RUNTIME_CORRELATION_VALUE_PATTERN = new RegExp(
2911
- VERB_ID_PATTERNS.runtimeCorrelationValue
2912
- );
2913
- var RUNTIME_METHOD_PATTERN = new RegExp(VERB_ID_PATTERNS.runtimeMethod);
2914
- var RUNTIME_HANDLE_KEY_PATTERN = new RegExp(VERB_ID_PATTERNS.runtimeHandleKey);
2915
- var NO_REPLY_ACK_NOTE = "Acknowledged \u2014 nothing was posted to the conversation.";
2916
-
2917
- // ../backend-contracts/src/verbSchemas.ts
2918
- var REF = /* @__PURE__ */ __name((def) => ({ $ref: `#/$defs/${def}` }), "REF");
2919
- var selfContextDef = {
2754
+ var check_approval_input = {
2920
2755
  type: "object",
2921
- description: "Private note-to-self attached to a cross-conversation send. Visible only to the sending agent (surfaced back as provenance.activeSelfContext); never shown to recipients. Enforced by functions/src/utils/selfContexts.ts.",
2922
- required: ["type", "context"],
2756
+ description: "Fetch the decision of a detached approval. Consume is idempotent within the replay window: a resolved decision replays for up to 72h after first consume, then the id becomes unknown. Bindings must bind the check to the conversation that created the approval.",
2757
+ required: ["approvalId"],
2923
2758
  additionalProperties: false,
2924
2759
  properties: {
2925
- type: { const: SELF_CONTEXT_TYPE },
2926
- context: {
2927
- type: "string",
2928
- minLength: 1,
2929
- maxLength: VERB_LIMITS.selfContextChars,
2930
- description: "What future-you needs to know about this transfer."
2931
- }
2760
+ approvalId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
2761
+ conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId }
2932
2762
  }
2933
2763
  };
2934
- var sessionSelectionDef = {
2935
- description: "How to pick the conversation when targeting a user whose chats are session-shaped (agent targets). Canon default for user-targeted sends: continue_or_create.",
2764
+ var check_approval_result = {
2765
+ description: "'unknown' is NOT a denial \u2014 only 'resolved' carries a decision. Canonical statuses map from the approval interaction consume result as: allow/deny -> resolved.decision, timeout -> expired, pending -> pending, HTTP 404 RUNTIME_APPROVAL_NOT_FOUND -> unknown.",
2936
2766
  oneOf: [
2937
2767
  {
2938
2768
  type: "object",
2939
- required: ["mode"],
2940
- additionalProperties: false,
2941
- properties: { mode: { enum: ["new", "continue_latest", "continue_or_create"] } }
2769
+ required: ["status", "approvalId", "decision"],
2770
+ additionalProperties: true,
2771
+ properties: {
2772
+ status: { const: "resolved" },
2773
+ approvalId: { type: "string" },
2774
+ decision: { enum: ["allow", "deny"] },
2775
+ respondedBy: { type: "string" },
2776
+ conversationId: { type: "string" }
2777
+ }
2942
2778
  },
2943
2779
  {
2944
2780
  type: "object",
2945
- required: ["mode", "conversationId"],
2946
- additionalProperties: false,
2781
+ required: ["status", "approvalId"],
2782
+ additionalProperties: true,
2947
2783
  properties: {
2948
- mode: { const: "specific" },
2949
- conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId }
2784
+ status: { enum: ["pending", "expired", "unknown"] },
2785
+ approvalId: { type: "string" },
2786
+ expiresAt: { type: "integer" },
2787
+ conversationId: { type: "string" }
2950
2788
  }
2951
2789
  }
2952
2790
  ]
2953
2791
  };
2954
- var mediaAttachmentDef = {
2792
+ var send_card_input = {
2955
2793
  type: "object",
2956
- required: ["kind", "url"],
2794
+ description: "Display a card with no actions \u2014 fire-and-forget, no pending state, no deadline.",
2795
+ required: ["card"],
2957
2796
  additionalProperties: false,
2958
2797
  properties: {
2959
- kind: { enum: ["image", "audio", "video", "file"] },
2960
- url: {
2961
- type: "string",
2962
- minLength: 1,
2963
- description: "Must come from a canonical Canon upload/finalization response or the GIF picker \u2014 server allowlist, fail-closed (functions/src/utils/mediaUploadSafety.ts)."
2964
- },
2965
- uploadId: {
2966
- type: "string",
2967
- pattern: VERB_ID_PATTERNS.resumableUploadId,
2968
- description: "Server-issued resumable identity. Preserve it from finalization so the durable message transaction can validate and retain initially temporary media."
2969
- },
2970
- mimeType: { type: "string" },
2971
- fileName: { type: "string" },
2972
- sizeBytes: { type: "number", minimum: 0 },
2973
- width: { type: "number", minimum: 0 },
2974
- height: { type: "number", minimum: 0 },
2975
- durationMs: { type: "number", minimum: 0 },
2976
- thumbnailUrl: { type: "string", minLength: 1 },
2977
- processingStatus: { enum: ["processing", "ready", "failed"] },
2978
- processingErrorCode: {
2979
- enum: ["invalid_video", "video_limits_exceeded", "video_processing_failed"]
2980
- }
2798
+ conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
2799
+ card: REF("runtimeCard"),
2800
+ cardId: { type: "string", pattern: VERB_ID_PATTERNS.cardId },
2801
+ native: REF("native"),
2802
+ runtimeId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
2803
+ turnId: { type: "string", maxLength: VERB_LIMITS.turnIdChars }
2981
2804
  }
2982
2805
  };
2983
- var turnMetadataDef = {
2806
+ var send_card_result = {
2984
2807
  type: "object",
2985
- description: `Free-form metadata envelope; JSON.stringify length <= ${VERB_LIMITS.messageMetadataJsonChars} UTF-16 code units (functions/src/api/sendMessage.ts checks string length, not bytes). Well-known turn-protocol keys below (authoritative type: turnProtocol.ts TurnMetadata, which also carries requestedTurnMode); receipt metadata.type values are server-owned and rejected.`,
2808
+ required: ["status", "cardId"],
2986
2809
  additionalProperties: true,
2987
2810
  properties: {
2988
- turnId: { type: ["string", "null"] },
2989
- turnSemantics: { enum: ["progress", "turn_complete", "control"] },
2990
- deliveryIntent: {
2991
- enum: ["queue", "interrupt", "interleave", "stop"],
2992
- description: "What the recipient runtime should do if the message lands mid-turn. Default queue."
2993
- },
2994
- replyBehavior: { enum: ["allow_auto_reply", "suppress_auto_reply"] }
2811
+ status: { const: "displayed" },
2812
+ cardId: { type: "string" },
2813
+ conversationId: { type: "string" },
2814
+ responseUserId: { type: "string" }
2995
2815
  }
2996
2816
  };
2997
- var messageIdDef = {
2998
- type: "string",
2999
- pattern: VERB_ID_PATTERNS.runtimeId,
3000
- not: {
3001
- pattern: "^(\\.{1,2}|__.*__)$",
3002
- description: "Reserved forms the server 400s: '.', '..', and __x__ ids."
3003
- },
3004
- description: `Client idempotency key (<= ${VERB_LIMITS.messageIdChars} chars / ${VERB_LIMITS.messageIdBytes} UTF-8 bytes; no slashes, control chars, '.', '..', __x__). Same id + identical payload replays idempotently; different payload -> 409 MESSAGE_ID_EXISTS.`
3005
- };
3006
- var messageOptionsDef = {
2817
+ var request_card_input = {
3007
2818
  type: "object",
3008
- description: "Message composition options (mirrors POST /messages/send). Deliberately absent: contact_card contentType (use share_contact) and forwarded/forwardedFrom (forwarding has its own verb \u2014 use forward, not send_to).",
2819
+ description: `Show an interactive card (>= 1 actions block required) and wait for the response. Deadline ceiling ${VERB_LIMITS.maxTimeoutMs / 6e4} minutes.`,
2820
+ required: ["card"],
3009
2821
  additionalProperties: false,
3010
2822
  properties: {
3011
- messageId: REF("messageId"),
3012
- contentType: {
3013
- enum: ["text", "image", "audio", "video", "file"],
3014
- description: "Derived from attachments when omitted. contact_card is deliberately excluded here \u2014 use the share_contact verb."
2823
+ conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
2824
+ card: REF("runtimeCard"),
2825
+ cardId: { type: "string", pattern: VERB_ID_PATTERNS.cardId },
2826
+ responseUserId: {
2827
+ type: "string",
2828
+ pattern: VERB_ID_PATTERNS.runtimeId,
2829
+ description: "Must be a human conversation member; defaults to the turn-triggering human (trust-critical bindings ignore model-supplied values)."
3015
2830
  },
3016
- attachments: {
3017
- type: "array",
3018
- maxItems: VERB_LIMITS.messageAttachments,
3019
- items: REF("mediaAttachment")
2831
+ native: REF("native"),
2832
+ runtimeId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
2833
+ turnId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
2834
+ ...timeoutFields(VERB_LIMITS.maxTimeoutMs, "30 minutes")
2835
+ }
2836
+ };
2837
+ var request_card_result = {
2838
+ oneOf: [
2839
+ {
2840
+ type: "object",
2841
+ required: ["status", "cardId"],
2842
+ additionalProperties: true,
2843
+ properties: {
2844
+ status: { const: "submitted" },
2845
+ cardId: { type: "string" },
2846
+ actionId: { type: "string" },
2847
+ values: {
2848
+ type: "object",
2849
+ description: `Validated server-side against the card's actionFields (validateRuntimeCardFieldValues); <= ${VERB_LIMITS.cardValuesBytes} bytes, depth <= ${VERB_LIMITS.cardValuesDepth}.`
2850
+ },
2851
+ respondedBy: {
2852
+ type: "string",
2853
+ description: "Canon-authenticated responder (server-verified vs responseUserId)."
2854
+ }
2855
+ }
3020
2856
  },
3021
- mentions: {
3022
- type: "array",
3023
- items: { type: "string" },
3024
- description: "Member userIds; every id must be a conversation member (400 otherwise)."
2857
+ {
2858
+ type: "object",
2859
+ required: ["status", "cardId"],
2860
+ additionalProperties: true,
2861
+ properties: {
2862
+ status: { const: "cancelled" },
2863
+ cardId: { type: "string" },
2864
+ respondedBy: { type: "string" }
2865
+ }
3025
2866
  },
3026
- replyTo: { type: "string", description: "Message id being replied to (not existence-validated)." },
3027
- replyToPosition: { type: "integer" },
3028
- metadata: REF("turnMetadata")
3029
- }
2867
+ {
2868
+ type: "object",
2869
+ required: ["status", "cardId"],
2870
+ additionalProperties: true,
2871
+ properties: { status: { const: "timeout" }, cardId: { type: "string" } }
2872
+ }
2873
+ ]
3030
2874
  };
3031
- var inputChoiceDef = {
2875
+ var share_contact_input = {
3032
2876
  type: "object",
3033
- required: ["label"],
2877
+ description: "Share a contact card into a conversation (POST /messages/send, contentType 'contact_card'). The shared user must be in the sending agent's contacts (403 otherwise; the owner shortcut applies only to human senders sharing their own agents). The server snapshots the card.",
2878
+ required: ["conversationId", "contactUserId"],
3034
2879
  additionalProperties: false,
3035
2880
  properties: {
3036
- label: { type: "string", minLength: 1, maxLength: VERB_LIMITS.inputChoiceLabelChars },
3037
- value: { type: "string", maxLength: VERB_LIMITS.inputChoiceValueChars },
3038
- description: { type: "string", maxLength: VERB_LIMITS.inputChoiceDescriptionChars }
2881
+ conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
2882
+ contactUserId: { type: "string", minLength: 1 },
2883
+ text: { type: "string", maxLength: VERB_LIMITS.messageTextBytes },
2884
+ messageId: REF("messageId")
3039
2885
  }
3040
2886
  };
3041
- var inputQuestionDef = {
2887
+ var share_contact_result = {
3042
2888
  type: "object",
3043
- required: ["id", "question"],
3044
- additionalProperties: false,
2889
+ required: ["status", "messageId"],
2890
+ additionalProperties: true,
3045
2891
  properties: {
3046
- id: { type: "string", pattern: VERB_ID_PATTERNS.questionId },
3047
- question: { type: "string", minLength: 1, maxLength: VERB_LIMITS.inputQuestionChars },
3048
- header: { type: "string", maxLength: VERB_LIMITS.inputQuestionHeaderChars },
3049
- choices: { type: "array", maxItems: VERB_LIMITS.inputChoices, items: REF("inputChoice") },
3050
- allowOther: { type: "boolean" },
3051
- isSecret: {
3052
- type: "boolean",
3053
- description: "Forces owner-only routing for the whole request."
3054
- },
3055
- multiSelect: { type: "boolean" }
2892
+ status: { const: "shared" },
2893
+ messageId: { type: "string" }
3056
2894
  }
3057
2895
  };
3058
- var approvalDetailDef = {
2896
+ var react_input = {
3059
2897
  type: "object",
3060
- required: ["label", "value"],
2898
+ description: "Toggle an emoji reaction on a message (server-side atomic toggle; functions/src/api/reactToMessage.ts).",
2899
+ required: ["conversationId", "messageId", "emoji"],
3061
2900
  additionalProperties: false,
3062
2901
  properties: {
3063
- label: { type: "string", minLength: 1, maxLength: VERB_LIMITS.approvalDetailLabelChars },
3064
- value: { type: "string", minLength: 1, maxLength: VERB_LIMITS.approvalDetailValueChars },
3065
- monospace: { type: "boolean" }
2902
+ conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
2903
+ messageId: { type: "string", minLength: 1 },
2904
+ emoji: {
2905
+ type: "string",
2906
+ minLength: 1,
2907
+ maxLength: VERB_LIMITS.reactionEmojiChars,
2908
+ description: "Reaction key. Content-adjacent (D5) \u2014 rides the wire body."
2909
+ }
3066
2910
  }
3067
2911
  };
3068
- var unifiedDiffDef = {
2912
+ var react_result = {
3069
2913
  type: "object",
3070
- description: `Unified diff for file-change approvals. Server sanitizes: sensitive paths suppressed, secret tokens redacted, per-file clip ${VERB_LIMITS.diffFileBytes} bytes, total ${VERB_LIMITS.diffTotalBytes} bytes (functions/src/api/interactionApproval.ts; primitives in backend-contracts diffRedaction.ts).`,
3071
- required: ["files"],
3072
- additionalProperties: false,
2914
+ required: ["status", "action"],
2915
+ additionalProperties: true,
3073
2916
  properties: {
3074
- files: {
3075
- type: "array",
3076
- maxItems: VERB_LIMITS.diffFiles,
3077
- items: {
3078
- type: "object",
3079
- required: ["path", "status"],
3080
- additionalProperties: false,
3081
- properties: {
3082
- path: { type: "string", minLength: 1, maxLength: VERB_LIMITS.diffPathChars },
3083
- status: { enum: ["modified", "created", "deleted", "renamed"] },
3084
- oldPath: { type: "string", maxLength: VERB_LIMITS.diffPathChars },
3085
- additions: { type: "integer", minimum: 0 },
3086
- deletions: { type: "integer", minimum: 0 },
3087
- diff: { type: "string" },
3088
- suppressed: { type: "boolean" }
3089
- }
3090
- }
3091
- },
3092
- truncated: { type: "boolean" }
2917
+ status: { const: "reacted" },
2918
+ action: { enum: ["added", "removed"] },
2919
+ reactions: { type: "object" }
3093
2920
  }
3094
2921
  };
3095
- var sessionRuleDef = {
2922
+ var forward_input = {
3096
2923
  type: "object",
3097
- description: "Standing approval rule. Owner-only: the server forces allowSessionRule=false for non-owner responders.",
3098
- required: ["type"],
2924
+ description: "Forward an existing message into another conversation. Under E2EE this becomes client-side re-encrypt; only routing ids + an optional caption cross the wire.",
2925
+ required: ["sourceConversationId", "targetConversationId", "messageId"],
3099
2926
  additionalProperties: false,
3100
2927
  properties: {
3101
- type: { enum: ["approve-all", "approve-tool", "deny-tool"] },
3102
- toolPattern: { type: "string", pattern: VERB_ID_PATTERNS.sessionRuleToolPattern },
3103
- expiresAt: { type: ["string", "null"], description: "ISO timestamp or null." }
3104
- },
3105
- if: { properties: { type: { enum: ["approve-tool", "deny-tool"] } }, required: ["type"] },
3106
- then: { required: ["toolPattern"], properties: { toolPattern: true } }
2928
+ sourceConversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
2929
+ targetConversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
2930
+ messageId: { type: "string", minLength: 1 },
2931
+ text: {
2932
+ type: "string",
2933
+ maxLength: VERB_LIMITS.messageTextBytes,
2934
+ description: `Optional caption; <= ${VERB_LIMITS.messageTextBytes} UTF-8 bytes.`
2935
+ }
2936
+ }
3107
2937
  };
3108
- var runtimeCardDef = {
2938
+ var forward_result = {
3109
2939
  type: "object",
3110
- description: `ENVELOPE-ONLY validation of a canon.card.v1 document: block structures are NOT checked here. Full document contract: @canonmsg/rich-cards RUNTIME_CARD_JSON_SCHEMA_V1 ($id https://canonmsg.com/schemas/canon.card.v1.json) \u2014 authoring caps title 120 / fallbackText 500 / blocks 24. Server envelope acceptance (interactionCard.ts): <= ${VERB_LIMITS.cardEnvelopeBytes} bytes serialized, title <= ${VERB_LIMITS.cardServerTitleChars}, fallbackText <= ${VERB_LIMITS.cardServerFallbackTextChars}, blocks <= ${VERB_LIMITS.cardServerBlocks}; no secret-bearing top-level keys.`,
3111
- required: ["schema", "title", "fallbackText", "blocks"],
2940
+ required: ["status", "messageId", "targetConversationId"],
3112
2941
  additionalProperties: true,
3113
2942
  properties: {
3114
- schema: { const: "canon.card.v1" },
3115
- cardId: { type: "string", pattern: VERB_ID_PATTERNS.cardId },
3116
- title: { type: "string", minLength: 1, maxLength: VERB_LIMITS.cardServerTitleChars },
3117
- fallbackText: { type: "string", minLength: 1, maxLength: VERB_LIMITS.cardServerFallbackTextChars },
3118
- blocks: { type: "array", minItems: 1, maxItems: VERB_LIMITS.cardServerBlocks }
2943
+ status: { const: "forwarded" },
2944
+ messageId: { type: "string" },
2945
+ targetConversationId: { type: "string" }
3119
2946
  }
3120
2947
  };
3121
- var runtimeCorrelationValueDef = {
3122
- type: "string",
3123
- pattern: VERB_ID_PATTERNS.runtimeCorrelationValue,
3124
- maxLength: VERB_LIMITS.nativeValueChars
3125
- };
3126
- var runtimeMethodDef = {
3127
- type: "string",
3128
- pattern: VERB_ID_PATTERNS.runtimeMethod,
3129
- maxLength: VERB_LIMITS.nativeValueChars
3130
- };
3131
- var VERB_NATIVE_METADATA_JSON_SCHEMA = {
2948
+ var create_group_input = {
3132
2949
  type: "object",
3133
- description: "Content-free runtime correlation ids. Commands, paths, prompts, summaries, and model labels are not permitted in this public envelope.",
3134
- maxProperties: VERB_LIMITS.nativeKeys,
2950
+ description: "Create a group conversation. Each target's groupJoinPolicy is enforced server-side with staged admission: directly-addable members join at creation, approval-required members become pending group_invite requests; hard-denied members are skipped (see the result). A creator-only group is valid when at least one requested member has a pending invite. Under MLS, membership changes are Add/Remove proposals + Commit \u2014 a group operation is a cryptographic state change, not a codec swap.",
2951
+ required: ["memberIds"],
3135
2952
  additionalProperties: false,
3136
2953
  properties: {
3137
- runtime: runtimeCorrelationValueDef,
3138
- method: runtimeMethodDef,
3139
- requestId: runtimeCorrelationValueDef,
3140
- provider: runtimeCorrelationValueDef,
3141
- origin: runtimeCorrelationValueDef,
3142
- surface: runtimeCorrelationValueDef,
3143
- threadId: runtimeCorrelationValueDef,
3144
- turnId: runtimeCorrelationValueDef,
3145
- runId: runtimeCorrelationValueDef,
3146
- itemId: runtimeCorrelationValueDef,
3147
- toolCallId: runtimeCorrelationValueDef,
3148
- approvalId: runtimeCorrelationValueDef,
3149
- pluginId: runtimeCorrelationValueDef,
3150
- sessionKey: runtimeCorrelationValueDef,
3151
- nodeId: runtimeCorrelationValueDef,
3152
- handles: {
3153
- type: "object",
3154
- maxProperties: VERB_LIMITS.nativeHandles,
3155
- propertyNames: { pattern: VERB_ID_PATTERNS.runtimeHandleKey },
3156
- additionalProperties: runtimeCorrelationValueDef
2954
+ name: { type: "string", minLength: 1, maxLength: VERB_LIMITS.groupNameChars },
2955
+ memberIds: {
2956
+ type: "array",
2957
+ minItems: 1,
2958
+ maxItems: VERB_LIMITS.groupMembers - 1,
2959
+ items: { type: "string", minLength: 1 },
2960
+ description: "Other members; the caller is added automatically."
3157
2961
  }
3158
2962
  }
3159
2963
  };
3160
- var timeoutFields = /* @__PURE__ */ __name((maxMs, ceilingNote) => ({
3161
- timeoutMs: {
3162
- type: "integer",
3163
- minimum: VERB_LIMITS.minTimeoutMs,
3164
- maximum: maxMs,
3165
- description: `Relative deadline in ms (server ceiling ${ceilingNote}). expiresAt wins when both are given.`
3166
- },
3167
- expiresAt: {
3168
- type: "integer",
3169
- minimum: 0,
3170
- description: "Absolute epoch-ms deadline. The REST interaction creators REQUIRE a deadline (input interactions also require input subtype kind): bindings must fill defaults and convert timeoutMs into expiresAt before calling the endpoint."
3171
- }
3172
- }), "timeoutFields");
3173
- var send_to_input = {
2964
+ var create_group_result = {
3174
2965
  type: "object",
3175
- description: "Message another conversation or user (admission-aware), optionally carrying a private self-context. Exactly one of targetConversationId / targetUserId / canonContactId. canonContactId is NOT a wire field: bindings resolve it via POST /admission/resolve to a targetUserId first. Projections: selfContext sends -> POST /messages/send-contextual (which requires selfContext and exactly one Canon conversation or external source); known-conversation sends without selfContext -> POST /messages/send; plain user sends -> admission resolve + create + send (the core reachOut composite). User targets resolve admission first: open targets get the message, approval-required targets get a contact request (send deferred and auto-fulfilled on approval), closed targets surface as status 'unavailable' with reason closed.",
3176
- additionalProperties: false,
2966
+ description: "Staged admission: the group exists with the directly-added members; `pending` lists group_invite requests awaiting policy approval; `skipped` lists hard-denied targets.",
2967
+ required: ["status", "conversationId", "added", "pending", "skipped"],
2968
+ additionalProperties: true,
3177
2969
  properties: {
3178
- targetConversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3179
- targetUserId: { type: "string", minLength: 1 },
3180
- canonContactId: { type: "string", minLength: 1 },
3181
- text: {
3182
- type: "string",
3183
- maxLength: VERB_LIMITS.messageTextBytes,
3184
- description: `<= ${VERB_LIMITS.messageTextBytes} UTF-8 bytes. Required unless messageOptions.attachments carries the content.`
3185
- },
3186
- sourceConversationId: {
3187
- type: "string",
3188
- pattern: VERB_ID_PATTERNS.runtimeId,
3189
- description: "Canon source; the agent must be a member. Exclusive with externalSource."
3190
- },
3191
- externalSource: {
3192
- type: "object",
3193
- required: ["kind", "integration"],
3194
- additionalProperties: false,
3195
- properties: {
3196
- kind: { const: "external" },
3197
- integration: { const: "codex" },
3198
- label: { type: "string", minLength: 1, maxLength: VERB_LIMITS.externalSourceLabelChars }
3199
- }
3200
- },
3201
- selfContext: REF("selfContext"),
3202
- requestMessage: {
3203
- type: "string",
3204
- maxLength: VERB_LIMITS.contactRequestNoteChars,
3205
- description: `Contact-request note when admission requires approval; defaults to text; senders truncate to ${VERB_LIMITS.contactRequestNoteChars} chars.`
3206
- },
3207
- sessionSelection: REF("sessionSelection"),
3208
- messageOptions: REF("messageOptions")
3209
- },
3210
- oneOf: [
3211
- { required: ["targetConversationId"], properties: { targetConversationId: true } },
3212
- { required: ["targetUserId"], properties: { targetUserId: true } },
3213
- { required: ["canonContactId"], properties: { canonContactId: true } }
3214
- ],
3215
- allOf: [
3216
- {
3217
- if: { required: ["selfContext"], properties: { selfContext: true } },
3218
- then: { oneOf: [
3219
- { required: ["sourceConversationId"], properties: { sourceConversationId: true } },
3220
- { required: ["externalSource"], properties: { externalSource: true } }
3221
- ] }
3222
- },
3223
- { if: { required: ["externalSource"], properties: { externalSource: true } }, then: { required: ["selfContext"], properties: { selfContext: true } } }
3224
- ]
3225
- };
3226
- var send_to_result = {
3227
- description: "Canonical outcome vocabulary.",
3228
- oneOf: [
3229
- {
3230
- type: "object",
3231
- required: ["status", "conversationId"],
3232
- additionalProperties: true,
3233
- properties: {
3234
- status: { const: "messaged" },
3235
- conversationId: { type: "string" },
3236
- messageId: { type: "string" },
3237
- selfContextId: { type: "string" },
3238
- created: { type: "boolean" },
3239
- reused: { type: "boolean" },
3240
- sessionSelection: { type: "string" }
3241
- }
3242
- },
3243
- {
3244
- type: "object",
3245
- required: ["status", "conversationId", "messageId", "openingText"],
3246
- additionalProperties: true,
3247
- description: "The room is encrypted, so Canon stored no opening text: the sender sends it encrypted under messageId.",
3248
- properties: {
3249
- status: { const: "opened" },
3250
- conversationId: { type: "string" },
3251
- messageId: { type: "string" },
3252
- openingText: { const: "deferred" },
3253
- created: { type: "boolean" },
3254
- reused: { type: "boolean" }
3255
- }
2970
+ status: { const: "created" },
2971
+ conversationId: { type: "string" },
2972
+ added: {
2973
+ type: "array",
2974
+ items: { type: "string" }
3256
2975
  },
3257
- {
3258
- type: "object",
3259
- required: ["status", "requestId"],
3260
- additionalProperties: true,
3261
- properties: {
3262
- status: { enum: ["requested", "pending"] },
3263
- requestId: { type: ["string", "null"] },
3264
- deferredIntentId: {
3265
- type: ["string", "null"],
3266
- description: "Present when the send is deferred until the contact request resolves."
3267
- },
3268
- conversationId: { type: "string" },
3269
- messageId: { type: "string" },
3270
- openingText: {
3271
- const: "deferred",
3272
- description: "The room is encrypted, so Canon stored no opening text: the sender sends it encrypted under messageId."
2976
+ pending: {
2977
+ type: "array",
2978
+ items: {
2979
+ type: "object",
2980
+ required: ["userId", "requestId"],
2981
+ additionalProperties: true,
2982
+ properties: {
2983
+ userId: { type: "string" },
2984
+ requestId: { type: "string" }
3273
2985
  }
3274
2986
  }
3275
2987
  },
3276
- {
3277
- type: "object",
3278
- required: ["status", "reason"],
3279
- additionalProperties: true,
3280
- properties: {
3281
- status: { enum: ["setup_required", "no_session", "blocked", "unavailable"] },
3282
- reason: { type: "string" }
2988
+ skipped: {
2989
+ type: "array",
2990
+ items: {
2991
+ type: "object",
2992
+ required: ["userId", "reason"],
2993
+ additionalProperties: true,
2994
+ properties: {
2995
+ userId: { type: "string" },
2996
+ reason: { type: "string" }
2997
+ }
3283
2998
  }
3284
2999
  }
3285
- ]
3000
+ }
3286
3001
  };
3287
- var request_input_input = {
3002
+ var add_member_input = {
3288
3003
  type: "object",
3289
- description: `Ask a human a structured question mid-turn (HITL input card). sudo/secret kinds, sensitive:true, or any isSecret question force owner-only routing server-side; bindings must not let the model redirect the responder for those. Ceiling ${VERB_LIMITS.maxTimeoutMs / 6e4} minutes.`,
3004
+ description: "A target needing policy approval yields one pending group_invite. Under MLS an add is an Add proposal + Commit (new epoch), not a server-side membership write \u2014 the verb semantics are stable, the mechanism is not.",
3005
+ required: ["conversationId", "userId"],
3290
3006
  additionalProperties: false,
3291
3007
  properties: {
3292
- conversationId: {
3293
- type: "string",
3294
- pattern: VERB_ID_PATTERNS.runtimeId,
3295
- description: "Bindings default to the active conversation."
3296
- },
3297
- inputId: {
3298
- type: "string",
3299
- pattern: VERB_ID_PATTERNS.runtimeId,
3300
- description: "Durable single-use id (reuse -> 409); generated when omitted."
3301
- },
3302
- kind: { enum: ["clarify", "sudo", "secret"], default: "clarify" },
3303
- title: { type: "string", maxLength: VERB_LIMITS.inputTitleChars },
3304
- prompt: { type: "string", maxLength: VERB_LIMITS.inputPromptChars },
3305
- choices: { type: "array", maxItems: VERB_LIMITS.inputChoices, items: REF("inputChoice") },
3306
- questions: { type: "array", maxItems: VERB_LIMITS.inputQuestions, items: REF("inputQuestion") },
3307
- secretName: { type: "string", maxLength: VERB_LIMITS.inputSecretNameChars },
3308
- sensitive: { type: "boolean" },
3309
- responseUserId: {
3310
- type: "string",
3311
- pattern: VERB_ID_PATTERNS.runtimeId,
3312
- description: "Must be a human conversation member. Trust-critical bindings derive this from turn provenance and ignore model-supplied values."
3313
- },
3314
- native: REF("native"),
3315
- turnId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
3316
- ...timeoutFields(VERB_LIMITS.maxTimeoutMs, "30 minutes")
3008
+ conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3009
+ userId: { type: "string", minLength: 1 }
3317
3010
  }
3318
3011
  };
3319
- var request_input_result = {
3012
+ var add_member_result = {
3013
+ description: "Targets needing policy approval yield an exact pending group invite.",
3320
3014
  oneOf: [
3321
3015
  {
3322
3016
  type: "object",
3323
- required: ["status", "inputId", "value"],
3017
+ required: ["status"],
3324
3018
  additionalProperties: true,
3325
- properties: {
3326
- status: { const: "submitted" },
3327
- inputId: { type: "string" },
3328
- value: { type: "string", description: "Free-text answer ('' when structured answers were used)." },
3329
- answers: {
3330
- type: "object",
3331
- description: "Structured answers keyed by question id.",
3332
- additionalProperties: {
3333
- type: "object",
3334
- required: ["answers"],
3335
- properties: { answers: { type: "array", items: { type: "string" } } }
3336
- }
3337
- }
3338
- }
3019
+ properties: { status: { const: "added" } }
3339
3020
  },
3340
3021
  {
3341
3022
  type: "object",
3342
- required: ["status", "inputId"],
3023
+ required: ["status", "requestId"],
3343
3024
  additionalProperties: true,
3344
3025
  properties: {
3345
- status: { enum: ["cancelled", "timeout"] },
3346
- inputId: { type: "string" }
3026
+ status: { const: "pending" },
3027
+ requestId: { type: "string" }
3347
3028
  }
3348
3029
  }
3349
3030
  ]
3350
3031
  };
3351
- var request_approval_input = {
3032
+ var remove_member_input = {
3352
3033
  type: "object",
3353
- description: `Ask a human to allow/deny an action. mode 'blocking' (default) waits inside the verb call; mode 'detached' returns pending immediately \u2014 fetch the decision later with check_approval. Approval deadline ceiling is ${VERB_LIMITS.maxApprovalTimeoutMs / 36e5} hours (other kinds: 30 minutes). Timeouts fail closed to deny.`,
3354
- required: ["toolName", "toolSummary"],
3034
+ description: "Requester must be a group owner/admin (server-enforced). Under MLS a removal is a Remove proposal + Commit that rotates the group secret away from the removed leaf.",
3035
+ required: ["conversationId", "userId"],
3355
3036
  additionalProperties: false,
3356
3037
  properties: {
3357
3038
  conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3358
- approvalId: {
3359
- type: "string",
3360
- pattern: VERB_ID_PATTERNS.runtimeId,
3361
- description: "Durable single-use id; server-generated when omitted."
3362
- },
3363
- toolName: { type: "string", minLength: 1, maxLength: VERB_LIMITS.toolNameChars },
3364
- toolSummary: {
3365
- type: "string",
3366
- minLength: 1,
3367
- maxLength: VERB_LIMITS.toolSummaryChars,
3368
- description: "Pre-redact secrets \u2014 this renders to humans."
3369
- },
3370
- mode: { enum: ["blocking", "detached"], default: "blocking" },
3371
- riskLevel: {
3372
- enum: ["normal", "destructive"],
3373
- description: "Server accepts only these values; others are silently dropped, not rejected."
3374
- },
3375
- risk: {
3376
- enum: ["low", "normal", "high", "destructive"],
3377
- description: "Advisory; server stores any string <= 64 chars today."
3378
- },
3379
- category: {
3380
- enum: ["command", "file", "network", "browser", "mcp", "plugin", "canon", "tool"],
3381
- description: "Advisory; server stores any string <= 64 chars today."
3382
- },
3383
- details: { type: "array", maxItems: VERB_LIMITS.approvalDetails, items: REF("approvalDetail") },
3384
- diff: REF("unifiedDiff"),
3385
- native: REF("native"),
3386
- runtimeId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
3387
- turnId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
3388
- responseUserId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3389
- allowSessionRule: {
3390
- type: "boolean",
3391
- description: "Server forces false when the responder is not the agent owner."
3392
- },
3393
- ...timeoutFields(VERB_LIMITS.maxApprovalTimeoutMs, "72 hours")
3039
+ userId: { type: "string", minLength: 1 }
3394
3040
  }
3395
3041
  };
3396
- var request_approval_result = {
3397
- oneOf: [
3398
- {
3399
- type: "object",
3400
- required: ["status", "approvalId"],
3401
- additionalProperties: true,
3402
- properties: {
3403
- status: { const: "allow" },
3404
- approvalId: { type: "string" },
3405
- sessionRule: REF("sessionRule"),
3406
- respondedBy: { type: "string", description: "Canon-authenticated responder userId." }
3407
- }
3408
- },
3409
- {
3410
- type: "object",
3411
- required: ["status", "approvalId"],
3412
- additionalProperties: true,
3413
- properties: {
3414
- status: { const: "deny" },
3415
- approvalId: { type: "string" },
3416
- sessionRule: REF("sessionRule"),
3417
- respondedBy: { type: "string" }
3418
- }
3419
- },
3420
- {
3421
- type: "object",
3422
- required: ["status", "approvalId"],
3423
- additionalProperties: true,
3424
- properties: { status: { const: "timeout" }, approvalId: { type: "string" } }
3425
- },
3426
- {
3427
- type: "object",
3428
- description: "Detached acceptance \u2014 the decision arrives via check_approval.",
3429
- required: ["status", "approvalId", "expiresAt"],
3430
- additionalProperties: true,
3431
- properties: {
3432
- status: { const: "pending" },
3433
- approvalId: { type: "string" },
3434
- conversationId: { type: "string" },
3435
- expiresAt: { type: "integer" },
3436
- responseUserId: { type: "string" }
3437
- }
3438
- }
3439
- ]
3042
+ var remove_member_result = {
3043
+ type: "object",
3044
+ required: ["status"],
3045
+ additionalProperties: true,
3046
+ properties: { status: { const: "removed" } }
3440
3047
  };
3441
- var check_approval_input = {
3048
+ var leave_conversation_input = {
3442
3049
  type: "object",
3443
- description: "Fetch the decision of a detached approval. Consume is idempotent within the replay window: a resolved decision replays for up to 72h after first consume, then the id becomes unknown. Bindings must bind the check to the conversation that created the approval.",
3444
- required: ["approvalId"],
3050
+ required: ["conversationId"],
3445
3051
  additionalProperties: false,
3446
3052
  properties: {
3447
- approvalId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3448
3053
  conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId }
3449
3054
  }
3450
3055
  };
3451
- var check_approval_result = {
3452
- description: "'unknown' is NOT a denial \u2014 only 'resolved' carries a decision. Canonical statuses map from the approval interaction consume result as: allow/deny -> resolved.decision, timeout -> expired, pending -> pending, HTTP 404 RUNTIME_APPROVAL_NOT_FOUND -> unknown.",
3453
- oneOf: [
3454
- {
3455
- type: "object",
3456
- required: ["status", "approvalId", "decision"],
3457
- additionalProperties: true,
3458
- properties: {
3459
- status: { const: "resolved" },
3460
- approvalId: { type: "string" },
3461
- decision: { enum: ["allow", "deny"] },
3462
- respondedBy: { type: "string" },
3463
- conversationId: { type: "string" }
3464
- }
3465
- },
3466
- {
3467
- type: "object",
3468
- required: ["status", "approvalId"],
3469
- additionalProperties: true,
3470
- properties: {
3471
- status: { enum: ["pending", "expired", "unknown"] },
3472
- approvalId: { type: "string" },
3473
- expiresAt: { type: "integer" },
3474
- conversationId: { type: "string" }
3475
- }
3476
- }
3477
- ]
3056
+ var leave_conversation_result = {
3057
+ type: "object",
3058
+ required: ["status"],
3059
+ additionalProperties: true,
3060
+ properties: { status: { const: "left" } }
3478
3061
  };
3479
- var send_card_input = {
3062
+ var list_contacts_input = {
3480
3063
  type: "object",
3481
- description: "Display a card with no actions \u2014 fire-and-forget, no pending state, no deadline.",
3482
- required: ["card"],
3483
3064
  additionalProperties: false,
3484
- properties: {
3485
- conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3486
- card: REF("runtimeCard"),
3487
- cardId: { type: "string", pattern: VERB_ID_PATTERNS.cardId },
3488
- native: REF("native"),
3489
- runtimeId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
3490
- turnId: { type: "string", maxLength: VERB_LIMITS.turnIdChars }
3491
- }
3065
+ properties: {}
3492
3066
  };
3493
- var send_card_result = {
3067
+ var list_contacts_result = {
3494
3068
  type: "object",
3495
- required: ["status", "cardId"],
3069
+ required: ["contacts"],
3496
3070
  additionalProperties: true,
3497
3071
  properties: {
3498
- status: { const: "displayed" },
3499
- cardId: { type: "string" },
3500
- conversationId: { type: "string" },
3501
- responseUserId: { type: "string" }
3502
- }
3503
- };
3504
- var request_card_input = {
3505
- type: "object",
3506
- description: `Show an interactive card (>= 1 actions block required) and wait for the response. Deadline ceiling ${VERB_LIMITS.maxTimeoutMs / 6e4} minutes.`,
3507
- required: ["card"],
3508
- additionalProperties: false,
3509
- properties: {
3510
- conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3511
- card: REF("runtimeCard"),
3512
- cardId: { type: "string", pattern: VERB_ID_PATTERNS.cardId },
3513
- responseUserId: {
3514
- type: "string",
3515
- pattern: VERB_ID_PATTERNS.runtimeId,
3516
- description: "Must be a human conversation member; defaults to the turn-triggering human (trust-critical bindings ignore model-supplied values)."
3517
- },
3518
- native: REF("native"),
3519
- runtimeId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
3520
- turnId: { type: "string", maxLength: VERB_LIMITS.turnIdChars },
3521
- ...timeoutFields(VERB_LIMITS.maxTimeoutMs, "30 minutes")
3522
- }
3523
- };
3524
- var request_card_result = {
3525
- oneOf: [
3526
- {
3527
- type: "object",
3528
- required: ["status", "cardId"],
3529
- additionalProperties: true,
3530
- properties: {
3531
- status: { const: "submitted" },
3532
- cardId: { type: "string" },
3533
- actionId: { type: "string" },
3534
- values: {
3535
- type: "object",
3536
- description: `Validated server-side against the card's actionFields (validateRuntimeCardFieldValues); <= ${VERB_LIMITS.cardValuesBytes} bytes, depth <= ${VERB_LIMITS.cardValuesDepth}.`
3537
- },
3538
- respondedBy: {
3539
- type: "string",
3540
- description: "Canon-authenticated responder (server-verified vs responseUserId)."
3072
+ contacts: {
3073
+ type: "array",
3074
+ items: {
3075
+ type: "object",
3076
+ required: ["id", "source", "addedAt", "displayNameOverride"],
3077
+ additionalProperties: true,
3078
+ properties: {
3079
+ id: { type: "string", description: "The contact's userId." },
3080
+ source: {
3081
+ type: "string",
3082
+ description: "Vocabulary: direct_add | phone_book | contact_request | link | qr | group | open_inbound_message | unknown (server passes strings through)."
3083
+ },
3084
+ addedAt: { type: ["string", "null"] },
3085
+ displayNameOverride: { type: ["string", "null"] }
3541
3086
  }
3542
3087
  }
3543
- },
3544
- {
3545
- type: "object",
3546
- required: ["status", "cardId"],
3547
- additionalProperties: true,
3548
- properties: {
3549
- status: { const: "cancelled" },
3550
- cardId: { type: "string" },
3551
- respondedBy: { type: "string" }
3552
- }
3553
- },
3554
- {
3555
- type: "object",
3556
- required: ["status", "cardId"],
3557
- additionalProperties: true,
3558
- properties: { status: { const: "timeout" }, cardId: { type: "string" } }
3559
3088
  }
3560
- ]
3561
- };
3562
- var share_contact_input = {
3563
- type: "object",
3564
- description: "Share a contact card into a conversation (POST /messages/send, contentType 'contact_card'). The shared user must be in the sending agent's contacts (403 otherwise; the owner shortcut applies only to human senders sharing their own agents). The server snapshots the card.",
3565
- required: ["conversationId", "contactUserId"],
3566
- additionalProperties: false,
3567
- properties: {
3568
- conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3569
- contactUserId: { type: "string", minLength: 1 },
3570
- text: { type: "string", maxLength: VERB_LIMITS.messageTextBytes },
3571
- messageId: REF("messageId")
3572
- }
3573
- };
3574
- var share_contact_result = {
3575
- type: "object",
3576
- required: ["status", "messageId"],
3577
- additionalProperties: true,
3578
- properties: {
3579
- status: { const: "shared" },
3580
- messageId: { type: "string" }
3581
3089
  }
3582
3090
  };
3583
- var react_input = {
3091
+ var list_conversations_input = {
3584
3092
  type: "object",
3585
- description: "Toggle an emoji reaction on a message (server-side atomic toggle; functions/src/api/reactToMessage.ts).",
3586
- required: ["conversationId", "messageId", "emoji"],
3587
3093
  additionalProperties: false,
3588
3094
  properties: {
3589
- conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3590
- messageId: { type: "string", minLength: 1 },
3591
- emoji: {
3592
- type: "string",
3593
- minLength: 1,
3594
- maxLength: VERB_LIMITS.reactionEmojiChars,
3595
- description: "Reaction key. Content-adjacent (D5) \u2014 rides the wire body."
3095
+ limit: {
3096
+ type: "integer",
3097
+ minimum: 1,
3098
+ description: "Optional client-side cap applied by the binding; the REST endpoint has no pagination and returns all visible memberships."
3596
3099
  }
3597
3100
  }
3598
3101
  };
3599
- var react_result = {
3102
+ var list_conversations_result = {
3600
3103
  type: "object",
3601
- required: ["status", "action"],
3104
+ required: ["conversations"],
3602
3105
  additionalProperties: true,
3603
3106
  properties: {
3604
- status: { const: "reacted" },
3605
- action: { enum: ["added", "removed"] },
3606
- reactions: { type: "object" }
3107
+ conversations: {
3108
+ type: "array",
3109
+ items: {
3110
+ type: "object",
3111
+ // topic/lastMessage/createdAt are ??-null'd by the serializer (always
3112
+ // present); name is a raw passthrough and may be absent on direct chats.
3113
+ required: ["id", "type", "memberIds", "isAgentChat", "topic", "lastMessage", "createdAt"],
3114
+ additionalProperties: true,
3115
+ properties: {
3116
+ id: { type: "string" },
3117
+ type: { enum: ["direct", "group"] },
3118
+ name: { type: ["string", "null"] },
3119
+ topic: { type: ["string", "null"] },
3120
+ memberIds: { type: "array", items: { type: "string" } },
3121
+ originalParticipantIds: { type: "array", items: { type: "string" }, minItems: 2, maxItems: 2, uniqueItems: true },
3122
+ isAgentChat: { type: "boolean" },
3123
+ hasUnread: { type: "boolean" },
3124
+ lastMessage: {
3125
+ type: ["object", "null"],
3126
+ additionalProperties: true,
3127
+ properties: {
3128
+ text: { type: ["string", "null"] },
3129
+ messageId: { type: "string" },
3130
+ senderId: { type: "string" },
3131
+ senderType: { type: "string" },
3132
+ contentType: { type: "string" },
3133
+ timestamp: { type: ["string", "null"] }
3134
+ }
3135
+ },
3136
+ createdAt: { type: ["string", "null"] }
3137
+ }
3138
+ }
3139
+ }
3607
3140
  }
3608
3141
  };
3609
- var forward_input = {
3142
+ var no_reply_input = {
3610
3143
  type: "object",
3611
- description: "Forward an existing message into another conversation. Under E2EE this becomes client-side re-encrypt; only routing ids + an optional caption cross the wire.",
3612
- required: ["sourceConversationId", "targetConversationId", "messageId"],
3144
+ description: "End this turn without posting anything to the conversation. Nothing is rendered and no other member or agent is triggered.",
3613
3145
  additionalProperties: false,
3614
3146
  properties: {
3615
- sourceConversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3616
- targetConversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3617
- messageId: { type: "string", minLength: 1 },
3618
- text: {
3147
+ conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3148
+ messageId: {
3619
3149
  type: "string",
3620
- maxLength: VERB_LIMITS.messageTextBytes,
3621
- description: `Optional caption; <= ${VERB_LIMITS.messageTextBytes} UTF-8 bytes.`
3150
+ pattern: VERB_ID_PATTERNS.runtimeId,
3151
+ description: "The triggering message this silence answers, so the record cannot be misread against a later message."
3152
+ },
3153
+ reason: {
3154
+ type: "string",
3155
+ maxLength: VERB_LIMITS.noReplyReasonChars,
3156
+ description: "Never rendered; logged only."
3622
3157
  }
3623
3158
  }
3624
3159
  };
3625
- var forward_result = {
3160
+ var no_reply_result = {
3626
3161
  type: "object",
3627
- required: ["status", "messageId", "targetConversationId"],
3162
+ required: ["status"],
3628
3163
  additionalProperties: true,
3629
3164
  properties: {
3630
- status: { const: "forwarded" },
3631
- messageId: { type: "string" },
3632
- targetConversationId: { type: "string" }
3633
- }
3634
- };
3635
- var create_group_input = {
3636
- type: "object",
3637
- description: "Create a group conversation. Each target's groupJoinPolicy is enforced server-side with staged admission: directly-addable members join at creation, approval-required members become pending group_invite requests; hard-denied members are skipped (see the result). A creator-only group is valid when at least one requested member has a pending invite. Under MLS, membership changes are Add/Remove proposals + Commit \u2014 a group operation is a cryptographic state change, not a codec swap.",
3638
- required: ["memberIds"],
3639
- additionalProperties: false,
3640
- properties: {
3641
- name: { type: "string", minLength: 1, maxLength: VERB_LIMITS.groupNameChars },
3642
- memberIds: {
3643
- type: "array",
3644
- minItems: 1,
3645
- maxItems: VERB_LIMITS.groupMembers - 1,
3646
- items: { type: "string", minLength: 1 },
3647
- description: "Other members; the caller is added automatically."
3648
- }
3649
- }
3650
- };
3651
- var create_group_result = {
3652
- type: "object",
3653
- description: "Staged admission: the group exists with the directly-added members; `pending` lists group_invite requests awaiting policy approval; `skipped` lists hard-denied targets.",
3654
- required: ["status", "conversationId", "added", "pending", "skipped"],
3655
- additionalProperties: true,
3656
- properties: {
3657
- status: { const: "created" },
3658
- conversationId: { type: "string" },
3659
- added: {
3660
- type: "array",
3661
- items: { type: "string" }
3662
- },
3663
- pending: {
3664
- type: "array",
3665
- items: {
3666
- type: "object",
3667
- required: ["userId", "requestId"],
3668
- additionalProperties: true,
3669
- properties: {
3670
- userId: { type: "string" },
3671
- requestId: { type: "string" }
3672
- }
3673
- }
3674
- },
3675
- skipped: {
3676
- type: "array",
3677
- items: {
3678
- type: "object",
3679
- required: ["userId", "reason"],
3680
- additionalProperties: true,
3681
- properties: {
3682
- userId: { type: "string" },
3683
- reason: { type: "string" }
3684
- }
3685
- }
3686
- }
3687
- }
3688
- };
3689
- var add_member_input = {
3690
- type: "object",
3691
- description: "A target needing policy approval yields one pending group_invite. Under MLS an add is an Add proposal + Commit (new epoch), not a server-side membership write \u2014 the verb semantics are stable, the mechanism is not.",
3692
- required: ["conversationId", "userId"],
3693
- additionalProperties: false,
3694
- properties: {
3695
- conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3696
- userId: { type: "string", minLength: 1 }
3697
- }
3698
- };
3699
- var add_member_result = {
3700
- description: "Targets needing policy approval yield an exact pending group invite.",
3701
- oneOf: [
3702
- {
3703
- type: "object",
3704
- required: ["status"],
3705
- additionalProperties: true,
3706
- properties: { status: { const: "added" } }
3707
- },
3708
- {
3709
- type: "object",
3710
- required: ["status", "requestId"],
3711
- additionalProperties: true,
3712
- properties: {
3713
- status: { const: "pending" },
3714
- requestId: { type: "string" }
3715
- }
3716
- }
3717
- ]
3718
- };
3719
- var remove_member_input = {
3720
- type: "object",
3721
- description: "Requester must be a group owner/admin (server-enforced). Under MLS a removal is a Remove proposal + Commit that rotates the group secret away from the removed leaf.",
3722
- required: ["conversationId", "userId"],
3723
- additionalProperties: false,
3724
- properties: {
3725
- conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3726
- userId: { type: "string", minLength: 1 }
3727
- }
3728
- };
3729
- var remove_member_result = {
3730
- type: "object",
3731
- required: ["status"],
3732
- additionalProperties: true,
3733
- properties: { status: { const: "removed" } }
3734
- };
3735
- var leave_conversation_input = {
3736
- type: "object",
3737
- required: ["conversationId"],
3738
- additionalProperties: false,
3739
- properties: {
3740
- conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId }
3741
- }
3742
- };
3743
- var leave_conversation_result = {
3744
- type: "object",
3745
- required: ["status"],
3746
- additionalProperties: true,
3747
- properties: { status: { const: "left" } }
3748
- };
3749
- var list_contacts_input = {
3750
- type: "object",
3751
- additionalProperties: false,
3752
- properties: {}
3753
- };
3754
- var list_contacts_result = {
3755
- type: "object",
3756
- required: ["contacts"],
3757
- additionalProperties: true,
3758
- properties: {
3759
- contacts: {
3760
- type: "array",
3761
- items: {
3762
- type: "object",
3763
- required: ["id", "source", "addedAt", "displayNameOverride"],
3764
- additionalProperties: true,
3765
- properties: {
3766
- id: { type: "string", description: "The contact's userId." },
3767
- source: {
3768
- type: "string",
3769
- description: "Vocabulary: direct_add | phone_book | contact_request | link | qr | group | open_inbound_message | unknown (server passes strings through)."
3770
- },
3771
- addedAt: { type: ["string", "null"] },
3772
- displayNameOverride: { type: ["string", "null"] }
3773
- }
3774
- }
3775
- }
3776
- }
3777
- };
3778
- var list_conversations_input = {
3779
- type: "object",
3780
- additionalProperties: false,
3781
- properties: {
3782
- limit: {
3783
- type: "integer",
3784
- minimum: 1,
3785
- description: "Optional client-side cap applied by the binding; the REST endpoint has no pagination and returns all visible memberships."
3786
- }
3787
- }
3788
- };
3789
- var list_conversations_result = {
3790
- type: "object",
3791
- required: ["conversations"],
3792
- additionalProperties: true,
3793
- properties: {
3794
- conversations: {
3795
- type: "array",
3796
- items: {
3797
- type: "object",
3798
- // topic/lastMessage/createdAt are ??-null'd by the serializer (always
3799
- // present); name is a raw passthrough and may be absent on direct chats.
3800
- required: ["id", "type", "memberIds", "isAgentChat", "topic", "lastMessage", "createdAt"],
3801
- additionalProperties: true,
3802
- properties: {
3803
- id: { type: "string" },
3804
- type: { enum: ["direct", "group"] },
3805
- name: { type: ["string", "null"] },
3806
- topic: { type: ["string", "null"] },
3807
- memberIds: { type: "array", items: { type: "string" } },
3808
- originalParticipantIds: { type: "array", items: { type: "string" }, minItems: 2, maxItems: 2, uniqueItems: true },
3809
- isAgentChat: { type: "boolean" },
3810
- hasUnread: { type: "boolean" },
3811
- lastMessage: {
3812
- type: ["object", "null"],
3813
- additionalProperties: true,
3814
- properties: {
3815
- text: { type: ["string", "null"] },
3816
- messageId: { type: "string" },
3817
- senderId: { type: "string" },
3818
- senderType: { type: "string" },
3819
- contentType: { type: "string" },
3820
- timestamp: { type: ["string", "null"] }
3821
- }
3822
- },
3823
- createdAt: { type: ["string", "null"] }
3824
- }
3825
- }
3826
- }
3827
- }
3828
- };
3829
- var no_reply_input = {
3830
- type: "object",
3831
- description: "End this turn without posting anything to the conversation. Nothing is rendered and no other member or agent is triggered.",
3832
- additionalProperties: false,
3833
- properties: {
3834
- conversationId: { type: "string", pattern: VERB_ID_PATTERNS.runtimeId },
3835
- messageId: {
3836
- type: "string",
3837
- pattern: VERB_ID_PATTERNS.runtimeId,
3838
- description: "The triggering message this silence answers, so the record cannot be misread against a later message."
3839
- },
3840
- reason: {
3841
- type: "string",
3842
- maxLength: VERB_LIMITS.noReplyReasonChars,
3843
- description: "Never rendered; logged only."
3844
- }
3845
- }
3846
- };
3847
- var no_reply_result = {
3848
- type: "object",
3849
- required: ["status"],
3850
- additionalProperties: true,
3851
- properties: {
3852
- status: { const: "acknowledged" },
3853
- conversationId: { type: "string" },
3854
- note: { type: "string" }
3165
+ status: { const: "acknowledged" },
3166
+ conversationId: { type: "string" },
3167
+ note: { type: "string" }
3855
3168
  }
3856
3169
  };
3857
3170
  var CANON_VERBS_JSON_SCHEMA = {
@@ -4341,219 +3654,6 @@ function projectVerbIntentToWire(verb, intent, options) {
4341
3654
  }
4342
3655
  __name(projectVerbIntentToWire, "projectVerbIntentToWire");
4343
3656
 
4344
- // ../backend-contracts/src/communication.ts
4345
- function parseDirectConversationSelection(value) {
4346
- if (value == null) return { mode: "latest_or_new" };
4347
- if (!value || typeof value !== "object" || Array.isArray(value)) {
4348
- throw new Error("selection must be an object");
4349
- }
4350
- const record4 = value;
4351
- if (record4.mode === "latest_or_new" || record4.mode === "new") {
4352
- return { mode: record4.mode };
4353
- }
4354
- if (record4.mode === "specific") {
4355
- const conversationId = typeof record4.conversationId === "string" ? record4.conversationId.trim() : "";
4356
- if (!conversationId) throw new Error("selection.conversationId is required");
4357
- return { mode: "specific", conversationId };
4358
- }
4359
- throw new Error("selection.mode is invalid");
4360
- }
4361
- __name(parseDirectConversationSelection, "parseDirectConversationSelection");
4362
-
4363
- // ../backend-contracts/src/runtimeDescriptor.ts
4364
- function normalizeRuntimeCommandAlias(value) {
4365
- const normalized = value.trim().replace(/^\/+/, "").replace(/\s+/g, "-");
4366
- return /^[A-Za-z0-9:_-]+$/.test(normalized) ? normalized : null;
4367
- }
4368
- __name(normalizeRuntimeCommandAlias, "normalizeRuntimeCommandAlias");
4369
- function normalizeRuntimeCommandAliases(command) {
4370
- const rawAliases = command.aliases?.length ? command.aliases : [command.id];
4371
- const aliases = rawAliases.map(normalizeRuntimeCommandAlias).filter((value) => Boolean(value));
4372
- return Array.from(new Set(aliases));
4373
- }
4374
- __name(normalizeRuntimeCommandAliases, "normalizeRuntimeCommandAliases");
4375
- var MAX_PUBLIC_RUNTIME_COMMANDS = 256;
4376
- function record(value) {
4377
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
4378
- }
4379
- __name(record, "record");
4380
- function text3(value, max) {
4381
- return typeof value === "string" && value.length > 0 && value.length <= max ? value : void 0;
4382
- }
4383
- __name(text3, "text");
4384
- function member(value, choices) {
4385
- return choices.includes(value) ? value : void 0;
4386
- }
4387
- __name(member, "member");
4388
- function members(value, choices) {
4389
- if (!Array.isArray(value)) return void 0;
4390
- return [...new Set(value.filter((entry) => choices.includes(entry)))];
4391
- }
4392
- __name(members, "members");
4393
- function defined(value) {
4394
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
4395
- }
4396
- __name(defined, "defined");
4397
- var PRIMITIVES = [
4398
- "runtime.status",
4399
- "runtime.reasoning.set",
4400
- "runtime.verbosity.set",
4401
- "runtime.usage",
4402
- "context.compact",
4403
- "session.new",
4404
- "session.reset"
4405
- ];
4406
- function normalizeDispatch(value) {
4407
- const dispatch = record(value);
4408
- switch (dispatch.kind) {
4409
- case "signal": {
4410
- const signal = member(dispatch.signal, ["interrupt", "stop_and_drop", "new_session"]);
4411
- return signal ? { kind: "signal", signal } : void 0;
4412
- }
4413
- case "primitive": {
4414
- const primitive = member(dispatch.primitive, PRIMITIVES);
4415
- return primitive ? { kind: "primitive", primitive } : void 0;
4416
- }
4417
- case "control": {
4418
- const controlId = text3(dispatch.controlId, 128);
4419
- return controlId ? defined({ kind: "control", controlId, value: text3(dispatch.value, 1024) }) : void 0;
4420
- }
4421
- case "text_passthrough": {
4422
- const template = text3(dispatch.template, 2048);
4423
- return template ? { kind: "text_passthrough", template } : void 0;
4424
- }
4425
- case "compose": {
4426
- const content = text3(dispatch.text, 2048);
4427
- return content ? { kind: "compose", text: content } : void 0;
4428
- }
4429
- case "open_details":
4430
- return defined({ kind: "open_details", target: text3(dispatch.target, 128) });
4431
- default:
4432
- return void 0;
4433
- }
4434
- }
4435
- __name(normalizeDispatch, "normalizeDispatch");
4436
- function normalizeArgument(value) {
4437
- const arg = record(value);
4438
- const id7 = text3(arg.id, 128);
4439
- const label = text3(arg.label, 160);
4440
- const kind = member(arg.kind, ["string", "enum", "boolean"]);
4441
- if (!id7 || !label || !kind) return void 0;
4442
- if (["required", "captureRemaining"].some((key) => arg[key] !== void 0 && typeof arg[key] !== "boolean")) return void 0;
4443
- if (arg.choices !== void 0 && !Array.isArray(arg.choices)) return void 0;
4444
- const choices = Array.isArray(arg.choices) ? arg.choices.slice(0, 128).flatMap((value2) => {
4445
- const choice = record(value2);
4446
- const entry = text3(choice.value, 1024);
4447
- const label2 = text3(choice.label, 160);
4448
- return entry && label2 ? [defined({ value: entry, label: label2, description: text3(choice.description, 1024) })] : [];
4449
- }) : void 0;
4450
- if (kind === "enum" && !choices?.length) return void 0;
4451
- return defined({
4452
- id: id7,
4453
- label,
4454
- kind,
4455
- choices,
4456
- required: typeof arg.required === "boolean" ? arg.required : void 0,
4457
- captureRemaining: typeof arg.captureRemaining === "boolean" ? arg.captureRemaining : void 0
4458
- });
4459
- }
4460
- __name(normalizeArgument, "normalizeArgument");
4461
- function normalizePublicRuntimeCommands(value) {
4462
- if (!Array.isArray(value)) return [];
4463
- const seenIds = /* @__PURE__ */ new Set();
4464
- const seenAliases = /* @__PURE__ */ new Set();
4465
- const result = [];
4466
- for (const item2 of value.slice(0, MAX_PUBLIC_RUNTIME_COMMANDS)) {
4467
- const command = record(item2);
4468
- const id7 = text3(command.id, 128);
4469
- const label = text3(command.label, 160);
4470
- const dispatch = normalizeDispatch(command.dispatch);
4471
- if (!id7 || !label || !dispatch || seenIds.has(id7) || command.visibility === "hidden" || command.sensitive === true) continue;
4472
- if (["ownerOnly", "sensitive"].some((key) => command[key] !== void 0 && typeof command[key] !== "boolean")) continue;
4473
- if (["aliases", "args", "placements", "availability"].some((key) => command[key] !== void 0 && !Array.isArray(command[key]))) continue;
4474
- const rawAliases = Array.isArray(command.aliases) && command.aliases.length ? command.aliases : [id7];
4475
- const aliases = [...new Set(rawAliases.slice(0, 8).flatMap((value2) => {
4476
- const alias = typeof value2 === "string" && value2.length <= 128 ? normalizeRuntimeCommandAlias(value2) : null;
4477
- return alias && /^[A-Za-z0-9:_-]+$/.test(alias) && !seenAliases.has(alias.toLowerCase()) ? [alias] : [];
4478
- }))];
4479
- if (!aliases.length) continue;
4480
- const args = Array.isArray(command.args) ? command.args.slice(0, 12).map(normalizeArgument) : void 0;
4481
- if (args?.some((arg) => !arg) || Array.isArray(command.args) && command.args.length > 12) continue;
4482
- seenIds.add(id7);
4483
- aliases.forEach((alias) => seenAliases.add(alias.toLowerCase()));
4484
- result.push(defined({
4485
- id: id7,
4486
- label,
4487
- aliases,
4488
- dispatch,
4489
- args,
4490
- description: text3(command.description, 1024),
4491
- category: member(command.category, ["plan", "turn", "session", "runtime", "details", "skill", "custom"]),
4492
- placements: members(command.placements, ["composer_slash", "command_palette", "session_strip"]),
4493
- availability: members(command.availability, ["idle", "busy", "busy_with_queue", "waiting_input", "always"]),
4494
- ownerOnly: typeof command.ownerOnly === "boolean" ? command.ownerOnly : void 0,
4495
- disabledReason: text3(command.disabledReason, 1024),
4496
- trailingTextBehavior: member(command.trailingTextBehavior, ["ignore", "send_as_prompt"]),
4497
- primitive: member(command.primitive, PRIMITIVES),
4498
- tier: member(command.tier, ["primary", "detail", "diagnostic"])
4499
- }));
4500
- }
4501
- return result;
4502
- }
4503
- __name(normalizePublicRuntimeCommands, "normalizePublicRuntimeCommands");
4504
- function normalizePublicRuntimeDescriptor(value) {
4505
- const descriptor = record(value);
4506
- return defined({
4507
- supportsInterrupt: typeof descriptor.supportsInterrupt === "boolean" ? descriptor.supportsInterrupt : void 0,
4508
- supportsInputInterrupt: typeof descriptor.supportsInputInterrupt === "boolean" ? descriptor.supportsInputInterrupt : void 0,
4509
- streamingTextMode: member(descriptor.streamingTextMode, ["none", "status", "snapshot", "block", "delta"]),
4510
- commands: Array.isArray(descriptor.commands) ? normalizePublicRuntimeCommands(descriptor.commands) : void 0
4511
- });
4512
- }
4513
- __name(normalizePublicRuntimeDescriptor, "normalizePublicRuntimeDescriptor");
4514
- var PUBLIC_RUNTIME_FACT_GROUPS = {
4515
- harness: "runtime",
4516
- runtime: "runtime",
4517
- provider: "model",
4518
- model: "model",
4519
- reasoning: "model",
4520
- connection: "connection",
4521
- gateway: "connection",
4522
- route: "route"
4523
- };
4524
- function normalizePublicRuntimeFacts(value) {
4525
- if (!Array.isArray(value)) return [];
4526
- const seen = /* @__PURE__ */ new Set();
4527
- return value.slice(0, 16).flatMap((entry) => {
4528
- const fact = record(entry);
4529
- const id7 = text3(fact.id, 64);
4530
- const group = id7 && Object.hasOwn(PUBLIC_RUNTIME_FACT_GROUPS, id7) ? PUBLIC_RUNTIME_FACT_GROUPS[id7] : void 0;
4531
- const label = text3(fact.label, 80);
4532
- const content = text3(fact.value, 512);
4533
- if (!id7 || !group || !label || !content || seen.has(id7) || fact.sensitive === true || fact.visibility === "hidden") return [];
4534
- seen.add(id7);
4535
- return [defined({
4536
- id: id7,
4537
- group,
4538
- label,
4539
- value: content,
4540
- tier: member(fact.tier, ["primary", "detail", "diagnostic"]),
4541
- tone: member(fact.tone, ["neutral", "good", "warning", "danger"]),
4542
- copyable: typeof fact.copyable === "boolean" ? fact.copyable : void 0,
4543
- updatedAt: typeof fact.updatedAt === "number" && Number.isFinite(fact.updatedAt) && fact.updatedAt >= 0 ? fact.updatedAt : void 0
4544
- })];
4545
- });
4546
- }
4547
- __name(normalizePublicRuntimeFacts, "normalizePublicRuntimeFacts");
4548
-
4549
- // ../backend-contracts/src/conversationMembership.ts
4550
- function resolveConversationPolicyScope(memberIdsOrCount) {
4551
- const count = Array.isArray(memberIdsOrCount) ? new Set(memberIdsOrCount.filter((id7) => typeof id7 === "string" && id7.length > 0)).size : memberIdsOrCount;
4552
- if (typeof count !== "number" || !Number.isSafeInteger(count)) return "unknown";
4553
- return count === 2 ? "direct" : count > 2 ? "group" : "unknown";
4554
- }
4555
- __name(resolveConversationPolicyScope, "resolveConversationPolicyScope");
4556
-
4557
3657
  // ../backend-contracts/src/endpointContentPolicy.ts
4558
3658
  var MLS_DEV_UNSUPPORTED_CONTENT_ACTIONS = ["forward_message"];
4559
3659
  var MLS_DEV_DISCLOSED_PROCESSING_FIELDS = {
@@ -4872,234 +3972,1146 @@ var turnPresence = {
4872
3972
  openedAt: timestamp,
4873
3973
  turnUpdatedAt: timestamp
4874
3974
  };
4875
- var transientRouting = {
4876
- create_memo_stream: ["conversationId", "streamId"],
4877
- append_memo_chunk: ["streamId"],
4878
- prewarm_agents: ["conversationId"],
4879
- publish_presence: [],
4880
- publish_typing: ["conversationId"],
4881
- publish_streaming: ["conversationId"],
4882
- publish_turn: ["conversationId"],
4883
- publish_runtime_status: [],
4884
- publish_session_state: ["conversationId"],
4885
- publish_runtime_info: ["conversationId"],
4886
- publish_runtime_activity: ["conversationId", "itemId"]
3975
+ var transientRouting = {
3976
+ create_memo_stream: ["conversationId", "streamId"],
3977
+ append_memo_chunk: ["streamId"],
3978
+ prewarm_agents: ["conversationId"],
3979
+ publish_presence: [],
3980
+ publish_typing: ["conversationId"],
3981
+ publish_streaming: ["conversationId"],
3982
+ publish_turn: ["conversationId"],
3983
+ publish_runtime_status: [],
3984
+ publish_session_state: ["conversationId"],
3985
+ publish_runtime_info: ["conversationId"],
3986
+ publish_runtime_activity: ["conversationId", "itemId"]
3987
+ };
3988
+ function eventRouting(name) {
3989
+ if (name.startsWith("ephemeral.")) return ["conversationId", "scopeId"];
3990
+ if (name === "connected") return ["protocol", "connectionId", "agentId", "principalId", "principalType"];
3991
+ if (name === "heartbeat") return ["timestamp"];
3992
+ if (name === "error") return ["code", "status", "retryable", "retryAfterMs", "retryAfter", "activeConnections", "maxConnections"];
3993
+ if (name === "participation.suppressed") return ["conversationId", "agentId", "messageId"];
3994
+ if (name === "agent.context") return ["agentId", "ownerId", "ownershipVersion"];
3995
+ if (name === "conversations.changed") return ["conversationIds.*", "removedConversationIds.*"];
3996
+ if (name === "artifact.changed") return ["conversationId", "artifactId", "sequence", "eventId"];
3997
+ if (name.startsWith("message.")) return ["conversationId", "messageId", "eventId", "id", "message.id", "message.conversationId", "message.senderId", "message.senderType", "message.createdAt", "message.replyTo", "message.mentions.*"];
3998
+ if (name.startsWith("conversation.")) return ["conversationId"];
3999
+ if (name.startsWith("contact.")) return ["contactId"];
4000
+ if (name.startsWith("voice.session.")) return ["conversationId", "sessionId"];
4001
+ if (/^(runtime\.|turn\.|typing$|presence$)/.test(name)) return ["conversationId", "agentId", "userId", "memberId"];
4002
+ return [];
4003
+ }
4004
+ __name(eventRouting, "eventRouting");
4005
+ var secretSegments = /* @__PURE__ */ new Set(["token", "accessToken", "refreshToken", "uploadUrl", "signedUrl", "livekitToken", "replyAuthority", "renewReplyAuthorityToken", "credentials", "password", "secret", "authorization", "answer", "answers", "response", "submittedValue"]);
4006
+ var matches = /* @__PURE__ */ __name((path, pattern) => {
4007
+ const parts = pattern.split(".");
4008
+ return parts.length === path.length && parts.every((part, index) => part === path[index] || part === "*" && /^\d+$/.test(path[index]));
4009
+ }, "matches");
4010
+ var publicMatch = /* @__PURE__ */ __name((path, patterns) => patterns.some((pattern) => matches(path, pattern)), "publicMatch");
4011
+ function endpointOperationRequestRetention(action, body, envelope) {
4012
+ if (action !== "respond_interaction") return "journal";
4013
+ if (envelope && Object.keys(envelope).length > 0) return "memory";
4014
+ const routing = ["kind", "conversationId", "agentId", "requestId"];
4015
+ const hasOnly = /* @__PURE__ */ __name((fields2) => Object.keys(body).every((field) => fields2.includes(field)), "hasOnly");
4016
+ if (routing.some((field) => body[field] !== void 0 && typeof body[field] !== "string")) return "memory";
4017
+ if ((body.kind === "input" || body.kind === "card") && hasOnly([...routing, "status", "actionId"])) {
4018
+ if (!["submitted", "cancelled"].includes(String(body.status)) || body.actionId !== void 0 && typeof body.actionId !== "string") return "memory";
4019
+ return "journal";
4020
+ }
4021
+ if (body.kind === "approval" && hasOnly([...routing, "decision", "sessionRule"])) {
4022
+ if (body.decision !== "allow" && body.decision !== "deny") return "memory";
4023
+ const rule = body.sessionRule;
4024
+ if (rule !== void 0 && (!rule || typeof rule !== "object" || Array.isArray(rule) || !Object.entries(rule).every(([field, value]) => ["type", "toolPattern", "expiresAt"].includes(field) && (typeof value === "string" || field === "expiresAt" && value === null)))) return "memory";
4025
+ return "journal";
4026
+ }
4027
+ if (body.kind === "plan" && hasOnly([...routing, "decision", "feedback", "grantedPrompts"])) {
4028
+ if (!["approve", "revise", "reject"].includes(String(body.decision)) || body.feedback !== void 0 && typeof body.feedback !== "string") return "memory";
4029
+ const prompts = body.grantedPrompts;
4030
+ if (prompts !== void 0 && (!Array.isArray(prompts) || prompts.some((prompt) => !prompt || typeof prompt !== "object" || Array.isArray(prompt) || !Object.entries(prompt).every(([field, value]) => ["tool", "prompt"].includes(field) && typeof value === "string")))) return "memory";
4031
+ return "journal";
4032
+ }
4033
+ return "memory";
4034
+ }
4035
+ __name(endpointOperationRequestRetention, "endpointOperationRequestRetention");
4036
+ function endpointContentBoundary(lane, name, payload) {
4037
+ if (!namesForLane[lane]?.includes(name)) throw new EndpointContentPolicyError(lane, name);
4038
+ const record4 = payload && typeof payload === "object" ? payload : void 0;
4039
+ if (lane === "query.request" && name === "get_ephemeral" && record4 && !ENDPOINT_CONTENT_EPHEMERAL_FAMILIES.includes(record4.family)) {
4040
+ throw new EndpointContentPolicyError(lane, `${name}/unknown-family`);
4041
+ }
4042
+ const requestRoutingPaths = (actionRouting[name] ?? []).flatMap((field) => [`body.value.${field}`, `envelope.${field}`]);
4043
+ const classify = /* @__PURE__ */ __name((path) => {
4044
+ if (path.some((part) => secretSegments.has(part))) return "restricted-secret";
4045
+ if (lane === "operation.request" && ["respond_interaction", "sync_phone_contacts"].includes(name) && matches(path, "preparedContent.payload")) return "restricted-secret";
4046
+ if (lane === "operation.request" && ["respond_interaction", "sync_phone_contacts"].includes(name) && path[0] === "body" && path[1] === "value" && !publicMatch(path, requestRoutingPaths)) return "restricted-secret";
4047
+ if (lane === "operation.receipt" && path[0] === "result" && (["consume_interaction", "respond_interaction"].includes(name) || record4?.resultReference?.kind === "interaction")) return "restricted-secret";
4048
+ let selected = payload;
4049
+ for (const part of path) selected = selected && typeof selected === "object" && Object.hasOwn(selected, part) ? selected[part] : void 0;
4050
+ if (selected !== null && typeof selected === "object") return "private-content";
4051
+ if (lane === "operation.request") {
4052
+ if (publicMatch(path, protocolPaths) || publicMatch(path, requestRoutingPaths)) return "public-metadata";
4053
+ if (name === "respond_interaction" && path[0] === "body" && path[1] === "value") return "restricted-secret";
4054
+ if (name === "sync_phone_contacts" && path[0] === "body" && path[1] === "value") return "restricted-secret";
4055
+ if (name === "verb_execute") {
4056
+ const wire4 = record4?.body?.value;
4057
+ const spec = VERB_WIRE_ENVELOPE_FIELDS[wire4?.verb];
4058
+ if (path[0] === "body" && path[1] === "value") {
4059
+ if (path.length === 3 && ["wire", "verb"].includes(path[2])) return "public-metadata";
4060
+ if (path[2] === "envelope" && spec) {
4061
+ const field = path[3];
4062
+ const allowed = [...spec.required, ...spec.optional];
4063
+ if (field && allowed.includes(field) && (path.length === 4 || path.length === 5 && ["mentions", "memberIds"].includes(field) && /^\d+$/.test(path[4]))) return "public-metadata";
4064
+ }
4065
+ }
4066
+ }
4067
+ } else if (lane === "operation.receipt") {
4068
+ if (publicMatch(path, [...protocolPaths, "status", "createdAt", "updatedAt", "error.code", "error.status", "error.retryable", "error.retryAfterMs", "resultError.code", "resultError.status", "resultReference.kind", "resultReference.conversationId", "resultReference.artifactId", "resultReference.assetId", "resultReference.uploadId", "resultReference.sessionId", "resultReference.requestId"])) return "public-metadata";
4069
+ if (path[0] === "result" && (name === "consume_interaction" || name === "respond_interaction" || record4?.resultReference?.kind === "interaction")) return "restricted-secret";
4070
+ } else if (lane === "query.request") {
4071
+ if (publicMatch(path, queryRouting[name] ?? [])) return "public-metadata";
4072
+ } else if (lane === "query.result" || lane === "ephemeral.result") {
4073
+ if (name === "conversation_changes" && publicMatch(path, ["cursor", "hasMore", "reset", "admissionId"])) return "public-metadata";
4074
+ if (name === "get_ephemeral" && path[0] === "value") return "private-content";
4075
+ } else if (lane === "ephemeral.request") {
4076
+ if (publicMatch(path, transientRouting[name] ?? [])) return "public-metadata";
4077
+ if (name === "publish_turn" && path.length === 1 && Object.hasOwn(turnPresence, path[0]) && turnPresence[path[0]](selected)) return "public-metadata";
4078
+ } else if (lane === "event") {
4079
+ if (publicMatch(path, eventRouting(name))) return "public-metadata";
4080
+ }
4081
+ return "private-content";
4082
+ }, "classify");
4083
+ return Object.freeze({ lane, name, defaultClass: "private-content", classify });
4084
+ }
4085
+ __name(endpointContentBoundary, "endpointContentBoundary");
4086
+
4087
+ // ../backend-contracts/src/preparedContent.ts
4088
+ var PREPARED_CONTENT_VERSION = 1;
4089
+ var PREPARED_JSON_CODEC = "canon.json.v1";
4090
+ var PreparedContentError = class extends Error {
4091
+ constructor(code) {
4092
+ super(code);
4093
+ this.code = code;
4094
+ this.name = "PreparedContentError";
4095
+ }
4096
+ code;
4097
+ static {
4098
+ __name(this, "PreparedContentError");
4099
+ }
4100
+ };
4101
+ function invalid10() {
4102
+ throw new PreparedContentError("INVALID_PREPARED_CONTENT");
4103
+ }
4104
+ __name(invalid10, "invalid");
4105
+ var record = /* @__PURE__ */ __name((value) => !!value && typeof value === "object" && !Array.isArray(value), "record");
4106
+ var keys = /* @__PURE__ */ __name((value, allowed) => Object.keys(value).every((key) => allowed.includes(key)), "keys");
4107
+ var revision = /* @__PURE__ */ __name((value) => Number.isSafeInteger(value) && value >= 0, "revision");
4108
+ var blobIdentity = /* @__PURE__ */ __name((value) => typeof value === "string" && value.length > 0 && value.length <= 1024 && !/[\u0000-\u001f]/.test(value), "blobIdentity");
4109
+ var identity = /* @__PURE__ */ __name((value) => typeof value === "string" && ENDPOINT_ID_PATTERN.test(value), "identity");
4110
+ function canonicalEndpointJson(value) {
4111
+ const serialized = JSON.stringify(value);
4112
+ if (serialized === void 0) throw new Error("Endpoint value is not JSON");
4113
+ const normalized = JSON.parse(serialized);
4114
+ function encode(input) {
4115
+ if (input === null || typeof input !== "object") return JSON.stringify(input);
4116
+ if (Array.isArray(input)) return `[${input.map(encode).join(",")}]`;
4117
+ const object = input;
4118
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${encode(object[key])}`).join(",")}}`;
4119
+ }
4120
+ __name(encode, "encode");
4121
+ return encode(normalized);
4122
+ }
4123
+ __name(canonicalEndpointJson, "canonicalEndpointJson");
4124
+ function validateMetadata(value) {
4125
+ if (!record(value)) invalid10();
4126
+ if (value.version !== PREPARED_CONTENT_VERSION) throw new PreparedContentError("UNSUPPORTED_PREPARED_CONTENT_VERSION");
4127
+ if (value.codec !== PREPARED_JSON_CODEC) throw new PreparedContentError("UNSUPPORTED_PREPARED_CONTENT_CODEC");
4128
+ if (!keys(value, ["version", "context", "codec", "payload", "blobs", "expected"])) invalid10();
4129
+ const context = value.context;
4130
+ if (!record(context) || !keys(context, ["action", "conversationId", "messageId"]) || typeof context.action !== "string" || !ENDPOINT_ACTION_PATTERN.test(context.action) || context.conversationId !== void 0 && !identity(context.conversationId) || context.messageId !== void 0 && !identity(context.messageId)) invalid10();
4131
+ const expected = value.expected;
4132
+ if (!record(expected) || !keys(expected, ["installationGeneration", "protocolRevision", "admission"]) || !revision(expected.installationGeneration) || !revision(expected.protocolRevision)) invalid10();
4133
+ if (expected.admission !== void 0) {
4134
+ const admission = expected.admission;
4135
+ if (!record(admission) || !keys(admission, ["conversationId", "membershipRevision", "admissionId", "targetAdmissionId"]) || ["conversationId", "admissionId", "targetAdmissionId"].some((key) => admission[key] !== void 0 && !identity(admission[key])) || admission.membershipRevision !== void 0 && !revision(admission.membershipRevision) || context.conversationId !== void 0 && admission.conversationId !== void 0 && admission.conversationId !== context.conversationId) invalid10();
4136
+ }
4137
+ if (!Array.isArray(value.blobs)) invalid10();
4138
+ const ids = /* @__PURE__ */ new Set();
4139
+ for (const [index, blob] of value.blobs.entries()) {
4140
+ if (!record(blob) || !keys(blob, ["id", "index"]) || !blobIdentity(blob.id) || blob.index !== index || ids.has(blob.id)) invalid10();
4141
+ ids.add(blob.id);
4142
+ }
4143
+ if (typeof value.payload !== "string") invalid10();
4144
+ }
4145
+ __name(validateMetadata, "validateMetadata");
4146
+ function decodePreparedJsonContent(value) {
4147
+ validateMetadata(value);
4148
+ let decoded;
4149
+ try {
4150
+ decoded = JSON.parse(value.payload);
4151
+ } catch {
4152
+ invalid10();
4153
+ }
4154
+ if (!record(decoded) || !keys(decoded, ["body", "envelope"]) || !record(decoded.body) || decoded.envelope !== void 0 && !record(decoded.envelope)) invalid10();
4155
+ if (canonicalEndpointJson(decoded) !== value.payload) invalid10();
4156
+ const { body, envelope } = decoded;
4157
+ if (endpointOperationRequestRetention(value.context.action, body, envelope) === "memory") {
4158
+ throw new PreparedContentError("PREPARED_CONTENT_MEMORY_ONLY");
4159
+ }
4160
+ const effective = { ...body, ...envelope };
4161
+ for (const key of ["conversationId", "messageId"]) {
4162
+ const bound = value.context[key];
4163
+ if (bound !== void 0 && effective[key] !== void 0 && effective[key] !== bound) invalid10();
4164
+ }
4165
+ return envelope === void 0 ? { body } : { body, envelope };
4166
+ }
4167
+ __name(decodePreparedJsonContent, "decodePreparedJsonContent");
4168
+ function freeze(value) {
4169
+ if (value && typeof value === "object") {
4170
+ for (const child of Object.values(value)) freeze(child);
4171
+ Object.freeze(value);
4172
+ }
4173
+ return value;
4174
+ }
4175
+ __name(freeze, "freeze");
4176
+ function prepareJsonContent(input) {
4177
+ if (endpointOperationRequestRetention(input.context.action, input.body, input.envelope) === "memory") {
4178
+ throw new PreparedContentError("PREPARED_CONTENT_MEMORY_ONLY");
4179
+ }
4180
+ const content = JSON.parse(canonicalEndpointJson({
4181
+ version: PREPARED_CONTENT_VERSION,
4182
+ codec: PREPARED_JSON_CODEC,
4183
+ context: input.context,
4184
+ payload: canonicalEndpointJson({ body: input.body, ...input.envelope === void 0 ? {} : { envelope: input.envelope } }),
4185
+ blobs: input.blobs ?? [],
4186
+ expected: input.expected
4187
+ }));
4188
+ decodePreparedJsonContent(content);
4189
+ return freeze(content);
4190
+ }
4191
+ __name(prepareJsonContent, "prepareJsonContent");
4192
+
4193
+ // ../backend-contracts/src/endpoint.ts
4194
+ var CANON_ENDPOINT_PROTOCOL = "canon.endpoint.v1";
4195
+ var ENDPOINT_ID_PATTERN = /^[A-Za-z0-9_.:~-]{1,200}$/;
4196
+ var ENDPOINT_ACTION_PATTERN = /^[a-z][a-z0-9_.-]{0,95}$/;
4197
+ function isEndpointOperationReceipt(value) {
4198
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
4199
+ const receipt = value;
4200
+ return receipt.protocol === CANON_ENDPOINT_PROTOCOL && typeof receipt.operationId === "string" && ENDPOINT_ID_PATTERN.test(receipt.operationId) && typeof receipt.installationId === "string" && ENDPOINT_ID_PATTERN.test(receipt.installationId) && typeof receipt.action === "string" && ENDPOINT_ACTION_PATTERN.test(receipt.action) && ["pending", "succeeded", "rejected", "uncertain"].includes(String(receipt.status)) && typeof receipt.createdAt === "string" && typeof receipt.updatedAt === "string";
4201
+ }
4202
+ __name(isEndpointOperationReceipt, "isEndpointOperationReceipt");
4203
+ var AGENT_HOME_ELSEWHERE = "AGENT_HOME_ELSEWHERE";
4204
+ function agentHomeElsewhereMessage(agentName, home) {
4205
+ const name = agentName?.trim() || "This agent";
4206
+ const since = home.since ? `, since ${new Date(home.since).toISOString().slice(0, 16).replace("T", " ")} UTC` : "";
4207
+ return home.kind === "connector" ? `${name} gets its messages through Canon's relay for its MCP connection${since}. Its owner can turn the relay off at /connect on Canon's web app to run it here.` : `${name} now runs on another machine${since}. Stop this one, or start it with --take-over to move ${name} back here.`;
4208
+ }
4209
+ __name(agentHomeElsewhereMessage, "agentHomeElsewhereMessage");
4210
+
4211
+ // ../backend-contracts/src/mlsDevCredentialReconciliation.ts
4212
+ var MLS_DEV_CREDENTIAL_RECONCILIATION_CHALLENGE_TTL_MS = 3e5;
4213
+ var MLS_DEV_CREDENTIAL_RECONCILIATION_MAX_BYTES = 8192;
4214
+ var MlsDevCredentialReconciliationValidationError = class extends Error {
4215
+ static {
4216
+ __name(this, "MlsDevCredentialReconciliationValidationError");
4217
+ }
4218
+ code = "INVALID_MLS_DEV_CREDENTIAL_RECONCILIATION";
4219
+ constructor() {
4220
+ super("INVALID_MLS_DEV_CREDENTIAL_RECONCILIATION");
4221
+ this.name = "MlsDevCredentialReconciliationValidationError";
4222
+ }
4223
+ };
4224
+ var utf86 = new TextEncoder();
4225
+ var domain2 = utf86.encode("canon.mls.credential-reconciliation-proof.v1\0");
4226
+ function invalid11() {
4227
+ throw new MlsDevCredentialReconciliationValidationError();
4228
+ }
4229
+ __name(invalid11, "invalid");
4230
+ function boundary9(read) {
4231
+ try {
4232
+ return read();
4233
+ } catch {
4234
+ return invalid11();
4235
+ }
4236
+ }
4237
+ __name(boundary9, "boundary");
4238
+ function bounded7(value) {
4239
+ if (utf86.encode(JSON.stringify(value)).length > MLS_DEV_CREDENTIAL_RECONCILIATION_MAX_BYTES) invalid11();
4240
+ return Object.freeze(value);
4241
+ }
4242
+ __name(bounded7, "bounded");
4243
+ function encoded6(value, bytes) {
4244
+ decodeCanonicalMlsBase64Url(value, bytes);
4245
+ return value;
4246
+ }
4247
+ __name(encoded6, "encoded");
4248
+ function parseMlsDevCredentialReconciliationBegin(value) {
4249
+ return boundary9(() => {
4250
+ const input = readMlsDataRecord(value, ["version", "deviceId", "requestId", "expectedCredentialGeneration"]);
4251
+ return bounded7({
4252
+ version: requireMlsLiteral(input.version, 1),
4253
+ deviceId: requireMlsUuid(input.deviceId),
4254
+ requestId: requireMlsUuid(input.requestId),
4255
+ expectedCredentialGeneration: requireMlsInteger(input.expectedCredentialGeneration)
4256
+ });
4257
+ });
4258
+ }
4259
+ __name(parseMlsDevCredentialReconciliationBegin, "parseMlsDevCredentialReconciliationBegin");
4260
+ function parseMlsDevCredentialReconciliationChallenge(value) {
4261
+ return boundary9(() => {
4262
+ const input = readMlsDataRecord(value, [
4263
+ "version",
4264
+ "challengeId",
4265
+ "requestId",
4266
+ "nonce",
4267
+ "device",
4268
+ "expectedCredentialGeneration",
4269
+ "issuedAtMs",
4270
+ "expiresAtMs"
4271
+ ]);
4272
+ const device = parseMlsDevEnrollmentResult({ version: 1, authorization: "registration_only", device: input.device }).device;
4273
+ const issuedAtMs = requireMlsInteger(input.issuedAtMs), expiresAtMs = requireMlsInteger(input.expiresAtMs);
4274
+ if (issuedAtMs < device.registeredAtMs || expiresAtMs - issuedAtMs !== MLS_DEV_CREDENTIAL_RECONCILIATION_CHALLENGE_TTL_MS) invalid11();
4275
+ return bounded7({
4276
+ version: requireMlsLiteral(input.version, 1),
4277
+ challengeId: requireMlsUuid(input.challengeId),
4278
+ requestId: requireMlsUuid(input.requestId),
4279
+ nonce: encoded6(input.nonce, 32),
4280
+ device,
4281
+ expectedCredentialGeneration: requireMlsInteger(input.expectedCredentialGeneration),
4282
+ issuedAtMs,
4283
+ expiresAtMs
4284
+ });
4285
+ });
4286
+ }
4287
+ __name(parseMlsDevCredentialReconciliationChallenge, "parseMlsDevCredentialReconciliationChallenge");
4288
+ function parseMlsDevCredentialReconciliationComplete(value) {
4289
+ return boundary9(() => {
4290
+ const input = readMlsDataRecord(value, ["version", "challengeId", "proof"]);
4291
+ return bounded7({ version: requireMlsLiteral(input.version, 1), challengeId: requireMlsUuid(input.challengeId), proof: encoded6(input.proof, 64) });
4292
+ });
4293
+ }
4294
+ __name(parseMlsDevCredentialReconciliationComplete, "parseMlsDevCredentialReconciliationComplete");
4295
+ function parseMlsDevCredentialReconciliationReceipt(value) {
4296
+ return boundary9(() => {
4297
+ const input = readMlsDataRecord(value, ["version", "authorization", "challenge", "observedCredentialGeneration", "observedAtMs"]);
4298
+ const challenge = parseMlsDevCredentialReconciliationChallenge(input.challenge);
4299
+ const observedCredentialGeneration = requireMlsInteger(input.observedCredentialGeneration), observedAtMs = requireMlsInteger(input.observedAtMs);
4300
+ if (observedCredentialGeneration < challenge.expectedCredentialGeneration || observedAtMs < challenge.issuedAtMs || observedAtMs >= challenge.expiresAtMs) invalid11();
4301
+ return bounded7({
4302
+ version: requireMlsLiteral(input.version, 1),
4303
+ authorization: requireMlsLiteral(input.authorization, "credential_generation_observation_only"),
4304
+ challenge,
4305
+ observedCredentialGeneration,
4306
+ observedAtMs
4307
+ });
4308
+ });
4309
+ }
4310
+ __name(parseMlsDevCredentialReconciliationReceipt, "parseMlsDevCredentialReconciliationReceipt");
4311
+
4312
+ // ../backend-contracts/src/mlsDevDeviceCustody.ts
4313
+ var MLS_DEV_DEVICE_CONTEXT_MAX_BYTES = 2048;
4314
+ var MLS_DEV_DEVICE_COMMAND_MAX_BYTES = 16384;
4315
+ var MLS_DEV_DEVICE_WRAPPED_STATE_MAX_BYTES = 6144;
4316
+ var MlsDevDeviceCustodyValidationError = class extends Error {
4317
+ static {
4318
+ __name(this, "MlsDevDeviceCustodyValidationError");
4319
+ }
4320
+ code = "INVALID_MLS_DEV_DEVICE_CUSTODY";
4321
+ constructor() {
4322
+ super("INVALID_MLS_DEV_DEVICE_CUSTODY");
4323
+ this.name = "MlsDevDeviceCustodyValidationError";
4324
+ }
4325
+ };
4326
+ var utf87 = new TextEncoder();
4327
+ function invalid12() {
4328
+ throw new MlsDevDeviceCustodyValidationError();
4329
+ }
4330
+ __name(invalid12, "invalid");
4331
+ function boundary10(read) {
4332
+ try {
4333
+ return read();
4334
+ } catch {
4335
+ return invalid12();
4336
+ }
4337
+ }
4338
+ __name(boundary10, "boundary");
4339
+ function bounded8(value, limit2) {
4340
+ if (utf87.encode(JSON.stringify(value)).length > limit2) invalid12();
4341
+ return Object.freeze(value);
4342
+ }
4343
+ __name(bounded8, "bounded");
4344
+ function binding2(input) {
4345
+ return {
4346
+ version: requireMlsLiteral(input.version, 1),
4347
+ environmentId: requireMlsEnvironmentId(input.environmentId),
4348
+ principal: parseMlsDevEnrollmentPrincipal(input.principal),
4349
+ installationId: parseMlsDevInstallationId(input.installationId)
4350
+ };
4351
+ }
4352
+ __name(binding2, "binding");
4353
+ function parseMlsDevDeviceBinding(value) {
4354
+ return boundary10(() => bounded8(binding2(readMlsDataRecord(value, ["version", "environmentId", "principal", "installationId"])), MLS_DEV_DEVICE_CONTEXT_MAX_BYTES));
4355
+ }
4356
+ __name(parseMlsDevDeviceBinding, "parseMlsDevDeviceBinding");
4357
+ function parseMlsDevDeviceContext(value) {
4358
+ return boundary10(() => {
4359
+ const input = readMlsDataRecord(value, ["version", "environmentId", "principal", "installationId", "revision"]);
4360
+ const revision2 = requireMlsInteger(input.revision);
4361
+ if (revision2 >= Number.MAX_SAFE_INTEGER) invalid12();
4362
+ return bounded8({ ...binding2(input), revision: revision2 }, MLS_DEV_DEVICE_CONTEXT_MAX_BYTES);
4363
+ });
4364
+ }
4365
+ __name(parseMlsDevDeviceContext, "parseMlsDevDeviceContext");
4366
+ function parseMlsDevDeviceCommand(value) {
4367
+ return boundary10(() => {
4368
+ if (value === null || typeof value !== "object") invalid12();
4369
+ const descriptor = Object.getOwnPropertyDescriptor(value, "op");
4370
+ if (!descriptor || !Object.hasOwn(descriptor, "value")) invalid12();
4371
+ if (descriptor.value === "create") {
4372
+ const input2 = readMlsDataRecord(value, ["op"]);
4373
+ return Object.freeze({ op: requireMlsLiteral(input2.op, "create") });
4374
+ }
4375
+ if (descriptor.value === "enrollment_proof") {
4376
+ const input2 = readMlsDataRecord(value, ["op", "challenge"]);
4377
+ return bounded8({
4378
+ op: requireMlsLiteral(input2.op, "enrollment_proof"),
4379
+ challenge: parseMlsDevEnrollmentChallenge(input2.challenge)
4380
+ }, MLS_DEV_DEVICE_COMMAND_MAX_BYTES);
4381
+ }
4382
+ if (descriptor.value === "credential_reconciliation_proof") {
4383
+ const input2 = readMlsDataRecord(value, ["op", "challenge", "expectedCredentialGeneration"]);
4384
+ const challenge2 = parseMlsDevCredentialReconciliationChallenge(input2.challenge);
4385
+ const expectedCredentialGeneration2 = requireMlsInteger(input2.expectedCredentialGeneration);
4386
+ if (challenge2.expectedCredentialGeneration !== expectedCredentialGeneration2) invalid12();
4387
+ return bounded8({
4388
+ op: requireMlsLiteral(input2.op, "credential_reconciliation_proof"),
4389
+ challenge: challenge2,
4390
+ expectedCredentialGeneration: expectedCredentialGeneration2
4391
+ }, MLS_DEV_DEVICE_COMMAND_MAX_BYTES);
4392
+ }
4393
+ if (descriptor.value !== "credential_proof") invalid12();
4394
+ const input = readMlsDataRecord(value, ["op", "challenge", "registrationResult", "expectedCredentialGeneration"]);
4395
+ const challenge = parseMlsDevCredentialChallenge(input.challenge);
4396
+ const registrationResult = parseMlsDevEnrollmentResult(input.registrationResult);
4397
+ const expectedCredentialGeneration = requireMlsInteger(input.expectedCredentialGeneration);
4398
+ if (expectedCredentialGeneration >= Number.MAX_SAFE_INTEGER || challenge.credential.claims.generation !== expectedCredentialGeneration + 1 || JSON.stringify(challenge.credential.claims.device) !== JSON.stringify(registrationResult.device)) invalid12();
4399
+ return bounded8({
4400
+ op: requireMlsLiteral(input.op, "credential_proof"),
4401
+ challenge,
4402
+ registrationResult,
4403
+ expectedCredentialGeneration
4404
+ }, MLS_DEV_DEVICE_COMMAND_MAX_BYTES);
4405
+ });
4406
+ }
4407
+ __name(parseMlsDevDeviceCommand, "parseMlsDevDeviceCommand");
4408
+ function encoded7(value, bytes) {
4409
+ decodeCanonicalMlsBase64Url(value, bytes);
4410
+ return value;
4411
+ }
4412
+ __name(encoded7, "encoded");
4413
+ function wrapped(value) {
4414
+ if (typeof value !== "string" || value.length > MLS_DEV_DEVICE_WRAPPED_STATE_MAX_BYTES || !value.startsWith("v1:")) invalid12();
4415
+ const body = value.slice(3);
4416
+ if (body.length < 40 || body.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(body)) invalid12();
4417
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4418
+ if (body.endsWith("==") && (alphabet.indexOf(body.at(-3)) & 15) !== 0) invalid12();
4419
+ if (!body.endsWith("==") && body.endsWith("=") && (alphabet.indexOf(body.at(-2)) & 3) !== 0) invalid12();
4420
+ return value;
4421
+ }
4422
+ __name(wrapped, "wrapped");
4423
+ function parseMlsDevDevicePreparation(value, op) {
4424
+ return boundary10(() => {
4425
+ if (!["create", "enrollment_proof", "credential_proof", "credential_reconciliation_proof"].includes(op)) invalid12();
4426
+ const input = readMlsDataRecord(value, ["wrappedState", "payload"]);
4427
+ const payload = readMlsDataRecord(input.payload, op === "create" ? ["signaturePublicKey"] : ["signaturePublicKey", "proof"]);
4428
+ return bounded8({ wrappedState: wrapped(input.wrappedState), payload: Object.freeze({
4429
+ signaturePublicKey: encoded7(payload.signaturePublicKey, 32),
4430
+ ...op === "create" ? {} : { proof: encoded7(payload.proof, 64) }
4431
+ }) }, MLS_DEV_DEVICE_COMMAND_MAX_BYTES);
4432
+ });
4433
+ }
4434
+ __name(parseMlsDevDevicePreparation, "parseMlsDevDevicePreparation");
4435
+
4436
+ // ../backend-contracts/src/clientFloor.ts
4437
+ var CANON_CLIENT_HEADER = "X-Canon-Client";
4438
+ var CLIENT_UPDATE_REQUIRED = "CLIENT_UPDATE_REQUIRED";
4439
+ var CANON_CLIENT_KINDS = ["app-ios", "app-android", "web", "runner", "hermes", "core", "connector"];
4440
+ var VERSION = /^(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})\.(0|[1-9]\d{0,5})$/;
4441
+ var isKind = /* @__PURE__ */ __name((value) => CANON_CLIENT_KINDS.includes(value), "isKind");
4442
+ function isCanonClientVersion(value) {
4443
+ return typeof value === "string" && VERSION.test(value);
4444
+ }
4445
+ __name(isCanonClientVersion, "isCanonClientVersion");
4446
+ function formatCanonClientHeader(client) {
4447
+ if (!isKind(client.kind) || !isCanonClientVersion(client.version)) throw new Error("Invalid Canon client identity");
4448
+ return `${client.kind}/${client.version}`;
4449
+ }
4450
+ __name(formatCanonClientHeader, "formatCanonClientHeader");
4451
+
4452
+ // ../backend-contracts/src/upload-mime-types.ts
4453
+ var MIME_TYPE_RE = /^[a-z0-9.+-]+\/[a-z0-9.+-]+$/i;
4454
+ var SAFE_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
4455
+ "image/jpeg",
4456
+ "image/png",
4457
+ "image/webp",
4458
+ "image/gif"
4459
+ ]);
4460
+ var SAFE_AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
4461
+ "audio/mp4",
4462
+ "audio/x-m4a",
4463
+ "audio/mpeg",
4464
+ "audio/webm",
4465
+ "audio/ogg",
4466
+ "audio/wav",
4467
+ // ADTS AAC — the future Android streamable-memo lane; harmless standalone.
4468
+ "audio/aac"
4469
+ ]);
4470
+ var SAFE_VIDEO_MIME_TYPES = /* @__PURE__ */ new Set([
4471
+ "video/mp4",
4472
+ "video/quicktime",
4473
+ "video/webm",
4474
+ "video/x-m4v"
4475
+ ]);
4476
+ var SAFE_FILE_MIME_TYPES = /* @__PURE__ */ new Set([
4477
+ "application/pdf",
4478
+ "text/plain",
4479
+ "text/markdown",
4480
+ "text/x-markdown",
4481
+ "text/csv",
4482
+ "application/csv",
4483
+ "application/json",
4484
+ "application/zip",
4485
+ "application/vnd.ms-excel",
4486
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
4487
+ ]);
4488
+ var SAFE_FILE_MIME_BY_EXTENSION = {
4489
+ csv: "text/csv",
4490
+ json: "application/json",
4491
+ md: "text/markdown",
4492
+ markdown: "text/markdown",
4493
+ pdf: "application/pdf",
4494
+ txt: "text/plain",
4495
+ xls: "application/vnd.ms-excel",
4496
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4497
+ zip: "application/zip"
4498
+ };
4499
+ var SAFE_VIDEO_MIME_BY_EXTENSION = {
4500
+ m4v: "video/x-m4v",
4501
+ mov: "video/quicktime",
4502
+ mp4: "video/mp4",
4503
+ webm: "video/webm"
4887
4504
  };
4888
- function eventRouting(name) {
4889
- if (name.startsWith("ephemeral.")) return ["conversationId", "scopeId"];
4890
- if (name === "connected") return ["protocol", "connectionId", "agentId", "principalId", "principalType"];
4891
- if (name === "heartbeat") return ["timestamp"];
4892
- if (name === "error") return ["code", "status", "retryable", "retryAfterMs", "retryAfter", "activeConnections", "maxConnections"];
4893
- if (name === "participation.suppressed") return ["conversationId", "agentId", "messageId"];
4894
- if (name === "agent.context") return ["agentId", "ownerId", "ownershipVersion"];
4895
- if (name === "conversations.changed") return ["conversationIds.*", "removedConversationIds.*"];
4896
- if (name === "artifact.changed") return ["conversationId", "artifactId", "sequence", "eventId"];
4897
- if (name.startsWith("message.")) return ["conversationId", "messageId", "eventId", "id", "message.id", "message.conversationId", "message.senderId", "message.senderType", "message.createdAt", "message.replyTo", "message.mentions.*"];
4898
- if (name.startsWith("conversation.")) return ["conversationId"];
4899
- if (name.startsWith("contact.")) return ["contactId"];
4900
- if (name.startsWith("voice.session.")) return ["conversationId", "sessionId"];
4901
- if (/^(runtime\.|turn\.|typing$|presence$)/.test(name)) return ["conversationId", "agentId", "userId", "memberId"];
4902
- return [];
4505
+ var SAFE_AUDIO_MIME_BY_EXTENSION = {
4506
+ aac: "audio/aac",
4507
+ m4a: "audio/x-m4a",
4508
+ mp3: "audio/mpeg",
4509
+ oga: "audio/ogg",
4510
+ ogg: "audio/ogg",
4511
+ wav: "audio/wav"
4512
+ };
4513
+ function normalizeMimeType(value) {
4514
+ if (typeof value !== "string") return null;
4515
+ const normalized = value.split(";")[0].trim().toLowerCase();
4516
+ if (!MIME_TYPE_RE.test(normalized)) return null;
4517
+ if (normalized === "image/jpg") return "image/jpeg";
4518
+ return normalized;
4903
4519
  }
4904
- __name(eventRouting, "eventRouting");
4905
- var secretSegments = /* @__PURE__ */ new Set(["token", "accessToken", "refreshToken", "uploadUrl", "signedUrl", "livekitToken", "replyAuthority", "renewReplyAuthorityToken", "credentials", "password", "secret", "authorization", "answer", "answers", "response", "submittedValue"]);
4906
- var matches = /* @__PURE__ */ __name((path, pattern) => {
4907
- const parts = pattern.split(".");
4908
- return parts.length === path.length && parts.every((part, index) => part === path[index] || part === "*" && /^\d+$/.test(path[index]));
4909
- }, "matches");
4910
- var publicMatch = /* @__PURE__ */ __name((path, patterns) => patterns.some((pattern) => matches(path, pattern)), "publicMatch");
4911
- function endpointOperationRequestRetention(action, body, envelope) {
4912
- if (action !== "respond_interaction") return "journal";
4913
- if (envelope && Object.keys(envelope).length > 0) return "memory";
4914
- const routing = ["kind", "conversationId", "agentId", "requestId"];
4915
- const hasOnly = /* @__PURE__ */ __name((fields2) => Object.keys(body).every((field) => fields2.includes(field)), "hasOnly");
4916
- if (routing.some((field) => body[field] !== void 0 && typeof body[field] !== "string")) return "memory";
4917
- if ((body.kind === "input" || body.kind === "card") && hasOnly([...routing, "status", "actionId"])) {
4918
- if (!["submitted", "cancelled"].includes(String(body.status)) || body.actionId !== void 0 && typeof body.actionId !== "string") return "memory";
4919
- return "journal";
4520
+ __name(normalizeMimeType, "normalizeMimeType");
4521
+ function inferSafeFileMimeTypeFromName(value) {
4522
+ if (typeof value !== "string") return null;
4523
+ const extension = value.trim().split(/[\\/]/).pop()?.split(".").pop()?.toLowerCase() ?? "";
4524
+ return SAFE_FILE_MIME_BY_EXTENSION[extension] ?? null;
4525
+ }
4526
+ __name(inferSafeFileMimeTypeFromName, "inferSafeFileMimeTypeFromName");
4527
+ function inferSafeVideoMimeTypeFromName(value) {
4528
+ if (typeof value !== "string") return null;
4529
+ const extension = value.trim().split(/[\\/]/).pop()?.split(".").pop()?.toLowerCase() ?? "";
4530
+ return SAFE_VIDEO_MIME_BY_EXTENSION[extension] ?? null;
4531
+ }
4532
+ __name(inferSafeVideoMimeTypeFromName, "inferSafeVideoMimeTypeFromName");
4533
+ function inferSafeAudioMimeTypeFromName(value) {
4534
+ if (typeof value !== "string") return null;
4535
+ const extension = value.trim().split(/[\\/]/).pop()?.split(".").pop()?.toLowerCase() ?? "";
4536
+ return SAFE_AUDIO_MIME_BY_EXTENSION[extension] ?? null;
4537
+ }
4538
+ __name(inferSafeAudioMimeTypeFromName, "inferSafeAudioMimeTypeFromName");
4539
+ function shouldTrustSafeFileName(mimeType) {
4540
+ return !mimeType || mimeType === "application/octet-stream" || mimeType === "application/vnd.ms-excel" || mimeType === "text/plain";
4541
+ }
4542
+ __name(shouldTrustSafeFileName, "shouldTrustSafeFileName");
4543
+ function classifyAllowedUploadMimeType(value) {
4544
+ const mimeType = normalizeMimeType(value);
4545
+ if (!mimeType) return null;
4546
+ if (SAFE_IMAGE_MIME_TYPES.has(mimeType)) return "image";
4547
+ if (SAFE_AUDIO_MIME_TYPES.has(mimeType)) return "audio";
4548
+ if (SAFE_VIDEO_MIME_TYPES.has(mimeType)) return "video";
4549
+ if (SAFE_FILE_MIME_TYPES.has(mimeType)) return "file";
4550
+ return null;
4551
+ }
4552
+ __name(classifyAllowedUploadMimeType, "classifyAllowedUploadMimeType");
4553
+ function resolveAllowedUploadMimeType(input) {
4554
+ const normalizedMimeType = normalizeMimeType(input.mimeType);
4555
+ const hasExplicitMimeType = typeof input.mimeType === "string" && input.mimeType.trim().length > 0;
4556
+ const inferredVideoMimeType = inferSafeVideoMimeTypeFromName(input.fileName);
4557
+ const inferredAudioMimeType = inferSafeAudioMimeTypeFromName(input.fileName);
4558
+ const inferredFileMimeType = inferSafeFileMimeTypeFromName(input.fileName);
4559
+ if (hasExplicitMimeType && !normalizedMimeType) {
4560
+ return null;
4561
+ }
4562
+ if (inferredVideoMimeType && (!normalizedMimeType || normalizedMimeType === "application/octet-stream")) {
4563
+ return inferredVideoMimeType;
4564
+ }
4565
+ if (inferredAudioMimeType && (!normalizedMimeType || normalizedMimeType === "application/octet-stream")) {
4566
+ return inferredAudioMimeType;
4567
+ }
4568
+ if (inferredFileMimeType && shouldTrustSafeFileName(normalizedMimeType)) {
4569
+ return inferredFileMimeType;
4570
+ }
4571
+ if (normalizedMimeType && classifyAllowedUploadMimeType(normalizedMimeType)) {
4572
+ return normalizedMimeType;
4573
+ }
4574
+ return normalizedMimeType ? null : inferredFileMimeType;
4575
+ }
4576
+ __name(resolveAllowedUploadMimeType, "resolveAllowedUploadMimeType");
4577
+
4578
+ // ../backend-contracts/src/messageText.ts
4579
+ var HUMAN_MESSAGE_TEXT_MAX_BYTES = 32 * 1024;
4580
+ var UTF8_ENCODER = new TextEncoder();
4581
+
4582
+ // ../backend-contracts/src/runtimeCardFields.ts
4583
+ var RUNTIME_CARD_FIELD_TYPES = [
4584
+ "text",
4585
+ "textarea",
4586
+ "select",
4587
+ "multiSelect",
4588
+ "boolean",
4589
+ "date",
4590
+ "number",
4591
+ "currency",
4592
+ "searchSelect",
4593
+ "lineItems"
4594
+ ];
4595
+ var RUNTIME_CARD_LINE_ITEM_COLUMN_TYPES = [
4596
+ "text",
4597
+ "number",
4598
+ "currency",
4599
+ "date",
4600
+ "select"
4601
+ ];
4602
+
4603
+ // ../backend-contracts/src/membershipChange.ts
4604
+ function isRecord(value) {
4605
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4606
+ }
4607
+ __name(isRecord, "isRecord");
4608
+ function isMembershipChangeMessage(message) {
4609
+ return isRecord(message.metadata) && message.metadata.type === "membership_change";
4610
+ }
4611
+ __name(isMembershipChangeMessage, "isMembershipChangeMessage");
4612
+ function isNameOrId(value) {
4613
+ return value === null || typeof value === "string" && value.trim().length > 0;
4614
+ }
4615
+ __name(isNameOrId, "isNameOrId");
4616
+ function readMembershipChangeMetadata(metadata) {
4617
+ if (!isRecord(metadata) || metadata.type !== "membership_change" || metadata.action !== "added" && metadata.action !== "removed" && metadata.action !== "left" || !isNameOrId(metadata.actorId) || !isNameOrId(metadata.actorName) || !isNameOrId(metadata.memberId) || typeof metadata.memberName !== "string" || !metadata.memberName.trim() || !Number.isSafeInteger(metadata.membershipRevision) || Number(metadata.membershipRevision) < 1 || metadata.turnSemantics !== "control" || metadata.replyBehavior !== "suppress_auto_reply") {
4618
+ return null;
4619
+ }
4620
+ return {
4621
+ type: "membership_change",
4622
+ action: metadata.action,
4623
+ actorId: metadata.actorId,
4624
+ actorName: metadata.actorName,
4625
+ memberId: metadata.memberId,
4626
+ memberName: metadata.memberName,
4627
+ membershipRevision: metadata.membershipRevision,
4628
+ turnSemantics: "control",
4629
+ replyBehavior: "suppress_auto_reply"
4630
+ };
4631
+ }
4632
+ __name(readMembershipChangeMetadata, "readMembershipChangeMetadata");
4633
+ function formatMembershipChangeNotice(metadata, currentUserId) {
4634
+ const isActor = !!currentUserId && metadata.actorId === currentUserId;
4635
+ const isMember = !!currentUserId && metadata.memberId === currentUserId;
4636
+ if (metadata.action === "left") {
4637
+ return `${isMember ? "You" : metadata.memberName} left`;
4638
+ }
4639
+ const actor = isActor ? "You" : metadata.actorName ?? "A member";
4640
+ const member2 = isMember ? "you" : metadata.memberName;
4641
+ return `${actor} ${metadata.action} ${member2}`;
4642
+ }
4643
+ __name(formatMembershipChangeNotice, "formatMembershipChangeNotice");
4644
+
4645
+ // ../backend-contracts/src/turnProtocol.ts
4646
+ var ACTIVE_TURN_STALE_THRESHOLD_MS = 30 * 60 * 1e3;
4647
+ var WAITING_INPUT_STALE_THRESHOLD_MS = 12 * 60 * 60 * 1e3;
4648
+ var TURN_SEMANTICS = [
4649
+ "progress",
4650
+ "turn_complete",
4651
+ "control"
4652
+ ];
4653
+ var DELIVERY_INTENTS = [
4654
+ "queue",
4655
+ "interrupt",
4656
+ "interleave",
4657
+ "stop"
4658
+ ];
4659
+ function isRecord2(value) {
4660
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4661
+ }
4662
+ __name(isRecord2, "isRecord");
4663
+ function normalizeTurnMetadata(metadata) {
4664
+ if (!isRecord2(metadata)) return null;
4665
+ const turnId = typeof metadata.turnId === "string" && metadata.turnId.trim() ? metadata.turnId.trim() : null;
4666
+ const turnSemantics = TURN_SEMANTICS.includes(metadata.turnSemantics) ? metadata.turnSemantics : void 0;
4667
+ const deliveryIntent = DELIVERY_INTENTS.includes(metadata.deliveryIntent) ? metadata.deliveryIntent : void 0;
4668
+ const replyBehavior = metadata.replyBehavior === "allow_auto_reply" || metadata.replyBehavior === "suppress_auto_reply" ? metadata.replyBehavior : void 0;
4669
+ const inboundDisposition = metadata.inboundDisposition === "queued" || metadata.inboundDisposition === "accepted_now" || metadata.inboundDisposition === "interleaved" || metadata.inboundDisposition === "trigger_suppressed" || metadata.inboundDisposition === "rejected" || metadata.inboundDisposition === "cancelled" ? metadata.inboundDisposition : void 0;
4670
+ const requestedTurnMode = typeof metadata.requestedTurnMode === "string" && /^[A-Za-z0-9_.:-]{1,80}$/.test(metadata.requestedTurnMode.trim()) ? metadata.requestedTurnMode.trim() : void 0;
4671
+ if (!turnId && !turnSemantics && !deliveryIntent && !replyBehavior && !inboundDisposition && !requestedTurnMode) {
4672
+ return null;
4673
+ }
4674
+ return {
4675
+ ...turnId ? { turnId } : {},
4676
+ ...turnSemantics ? { turnSemantics } : {},
4677
+ ...deliveryIntent ? { deliveryIntent } : {},
4678
+ ...replyBehavior ? { replyBehavior } : {},
4679
+ ...inboundDisposition ? { inboundDisposition } : {},
4680
+ ...requestedTurnMode ? { requestedTurnMode } : {}
4681
+ };
4682
+ }
4683
+ __name(normalizeTurnMetadata, "normalizeTurnMetadata");
4684
+ function resolveTurnMessageSemantics(input) {
4685
+ if (isMembershipChangeMessage(input)) return "control";
4686
+ const turnMetadata = normalizeTurnMetadata(input.metadata);
4687
+ if (turnMetadata?.turnSemantics) return turnMetadata.turnSemantics;
4688
+ if (input.senderType === "human") return "turn_complete";
4689
+ return "progress";
4690
+ }
4691
+ __name(resolveTurnMessageSemantics, "resolveTurnMessageSemantics");
4692
+ function shouldTriggerAgentTurn(input) {
4693
+ if (isMembershipChangeMessage(input)) {
4694
+ return { allow: false, semantics: "control", reason: "membership notices do not trigger agent replies" };
4695
+ }
4696
+ const semantics = resolveTurnMessageSemantics(input);
4697
+ const turnMetadata = normalizeTurnMetadata(input.metadata);
4698
+ if (turnMetadata?.replyBehavior === "suppress_auto_reply") {
4699
+ return {
4700
+ allow: false,
4701
+ semantics,
4702
+ reason: "metadata explicitly suppresses auto-reply"
4703
+ };
4704
+ }
4705
+ if (input.senderType === "human") {
4706
+ return {
4707
+ allow: true,
4708
+ semantics,
4709
+ reason: "human messages always remain triggerable"
4710
+ };
4711
+ }
4712
+ if (semantics === "progress") {
4713
+ return {
4714
+ allow: false,
4715
+ semantics,
4716
+ reason: turnMetadata?.turnSemantics === "progress" ? "non-final agent progress does not trigger other agents" : "agent messages require explicit turn_complete semantics"
4717
+ };
4920
4718
  }
4921
- if (body.kind === "approval" && hasOnly([...routing, "decision", "sessionRule"])) {
4922
- if (body.decision !== "allow" && body.decision !== "deny") return "memory";
4923
- const rule = body.sessionRule;
4924
- if (rule !== void 0 && (!rule || typeof rule !== "object" || Array.isArray(rule) || !Object.entries(rule).every(([field, value]) => ["type", "toolPattern", "expiresAt"].includes(field) && (typeof value === "string" || field === "expiresAt" && value === null)))) return "memory";
4925
- return "journal";
4719
+ return {
4720
+ allow: true,
4721
+ semantics,
4722
+ reason: "agent message is treated as turn-complete"
4723
+ };
4724
+ }
4725
+ __name(shouldTriggerAgentTurn, "shouldTriggerAgentTurn");
4726
+
4727
+ // ../backend-contracts/src/agentBehaviorPolicy.ts
4728
+ var PARTICIPATION_HISTORY_FETCH_LIMIT = 50;
4729
+ var DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS = 4;
4730
+ var DEFAULT_DIRECT_MAX_CONSECUTIVE_AGENT_TURNS = 4;
4731
+ function defaultMaxConsecutiveAgentTurns(conversationType) {
4732
+ if (conversationType === "group") {
4733
+ return DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS;
4926
4734
  }
4927
- if (body.kind === "plan" && hasOnly([...routing, "decision", "feedback", "grantedPrompts"])) {
4928
- if (!["approve", "revise", "reject"].includes(String(body.decision)) || body.feedback !== void 0 && typeof body.feedback !== "string") return "memory";
4929
- const prompts = body.grantedPrompts;
4930
- if (prompts !== void 0 && (!Array.isArray(prompts) || prompts.some((prompt) => !prompt || typeof prompt !== "object" || Array.isArray(prompt) || !Object.entries(prompt).every(([field, value]) => ["tool", "prompt"].includes(field) && typeof value === "string")))) return "memory";
4931
- return "journal";
4735
+ if (conversationType === "direct") {
4736
+ return DEFAULT_DIRECT_MAX_CONSECUTIVE_AGENT_TURNS;
4932
4737
  }
4933
- return "memory";
4738
+ return null;
4934
4739
  }
4935
- __name(endpointOperationRequestRetention, "endpointOperationRequestRetention");
4936
- function endpointContentBoundary(lane, name, payload) {
4937
- if (!namesForLane[lane]?.includes(name)) throw new EndpointContentPolicyError(lane, name);
4938
- const record4 = payload && typeof payload === "object" ? payload : void 0;
4939
- if (lane === "query.request" && name === "get_ephemeral" && record4 && !ENDPOINT_CONTENT_EPHEMERAL_FAMILIES.includes(record4.family)) {
4940
- throw new EndpointContentPolicyError(lane, `${name}/unknown-family`);
4740
+ __name(defaultMaxConsecutiveAgentTurns, "defaultMaxConsecutiveAgentTurns");
4741
+ var DEFAULT_POLICY = {
4742
+ participation: {
4743
+ style: "natural",
4744
+ allowAgentToAgent: true,
4745
+ allowHumanToAgent: true,
4746
+ allowLongRunningCollaboration: true,
4747
+ requireMentionForGroupReplies: false,
4748
+ maxConsecutiveAgentTurns: null
4749
+ },
4750
+ instructions: [],
4751
+ source: {
4752
+ hasAgentDefault: false,
4753
+ hasConversationOverride: false
4941
4754
  }
4942
- const requestRoutingPaths = (actionRouting[name] ?? []).flatMap((field) => [`body.value.${field}`, `envelope.${field}`]);
4943
- const classify = /* @__PURE__ */ __name((path) => {
4944
- if (path.some((part) => secretSegments.has(part))) return "restricted-secret";
4945
- if (lane === "operation.request" && ["respond_interaction", "sync_phone_contacts"].includes(name) && matches(path, "preparedContent.payload")) return "restricted-secret";
4946
- if (lane === "operation.request" && ["respond_interaction", "sync_phone_contacts"].includes(name) && path[0] === "body" && path[1] === "value" && !publicMatch(path, requestRoutingPaths)) return "restricted-secret";
4947
- if (lane === "operation.receipt" && path[0] === "result" && (["consume_interaction", "respond_interaction"].includes(name) || record4?.resultReference?.kind === "interaction")) return "restricted-secret";
4948
- let selected = payload;
4949
- for (const part of path) selected = selected && typeof selected === "object" && Object.hasOwn(selected, part) ? selected[part] : void 0;
4950
- if (selected !== null && typeof selected === "object") return "private-content";
4951
- if (lane === "operation.request") {
4952
- if (publicMatch(path, protocolPaths) || publicMatch(path, requestRoutingPaths)) return "public-metadata";
4953
- if (name === "respond_interaction" && path[0] === "body" && path[1] === "value") return "restricted-secret";
4954
- if (name === "sync_phone_contacts" && path[0] === "body" && path[1] === "value") return "restricted-secret";
4955
- if (name === "verb_execute") {
4956
- const wire4 = record4?.body?.value;
4957
- const spec = VERB_WIRE_ENVELOPE_FIELDS[wire4?.verb];
4958
- if (path[0] === "body" && path[1] === "value") {
4959
- if (path.length === 3 && ["wire", "verb"].includes(path[2])) return "public-metadata";
4960
- if (path[2] === "envelope" && spec) {
4961
- const field = path[3];
4962
- const allowed = [...spec.required, ...spec.optional];
4963
- if (field && allowed.includes(field) && (path.length === 4 || path.length === 5 && ["mentions", "memberIds"].includes(field) && /^\d+$/.test(path[4]))) return "public-metadata";
4964
- }
4965
- }
4966
- }
4967
- } else if (lane === "operation.receipt") {
4968
- if (publicMatch(path, [...protocolPaths, "status", "createdAt", "updatedAt", "error.code", "error.status", "error.retryable", "error.retryAfterMs", "resultError.code", "resultError.status", "resultReference.kind", "resultReference.conversationId", "resultReference.artifactId", "resultReference.assetId", "resultReference.uploadId", "resultReference.sessionId", "resultReference.requestId"])) return "public-metadata";
4969
- if (path[0] === "result" && (name === "consume_interaction" || name === "respond_interaction" || record4?.resultReference?.kind === "interaction")) return "restricted-secret";
4970
- } else if (lane === "query.request") {
4971
- if (publicMatch(path, queryRouting[name] ?? [])) return "public-metadata";
4972
- } else if (lane === "query.result" || lane === "ephemeral.result") {
4973
- if (name === "conversation_changes" && publicMatch(path, ["cursor", "hasMore", "reset", "admissionId"])) return "public-metadata";
4974
- if (name === "get_ephemeral" && path[0] === "value") return "private-content";
4975
- } else if (lane === "ephemeral.request") {
4976
- if (publicMatch(path, transientRouting[name] ?? [])) return "public-metadata";
4977
- if (name === "publish_turn" && path.length === 1 && Object.hasOwn(turnPresence, path[0]) && turnPresence[path[0]](selected)) return "public-metadata";
4978
- } else if (lane === "event") {
4979
- if (publicMatch(path, eventRouting(name))) return "public-metadata";
4755
+ };
4756
+ function coalesceBoolean(overrideValue, fallbackValue, defaultValue) {
4757
+ if (typeof overrideValue === "boolean") return overrideValue;
4758
+ if (typeof fallbackValue === "boolean") return fallbackValue;
4759
+ return defaultValue;
4760
+ }
4761
+ __name(coalesceBoolean, "coalesceBoolean");
4762
+ function normalizeAgentBehaviorInstructions(value) {
4763
+ if (typeof value !== "string") return null;
4764
+ const trimmed = value.trim();
4765
+ return trimmed.length > 0 ? trimmed : null;
4766
+ }
4767
+ __name(normalizeAgentBehaviorInstructions, "normalizeAgentBehaviorInstructions");
4768
+ function resolveAgentBehaviorPolicy(params) {
4769
+ const agentDefault = params?.agentDefault ?? null;
4770
+ const conversationOverride = params?.conversationOverride ?? null;
4771
+ const defaultInstructions = normalizeAgentBehaviorInstructions(agentDefault?.instructions);
4772
+ const overrideInstructions = normalizeAgentBehaviorInstructions(conversationOverride?.instructions);
4773
+ return {
4774
+ participation: {
4775
+ style: conversationOverride?.participationStyle ?? agentDefault?.participationStyle ?? DEFAULT_POLICY.participation.style,
4776
+ allowAgentToAgent: coalesceBoolean(
4777
+ conversationOverride?.allowAgentToAgent,
4778
+ agentDefault?.allowAgentToAgent,
4779
+ DEFAULT_POLICY.participation.allowAgentToAgent
4780
+ ),
4781
+ allowHumanToAgent: DEFAULT_POLICY.participation.allowHumanToAgent,
4782
+ allowLongRunningCollaboration: coalesceBoolean(
4783
+ conversationOverride?.allowLongRunningCollaboration,
4784
+ agentDefault?.allowLongRunningCollaboration,
4785
+ DEFAULT_POLICY.participation.allowLongRunningCollaboration
4786
+ ),
4787
+ requireMentionForGroupReplies: coalesceBoolean(
4788
+ conversationOverride?.requireMentionForGroupReplies,
4789
+ agentDefault?.requireMentionForGroupReplies,
4790
+ DEFAULT_POLICY.participation.requireMentionForGroupReplies
4791
+ ),
4792
+ maxConsecutiveAgentTurns: conversationOverride?.maxConsecutiveAgentTurns !== void 0 ? conversationOverride.maxConsecutiveAgentTurns ?? null : agentDefault?.maxConsecutiveAgentTurns !== void 0 ? agentDefault.maxConsecutiveAgentTurns ?? null : defaultMaxConsecutiveAgentTurns(params?.conversationType)
4793
+ },
4794
+ instructions: [
4795
+ ...defaultInstructions ? [defaultInstructions] : [],
4796
+ ...overrideInstructions ? [overrideInstructions] : []
4797
+ ],
4798
+ source: {
4799
+ hasAgentDefault: agentDefault != null,
4800
+ hasConversationOverride: conversationOverride != null
4980
4801
  }
4981
- return "private-content";
4982
- }, "classify");
4983
- return Object.freeze({ lane, name, defaultClass: "private-content", classify });
4802
+ };
4984
4803
  }
4985
- __name(endpointContentBoundary, "endpointContentBoundary");
4986
-
4987
- // ../backend-contracts/src/preparedContent.ts
4988
- var PREPARED_CONTENT_VERSION = 1;
4989
- var PREPARED_JSON_CODEC = "canon.json.v1";
4990
- var PreparedContentError = class extends Error {
4991
- constructor(code) {
4992
- super(code);
4993
- this.code = code;
4994
- this.name = "PreparedContentError";
4995
- }
4996
- code;
4997
- static {
4998
- __name(this, "PreparedContentError");
4804
+ __name(resolveAgentBehaviorPolicy, "resolveAgentBehaviorPolicy");
4805
+ function toHistorySenderType(message) {
4806
+ if (isMembershipChangeMessage(message)) return null;
4807
+ if (message.senderType !== "ai_agent") {
4808
+ return "human";
4999
4809
  }
5000
- };
5001
- function invalid12() {
5002
- throw new PreparedContentError("INVALID_PREPARED_CONTENT");
4810
+ return shouldTriggerAgentTurn({
4811
+ senderType: "ai_agent",
4812
+ metadata: message.metadata
4813
+ }).allow ? "ai_agent" : null;
5003
4814
  }
5004
- __name(invalid12, "invalid");
5005
- var record2 = /* @__PURE__ */ __name((value) => !!value && typeof value === "object" && !Array.isArray(value), "record");
5006
- var keys = /* @__PURE__ */ __name((value, allowed) => Object.keys(value).every((key) => allowed.includes(key)), "keys");
5007
- var revision = /* @__PURE__ */ __name((value) => Number.isSafeInteger(value) && value >= 0, "revision");
5008
- var blobIdentity = /* @__PURE__ */ __name((value) => typeof value === "string" && value.length > 0 && value.length <= 1024 && !/[\u0000-\u001f]/.test(value), "blobIdentity");
5009
- var identity = /* @__PURE__ */ __name((value) => typeof value === "string" && ENDPOINT_ID_PATTERN.test(value), "identity");
5010
- function canonicalEndpointJson(value) {
5011
- const serialized = JSON.stringify(value);
5012
- if (serialized === void 0) throw new Error("Endpoint value is not JSON");
5013
- const normalized = JSON.parse(serialized);
5014
- function encode(input) {
5015
- if (input === null || typeof input !== "object") return JSON.stringify(input);
5016
- if (Array.isArray(input)) return `[${input.map(encode).join(",")}]`;
5017
- const object = input;
5018
- return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${encode(object[key])}`).join(",")}}`;
4815
+ __name(toHistorySenderType, "toHistorySenderType");
4816
+ function buildParticipationHistorySnapshotFromSenderTypes(recentSenderTypes) {
4817
+ let consecutiveAgentTurns = 0;
4818
+ for (const senderType of recentSenderTypes) {
4819
+ if (senderType !== "ai_agent") break;
4820
+ consecutiveAgentTurns += 1;
5019
4821
  }
5020
- __name(encode, "encode");
5021
- return encode(normalized);
4822
+ return {
4823
+ recentSenderTypes,
4824
+ recentHumanCount: recentSenderTypes.filter((senderType) => senderType === "human").length,
4825
+ recentAgentCount: recentSenderTypes.filter((senderType) => senderType === "ai_agent").length,
4826
+ consecutiveAgentTurns,
4827
+ currentAgentStreakStartedByHuman: consecutiveAgentTurns > 0 && recentSenderTypes[consecutiveAgentTurns] === "human"
4828
+ };
5022
4829
  }
5023
- __name(canonicalEndpointJson, "canonicalEndpointJson");
5024
- function validateMetadata(value) {
5025
- if (!record2(value)) invalid12();
5026
- if (value.version !== PREPARED_CONTENT_VERSION) throw new PreparedContentError("UNSUPPORTED_PREPARED_CONTENT_VERSION");
5027
- if (value.codec !== PREPARED_JSON_CODEC) throw new PreparedContentError("UNSUPPORTED_PREPARED_CONTENT_CODEC");
5028
- if (!keys(value, ["version", "context", "codec", "payload", "blobs", "expected"])) invalid12();
5029
- const context = value.context;
5030
- if (!record2(context) || !keys(context, ["action", "conversationId", "messageId"]) || typeof context.action !== "string" || !ENDPOINT_ACTION_PATTERN.test(context.action) || context.conversationId !== void 0 && !identity(context.conversationId) || context.messageId !== void 0 && !identity(context.messageId)) invalid12();
5031
- const expected = value.expected;
5032
- if (!record2(expected) || !keys(expected, ["installationGeneration", "protocolRevision", "admission"]) || !revision(expected.installationGeneration) || !revision(expected.protocolRevision)) invalid12();
5033
- if (expected.admission !== void 0) {
5034
- const admission = expected.admission;
5035
- if (!record2(admission) || !keys(admission, ["conversationId", "membershipRevision", "admissionId", "targetAdmissionId"]) || ["conversationId", "admissionId", "targetAdmissionId"].some((key) => admission[key] !== void 0 && !identity(admission[key])) || admission.membershipRevision !== void 0 && !revision(admission.membershipRevision) || context.conversationId !== void 0 && admission.conversationId !== void 0 && admission.conversationId !== context.conversationId) invalid12();
4830
+ __name(buildParticipationHistorySnapshotFromSenderTypes, "buildParticipationHistorySnapshotFromSenderTypes");
4831
+ function buildParticipationHistorySnapshot(messages, agentId) {
4832
+ void agentId;
4833
+ const recentSenderTypes = messages.flatMap((message) => {
4834
+ const senderType = toHistorySenderType(message);
4835
+ return senderType ? [senderType] : [];
4836
+ });
4837
+ return buildParticipationHistorySnapshotFromSenderTypes(recentSenderTypes);
4838
+ }
4839
+ __name(buildParticipationHistorySnapshot, "buildParticipationHistorySnapshot");
4840
+ function evaluateParticipationPolicy(policy, input) {
4841
+ const resolved = policy ?? resolveAgentBehaviorPolicy({ conversationType: input.conversationType });
4842
+ const participation = resolved.participation;
4843
+ const priorAgentTurns = Math.max(input.consecutiveAgentTurns ?? 0, 0);
4844
+ const agentTurnsWithCurrent = priorAgentTurns + (input.senderType === "ai_agent" ? 1 : 0);
4845
+ const consecutiveAgentTurns = Math.max(
4846
+ priorAgentTurns,
4847
+ input.senderType === "ai_agent" ? 1 : 0
4848
+ );
4849
+ const currentAgentStreakStartedByHuman = input.currentAgentStreakStartedByHuman === true;
4850
+ if (input.conversationType === "group" && participation.requireMentionForGroupReplies && !input.mentionedAgent) {
4851
+ return {
4852
+ allow: false,
4853
+ reasonCode: "group_mention_required",
4854
+ reason: "group replies require a direct mention"
4855
+ };
5036
4856
  }
5037
- if (!Array.isArray(value.blobs)) invalid12();
5038
- const ids = /* @__PURE__ */ new Set();
5039
- for (const [index, blob] of value.blobs.entries()) {
5040
- if (!record2(blob) || !keys(blob, ["id", "index"]) || !blobIdentity(blob.id) || blob.index !== index || ids.has(blob.id)) invalid12();
5041
- ids.add(blob.id);
4857
+ if (input.isOwner) {
4858
+ return {
4859
+ allow: true,
4860
+ reasonCode: "owner_sender",
4861
+ reason: "owner messages pass through outside mention-required group turns"
4862
+ };
4863
+ }
4864
+ if (input.senderType !== "ai_agent") {
4865
+ return { allow: true, reasonCode: "human_sender", reason: "latest sender is human" };
4866
+ }
4867
+ if (!participation.allowAgentToAgent) {
4868
+ return {
4869
+ allow: false,
4870
+ reasonCode: "agent_to_agent_disabled",
4871
+ reason: "agent-to-agent participation is disabled by policy"
4872
+ };
4873
+ }
4874
+ if (!participation.allowLongRunningCollaboration && (consecutiveAgentTurns > 1 || !currentAgentStreakStartedByHuman)) {
4875
+ return {
4876
+ allow: false,
4877
+ reasonCode: "human_reset_required",
4878
+ reason: "a fresh human steer is required before continuing agent collaboration"
4879
+ };
5042
4880
  }
5043
- if (typeof value.payload !== "string") invalid12();
4881
+ if (typeof participation.maxConsecutiveAgentTurns === "number" && participation.maxConsecutiveAgentTurns >= 0 && agentTurnsWithCurrent >= participation.maxConsecutiveAgentTurns) {
4882
+ return {
4883
+ allow: false,
4884
+ reasonCode: "agent_turn_limit_reached",
4885
+ reason: "maximum consecutive agent turns reached"
4886
+ };
4887
+ }
4888
+ return {
4889
+ allow: true,
4890
+ reasonCode: input.conversationType === "group" ? "group_agent_allowed" : "direct_agent_allowed",
4891
+ reason: participation.requireMentionForGroupReplies ? "policy allows this directly mentioned group reply" : "agent-to-agent participation allowed by policy"
4892
+ };
5044
4893
  }
5045
- __name(validateMetadata, "validateMetadata");
5046
- function decodePreparedJsonContent(value) {
5047
- validateMetadata(value);
5048
- let decoded;
5049
- try {
5050
- decoded = JSON.parse(value.payload);
5051
- } catch {
5052
- invalid12();
4894
+ __name(evaluateParticipationPolicy, "evaluateParticipationPolicy");
4895
+
4896
+ // ../backend-contracts/src/agentDirectory.ts
4897
+ var MAX_DISCOVER_AGENTS_QUERY_LENGTH = 64;
4898
+
4899
+ // ../backend-contracts/src/communication.ts
4900
+ function parseDirectConversationSelection(value) {
4901
+ if (value == null) return { mode: "latest_or_new" };
4902
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4903
+ throw new Error("selection must be an object");
5053
4904
  }
5054
- if (!record2(decoded) || !keys(decoded, ["body", "envelope"]) || !record2(decoded.body) || decoded.envelope !== void 0 && !record2(decoded.envelope)) invalid12();
5055
- if (canonicalEndpointJson(decoded) !== value.payload) invalid12();
5056
- const { body, envelope } = decoded;
5057
- if (endpointOperationRequestRetention(value.context.action, body, envelope) === "memory") {
5058
- throw new PreparedContentError("PREPARED_CONTENT_MEMORY_ONLY");
4905
+ const record4 = value;
4906
+ const fields2 = record4.mode === "specific" ? ["mode", "conversationId"] : ["mode"];
4907
+ if (Object.keys(record4).some((key) => !fields2.includes(key))) {
4908
+ throw new Error(`selection ${String(record4.mode)} only takes ${fields2.join(" and ")}`);
5059
4909
  }
5060
- const effective = { ...body, ...envelope };
5061
- for (const key of ["conversationId", "messageId"]) {
5062
- const bound = value.context[key];
5063
- if (bound !== void 0 && effective[key] !== void 0 && effective[key] !== bound) invalid12();
4910
+ if (record4.mode === "latest_or_new" || record4.mode === "new") {
4911
+ return { mode: record4.mode };
5064
4912
  }
5065
- return envelope === void 0 ? { body } : { body, envelope };
4913
+ if (record4.mode === "specific") {
4914
+ const conversationId = typeof record4.conversationId === "string" ? record4.conversationId.trim() : "";
4915
+ if (!conversationId) throw new Error("selection.conversationId is required");
4916
+ return { mode: "specific", conversationId };
4917
+ }
4918
+ throw new Error("selection.mode is invalid");
5066
4919
  }
5067
- __name(decodePreparedJsonContent, "decodePreparedJsonContent");
5068
- function freeze(value) {
5069
- if (value && typeof value === "object") {
5070
- for (const child of Object.values(value)) freeze(child);
5071
- Object.freeze(value);
4920
+ __name(parseDirectConversationSelection, "parseDirectConversationSelection");
4921
+
4922
+ // ../backend-contracts/src/runtimeDescriptor.ts
4923
+ function normalizeRuntimeCommandAlias(value) {
4924
+ const normalized = value.trim().replace(/^\/+/, "").replace(/\s+/g, "-");
4925
+ return /^[A-Za-z0-9:_-]+$/.test(normalized) ? normalized : null;
4926
+ }
4927
+ __name(normalizeRuntimeCommandAlias, "normalizeRuntimeCommandAlias");
4928
+ function normalizeRuntimeCommandAliases(command) {
4929
+ const rawAliases = command.aliases?.length ? command.aliases : [command.id];
4930
+ const aliases = rawAliases.map(normalizeRuntimeCommandAlias).filter((value) => Boolean(value));
4931
+ return Array.from(new Set(aliases));
4932
+ }
4933
+ __name(normalizeRuntimeCommandAliases, "normalizeRuntimeCommandAliases");
4934
+ var MAX_PUBLIC_RUNTIME_COMMANDS = 256;
4935
+ function record2(value) {
4936
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
4937
+ }
4938
+ __name(record2, "record");
4939
+ function text3(value, max) {
4940
+ return typeof value === "string" && value.length > 0 && value.length <= max ? value : void 0;
4941
+ }
4942
+ __name(text3, "text");
4943
+ function member(value, choices) {
4944
+ return choices.includes(value) ? value : void 0;
4945
+ }
4946
+ __name(member, "member");
4947
+ function members(value, choices) {
4948
+ if (!Array.isArray(value)) return void 0;
4949
+ return [...new Set(value.filter((entry) => choices.includes(entry)))];
4950
+ }
4951
+ __name(members, "members");
4952
+ function defined(value) {
4953
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
4954
+ }
4955
+ __name(defined, "defined");
4956
+ var PRIMITIVES = [
4957
+ "runtime.status",
4958
+ "runtime.reasoning.set",
4959
+ "runtime.verbosity.set",
4960
+ "runtime.usage",
4961
+ "context.compact",
4962
+ "session.new",
4963
+ "session.reset"
4964
+ ];
4965
+ function normalizeDispatch(value) {
4966
+ const dispatch = record2(value);
4967
+ switch (dispatch.kind) {
4968
+ case "signal": {
4969
+ const signal = member(dispatch.signal, ["interrupt", "stop_and_drop", "new_session"]);
4970
+ return signal ? { kind: "signal", signal } : void 0;
4971
+ }
4972
+ case "primitive": {
4973
+ const primitive = member(dispatch.primitive, PRIMITIVES);
4974
+ return primitive ? { kind: "primitive", primitive } : void 0;
4975
+ }
4976
+ case "control": {
4977
+ const controlId = text3(dispatch.controlId, 128);
4978
+ return controlId ? defined({ kind: "control", controlId, value: text3(dispatch.value, 1024) }) : void 0;
4979
+ }
4980
+ case "text_passthrough": {
4981
+ const template = text3(dispatch.template, 2048);
4982
+ return template ? { kind: "text_passthrough", template } : void 0;
4983
+ }
4984
+ case "compose": {
4985
+ const content = text3(dispatch.text, 2048);
4986
+ return content ? { kind: "compose", text: content } : void 0;
4987
+ }
4988
+ case "open_details":
4989
+ return defined({ kind: "open_details", target: text3(dispatch.target, 128) });
4990
+ default:
4991
+ return void 0;
5072
4992
  }
5073
- return value;
5074
4993
  }
5075
- __name(freeze, "freeze");
5076
- function prepareJsonContent(input) {
5077
- if (endpointOperationRequestRetention(input.context.action, input.body, input.envelope) === "memory") {
5078
- throw new PreparedContentError("PREPARED_CONTENT_MEMORY_ONLY");
4994
+ __name(normalizeDispatch, "normalizeDispatch");
4995
+ function normalizeArgument(value) {
4996
+ const arg = record2(value);
4997
+ const id7 = text3(arg.id, 128);
4998
+ const label = text3(arg.label, 160);
4999
+ const kind = member(arg.kind, ["string", "enum", "boolean"]);
5000
+ if (!id7 || !label || !kind) return void 0;
5001
+ if (["required", "captureRemaining"].some((key) => arg[key] !== void 0 && typeof arg[key] !== "boolean")) return void 0;
5002
+ if (arg.choices !== void 0 && !Array.isArray(arg.choices)) return void 0;
5003
+ const choices = Array.isArray(arg.choices) ? arg.choices.slice(0, 128).flatMap((value2) => {
5004
+ const choice = record2(value2);
5005
+ const entry = text3(choice.value, 1024);
5006
+ const label2 = text3(choice.label, 160);
5007
+ return entry && label2 ? [defined({ value: entry, label: label2, description: text3(choice.description, 1024) })] : [];
5008
+ }) : void 0;
5009
+ if (kind === "enum" && !choices?.length) return void 0;
5010
+ return defined({
5011
+ id: id7,
5012
+ label,
5013
+ kind,
5014
+ choices,
5015
+ required: typeof arg.required === "boolean" ? arg.required : void 0,
5016
+ captureRemaining: typeof arg.captureRemaining === "boolean" ? arg.captureRemaining : void 0
5017
+ });
5018
+ }
5019
+ __name(normalizeArgument, "normalizeArgument");
5020
+ function normalizePublicRuntimeCommands(value) {
5021
+ if (!Array.isArray(value)) return [];
5022
+ const seenIds = /* @__PURE__ */ new Set();
5023
+ const seenAliases = /* @__PURE__ */ new Set();
5024
+ const result = [];
5025
+ for (const item2 of value.slice(0, MAX_PUBLIC_RUNTIME_COMMANDS)) {
5026
+ const command = record2(item2);
5027
+ const id7 = text3(command.id, 128);
5028
+ const label = text3(command.label, 160);
5029
+ const dispatch = normalizeDispatch(command.dispatch);
5030
+ if (!id7 || !label || !dispatch || seenIds.has(id7) || command.visibility === "hidden" || command.sensitive === true) continue;
5031
+ if (["ownerOnly", "sensitive"].some((key) => command[key] !== void 0 && typeof command[key] !== "boolean")) continue;
5032
+ if (["aliases", "args", "placements", "availability"].some((key) => command[key] !== void 0 && !Array.isArray(command[key]))) continue;
5033
+ const rawAliases = Array.isArray(command.aliases) && command.aliases.length ? command.aliases : [id7];
5034
+ const aliases = [...new Set(rawAliases.slice(0, 8).flatMap((value2) => {
5035
+ const alias = typeof value2 === "string" && value2.length <= 128 ? normalizeRuntimeCommandAlias(value2) : null;
5036
+ return alias && /^[A-Za-z0-9:_-]+$/.test(alias) && !seenAliases.has(alias.toLowerCase()) ? [alias] : [];
5037
+ }))];
5038
+ if (!aliases.length) continue;
5039
+ const args = Array.isArray(command.args) ? command.args.slice(0, 12).map(normalizeArgument) : void 0;
5040
+ if (args?.some((arg) => !arg) || Array.isArray(command.args) && command.args.length > 12) continue;
5041
+ seenIds.add(id7);
5042
+ aliases.forEach((alias) => seenAliases.add(alias.toLowerCase()));
5043
+ result.push(defined({
5044
+ id: id7,
5045
+ label,
5046
+ aliases,
5047
+ dispatch,
5048
+ args,
5049
+ description: text3(command.description, 1024),
5050
+ category: member(command.category, ["plan", "turn", "session", "runtime", "details", "skill", "custom"]),
5051
+ placements: members(command.placements, ["composer_slash", "command_palette", "session_strip"]),
5052
+ availability: members(command.availability, ["idle", "busy", "busy_with_queue", "waiting_input", "always"]),
5053
+ ownerOnly: typeof command.ownerOnly === "boolean" ? command.ownerOnly : void 0,
5054
+ disabledReason: text3(command.disabledReason, 1024),
5055
+ trailingTextBehavior: member(command.trailingTextBehavior, ["ignore", "send_as_prompt"]),
5056
+ primitive: member(command.primitive, PRIMITIVES),
5057
+ tier: member(command.tier, ["primary", "detail", "diagnostic"])
5058
+ }));
5079
5059
  }
5080
- const content = JSON.parse(canonicalEndpointJson({
5081
- version: PREPARED_CONTENT_VERSION,
5082
- codec: PREPARED_JSON_CODEC,
5083
- context: input.context,
5084
- payload: canonicalEndpointJson({ body: input.body, ...input.envelope === void 0 ? {} : { envelope: input.envelope } }),
5085
- blobs: input.blobs ?? [],
5086
- expected: input.expected
5087
- }));
5088
- decodePreparedJsonContent(content);
5089
- return freeze(content);
5060
+ return result;
5090
5061
  }
5091
- __name(prepareJsonContent, "prepareJsonContent");
5062
+ __name(normalizePublicRuntimeCommands, "normalizePublicRuntimeCommands");
5063
+ function normalizePublicRuntimeDescriptor(value) {
5064
+ const descriptor = record2(value);
5065
+ return defined({
5066
+ supportsInterrupt: typeof descriptor.supportsInterrupt === "boolean" ? descriptor.supportsInterrupt : void 0,
5067
+ supportsInputInterrupt: typeof descriptor.supportsInputInterrupt === "boolean" ? descriptor.supportsInputInterrupt : void 0,
5068
+ streamingTextMode: member(descriptor.streamingTextMode, ["none", "status", "snapshot", "block", "delta"]),
5069
+ commands: Array.isArray(descriptor.commands) ? normalizePublicRuntimeCommands(descriptor.commands) : void 0
5070
+ });
5071
+ }
5072
+ __name(normalizePublicRuntimeDescriptor, "normalizePublicRuntimeDescriptor");
5073
+ var PUBLIC_RUNTIME_FACT_GROUPS = {
5074
+ harness: "runtime",
5075
+ runtime: "runtime",
5076
+ provider: "model",
5077
+ model: "model",
5078
+ reasoning: "model",
5079
+ connection: "connection",
5080
+ gateway: "connection",
5081
+ route: "route"
5082
+ };
5083
+ function normalizePublicRuntimeFacts(value) {
5084
+ if (!Array.isArray(value)) return [];
5085
+ const seen = /* @__PURE__ */ new Set();
5086
+ return value.slice(0, 16).flatMap((entry) => {
5087
+ const fact = record2(entry);
5088
+ const id7 = text3(fact.id, 64);
5089
+ const group = id7 && Object.hasOwn(PUBLIC_RUNTIME_FACT_GROUPS, id7) ? PUBLIC_RUNTIME_FACT_GROUPS[id7] : void 0;
5090
+ const label = text3(fact.label, 80);
5091
+ const content = text3(fact.value, 512);
5092
+ if (!id7 || !group || !label || !content || seen.has(id7) || fact.sensitive === true || fact.visibility === "hidden") return [];
5093
+ seen.add(id7);
5094
+ return [defined({
5095
+ id: id7,
5096
+ group,
5097
+ label,
5098
+ value: content,
5099
+ tier: member(fact.tier, ["primary", "detail", "diagnostic"]),
5100
+ tone: member(fact.tone, ["neutral", "good", "warning", "danger"]),
5101
+ copyable: typeof fact.copyable === "boolean" ? fact.copyable : void 0,
5102
+ updatedAt: typeof fact.updatedAt === "number" && Number.isFinite(fact.updatedAt) && fact.updatedAt >= 0 ? fact.updatedAt : void 0
5103
+ })];
5104
+ });
5105
+ }
5106
+ __name(normalizePublicRuntimeFacts, "normalizePublicRuntimeFacts");
5092
5107
 
5093
- // ../backend-contracts/src/endpoint.ts
5094
- var CANON_ENDPOINT_PROTOCOL = "canon.endpoint.v1";
5095
- var ENDPOINT_ID_PATTERN = /^[A-Za-z0-9_.:~-]{1,200}$/;
5096
- var ENDPOINT_ACTION_PATTERN = /^[a-z][a-z0-9_.-]{0,95}$/;
5097
- function isEndpointOperationReceipt(value) {
5098
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
5099
- const receipt = value;
5100
- return receipt.protocol === CANON_ENDPOINT_PROTOCOL && typeof receipt.operationId === "string" && ENDPOINT_ID_PATTERN.test(receipt.operationId) && typeof receipt.installationId === "string" && ENDPOINT_ID_PATTERN.test(receipt.installationId) && typeof receipt.action === "string" && ENDPOINT_ACTION_PATTERN.test(receipt.action) && ["pending", "succeeded", "rejected", "uncertain"].includes(String(receipt.status)) && typeof receipt.createdAt === "string" && typeof receipt.updatedAt === "string";
5108
+ // ../backend-contracts/src/conversationMembership.ts
5109
+ function resolveConversationPolicyScope(memberIdsOrCount) {
5110
+ const count = Array.isArray(memberIdsOrCount) ? new Set(memberIdsOrCount.filter((id7) => typeof id7 === "string" && id7.length > 0)).size : memberIdsOrCount;
5111
+ if (typeof count !== "number" || !Number.isSafeInteger(count)) return "unknown";
5112
+ return count === 2 ? "direct" : count > 2 ? "group" : "unknown";
5101
5113
  }
5102
- __name(isEndpointOperationReceipt, "isEndpointOperationReceipt");
5114
+ __name(resolveConversationPolicyScope, "resolveConversationPolicyScope");
5103
5115
 
5104
5116
  // ../backend-contracts/src/connectorRelay.ts
5105
5117
  var CONNECTOR_RELAY_INSTALLATION_PREFIX = "canon-relay:";
@@ -5519,6 +5531,7 @@ export {
5519
5531
  parseMlsDevGroupFeedQuery,
5520
5532
  parseMlsDevGroupFeedResult,
5521
5533
  MLS_DEV_KEY_PACKAGES_MAX_BYTES,
5534
+ MLS_DEV_KEY_PACKAGE_CLAIM_LEASE_MS,
5522
5535
  parseMlsDevKeyPackageClaimPreconditions,
5523
5536
  parseMlsDevKeyPackagePublish,
5524
5537
  parseMlsDevKeyPackagePublicationReceipt,
@@ -5562,6 +5575,7 @@ export {
5562
5575
  buildParticipationHistorySnapshot,
5563
5576
  evaluateParticipationPolicy,
5564
5577
  MAX_DISCOVER_AGENTS_QUERY_LENGTH,
5578
+ VERB_LIMITS,
5565
5579
  NO_REPLY_ACK_NOTE,
5566
5580
  CANON_VERB_WIRE_SCHEMA_VERSION,
5567
5581
  projectVerbIntentToWire,
@@ -5583,6 +5597,8 @@ export {
5583
5597
  ENDPOINT_ID_PATTERN,
5584
5598
  ENDPOINT_ACTION_PATTERN,
5585
5599
  isEndpointOperationReceipt,
5600
+ AGENT_HOME_ELSEWHERE,
5601
+ agentHomeElsewhereMessage,
5586
5602
  CONNECTOR_RELAY_INSTALLATION_PREFIX,
5587
5603
  CONNECTOR_RELAY_INSTALLATION_PATTERN,
5588
5604
  isConnectorRelayInstallation,