@canonmsg/backend-contracts 7.0.0 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,9 +32,9 @@ The JSON Schemas are also emitted as files for non-JavaScript consumers: `@canon
32
32
 
33
33
  **Message and media serialization.** The canonical `SerializedContentType` union (`text | image | audio | video | file | contact_card | interaction`), attachment normalization, and the single runtime-card decoder that `@canonmsg/core` re-exports rather than reimplements.
34
34
 
35
- **Behavior policy and contact requests.** The participation evaluator the stream service runs before dispatching a turn, and the contact-request serializer both sides validate against.
35
+ **Behavior policy.** The participation evaluator the stream service runs before dispatching a turn.
36
36
 
37
- **Server-side normalizers.** `readCanonicalPolicy` resolves a stored `inboundPolicy` / `groupJoinPolicy` field to the access triplet's three values, defaulting to `approval-required` rather than widening to `open`. `serializeSelfContext` is the agent-facing projection of a stored self-context. `readModerationStatus` is the single definition of an ejected account. All take plain document-shaped data — the package still performs no I/O.
37
+ **Server-side normalizers.** `readCommunicationRule` resolves a stored communication-policy field to `open | approval-required | closed`, defaulting to `approval-required` rather than widening to `open`. `serializeSelfContext` is the agent-facing projection of a stored self-context. `readModerationStatus` is the single definition of an ejected account. All take plain document-shaped data — the package still performs no I/O.
38
38
 
39
39
  **Environments.** `CANON_ENVIRONMENT_CONTRACTS` binds `canon-dev-v1` and `canon-prod-v1` to their project and region; `getCanonEnvironmentContract` rejects anything else rather than defaulting.
40
40
 
@@ -81,32 +81,24 @@ export interface ParticipationHistorySnapshot {
81
81
  }
82
82
  export declare const PARTICIPATION_HISTORY_FETCH_LIMIT = 50;
83
83
  /**
84
- * Safety backstop for groups with no explicit turn cap: at most this many
85
- * consecutive agent turns before a human has to speak again.
84
+ * Safety backstop for conversations with no explicit turn cap: at most this
85
+ * many consecutive agent turns before a human speaks or a new conversation
86
+ * starts.
86
87
  *
87
88
  * This is a backstop, not the steering wheel — agents are expected to stop on
88
- * their own. Direct conversations deliberately stay unlimited: only a non-agent
89
- * message resets the streak, so a default cap in a pure agent-to-agent DM would
90
- * halt the room permanently instead of merely bounding a loop.
91
- *
92
- * The same caveat applies to the one group shape this default does cover: a
93
- * group whose members are all agents (reachable — agent-created groups need no
94
- * human member) has nothing that resets the streak, so it stops auto-replying
95
- * for good once the cap is reached. That is deliberate: an unattended room is
96
- * where a runaway loop is most expensive. Recovery needs a human member — the
97
- * per-conversation `null` opt-out is writable only by a group owner or admin,
98
- * who must be a member. Teaching the resolver "does this room contain a
99
- * human?" would mean loading member types at every resolution site, including
100
- * the stream gate, which does not read them today.
89
+ * their own. Every triggerable agent message counts in both direct and group
90
+ * conversations. An explicit `null` remains the deployer opt-out for trusted,
91
+ * intentionally long-running collaboration.
101
92
  */
102
93
  export declare const DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS = 4;
94
+ export declare const DEFAULT_DIRECT_MAX_CONSECUTIVE_AGENT_TURNS = 4;
103
95
  export declare function parseAgentBehaviorSettings(raw: unknown): AgentBehaviorSettingsRecord;
104
96
  export declare function normalizeStoredAgentBehaviorPolicy(raw: Record<string, unknown> | undefined): AgentBehaviorSettingsRecord | null;
105
97
  export declare function normalizeAgentBehaviorInstructions(value: string | null | undefined): string | null;
106
98
  /**
107
99
  * The scope-independent participation defaults. `maxConsecutiveAgentTurns` is
108
- * `null` here because the turn cap is scope-dependent — a group with no stored
109
- * setting resolves to {@link DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS}. Use
100
+ * `null` here because the turn cap is scope-dependent — a conversation with no
101
+ * stored setting resolves to its direct or group safety default. Use
110
102
  * {@link resolveAgentBehaviorPolicy} with a `conversationType` to learn what a
111
103
  * given conversation actually enforces.
112
104
  */
@@ -114,9 +106,9 @@ export declare function getDefaultParticipationPolicy(): ParticipationPolicy;
114
106
  /**
115
107
  * Coalesce agent defaults and a conversation override into the resolved record
116
108
  * every runtime reads. `conversationType` selects the Canon-wide fallbacks that
117
- * differ per scope — today only the turn cap, which defaults to
118
- * {@link DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS} in groups and to unlimited
119
- * everywhere else. Omit it when resolving for the agent scope (no conversation);
109
+ * differ per scope — today only the turn cap, which defaults to 4 in direct and
110
+ * group conversations and to unlimited elsewhere. Omit it when resolving for
111
+ * the agent scope (no conversation);
120
112
  * an absent or unknown type always resolves to the unlimited default, so a call
121
113
  * site that forgets to thread it can never invent a cap.
122
114
  */
@@ -1,29 +1,25 @@
1
1
  import { shouldTriggerAgentTurn } from './turnProtocol.js';
2
2
  export const PARTICIPATION_HISTORY_FETCH_LIMIT = 50;
3
3
  /**
4
- * Safety backstop for groups with no explicit turn cap: at most this many
5
- * consecutive agent turns before a human has to speak again.
4
+ * Safety backstop for conversations with no explicit turn cap: at most this
5
+ * many consecutive agent turns before a human speaks or a new conversation
6
+ * starts.
6
7
  *
7
8
  * This is a backstop, not the steering wheel — agents are expected to stop on
8
- * their own. Direct conversations deliberately stay unlimited: only a non-agent
9
- * message resets the streak, so a default cap in a pure agent-to-agent DM would
10
- * halt the room permanently instead of merely bounding a loop.
11
- *
12
- * The same caveat applies to the one group shape this default does cover: a
13
- * group whose members are all agents (reachable — agent-created groups need no
14
- * human member) has nothing that resets the streak, so it stops auto-replying
15
- * for good once the cap is reached. That is deliberate: an unattended room is
16
- * where a runaway loop is most expensive. Recovery needs a human member — the
17
- * per-conversation `null` opt-out is writable only by a group owner or admin,
18
- * who must be a member. Teaching the resolver "does this room contain a
19
- * human?" would mean loading member types at every resolution site, including
20
- * the stream gate, which does not read them today.
9
+ * their own. Every triggerable agent message counts in both direct and group
10
+ * conversations. An explicit `null` remains the deployer opt-out for trusted,
11
+ * intentionally long-running collaboration.
21
12
  */
22
13
  export const DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS = 4;
14
+ export const DEFAULT_DIRECT_MAX_CONSECUTIVE_AGENT_TURNS = 4;
23
15
  function defaultMaxConsecutiveAgentTurns(conversationType) {
24
- return conversationType === 'group'
25
- ? DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS
26
- : null;
16
+ if (conversationType === 'group') {
17
+ return DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS;
18
+ }
19
+ if (conversationType === 'direct') {
20
+ return DEFAULT_DIRECT_MAX_CONSECUTIVE_AGENT_TURNS;
21
+ }
22
+ return null;
27
23
  }
28
24
  const VALID_PARTICIPATION_STYLES = new Set([
29
25
  'natural',
@@ -146,8 +142,8 @@ export function normalizeAgentBehaviorInstructions(value) {
146
142
  }
147
143
  /**
148
144
  * The scope-independent participation defaults. `maxConsecutiveAgentTurns` is
149
- * `null` here because the turn cap is scope-dependent — a group with no stored
150
- * setting resolves to {@link DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS}. Use
145
+ * `null` here because the turn cap is scope-dependent — a conversation with no
146
+ * stored setting resolves to its direct or group safety default. Use
151
147
  * {@link resolveAgentBehaviorPolicy} with a `conversationType` to learn what a
152
148
  * given conversation actually enforces.
153
149
  */
@@ -157,9 +153,9 @@ export function getDefaultParticipationPolicy() {
157
153
  /**
158
154
  * Coalesce agent defaults and a conversation override into the resolved record
159
155
  * every runtime reads. `conversationType` selects the Canon-wide fallbacks that
160
- * differ per scope — today only the turn cap, which defaults to
161
- * {@link DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS} in groups and to unlimited
162
- * everywhere else. Omit it when resolving for the agent scope (no conversation);
156
+ * differ per scope — today only the turn cap, which defaults to 4 in direct and
157
+ * group conversations and to unlimited elsewhere. Omit it when resolving for
158
+ * the agent scope (no conversation);
163
159
  * an absent or unknown type always resolves to the unlimited default, so a call
164
160
  * site that forgets to thread it can never invent a cap.
165
161
  */
@@ -31,7 +31,6 @@
31
31
  "remove_member",
32
32
  "leave_conversation",
33
33
  "list_contacts",
34
- "list_contact_requests",
35
34
  "list_conversations",
36
35
  "no_reply"
37
36
  ]
@@ -212,71 +211,6 @@
212
211
  }
213
212
  ]
214
213
  },
215
- "sessionConfig": {
216
- "oneOf": [
217
- {
218
- "type": "object",
219
- "description": "Typed setup policy for an agent target. Values are stable runtime-advertised option ids and are validated against the target descriptor before session creation; labels, prompts, workspace paths, and arbitrary fields are not permitted.",
220
- "additionalProperties": false,
221
- "properties": {
222
- "model": {
223
- "type": "string",
224
- "pattern": "^[A-Za-z0-9@_][A-Za-z0-9_.:/@+\\-]{0,255}$",
225
- "maxLength": 256
226
- },
227
- "permissionMode": {
228
- "type": "string",
229
- "pattern": "^[A-Za-z0-9@_][A-Za-z0-9_.:/@+\\-]{0,255}$",
230
- "maxLength": 256
231
- },
232
- "effort": {
233
- "type": "string",
234
- "pattern": "^[A-Za-z0-9@_][A-Za-z0-9_.:/@+\\-]{0,255}$",
235
- "maxLength": 256
236
- },
237
- "workspaceId": {
238
- "type": "string",
239
- "pattern": "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$",
240
- "maxLength": 256
241
- },
242
- "executionMode": {
243
- "enum": [
244
- "worktree",
245
- "locked"
246
- ]
247
- },
248
- "runtimeControlValues": {
249
- "type": "object",
250
- "maxProperties": 24,
251
- "propertyNames": {
252
- "pattern": "^[A-Za-z0-9_-]{1,80}$",
253
- "not": {
254
- "enum": [
255
- "model",
256
- "workspace",
257
- "executionMode",
258
- "permissionMode",
259
- "effort",
260
- "interrupt",
261
- "runtimeControlValues",
262
- "updatedAt",
263
- "updatedBy"
264
- ]
265
- }
266
- },
267
- "additionalProperties": {
268
- "type": "string",
269
- "pattern": "^[A-Za-z0-9@_][A-Za-z0-9_.:/@+\\-]{0,255}$",
270
- "maxLength": 256
271
- }
272
- }
273
- }
274
- },
275
- {
276
- "type": "null"
277
- }
278
- ]
279
- },
280
214
  "idempotencyKey": {
281
215
  "type": "string",
282
216
  "pattern": "^[A-Za-z0-9_.:-]{1,160}$",
@@ -506,7 +440,6 @@
506
440
  "remove_member",
507
441
  "leave_conversation",
508
442
  "list_contacts",
509
- "list_contact_requests",
510
443
  "list_conversations",
511
444
  "no_reply"
512
445
  ]
@@ -38,8 +38,6 @@
38
38
  "nativeKeys": 24,
39
39
  "nativeValueChars": 256,
40
40
  "nativeHandles": 16,
41
- "sessionConfigValues": 24,
42
- "sessionConfigValueChars": 256,
43
41
  "minTimeoutMs": 1000,
44
42
  "maxTimeoutMs": 1800000,
45
43
  "maxApprovalTimeoutMs": 259200000,
@@ -61,10 +59,7 @@
61
59
  "sessionRuleToolPattern": "^[\\w.*:-]{1,128}$",
62
60
  "runtimeCorrelationValue": "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$",
63
61
  "runtimeMethod": "^[A-Za-z0-9_][A-Za-z0-9_./:\\-]{0,255}$",
64
- "runtimeOptionValue": "^[A-Za-z0-9@_][A-Za-z0-9_.:/@+\\-]{0,255}$",
65
- "workspaceOptionId": "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$",
66
62
  "runtimeHandleKey": "^[A-Za-z0-9_.:-]{1,80}$",
67
- "runtimeControlId": "^[A-Za-z0-9_-]{1,80}$",
68
63
  "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}$"
69
64
  },
70
65
  "byteSemantics": {
@@ -13,23 +13,6 @@
13
13
  },
14
14
  "description": "Client idempotency key (<= 160 chars / 256 UTF-8 bytes; no slashes, control chars, '.', '..', __x__). Same id + identical payload replays idempotently; different payload -> 409 MESSAGE_ID_EXISTS."
15
15
  },
16
- "groupInviteRequirements": {
17
- "type": "object",
18
- "description": "Independent gates on a pending group invite. policyApproval is social consent; ownerSessionSetup means the agent owner must choose the runtime session configuration before membership is activated.",
19
- "required": [
20
- "policyApproval",
21
- "ownerSessionSetup"
22
- ],
23
- "additionalProperties": false,
24
- "properties": {
25
- "policyApproval": {
26
- "type": "boolean"
27
- },
28
- "ownerSessionSetup": {
29
- "type": "boolean"
30
- }
31
- }
32
- },
33
16
  "selfContext": {
34
17
  "type": "object",
35
18
  "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.",
@@ -557,67 +540,9 @@
557
540
  }
558
541
  }
559
542
  },
560
- "sessionConfig": {
561
- "type": "object",
562
- "description": "Typed setup policy for an agent target. Values are stable runtime-advertised option ids and are validated against the target descriptor before session creation; labels, prompts, workspace paths, and arbitrary fields are not permitted.",
563
- "additionalProperties": false,
564
- "properties": {
565
- "model": {
566
- "type": "string",
567
- "pattern": "^[A-Za-z0-9@_][A-Za-z0-9_.:/@+\\-]{0,255}$",
568
- "maxLength": 256
569
- },
570
- "permissionMode": {
571
- "type": "string",
572
- "pattern": "^[A-Za-z0-9@_][A-Za-z0-9_.:/@+\\-]{0,255}$",
573
- "maxLength": 256
574
- },
575
- "effort": {
576
- "type": "string",
577
- "pattern": "^[A-Za-z0-9@_][A-Za-z0-9_.:/@+\\-]{0,255}$",
578
- "maxLength": 256
579
- },
580
- "workspaceId": {
581
- "type": "string",
582
- "pattern": "^[A-Za-z0-9@_][A-Za-z0-9_.:@+\\-]{0,255}$",
583
- "maxLength": 256
584
- },
585
- "executionMode": {
586
- "enum": [
587
- "worktree",
588
- "locked"
589
- ]
590
- },
591
- "runtimeControlValues": {
592
- "type": "object",
593
- "maxProperties": 24,
594
- "propertyNames": {
595
- "pattern": "^[A-Za-z0-9_-]{1,80}$",
596
- "not": {
597
- "enum": [
598
- "model",
599
- "workspace",
600
- "executionMode",
601
- "permissionMode",
602
- "effort",
603
- "interrupt",
604
- "runtimeControlValues",
605
- "updatedAt",
606
- "updatedBy"
607
- ]
608
- }
609
- },
610
- "additionalProperties": {
611
- "type": "string",
612
- "pattern": "^[A-Za-z0-9@_][A-Za-z0-9_.:/@+\\-]{0,255}$",
613
- "maxLength": 256
614
- }
615
- }
616
- }
617
- },
618
543
  "send_to_input": {
619
544
  "type": "object",
620
- "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 sourceConversationId + selfContext); 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), owner-only targets surface as status 'unavailable' with reason owner-only.",
545
+ "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 sourceConversationId + selfContext); 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.",
621
546
  "additionalProperties": false,
622
547
  "properties": {
623
548
  "targetConversationId": {
@@ -653,17 +578,6 @@
653
578
  "sessionSelection": {
654
579
  "$ref": "#/$defs/sessionSelection"
655
580
  },
656
- "sessionConfig": {
657
- "oneOf": [
658
- {
659
- "$ref": "#/$defs/sessionConfig"
660
- },
661
- {
662
- "type": "null"
663
- }
664
- ],
665
- "description": "Coding-agent session setup (model/permissionMode/effort/workspaceId/executionMode); only applied to agent targets, stripped for humans."
666
- },
667
581
  "messageOptions": {
668
582
  "$ref": "#/$defs/messageOptions"
669
583
  }
@@ -1511,7 +1425,7 @@
1511
1425
  },
1512
1426
  "create_group_input": {
1513
1427
  "type": "object",
1514
- "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; agents needing explicit group-session setup become pending owner 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. A creator that itself requires setup cannot create groups (403 CREATE_GROUP_CREATOR_SETUP_REQUIRED). Under MLS, membership changes are Add/Remove proposals + Commit — a group operation is a cryptographic state change, not a codec swap.",
1428
+ "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 — a group operation is a cryptographic state change, not a codec swap.",
1515
1429
  "required": [
1516
1430
  "name",
1517
1431
  "memberIds"
@@ -1537,7 +1451,7 @@
1537
1451
  },
1538
1452
  "create_group_result": {
1539
1453
  "type": "object",
1540
- "description": "Staged admission: the group exists with the directly-added members; `pending` lists group_invite requests awaiting policy approval, owner session setup, or both; `skipped` lists hard-denied targets.",
1454
+ "description": "Staged admission: the group exists with the directly-added members; `pending` lists group_invite requests awaiting policy approval; `skipped` lists hard-denied targets.",
1541
1455
  "required": [
1542
1456
  "status",
1543
1457
  "conversationId",
@@ -1565,8 +1479,7 @@
1565
1479
  "type": "object",
1566
1480
  "required": [
1567
1481
  "userId",
1568
- "requestId",
1569
- "requirements"
1482
+ "requestId"
1570
1483
  ],
1571
1484
  "additionalProperties": true,
1572
1485
  "properties": {
@@ -1575,9 +1488,6 @@
1575
1488
  },
1576
1489
  "requestId": {
1577
1490
  "type": "string"
1578
- },
1579
- "requirements": {
1580
- "$ref": "#/$defs/groupInviteRequirements"
1581
1491
  }
1582
1492
  }
1583
1493
  }
@@ -1605,7 +1515,7 @@
1605
1515
  },
1606
1516
  "add_member_input": {
1607
1517
  "type": "object",
1608
- "description": "A target needing policy approval and/or explicit owner session setup yields one pending group_invite. Under MLS an add is an Add proposal + Commit (new epoch), not a server-side membership write — the verb semantics are stable, the mechanism is not.",
1518
+ "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 — the verb semantics are stable, the mechanism is not.",
1609
1519
  "required": [
1610
1520
  "conversationId",
1611
1521
  "userId"
@@ -1623,7 +1533,7 @@
1623
1533
  }
1624
1534
  },
1625
1535
  "add_member_result": {
1626
- "description": "Targets needing policy approval and/or owner session setup yield one pending group_invite routed to the target or agent owner.",
1536
+ "description": "Targets needing policy approval yield an exact pending group invite.",
1627
1537
  "oneOf": [
1628
1538
  {
1629
1539
  "type": "object",
@@ -1641,8 +1551,7 @@
1641
1551
  "type": "object",
1642
1552
  "required": [
1643
1553
  "status",
1644
- "requestId",
1645
- "requirements"
1554
+ "requestId"
1646
1555
  ],
1647
1556
  "additionalProperties": true,
1648
1557
  "properties": {
@@ -1651,9 +1560,6 @@
1651
1560
  },
1652
1561
  "requestId": {
1653
1562
  "type": "string"
1654
- },
1655
- "requirements": {
1656
- "$ref": "#/$defs/groupInviteRequirements"
1657
1563
  }
1658
1564
  }
1659
1565
  }
@@ -1764,81 +1670,6 @@
1764
1670
  }
1765
1671
  }
1766
1672
  },
1767
- "list_contact_requests_input": {
1768
- "type": "object",
1769
- "additionalProperties": false,
1770
- "properties": {}
1771
- },
1772
- "list_contact_requests_result": {
1773
- "type": "object",
1774
- "description": "Pending inbound requests, newest first, capped at 100. Items are SerializedContactRequest (backend-contracts contactRequest.ts). Read-only awareness: approval routes to the owner; agents cannot approve/reject.",
1775
- "required": [
1776
- "requests"
1777
- ],
1778
- "additionalProperties": true,
1779
- "properties": {
1780
- "requests": {
1781
- "type": "array",
1782
- "items": {
1783
- "type": "object",
1784
- "required": [
1785
- "id",
1786
- "requesterId",
1787
- "targetId",
1788
- "status",
1789
- "kind"
1790
- ],
1791
- "additionalProperties": true,
1792
- "properties": {
1793
- "id": {
1794
- "type": "string"
1795
- },
1796
- "requesterId": {
1797
- "type": "string"
1798
- },
1799
- "requesterName": {
1800
- "type": "string"
1801
- },
1802
- "targetId": {
1803
- "type": "string"
1804
- },
1805
- "status": {
1806
- "enum": [
1807
- "pending",
1808
- "approved",
1809
- "rejected",
1810
- "expired"
1811
- ]
1812
- },
1813
- "kind": {
1814
- "enum": [
1815
- "dm",
1816
- "group_invite"
1817
- ]
1818
- },
1819
- "message": {
1820
- "type": [
1821
- "string",
1822
- "null"
1823
- ]
1824
- },
1825
- "createdAt": {
1826
- "type": [
1827
- "string",
1828
- "null"
1829
- ]
1830
- },
1831
- "expiresAt": {
1832
- "type": [
1833
- "string",
1834
- "null"
1835
- ]
1836
- }
1837
- }
1838
- }
1839
- }
1840
- }
1841
- },
1842
1673
  "list_conversations_input": {
1843
1674
  "type": "object",
1844
1675
  "additionalProperties": false,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS = exports.PARTICIPATION_HISTORY_FETCH_LIMIT = void 0;
3
+ exports.DEFAULT_DIRECT_MAX_CONSECUTIVE_AGENT_TURNS = exports.DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS = exports.PARTICIPATION_HISTORY_FETCH_LIMIT = void 0;
4
4
  exports.parseAgentBehaviorSettings = parseAgentBehaviorSettings;
5
5
  exports.normalizeStoredAgentBehaviorPolicy = normalizeStoredAgentBehaviorPolicy;
6
6
  exports.normalizeAgentBehaviorInstructions = normalizeAgentBehaviorInstructions;
@@ -12,29 +12,25 @@ exports.evaluateParticipationPolicy = evaluateParticipationPolicy;
12
12
  const turnProtocol_js_1 = require("./turnProtocol.js");
13
13
  exports.PARTICIPATION_HISTORY_FETCH_LIMIT = 50;
14
14
  /**
15
- * Safety backstop for groups with no explicit turn cap: at most this many
16
- * consecutive agent turns before a human has to speak again.
15
+ * Safety backstop for conversations with no explicit turn cap: at most this
16
+ * many consecutive agent turns before a human speaks or a new conversation
17
+ * starts.
17
18
  *
18
19
  * This is a backstop, not the steering wheel — agents are expected to stop on
19
- * their own. Direct conversations deliberately stay unlimited: only a non-agent
20
- * message resets the streak, so a default cap in a pure agent-to-agent DM would
21
- * halt the room permanently instead of merely bounding a loop.
22
- *
23
- * The same caveat applies to the one group shape this default does cover: a
24
- * group whose members are all agents (reachable — agent-created groups need no
25
- * human member) has nothing that resets the streak, so it stops auto-replying
26
- * for good once the cap is reached. That is deliberate: an unattended room is
27
- * where a runaway loop is most expensive. Recovery needs a human member — the
28
- * per-conversation `null` opt-out is writable only by a group owner or admin,
29
- * who must be a member. Teaching the resolver "does this room contain a
30
- * human?" would mean loading member types at every resolution site, including
31
- * the stream gate, which does not read them today.
20
+ * their own. Every triggerable agent message counts in both direct and group
21
+ * conversations. An explicit `null` remains the deployer opt-out for trusted,
22
+ * intentionally long-running collaboration.
32
23
  */
33
24
  exports.DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS = 4;
25
+ exports.DEFAULT_DIRECT_MAX_CONSECUTIVE_AGENT_TURNS = 4;
34
26
  function defaultMaxConsecutiveAgentTurns(conversationType) {
35
- return conversationType === 'group'
36
- ? exports.DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS
37
- : null;
27
+ if (conversationType === 'group') {
28
+ return exports.DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS;
29
+ }
30
+ if (conversationType === 'direct') {
31
+ return exports.DEFAULT_DIRECT_MAX_CONSECUTIVE_AGENT_TURNS;
32
+ }
33
+ return null;
38
34
  }
39
35
  const VALID_PARTICIPATION_STYLES = new Set([
40
36
  'natural',
@@ -157,8 +153,8 @@ function normalizeAgentBehaviorInstructions(value) {
157
153
  }
158
154
  /**
159
155
  * The scope-independent participation defaults. `maxConsecutiveAgentTurns` is
160
- * `null` here because the turn cap is scope-dependent — a group with no stored
161
- * setting resolves to {@link DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS}. Use
156
+ * `null` here because the turn cap is scope-dependent — a conversation with no
157
+ * stored setting resolves to its direct or group safety default. Use
162
158
  * {@link resolveAgentBehaviorPolicy} with a `conversationType` to learn what a
163
159
  * given conversation actually enforces.
164
160
  */
@@ -168,9 +164,9 @@ function getDefaultParticipationPolicy() {
168
164
  /**
169
165
  * Coalesce agent defaults and a conversation override into the resolved record
170
166
  * every runtime reads. `conversationType` selects the Canon-wide fallbacks that
171
- * differ per scope — today only the turn cap, which defaults to
172
- * {@link DEFAULT_GROUP_MAX_CONSECUTIVE_AGENT_TURNS} in groups and to unlimited
173
- * everywhere else. Omit it when resolving for the agent scope (no conversation);
167
+ * differ per scope — today only the turn cap, which defaults to 4 in direct and
168
+ * group conversations and to unlimited elsewhere. Omit it when resolving for
169
+ * the agent scope (no conversation);
174
170
  * an absent or unknown type always resolves to the unlimited default, so a call
175
171
  * site that forgets to thread it can never invent a cap.
176
172
  */
@@ -7,10 +7,6 @@ function readCommunicationRule(value) {
7
7
  if (value === 'open' || value === 'approval-required' || value === 'closed') {
8
8
  return value;
9
9
  }
10
- // Existing owner-only records have the same outsider-facing meaning as
11
- // closed; the agent owner bypass remains a separate server-side rule.
12
- if (value === 'owner-only')
13
- return 'closed';
14
10
  return 'approval-required';
15
11
  }
16
12
  function parseDirectConversationSelection(value) {
@@ -14,7 +14,7 @@ function normalizeStoredString(value) {
14
14
  /**
15
15
  * Firestore Timestamp-like -> ISO 8601, or null. Accepts only objects exposing
16
16
  * `toDate()`. A raw `Date` is deliberately NOT accepted: that matches both call
17
- * sites this replaced. `message.ts` and `contactRequest.ts` keep their own
17
+ * sites this replaced. `message.ts` keeps its own
18
18
  * Date-tolerant normalizers — a different contract, not this one.
19
19
  */
20
20
  function firestoreTimestampToISOString(value) {
package/dist/cjs/index.js CHANGED
@@ -24,13 +24,12 @@ __exportStar(require("./runtimeCardStorage.js"), exports);
24
24
  __exportStar(require("./turnProtocol.js"), exports);
25
25
  __exportStar(require("./agentBehaviorPolicy.js"), exports);
26
26
  __exportStar(require("./agentSearch.js"), exports);
27
- __exportStar(require("./contactRequest.js"), exports);
28
27
  __exportStar(require("./verbContract.js"), exports);
29
28
  __exportStar(require("./verbSchemas.js"), exports);
30
29
  __exportStar(require("./verbWire.js"), exports);
31
- __exportStar(require("./accessPolicy.js"), exports);
32
30
  __exportStar(require("./communication.js"), exports);
33
31
  __exportStar(require("./apiErrors.js"), exports);
34
32
  __exportStar(require("./firestoreValues.js"), exports);
35
33
  __exportStar(require("./moderation.js"), exports);
36
34
  __exportStar(require("./selfContext.js"), exports);
35
+ __exportStar(require("./replyAuthority.js"), exports);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });