@canonmsg/agent-sdk 12.5.1 → 13.0.2

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.
Files changed (46) hide show
  1. package/README.md +57 -24
  2. package/dist/chunks/{chunk-C2WJT3VX.js → chunk-5WPAB5MB.js} +1047 -856
  3. package/dist/chunks/{chunk-3KFVBOVI.js → chunk-DNHJGAEL.js} +1115 -1020
  4. package/dist/chunks/chunk-DTAWEQCM.js +406 -0
  5. package/dist/chunks/{chunk-TFSURFF6.js → chunk-TYZT346O.js} +1720 -4006
  6. package/dist/chunks/chunk-YHDDOADK.js +118 -0
  7. package/dist/chunks/chunk-YJUPKU47.js +1490 -0
  8. package/dist/chunks/{mls-crypto-Y44BOSEM.js → mls-crypto-NZVMSUFU.js} +2 -1
  9. package/dist/cli.js +86 -48
  10. package/dist/drivers/acp.d.ts +56 -0
  11. package/dist/drivers/codex.d.ts +1 -2
  12. package/dist/drivers/subprocess.d.ts +38 -0
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.js +12 -3
  15. package/dist/internal/backend-contracts/endpointContentPolicy.d.ts +2 -2
  16. package/dist/internal/backend-contracts/index.d.ts +1 -0
  17. package/dist/internal/backend-contracts/loggedOutSessions.d.ts +7 -0
  18. package/dist/internal/backend-contracts/mlsDevCredential.d.ts +3 -0
  19. package/dist/internal/backend-contracts/mlsDevGroup.d.ts +17 -0
  20. package/dist/internal/core/completed-output-journal.d.ts +0 -5
  21. package/dist/internal/core/index.d.ts +3 -9
  22. package/dist/internal/core/retry.d.ts +2 -89
  23. package/dist/internal/core/silent-turn.d.ts +4 -33
  24. package/dist/internal/endpoint/engine.d.ts +6 -0
  25. package/dist/internal/endpoint/mls-dev-client.d.ts +2 -0
  26. package/dist/internal/endpoint/mls-dev-groups.d.ts +12 -0
  27. package/dist/internal/endpoint/mls-dev-text.d.ts +32 -0
  28. package/dist/internal/endpoint/node-mls-custody-protocol.d.ts +5 -0
  29. package/dist/internal/endpoint/node.d.ts +1 -0
  30. package/dist/internal/endpoint/transport.d.ts +1 -1
  31. package/dist/media.js +4 -2
  32. package/dist/mls/darwin-arm64/canon-mls-custody +0 -0
  33. package/dist/mls/linux-arm64/canon-mls-custody +0 -0
  34. package/dist/mls/linux-x64/canon-mls-custody +0 -0
  35. package/dist/mls/wasm/canon_mls_wasm.js +363 -0
  36. package/dist/mls/wasm/canon_mls_wasm_bg.wasm +0 -0
  37. package/dist/mls-wasm-host.js +258 -0
  38. package/dist/runner/mls-bundled.d.ts +15 -2
  39. package/dist/runner/mls-managed.d.ts +6 -3
  40. package/dist/runner/mls.d.ts +4 -7
  41. package/dist/runner/runner.d.ts +9 -12
  42. package/dist/runner/types.d.ts +37 -1
  43. package/package.json +1 -1
  44. package/dist/internal/core/approval-manager.d.ts +0 -43
  45. package/dist/internal/core/runtime-request-manager.d.ts +0 -283
  46. package/dist/internal/core/turn-verbosity.d.ts +0 -79
