@openscout/protocol 0.2.64 → 0.2.83

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 (214) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +103 -52
  3. package/dist/a2a.d.ts +224 -0
  4. package/dist/a2a.d.ts.map +1 -0
  5. package/dist/a2a.js +225 -0
  6. package/dist/activity.d.ts +70 -0
  7. package/dist/activity.d.ts.map +1 -0
  8. package/dist/activity.js +1 -0
  9. package/dist/actors.d.ts +7 -3
  10. package/dist/actors.d.ts.map +1 -0
  11. package/dist/actors.js +15 -1
  12. package/dist/agent-broker-feed.d.ts +84 -0
  13. package/dist/agent-broker-feed.d.ts.map +1 -0
  14. package/dist/agent-broker-feed.js +1 -0
  15. package/dist/agent-identity.d.ts +23 -0
  16. package/dist/agent-identity.d.ts.map +1 -0
  17. package/dist/agent-identity.js +122 -8
  18. package/dist/agent-runs.d.ts +191 -0
  19. package/dist/agent-runs.d.ts.map +1 -0
  20. package/dist/agent-runs.js +365 -0
  21. package/dist/assigned-roles.d.ts +205 -0
  22. package/dist/assigned-roles.d.ts.map +1 -0
  23. package/dist/assigned-roles.js +228 -0
  24. package/dist/broker-routes.d.ts +40 -0
  25. package/dist/broker-routes.d.ts.map +1 -0
  26. package/dist/broker-routes.js +56 -0
  27. package/dist/budget.d.ts +65 -0
  28. package/dist/budget.d.ts.map +1 -0
  29. package/dist/budget.js +19 -0
  30. package/dist/capability-matrix.d.ts +230 -0
  31. package/dist/capability-matrix.d.ts.map +1 -0
  32. package/dist/capability-matrix.js +304 -0
  33. package/dist/channel-identity.d.ts +40 -0
  34. package/dist/channel-identity.d.ts.map +1 -0
  35. package/dist/channel-identity.js +108 -0
  36. package/dist/collaboration.d.ts +45 -16
  37. package/dist/collaboration.d.ts.map +1 -0
  38. package/dist/collaboration.js +39 -23
  39. package/dist/common.d.ts +4 -3
  40. package/dist/common.d.ts.map +1 -0
  41. package/dist/conversations.d.ts +1 -0
  42. package/dist/conversations.d.ts.map +1 -0
  43. package/dist/cursor-transport.d.ts +22 -0
  44. package/dist/cursor-transport.d.ts.map +1 -0
  45. package/dist/cursor-transport.js +10 -0
  46. package/dist/debug-attach.d.ts +29 -0
  47. package/dist/debug-attach.d.ts.map +1 -0
  48. package/dist/debug-attach.js +6 -0
  49. package/dist/deliveries.d.ts +16 -2
  50. package/dist/deliveries.d.ts.map +1 -0
  51. package/dist/durable-actions.d.ts +94 -0
  52. package/dist/durable-actions.d.ts.map +1 -0
  53. package/dist/durable-actions.js +1 -0
  54. package/dist/events.d.ts +27 -1
  55. package/dist/events.d.ts.map +1 -0
  56. package/dist/events.js +14 -1
  57. package/dist/harness-feature-support.d.ts +76 -0
  58. package/dist/harness-feature-support.d.ts.map +1 -0
  59. package/dist/harness-feature-support.js +29 -0
  60. package/dist/host-integrations.d.ts +22 -0
  61. package/dist/host-integrations.d.ts.map +1 -0
  62. package/dist/host-integrations.js +64 -0
  63. package/dist/inbox.d.ts +50 -0
  64. package/dist/inbox.d.ts.map +1 -0
  65. package/dist/inbox.js +9 -0
  66. package/dist/index.d.ts +35 -2
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +34 -2
  69. package/dist/invocations.d.ts +119 -4
  70. package/dist/invocations.d.ts.map +1 -0
  71. package/dist/invocations.js +201 -1
  72. package/dist/mesh.d.ts +98 -0
  73. package/dist/mesh.d.ts.map +1 -0
  74. package/dist/mesh.js +18 -1
  75. package/dist/messages.d.ts +3 -0
  76. package/dist/messages.d.ts.map +1 -0
  77. package/dist/native-interactions.d.ts +30 -0
  78. package/dist/native-interactions.d.ts.map +1 -0
  79. package/dist/native-interactions.js +51 -0
  80. package/dist/observed-status.d.ts +33 -0
  81. package/dist/observed-status.d.ts.map +1 -0
  82. package/dist/observed-status.js +6 -0
  83. package/dist/orchestrator-prompt.d.ts +10 -0
  84. package/dist/orchestrator-prompt.d.ts.map +1 -0
  85. package/dist/orchestrator-prompt.js +36 -0
  86. package/dist/permission-policy.d.ts +5 -0
  87. package/dist/permission-policy.d.ts.map +1 -0
  88. package/dist/permission-policy.js +15 -0
  89. package/dist/provisional-agent-names.d.ts +30 -0
  90. package/dist/provisional-agent-names.d.ts.map +1 -0
  91. package/dist/provisional-agent-names.js +224 -0
  92. package/dist/read-receipts.d.ts +12 -0
  93. package/dist/read-receipts.d.ts.map +1 -0
  94. package/dist/read-receipts.js +1 -0
  95. package/dist/relay-agent-card.d.ts +10 -0
  96. package/dist/relay-agent-card.d.ts.map +1 -0
  97. package/dist/rendezvous.d.ts +97 -0
  98. package/dist/rendezvous.d.ts.map +1 -0
  99. package/dist/rendezvous.js +35 -0
  100. package/dist/reserved-vocabulary.d.ts +21 -0
  101. package/dist/reserved-vocabulary.d.ts.map +1 -0
  102. package/dist/reserved-vocabulary.js +101 -0
  103. package/dist/route-aliases.d.ts +112 -0
  104. package/dist/route-aliases.d.ts.map +1 -0
  105. package/dist/route-aliases.js +1 -0
  106. package/dist/runtime-catalog-contract.d.ts +39 -0
  107. package/dist/runtime-catalog-contract.d.ts.map +1 -0
  108. package/dist/runtime-catalog-contract.js +17 -0
  109. package/dist/runtime-catalog.d.ts +11 -0
  110. package/dist/runtime-catalog.d.ts.map +1 -0
  111. package/dist/runtime-catalog.generated.d.ts +230 -0
  112. package/dist/runtime-catalog.generated.d.ts.map +1 -0
  113. package/dist/runtime-catalog.generated.js +299 -0
  114. package/dist/runtime-catalog.js +192 -0
  115. package/dist/runtime-execution.d.ts +161 -0
  116. package/dist/runtime-execution.d.ts.map +1 -0
  117. package/dist/runtime-execution.js +327 -0
  118. package/dist/runtime-profiles.d.ts +12 -0
  119. package/dist/runtime-profiles.d.ts.map +1 -0
  120. package/dist/runtime-profiles.js +20 -0
  121. package/dist/scout-agent-card.d.ts +10 -0
  122. package/dist/scout-agent-card.d.ts.map +1 -0
  123. package/dist/scout-composer.d.ts +27 -0
  124. package/dist/scout-composer.d.ts.map +1 -0
  125. package/dist/scout-composer.js +192 -0
  126. package/dist/scout-delivery.d.ts +99 -4
  127. package/dist/scout-delivery.d.ts.map +1 -0
  128. package/dist/scout-dispatch.d.ts +40 -3
  129. package/dist/scout-dispatch.d.ts.map +1 -0
  130. package/dist/scout-reply-context.d.ts +1 -0
  131. package/dist/scout-reply-context.d.ts.map +1 -0
  132. package/dist/session-display-state.d.ts +132 -0
  133. package/dist/session-display-state.d.ts.map +1 -0
  134. package/dist/session-display-state.js +94 -0
  135. package/dist/terminal-host-topology.d.ts +119 -0
  136. package/dist/terminal-host-topology.d.ts.map +1 -0
  137. package/dist/terminal-host-topology.js +21 -0
  138. package/dist/terminal-sessions.d.ts +142 -0
  139. package/dist/terminal-sessions.d.ts.map +1 -0
  140. package/dist/terminal-sessions.js +81 -0
  141. package/dist/terminal-surface-id.d.ts +73 -0
  142. package/dist/terminal-surface-id.d.ts.map +1 -0
  143. package/dist/terminal-surface-id.js +162 -0
  144. package/dist/terminal-workspaces.d.ts +199 -0
  145. package/dist/terminal-workspaces.d.ts.map +1 -0
  146. package/dist/terminal-workspaces.js +287 -0
  147. package/dist/thread-events.d.ts +1 -0
  148. package/dist/thread-events.d.ts.map +1 -0
  149. package/dist/time.d.ts +16 -0
  150. package/dist/time.d.ts.map +1 -0
  151. package/dist/time.js +33 -0
  152. package/dist/transports.d.ts +1 -0
  153. package/dist/transports.d.ts.map +1 -0
  154. package/package.json +37 -9
  155. package/src/a2a.ts +471 -0
  156. package/src/activity.ts +113 -0
  157. package/src/actors.ts +42 -8
  158. package/src/agent-broker-feed.ts +102 -0
  159. package/src/agent-identity.ts +143 -8
  160. package/src/agent-runs.ts +675 -0
  161. package/src/assigned-roles.ts +437 -0
  162. package/src/broker-routes.ts +62 -0
  163. package/src/budget.ts +88 -0
  164. package/src/capability-matrix.ts +648 -0
  165. package/src/channel-identity.ts +147 -0
  166. package/src/collaboration.ts +78 -48
  167. package/src/common.ts +24 -5
  168. package/src/cursor-transport.ts +34 -0
  169. package/src/debug-attach.ts +38 -0
  170. package/src/deliveries.ts +16 -1
  171. package/src/durable-actions.ts +125 -0
  172. package/src/events.ts +31 -0
  173. package/src/harness-feature-support.ts +116 -0
  174. package/src/host-integrations.ts +104 -0
  175. package/src/inbox.ts +68 -0
  176. package/src/index.ts +34 -2
  177. package/src/invocations.ts +351 -4
  178. package/src/mesh.ts +130 -0
  179. package/src/messages.ts +2 -0
  180. package/src/native-interactions.ts +100 -0
  181. package/src/observed-status.ts +90 -0
  182. package/src/orchestrator-prompt.ts +47 -0
  183. package/src/permission-policy.ts +21 -0
  184. package/src/provisional-agent-names.ts +272 -0
  185. package/src/read-receipts.ts +12 -0
  186. package/src/relay-agent-card.ts +73 -63
  187. package/src/rendezvous.ts +154 -0
  188. package/src/reserved-vocabulary.ts +135 -0
  189. package/src/route-aliases.ts +130 -0
  190. package/src/runtime-catalog-contract.ts +60 -0
  191. package/src/runtime-catalog.generated.ts +300 -0
  192. package/src/runtime-catalog.md +21 -0
  193. package/src/runtime-catalog.ts +193 -0
  194. package/src/runtime-catalog.v1.json +81 -0
  195. package/src/runtime-execution.ts +545 -0
  196. package/src/runtime-profiles.ts +37 -0
  197. package/src/scout-agent-card.ts +10 -0
  198. package/src/scout-composer.ts +249 -0
  199. package/src/scout-delivery.ts +113 -3
  200. package/src/scout-dispatch.ts +33 -2
  201. package/src/session-display-state.ts +202 -0
  202. package/src/terminal-host-topology.ts +132 -0
  203. package/src/terminal-sessions.ts +187 -0
  204. package/src/terminal-surface-id.ts +189 -0
  205. package/src/terminal-workspaces.ts +455 -0
  206. package/src/time.ts +49 -0
  207. package/dist/agent-address.d.ts +0 -1
  208. package/dist/agent-address.js +0 -1
  209. package/dist/agent-selectors.d.ts +0 -1
  210. package/dist/agent-selectors.js +0 -1
  211. package/src/agent-address.ts +0 -1
  212. package/src/agent-identity.test.ts +0 -435
  213. package/src/agent-selectors.ts +0 -1
  214. package/src/scout-agent-card.test.ts +0 -54
