@openscout/protocol 0.2.16 → 0.2.17
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/package.json +2 -1
- package/src/actors.ts +85 -0
- package/src/agent-address.ts +1 -0
- package/src/agent-identity.test.ts +265 -0
- package/src/agent-identity.ts +508 -0
- package/src/agent-selectors.ts +1 -0
- package/src/collaboration.ts +272 -0
- package/src/common.ts +71 -0
- package/src/conversations.ts +37 -0
- package/src/deliveries.ts +46 -0
- package/src/events.ts +84 -0
- package/src/index.ts +14 -0
- package/src/invocations.ts +55 -0
- package/src/mesh.ts +16 -0
- package/src/messages.ts +55 -0
- package/src/relay-agent-card.test.ts +54 -0
- package/src/relay-agent-card.ts +76 -0
- package/src/transports.ts +92 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import type { MetadataMap, ScoutId } from "./common.js";
|
|
2
|
+
|
|
3
|
+
export type CollaborationKind = "question" | "work_item";
|
|
4
|
+
|
|
5
|
+
export type CollaborationPriority = "low" | "normal" | "high" | "urgent";
|
|
6
|
+
|
|
7
|
+
export type CollaborationAcceptanceState =
|
|
8
|
+
| "none"
|
|
9
|
+
| "pending"
|
|
10
|
+
| "accepted"
|
|
11
|
+
| "reopened";
|
|
12
|
+
|
|
13
|
+
export type QuestionState =
|
|
14
|
+
| "open"
|
|
15
|
+
| "answered"
|
|
16
|
+
| "closed"
|
|
17
|
+
| "declined";
|
|
18
|
+
|
|
19
|
+
export type WorkItemState =
|
|
20
|
+
| "open"
|
|
21
|
+
| "working"
|
|
22
|
+
| "waiting"
|
|
23
|
+
| "review"
|
|
24
|
+
| "done"
|
|
25
|
+
| "cancelled";
|
|
26
|
+
|
|
27
|
+
export type CollaborationRelationKind =
|
|
28
|
+
| "blocks"
|
|
29
|
+
| "spawns"
|
|
30
|
+
| "relates_to"
|
|
31
|
+
| "references";
|
|
32
|
+
|
|
33
|
+
export interface CollaborationRelation {
|
|
34
|
+
kind: CollaborationRelationKind;
|
|
35
|
+
targetId: ScoutId;
|
|
36
|
+
metadata?: MetadataMap;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CollaborationWaitingOn {
|
|
40
|
+
kind: "actor" | "question" | "work_item" | "approval" | "artifact" | "condition";
|
|
41
|
+
label: string;
|
|
42
|
+
targetId?: ScoutId;
|
|
43
|
+
metadata?: MetadataMap;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CollaborationProgress {
|
|
47
|
+
completedSteps?: number;
|
|
48
|
+
totalSteps?: number;
|
|
49
|
+
checkpoint?: string;
|
|
50
|
+
summary?: string;
|
|
51
|
+
percent?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface CollaborationRecordBase {
|
|
55
|
+
id: ScoutId;
|
|
56
|
+
kind: CollaborationKind;
|
|
57
|
+
title: string;
|
|
58
|
+
summary?: string;
|
|
59
|
+
createdById: ScoutId;
|
|
60
|
+
ownerId?: ScoutId;
|
|
61
|
+
nextMoveOwnerId?: ScoutId;
|
|
62
|
+
conversationId?: ScoutId;
|
|
63
|
+
parentId?: ScoutId;
|
|
64
|
+
priority?: CollaborationPriority;
|
|
65
|
+
labels?: string[];
|
|
66
|
+
relations?: CollaborationRelation[];
|
|
67
|
+
createdAt: number;
|
|
68
|
+
updatedAt: number;
|
|
69
|
+
metadata?: MetadataMap;
|
|
70
|
+
}
|
|
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
|
+
export interface WorkItemRecord extends CollaborationRecordBase {
|
|
84
|
+
kind: "work_item";
|
|
85
|
+
state: WorkItemState;
|
|
86
|
+
acceptanceState: CollaborationAcceptanceState;
|
|
87
|
+
requestedById?: ScoutId;
|
|
88
|
+
waitingOn?: CollaborationWaitingOn;
|
|
89
|
+
progress?: CollaborationProgress;
|
|
90
|
+
startedAt?: number;
|
|
91
|
+
reviewRequestedAt?: number;
|
|
92
|
+
completedAt?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type CollaborationRecord = QuestionRecord | WorkItemRecord;
|
|
96
|
+
|
|
97
|
+
export type CollaborationEventKind =
|
|
98
|
+
| "created"
|
|
99
|
+
| "claimed"
|
|
100
|
+
| "answered"
|
|
101
|
+
| "accepted"
|
|
102
|
+
| "reopened"
|
|
103
|
+
| "waiting"
|
|
104
|
+
| "progressed"
|
|
105
|
+
| "handoff"
|
|
106
|
+
| "review_requested"
|
|
107
|
+
| "done"
|
|
108
|
+
| "declined"
|
|
109
|
+
| "cancelled";
|
|
110
|
+
|
|
111
|
+
export interface CollaborationEvent {
|
|
112
|
+
id: ScoutId;
|
|
113
|
+
recordId: ScoutId;
|
|
114
|
+
recordKind: CollaborationKind;
|
|
115
|
+
kind: CollaborationEventKind;
|
|
116
|
+
actorId: ScoutId;
|
|
117
|
+
at: number;
|
|
118
|
+
summary?: string;
|
|
119
|
+
metadata?: MetadataMap;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function isQuestionTerminalState(state: QuestionState): boolean {
|
|
123
|
+
return state === "closed" || state === "declined";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function isWorkItemTerminalState(state: WorkItemState): boolean {
|
|
127
|
+
return state === "done" || state === "cancelled";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function collaborationRequiresNextMoveOwner(record: CollaborationRecord): boolean {
|
|
131
|
+
if (record.kind === "question") {
|
|
132
|
+
return !isQuestionTerminalState(record.state);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return !isWorkItemTerminalState(record.state);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function collaborationRequiresOwner(record: CollaborationRecord): boolean {
|
|
139
|
+
return record.kind === "work_item" && !isWorkItemTerminalState(record.state);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function collaborationRequiresWaitingOn(record: CollaborationRecord): boolean {
|
|
143
|
+
return record.kind === "work_item" && record.state === "waiting";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function collaborationRequiresAcceptance(record: CollaborationRecord): boolean {
|
|
147
|
+
if (record.acceptanceState === "none") {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (record.kind === "question") {
|
|
152
|
+
return Boolean(record.askedById && record.askedOfId);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return Boolean(record.requestedById);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function validateCollaborationRecord(record: CollaborationRecord): string[] {
|
|
159
|
+
const errors: string[] = [];
|
|
160
|
+
|
|
161
|
+
if (!record.id.trim()) {
|
|
162
|
+
errors.push("collaboration record id is required");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!record.title.trim()) {
|
|
166
|
+
errors.push("collaboration title is required");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!record.createdById.trim()) {
|
|
170
|
+
errors.push("createdById is required");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (record.parentId && record.parentId === record.id) {
|
|
174
|
+
errors.push("parentId cannot reference the record itself");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (record.createdAt > record.updatedAt) {
|
|
178
|
+
errors.push("updatedAt must be greater than or equal to createdAt");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (collaborationRequiresOwner(record) && !record.ownerId) {
|
|
182
|
+
errors.push("non-terminal work items require ownerId");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (collaborationRequiresNextMoveOwner(record) && !record.nextMoveOwnerId) {
|
|
186
|
+
errors.push("non-terminal collaboration records require nextMoveOwnerId");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (record.kind === "work_item" && collaborationRequiresWaitingOn(record) && !record.waitingOn) {
|
|
190
|
+
errors.push("waiting work items require waitingOn");
|
|
191
|
+
}
|
|
192
|
+
|
|
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) {
|
|
198
|
+
errors.push("waitingOn.targetId cannot reference the work item itself");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (record.acceptanceState !== "none" && !collaborationRequiresAcceptance(record)) {
|
|
202
|
+
errors.push("acceptanceState requires the corresponding requester and reviewer identities");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return errors;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function assertValidCollaborationRecord(record: CollaborationRecord): void {
|
|
209
|
+
const errors = validateCollaborationRecord(record);
|
|
210
|
+
if (errors.length > 0) {
|
|
211
|
+
throw new Error(errors.join("; "));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function validateCollaborationEvent(
|
|
216
|
+
event: CollaborationEvent,
|
|
217
|
+
record?: CollaborationRecord,
|
|
218
|
+
): string[] {
|
|
219
|
+
const errors: string[] = [];
|
|
220
|
+
|
|
221
|
+
if (!event.id.trim()) {
|
|
222
|
+
errors.push("collaboration event id is required");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!event.recordId.trim()) {
|
|
226
|
+
errors.push("collaboration event recordId is required");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!event.actorId.trim()) {
|
|
230
|
+
errors.push("collaboration event actorId is required");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (record) {
|
|
234
|
+
if (record.id !== event.recordId) {
|
|
235
|
+
errors.push("collaboration event recordId does not match the target record");
|
|
236
|
+
}
|
|
237
|
+
if (record.kind !== event.recordKind) {
|
|
238
|
+
errors.push("collaboration event recordKind does not match the target record");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
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
|
+
if (
|
|
251
|
+
(event.kind === "waiting"
|
|
252
|
+
|| event.kind === "progressed"
|
|
253
|
+
|| event.kind === "review_requested"
|
|
254
|
+
|| event.kind === "done"
|
|
255
|
+
|| event.kind === "cancelled")
|
|
256
|
+
&& event.recordKind !== "work_item"
|
|
257
|
+
) {
|
|
258
|
+
errors.push(`${event.kind} events only apply to work items`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return errors;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function assertValidCollaborationEvent(
|
|
265
|
+
event: CollaborationEvent,
|
|
266
|
+
record?: CollaborationRecord,
|
|
267
|
+
): void {
|
|
268
|
+
const errors = validateCollaborationEvent(event, record);
|
|
269
|
+
if (errors.length > 0) {
|
|
270
|
+
throw new Error(errors.join("; "));
|
|
271
|
+
}
|
|
272
|
+
}
|
package/src/common.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export type ScoutId = string;
|
|
2
|
+
|
|
3
|
+
export type ActorKind =
|
|
4
|
+
| "person"
|
|
5
|
+
| "helper"
|
|
6
|
+
| "agent"
|
|
7
|
+
| "system"
|
|
8
|
+
| "bridge"
|
|
9
|
+
| "device";
|
|
10
|
+
|
|
11
|
+
export type AgentState =
|
|
12
|
+
| "offline"
|
|
13
|
+
| "idle"
|
|
14
|
+
| "active"
|
|
15
|
+
| "waiting"
|
|
16
|
+
| "degraded";
|
|
17
|
+
|
|
18
|
+
export type VisibilityScope = "private" | "workspace" | "public" | "system";
|
|
19
|
+
|
|
20
|
+
export type DeliveryStatus =
|
|
21
|
+
| "pending"
|
|
22
|
+
| "leased"
|
|
23
|
+
| "sent"
|
|
24
|
+
| "acknowledged"
|
|
25
|
+
| "failed"
|
|
26
|
+
| "cancelled";
|
|
27
|
+
|
|
28
|
+
export type DeliveryPolicy = "best_effort" | "must_ack" | "durable" | "ephemeral";
|
|
29
|
+
|
|
30
|
+
export type DeliveryTargetKind =
|
|
31
|
+
| "participant"
|
|
32
|
+
| "agent"
|
|
33
|
+
| "bridge"
|
|
34
|
+
| "device"
|
|
35
|
+
| "voice_session"
|
|
36
|
+
| "webhook";
|
|
37
|
+
|
|
38
|
+
export type DeliveryTransport =
|
|
39
|
+
| "local_socket"
|
|
40
|
+
| "websocket"
|
|
41
|
+
| "peer_broker"
|
|
42
|
+
| "http"
|
|
43
|
+
| "webhook"
|
|
44
|
+
| "telegram"
|
|
45
|
+
| "discord"
|
|
46
|
+
| "sms"
|
|
47
|
+
| "email"
|
|
48
|
+
| "tts"
|
|
49
|
+
| "native_voice"
|
|
50
|
+
| "claude_stream_json"
|
|
51
|
+
| "codex_app_server"
|
|
52
|
+
| "codex_exec"
|
|
53
|
+
| "claude_resume"
|
|
54
|
+
| "tmux";
|
|
55
|
+
|
|
56
|
+
export type DeliveryReason =
|
|
57
|
+
| "conversation_visibility"
|
|
58
|
+
| "direct_message"
|
|
59
|
+
| "mention"
|
|
60
|
+
| "thread_reply"
|
|
61
|
+
| "invocation"
|
|
62
|
+
| "bridge_outbound"
|
|
63
|
+
| "speech";
|
|
64
|
+
|
|
65
|
+
export type AdvertiseScope = "local" | "mesh";
|
|
66
|
+
|
|
67
|
+
export type ShareMode = "local" | "summary" | "shared";
|
|
68
|
+
|
|
69
|
+
export interface MetadataMap {
|
|
70
|
+
[key: string]: unknown;
|
|
71
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MetadataMap,
|
|
3
|
+
ScoutId,
|
|
4
|
+
ShareMode,
|
|
5
|
+
VisibilityScope,
|
|
6
|
+
} from "./common.js";
|
|
7
|
+
|
|
8
|
+
export type ConversationKind =
|
|
9
|
+
| "channel"
|
|
10
|
+
| "direct"
|
|
11
|
+
| "group_direct"
|
|
12
|
+
| "thread"
|
|
13
|
+
| "system";
|
|
14
|
+
|
|
15
|
+
export interface ConversationDefinition {
|
|
16
|
+
id: ScoutId;
|
|
17
|
+
kind: ConversationKind;
|
|
18
|
+
title: string;
|
|
19
|
+
visibility: VisibilityScope;
|
|
20
|
+
shareMode: ShareMode;
|
|
21
|
+
authorityNodeId: ScoutId;
|
|
22
|
+
participantIds: ScoutId[];
|
|
23
|
+
topic?: string;
|
|
24
|
+
parentConversationId?: ScoutId;
|
|
25
|
+
messageId?: ScoutId;
|
|
26
|
+
metadata?: MetadataMap;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ConversationBinding {
|
|
30
|
+
id: ScoutId;
|
|
31
|
+
conversationId: ScoutId;
|
|
32
|
+
platform: string;
|
|
33
|
+
mode: "inbound" | "outbound" | "bidirectional";
|
|
34
|
+
externalChannelId: string;
|
|
35
|
+
externalThreadId?: string;
|
|
36
|
+
metadata?: MetadataMap;
|
|
37
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DeliveryPolicy,
|
|
3
|
+
DeliveryReason,
|
|
4
|
+
DeliveryStatus,
|
|
5
|
+
DeliveryTargetKind,
|
|
6
|
+
DeliveryTransport,
|
|
7
|
+
MetadataMap,
|
|
8
|
+
ScoutId,
|
|
9
|
+
} from "./common.js";
|
|
10
|
+
|
|
11
|
+
export interface DeliveryTarget {
|
|
12
|
+
id: ScoutId;
|
|
13
|
+
kind: DeliveryTargetKind;
|
|
14
|
+
transport: DeliveryTransport;
|
|
15
|
+
address?: string;
|
|
16
|
+
bindingId?: ScoutId;
|
|
17
|
+
metadata?: MetadataMap;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DeliveryIntent {
|
|
21
|
+
id: ScoutId;
|
|
22
|
+
messageId?: ScoutId;
|
|
23
|
+
invocationId?: ScoutId;
|
|
24
|
+
targetId: ScoutId;
|
|
25
|
+
targetNodeId?: ScoutId;
|
|
26
|
+
targetKind: DeliveryTargetKind;
|
|
27
|
+
transport: DeliveryTransport;
|
|
28
|
+
reason: DeliveryReason;
|
|
29
|
+
policy: DeliveryPolicy;
|
|
30
|
+
status: DeliveryStatus;
|
|
31
|
+
bindingId?: ScoutId;
|
|
32
|
+
leaseOwner?: string;
|
|
33
|
+
leaseExpiresAt?: number;
|
|
34
|
+
metadata?: MetadataMap;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DeliveryAttempt {
|
|
38
|
+
id: ScoutId;
|
|
39
|
+
deliveryId: ScoutId;
|
|
40
|
+
attempt: number;
|
|
41
|
+
status: "sent" | "acknowledged" | "failed";
|
|
42
|
+
error?: string;
|
|
43
|
+
externalRef?: string;
|
|
44
|
+
createdAt: number;
|
|
45
|
+
metadata?: MetadataMap;
|
|
46
|
+
}
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ScoutId } from "./common.js";
|
|
2
|
+
import type { NodeDefinition } from "./mesh.js";
|
|
3
|
+
import type { ConversationBinding, ConversationDefinition } from "./conversations.js";
|
|
4
|
+
import type { DeliveryAttempt, DeliveryIntent } from "./deliveries.js";
|
|
5
|
+
import type { FlightRecord, InvocationRequest } from "./invocations.js";
|
|
6
|
+
import type { MessageRecord } from "./messages.js";
|
|
7
|
+
import type { AgentDefinition, AgentEndpoint, ActorIdentity } from "./actors.js";
|
|
8
|
+
import type { CollaborationEvent, CollaborationRecord } from "./collaboration.js";
|
|
9
|
+
|
|
10
|
+
export interface ControlEventBase<K extends string, P> {
|
|
11
|
+
id: ScoutId;
|
|
12
|
+
kind: K;
|
|
13
|
+
ts: number;
|
|
14
|
+
actorId: ScoutId;
|
|
15
|
+
nodeId?: ScoutId;
|
|
16
|
+
payload: P;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type NodeUpsertedEvent = ControlEventBase<"node.upserted", {
|
|
20
|
+
node: NodeDefinition;
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
export type ActorRegisteredEvent = ControlEventBase<"actor.registered", {
|
|
24
|
+
actor: ActorIdentity;
|
|
25
|
+
}>;
|
|
26
|
+
|
|
27
|
+
export type AgentRegisteredEvent = ControlEventBase<"agent.registered", {
|
|
28
|
+
agent: AgentDefinition;
|
|
29
|
+
}>;
|
|
30
|
+
|
|
31
|
+
export type AgentEndpointUpsertedEvent = ControlEventBase<"agent.endpoint.upserted", {
|
|
32
|
+
endpoint: AgentEndpoint;
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
export type ConversationUpsertedEvent = ControlEventBase<"conversation.upserted", {
|
|
36
|
+
conversation: ConversationDefinition;
|
|
37
|
+
}>;
|
|
38
|
+
|
|
39
|
+
export type BindingUpsertedEvent = ControlEventBase<"binding.upserted", {
|
|
40
|
+
binding: ConversationBinding;
|
|
41
|
+
}>;
|
|
42
|
+
|
|
43
|
+
export type MessagePostedEvent = ControlEventBase<"message.posted", {
|
|
44
|
+
message: MessageRecord;
|
|
45
|
+
}>;
|
|
46
|
+
|
|
47
|
+
export type InvocationRequestedEvent = ControlEventBase<"invocation.requested", {
|
|
48
|
+
invocation: InvocationRequest;
|
|
49
|
+
}>;
|
|
50
|
+
|
|
51
|
+
export type FlightUpdatedEvent = ControlEventBase<"flight.updated", {
|
|
52
|
+
flight: FlightRecord;
|
|
53
|
+
}>;
|
|
54
|
+
|
|
55
|
+
export type DeliveryPlannedEvent = ControlEventBase<"delivery.planned", {
|
|
56
|
+
delivery: DeliveryIntent;
|
|
57
|
+
}>;
|
|
58
|
+
|
|
59
|
+
export type DeliveryAttemptedEvent = ControlEventBase<"delivery.attempted", {
|
|
60
|
+
attempt: DeliveryAttempt;
|
|
61
|
+
}>;
|
|
62
|
+
|
|
63
|
+
export type CollaborationUpsertedEvent = ControlEventBase<"collaboration.upserted", {
|
|
64
|
+
record: CollaborationRecord;
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
export type CollaborationEventAppendedEvent = ControlEventBase<"collaboration.event.appended", {
|
|
68
|
+
event: CollaborationEvent;
|
|
69
|
+
}>;
|
|
70
|
+
|
|
71
|
+
export type ControlEvent =
|
|
72
|
+
| NodeUpsertedEvent
|
|
73
|
+
| ActorRegisteredEvent
|
|
74
|
+
| AgentRegisteredEvent
|
|
75
|
+
| AgentEndpointUpsertedEvent
|
|
76
|
+
| ConversationUpsertedEvent
|
|
77
|
+
| BindingUpsertedEvent
|
|
78
|
+
| MessagePostedEvent
|
|
79
|
+
| InvocationRequestedEvent
|
|
80
|
+
| FlightUpdatedEvent
|
|
81
|
+
| DeliveryPlannedEvent
|
|
82
|
+
| DeliveryAttemptedEvent
|
|
83
|
+
| CollaborationUpsertedEvent
|
|
84
|
+
| CollaborationEventAppendedEvent;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./common.js";
|
|
2
|
+
export * from "./actors.js";
|
|
3
|
+
export * from "./agent-identity.js";
|
|
4
|
+
export * from "./agent-address.js";
|
|
5
|
+
export * from "./agent-selectors.js";
|
|
6
|
+
export * from "./relay-agent-card.js";
|
|
7
|
+
export * from "./mesh.js";
|
|
8
|
+
export * from "./conversations.js";
|
|
9
|
+
export * from "./collaboration.js";
|
|
10
|
+
export * from "./messages.js";
|
|
11
|
+
export * from "./invocations.js";
|
|
12
|
+
export * from "./deliveries.js";
|
|
13
|
+
export * from "./transports.js";
|
|
14
|
+
export * from "./events.js";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AgentHarness } from "./actors.js";
|
|
2
|
+
import type { MetadataMap, ScoutId } from "./common.js";
|
|
3
|
+
|
|
4
|
+
export type InvocationAction =
|
|
5
|
+
| "consult"
|
|
6
|
+
| "execute"
|
|
7
|
+
| "summarize"
|
|
8
|
+
| "status"
|
|
9
|
+
| "wake";
|
|
10
|
+
|
|
11
|
+
export type FlightState =
|
|
12
|
+
| "queued"
|
|
13
|
+
| "waking"
|
|
14
|
+
| "running"
|
|
15
|
+
| "waiting"
|
|
16
|
+
| "completed"
|
|
17
|
+
| "failed"
|
|
18
|
+
| "cancelled";
|
|
19
|
+
|
|
20
|
+
export interface InvocationExecutionPreference {
|
|
21
|
+
harness?: AgentHarness;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface InvocationRequest {
|
|
25
|
+
id: ScoutId;
|
|
26
|
+
requesterId: ScoutId;
|
|
27
|
+
requesterNodeId: ScoutId;
|
|
28
|
+
targetAgentId: ScoutId;
|
|
29
|
+
targetNodeId?: ScoutId;
|
|
30
|
+
action: InvocationAction;
|
|
31
|
+
task: string;
|
|
32
|
+
conversationId?: ScoutId;
|
|
33
|
+
messageId?: ScoutId;
|
|
34
|
+
context?: MetadataMap;
|
|
35
|
+
execution?: InvocationExecutionPreference;
|
|
36
|
+
ensureAwake: boolean;
|
|
37
|
+
stream: boolean;
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
createdAt: number;
|
|
40
|
+
metadata?: MetadataMap;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface FlightRecord {
|
|
44
|
+
id: ScoutId;
|
|
45
|
+
invocationId: ScoutId;
|
|
46
|
+
requesterId: ScoutId;
|
|
47
|
+
targetAgentId: ScoutId;
|
|
48
|
+
state: FlightState;
|
|
49
|
+
summary?: string;
|
|
50
|
+
output?: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
startedAt?: number;
|
|
53
|
+
completedAt?: number;
|
|
54
|
+
metadata?: MetadataMap;
|
|
55
|
+
}
|
package/src/mesh.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AdvertiseScope, MetadataMap, ScoutId } from "./common.js";
|
|
2
|
+
|
|
3
|
+
export interface NodeDefinition {
|
|
4
|
+
id: ScoutId;
|
|
5
|
+
meshId: ScoutId;
|
|
6
|
+
name: string;
|
|
7
|
+
hostName?: string;
|
|
8
|
+
advertiseScope: AdvertiseScope;
|
|
9
|
+
brokerUrl?: string;
|
|
10
|
+
tailnetName?: string;
|
|
11
|
+
capabilities?: string[];
|
|
12
|
+
labels?: string[];
|
|
13
|
+
metadata?: MetadataMap;
|
|
14
|
+
lastSeenAt?: number;
|
|
15
|
+
registeredAt: number;
|
|
16
|
+
}
|
package/src/messages.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DeliveryPolicy,
|
|
3
|
+
DeliveryReason,
|
|
4
|
+
MetadataMap,
|
|
5
|
+
ScoutId,
|
|
6
|
+
VisibilityScope,
|
|
7
|
+
} from "./common.js";
|
|
8
|
+
|
|
9
|
+
export type MessageClass = "agent" | "log" | "system" | "status" | "artifact";
|
|
10
|
+
|
|
11
|
+
export interface MessageSpeechDirective {
|
|
12
|
+
text: string;
|
|
13
|
+
voice?: string;
|
|
14
|
+
interruptible?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MessageAttachment {
|
|
18
|
+
id: ScoutId;
|
|
19
|
+
mediaType: string;
|
|
20
|
+
fileName?: string;
|
|
21
|
+
blobKey?: string;
|
|
22
|
+
url?: string;
|
|
23
|
+
metadata?: MetadataMap;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface MessageMention {
|
|
27
|
+
actorId: ScoutId;
|
|
28
|
+
label?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MessageAudience {
|
|
32
|
+
visibleTo?: ScoutId[];
|
|
33
|
+
notify?: ScoutId[];
|
|
34
|
+
invoke?: ScoutId[];
|
|
35
|
+
reason?: DeliveryReason;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface MessageRecord {
|
|
39
|
+
id: ScoutId;
|
|
40
|
+
conversationId: ScoutId;
|
|
41
|
+
actorId: ScoutId;
|
|
42
|
+
originNodeId: ScoutId;
|
|
43
|
+
class: MessageClass;
|
|
44
|
+
body: string;
|
|
45
|
+
replyToMessageId?: ScoutId;
|
|
46
|
+
threadConversationId?: ScoutId;
|
|
47
|
+
mentions?: MessageMention[];
|
|
48
|
+
attachments?: MessageAttachment[];
|
|
49
|
+
speech?: MessageSpeechDirective;
|
|
50
|
+
audience?: MessageAudience;
|
|
51
|
+
visibility: VisibilityScope;
|
|
52
|
+
policy: DeliveryPolicy;
|
|
53
|
+
createdAt: number;
|
|
54
|
+
metadata?: MetadataMap;
|
|
55
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { buildRelayReturnAddress } from "./relay-agent-card";
|
|
4
|
+
|
|
5
|
+
describe("buildRelayReturnAddress", () => {
|
|
6
|
+
test("keeps only meaningful optional fields", () => {
|
|
7
|
+
const address = buildRelayReturnAddress({
|
|
8
|
+
actorId: "dewey.node.workspace",
|
|
9
|
+
handle: "dewey",
|
|
10
|
+
displayName: "Dewey",
|
|
11
|
+
selector: "@dewey.node.workspace",
|
|
12
|
+
defaultSelector: "@dewey",
|
|
13
|
+
conversationId: "dm.dewey.arc",
|
|
14
|
+
replyToMessageId: "msg-1",
|
|
15
|
+
nodeId: "node-1",
|
|
16
|
+
projectRoot: "/Users/arach/dev/dewey",
|
|
17
|
+
sessionId: "relay-dewey-claude",
|
|
18
|
+
metadata: {
|
|
19
|
+
surface: "scout-card",
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(address).toEqual({
|
|
24
|
+
actorId: "dewey.node.workspace",
|
|
25
|
+
handle: "dewey",
|
|
26
|
+
displayName: "Dewey",
|
|
27
|
+
selector: "@dewey.node.workspace",
|
|
28
|
+
defaultSelector: "@dewey",
|
|
29
|
+
conversationId: "dm.dewey.arc",
|
|
30
|
+
replyToMessageId: "msg-1",
|
|
31
|
+
nodeId: "node-1",
|
|
32
|
+
projectRoot: "/Users/arach/dev/dewey",
|
|
33
|
+
sessionId: "relay-dewey-claude",
|
|
34
|
+
metadata: {
|
|
35
|
+
surface: "scout-card",
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("drops empty optional fields", () => {
|
|
41
|
+
const address = buildRelayReturnAddress({
|
|
42
|
+
actorId: "arc",
|
|
43
|
+
handle: "arc",
|
|
44
|
+
displayName: " ",
|
|
45
|
+
selector: " ",
|
|
46
|
+
metadata: {},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(address).toEqual({
|
|
50
|
+
actorId: "arc",
|
|
51
|
+
handle: "arc",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|