@@ -0,0 +1,258 @@
1
+ import {
2
+ WASM_HOST_CONFIGURE,
3
+ WASM_HOST_PROVISION,
4
+ keyFile
5
+ } from "./chunks/chunk-DTAWEQCM.js";
6
+ import {
7
+ MAX_INPUT,
8
+ NodeMlsDevCustodyError,
9
+ parseFrame,
10
+ rejected,
11
+ requireValue
12
+ } from "./chunks/chunk-YJUPKU47.js";
13
+ import {
14
+ __name,
15
+ canonicalEndpointJson,
16
+ isMlsDevReceiptClockSkew,
17
+ parseMlsDevDeviceContext
18
+ } from "./chunks/chunk-5WPAB5MB.js";
19
+
20
+ // src/mls-wasm-host.ts
21
+ import { createHash } from "node:crypto";
22
+ import { existsSync, readFileSync } from "node:fs";
23
+
24
+ // ../endpoint/src/mls-browser-custody.ts
25
+ var AAD_DOMAIN = "canon.mls.browser-state.v1";
26
+ var encoder = new TextEncoder();
27
+ var decoder = new TextDecoder();
28
+ var MAX_PAYLOAD_BYTES = 4 * 1024 * 1024;
29
+ var refused = /* @__PURE__ */ __name(() => {
30
+ throw new Error("CANON_MLS_CUSTODY_REFUSED");
31
+ }, "refused");
32
+ function toBase64(bytes) {
33
+ let text = "";
34
+ for (let at = 0; at < bytes.length; at += 32768) text += String.fromCharCode(...bytes.subarray(at, at + 32768));
35
+ return btoa(text);
36
+ }
37
+ __name(toBase64, "toBase64");
38
+ function fromBase64(text) {
39
+ const binary = atob(text), bytes = new Uint8Array(binary.length);
40
+ for (let at = 0; at < binary.length; at += 1) bytes[at] = binary.charCodeAt(at);
41
+ return bytes;
42
+ }
43
+ __name(fromBase64, "fromBase64");
44
+ function parsedContext(json) {
45
+ const { revision, ...context } = JSON.parse(json);
46
+ if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 0) refused();
47
+ return { contextId: canonicalEndpointJson(context), context, revision };
48
+ }
49
+ __name(parsedContext, "parsedContext");
50
+ function createMlsBrowserCustody(options) {
51
+ const { engine, wrappingKey } = options;
52
+ const clock = options.clock ?? Date.now;
53
+ const issuerKeys = encoder.encode(JSON.stringify(options.issuerKeys)), receiptKeys = encoder.encode(JSON.stringify(options.receiptKeys));
54
+ const aad = /* @__PURE__ */ __name((purpose, context, binding) => encoder.encode(canonicalEndpointJson({ domain: AAD_DOMAIN, purpose, context, binding })), "aad");
55
+ async function wrap(key, additionalData, clear) {
56
+ const iv = crypto.getRandomValues(new Uint8Array(12));
57
+ const sealed = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData }, key, clear));
58
+ const framed = new Uint8Array(12 + sealed.length);
59
+ framed.set(iv);
60
+ framed.set(sealed, 12);
61
+ return `v1:${toBase64(framed)}`;
62
+ }
63
+ __name(wrap, "wrap");
64
+ async function unwrap(key, additionalData, wrapped) {
65
+ if (!wrapped.startsWith("v1:")) refused();
66
+ const framed = fromBase64(wrapped.slice(3));
67
+ if (framed.length < 28) refused();
68
+ return new Uint8Array(await crypto.subtle.decrypt({ name: "AES-GCM", iv: framed.subarray(0, 12), additionalData }, key, framed.subarray(12)));
69
+ }
70
+ __name(unwrap, "unwrap");
71
+ function answer2(bytes) {
72
+ if (bytes.length < 4) refused();
73
+ const length = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);
74
+ if (length === 0) return { skew: decoder.decode(bytes.subarray(4)) };
75
+ if (length > MAX_PAYLOAD_BYTES || length > bytes.length - 4) refused();
76
+ return { payload: decoder.decode(bytes.subarray(4, 4 + length)), clear: bytes.slice(4 + length) };
77
+ }
78
+ __name(answer2, "answer");
79
+ async function finish(bytes, seal) {
80
+ try {
81
+ const result = answer2(bytes);
82
+ if ("skew" in result) return result.skew;
83
+ try {
84
+ return `{"wrappedState":${JSON.stringify(await seal(result.clear, result.payload))},"payload":${result.payload}}`;
85
+ } finally {
86
+ result.clear.fill(0);
87
+ }
88
+ } finally {
89
+ bytes.fill(0);
90
+ }
91
+ }
92
+ __name(finish, "finish");
93
+ return Object.freeze({
94
+ async initializeDevSigning(contextJson) {
95
+ await wrappingKey(parsedContext(contextJson).contextId);
96
+ },
97
+ async prepareDevSigning(contextJson, commandJson, wrappedState) {
98
+ const { contextId, context, revision } = parsedContext(contextJson), key = await wrappingKey(contextId);
99
+ const clear = wrappedState === null ? void 0 : await unwrap(key, aad("identity", context, { revision }), wrappedState);
100
+ try {
101
+ const prepared2 = engine.prepare_signing(encoder.encode(contextJson), encoder.encode(commandJson), clear, clock());
102
+ return await finish(prepared2, (next) => wrap(key, aad("identity", context, { revision: revision + 1 }), next));
103
+ } finally {
104
+ clear?.fill(0);
105
+ }
106
+ },
107
+ async prepareDevKeyPackage(contextJson, commandJson, wrappedIdentityState) {
108
+ const { contextId, context, revision } = parsedContext(contextJson), key = await wrappingKey(contextId);
109
+ const identity2 = await unwrap(key, aad("identity", context, { revision }), wrappedIdentityState);
110
+ try {
111
+ const prepared2 = engine.prepare_key_package(encoder.encode(contextJson), encoder.encode(commandJson), identity2, issuerKeys, clock());
112
+ return await finish(prepared2, (next, payload) => {
113
+ const { keyPackageId, deviceId } = JSON.parse(payload);
114
+ return wrap(key, aad("key-package", context, { version: 1, keyPackageId, deviceId }), next);
115
+ });
116
+ } finally {
117
+ identity2.fill(0);
118
+ }
119
+ },
120
+ async prepareDevGroup(identityContextJson, groupContextJson, commandJson, wrappedIdentityState, wrappedGroupState, wrappedKeyPackageState) {
121
+ const { contextId, context, revision } = parsedContext(identityContextJson), key = await wrappingKey(contextId);
122
+ const group = JSON.parse(groupContextJson);
123
+ const clear = [];
124
+ const open = /* @__PURE__ */ __name(async (additionalData, wrapped) => {
125
+ const opened = await unwrap(key, additionalData, wrapped);
126
+ clear.push(opened);
127
+ return opened;
128
+ }, "open");
129
+ try {
130
+ const identity2 = await open(aad("identity", context, { revision }), wrappedIdentityState);
131
+ const groupState = wrappedGroupState === null ? void 0 : await open(aad("group", context, group), wrappedGroupState);
132
+ let keyPackageState;
133
+ if (wrappedKeyPackageState !== null) {
134
+ const { keyPackageId } = JSON.parse(commandJson);
135
+ keyPackageState = await open(aad("key-package", context, { version: 1, keyPackageId, deviceId: group.deviceId }), wrappedKeyPackageState);
136
+ }
137
+ const prepared2 = engine.prepare_group(
138
+ encoder.encode(identityContextJson),
139
+ encoder.encode(groupContextJson),
140
+ encoder.encode(commandJson),
141
+ identity2,
142
+ groupState,
143
+ keyPackageState,
144
+ issuerKeys,
145
+ receiptKeys,
146
+ clock()
147
+ );
148
+ return await finish(prepared2, (next) => wrap(key, aad("group", context, { ...group, revision: group.revision + 1 }), next));
149
+ } finally {
150
+ clear.forEach((bytes) => bytes.fill(0));
151
+ }
152
+ }
153
+ });
154
+ }
155
+ __name(createMlsBrowserCustody, "createMlsBrowserCustody");
156
+
157
+ // src/mls-wasm-host.ts
158
+ var NULL = Buffer.from('{"version":1,"ok":true,"value":null}');
159
+ var REJECTED = Buffer.from('{"version":1,"ok":false,"error":"MLS_NODE_CUSTODY_REJECTED"}');
160
+ var sha256 = /* @__PURE__ */ __name((bytes) => createHash("sha256").update(bytes).digest("hex"), "sha256");
161
+ var prepared = /* @__PURE__ */ __name((value) => Buffer.from(isMlsDevReceiptClockSkew(JSON.parse(value)) ? value : `{"version":1,"ok":true,"value":${value}}`), "prepared");
162
+ var configuration;
163
+ var custody;
164
+ var wrapping;
165
+ async function load() {
166
+ requireValue(existsSync(configuration.keyPath));
167
+ const bytes = keyFile(configuration.keyPath);
168
+ try {
169
+ return await crypto.subtle.importKey("raw", bytes, "AES-GCM", false, ["encrypt", "decrypt"]);
170
+ } finally {
171
+ bytes.fill(0);
172
+ }
173
+ }
174
+ __name(load, "load");
175
+ async function configure(value) {
176
+ const wasm = readFileSync(value.wasmPath), glue = readFileSync(value.gluePath);
177
+ requireValue(sha256(wasm) === value.wasmSha256 && sha256(glue) === value.glueSha256);
178
+ const engine = await import(`data:text/javascript;base64,${glue.toString("base64")}`);
179
+ engine.initSync({ module: wasm });
180
+ configuration = value;
181
+ custody = createMlsBrowserCustody({
182
+ engine,
183
+ issuerKeys: value.issuerKeys,
184
+ receiptKeys: value.receiptKeys,
185
+ wrappingKey: /* @__PURE__ */ __name(() => wrapping ??= load().catch((error) => {
186
+ wrapping = void 0;
187
+ throw error;
188
+ }), "wrappingKey")
189
+ });
190
+ }
191
+ __name(configure, "configure");
192
+ var identity = /* @__PURE__ */ __name((value) => {
193
+ const { revision: _revision, ...device } = parseMlsDevDeviceContext(value);
194
+ return JSON.stringify(device);
195
+ }, "identity");
196
+ function own(value) {
197
+ requireValue(typeof value === "string" && configuration);
198
+ requireValue(identity(JSON.parse(value)) === identity({ ...configuration.context, revision: 0 }));
199
+ return value;
200
+ }
201
+ __name(own, "own");
202
+ async function answer(operation, values) {
203
+ if (operation === WASM_HOST_CONFIGURE) {
204
+ requireValue(!custody && values.length === 1);
205
+ await configure(JSON.parse(values[0]));
206
+ return NULL;
207
+ }
208
+ const context = own(values[0]);
209
+ if (operation === WASM_HOST_PROVISION) {
210
+ requireValue(values.length === 1 && !existsSync(configuration.keyPath));
211
+ keyFile(configuration.keyPath).fill(0);
212
+ return NULL;
213
+ }
214
+ if (operation === 1 && values.length === 1) {
215
+ await custody.initializeDevSigning(context);
216
+ return NULL;
217
+ }
218
+ if (operation === 2 && values.length === 3) return prepared(await custody.prepareDevSigning(context, values[1], values[2]));
219
+ if (operation === 3 && values.length === 3) return prepared(await custody.prepareDevKeyPackage(context, values[1], values[2]));
220
+ if (operation === 4 && values.length === 6) {
221
+ return prepared(await custody.prepareDevGroup(context, values[1], values[2], values[3], values[4], values[5]));
222
+ }
223
+ return rejected();
224
+ }
225
+ __name(answer, "answer");
226
+ async function* frames(input) {
227
+ let buffered = Buffer.alloc(0);
228
+ for await (const chunk of input) {
229
+ buffered = Buffer.concat([buffered, chunk]);
230
+ while (buffered.length >= 4) {
231
+ const length = buffered.readUInt32BE(0);
232
+ requireValue(length <= MAX_INPUT);
233
+ if (buffered.length < 4 + length) break;
234
+ yield buffered.subarray(4, 4 + length);
235
+ buffered = buffered.subarray(4 + length);
236
+ }
237
+ }
238
+ }
239
+ __name(frames, "frames");
240
+ function send(message) {
241
+ const length = Buffer.alloc(4);
242
+ length.writeUInt32BE(message.length);
243
+ return new Promise((sent) => {
244
+ process.stdout.write(Buffer.concat([length, message]), () => sent());
245
+ });
246
+ }
247
+ __name(send, "send");
248
+ for (const signal of ["SIGINT", "SIGTERM", "SIGBREAK", "SIGHUP"]) process.on(signal, () => void 0);
249
+ for await (const message of frames(process.stdin)) {
250
+ try {
251
+ const { operation, values } = parseFrame(message);
252
+ await send(await answer(operation, values));
253
+ } catch (error) {
254
+ if (!custody || !(error instanceof NodeMlsDevCustodyError) && error.message !== "CANON_MLS_CUSTODY_REFUSED") process.exit(70);
255
+ await send(REJECTED);
256
+ }
257
+ }
258
+ process.exit(0);
@@ -16,10 +16,23 @@ export declare const MLS_ENVIRONMENT_PINS: Readonly<Record<MlsEnvironmentId, {
16
16
  }>>;
