@openscout/protocol 0.1.0 → 0.2.2
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 +1 -1
- package/dist/agent-address.d.ts +1 -0
- package/dist/agent-address.js +1 -0
- package/dist/agent-identity.d.ts +69 -0
- package/dist/agent-identity.js +369 -0
- package/dist/agent-selectors.d.ts +1 -27
- package/dist/agent-selectors.js +1 -103
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/relay-agent-card.d.ts +38 -0
- package/dist/relay-agent-card.js +34 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ This lets a person work with a helper in Codex or Claude while still invoking re
|
|
|
89
89
|
|
|
90
90
|
The intended machine lifecycle is:
|
|
91
91
|
|
|
92
|
-
1. `scout
|
|
92
|
+
1. `scout setup` creates machine-local settings, a relay agent registry, and repo-local `.openscout/project.json` when needed.
|
|
93
93
|
2. The runtime installs a launch agent under `~/Library/LaunchAgents/` for the broker.
|
|
94
94
|
3. `launchd` keeps the broker process alive and restarts it if it exits.
|
|
95
95
|
4. Workspace discovery scans configured roots and repo-local manifests to map projects to agent identities.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./agent-identity.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./agent-identity.js";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ScoutId } from "./common.js";
|
|
2
|
+
export type AgentIdentityDimension = "workspace" | "profile" | "harness" | "node";
|
|
3
|
+
export interface AgentIdentity {
|
|
4
|
+
raw: string;
|
|
5
|
+
label: string;
|
|
6
|
+
definitionId: ScoutId;
|
|
7
|
+
nodeQualifier?: string;
|
|
8
|
+
workspaceQualifier?: string;
|
|
9
|
+
profile?: string;
|
|
10
|
+
harness?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AgentIdentityCandidate {
|
|
13
|
+
agentId: ScoutId;
|
|
14
|
+
definitionId?: ScoutId;
|
|
15
|
+
nodeQualifier?: string;
|
|
16
|
+
workspaceQualifier?: string;
|
|
17
|
+
profile?: string;
|
|
18
|
+
harness?: string;
|
|
19
|
+
aliases?: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface AgentIdentityInput {
|
|
22
|
+
definitionId: ScoutId;
|
|
23
|
+
nodeQualifier?: string;
|
|
24
|
+
workspaceQualifier?: string;
|
|
25
|
+
profile?: string;
|
|
26
|
+
harness?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface AgentIdentityAlias {
|
|
29
|
+
alias: string;
|
|
30
|
+
target: AgentIdentityInput;
|
|
31
|
+
}
|
|
32
|
+
export declare function normalizeAgentIdentitySegment(value: string): string;
|
|
33
|
+
export declare const normalizeAgentSelectorSegment: typeof normalizeAgentIdentitySegment;
|
|
34
|
+
export declare function parseAgentIdentity(value: string): AgentIdentity | null;
|
|
35
|
+
export declare function formatAgentIdentity(input: AgentIdentityInput, options?: {
|
|
36
|
+
includeSigil?: boolean;
|
|
37
|
+
}): string;
|
|
38
|
+
export declare function constructAgentIdentity(input: AgentIdentityInput, options?: {
|
|
39
|
+
raw?: string;
|
|
40
|
+
}): AgentIdentity | null;
|
|
41
|
+
export declare function extractAgentIdentities(text: string): AgentIdentity[];
|
|
42
|
+
export declare function agentIdentityMatches(identity: AgentIdentity, candidate: AgentIdentityCandidate): boolean;
|
|
43
|
+
export declare function resolveAgentIdentity<T extends AgentIdentityCandidate>(identity: AgentIdentity, candidates: T[]): T | null;
|
|
44
|
+
export declare function constructAgentAlias(input: {
|
|
45
|
+
alias: string;
|
|
46
|
+
target: AgentIdentityInput;
|
|
47
|
+
}): AgentIdentityAlias | null;
|
|
48
|
+
export declare function formatAgentAlias(alias: AgentIdentityAlias, options?: {
|
|
49
|
+
includeSigil?: boolean;
|
|
50
|
+
}): string;
|
|
51
|
+
export declare function resolveAgentAlias(value: string, aliases: AgentIdentityAlias[]): AgentIdentity | null;
|
|
52
|
+
export declare function formatMinimalAgentIdentity<T extends AgentIdentityCandidate>(candidate: T, candidates: T[], options?: {
|
|
53
|
+
includeSigil?: boolean;
|
|
54
|
+
}): string;
|
|
55
|
+
export type AgentAddress = AgentIdentity;
|
|
56
|
+
export type AgentAddressCandidate = AgentIdentityCandidate;
|
|
57
|
+
export type AgentSelector = AgentIdentity;
|
|
58
|
+
export type AgentSelectorCandidate = AgentIdentityCandidate;
|
|
59
|
+
export declare const parseAgentAddress: typeof parseAgentIdentity;
|
|
60
|
+
export declare const formatAgentAddress: typeof formatAgentIdentity;
|
|
61
|
+
export declare const constructAgentAddress: typeof constructAgentIdentity;
|
|
62
|
+
export declare const extractAgentAddresses: typeof extractAgentIdentities;
|
|
63
|
+
export declare const agentAddressMatches: typeof agentIdentityMatches;
|
|
64
|
+
export declare const resolveAgentAddress: typeof resolveAgentIdentity;
|
|
65
|
+
export declare const parseAgentSelector: typeof parseAgentIdentity;
|
|
66
|
+
export declare const formatAgentSelector: typeof formatAgentIdentity;
|
|
67
|
+
export declare const extractAgentSelectors: typeof extractAgentIdentities;
|
|
68
|
+
export declare const agentSelectorMatches: typeof agentIdentityMatches;
|
|
69
|
+
export declare const resolveAgentSelector: typeof resolveAgentIdentity;
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
const DIMENSION_ALIASES = {
|
|
2
|
+
workspace: "workspace",
|
|
3
|
+
worktree: "workspace",
|
|
4
|
+
branch: "workspace",
|
|
5
|
+
profile: "profile",
|
|
6
|
+
persona: "profile",
|
|
7
|
+
harness: "harness",
|
|
8
|
+
runtime: "harness",
|
|
9
|
+
node: "node",
|
|
10
|
+
host: "node",
|
|
11
|
+
};
|
|
12
|
+
function trimIdentityPrefix(value) {
|
|
13
|
+
return value
|
|
14
|
+
.trim()
|
|
15
|
+
.replace(/^@+/, "")
|
|
16
|
+
.replace(/[.,!?;)\]]+$/, "");
|
|
17
|
+
}
|
|
18
|
+
export function normalizeAgentIdentitySegment(value) {
|
|
19
|
+
return value
|
|
20
|
+
.trim()
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
23
|
+
.replace(/-+/g, "-")
|
|
24
|
+
.replace(/^-+|-+$/g, "");
|
|
25
|
+
}
|
|
26
|
+
export const normalizeAgentSelectorSegment = normalizeAgentIdentitySegment;
|
|
27
|
+
function normalizeDimensionKey(value) {
|
|
28
|
+
return DIMENSION_ALIASES[normalizeAgentIdentitySegment(value)] ?? null;
|
|
29
|
+
}
|
|
30
|
+
function canonicalizeIdentity(input) {
|
|
31
|
+
const definitionId = normalizeAgentIdentitySegment(input.definitionId);
|
|
32
|
+
const workspaceQualifier = input.workspaceQualifier
|
|
33
|
+
? normalizeAgentIdentitySegment(input.workspaceQualifier)
|
|
34
|
+
: undefined;
|
|
35
|
+
const profile = input.profile ? normalizeAgentIdentitySegment(input.profile) : undefined;
|
|
36
|
+
const harness = input.harness ? normalizeAgentIdentitySegment(input.harness) : undefined;
|
|
37
|
+
const nodeQualifier = input.nodeQualifier
|
|
38
|
+
? normalizeAgentIdentitySegment(input.nodeQualifier)
|
|
39
|
+
: undefined;
|
|
40
|
+
if (!definitionId) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
definitionId,
|
|
45
|
+
...(workspaceQualifier ? { workspaceQualifier } : {}),
|
|
46
|
+
...(profile ? { profile } : {}),
|
|
47
|
+
...(harness ? { harness } : {}),
|
|
48
|
+
...(nodeQualifier ? { nodeQualifier } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function parseSegmentedIdentity(raw) {
|
|
52
|
+
const parts = raw.split(".").filter(Boolean);
|
|
53
|
+
const definitionPart = parts.shift();
|
|
54
|
+
if (!definitionPart) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const next = {
|
|
58
|
+
definitionId: definitionPart,
|
|
59
|
+
};
|
|
60
|
+
let sawPositionalWorkspace = false;
|
|
61
|
+
for (const part of parts) {
|
|
62
|
+
if (part.includes(":")) {
|
|
63
|
+
const [rawKey, rawValue = ""] = part.split(":", 2);
|
|
64
|
+
const key = normalizeDimensionKey(rawKey);
|
|
65
|
+
const value = normalizeAgentIdentitySegment(rawValue);
|
|
66
|
+
if (!key || !value) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
if (key === "workspace") {
|
|
70
|
+
next.workspaceQualifier = value;
|
|
71
|
+
}
|
|
72
|
+
else if (key === "profile") {
|
|
73
|
+
next.profile = value;
|
|
74
|
+
}
|
|
75
|
+
else if (key === "harness") {
|
|
76
|
+
next.harness = value;
|
|
77
|
+
}
|
|
78
|
+
else if (key === "node") {
|
|
79
|
+
next.nodeQualifier = value;
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (sawPositionalWorkspace) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const value = normalizeAgentIdentitySegment(part);
|
|
87
|
+
if (!value) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
next.workspaceQualifier = value;
|
|
91
|
+
sawPositionalWorkspace = true;
|
|
92
|
+
}
|
|
93
|
+
return constructAgentIdentity(next, { raw });
|
|
94
|
+
}
|
|
95
|
+
export function parseAgentIdentity(value) {
|
|
96
|
+
const raw = trimIdentityPrefix(value);
|
|
97
|
+
if (!raw) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
if (raw.includes("@") || raw.includes("#")) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return parseSegmentedIdentity(raw);
|
|
104
|
+
}
|
|
105
|
+
export function formatAgentIdentity(input, options = {}) {
|
|
106
|
+
const canonical = canonicalizeIdentity(input);
|
|
107
|
+
if (!canonical) {
|
|
108
|
+
return options.includeSigil === false ? "" : "@";
|
|
109
|
+
}
|
|
110
|
+
const prefix = options.includeSigil === false ? "" : "@";
|
|
111
|
+
const segments = [`${prefix}${canonical.definitionId}`];
|
|
112
|
+
if (canonical.workspaceQualifier) {
|
|
113
|
+
segments.push(canonical.workspaceQualifier);
|
|
114
|
+
}
|
|
115
|
+
if (canonical.profile) {
|
|
116
|
+
segments.push(`profile:${canonical.profile}`);
|
|
117
|
+
}
|
|
118
|
+
if (canonical.harness) {
|
|
119
|
+
segments.push(`harness:${canonical.harness}`);
|
|
120
|
+
}
|
|
121
|
+
if (canonical.nodeQualifier) {
|
|
122
|
+
segments.push(`node:${canonical.nodeQualifier}`);
|
|
123
|
+
}
|
|
124
|
+
return segments.join(".");
|
|
125
|
+
}
|
|
126
|
+
export function constructAgentIdentity(input, options = {}) {
|
|
127
|
+
const canonical = canonicalizeIdentity(input);
|
|
128
|
+
if (!canonical) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
raw: options.raw ?? formatAgentIdentity(canonical, { includeSigil: false }),
|
|
133
|
+
label: formatAgentIdentity(canonical),
|
|
134
|
+
definitionId: canonical.definitionId,
|
|
135
|
+
...(canonical.nodeQualifier ? { nodeQualifier: canonical.nodeQualifier } : {}),
|
|
136
|
+
...(canonical.workspaceQualifier ? { workspaceQualifier: canonical.workspaceQualifier } : {}),
|
|
137
|
+
...(canonical.profile ? { profile: canonical.profile } : {}),
|
|
138
|
+
...(canonical.harness ? { harness: canonical.harness } : {}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function extractAgentIdentities(text) {
|
|
142
|
+
const matches = Array.from(text.matchAll(/(^|\s)@([a-z0-9][a-z0-9._/:-]*)/gi));
|
|
143
|
+
const identities = new Map();
|
|
144
|
+
for (const match of matches) {
|
|
145
|
+
const candidate = parseAgentIdentity(match[2] ?? "");
|
|
146
|
+
if (!candidate) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
identities.set(candidate.label, candidate);
|
|
150
|
+
}
|
|
151
|
+
return Array.from(identities.values());
|
|
152
|
+
}
|
|
153
|
+
function candidateAliases(candidate) {
|
|
154
|
+
const canonical = canonicalizeIdentity({
|
|
155
|
+
definitionId: candidate.definitionId || candidate.agentId,
|
|
156
|
+
nodeQualifier: candidate.nodeQualifier,
|
|
157
|
+
workspaceQualifier: candidate.workspaceQualifier,
|
|
158
|
+
profile: candidate.profile,
|
|
159
|
+
harness: candidate.harness,
|
|
160
|
+
});
|
|
161
|
+
if (!canonical) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const aliases = [
|
|
165
|
+
canonical.definitionId,
|
|
166
|
+
formatAgentIdentity(canonical),
|
|
167
|
+
canonical.workspaceQualifier
|
|
168
|
+
? formatAgentIdentity({ definitionId: canonical.definitionId, workspaceQualifier: canonical.workspaceQualifier })
|
|
169
|
+
: "",
|
|
170
|
+
canonical.profile
|
|
171
|
+
? formatAgentIdentity({ definitionId: canonical.definitionId, profile: canonical.profile })
|
|
172
|
+
: "",
|
|
173
|
+
canonical.harness
|
|
174
|
+
? formatAgentIdentity({ definitionId: canonical.definitionId, harness: canonical.harness })
|
|
175
|
+
: "",
|
|
176
|
+
canonical.nodeQualifier
|
|
177
|
+
? formatAgentIdentity({ definitionId: canonical.definitionId, nodeQualifier: canonical.nodeQualifier })
|
|
178
|
+
: "",
|
|
179
|
+
...((candidate.aliases ?? []).map((alias) => alias.trim()).filter(Boolean)),
|
|
180
|
+
];
|
|
181
|
+
return Array.from(new Set(aliases.map((alias) => trimIdentityPrefix(alias)).filter(Boolean)));
|
|
182
|
+
}
|
|
183
|
+
function explicitCandidateAliases(candidate) {
|
|
184
|
+
return Array.from(new Set((candidate.aliases ?? [])
|
|
185
|
+
.map((alias) => normalizeAgentIdentitySegment(trimIdentityPrefix(alias)))
|
|
186
|
+
.filter(Boolean)));
|
|
187
|
+
}
|
|
188
|
+
function canonicalizeAliasValue(value) {
|
|
189
|
+
const parsed = parseAgentIdentity(value.startsWith("@") ? value : `@${value}`);
|
|
190
|
+
if (parsed) {
|
|
191
|
+
return trimIdentityPrefix(parsed.label);
|
|
192
|
+
}
|
|
193
|
+
return normalizeAgentIdentitySegment(value);
|
|
194
|
+
}
|
|
195
|
+
function identityAliasKeys(identity) {
|
|
196
|
+
return Array.from(new Set([
|
|
197
|
+
trimIdentityPrefix(identity.label),
|
|
198
|
+
trimIdentityPrefix(identity.raw),
|
|
199
|
+
trimIdentityPrefix(formatAgentIdentity(identity, { includeSigil: false })),
|
|
200
|
+
].filter(Boolean)));
|
|
201
|
+
}
|
|
202
|
+
function candidateCanonicalIdentity(candidate) {
|
|
203
|
+
return constructAgentIdentity({
|
|
204
|
+
definitionId: candidate.definitionId || candidate.agentId,
|
|
205
|
+
nodeQualifier: candidate.nodeQualifier,
|
|
206
|
+
workspaceQualifier: candidate.workspaceQualifier,
|
|
207
|
+
profile: candidate.profile,
|
|
208
|
+
harness: candidate.harness,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
function candidateDimensionValue(candidate, dimension) {
|
|
212
|
+
const canonical = candidateCanonicalIdentity(candidate);
|
|
213
|
+
if (!canonical) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
if (dimension === "workspace")
|
|
217
|
+
return canonical.workspaceQualifier;
|
|
218
|
+
if (dimension === "profile")
|
|
219
|
+
return canonical.profile;
|
|
220
|
+
if (dimension === "harness")
|
|
221
|
+
return canonical.harness;
|
|
222
|
+
return canonical.nodeQualifier;
|
|
223
|
+
}
|
|
224
|
+
export function agentIdentityMatches(identity, candidate) {
|
|
225
|
+
const aliasKeys = identityAliasKeys(identity);
|
|
226
|
+
const candidateAliasKeys = explicitCandidateAliases(candidate).map(canonicalizeAliasValue);
|
|
227
|
+
if (aliasKeys.some((key) => candidateAliasKeys.includes(key))) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
const canonical = canonicalizeIdentity({
|
|
231
|
+
definitionId: candidate.definitionId || candidate.agentId,
|
|
232
|
+
nodeQualifier: candidate.nodeQualifier,
|
|
233
|
+
workspaceQualifier: candidate.workspaceQualifier,
|
|
234
|
+
profile: candidate.profile,
|
|
235
|
+
harness: candidate.harness,
|
|
236
|
+
});
|
|
237
|
+
if (!canonical || identity.definitionId !== canonical.definitionId) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
if (identity.nodeQualifier && identity.nodeQualifier !== canonical.nodeQualifier) {
|
|
241
|
+
return candidateAliases(candidate).includes(trimIdentityPrefix(identity.label));
|
|
242
|
+
}
|
|
243
|
+
if (identity.workspaceQualifier && identity.workspaceQualifier !== canonical.workspaceQualifier) {
|
|
244
|
+
return candidateAliases(candidate).includes(trimIdentityPrefix(identity.label));
|
|
245
|
+
}
|
|
246
|
+
if (identity.profile && identity.profile !== canonical.profile) {
|
|
247
|
+
return candidateAliases(candidate).includes(trimIdentityPrefix(identity.label));
|
|
248
|
+
}
|
|
249
|
+
if (identity.harness && identity.harness !== canonical.harness) {
|
|
250
|
+
return candidateAliases(candidate).includes(trimIdentityPrefix(identity.label));
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
export function resolveAgentIdentity(identity, candidates) {
|
|
255
|
+
const aliasKeys = identityAliasKeys(identity);
|
|
256
|
+
const exactAliasMatches = candidates.filter((candidate) => {
|
|
257
|
+
const candidateAliasKeys = explicitCandidateAliases(candidate).map(canonicalizeAliasValue);
|
|
258
|
+
return aliasKeys.some((key) => candidateAliasKeys.includes(key));
|
|
259
|
+
});
|
|
260
|
+
if (exactAliasMatches.length === 1) {
|
|
261
|
+
return exactAliasMatches[0];
|
|
262
|
+
}
|
|
263
|
+
if (exactAliasMatches.length > 1) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
const matches = candidates.filter((candidate) => agentIdentityMatches(identity, candidate));
|
|
267
|
+
if (matches.length === 1) {
|
|
268
|
+
return matches[0];
|
|
269
|
+
}
|
|
270
|
+
if (!identity.nodeQualifier && !identity.workspaceQualifier && !identity.profile && !identity.harness) {
|
|
271
|
+
return matches.find((candidate) => normalizeAgentIdentitySegment(candidate.agentId) === identity.definitionId)
|
|
272
|
+
?? matches[0]
|
|
273
|
+
?? null;
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
export function constructAgentAlias(input) {
|
|
278
|
+
const alias = normalizeAgentIdentitySegment(trimIdentityPrefix(input.alias));
|
|
279
|
+
const target = canonicalizeIdentity(input.target);
|
|
280
|
+
if (!alias || !target) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
return { alias, target };
|
|
284
|
+
}
|
|
285
|
+
export function formatAgentAlias(alias, options = {}) {
|
|
286
|
+
const prefix = options.includeSigil === false ? "" : "@";
|
|
287
|
+
return `${prefix}${normalizeAgentIdentitySegment(alias.alias)}`;
|
|
288
|
+
}
|
|
289
|
+
export function resolveAgentAlias(value, aliases) {
|
|
290
|
+
const aliasKey = normalizeAgentIdentitySegment(trimIdentityPrefix(value));
|
|
291
|
+
if (!aliasKey) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
const matches = aliases.filter((alias) => normalizeAgentIdentitySegment(alias.alias) === aliasKey);
|
|
295
|
+
if (matches.length !== 1) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
return constructAgentIdentity(matches[0].target);
|
|
299
|
+
}
|
|
300
|
+
function identitiesDifferOnDimension(left, right, dimension) {
|
|
301
|
+
return candidateDimensionValue(left, dimension) !== candidateDimensionValue(right, dimension);
|
|
302
|
+
}
|
|
303
|
+
function buildIdentitySubset(candidate, dimensions) {
|
|
304
|
+
const canonical = candidateCanonicalIdentity(candidate);
|
|
305
|
+
if (!canonical) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
return constructAgentIdentity({
|
|
309
|
+
definitionId: canonical.definitionId,
|
|
310
|
+
...(dimensions.includes("workspace") && canonical.workspaceQualifier
|
|
311
|
+
? { workspaceQualifier: canonical.workspaceQualifier }
|
|
312
|
+
: {}),
|
|
313
|
+
...(dimensions.includes("profile") && canonical.profile ? { profile: canonical.profile } : {}),
|
|
314
|
+
...(dimensions.includes("harness") && canonical.harness ? { harness: canonical.harness } : {}),
|
|
315
|
+
...(dimensions.includes("node") && canonical.nodeQualifier ? { nodeQualifier: canonical.nodeQualifier } : {}),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
export function formatMinimalAgentIdentity(candidate, candidates, options = {}) {
|
|
319
|
+
const canonical = candidateCanonicalIdentity(candidate);
|
|
320
|
+
if (!canonical) {
|
|
321
|
+
return options.includeSigil === false ? "" : "@";
|
|
322
|
+
}
|
|
323
|
+
const peers = candidates.filter((peer) => {
|
|
324
|
+
const peerCanonical = candidateCanonicalIdentity(peer);
|
|
325
|
+
return peerCanonical && peerCanonical.definitionId === canonical.definitionId;
|
|
326
|
+
});
|
|
327
|
+
const aliasMatches = explicitCandidateAliases(candidate)
|
|
328
|
+
.map((alias) => ({ alias, identity: parseAgentIdentity(`@${alias}`) }))
|
|
329
|
+
.filter((entry) => Boolean(entry.identity))
|
|
330
|
+
.filter((entry) => resolveAgentIdentity(entry.identity, candidates) === candidate)
|
|
331
|
+
.sort((left, right) => left.alias.length - right.alias.length || left.alias.localeCompare(right.alias));
|
|
332
|
+
if (aliasMatches.length > 0) {
|
|
333
|
+
return formatAgentIdentity({ definitionId: aliasMatches[0].alias }, options);
|
|
334
|
+
}
|
|
335
|
+
if (peers.length <= 1) {
|
|
336
|
+
return formatAgentIdentity({ definitionId: canonical.definitionId }, options);
|
|
337
|
+
}
|
|
338
|
+
const orderedDimensions = ["workspace", "profile", "harness", "node"];
|
|
339
|
+
const selectedDimensions = [];
|
|
340
|
+
let remainingPeers = peers.filter((peer) => peer !== candidate);
|
|
341
|
+
for (const dimension of orderedDimensions) {
|
|
342
|
+
const value = candidateDimensionValue(candidate, dimension);
|
|
343
|
+
if (!value) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
const splitsRemainingPeers = remainingPeers.some((peer) => identitiesDifferOnDimension(candidate, peer, dimension));
|
|
347
|
+
if (!splitsRemainingPeers) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
selectedDimensions.push(dimension);
|
|
351
|
+
remainingPeers = remainingPeers.filter((peer) => (!identitiesDifferOnDimension(candidate, peer, dimension)));
|
|
352
|
+
const current = buildIdentitySubset(candidate, selectedDimensions);
|
|
353
|
+
if (current && resolveAgentIdentity(current, candidates)) {
|
|
354
|
+
return formatAgentIdentity(current, options);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return formatAgentIdentity(canonical, options);
|
|
358
|
+
}
|
|
359
|
+
export const parseAgentAddress = parseAgentIdentity;
|
|
360
|
+
export const formatAgentAddress = formatAgentIdentity;
|
|
361
|
+
export const constructAgentAddress = constructAgentIdentity;
|
|
362
|
+
export const extractAgentAddresses = extractAgentIdentities;
|
|
363
|
+
export const agentAddressMatches = agentIdentityMatches;
|
|
364
|
+
export const resolveAgentAddress = resolveAgentIdentity;
|
|
365
|
+
export const parseAgentSelector = parseAgentIdentity;
|
|
366
|
+
export const formatAgentSelector = formatAgentIdentity;
|
|
367
|
+
export const extractAgentSelectors = extractAgentIdentities;
|
|
368
|
+
export const agentSelectorMatches = agentIdentityMatches;
|
|
369
|
+
export const resolveAgentSelector = resolveAgentIdentity;
|
|
@@ -1,27 +1 @@
|
|
|
1
|
-
|
|
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;
|
|
1
|
+
export * from "./agent-address.js";
|
package/dist/agent-selectors.js
CHANGED
|
@@ -1,103 +1 @@
|
|
|
1
|
-
|
|
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
|
-
}
|
|
1
|
+
export * from "./agent-address.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export * from "./common.js";
|
|
2
2
|
export * from "./actors.js";
|
|
3
|
+
export * from "./agent-identity.js";
|
|
4
|
+
export * from "./agent-address.js";
|
|
3
5
|
export * from "./agent-selectors.js";
|
|
6
|
+
export * from "./relay-agent-card.js";
|
|
4
7
|
export * from "./mesh.js";
|
|
5
8
|
export * from "./conversations.js";
|
|
6
9
|
export * from "./collaboration.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export * from "./common.js";
|
|
2
2
|
export * from "./actors.js";
|
|
3
|
+
export * from "./agent-identity.js";
|
|
4
|
+
export * from "./agent-address.js";
|
|
3
5
|
export * from "./agent-selectors.js";
|
|
6
|
+
export * from "./relay-agent-card.js";
|
|
4
7
|
export * from "./mesh.js";
|
|
5
8
|
export * from "./conversations.js";
|
|
6
9
|
export * from "./collaboration.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AgentEndpoint, AgentHarness } from "./actors.js";
|
|
2
|
+
import type { MetadataMap, ScoutId } from "./common.js";
|
|
3
|
+
export interface RelayReturnAddress {
|
|
4
|
+
actorId: ScoutId;
|
|
5
|
+
handle: string;
|
|
6
|
+
displayName?: string;
|
|
7
|
+
selector?: string;
|
|
8
|
+
defaultSelector?: string;
|
|
9
|
+
conversationId?: ScoutId;
|
|
10
|
+
replyToMessageId?: ScoutId;
|
|
11
|
+
nodeId?: ScoutId;
|
|
12
|
+
projectRoot?: string;
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
metadata?: MetadataMap;
|
|
15
|
+
}
|
|
16
|
+
export interface RelayAgentCard {
|
|
17
|
+
id: ScoutId;
|
|
18
|
+
agentId: ScoutId;
|
|
19
|
+
definitionId: ScoutId;
|
|
20
|
+
displayName: string;
|
|
21
|
+
handle: string;
|
|
22
|
+
selector?: string;
|
|
23
|
+
defaultSelector?: string;
|
|
24
|
+
projectName?: string;
|
|
25
|
+
projectRoot: string;
|
|
26
|
+
currentDirectory: string;
|
|
27
|
+
harness: AgentHarness;
|
|
28
|
+
transport: AgentEndpoint["transport"];
|
|
29
|
+
sessionId?: string;
|
|
30
|
+
branch?: string;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
createdById?: ScoutId;
|
|
33
|
+
brokerRegistered: boolean;
|
|
34
|
+
inboxConversationId?: ScoutId;
|
|
35
|
+
returnAddress: RelayReturnAddress;
|
|
36
|
+
metadata?: MetadataMap;
|
|
37
|
+
}
|
|
38
|
+
export declare function buildRelayReturnAddress(input: RelayReturnAddress): RelayReturnAddress;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function buildRelayReturnAddress(input) {
|
|
2
|
+
const next = {
|
|
3
|
+
actorId: input.actorId,
|
|
4
|
+
handle: input.handle.trim(),
|
|
5
|
+
};
|
|
6
|
+
if (input.displayName?.trim()) {
|
|
7
|
+
next.displayName = input.displayName.trim();
|
|
8
|
+
}
|
|
9
|
+
if (input.selector?.trim()) {
|
|
10
|
+
next.selector = input.selector.trim();
|
|
11
|
+
}
|
|
12
|
+
if (input.defaultSelector?.trim()) {
|
|
13
|
+
next.defaultSelector = input.defaultSelector.trim();
|
|
14
|
+
}
|
|
15
|
+
if (input.conversationId?.trim()) {
|
|
16
|
+
next.conversationId = input.conversationId.trim();
|
|
17
|
+
}
|
|
18
|
+
if (input.replyToMessageId?.trim()) {
|
|
19
|
+
next.replyToMessageId = input.replyToMessageId.trim();
|
|
20
|
+
}
|
|
21
|
+
if (input.nodeId?.trim()) {
|
|
22
|
+
next.nodeId = input.nodeId.trim();
|
|
23
|
+
}
|
|
24
|
+
if (input.projectRoot?.trim()) {
|
|
25
|
+
next.projectRoot = input.projectRoot.trim();
|
|
26
|
+
}
|
|
27
|
+
if (input.sessionId?.trim()) {
|
|
28
|
+
next.sessionId = input.sessionId.trim();
|
|
29
|
+
}
|
|
30
|
+
if (input.metadata && Object.keys(input.metadata).length > 0) {
|
|
31
|
+
next.metadata = input.metadata;
|
|
32
|
+
}
|
|
33
|
+
return next;
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openscout/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Typed protocol for the OpenScout local control plane",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
20
|
-
"check": "tsc --noEmit -p tsconfig.json"
|
|
20
|
+
"check": "tsc --noEmit -p tsconfig.json",
|
|
21
|
+
"test": "bun test src/*.test.ts"
|
|
21
22
|
},
|
|
22
23
|
"publishConfig": {
|
|
23
24
|
"access": "public"
|