@openscout/protocol 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +245 -0
- package/dist/actors.d.ts +49 -0
- package/dist/actors.js +1 -0
- package/dist/agent-selectors.d.ts +27 -0
- package/dist/agent-selectors.js +103 -0
- package/dist/collaboration.d.ts +85 -0
- package/dist/collaboration.js +113 -0
- package/dist/common.d.ts +14 -0
- package/dist/common.js +1 -0
- package/dist/conversations.d.ts +24 -0
- package/dist/conversations.js +1 -0
- package/dist/deliveries.d.ts +35 -0
- package/dist/deliveries.js +1 -0
- package/dist/events.d.ts +56 -0
- package/dist/events.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/invocations.d.ts +38 -0
- package/dist/invocations.js +1 -0
- package/dist/mesh.d.ts +15 -0
- package/dist/mesh.js +1 -0
- package/dist/messages.d.ts +43 -0
- package/dist/messages.js +1 -0
- package/dist/transports.d.ts +66 -0
- package/dist/transports.js +1 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# OpenScout Control Protocol
|
|
2
|
+
|
|
3
|
+
`@openscout/protocol` defines the durable local communication and execution contract for OpenScout.
|
|
4
|
+
|
|
5
|
+
This package is intentionally not named `relay`. Relay is now a surface and compatibility layer. The control protocol is the canonical model underneath it.
|
|
6
|
+
|
|
7
|
+
## What Makes This A Good Local Communication Protocol
|
|
8
|
+
|
|
9
|
+
The target is a protocol that is:
|
|
10
|
+
|
|
11
|
+
- explicit: conversation, work, delivery, and external bindings are separate records
|
|
12
|
+
- durable: the broker is the only writer and local state is stored canonically
|
|
13
|
+
- addressable: actors, conversations, messages, invocations, flights, and deliveries all have stable IDs
|
|
14
|
+
- replayable: read models can be rebuilt from durable records and events
|
|
15
|
+
- observable: status, ownership, outputs, and failures are inspectable
|
|
16
|
+
- recoverable: broker restarts do not have to erase the story of what happened
|
|
17
|
+
- harness-agnostic: harness details live at endpoints and adapters, not in the protocol itself
|
|
18
|
+
|
|
19
|
+
If someone asks why this is better than terminal scrollback or ad hoc file sharing, that is the answer.
|
|
20
|
+
|
|
21
|
+
## Core Model
|
|
22
|
+
|
|
23
|
+
The protocol keeps a small set of nouns and gives each one a single job:
|
|
24
|
+
|
|
25
|
+
- `actor`: an identity in the system such as a person, helper, agent, system process, bridge, or device
|
|
26
|
+
- `agent`: a durable autonomous target with capabilities and one or more endpoints
|
|
27
|
+
- `conversation`: an addressable context boundary such as a channel, direct message, thread, or system conversation
|
|
28
|
+
- `message`: a human-readable conversation record
|
|
29
|
+
- `invocation`: an explicit request for work
|
|
30
|
+
- `flight`: the tracked lifecycle of one invocation
|
|
31
|
+
- `delivery`: a transport-specific fan-out intent for a message or invocation
|
|
32
|
+
- `binding`: a mapping from an OpenScout conversation to an external thread or channel
|
|
33
|
+
- `event`: an append-only fact emitted whenever one of the durable records changes
|
|
34
|
+
|
|
35
|
+
## Emerging Collaboration Model
|
|
36
|
+
|
|
37
|
+
The protocol package now also exports a collaboration vocabulary for the next layer above
|
|
38
|
+
messages and invocations. This is the first thin slice toward explicit human-agent and
|
|
39
|
+
agent-agent workflow tracking. It is intentionally small.
|
|
40
|
+
|
|
41
|
+
The current canonical collaboration kinds are:
|
|
42
|
+
|
|
43
|
+
- `question`: a lightweight information-seeking interaction with states such as `open`,
|
|
44
|
+
`answered`, `closed`, and `declined`
|
|
45
|
+
- `work_item`: a durable execution object with states such as `open`, `working`,
|
|
46
|
+
`waiting`, `review`, `done`, and `cancelled`
|
|
47
|
+
|
|
48
|
+
These are peers, not points on one severity ladder:
|
|
49
|
+
|
|
50
|
+
- a question can resolve directly
|
|
51
|
+
- a question can attach to a work item
|
|
52
|
+
- a question can spawn a work item
|
|
53
|
+
- a work item can accumulate progress, waiting conditions, and review state without
|
|
54
|
+
pretending it started as a question
|
|
55
|
+
|
|
56
|
+
Acceptance is modeled separately from workflow state so that a reply and satisfaction do
|
|
57
|
+
not collapse into one transition. A work item can be done without peer acceptance, and a
|
|
58
|
+
question can be answered without being closed yet.
|
|
59
|
+
|
|
60
|
+
The exported collaboration shapes are a protocol vocabulary for upcoming runtime and UI
|
|
61
|
+
work. They do not yet imply that the broker persists the full collaboration layer today.
|
|
62
|
+
See [docs/collaboration-workflows-v1.md](../../docs/collaboration-workflows-v1.md) for the
|
|
63
|
+
v1 model.
|
|
64
|
+
|
|
65
|
+
## Identity Model
|
|
66
|
+
|
|
67
|
+
The important distinction is between a helper and an agent:
|
|
68
|
+
|
|
69
|
+
- `person`: the actual human identity
|
|
70
|
+
- `helper`: a session-bound assistant working on behalf of a person
|
|
71
|
+
- `agent`: a durable autonomous player with its own identity and capabilities
|
|
72
|
+
- `system`: runtime-owned internal identity
|
|
73
|
+
- `bridge`: external platform adapter identity
|
|
74
|
+
- `device`: a concrete endpoint such as a native app client or speaker session
|
|
75
|
+
|
|
76
|
+
This lets a person work with a helper in Codex or Claude while still invoking real agents as first-class targets.
|
|
77
|
+
|
|
78
|
+
## Core Design Rules
|
|
79
|
+
|
|
80
|
+
1. A message is conversation.
|
|
81
|
+
2. An invocation is work.
|
|
82
|
+
3. A flight is the tracked lifecycle of that work.
|
|
83
|
+
4. Delivery is planned explicitly per target and transport.
|
|
84
|
+
5. Bindings map external channels into the same internal model.
|
|
85
|
+
6. Voice is metadata and transport, not the canonical message body.
|
|
86
|
+
7. The broker is the only canonical writer.
|
|
87
|
+
|
|
88
|
+
## Bootstrap And Startup
|
|
89
|
+
|
|
90
|
+
The intended machine lifecycle is:
|
|
91
|
+
|
|
92
|
+
1. `scout init` creates machine-local settings, a relay agent registry, and repo-local `.openscout/project.json` when needed.
|
|
93
|
+
2. The runtime installs a launch agent under `~/Library/LaunchAgents/` for the broker.
|
|
94
|
+
3. `launchd` keeps the broker process alive and restarts it if it exits.
|
|
95
|
+
4. Workspace discovery scans configured roots and repo-local manifests to map projects to agent identities.
|
|
96
|
+
5. Agents register endpoints that describe their harness, transport, session, cwd, and project root.
|
|
97
|
+
6. Surfaces such as the desktop shell, CLI, and relay compatibility layer talk to the broker instead of writing shared files directly.
|
|
98
|
+
|
|
99
|
+
## Conversation, Work, And Delivery
|
|
100
|
+
|
|
101
|
+
### Conversation
|
|
102
|
+
|
|
103
|
+
Conversation is human-readable history:
|
|
104
|
+
|
|
105
|
+
- channels
|
|
106
|
+
- direct messages
|
|
107
|
+
- group direct messages
|
|
108
|
+
- threads
|
|
109
|
+
- system conversations
|
|
110
|
+
|
|
111
|
+
Conversation state is designed for:
|
|
112
|
+
|
|
113
|
+
- visibility
|
|
114
|
+
- unread tracking
|
|
115
|
+
- mentions
|
|
116
|
+
- search
|
|
117
|
+
- auditability
|
|
118
|
+
|
|
119
|
+
### Invocation
|
|
120
|
+
|
|
121
|
+
Invocation is a request for action:
|
|
122
|
+
|
|
123
|
+
- consult an agent
|
|
124
|
+
- execute a task
|
|
125
|
+
- summarize state
|
|
126
|
+
- report status
|
|
127
|
+
- wake an agent
|
|
128
|
+
|
|
129
|
+
Invocations create flights. Flights stream lifecycle state separately from the chat surface they came from.
|
|
130
|
+
|
|
131
|
+
### Delivery
|
|
132
|
+
|
|
133
|
+
Each authored message exists once. Delivery fans out into typed intents.
|
|
134
|
+
|
|
135
|
+
The runtime plans deliveries separately for:
|
|
136
|
+
|
|
137
|
+
- conversation visibility
|
|
138
|
+
- notifications
|
|
139
|
+
- explicit invocations
|
|
140
|
+
- bridge outbound traffic
|
|
141
|
+
- speech playback
|
|
142
|
+
|
|
143
|
+
That means a single message can be visible to a channel, notify a mention, invoke an agent, and bridge outbound without duplicating the body.
|
|
144
|
+
|
|
145
|
+
## Lifecycle
|
|
146
|
+
|
|
147
|
+
```mermaid
|
|
148
|
+
sequenceDiagram
|
|
149
|
+
participant S as "Surface (CLI/Desktop/Bridge)"
|
|
150
|
+
participant B as "Broker"
|
|
151
|
+
participant DB as "Local Store"
|
|
152
|
+
participant H as "Agent Harness"
|
|
153
|
+
|
|
154
|
+
S->>B: Post message or invocation
|
|
155
|
+
B->>DB: Persist durable record
|
|
156
|
+
B->>DB: Append control event
|
|
157
|
+
B->>H: Plan delivery or wake target endpoint
|
|
158
|
+
H->>B: Flight update (queued/running/waiting/completed/failed)
|
|
159
|
+
B->>DB: Persist flight and delivery state
|
|
160
|
+
H->>B: Result message, artifact, or status
|
|
161
|
+
B->>DB: Persist output and append event
|
|
162
|
+
B-->>S: Stream updated conversation and work state
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The important part is the separation:
|
|
166
|
+
|
|
167
|
+
- messages make the conversation legible
|
|
168
|
+
- invocations make work explicit
|
|
169
|
+
- flights track execution without overloading chat
|
|
170
|
+
- deliveries make routing visible instead of implicit
|
|
171
|
+
|
|
172
|
+
## Storage Model
|
|
173
|
+
|
|
174
|
+
The runtime package owns the SQLite schema. The durable model is:
|
|
175
|
+
|
|
176
|
+
- `nodes`
|
|
177
|
+
- `actors`
|
|
178
|
+
- `agents`
|
|
179
|
+
- `agent_endpoints`
|
|
180
|
+
- `conversations`
|
|
181
|
+
- `conversation_members`
|
|
182
|
+
- `messages`
|
|
183
|
+
- `message_mentions`
|
|
184
|
+
- `message_attachments`
|
|
185
|
+
- `invocations`
|
|
186
|
+
- `flights`
|
|
187
|
+
- `bindings`
|
|
188
|
+
- `deliveries`
|
|
189
|
+
- `delivery_attempts`
|
|
190
|
+
- `events`
|
|
191
|
+
|
|
192
|
+
SQLite is the canonical store because it stays local and inspectable while handling append races, indexing, leases, retries, and subscriptions better than raw JSONL.
|
|
193
|
+
|
|
194
|
+
## Why Work Does Not Get Lost
|
|
195
|
+
|
|
196
|
+
The protocol is designed so that the system does not depend on terminal scrollback to remember what happened.
|
|
197
|
+
|
|
198
|
+
- messages are durable conversation records
|
|
199
|
+
- invocations are durable work requests
|
|
200
|
+
- flights are durable execution state
|
|
201
|
+
- deliveries and delivery attempts make routing and failures inspectable
|
|
202
|
+
- bindings make external channel mappings durable
|
|
203
|
+
- append-only events let read models be rebuilt
|
|
204
|
+
|
|
205
|
+
That does not require every surface to be smart. The broker owns the hard part, and the surfaces can recover by reading the canonical store.
|
|
206
|
+
|
|
207
|
+
## Harness-Agnostic By Design
|
|
208
|
+
|
|
209
|
+
OpenScout should not fork its protocol per harness.
|
|
210
|
+
|
|
211
|
+
- endpoint records describe `harness`, `transport`, `session_id`, `cwd`, and `project_root`
|
|
212
|
+
- the same invocation and flight model applies whether the endpoint is Claude, Codex, tmux, or a future harness
|
|
213
|
+
- harness-specific launch and wake behavior belongs in runtime adapters
|
|
214
|
+
- bridge integrations stay at the edge and map into the same durable model
|
|
215
|
+
|
|
216
|
+
This keeps the protocol stable even when the execution layer changes.
|
|
217
|
+
|
|
218
|
+
## Modalities
|
|
219
|
+
|
|
220
|
+
The protocol supports multiple modalities, but text remains canonical.
|
|
221
|
+
|
|
222
|
+
- HTTP: commands, admin, webhook intake
|
|
223
|
+
- WebSocket: subscriptions, streaming flight updates, typing/presence
|
|
224
|
+
- local socket: trusted local clients such as the native app and CLI
|
|
225
|
+
- bridges: Telegram, Discord, telecom adapters
|
|
226
|
+
- voice: transcripts, playback directives, media references
|
|
227
|
+
|
|
228
|
+
Raw media does not belong in the primary message log. The protocol stores transcript, speech, and attachment metadata while media transport stays on a dedicated transport.
|
|
229
|
+
|
|
230
|
+
## What The Protocol Is Not
|
|
231
|
+
|
|
232
|
+
OpenScout is intentionally not trying to be:
|
|
233
|
+
|
|
234
|
+
- a hosted chat service
|
|
235
|
+
- a workflow engine with mandatory plan bureaucracy
|
|
236
|
+
- a harness-specific control plane
|
|
237
|
+
- a replacement for Telegram, Discord, or tmux
|
|
238
|
+
|
|
239
|
+
It is the durable local substrate that makes agent communication legible, inspectable, and recoverable.
|
|
240
|
+
|
|
241
|
+
## Migration Direction
|
|
242
|
+
|
|
243
|
+
The control protocol replaces Relay as the core architecture.
|
|
244
|
+
|
|
245
|
+
Any remaining Relay-specific tools should be treated as surfaces or compatibility utilities, not as canonical storage or runtime paths.
|
package/dist/actors.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ActorKind, AdvertiseScope, AgentState, MetadataMap, ScoutId } from "./common.js";
|
|
2
|
+
export type AgentClass = "general" | "builder" | "reviewer" | "researcher" | "operator" | "bridge" | "system";
|
|
3
|
+
export type AgentCapability = "chat" | "invoke" | "deliver" | "speak" | "listen" | "bridge" | "summarize" | "review" | "execute";
|
|
4
|
+
export type AgentHarness = "codex" | "claude" | "native" | "worker" | "bridge" | "http";
|
|
5
|
+
export type WakePolicy = "manual" | "on_demand" | "keep_warm";
|
|
6
|
+
export interface ActorIdentity {
|
|
7
|
+
id: ScoutId;
|
|
8
|
+
kind: ActorKind;
|
|
9
|
+
displayName: string;
|
|
10
|
+
handle?: string;
|
|
11
|
+
labels?: string[];
|
|
12
|
+
metadata?: MetadataMap;
|
|
13
|
+
}
|
|
14
|
+
export interface AgentDefinition extends ActorIdentity {
|
|
15
|
+
kind: "agent";
|
|
16
|
+
definitionId: ScoutId;
|
|
17
|
+
nodeQualifier?: string;
|
|
18
|
+
workspaceQualifier?: string;
|
|
19
|
+
selector?: string;
|
|
20
|
+
defaultSelector?: string;
|
|
21
|
+
agentClass: AgentClass;
|
|
22
|
+
capabilities: AgentCapability[];
|
|
23
|
+
wakePolicy: WakePolicy;
|
|
24
|
+
homeNodeId: ScoutId;
|
|
25
|
+
authorityNodeId: ScoutId;
|
|
26
|
+
advertiseScope: AdvertiseScope;
|
|
27
|
+
ownerId?: ScoutId;
|
|
28
|
+
}
|
|
29
|
+
export interface HelperDefinition extends ActorIdentity {
|
|
30
|
+
kind: "helper";
|
|
31
|
+
ownerId: ScoutId;
|
|
32
|
+
nodeId: ScoutId;
|
|
33
|
+
engine: AgentHarness;
|
|
34
|
+
capabilities: AgentCapability[];
|
|
35
|
+
}
|
|
36
|
+
export interface AgentEndpoint {
|
|
37
|
+
id: ScoutId;
|
|
38
|
+
agentId: ScoutId;
|
|
39
|
+
nodeId: ScoutId;
|
|
40
|
+
harness: AgentHarness;
|
|
41
|
+
transport: "local_socket" | "http" | "websocket" | "claude_stream_json" | "codex_app_server" | "codex_exec" | "claude_resume" | "tmux";
|
|
42
|
+
state: AgentState;
|
|
43
|
+
address?: string;
|
|
44
|
+
sessionId?: string;
|
|
45
|
+
pane?: string;
|
|
46
|
+
cwd?: string;
|
|
47
|
+
projectRoot?: string;
|
|
48
|
+
metadata?: MetadataMap;
|
|
49
|
+
}
|
package/dist/actors.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ScoutId } from "./common.js";
|
|
2
|
+
export interface AgentSelector {
|
|
3
|
+
raw: string;
|
|
4
|
+
label: string;
|
|
5
|
+
definitionId: ScoutId;
|
|
6
|
+
nodeQualifier?: string;
|
|
7
|
+
workspaceQualifier?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AgentSelectorCandidate {
|
|
10
|
+
agentId: ScoutId;
|
|
11
|
+
definitionId?: ScoutId;
|
|
12
|
+
nodeQualifier?: string;
|
|
13
|
+
workspaceQualifier?: string;
|
|
14
|
+
aliases?: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function normalizeAgentSelectorSegment(value: string): string;
|
|
17
|
+
export declare function parseAgentSelector(value: string): AgentSelector | null;
|
|
18
|
+
export declare function formatAgentSelector(input: {
|
|
19
|
+
definitionId: ScoutId;
|
|
20
|
+
nodeQualifier?: string;
|
|
21
|
+
workspaceQualifier?: string;
|
|
22
|
+
}, options?: {
|
|
23
|
+
includeSigil?: boolean;
|
|
24
|
+
}): string;
|
|
25
|
+
export declare function extractAgentSelectors(text: string): AgentSelector[];
|
|
26
|
+
export declare function agentSelectorMatches(selector: AgentSelector, candidate: AgentSelectorCandidate): boolean;
|
|
27
|
+
export declare function resolveAgentSelector<T extends AgentSelectorCandidate>(selector: AgentSelector, candidates: T[]): T | null;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
function trimSelectorPrefix(value) {
|
|
2
|
+
return value
|
|
3
|
+
.trim()
|
|
4
|
+
.replace(/^@+/, "")
|
|
5
|
+
.replace(/[.,!?;:)\]]+$/, "");
|
|
6
|
+
}
|
|
7
|
+
export function normalizeAgentSelectorSegment(value) {
|
|
8
|
+
return value
|
|
9
|
+
.trim()
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/[^a-z0-9._/-]+/g, "-")
|
|
12
|
+
.replace(/[\\/]+/g, "-")
|
|
13
|
+
.replace(/^-+|-+$/g, "");
|
|
14
|
+
}
|
|
15
|
+
export function parseAgentSelector(value) {
|
|
16
|
+
const raw = trimSelectorPrefix(value);
|
|
17
|
+
if (!raw) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const [definitionAndNode, workspacePart] = raw.split("#", 2);
|
|
21
|
+
const [definitionPart, nodePart] = definitionAndNode.split("@", 2);
|
|
22
|
+
const definitionId = normalizeAgentSelectorSegment(definitionPart);
|
|
23
|
+
const nodeQualifier = nodePart ? normalizeAgentSelectorSegment(nodePart) : undefined;
|
|
24
|
+
const workspaceQualifier = workspacePart ? normalizeAgentSelectorSegment(workspacePart) : undefined;
|
|
25
|
+
if (!definitionId) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
raw,
|
|
30
|
+
label: formatAgentSelector({
|
|
31
|
+
definitionId,
|
|
32
|
+
nodeQualifier,
|
|
33
|
+
workspaceQualifier,
|
|
34
|
+
}),
|
|
35
|
+
definitionId,
|
|
36
|
+
...(nodeQualifier ? { nodeQualifier } : {}),
|
|
37
|
+
...(workspaceQualifier ? { workspaceQualifier } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function formatAgentSelector(input, options = {}) {
|
|
41
|
+
const definitionId = normalizeAgentSelectorSegment(input.definitionId);
|
|
42
|
+
if (!definitionId) {
|
|
43
|
+
return options.includeSigil === false ? "" : "@";
|
|
44
|
+
}
|
|
45
|
+
const nodeQualifier = input.nodeQualifier ? normalizeAgentSelectorSegment(input.nodeQualifier) : "";
|
|
46
|
+
const workspaceQualifier = input.workspaceQualifier ? normalizeAgentSelectorSegment(input.workspaceQualifier) : "";
|
|
47
|
+
const prefix = options.includeSigil === false ? "" : "@";
|
|
48
|
+
return [
|
|
49
|
+
`${prefix}${definitionId}`,
|
|
50
|
+
nodeQualifier ? `@${nodeQualifier}` : "",
|
|
51
|
+
workspaceQualifier ? `#${workspaceQualifier}` : "",
|
|
52
|
+
].join("");
|
|
53
|
+
}
|
|
54
|
+
export function extractAgentSelectors(text) {
|
|
55
|
+
const matches = Array.from(text.matchAll(/(^|\s)@([a-z0-9._/-]+(?:@[a-z0-9._/-]+)?(?:#[a-z0-9._/-]+)?)/gi));
|
|
56
|
+
const selectors = new Map();
|
|
57
|
+
for (const match of matches) {
|
|
58
|
+
const candidate = parseAgentSelector(match[2] ?? "");
|
|
59
|
+
if (!candidate) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
selectors.set(candidate.label, candidate);
|
|
63
|
+
}
|
|
64
|
+
return Array.from(selectors.values());
|
|
65
|
+
}
|
|
66
|
+
function candidateAliases(candidate) {
|
|
67
|
+
const definitionId = normalizeAgentSelectorSegment(candidate.definitionId || candidate.agentId);
|
|
68
|
+
const nodeQualifier = candidate.nodeQualifier ? normalizeAgentSelectorSegment(candidate.nodeQualifier) : undefined;
|
|
69
|
+
const workspaceQualifier = candidate.workspaceQualifier ? normalizeAgentSelectorSegment(candidate.workspaceQualifier) : undefined;
|
|
70
|
+
const aliases = [
|
|
71
|
+
definitionId,
|
|
72
|
+
formatAgentSelector({ definitionId, nodeQualifier }),
|
|
73
|
+
formatAgentSelector({ definitionId, workspaceQualifier }),
|
|
74
|
+
formatAgentSelector({ definitionId, nodeQualifier, workspaceQualifier }),
|
|
75
|
+
...((candidate.aliases ?? []).map((alias) => alias.trim()).filter(Boolean)),
|
|
76
|
+
];
|
|
77
|
+
return Array.from(new Set(aliases.map((alias) => trimSelectorPrefix(alias)).filter(Boolean)));
|
|
78
|
+
}
|
|
79
|
+
export function agentSelectorMatches(selector, candidate) {
|
|
80
|
+
const definitionId = normalizeAgentSelectorSegment(candidate.definitionId || candidate.agentId);
|
|
81
|
+
if (selector.definitionId !== definitionId) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
const nodeQualifier = candidate.nodeQualifier ? normalizeAgentSelectorSegment(candidate.nodeQualifier) : undefined;
|
|
85
|
+
if (selector.nodeQualifier && selector.nodeQualifier !== nodeQualifier) {
|
|
86
|
+
return candidateAliases(candidate).includes(trimSelectorPrefix(selector.label));
|
|
87
|
+
}
|
|
88
|
+
const workspaceQualifier = candidate.workspaceQualifier ? normalizeAgentSelectorSegment(candidate.workspaceQualifier) : undefined;
|
|
89
|
+
if (selector.workspaceQualifier && selector.workspaceQualifier !== workspaceQualifier) {
|
|
90
|
+
return candidateAliases(candidate).includes(trimSelectorPrefix(selector.label));
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
export function resolveAgentSelector(selector, candidates) {
|
|
95
|
+
const matches = candidates.filter((candidate) => agentSelectorMatches(selector, candidate));
|
|
96
|
+
if (matches.length === 1) {
|
|
97
|
+
return matches[0];
|
|
98
|
+
}
|
|
99
|
+
if (!selector.nodeQualifier && !selector.workspaceQualifier) {
|
|
100
|
+
return matches.find((candidate) => normalizeAgentSelectorSegment(candidate.agentId) === selector.definitionId) ?? matches[0] ?? null;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { MetadataMap, ScoutId } from "./common.js";
|
|
2
|
+
export type CollaborationKind = "question" | "work_item";
|
|
3
|
+
export type CollaborationPriority = "low" | "normal" | "high" | "urgent";
|
|
4
|
+
export type CollaborationAcceptanceState = "none" | "pending" | "accepted" | "reopened";
|
|
5
|
+
export type QuestionState = "open" | "answered" | "closed" | "declined";
|
|
6
|
+
export type WorkItemState = "open" | "working" | "waiting" | "review" | "done" | "cancelled";
|
|
7
|
+
export type CollaborationRelationKind = "blocks" | "spawns" | "relates_to" | "references";
|
|
8
|
+
export interface CollaborationRelation {
|
|
9
|
+
kind: CollaborationRelationKind;
|
|
10
|
+
targetId: ScoutId;
|
|
11
|
+
metadata?: MetadataMap;
|
|
12
|
+
}
|
|
13
|
+
export interface CollaborationWaitingOn {
|
|
14
|
+
kind: "actor" | "question" | "work_item" | "approval" | "artifact" | "condition";
|
|
15
|
+
label: string;
|
|
16
|
+
targetId?: ScoutId;
|
|
17
|
+
metadata?: MetadataMap;
|
|
18
|
+
}
|
|
19
|
+
export interface CollaborationProgress {
|
|
20
|
+
completedSteps?: number;
|
|
21
|
+
totalSteps?: number;
|
|
22
|
+
checkpoint?: string;
|
|
23
|
+
summary?: string;
|
|
24
|
+
percent?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface CollaborationRecordBase {
|
|
27
|
+
id: ScoutId;
|
|
28
|
+
kind: CollaborationKind;
|
|
29
|
+
title: string;
|
|
30
|
+
summary?: string;
|
|
31
|
+
createdById: ScoutId;
|
|
32
|
+
ownerId?: ScoutId;
|
|
33
|
+
nextMoveOwnerId?: ScoutId;
|
|
34
|
+
conversationId?: ScoutId;
|
|
35
|
+
parentId?: ScoutId;
|
|
36
|
+
priority?: CollaborationPriority;
|
|
37
|
+
labels?: string[];
|
|
38
|
+
relations?: CollaborationRelation[];
|
|
39
|
+
createdAt: number;
|
|
40
|
+
updatedAt: number;
|
|
41
|
+
metadata?: MetadataMap;
|
|
42
|
+
}
|
|
43
|
+
export interface QuestionRecord extends CollaborationRecordBase {
|
|
44
|
+
kind: "question";
|
|
45
|
+
state: QuestionState;
|
|
46
|
+
acceptanceState: CollaborationAcceptanceState;
|
|
47
|
+
askedById?: ScoutId;
|
|
48
|
+
askedOfId?: ScoutId;
|
|
49
|
+
answerMessageId?: ScoutId;
|
|
50
|
+
spawnedWorkItemId?: ScoutId;
|
|
51
|
+
closedAt?: number;
|
|
52
|
+
}
|
|
53
|
+
export interface WorkItemRecord extends CollaborationRecordBase {
|
|
54
|
+
kind: "work_item";
|
|
55
|
+
state: WorkItemState;
|
|
56
|
+
acceptanceState: CollaborationAcceptanceState;
|
|
57
|
+
requestedById?: ScoutId;
|
|
58
|
+
waitingOn?: CollaborationWaitingOn;
|
|
59
|
+
progress?: CollaborationProgress;
|
|
60
|
+
startedAt?: number;
|
|
61
|
+
reviewRequestedAt?: number;
|
|
62
|
+
completedAt?: number;
|
|
63
|
+
}
|
|
64
|
+
export type CollaborationRecord = QuestionRecord | WorkItemRecord;
|
|
65
|
+
export type CollaborationEventKind = "created" | "claimed" | "answered" | "accepted" | "reopened" | "waiting" | "progressed" | "handoff" | "review_requested" | "done" | "declined" | "cancelled";
|
|
66
|
+
export interface CollaborationEvent {
|
|
67
|
+
id: ScoutId;
|
|
68
|
+
recordId: ScoutId;
|
|
69
|
+
recordKind: CollaborationKind;
|
|
70
|
+
kind: CollaborationEventKind;
|
|
71
|
+
actorId: ScoutId;
|
|
72
|
+
at: number;
|
|
73
|
+
summary?: string;
|
|
74
|
+
metadata?: MetadataMap;
|
|
75
|
+
}
|
|
76
|
+
export declare function isQuestionTerminalState(state: QuestionState): boolean;
|
|
77
|
+
export declare function isWorkItemTerminalState(state: WorkItemState): boolean;
|
|
78
|
+
export declare function collaborationRequiresNextMoveOwner(record: CollaborationRecord): boolean;
|
|
79
|
+
export declare function collaborationRequiresOwner(record: CollaborationRecord): boolean;
|
|
80
|
+
export declare function collaborationRequiresWaitingOn(record: CollaborationRecord): boolean;
|
|
81
|
+
export declare function collaborationRequiresAcceptance(record: CollaborationRecord): boolean;
|
|
82
|
+
export declare function validateCollaborationRecord(record: CollaborationRecord): string[];
|
|
83
|
+
export declare function assertValidCollaborationRecord(record: CollaborationRecord): void;
|
|
84
|
+
export declare function validateCollaborationEvent(event: CollaborationEvent, record?: CollaborationRecord): string[];
|
|
85
|
+
export declare function assertValidCollaborationEvent(event: CollaborationEvent, record?: CollaborationRecord): void;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export function isQuestionTerminalState(state) {
|
|
2
|
+
return state === "closed" || state === "declined";
|
|
3
|
+
}
|
|
4
|
+
export function isWorkItemTerminalState(state) {
|
|
5
|
+
return state === "done" || state === "cancelled";
|
|
6
|
+
}
|
|
7
|
+
export function collaborationRequiresNextMoveOwner(record) {
|
|
8
|
+
if (record.kind === "question") {
|
|
9
|
+
return !isQuestionTerminalState(record.state);
|
|
10
|
+
}
|
|
11
|
+
return !isWorkItemTerminalState(record.state);
|
|
12
|
+
}
|
|
13
|
+
export function collaborationRequiresOwner(record) {
|
|
14
|
+
return record.kind === "work_item" && !isWorkItemTerminalState(record.state);
|
|
15
|
+
}
|
|
16
|
+
export function collaborationRequiresWaitingOn(record) {
|
|
17
|
+
return record.kind === "work_item" && record.state === "waiting";
|
|
18
|
+
}
|
|
19
|
+
export function collaborationRequiresAcceptance(record) {
|
|
20
|
+
if (record.acceptanceState === "none") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (record.kind === "question") {
|
|
24
|
+
return Boolean(record.askedById && record.askedOfId);
|
|
25
|
+
}
|
|
26
|
+
return Boolean(record.requestedById);
|
|
27
|
+
}
|
|
28
|
+
export function validateCollaborationRecord(record) {
|
|
29
|
+
const errors = [];
|
|
30
|
+
if (!record.id.trim()) {
|
|
31
|
+
errors.push("collaboration record id is required");
|
|
32
|
+
}
|
|
33
|
+
if (!record.title.trim()) {
|
|
34
|
+
errors.push("collaboration title is required");
|
|
35
|
+
}
|
|
36
|
+
if (!record.createdById.trim()) {
|
|
37
|
+
errors.push("createdById is required");
|
|
38
|
+
}
|
|
39
|
+
if (record.parentId && record.parentId === record.id) {
|
|
40
|
+
errors.push("parentId cannot reference the record itself");
|
|
41
|
+
}
|
|
42
|
+
if (record.createdAt > record.updatedAt) {
|
|
43
|
+
errors.push("updatedAt must be greater than or equal to createdAt");
|
|
44
|
+
}
|
|
45
|
+
if (collaborationRequiresOwner(record) && !record.ownerId) {
|
|
46
|
+
errors.push("non-terminal work items require ownerId");
|
|
47
|
+
}
|
|
48
|
+
if (collaborationRequiresNextMoveOwner(record) && !record.nextMoveOwnerId) {
|
|
49
|
+
errors.push("non-terminal collaboration records require nextMoveOwnerId");
|
|
50
|
+
}
|
|
51
|
+
if (record.kind === "work_item" && collaborationRequiresWaitingOn(record) && !record.waitingOn) {
|
|
52
|
+
errors.push("waiting work items require waitingOn");
|
|
53
|
+
}
|
|
54
|
+
if (record.kind === "question") {
|
|
55
|
+
if (record.spawnedWorkItemId && record.spawnedWorkItemId === record.id) {
|
|
56
|
+
errors.push("question spawnedWorkItemId cannot reference the question itself");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (record.waitingOn?.targetId && record.waitingOn.targetId === record.id) {
|
|
60
|
+
errors.push("waitingOn.targetId cannot reference the work item itself");
|
|
61
|
+
}
|
|
62
|
+
if (record.acceptanceState !== "none" && !collaborationRequiresAcceptance(record)) {
|
|
63
|
+
errors.push("acceptanceState requires the corresponding requester and reviewer identities");
|
|
64
|
+
}
|
|
65
|
+
return errors;
|
|
66
|
+
}
|
|
67
|
+
export function assertValidCollaborationRecord(record) {
|
|
68
|
+
const errors = validateCollaborationRecord(record);
|
|
69
|
+
if (errors.length > 0) {
|
|
70
|
+
throw new Error(errors.join("; "));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function validateCollaborationEvent(event, record) {
|
|
74
|
+
const errors = [];
|
|
75
|
+
if (!event.id.trim()) {
|
|
76
|
+
errors.push("collaboration event id is required");
|
|
77
|
+
}
|
|
78
|
+
if (!event.recordId.trim()) {
|
|
79
|
+
errors.push("collaboration event recordId is required");
|
|
80
|
+
}
|
|
81
|
+
if (!event.actorId.trim()) {
|
|
82
|
+
errors.push("collaboration event actorId is required");
|
|
83
|
+
}
|
|
84
|
+
if (record) {
|
|
85
|
+
if (record.id !== event.recordId) {
|
|
86
|
+
errors.push("collaboration event recordId does not match the target record");
|
|
87
|
+
}
|
|
88
|
+
if (record.kind !== event.recordKind) {
|
|
89
|
+
errors.push("collaboration event recordKind does not match the target record");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (event.kind === "answered" && event.recordKind !== "question") {
|
|
93
|
+
errors.push("answered events only apply to questions");
|
|
94
|
+
}
|
|
95
|
+
if (event.kind === "declined" && event.recordKind !== "question") {
|
|
96
|
+
errors.push("declined events only apply to questions");
|
|
97
|
+
}
|
|
98
|
+
if ((event.kind === "waiting"
|
|
99
|
+
|| event.kind === "progressed"
|
|
100
|
+
|| event.kind === "review_requested"
|
|
101
|
+
|| event.kind === "done"
|
|
102
|
+
|| event.kind === "cancelled")
|
|
103
|
+
&& event.recordKind !== "work_item") {
|
|
104
|
+
errors.push(`${event.kind} events only apply to work items`);
|
|
105
|
+
}
|
|
106
|
+
return errors;
|
|
107
|
+
}
|
|
108
|
+
export function assertValidCollaborationEvent(event, record) {
|
|
109
|
+
const errors = validateCollaborationEvent(event, record);
|
|
110
|
+
if (errors.length > 0) {
|
|
111
|
+
throw new Error(errors.join("; "));
|
|
112
|
+
}
|
|
113
|
+
}
|
package/dist/common.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type ScoutId = string;
|
|
2
|
+
export type ActorKind = "person" | "helper" | "agent" | "system" | "bridge" | "device";
|
|
3
|
+
export type AgentState = "offline" | "idle" | "active" | "waiting" | "degraded";
|
|
4
|
+
export type VisibilityScope = "private" | "workspace" | "public" | "system";
|
|
5
|
+
export type DeliveryStatus = "pending" | "leased" | "sent" | "acknowledged" | "failed" | "cancelled";
|
|
6
|
+
export type DeliveryPolicy = "best_effort" | "must_ack" | "durable" | "ephemeral";
|
|
7
|
+
export type DeliveryTargetKind = "participant" | "agent" | "bridge" | "device" | "voice_session" | "webhook";
|
|
8
|
+
export type DeliveryTransport = "local_socket" | "websocket" | "peer_broker" | "http" | "webhook" | "telegram" | "discord" | "sms" | "email" | "tts" | "native_voice" | "claude_stream_json" | "codex_app_server" | "codex_exec" | "claude_resume" | "tmux";
|
|
9
|
+
export type DeliveryReason = "conversation_visibility" | "direct_message" | "mention" | "thread_reply" | "invocation" | "bridge_outbound" | "speech";
|
|
10
|
+
export type AdvertiseScope = "local" | "mesh";
|
|
11
|
+
export type ShareMode = "local" | "summary" | "shared";
|
|
12
|
+
export interface MetadataMap {
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
package/dist/common.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { MetadataMap, ScoutId, ShareMode, VisibilityScope } from "./common.js";
|
|
2
|
+
export type ConversationKind = "channel" | "direct" | "group_direct" | "thread" | "system";
|
|
3
|
+
export interface ConversationDefinition {
|
|
4
|
+
id: ScoutId;
|
|
5
|
+
kind: ConversationKind;
|
|
6
|
+
title: string;
|
|
7
|
+
visibility: VisibilityScope;
|
|
8
|
+
shareMode: ShareMode;
|
|
9
|
+
authorityNodeId: ScoutId;
|
|
10
|
+
participantIds: ScoutId[];
|
|
11
|
+
topic?: string;
|
|
12
|
+
parentConversationId?: ScoutId;
|
|
13
|
+
messageId?: ScoutId;
|
|
14
|
+
metadata?: MetadataMap;
|
|
15
|
+
}
|
|
16
|
+
export interface ConversationBinding {
|
|
17
|
+
id: ScoutId;
|
|
18
|
+
conversationId: ScoutId;
|
|
19
|
+
platform: string;
|
|
20
|
+
mode: "inbound" | "outbound" | "bidirectional";
|
|
21
|
+
externalChannelId: string;
|
|
22
|
+
externalThreadId?: string;
|
|
23
|
+
metadata?: MetadataMap;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DeliveryPolicy, DeliveryReason, DeliveryStatus, DeliveryTargetKind, DeliveryTransport, MetadataMap, ScoutId } from "./common.js";
|
|
2
|
+
export interface DeliveryTarget {
|
|
3
|
+
id: ScoutId;
|
|
4
|
+
kind: DeliveryTargetKind;
|
|
5
|
+
transport: DeliveryTransport;
|
|
6
|
+
address?: string;
|
|
7
|
+
bindingId?: ScoutId;
|
|
8
|
+
metadata?: MetadataMap;
|
|
9
|
+
}
|
|
10
|
+
export interface DeliveryIntent {
|
|
11
|
+
id: ScoutId;
|
|
12
|
+
messageId?: ScoutId;
|
|
13
|
+
invocationId?: ScoutId;
|
|
14
|
+
targetId: ScoutId;
|
|
15
|
+
targetNodeId?: ScoutId;
|
|
16
|
+
targetKind: DeliveryTargetKind;
|
|
17
|
+
transport: DeliveryTransport;
|
|
18
|
+
reason: DeliveryReason;
|
|
19
|
+
policy: DeliveryPolicy;
|
|
20
|
+
status: DeliveryStatus;
|
|
21
|
+
bindingId?: ScoutId;
|
|
22
|
+
leaseOwner?: string;
|
|
23
|
+
leaseExpiresAt?: number;
|
|
24
|
+
metadata?: MetadataMap;
|
|
25
|
+
}
|
|
26
|
+
export interface DeliveryAttempt {
|
|
27
|
+
id: ScoutId;
|
|
28
|
+
deliveryId: ScoutId;
|
|
29
|
+
attempt: number;
|
|
30
|
+
status: "sent" | "acknowledged" | "failed";
|
|
31
|
+
error?: string;
|
|
32
|
+
externalRef?: string;
|
|
33
|
+
createdAt: number;
|
|
34
|
+
metadata?: MetadataMap;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
export interface ControlEventBase<K extends string, P> {
|
|
10
|
+
id: ScoutId;
|
|
11
|
+
kind: K;
|
|
12
|
+
ts: number;
|
|
13
|
+
actorId: ScoutId;
|
|
14
|
+
nodeId?: ScoutId;
|
|
15
|
+
payload: P;
|
|
16
|
+
}
|
|
17
|
+
export type NodeUpsertedEvent = ControlEventBase<"node.upserted", {
|
|
18
|
+
node: NodeDefinition;
|
|
19
|
+
}>;
|
|
20
|
+
export type ActorRegisteredEvent = ControlEventBase<"actor.registered", {
|
|
21
|
+
actor: ActorIdentity;
|
|
22
|
+
}>;
|
|
23
|
+
export type AgentRegisteredEvent = ControlEventBase<"agent.registered", {
|
|
24
|
+
agent: AgentDefinition;
|
|
25
|
+
}>;
|
|
26
|
+
export type AgentEndpointUpsertedEvent = ControlEventBase<"agent.endpoint.upserted", {
|
|
27
|
+
endpoint: AgentEndpoint;
|
|
28
|
+
}>;
|
|
29
|
+
export type ConversationUpsertedEvent = ControlEventBase<"conversation.upserted", {
|
|
30
|
+
conversation: ConversationDefinition;
|
|
31
|
+
}>;
|
|
32
|
+
export type BindingUpsertedEvent = ControlEventBase<"binding.upserted", {
|
|
33
|
+
binding: ConversationBinding;
|
|
34
|
+
}>;
|
|
35
|
+
export type MessagePostedEvent = ControlEventBase<"message.posted", {
|
|
36
|
+
message: MessageRecord;
|
|
37
|
+
}>;
|
|
38
|
+
export type InvocationRequestedEvent = ControlEventBase<"invocation.requested", {
|
|
39
|
+
invocation: InvocationRequest;
|
|
40
|
+
}>;
|
|
41
|
+
export type FlightUpdatedEvent = ControlEventBase<"flight.updated", {
|
|
42
|
+
flight: FlightRecord;
|
|
43
|
+
}>;
|
|
44
|
+
export type DeliveryPlannedEvent = ControlEventBase<"delivery.planned", {
|
|
45
|
+
delivery: DeliveryIntent;
|
|
46
|
+
}>;
|
|
47
|
+
export type DeliveryAttemptedEvent = ControlEventBase<"delivery.attempted", {
|
|
48
|
+
attempt: DeliveryAttempt;
|
|
49
|
+
}>;
|
|
50
|
+
export type CollaborationUpsertedEvent = ControlEventBase<"collaboration.upserted", {
|
|
51
|
+
record: CollaborationRecord;
|
|
52
|
+
}>;
|
|
53
|
+
export type CollaborationEventAppendedEvent = ControlEventBase<"collaboration.event.appended", {
|
|
54
|
+
event: CollaborationEvent;
|
|
55
|
+
}>;
|
|
56
|
+
export type ControlEvent = NodeUpsertedEvent | ActorRegisteredEvent | AgentRegisteredEvent | AgentEndpointUpsertedEvent | ConversationUpsertedEvent | BindingUpsertedEvent | MessagePostedEvent | InvocationRequestedEvent | FlightUpdatedEvent | DeliveryPlannedEvent | DeliveryAttemptedEvent | CollaborationUpsertedEvent | CollaborationEventAppendedEvent;
|
package/dist/events.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./common.js";
|
|
2
|
+
export * from "./actors.js";
|
|
3
|
+
export * from "./agent-selectors.js";
|
|
4
|
+
export * from "./mesh.js";
|
|
5
|
+
export * from "./conversations.js";
|
|
6
|
+
export * from "./collaboration.js";
|
|
7
|
+
export * from "./messages.js";
|
|
8
|
+
export * from "./invocations.js";
|
|
9
|
+
export * from "./deliveries.js";
|
|
10
|
+
export * from "./transports.js";
|
|
11
|
+
export * from "./events.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./common.js";
|
|
2
|
+
export * from "./actors.js";
|
|
3
|
+
export * from "./agent-selectors.js";
|
|
4
|
+
export * from "./mesh.js";
|
|
5
|
+
export * from "./conversations.js";
|
|
6
|
+
export * from "./collaboration.js";
|
|
7
|
+
export * from "./messages.js";
|
|
8
|
+
export * from "./invocations.js";
|
|
9
|
+
export * from "./deliveries.js";
|
|
10
|
+
export * from "./transports.js";
|
|
11
|
+
export * from "./events.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AgentHarness } from "./actors.js";
|
|
2
|
+
import type { MetadataMap, ScoutId } from "./common.js";
|
|
3
|
+
export type InvocationAction = "consult" | "execute" | "summarize" | "status" | "wake";
|
|
4
|
+
export type FlightState = "queued" | "waking" | "running" | "waiting" | "completed" | "failed" | "cancelled";
|
|
5
|
+
export interface InvocationExecutionPreference {
|
|
6
|
+
harness?: AgentHarness;
|
|
7
|
+
}
|
|
8
|
+
export interface InvocationRequest {
|
|
9
|
+
id: ScoutId;
|
|
10
|
+
requesterId: ScoutId;
|
|
11
|
+
requesterNodeId: ScoutId;
|
|
12
|
+
targetAgentId: ScoutId;
|
|
13
|
+
targetNodeId?: ScoutId;
|
|
14
|
+
action: InvocationAction;
|
|
15
|
+
task: string;
|
|
16
|
+
conversationId?: ScoutId;
|
|
17
|
+
messageId?: ScoutId;
|
|
18
|
+
context?: MetadataMap;
|
|
19
|
+
execution?: InvocationExecutionPreference;
|
|
20
|
+
ensureAwake: boolean;
|
|
21
|
+
stream: boolean;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
createdAt: number;
|
|
24
|
+
metadata?: MetadataMap;
|
|
25
|
+
}
|
|
26
|
+
export interface FlightRecord {
|
|
27
|
+
id: ScoutId;
|
|
28
|
+
invocationId: ScoutId;
|
|
29
|
+
requesterId: ScoutId;
|
|
30
|
+
targetAgentId: ScoutId;
|
|
31
|
+
state: FlightState;
|
|
32
|
+
summary?: string;
|
|
33
|
+
output?: string;
|
|
34
|
+
error?: string;
|
|
35
|
+
startedAt?: number;
|
|
36
|
+
completedAt?: number;
|
|
37
|
+
metadata?: MetadataMap;
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/mesh.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AdvertiseScope, MetadataMap, ScoutId } from "./common.js";
|
|
2
|
+
export interface NodeDefinition {
|
|
3
|
+
id: ScoutId;
|
|
4
|
+
meshId: ScoutId;
|
|
5
|
+
name: string;
|
|
6
|
+
hostName?: string;
|
|
7
|
+
advertiseScope: AdvertiseScope;
|
|
8
|
+
brokerUrl?: string;
|
|
9
|
+
tailnetName?: string;
|
|
10
|
+
capabilities?: string[];
|
|
11
|
+
labels?: string[];
|
|
12
|
+
metadata?: MetadataMap;
|
|
13
|
+
lastSeenAt?: number;
|
|
14
|
+
registeredAt: number;
|
|
15
|
+
}
|
package/dist/mesh.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { DeliveryPolicy, DeliveryReason, MetadataMap, ScoutId, VisibilityScope } from "./common.js";
|
|
2
|
+
export type MessageClass = "agent" | "log" | "system" | "status" | "artifact";
|
|
3
|
+
export interface MessageSpeechDirective {
|
|
4
|
+
text: string;
|
|
5
|
+
voice?: string;
|
|
6
|
+
interruptible?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface MessageAttachment {
|
|
9
|
+
id: ScoutId;
|
|
10
|
+
mediaType: string;
|
|
11
|
+
fileName?: string;
|
|
12
|
+
blobKey?: string;
|
|
13
|
+
url?: string;
|
|
14
|
+
metadata?: MetadataMap;
|
|
15
|
+
}
|
|
16
|
+
export interface MessageMention {
|
|
17
|
+
actorId: ScoutId;
|
|
18
|
+
label?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface MessageAudience {
|
|
21
|
+
visibleTo?: ScoutId[];
|
|
22
|
+
notify?: ScoutId[];
|
|
23
|
+
invoke?: ScoutId[];
|
|
24
|
+
reason?: DeliveryReason;
|
|
25
|
+
}
|
|
26
|
+
export interface MessageRecord {
|
|
27
|
+
id: ScoutId;
|
|
28
|
+
conversationId: ScoutId;
|
|
29
|
+
actorId: ScoutId;
|
|
30
|
+
originNodeId: ScoutId;
|
|
31
|
+
class: MessageClass;
|
|
32
|
+
body: string;
|
|
33
|
+
replyToMessageId?: ScoutId;
|
|
34
|
+
threadConversationId?: ScoutId;
|
|
35
|
+
mentions?: MessageMention[];
|
|
36
|
+
attachments?: MessageAttachment[];
|
|
37
|
+
speech?: MessageSpeechDirective;
|
|
38
|
+
audience?: MessageAudience;
|
|
39
|
+
visibility: VisibilityScope;
|
|
40
|
+
policy: DeliveryPolicy;
|
|
41
|
+
createdAt: number;
|
|
42
|
+
metadata?: MetadataMap;
|
|
43
|
+
}
|
package/dist/messages.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { DeliveryTransport, MetadataMap, ScoutId } from "./common.js";
|
|
2
|
+
import type { AgentDefinition, AgentEndpoint, ActorIdentity } from "./actors.js";
|
|
3
|
+
import type { ConversationBinding, ConversationDefinition } from "./conversations.js";
|
|
4
|
+
import type { InvocationRequest } from "./invocations.js";
|
|
5
|
+
import type { NodeDefinition } from "./mesh.js";
|
|
6
|
+
import type { MessageRecord } from "./messages.js";
|
|
7
|
+
import type { CollaborationEvent, CollaborationRecord } from "./collaboration.js";
|
|
8
|
+
export interface SubscriptionRequest {
|
|
9
|
+
actorId: ScoutId;
|
|
10
|
+
conversationIds?: ScoutId[];
|
|
11
|
+
flightIds?: ScoutId[];
|
|
12
|
+
eventKinds?: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface PostMessageCommand {
|
|
15
|
+
kind: "conversation.post";
|
|
16
|
+
message: MessageRecord;
|
|
17
|
+
}
|
|
18
|
+
export interface InvokeAgentCommand {
|
|
19
|
+
kind: "agent.invoke";
|
|
20
|
+
invocation: InvocationRequest;
|
|
21
|
+
}
|
|
22
|
+
export interface EnsureAwakeCommand {
|
|
23
|
+
kind: "agent.ensure_awake";
|
|
24
|
+
agentId: ScoutId;
|
|
25
|
+
requesterId: ScoutId;
|
|
26
|
+
reason: string;
|
|
27
|
+
metadata?: MetadataMap;
|
|
28
|
+
}
|
|
29
|
+
export interface SubscribeCommand {
|
|
30
|
+
kind: "stream.subscribe";
|
|
31
|
+
subscription: SubscriptionRequest;
|
|
32
|
+
transport: Extract<DeliveryTransport, "local_socket" | "websocket">;
|
|
33
|
+
}
|
|
34
|
+
export interface NodeUpsertCommand {
|
|
35
|
+
kind: "node.upsert";
|
|
36
|
+
node: NodeDefinition;
|
|
37
|
+
}
|
|
38
|
+
export interface ActorUpsertCommand {
|
|
39
|
+
kind: "actor.upsert";
|
|
40
|
+
actor: ActorIdentity;
|
|
41
|
+
}
|
|
42
|
+
export interface AgentUpsertCommand {
|
|
43
|
+
kind: "agent.upsert";
|
|
44
|
+
agent: AgentDefinition;
|
|
45
|
+
}
|
|
46
|
+
export interface AgentEndpointUpsertCommand {
|
|
47
|
+
kind: "agent.endpoint.upsert";
|
|
48
|
+
endpoint: AgentEndpoint;
|
|
49
|
+
}
|
|
50
|
+
export interface ConversationUpsertCommand {
|
|
51
|
+
kind: "conversation.upsert";
|
|
52
|
+
conversation: ConversationDefinition;
|
|
53
|
+
}
|
|
54
|
+
export interface BindingUpsertCommand {
|
|
55
|
+
kind: "binding.upsert";
|
|
56
|
+
binding: ConversationBinding;
|
|
57
|
+
}
|
|
58
|
+
export interface CollaborationUpsertCommand {
|
|
59
|
+
kind: "collaboration.upsert";
|
|
60
|
+
record: CollaborationRecord;
|
|
61
|
+
}
|
|
62
|
+
export interface CollaborationEventAppendCommand {
|
|
63
|
+
kind: "collaboration.event.append";
|
|
64
|
+
event: CollaborationEvent;
|
|
65
|
+
}
|
|
66
|
+
export type ControlCommand = NodeUpsertCommand | ActorUpsertCommand | AgentUpsertCommand | AgentEndpointUpsertCommand | ConversationUpsertCommand | BindingUpsertCommand | CollaborationUpsertCommand | CollaborationEventAppendCommand | PostMessageCommand | InvokeAgentCommand | EnsureAwakeCommand | SubscribeCommand;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openscout/protocol",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed protocol for the OpenScout local control plane",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"bun": "./src/index.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"types": "./src/index.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
20
|
+
"check": "tsc --noEmit -p tsconfig.json"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"typescript": "^5"
|
|
27
|
+
}
|
|
28
|
+
}
|