17
17
  /**
18
18
  * The custody helper this package ships for this host (`dist/mls/<platform>/canon-mls-custody`), checked against its
19
- * pin, or undefined where it ships none: an Intel Mac, musl, Windows, or a build of this package without helpers. A
20
- * file that is there but differs from its pin is refused.
19
+ * pin, or undefined where it ships none that runs: an Intel Mac, Windows, musl, an old glibc, or a build of this package
20
+ * without helpers. A file that is there but differs from its pin is refused.
21
21
  */
22
22
  export declare function bundledMlsCustodyHelper(): {
23
23
  path: string;
24
24
  sha256: string;
25
25
  } | undefined;
26
+ export interface BundledMlsWasmCore {
27
+ /** The process that runs the core (dist/mls-wasm-host.js). */
28
+ hostPath: string;
29
+ wasmPath: string;
30
+ wasmSha256: string;
31
+ gluePath: string;
32
+ glueSha256: string;
33
+ }
34
+ /**
35
+ * The WASM core this package ships for hosts with no helper (`dist/mls/wasm/`), checked against its pins, or undefined in a
36
+ * build of this package without it. A file that is there but differs from its pin is refused.
37
+ */
38
+ export declare function bundledMlsWasmCore(): BundledMlsWasmCore | undefined;
@@ -1,4 +1,5 @@
1
1
  import { type MlsDevDeviceContext, type MlsEnvironmentId } from '../internal/backend-contracts/index.js';
