@openscout/protocol 0.2.63 → 0.2.82

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 (216) 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 +36 -2
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +35 -2
  69. package/dist/invocations.d.ts +121 -0
  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 +101 -4
  127. package/dist/scout-delivery.d.ts.map +1 -0
  128. package/dist/scout-dispatch.d.ts +44 -3
  129. package/dist/scout-dispatch.d.ts.map +1 -0
  130. package/dist/scout-reply-context.d.ts +14 -0
  131. package/dist/scout-reply-context.d.ts.map +1 -0
  132. package/dist/scout-reply-context.js +1 -0
  133. package/dist/session-display-state.d.ts +132 -0
  134. package/dist/session-display-state.d.ts.map +1 -0
  135. package/dist/session-display-state.js +94 -0
  136. package/dist/terminal-host-topology.d.ts +119 -0
  137. package/dist/terminal-host-topology.d.ts.map +1 -0
  138. package/dist/terminal-host-topology.js +21 -0
  139. package/dist/terminal-sessions.d.ts +142 -0
  140. package/dist/terminal-sessions.d.ts.map +1 -0
  141. package/dist/terminal-sessions.js +81 -0
  142. package/dist/terminal-surface-id.d.ts +73 -0
  143. package/dist/terminal-surface-id.d.ts.map +1 -0
  144. package/dist/terminal-surface-id.js +162 -0
  145. package/dist/terminal-workspaces.d.ts +199 -0
  146. package/dist/terminal-workspaces.d.ts.map +1 -0
  147. package/dist/terminal-workspaces.js +287 -0
  148. package/dist/thread-events.d.ts +1 -0
  149. package/dist/thread-events.d.ts.map +1 -0
  150. package/dist/time.d.ts +16 -0
  151. package/dist/time.d.ts.map +1 -0
  152. package/dist/time.js +33 -0
  153. package/dist/transports.d.ts +1 -0
  154. package/dist/transports.d.ts.map +1 -0
  155. package/package.json +37 -9
  156. package/src/a2a.ts +471 -0
  157. package/src/activity.ts +113 -0
  158. package/src/actors.ts +42 -8
  159. package/src/agent-broker-feed.ts +102 -0
  160. package/src/agent-identity.ts +143 -8
  161. package/src/agent-runs.ts +675 -0
  162. package/src/assigned-roles.ts +437 -0
  163. package/src/broker-routes.ts +62 -0
  164. package/src/budget.ts +88 -0
  165. package/src/capability-matrix.ts +648 -0
  166. package/src/channel-identity.ts +147 -0
  167. package/src/collaboration.ts +78 -48
  168. package/src/common.ts +24 -5
  169. package/src/cursor-transport.ts +34 -0
  170. package/src/debug-attach.ts +38 -0
  171. package/src/deliveries.ts +16 -1
  172. package/src/durable-actions.ts +125 -0
  173. package/src/events.ts +31 -0
  174. package/src/harness-feature-support.ts +116 -0
  175. package/src/host-integrations.ts +104 -0
  176. package/src/inbox.ts +68 -0
  177. package/src/index.ts +35 -2
  178. package/src/invocations.ts +353 -0
  179. package/src/mesh.ts +130 -0
  180. package/src/messages.ts +2 -0
  181. package/src/native-interactions.ts +100 -0
  182. package/src/observed-status.ts +90 -0
  183. package/src/orchestrator-prompt.ts +47 -0
  184. package/src/permission-policy.ts +21 -0
  185. package/src/provisional-agent-names.ts +272 -0
  186. package/src/read-receipts.ts +12 -0
  187. package/src/relay-agent-card.ts +73 -63
  188. package/src/rendezvous.ts +154 -0
  189. package/src/reserved-vocabulary.ts +135 -0
  190. package/src/route-aliases.ts +130 -0
  191. package/src/runtime-catalog-contract.ts +60 -0
  192. package/src/runtime-catalog.generated.ts +300 -0
  193. package/src/runtime-catalog.md +21 -0
  194. package/src/runtime-catalog.ts +193 -0
  195. package/src/runtime-catalog.v1.json +81 -0
  196. package/src/runtime-execution.ts +545 -0
  197. package/src/runtime-profiles.ts +37 -0
  198. package/src/scout-agent-card.ts +10 -0
  199. package/src/scout-composer.ts +249 -0
  200. package/src/scout-delivery.ts +117 -4
  201. package/src/scout-dispatch.ts +34 -2
  202. package/src/scout-reply-context.ts +15 -0
  203. package/src/session-display-state.ts +202 -0
  204. package/src/terminal-host-topology.ts +132 -0
  205. package/src/terminal-sessions.ts +187 -0
  206. package/src/terminal-surface-id.ts +189 -0
  207. package/src/terminal-workspaces.ts +455 -0
  208. package/src/time.ts +49 -0
  209. package/dist/agent-address.d.ts +0 -1
  210. package/dist/agent-address.js +0 -1
  211. package/dist/agent-selectors.d.ts +0 -1
  212. package/dist/agent-selectors.js +0 -1
  213. package/src/agent-address.ts +0 -1
  214. package/src/agent-identity.test.ts +0 -435
  215. package/src/agent-selectors.ts +0 -1
  216. package/src/scout-agent-card.test.ts +0 -54