@@ -0,0 +1,147 @@
1
+ import type { MetadataMap, ScoutId } from "./common.js";
2
+
3
+ export type ConversationIdentityRecord = {
4
+ id: ScoutId;
5
+ kind: string;
6
+ metadata?: MetadataMap;
7
+ };
8
+
9
+ export const CHANNEL_ID_PREFIX = "chn-";
10
+ export const CHAT_ID_PREFIX = CHANNEL_ID_PREFIX;
11
+ export const LEGACY_CHAT_ID_PREFIX = "chat_";
12
+ export const LEGACY_CHANNEL_ID_PREFIX = "c.";
13
+ export const CHANNEL_NATURAL_KEY_METADATA = "naturalKey";
14
+
15
+ export function mintChannelId(randomUuid: () => string): ScoutId {
16
+ return `${CHAT_ID_PREFIX}${randomUuid().toLowerCase().replace(/-/g, "")}`;
17
+ }
18
+
19
+ /**
20
+ * Mint the opaque id for a named/system channel from its canonical identity.
21
+ * Every concurrent creator therefore arrives at the same broker record even
22
+ * when each caller began from a stale snapshot.
23
+ */
24
+ export function stableChannelId(naturalKey: string): ScoutId {
25
+ const normalized = naturalKey.trim().toLowerCase();
26
+ const hash = (seed: bigint): string => {
27
+ let value = seed;
28
+ for (let index = 0; index < normalized.length; index += 1) {
29
+ value ^= BigInt(normalized.charCodeAt(index));
30
+ value = BigInt.asUintN(64, value * 0x100000001b3n);
31
+ }
32
+ return value.toString(16).padStart(16, "0");
33
+ };
34
+ return `${CHAT_ID_PREFIX}${hash(0xcbf29ce484222325n)}${hash(0x84222325cbf29ce4n)}`;
35
+ }
36
+
37
+ export function isOpaqueChannelId(value: string | null | undefined): value is ScoutId {
38
+ if (typeof value !== "string") return false;
39
+ return (
40
+ value.startsWith(CHAT_ID_PREFIX) && value.length > CHAT_ID_PREFIX.length
41
+ ) || (
42
+ value.startsWith(LEGACY_CHAT_ID_PREFIX)
43
+ && value.length > LEGACY_CHAT_ID_PREFIX.length
44
+ ) || (
45
+ value.startsWith(LEGACY_CHANNEL_ID_PREFIX)
46
+ && value.length > LEGACY_CHANNEL_ID_PREFIX.length
47
+ );
48
+ }
49
+
50
+ export function directChannelNaturalKey(participantIds: ScoutId[]): string {
51
+ return `direct:${stableIdentityParts(participantIds).join(",")}`;
52
+ }
53
+
54
+ export function namedChannelNaturalKey(channel: string): string {
55
+ return `channel:${encodeIdentityPart(channel.trim().toLowerCase() || "shared")}`;
56
+ }
57
+
58
+ export function systemChannelNaturalKey(name: string): string {
59
+ return `system:${encodeIdentityPart(name.trim().toLowerCase() || "system")}`;
60
+ }
61
+
62
+ export function channelNaturalKeyFromMetadata(
63
+ metadata: MetadataMap | undefined,
64
+ ): string | null {
65
+ const value = metadata?.[CHANNEL_NATURAL_KEY_METADATA];
66
+ return typeof value === "string" && value.trim() ? value.trim() : null;
67
+ }
68
+
69
+ /**
70
+ * Read the semantic identity of a conversation, including the structural ids
71
+ * written by Scout versions that predate `metadata.naturalKey`.
72
+ *
73
+ * Structural ids remain read-only compatibility aliases. New writes must use
74
+ * an opaque id and persist the natural key in metadata.
75
+ */
76
+ export function conversationNaturalKey(
77
+ conversation: ConversationIdentityRecord,
78
+ ): string | null {
79
+ const explicit = channelNaturalKeyFromMetadata(conversation.metadata);
80
+ if (explicit) {
81
+ return explicit;
82
+ }
83
+
84
+ if (conversation.kind !== "channel" && conversation.kind !== "system") {
85
+ return null;
86
+ }
87
+
88
+ const metadataChannel = conversation.metadata?.channel;
89
+ const legacyChannel = conversation.id.startsWith("channel.")
90
+ ? conversation.id.slice("channel.".length)
91
+ : null;
92
+ const channel = typeof metadataChannel === "string" && metadataChannel.trim()
93
+ ? metadataChannel.trim()
94
+ : legacyChannel?.trim() || null;
95
+ if (!channel) {
96
+ return null;
97
+ }
98
+
99
+ return conversation.kind === "system"
100
+ ? systemChannelNaturalKey(channel)
101
+ : namedChannelNaturalKey(channel);
102
+ }
103
+
104
+ /**
105
+ * Return every record for one semantic conversation in deterministic priority
106
+ * order: the stable named-channel id, another opaque id, then structural
107
+ * compatibility aliases. This prevents snapshot insertion order from choosing
108
+ * which chat receives a write.
109
+ */
110
+ export function conversationsWithNaturalKey<T extends ConversationIdentityRecord>(
111
+ conversations: Iterable<T>,
112
+ naturalKey: string,
113
+ ): T[] {
114
+ const normalizedNaturalKey = naturalKey.trim();
115
+ const stableId = normalizedNaturalKey.startsWith("channel:")
116
+ || normalizedNaturalKey.startsWith("system:")
117
+ ? stableChannelId(normalizedNaturalKey)
118
+ : null;
119
+ const priority = (conversation: T): number => {
120
+ if (stableId && conversation.id === stableId) return 0;
121
+ if (isOpaqueChannelId(conversation.id)) return 1;
122
+ return 2;
123
+ };
124
+
125
+ return [...conversations]
126
+ .filter((conversation) => conversationNaturalKey(conversation) === normalizedNaturalKey)
127
+ .sort((left, right) => priority(left) - priority(right) || left.id.localeCompare(right.id));
128
+ }
129
+
130
+ export function preferredConversationWithNaturalKey<T extends ConversationIdentityRecord>(
131
+ conversations: Iterable<T>,
132
+ naturalKey: string,
133
+ ): T | undefined {
134
+ return conversationsWithNaturalKey(conversations, naturalKey)[0];
135
+ }
136
+
137
+ function stableIdentityParts(values: ScoutId[]): string[] {
138
+ return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)))
139
+ .sort()
140
+ .map(encodeIdentityPart);
141
+ }
142
+
143
+ function encodeIdentityPart(value: string): string {
144
+ return encodeURIComponent(value).replace(/[!'()*]/g, (char) =>
145
+ `%${char.charCodeAt(0).toString(16).toUpperCase()}`,
146
+ );
147
+ }
@@ -1,6 +1,6 @@
1
1
  import type { MetadataMap, ScoutId } from "./common.js";
2
2
 
3
- export type CollaborationKind = "question" | "work_item";
3
+ export type CollaborationKind = "work_item" | "question";
4
4
 
5
5
  export type CollaborationPriority = "low" | "normal" | "high" | "urgent";
6
6
 
@@ -10,12 +10,6 @@ export type CollaborationAcceptanceState =
10
10
  | "accepted"
11
11
  | "reopened";
12
12
 
13
- export type QuestionState =
14
- | "open"
15
- | "answered"
16
- | "closed"
17
- | "declined";
18
-
19
13
  export type WorkItemState =
20
14
  | "open"
21
15
  | "working"
@@ -24,6 +18,12 @@ export type WorkItemState =
24
18
  | "done"
25
19
  | "cancelled";
26
20
 
21
+ export type QuestionState =
22
+ | "open"
23
+ | "answered"
24
+ | "closed"
25
+ | "declined";
26
+
27
27
  export type CollaborationRelationKind =
28
28
  | "blocks"
29
29
  | "spawns"
@@ -37,7 +37,7 @@ export interface CollaborationRelation {
37
37
  }
38
38
 
39
39
  export interface CollaborationWaitingOn {
40
- kind: "actor" | "question" | "work_item" | "approval" | "artifact" | "condition";
40
+ kind: "actor" | "work_item" | "approval" | "artifact" | "condition";
41
41
  label: string;
42
42
  targetId?: ScoutId;
43
43
  metadata?: MetadataMap;
@@ -69,17 +69,6 @@ export interface CollaborationRecordBase {
69
69
  metadata?: MetadataMap;
70
70
  }
71
71
 
72
- export interface QuestionRecord extends CollaborationRecordBase {
73
- kind: "question";
74
- state: QuestionState;
75
- acceptanceState: CollaborationAcceptanceState;
76
- askedById?: ScoutId;
77
- askedOfId?: ScoutId;
78
- answerMessageId?: ScoutId;
79
- spawnedWorkItemId?: ScoutId;
80
- closedAt?: number;
81
- }
82
-
83
72
  export interface WorkItemRecord extends CollaborationRecordBase {
84
73
  kind: "work_item";
85
74
  state: WorkItemState;
@@ -92,12 +81,33 @@ export interface WorkItemRecord extends CollaborationRecordBase {
92
81
  completedAt?: number;
93
82
  }
94
83
 
95
- export type CollaborationRecord = QuestionRecord | WorkItemRecord;
84
+ /**
85
+ * A lightweight information-seeking interaction. A peer of {@link WorkItemRecord},
86
+ * not a point on the same severity ladder: a question can resolve directly, attach
87
+ * to a work item, or spawn one.
88
+ *
89
+ * Acceptance is modeled separately from workflow state (via `acceptanceState`) so a
90
+ * reply and satisfaction do not collapse into one transition — a question can be
91
+ * `answered` without yet being `closed`.
92
+ */
93
+ export interface QuestionRecord extends CollaborationRecordBase {
94
+ kind: "question";
95
+ state: QuestionState;
96
+ acceptanceState: CollaborationAcceptanceState;
97
+ /** The actor being asked to answer. */
98
+ askedById?: ScoutId;
99
+ answeredById?: ScoutId;
100
+ /** The delivered answer text, present once `state` is `answered`. */
101
+ answer?: string;
102
+ answeredAt?: number;
103
+ closedAt?: number;
104
+ }
105
+
106
+ export type CollaborationRecord = WorkItemRecord | QuestionRecord;
96
107
 
97
108
  export type CollaborationEventKind =
98
109
  | "created"
99
110
  | "claimed"
100
- | "answered"
101
111
  | "accepted"
102
112
  | "reopened"
103
113
  | "waiting"
@@ -105,7 +115,7 @@ export type CollaborationEventKind =
105
115
  | "handoff"
106
116
  | "review_requested"
107
117
  | "done"
108
- | "declined"
118
+ | "dismissed"
109
119
  | "cancelled";
110
120
 
111
121
  export interface CollaborationEvent {
@@ -119,20 +129,47 @@ export interface CollaborationEvent {
119
129
  metadata?: MetadataMap;
120
130
  }
121
131
 
132
+ export function isWorkItemTerminalState(state: WorkItemState): boolean {
133
+ return state === "done" || state === "cancelled";
134
+ }
135
+
136
+ /**
137
+ * A question is terminal once it is `closed` or `declined`. `answered` is NOT terminal
138
+ * — acceptance is tracked separately, so an answered question may still await closure.
139
+ */
122
140
  export function isQuestionTerminalState(state: QuestionState): boolean {
123
141
  return state === "closed" || state === "declined";
124
142
  }
125
143
 
126
- export function isWorkItemTerminalState(state: WorkItemState): boolean {
127
- return state === "done" || state === "cancelled";
144
+ /** Terminal-state check across both collaboration kinds. */
145
+ export function isCollaborationTerminalState(record: CollaborationRecord): boolean {
146
+ return record.kind === "work_item"
147
+ ? isWorkItemTerminalState(record.state)
148
+ : isQuestionTerminalState(record.state);
128
149
  }
129
150
 
130
- export function collaborationRequiresNextMoveOwner(record: CollaborationRecord): boolean {
131
- if (record.kind === "question") {
132
- return !isQuestionTerminalState(record.state);
133
- }
151
+ /** Narrow a {@link CollaborationRecord} to a {@link WorkItemRecord}. */
152
+ export function isWorkItem(record: CollaborationRecord): record is WorkItemRecord {
153
+ return record.kind === "work_item";
154
+ }
134
155
 
135
- return !isWorkItemTerminalState(record.state);
156
+ /** Narrow a {@link CollaborationRecord} to a {@link QuestionRecord}. */
157
+ export function isQuestion(record: CollaborationRecord): record is QuestionRecord {
158
+ return record.kind === "question";
159
+ }
160
+
161
+ /**
162
+ * The actor a collaboration record is directed at / awaiting acceptance from: the
163
+ * `requestedById` for a work item, the `askedById` for a question. Consumers that
164
+ * route or attribute a record by its requester should use this rather than reaching
165
+ * for a kind-specific field, so questions are handled instead of silently dropped.
166
+ */
167
+ export function collaborationRequesterId(record: CollaborationRecord): ScoutId | undefined {
168
+ return isWorkItem(record) ? record.requestedById : record.askedById;
169
+ }
170
+
171
+ export function collaborationRequiresNextMoveOwner(record: CollaborationRecord): boolean {
172
+ return !isCollaborationTerminalState(record);
136
173
  }
137
174
 
138
175
  export function collaborationRequiresOwner(record: CollaborationRecord): boolean {
@@ -148,11 +185,7 @@ export function collaborationRequiresAcceptance(record: CollaborationRecord): bo
148
185
  return false;
149
186
  }
150
187
 
151
- if (record.kind === "question") {
152
- return Boolean(record.askedById && record.askedOfId);
153
- }
154
-
155
- return Boolean(record.requestedById);
188
+ return Boolean(collaborationRequesterId(record));
156
189
  }
157
190
 
158
191
  export function validateCollaborationRecord(record: CollaborationRecord): string[] {
@@ -190,14 +223,18 @@ export function validateCollaborationRecord(record: CollaborationRecord): string
190
223
  errors.push("waiting work items require waitingOn");
191
224
  }
192
225
 
193
- if (record.kind === "question") {
194
- if (record.spawnedWorkItemId && record.spawnedWorkItemId === record.id) {
195
- errors.push("question spawnedWorkItemId cannot reference the question itself");
196
- }
197
- } else if (record.waitingOn?.targetId && record.waitingOn.targetId === record.id) {
226
+ if (
227
+ record.kind === "work_item"
228
+ && record.waitingOn?.targetId
229
+ && record.waitingOn.targetId === record.id
230
+ ) {
198
231
  errors.push("waitingOn.targetId cannot reference the work item itself");
199
232
  }
200
233
 
234
+ if (record.kind === "question" && record.state === "answered" && !record.answer?.trim()) {
235
+ errors.push("answered questions require an answer");
236
+ }
237
+
201
238
  if (record.acceptanceState !== "none" && !collaborationRequiresAcceptance(record)) {
202
239
  errors.push("acceptanceState requires the corresponding requester and reviewer identities");
203
240
  }
@@ -239,20 +276,13 @@ export function validateCollaborationEvent(
239
276
  }
240
277
  }
241
278
 
242
- if (event.kind === "answered" && event.recordKind !== "question") {
243
- errors.push("answered events only apply to questions");
244
- }
245
-
246
- if (event.kind === "declined" && event.recordKind !== "question") {
247
- errors.push("declined events only apply to questions");
248
- }
249
-
250
279
  if (
251
280
  (event.kind === "waiting"
252
281
  || event.kind === "progressed"
253
282
  || event.kind === "review_requested"
254
283
  || event.kind === "done"
255
- || event.kind === "cancelled")
284
+ || event.kind === "cancelled"
285
+ || event.kind === "claimed")
256
286
  && event.recordKind !== "work_item"
257
287
  ) {
258
288
  errors.push(`${event.kind} events only apply to work items`);
package/src/common.ts CHANGED
@@ -4,26 +4,35 @@ export type ActorKind =
4
4
  | "person"
5
5
  | "helper"
6
6
  | "agent"
7
+ | "session"
7
8
  | "system"
8
9
  | "bridge"
9
10
  | "device";
10
11
 
11
12
  export type AgentState =
13
+ | "registered"
14
+ | "attaching"
15
+ | "waking"
12
16
  | "offline"
13
17
  | "idle"
14
18
  | "active"
15
- | "waiting";
19
+ | "waiting"
20
+ | "working"
21
+ | "unreachable"
22
+ | "failed"
23
+ | "superseded"
24
+ | "stopped";
16
25
 
17
26
  export type VisibilityScope = "private" | "workspace" | "public" | "system";
18
27
 
19
28
  export type DeliveryStatus =
20
- // Outbox lifecycle (Issue 3 of broker-three-contracts)
29
+ // Outbox lifecycle
21
30
  | "accepted" // local journal write done, awaiting forward
22
31
  | "peer_acked" // peer broker journaled the envelope
23
32
  | "running" // target agent claimed the flight
24
33
  | "completed" // terminal success
25
- | "deferred" // within retry window — see metadata.nextAttemptAt
26
- | "failed" // terminal failure — see metadata.failureReason
34
+ | "deferred" // within retry window — see DeliveryMetadata.nextAttemptAt
35
+ | "failed" // terminal failure — see DeliveryMetadata.failureReason
27
36
  | "cancelled"
28
37
  // Legacy values kept for backwards-compatible journal replay
29
38
  | "pending"
@@ -63,11 +72,21 @@ export type DeliveryTransport =
63
72
  | "email"
64
73
  | "tts"
65
74
  | "native_voice"
75
+ | "claude_channel"
66
76
  | "claude_stream_json"
67
77
  | "codex_app_server"
68
78
  | "codex_exec"
69
79
  | "claude_resume"
70
- | "tmux";
80
+ | "pi_rpc"
81
+ | "grok_acp"
82
+ | "kimi_acp"
83
+ | "cursor_acp"
84
+ | "opencode_acp"
85
+ | "tmux"
86
+ | "cursor_exec"
87
+ | "cursor_cli_text"
88
+ | "cursor_cli_stream_json"
89
+ | "cursor_sdk_local";
71
90
 
72
91
  export type DeliveryReason =
73
92
  | "conversation_visibility"
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Local Cursor harness transport modes under evaluation.
3
+ * See docs/eng/sco-047-cursor-transport-spike.md.
4
+ */
5
+ export const CURSOR_LOCAL_TRANSPORT_MODES = [
6
+ "cursor_cli_text",
7
+ "cursor_cli_stream_json",
8
+ "cursor_sdk_local",
9
+ "cursor_sdk_local_no_key",
10
+ ] as const;
11
+
12
+ export type CursorLocalTransportMode = typeof CURSOR_LOCAL_TRANSPORT_MODES[number];
13
+
14
+ export type CursorTransportAuthSource =
15
+ | "none"
16
+ | "env"
17
+ | "cursor_api_key_file"
18
+ | "cli_flag"
19
+ | "sdk_option";
20
+
21
+ export type CursorTransportSpikeResult = {
22
+ mode: CursorLocalTransportMode;
23
+ ok: boolean;
24
+ durationMs: number;
25
+ authSource: CursorTransportAuthSource;
26
+ sessionId?: string;
27
+ agentId?: string;
28
+ runId?: string;
29
+ outputText?: string;
30
+ eventCount?: number;
31
+ errorCode?: string;
32
+ errorMessage?: string;
33
+ notes?: string[];
34
+ };
@@ -0,0 +1,38 @@
1
+ import type { ScoutId } from "./common.js";
2
+
3
+ export interface AgentEndpointDebugTransport {
4
+ kind: "tmux";
5
+ state: "ready" | "starting" | "missing" | "stale" | "error";
6
+ sessionName: string;
7
+ paneTarget?: string;
8
+ cwd?: string;
9
+ attachable: boolean;
10
+ multiAttach: boolean;
11
+ lastProbeAt: number;
12
+ lastAttachAt?: number;
13
+ lastDetachAt?: number;
14
+ activeClients?: number;
15
+ detail?: string;
16
+ }
17
+
18
+ export interface DebugTmuxAttachPlan {
19
+ endpointId: ScoutId;
20
+ transport: "tmux";
21
+ sessionName: string;
22
+ paneTarget?: string;
23
+ command?: ["tmux", "attach-session", "-t", string];
24
+ terminalRelay?: {
25
+ backend: "tmux";
26
+ tmuxSession: string;
27
+ };
28
+ }
29
+
30
+ export function isDebugTransportAttachable(
31
+ transport: Pick<AgentEndpointDebugTransport, "state" | "attachable"> | null | undefined,
32
+ ): boolean {
33
+ return Boolean(transport?.attachable && transport.state === "ready");
34
+ }
35
+
36
+ export function buildTmuxAttachCommand(sessionName: string): ["tmux", "attach-session", "-t", string] {
37
+ return ["tmux", "attach-session", "-t", sessionName];
38
+ }
package/src/deliveries.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ DeliveryFailureReason,
2
3
  DeliveryPolicy,
3
4
  DeliveryReason,
4
5
  DeliveryStatus,
@@ -8,6 +9,20 @@ import type {
8
9
  ScoutId,
9
10
  } from "./common.js";
10
11
 
12
+ /**
13
+ * Delivery metadata carries a small set of well-known keys alongside arbitrary
14
+ * extension data. The typed fields document the keys the runtime reads on the
15
+ * retry path; the {@link MetadataMap} index signature keeps the bag open for
16
+ * transport-specific extras. The wire/persistence shape is unchanged — these
17
+ * were always plain metadata keys, now they are typed.
18
+ */
19
+ export interface DeliveryMetadata extends MetadataMap {
20
+ /** Epoch ms of the next retry attempt. Set while `status` is `"deferred"`. */
21
+ nextAttemptAt?: number;
22
+ /** Classified failure reason. Set when `status` is `"failed"`. */
23
+ failureReason?: DeliveryFailureReason;
24
+ }
25
+
11
26
  export interface DeliveryTarget {
12
27
  id: ScoutId;
13
28
  kind: DeliveryTargetKind;
@@ -31,7 +46,7 @@ export interface DeliveryIntent {
31
46
  bindingId?: ScoutId;
32
47
  leaseOwner?: string;
33
48
  leaseExpiresAt?: number;
34
- metadata?: MetadataMap;
49
+ metadata?: DeliveryMetadata;
35
50
  }
36
51
 
37
52
  export interface DeliveryAttempt {
@@ -0,0 +1,125 @@
1
+ import type { MetadataMap, ScoutId } from "./common.js";
2
+
3
+ // First implementation slice. Add work_item/question/approval only after ask,
4
+ // message_delivery, and checkback semantics prove out against real paths.
5
+ export type DurableActionKind = "ask" | "message_delivery" | "checkback";
6
+
7
+ export type DurableCheckbackMode =
8
+ | "notify"
9
+ | "scoutbot"
10
+ | "agent"
11
+ | "routine";
12
+
13
+ export interface DurableCheckbackMetadata extends MetadataMap {
14
+ dueAt: number;
15
+ mode?: DurableCheckbackMode;
16
+ title?: string;
17
+ body?: string;
18
+ prompt?: string;
19
+ ownerId?: ScoutId;
20
+ conversationId?: ScoutId;
21
+ recordId?: ScoutId;
22
+ route?: unknown;
23
+ recurrence?: unknown;
24
+ }
25
+
26
+ export type DurableActionState =
27
+ | "pending"
28
+ | "leased"
29
+ | "running"
30
+ | "waiting"
31
+ | "completed"
32
+ | "failed"
33
+ | "cancelled";
34
+
35
+ export type DurableAttemptState =
36
+ | "pending"
37
+ | "running"
38
+ | "completed"
39
+ | "failed"
40
+ | "cancelled";
41
+
42
+ export interface DurableAction {
43
+ id: ScoutId;
44
+ kind: DurableActionKind;
45
+ subjectId: ScoutId;
46
+ authorityCellId: ScoutId;
47
+ state: DurableActionState;
48
+ idempotencyKey?: string;
49
+ leaseOwner?: string;
50
+ leaseGeneration: number;
51
+ leaseExpiresAt?: number;
52
+ createdAt: number;
53
+ updatedAt: number;
54
+ metadata?: MetadataMap;
55
+ }
56
+
57
+ export interface DurableAttempt {
58
+ id: ScoutId;
59
+ actionId: ScoutId;
60
+ attempt: number;
61
+ state: DurableAttemptState;
62
+ leaseGeneration: number;
63
+ error?: string;
64
+ startedAt?: number;
65
+ completedAt?: number;
66
+ metadata?: MetadataMap;
67
+ }
68
+
69
+ export interface DurableCheckpoint {
70
+ actionId: ScoutId;
71
+ name: string;
72
+ payload?: unknown;
73
+ ownerAttemptId?: ScoutId;
74
+ leaseOwner?: string;
75
+ leaseGeneration?: number;
76
+ createdAt: number;
77
+ }
78
+
79
+ export interface DurableSignal {
80
+ actionId: ScoutId;
81
+ name: string;
82
+ payload?: unknown;
83
+ leaseOwner?: string;
84
+ leaseGeneration?: number;
85
+ emittedAt: number;
86
+ }
87
+
88
+ export interface DurableActionCreateInput {
89
+ id: ScoutId;
90
+ kind: DurableActionKind;
91
+ subjectId: ScoutId;
92
+ authorityCellId: ScoutId;
93
+ idempotencyKey?: string;
94
+ createdAt: number;
95
+ metadata?: MetadataMap;
96
+ }
97
+
98
+ export interface DurableActionClaimInput {
99
+ actionId: ScoutId;
100
+ owner: string;
101
+ leaseMs: number;
102
+ claimedAt: number;
103
+ }
104
+
105
+ export interface DurableActionHeartbeatInput {
106
+ actionId: ScoutId;
107
+ owner: string;
108
+ generation: number;
109
+ leaseMs: number;
110
+ heartbeatAt: number;
111
+ }
112
+
113
+ export interface DurableActionLease {
114
+ owner: string;
115
+ generation: number;
116
+ expiresAt: number;
117
+ }
118
+
119
+ export interface DurableActionCommandReceipt {
120
+ accepted: boolean;
121
+ duplicate?: boolean;
122
+ action: DurableAction;
123
+ lease?: DurableActionLease;
124
+ events: string[];
125
+ }