2
+ import { type BundledMlsWasmCore } from './mls-bundled.js';
2
3
  import type { RunnerMlsOptions } from './mls.js';
3
4
  export interface ManagedMlsDeviceOptions {
4
5
  /** The runner's home (CANON_HOME, or a relay installation's home). */
@@ -9,6 +10,8 @@ export interface ManagedMlsDeviceOptions {
9
10
  path: string;
10
11
  sha256: string;
11
12
  };
13
+ /** The WASM core rather than a helper, as on a host with none. */
14
+ wasm?: BundledMlsWasmCore;
12
15
  /** Other public pins than this package's for the environment. */
13
16
  pins?: {
14
17
  issuerKeys: ReadonlyArray<{
@@ -24,13 +27,13 @@ export interface ManagedMlsDeviceOptions {
24
27
  }
25
28
  export type ManagedMlsDevice = RunnerMlsOptions & {
26
29
  provision(context: MlsDevDeviceContext): Promise<void>;
27
- /** Whether this context's vault exists: its device was provisioned here. */
30
+ /** Whether this context's device was provisioned here. Throws, saying why, if it was made in the way this host cannot open. */
28
31
  provisioned(context: MlsDevDeviceContext): boolean;
29
32
  close(): Promise<void>;
30
33
  };
31
34
  /**
32
- * A runner's device where it is given none (`createRunner` and the CLI alike): managed custody with this package's helper
33
- * for the host, or undefined where it ships none or the environment has no MLS.
35
+ * A runner's device where it is given none (`createRunner` and the CLI alike): managed custody as `custodyFor` chooses, or
36
+ * undefined where this package ships neither helper nor core, or the environment has no MLS.
34
37
  */
35
38
  export declare function defaultMlsDevice(home: string, environmentId: string): ManagedMlsDevice | undefined;
36
39
  export declare function managedMlsDevice(options: ManagedMlsDeviceOptions): ManagedMlsDevice;
@@ -27,12 +27,12 @@ export declare function mlsAudiencePrincipals(value: MlsDevCurrentAudience): str
27
27
  */
28
28
  export declare function mlsAudienceEpoch(value: MlsDevCurrentAudience): string;
29
29
  /** What an encrypted room's text message is to the runner; Canon never learns it (`MlsIds.text`). */
30
- export type MlsTextKind = 'final' | 'failed' | 'restart' | 'said' | 'item';
30
+ export type MlsTextKind = 'final' | 'failed' | 'restart' | 'said' | 'item' | 'part';
31
31
  /**
32
32
  * §6 ids. Canon sees the message id of everything the runner sends to an encrypted room, and a card's request id (its
33
33
  * public binding), so these are keyed with a secret only this installation holds (`privateKey`): HMAC-SHA256 over
34
34
  * domain-separated inputs, cut to 128 bits. Canon can test no guess of the text, question or turn an id names, and every
35
- * text message (a final, a failure or restart notice, a `communicate` send, an item) has the same shape, `msg-<32 hex>`, so none
35
+ * text message (a final, a part of one, a failure or restart notice, a `communicate` send, an item) has the same shape, `msg-<32 hex>`, so none
36
36
  * says what kind it is. The same inputs give the same id after a restart, so journal replay, a repeated `communicate`,
37
37
  * a re-asked card and a restart notice keep theirs.
38
38
  */
@@ -46,14 +46,11 @@ export interface MlsIds {
46
46
  export declare const MLS_ID_KEY_BYTES = 32;
47
47
  export declare function createMlsIds(key: Uint8Array): MlsIds;
48
48
  /**
49
- * Encrypted rooms are for agents whose outbound communication is not closed, in an environment with an MLS profile. Canon
50
- * decides who joins a room under the invitation policies, as for plaintext rooms, and whether that environment runs MLS.
49
+ * Encrypted rooms are for an environment with an MLS profile. Canon decides who joins a room under the invitation policies,
50
+ * as for plaintext rooms, and a closed agent's text needs authority for each reply (`replyGrant` in runner.ts).
51
51
  */
52
52
  export declare function requireMlsSupport(input: {
53
53
  environmentId: string;
54
- agent: {
55
- outboundPolicy: string;
56
- };
57
54
  }): void;
58
55
  /**
59
56
  * Whether `text` @mentions any of `names` (an agent's display name, id or aliases). Encrypted text carries no `mentions[]`,
@@ -33,11 +33,9 @@ export interface RunnerOptions {
33
33
  */
34
34
  mls?: RunnerMlsOptions | false;
35
35
  /**
36
- * Relay mode, the class-C relay (spec §11.4): only encrypted rooms are served, which is what lets a `remote` driver
37
- * (the connector) into them. Plaintext input is cancelled with no turn (the connector's own installation serves it),
38
- * control hints are read only for encrypted rooms, and `relayRead` and `relaySend` serve the relay channel. It publishes
39
- * the runtime descriptor and heartbeat, so the apps show a relayed turn working and offer Stop. It needs the agent's MLS
40
- * device.
36
+ * Relay mode, the class-C relay (spec §11.4): what lets a `remote` driver (the connector) into encrypted rooms, and
37
+ * `relayRead`, `relaySend` and `relayStart` serve the relay channel. It publishes the runtime descriptor and heartbeat,
38
+ * so the apps show a relayed turn working and offer Stop. It needs the agent's MLS device.
41
39
  */
42
40
  relay?: boolean;
43
41
  /** The engine's installation: the relay's reserved one, from its configuration. A store opened under another refuses to start. */
@@ -45,18 +43,17 @@ export interface RunnerOptions {
45
43
  /**
46
44
  * Team rooms: agents the owner trusts. A message from one of them counts as the owner's for tool permissions (the
47
45
  * drivers' guards read `TurnInputMessage.trustedAgent`) only in a room that holds no one but this agent, its owner and
48
- * listed agents, all of which Canon confirms at the start of the turn are active agents of this agent's own owner; in
49
- * a plaintext room only for a final its runner marked as answering the owner or confirmed teammates alone, and in an
50
- * encrypted room never with a relay device in the group. A `remote` runtime has no tools on this host: a relay ignores it.
46
+ * listed agents, all of which Canon confirms at the start of the turn are active agents of this agent's own owner, and
47
+ * never with a relay device in the group. A `remote` runtime has no tools on this host: a relay ignores it.
51
48
  */
52
49
  trustedAgents?: string[];
53
50
  /**
54
- * Encrypted rooms (§6): how many messages from other agents that @mention this agent a room may wake it with before a
55
- * person's message is admitted there again (default 6; 0 never wakes on an agent). Past the cap they are settled with
56
- * no turn, logged once. Plaintext rooms are Canon's to dispatch (RUN-29).
51
+ * How many messages from other agents that @mention this agent a room may wake it with before a person writes there
52
+ * again (default 6; 0 never wakes on an agent). Past the cap they are settled with no turn, logged once. The room's own
53
+ * rules can stop them sooner (`RoomRules`).
57
54
  */
58
55
  agentTurnCap?: number;
59
- /** Encrypted rooms: names besides the agent's display name and id that @mention it, such as a role (`@engineer`). */
56
+ /** Names besides the agent's display name and id that @mention it, such as a role (`@engineer`). */
60
57
  mentionAliases?: string[];
61
58
  /** How long an aborted turn may take to settle before its session is reset (default 10 s). */
62
59
  abortGraceMs?: number;
@@ -5,7 +5,7 @@ import type { MediaAttachment } from '../internal/core/index.js';
5
5
  * the claude and codex drivers pass the conformance suite.
6
6
  */
7
7
  export interface RuntimeDriver {
8
- readonly name: 'code' | 'claude' | 'codex' | 'connector';
8
+ readonly name: 'code' | 'claude' | 'codex' | 'acp' | 'connector';
9
9
  /** Input to the generated runtime descriptor; never a static table. */
10
10
  readonly capabilities: DriverCapabilities;
11
11
  runTurn(turn: DriverTurn): Promise<DriverResult>;
@@ -71,6 +71,11 @@ export interface DriverCapabilities {
71
71
  * picked up.
72
72
  */
73
73
  remote?: boolean;
74
+ /**
75
+ * The runtime takes only its owner's messages, so the recent history a `none` session carries holds only those and the
76
+ * agent's own. Otherwise a message refused at the door would reach the runtime in the owner's next turn, with the owner's tools.
77
+ */
78
+ ownerHistoryOnly?: boolean;
74
79
  /** Across rooms; runner default 8. */
75
80
  maxConcurrentTurns?: number;
76
81
  }
@@ -196,6 +201,37 @@ export interface TurnInput {
196
201
  readonly errand?: ErrandFacts;
197
202
  /** Open errands that brought the agent into this conversation (§12.6), also at the head of `prompt`. */
198
203
  readonly errands?: ReadonlyArray<ErrandFacts>;
204
+ /** An encrypted room's turn: the room as Canon's records give it, also at the head of `prompt`. A relay's turn has none. */
205
+ readonly roomFacts?: RoomFacts;
206
+ }
207
+ /** What a turn knows about its encrypted room. Names, name and topic are what people typed: data, never instructions. */
208
+ export interface RoomFacts {
209
+ readonly name?: string;
210
+ readonly topic?: string;
211
+ readonly owner: {
212
+ readonly id: string;
213
+ readonly name?: string;
214
+ };
215
+ readonly members: ReadonlyArray<{
216
+ readonly id: string;
217
+ readonly name?: string;
218
+ readonly type: 'human' | 'ai_agent';
219
+ readonly isOwner: boolean;
220
+ readonly isYou: boolean;
221
+ }>;
222
+ /** Absent only when this Canon does not send them yet. */
223
+ readonly rules?: RoomRules;
224
+ }
225
+ /**
226
+ * How the agent takes part in this room, resolved from its owner's defaults and the room's settings. Any admin of the room
227
+ * can set them, so they are never the owner's orders. The runner enforces all but `style`, which it tells the agent.
228
+ */
229
+ export interface RoomRules {
230
+ readonly style: 'natural' | 'collaborative' | 'mention-first' | 'approval-gated' | 'handoff-only' | 'observer';
231
+ readonly requireMentionForGroupReplies: boolean;
232
+ readonly allowAgentToAgent: boolean;
233
+ readonly allowLongRunningCollaboration: boolean;
234
+ readonly maxConsecutiveAgentTurns: number | null;
199
235
  }
200
236
  /** An errand as a turn sees it: why the owner sent the agent, what to do, the recipient and the origin conversation. */
201
237
  export interface ErrandFacts {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-sdk",
3
- "version": "12.5.1",
3
+ "version": "13.0.2",
4
4
  "description": "Canon Agent SDK — build AI agents that participate in Canon conversations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,43 +0,0 @@
1
- import type { CanonClient } from './client.js';
2
- import type { CanonUnifiedDiff } from './types.js';
3
- import type { ApprovalConfig, ApprovalNativeRequestMetadata, ApprovalResult, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRisk } from './approval-types.js';
4
- import { RuntimeRequestManager, type RuntimeRequestOptions } from './runtime-request-manager.js';
5
- interface ApprovalRequestOpts {
6
- riskLevel?: 'normal' | 'destructive';
7
- risk?: ApprovalRisk;
8
- category?: ApprovalRequestCategory;
9
- runtimeId?: string;
10
- turnId?: string;
11
- native?: ApprovalNativeRequestMetadata;
12
- toolSummary?: string;
13
- details?: ApprovalRequestDetail[];
14
- /** File-change preview rendered inline on the approval card (size-disciplined automatically). */
15
- diff?: CanonUnifiedDiff;
16
- ignoreSessionRules?: boolean;
17
- allowSessionRule?: boolean;
18
- /** Human conversation member who triggered this turn and should answer. */
19
- responseUserId?: string;
20
- }
21
- /**
22
- * Platform-agnostic approval protocol for Canon.
23
- *
24
- * Now a thin `RuntimeRequestManager` subclass: the pending map, deadline timers,
25
- * the hybrid poll-plus-push loop, and cancellation live in the generalized
26
- * engine; approval-specific behaviour (session-rule fast path, redacted request
27
- * metadata, owner-authored outcome messages, and session-rule storage) is
28
- * expressed as a single `KindDescriptor`. Behaviour is preserved verbatim.
29
- */
30
- export declare class ApprovalManager extends RuntimeRequestManager {
31
- private config;
32
- constructor(client: CanonClient, agentId: string, ownerId: string, config?: Partial<ApprovalConfig>);
33
- /**
34
- * Request approval for a tool call.
35
- *
36
- * 1. Checks session rules — may resolve immediately.
37
- * 2. Sends a message with approval card metadata.
38
- * 3. Waits for the owner's reply (or timeout).
39
- */
40
- requestApproval(conversationId: string, toolName: string, toolInput: Record<string, unknown>, opts?: ApprovalRequestOpts, runtimeOptions?: RuntimeRequestOptions): Promise<ApprovalResult>;
41
- private buildApprovalDescriptor;
42
- }
43
- export {};