@@ -1,5 +1,7 @@
1
1
  import type { AgentHarness } from "./actors.js";
2
2
  import type { MetadataMap, ScoutId } from "./common.js";
3
+ import type { ScoutPermissionProfile } from "./permission-policy.js";
4
+ import type { ScoutExecutionResolution } from "./runtime-execution.js";
3
5
 
4
6
  export type InvocationAction =
5
7
  | "consult"
@@ -17,8 +19,59 @@ export type FlightState =
17
19
  | "failed"
18
20
  | "cancelled";
19
21
 
22
+ export type InvocationSessionPolicy =
23
+ | "new"
24
+ | "reuse"
25
+ | "existing"
26
+ | "fork"
27
+ /** @deprecated use reuse */
28
+ | "any";
29
+
30
+ /** Where a newly created harness session should be discoverable. */
31
+ export type SessionPlacement = "background" | "foreground";
32
+
33
+ export type InvocationForkSourceKind =
34
+ | "native_thread_clone"
35
+ | "scout_state_snapshot";
36
+
37
+ export interface InvocationForkContextOptions {
38
+ maxMessages?: number;
39
+ maxBytes?: number;
40
+ includeBrokerRecords?: boolean;
41
+ includeObservedHarnessMaterial?: boolean;
42
+ }
43
+
44
+ export interface InvocationSessionLineage {
45
+ parentSessionId?: ScoutId;
46
+ parentHarnessThreadId?: string;
47
+ forkSourceKind?: InvocationForkSourceKind;
48
+ forkSourceId?: ScoutId | string;
49
+ forkedAt?: number;
50
+ metadata?: MetadataMap;
51
+ }
52
+
20
53
  export interface InvocationExecutionPreference {
21
54
  harness?: AgentHarness;
55
+ model?: string;
56
+ reasoningEffort?: string;
57
+ permissionProfile?: ScoutPermissionProfile;
58
+ /**
59
+ * Background sessions use Scout-managed harness state. Foreground sessions
60
+ * use the operator's native harness state so the task is openable there.
61
+ */
62
+ placement?: SessionPlacement;
63
+ /**
64
+ * Controls whether work should enter fresh model context, opportunistically
65
+ * reuse a warm compatible session, continue one exact session, or fork a new
66
+ * execution session from prior state. The legacy "any" value is a
67
+ * compatibility alias for "reuse"; exact continuation requires targetSessionId.
68
+ */
69
+ session?: InvocationSessionPolicy;
70
+ targetSessionId?: ScoutId;
71
+ forkFromStateId?: ScoutId;
72
+ forkFromSessionId?: ScoutId;
73
+ forkContext?: InvocationForkContextOptions;
74
+ lineage?: InvocationSessionLineage;
22
75
  }
23
76
 
24
77
  export interface InvocationRequest {
@@ -34,9 +87,11 @@ export interface InvocationRequest {
34
87
  messageId?: ScoutId;
35
88
  context?: MetadataMap;
36
89
  execution?: InvocationExecutionPreference;
90
+ executionResolution?: ScoutExecutionResolution;
37
91
  ensureAwake: boolean;
38
92
  stream: boolean;
39
93
  timeoutMs?: number;
94
+ labels?: string[];
40
95
  createdAt: number;
41
96
  metadata?: MetadataMap;
42
97
  }
@@ -52,5 +107,303 @@ export interface FlightRecord {
52
107
  error?: string;
53
108
  startedAt?: number;
54
109
  completedAt?: number;
110
+ labels?: string[];
55
111
  metadata?: MetadataMap;
56
112
  }
113
+
114
+ /**
115
+ * One concrete harness session used while executing a flight. Flights are the
116
+ * stable coordination handle; sessions are ephemeral execution resources and
117
+ * may change when a dispatch is retried, resumed, or replaced.
118
+ */
119
+ export interface FlightSessionTraceEntry {
120
+ sessionId: ScoutId;
121
+ endpointId?: ScoutId;
122
+ nodeId?: ScoutId;
123
+ harness?: AgentHarness;
124
+ transport?: string;
125
+ strategy?: string;
126
+ placement?: SessionPlacement;
127
+ executionResolution?: ScoutExecutionResolution;
128
+ startedAt: number;
129
+ lastAcknowledgedAt: number;
130
+ endedAt?: number;
131
+ }
132
+
133
+ export interface FlightDispatchAcknowledgement {
134
+ sessionId?: ScoutId | null;
135
+ /**
136
+ * Provisional session id replaced by `sessionId` for this same dispatch.
137
+ * Absent for a real transition between two concrete provider sessions.
138
+ */
139
+ refinesSessionId?: ScoutId | null;
140
+ endpointId?: ScoutId | null;
141
+ nodeId?: ScoutId | null;
142
+ harness?: AgentHarness | null;
143
+ transport?: string | null;
144
+ strategy?: string | null;
145
+ placement?: SessionPlacement | null;
146
+ executionResolution?: ScoutExecutionResolution | null;
147
+ acknowledgedAt?: number | null;
148
+ }
149
+
150
+ function cleanTraceString(value: unknown): string | undefined {
151
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
152
+ }
153
+
154
+ function cleanTraceTimestamp(value: unknown): number | undefined {
155
+ return typeof value === "number" && Number.isFinite(value) && value >= 0
156
+ ? value
157
+ : undefined;
158
+ }
159
+
160
+ function cleanTracePlacement(value: unknown): SessionPlacement | undefined {
161
+ return value === "background" || value === "foreground" ? value : undefined;
162
+ }
163
+
164
+ function parseFlightSessionTraceEntry(value: unknown): FlightSessionTraceEntry | null {
165
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
166
+ const record = value as Record<string, unknown>;
167
+ const sessionId = cleanTraceString(record.sessionId);
168
+ const startedAt = cleanTraceTimestamp(record.startedAt);
169
+ const lastAcknowledgedAt = cleanTraceTimestamp(record.lastAcknowledgedAt) ?? startedAt;
170
+ if (!sessionId || startedAt === undefined || lastAcknowledgedAt === undefined) return null;
171
+ const harness = cleanTraceString(record.harness);
172
+ const executionResolution = record.executionResolution
173
+ && typeof record.executionResolution === "object"
174
+ && !Array.isArray(record.executionResolution)
175
+ ? record.executionResolution as ScoutExecutionResolution
176
+ : undefined;
177
+ return {
178
+ sessionId,
179
+ ...(cleanTraceString(record.endpointId) ? { endpointId: cleanTraceString(record.endpointId) } : {}),
180
+ ...(cleanTraceString(record.nodeId) ? { nodeId: cleanTraceString(record.nodeId) } : {}),
181
+ ...(harness ? { harness: harness as AgentHarness } : {}),
182
+ ...(cleanTraceString(record.transport) ? { transport: cleanTraceString(record.transport) } : {}),
183
+ ...(cleanTraceString(record.strategy) ? { strategy: cleanTraceString(record.strategy) } : {}),
184
+ ...(cleanTracePlacement(record.placement) ? { placement: cleanTracePlacement(record.placement) } : {}),
185
+ ...(executionResolution ? { executionResolution } : {}),
186
+ startedAt,
187
+ lastAcknowledgedAt,
188
+ ...(cleanTraceTimestamp(record.endedAt) !== undefined
189
+ ? { endedAt: cleanTraceTimestamp(record.endedAt) }
190
+ : {}),
191
+ };
192
+ }
193
+
194
+ function parseDispatchAcknowledgement(value: unknown): FlightDispatchAcknowledgement | null {
195
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
196
+ const record = value as Record<string, unknown>;
197
+ const sessionId = cleanTraceString(record.sessionId);
198
+ const acknowledgedAt = cleanTraceTimestamp(record.acknowledgedAt);
199
+ if (!sessionId || acknowledgedAt === undefined) return null;
200
+ const harness = cleanTraceString(record.harness);
201
+ const executionResolution = record.executionResolution
202
+ && typeof record.executionResolution === "object"
203
+ && !Array.isArray(record.executionResolution)
204
+ ? record.executionResolution as ScoutExecutionResolution
205
+ : null;
206
+ return {
207
+ sessionId,
208
+ refinesSessionId: cleanTraceString(record.refinesSessionId) ?? null,
209
+ endpointId: cleanTraceString(record.endpointId) ?? null,
210
+ nodeId: cleanTraceString(record.nodeId) ?? null,
211
+ harness: harness ? harness as AgentHarness : null,
212
+ transport: cleanTraceString(record.transport) ?? null,
213
+ strategy: cleanTraceString(record.strategy) ?? null,
214
+ placement: cleanTracePlacement(record.placement) ?? null,
215
+ executionResolution,
216
+ acknowledgedAt,
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Read a flight's ordered session history. Legacy records that only contain a
222
+ * dispatch acknowledgement are projected as a one-session trace.
223
+ */
224
+ export function flightSessionTrace(
225
+ flightOrMetadata: Pick<FlightRecord, "metadata"> | MetadataMap | null | undefined,
226
+ ): FlightSessionTraceEntry[] {
227
+ const metadata = (
228
+ flightOrMetadata && "metadata" in flightOrMetadata
229
+ ? (flightOrMetadata as Pick<FlightRecord, "metadata">).metadata
230
+ : flightOrMetadata
231
+ ) as MetadataMap | null | undefined;
232
+ const recorded = Array.isArray(metadata?.sessionTrace)
233
+ ? metadata.sessionTrace
234
+ .map(parseFlightSessionTraceEntry)
235
+ .filter((entry): entry is FlightSessionTraceEntry => entry !== null)
236
+ : [];
237
+ if (recorded.length > 0) return recorded;
238
+
239
+ const dispatchAck = parseDispatchAcknowledgement(metadata?.dispatchAck);
240
+ if (!dispatchAck?.sessionId || dispatchAck.acknowledgedAt === null || dispatchAck.acknowledgedAt === undefined) {
241
+ return [];
242
+ }
243
+ return [{
244
+ sessionId: dispatchAck.sessionId,
245
+ ...(dispatchAck.endpointId ? { endpointId: dispatchAck.endpointId } : {}),
246
+ ...(dispatchAck.nodeId ? { nodeId: dispatchAck.nodeId } : {}),
247
+ ...(dispatchAck.harness ? { harness: dispatchAck.harness } : {}),
248
+ ...(dispatchAck.transport ? { transport: dispatchAck.transport } : {}),
249
+ ...(dispatchAck.strategy ? { strategy: dispatchAck.strategy } : {}),
250
+ ...(dispatchAck.placement ? { placement: dispatchAck.placement } : {}),
251
+ startedAt: dispatchAck.acknowledgedAt,
252
+ lastAcknowledgedAt: dispatchAck.acknowledgedAt,
253
+ }];
254
+ }
255
+
256
+ /** Append a dispatch acknowledgement without losing earlier session identity. */
257
+ export function recordFlightSessionDispatch(
258
+ metadata: MetadataMap | null | undefined,
259
+ acknowledgement: unknown,
260
+ ): MetadataMap {
261
+ const dispatchAck = parseDispatchAcknowledgement(acknowledgement);
262
+ const nextMetadata: MetadataMap = { ...(metadata ?? {}), dispatchAck: acknowledgement };
263
+ if (!dispatchAck?.sessionId || dispatchAck.acknowledgedAt === null || dispatchAck.acknowledgedAt === undefined) {
264
+ return nextMetadata;
265
+ }
266
+
267
+ const trace = flightSessionTrace(metadata).map((entry) => ({ ...entry }));
268
+ const previous = trace.at(-1);
269
+ const refinesSameDispatch = Boolean(
270
+ previous
271
+ && dispatchAck.refinesSessionId
272
+ && previous.sessionId === dispatchAck.refinesSessionId
273
+ && previous.sessionId !== dispatchAck.sessionId
274
+ && previous.endpointId
275
+ && dispatchAck.endpointId
276
+ && previous.endpointId === dispatchAck.endpointId
277
+ && previous.startedAt === dispatchAck.acknowledgedAt,
278
+ );
279
+ if (
280
+ previous
281
+ && (previous.sessionId === dispatchAck.sessionId || refinesSameDispatch)
282
+ && (previous.endpointId ?? null) === (dispatchAck.endpointId ?? null)
283
+ ) {
284
+ // A transport can acknowledge with Scout's provisional routing id, then
285
+ // refine that same acknowledgement once the provider id is known. The
286
+ // unchanged endpoint and timestamp prove this is identity promotion, not
287
+ // a second harness session span.
288
+ if (refinesSameDispatch) {
289
+ previous.sessionId = dispatchAck.sessionId;
290
+ }
291
+ previous.lastAcknowledgedAt = Math.max(previous.lastAcknowledgedAt, dispatchAck.acknowledgedAt);
292
+ previous.strategy = dispatchAck.strategy ?? previous.strategy;
293
+ previous.placement = dispatchAck.placement ?? previous.placement;
294
+ previous.harness = dispatchAck.harness ?? previous.harness;
295
+ previous.transport = dispatchAck.transport ?? previous.transport;
296
+ previous.nodeId = dispatchAck.nodeId ?? previous.nodeId;
297
+ previous.executionResolution = dispatchAck.executionResolution ?? previous.executionResolution;
298
+ delete previous.endedAt;
299
+ } else {
300
+ if (previous && previous.endedAt === undefined) {
301
+ previous.endedAt = dispatchAck.acknowledgedAt;
302
+ }
303
+ trace.push({
304
+ sessionId: dispatchAck.sessionId,
305
+ ...(dispatchAck.endpointId ? { endpointId: dispatchAck.endpointId } : {}),
306
+ ...(dispatchAck.nodeId ? { nodeId: dispatchAck.nodeId } : {}),
307
+ ...(dispatchAck.harness ? { harness: dispatchAck.harness } : {}),
308
+ ...(dispatchAck.transport ? { transport: dispatchAck.transport } : {}),
309
+ ...(dispatchAck.strategy ? { strategy: dispatchAck.strategy } : {}),
310
+ ...(dispatchAck.placement ? { placement: dispatchAck.placement } : {}),
311
+ ...(dispatchAck.executionResolution ? { executionResolution: dispatchAck.executionResolution } : {}),
312
+ startedAt: dispatchAck.acknowledgedAt,
313
+ lastAcknowledgedAt: dispatchAck.acknowledgedAt,
314
+ });
315
+ }
316
+ nextMetadata.sessionTrace = trace;
317
+ return nextMetadata;
318
+ }
319
+
320
+ /**
321
+ * Mutable execution status for an invocation. Every field is optional — a
322
+ * freshly-created invocation has no flight or status yet.
323
+ *
324
+ * Note the dispatch job's scheduler state (attempts / leaseOwner /
325
+ * leaseExpiresAt / lastError) is deliberately NOT part of this: it is a distinct
326
+ * concern (worker-lease mechanics), stays its own journal-backed record, and is
327
+ * surfaced separately rather than folded into the domain status.
328
+ */
329
+ export interface InvocationStatus {
330
+ /**
331
+ * Durable secondary id of the invocation's flight. Retained as a stable alias
332
+ * so projected run ids (`run:flight:<flightId>`) and follow links survive the
333
+ * flight→invocation storage merge.
334
+ */
335
+ flightId?: ScoutId;
336
+ state?: FlightState;
337
+ summary?: string;
338
+ output?: string;
339
+ error?: string;
340
+ startedAt?: number;
341
+ completedAt?: number;
342
+ }
343
+
344
+ /**
345
+ * The merged invocation record: the immutable request ({@link InvocationRequest})
346
+ * plus its mutable execution status ({@link InvocationStatus}). This is the
347
+ * single source of truth, from which the flight / work-item / agent-run views are
348
+ * projected. The standalone {@link FlightRecord} remains a compatibility shape
349
+ * over the same status subset.
350
+ */
351
+ export type Invocation = InvocationRequest & InvocationStatus;
352
+
353
+ export function normalizeInvocationSessionPolicy(
354
+ policy: InvocationSessionPolicy | string | null | undefined,
355
+ ): InvocationSessionPolicy | undefined {
356
+ switch (policy) {
357
+ case "new":
358
+ case "reuse":
359
+ case "existing":
360
+ case "fork":
361
+ case "any":
362
+ return policy;
363
+ default:
364
+ return undefined;
365
+ }
366
+ }
367
+
368
+ export function effectiveInvocationSessionPolicy(
369
+ execution: Pick<
370
+ InvocationExecutionPreference,
371
+ "session" | "targetSessionId" | "forkFromStateId" | "forkFromSessionId"
372
+ > | null | undefined,
373
+ ): Exclude<InvocationSessionPolicy, "any"> {
374
+ if (!execution) return "new";
375
+ if (execution.forkFromStateId || execution.forkFromSessionId || execution.session === "fork") {
376
+ return "fork";
377
+ }
378
+ if (execution.targetSessionId || execution.session === "existing") {
379
+ return "existing";
380
+ }
381
+ if (execution.session === "reuse" || execution.session === "any") {
382
+ return "reuse";
383
+ }
384
+ return "new";
385
+ }
386
+
387
+ export function validateInvocationExecutionPreference(
388
+ execution: InvocationExecutionPreference | null | undefined,
389
+ ): string[] {
390
+ if (!execution) return [];
391
+
392
+ const errors: string[] = [];
393
+ const policy = effectiveInvocationSessionPolicy(execution);
394
+
395
+ if (policy === "existing" && !execution.targetSessionId) {
396
+ errors.push("session existing requires targetSessionId");
397
+ }
398
+ if (policy === "fork" && !execution.forkFromStateId && !execution.forkFromSessionId) {
399
+ errors.push("session fork requires forkFromStateId or forkFromSessionId");
400
+ }
401
+ if (execution.session === "new" && execution.targetSessionId) {
402
+ errors.push("session new cannot target an existing session");
403
+ }
404
+ if (execution.session === "new" && (execution.forkFromStateId || execution.forkFromSessionId)) {
405
+ errors.push("session new cannot include fork source ids");
406
+ }
407
+
408
+ return errors;
409
+ }
package/src/mesh.ts CHANGED
@@ -1,5 +1,86 @@
1
1
  import type { AdvertiseScope, MetadataMap, ScoutId } from "./common.js";
2
2
 
3
+ export const OPENSCOUT_MESH_PROTOCOL_VERSION = 1;
4
+ export const OPENSCOUT_IROH_MESH_ALPN = "openscout/mesh/0";
5
+ export const DEFAULT_OPENSCOUT_MESH_RENDEZVOUS_URL = "https://mesh.oscout.net";
6
+ export const DEFAULT_OPENSCOUT_MOBILE_PAIRING_RELAY_URL = "wss://mesh.oscout.net/v1/relay";
7
+
8
+ export type NodeMeshEntrypoint =
9
+ | IrohMeshEntrypoint
10
+ | CloudflareTunnelMeshEntrypoint
11
+ | HttpMeshEntrypoint
12
+ | MobilePairingMeshEntrypoint;
13
+
14
+ export interface IrohMeshEntrypoint {
15
+ kind: "iroh";
16
+ endpointId: string;
17
+ endpointAddr: unknown;
18
+ alpn: typeof OPENSCOUT_IROH_MESH_ALPN;
19
+ bridgeProtocolVersion: typeof OPENSCOUT_MESH_PROTOCOL_VERSION;
20
+ lastSeenAt?: number;
21
+ expiresAt?: number;
22
+ metadata?: MetadataMap;
23
+ }
24
+
25
+ export interface CloudflareTunnelMeshEntrypoint {
26
+ kind: "cloudflare_tunnel";
27
+ url: string;
28
+ lastSeenAt?: number;
29
+ expiresAt?: number;
30
+ metadata?: MetadataMap;
31
+ }
32
+
33
+ export interface HttpMeshEntrypoint {
34
+ kind: "http";
35
+ url: string;
36
+ lastSeenAt?: number;
37
+ expiresAt?: number;
38
+ metadata?: MetadataMap;
39
+ }
40
+
41
+ export interface MobilePairingMeshEntrypoint {
42
+ kind: "mobile_pairing";
43
+ relay: string;
44
+ fallbackRelays?: string[];
45
+ room: string;
46
+ publicKey: string;
47
+ expiresAt: number;
48
+ lastSeenAt?: number;
49
+ metadata?: MetadataMap;
50
+ }
51
+
52
+ export type TrustedPeerTier = "observe" | "control";
53
+
54
+ export type TrustedPeerGrantChannel = "sas" | "github" | "ssh";
55
+
56
+ // A peer this node has enrolled into its trust cone (see
57
+ // docs/proposals/mesh-trust-cone.md §6). All enrollment channels produce the
58
+ // same artifact; revocation is soft via `revokedAt`.
59
+ export interface TrustedPeerRecord {
60
+ /** Canonical machine identity: full SHA-256 hex of the DER public key. */
61
+ keyId: string;
62
+ /** Base64 DER spki Ed25519 public key. */
63
+ publicKey: string;
64
+ /** Short `osc1:xxxx-xxxx` form — display-only, never an identity key. */
65
+ fingerprint: string;
66
+ /** Remote node's runtime id, when known. */
67
+ nodeId?: ScoutId;
68
+ label: string;
69
+ tier: TrustedPeerTier;
70
+ grantedVia: TrustedPeerGrantChannel;
71
+ grantedAt: number;
72
+ expiresAt?: number;
73
+ revokedAt?: number;
74
+ lastSeenAt?: number;
75
+ /**
76
+ * §11.2 durable TLS pin: SHA-256 hex of the peer TLS certificate's
77
+ * SubjectPublicKeyInfo DER, copied from the peer's verified signed card at
78
+ * enrollment. Absent for Tailscale-path peers and pre-P1.5 enrollments.
79
+ */
80
+ tlsSpkiFingerprint?: string;
81
+ metadata?: MetadataMap;
82
+ }
83
+
3
84
  export interface NodeDefinition {
4
85
  id: ScoutId;
5
86
  meshId: ScoutId;
@@ -7,6 +88,8 @@ export interface NodeDefinition {
7
88
  hostName?: string;
8
89
  advertiseScope: AdvertiseScope;
9
90
  brokerUrl?: string;
91
+ webUrl?: string;
92
+ meshEntrypoints?: NodeMeshEntrypoint[];
10
93
  tailnetName?: string;
11
94
  capabilities?: string[];
12
95
  labels?: string[];
@@ -14,3 +97,50 @@ export interface NodeDefinition {
14
97
  lastSeenAt?: number;
15
98
  registeredAt: number;
16
99
  }
100
+
101
+ export interface OpenScoutMeshPresence {
102
+ v: typeof OPENSCOUT_MESH_PROTOCOL_VERSION;
103
+ meshId: ScoutId;
104
+ nodeId: ScoutId;
105
+ nodeName: string;
106
+ issuedAt: number;
107
+ expiresAt: number;
108
+ entrypoints: NodeMeshEntrypoint[];
109
+ signature?: {
110
+ algorithm: "ed25519";
111
+ keyId: string;
112
+ value: string;
113
+ };
114
+ metadata?: MetadataMap;
115
+ }
116
+
117
+ export interface OpenScoutMeshPresenceRecord extends OpenScoutMeshPresence {
118
+ observedAt: number;
119
+ }
120
+
121
+ export interface OpenScoutMeshRendezvousList {
122
+ v: typeof OPENSCOUT_MESH_PROTOCOL_VERSION;
123
+ meshId: ScoutId;
124
+ nodes: OpenScoutMeshPresenceRecord[];
125
+ }
126
+
127
+ export function buildUnsignedMeshPresence(input: {
128
+ node: Pick<NodeDefinition, "id" | "meshId" | "name">;
129
+ entrypoints: NodeMeshEntrypoint[];
130
+ issuedAt?: number;
131
+ ttlMs?: number;
132
+ metadata?: MetadataMap;
133
+ }): OpenScoutMeshPresence {
134
+ const issuedAt = input.issuedAt ?? Date.now();
135
+ const ttlMs = input.ttlMs ?? 60_000;
136
+ return {
137
+ v: OPENSCOUT_MESH_PROTOCOL_VERSION,
138
+ meshId: input.node.meshId,
139
+ nodeId: input.node.id,
140
+ nodeName: input.node.name,
141
+ issuedAt,
142
+ expiresAt: issuedAt + ttlMs,
143
+ entrypoints: input.entrypoints,
144
+ ...(input.metadata ? { metadata: input.metadata } : {}),
145
+ };
146
+ }
package/src/messages.ts CHANGED
@@ -33,6 +33,8 @@ export interface MessageAudience {
33
33
  notify?: ScoutId[];
34
34
  invoke?: ScoutId[];
35
35
  reason?: DeliveryReason;
36
+ /** Persist the message without scheduling any transport deliveries. */
37
+ delivery?: "none";
36
38
  }
37
39
 
38
40
  export interface MessageRecord {
@@ -0,0 +1,100 @@
1
+ import type { ScoutId } from "./common.js";
2
+
3
+ export type HarnessNativeInteractionKind =
4
+ | "question"
5
+ | "plan_approval"
6
+ | "task_projection"
7
+ | "subagent_activity"
8
+ | "tool_approval"
9
+ | "follow_up"
10
+ | "steer";
11
+
12
+ export type ScoutNativeInteractionTargetKind =
13
+ | "question"
14
+ | "work_item"
15
+ | "invocation"
16
+ | "flight"
17
+ | "message"
18
+ | "session_projection";
19
+
20
+ export interface HarnessNativeInteractionSource {
21
+ harness: string;
22
+ sessionId?: ScoutId;
23
+ harnessThreadId?: string;
24
+ toolCallId?: string;
25
+ nativeName?: string;
26
+ rawEventId?: string;
27
+ }
28
+
29
+ export interface HarnessNativeInteraction {
30
+ id: ScoutId;
31
+ kind: HarnessNativeInteractionKind;
32
+ source: HarnessNativeInteractionSource;
33
+ title?: string;
34
+ summary?: string;
35
+ payload?: Record<string, unknown>;
36
+ observedAt: number;
37
+ }
38
+
39
+ export interface HarnessNativeInteractionMapping {
40
+ kind: HarnessNativeInteractionKind;
41
+ nativeNames: string[];
42
+ scoutTargets: ScoutNativeInteractionTargetKind[];
43
+ description: string;
44
+ }
45
+
46
+ export const DEFAULT_HARNESS_NATIVE_INTERACTION_MAPPINGS: readonly HarnessNativeInteractionMapping[] = [
47
+ {
48
+ kind: "question",
49
+ nativeNames: ["ask_user"],
50
+ scoutTargets: ["question", "session_projection"],
51
+ description: "A harness-native user question becomes a Scout question.",
52
+ },
53
+ {
54
+ kind: "plan_approval",
55
+ nativeNames: ["submit_plan"],
56
+ scoutTargets: ["work_item", "session_projection"],
57
+ description: "A harness-native plan review becomes a Scout work item or review-needed projection.",
58
+ },
59
+ {
60
+ kind: "task_projection",
61
+ nativeNames: ["task_write", "task_update", "task_complete", "task_check"],
62
+ scoutTargets: ["work_item", "session_projection"],
63
+ description: "Harness-native task tools project into Scout work item task state.",
64
+ },
65
+ {
66
+ kind: "subagent_activity",
67
+ nativeNames: ["subagent"],
68
+ scoutTargets: ["invocation", "flight", "session_projection"],
69
+ description: "Harness-native child agent activity links to child invocations or observed child runs.",
70
+ },
71
+ {
72
+ kind: "tool_approval",
73
+ nativeNames: ["tool_approval_required"],
74
+ scoutTargets: ["session_projection"],
75
+ description: "A harness-native tool approval projects into session state.",
76
+ },
77
+ {
78
+ kind: "follow_up",
79
+ nativeNames: ["follow_up"],
80
+ scoutTargets: ["message", "session_projection"],
81
+ description: "A queued follow-up remains in the same conversation/session context.",
82
+ },
83
+ {
84
+ kind: "steer",
85
+ nativeNames: ["steer"],
86
+ scoutTargets: ["flight", "message", "session_projection"],
87
+ description: "A steering request redirects the active session or flight with an explicit reason.",
88
+ },
89
+ ] as const;
90
+
91
+ export function scoutTargetsForNativeInteraction(
92
+ kind: HarnessNativeInteractionKind,
93
+ ): ScoutNativeInteractionTargetKind[] {
94
+ return [...(DEFAULT_HARNESS_NATIVE_INTERACTION_MAPPINGS.find(mapping => mapping.kind === kind)?.scoutTargets ?? [])];
95
+ }
96
+
97
+ export function nativeInteractionKindForName(nativeName: string): HarnessNativeInteractionKind | undefined {
98
+ const normalized = nativeName.trim();
99
+ return DEFAULT_HARNESS_NATIVE_INTERACTION_MAPPINGS.find(mapping => mapping.nativeNames.includes(normalized))?.kind;
100
+ }