@openscout/protocol 0.2.23 → 0.2.30

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.
@@ -39,6 +39,10 @@ export declare function constructAgentIdentity(input: AgentIdentityInput, option
39
39
  raw?: string;
40
40
  }): AgentIdentity | null;
41
41
  export declare function extractAgentIdentities(text: string): AgentIdentity[];
42
+ export declare function extractAgentMentions(text: string): {
43
+ parsed: AgentIdentity[];
44
+ unparsed: string[];
45
+ };
42
46
  export declare function agentIdentityMatches(identity: AgentIdentity, candidate: AgentIdentityCandidate): boolean;
43
47
  export declare function resolveAgentIdentity<T extends AgentIdentityCandidate>(identity: AgentIdentity, candidates: T[]): T | null;
44
48
  export declare function constructAgentAlias(input: {
@@ -57,7 +57,8 @@ function parseSegmentedIdentity(raw) {
57
57
  const next = {
58
58
  definitionId: definitionPart,
59
59
  };
60
- let sawPositionalWorkspace = false;
60
+ // Collect positional (non-keyed) segments
61
+ const positional = [];
61
62
  for (const part of parts) {
62
63
  if (part.includes(":")) {
63
64
  const [rawKey, rawValue = ""] = part.split(":", 2);
@@ -80,15 +81,29 @@ function parseSegmentedIdentity(raw) {
80
81
  }
81
82
  continue;
82
83
  }
83
- if (sawPositionalWorkspace) {
84
+ positional.push(part);
85
+ }
86
+ // 1 positional → workspaceQualifier (@agent.branch)
87
+ // 2 positionals → nodeQualifier.workspaceQualifier (@agent.node.branch — matches instance ID format)
88
+ // 3+ positionals → invalid
89
+ if (positional.length === 1) {
90
+ const value = normalizeAgentIdentitySegment(positional[0]);
91
+ if (!value)
84
92
  return null;
85
- }
86
- const value = normalizeAgentIdentitySegment(part);
87
- if (!value) {
88
- return null;
89
- }
90
93
  next.workspaceQualifier = value;
91
- sawPositionalWorkspace = true;
94
+ }
95
+ else if (positional.length === 2) {
96
+ const nodeValue = normalizeAgentIdentitySegment(positional[0]);
97
+ const workspaceValue = normalizeAgentIdentitySegment(positional[1]);
98
+ if (!nodeValue || !workspaceValue)
99
+ return null;
100
+ if (!next.nodeQualifier)
101
+ next.nodeQualifier = nodeValue;
102
+ if (!next.workspaceQualifier)
103
+ next.workspaceQualifier = workspaceValue;
104
+ }
105
+ else if (positional.length > 2) {
106
+ return null;
92
107
  }
93
108
  return constructAgentIdentity(next, { raw });
94
109
  }
@@ -139,16 +154,26 @@ export function constructAgentIdentity(input, options = {}) {
139
154
  };
140
155
  }
141
156
  export function extractAgentIdentities(text) {
157
+ return extractAgentMentions(text).parsed;
158
+ }
159
+ export function extractAgentMentions(text) {
142
160
  const matches = Array.from(text.matchAll(/(^|\s)@([a-z0-9][a-z0-9._/:-]*)/gi));
143
161
  const identities = new Map();
162
+ const unparsed = [];
144
163
  for (const match of matches) {
145
- const candidate = parseAgentIdentity(match[2] ?? "");
164
+ const raw = match[2] ?? "";
165
+ const candidate = parseAgentIdentity(raw);
146
166
  if (!candidate) {
167
+ if (raw)
168
+ unparsed.push(`@${raw}`);
147
169
  continue;
148
170
  }
149
171
  identities.set(candidate.label, candidate);
150
172
  }
151
- return Array.from(identities.values());
173
+ return {
174
+ parsed: Array.from(identities.values()),
175
+ unparsed: Array.from(new Set(unparsed)),
176
+ };
152
177
  }
153
178
  function candidateAliases(candidate) {
154
179
  const canonical = canonicalizeIdentity({
package/dist/common.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export type ScoutId = string;
2
2
  export type ActorKind = "person" | "helper" | "agent" | "system" | "bridge" | "device";
3
- export type AgentState = "offline" | "idle" | "active" | "waiting" | "degraded";
3
+ export type AgentState = "offline" | "idle" | "active" | "waiting";
4
4
  export type VisibilityScope = "private" | "workspace" | "public" | "system";
5
5
  export type DeliveryStatus = "pending" | "leased" | "sent" | "acknowledged" | "failed" | "cancelled";
6
6
  export type DeliveryPolicy = "best_effort" | "must_ack" | "durable" | "ephemeral";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openscout/protocol",
3
- "version": "0.2.23",
3
+ "version": "0.2.30",
4
4
  "description": "Typed protocol for the OpenScout local control plane",
5
5
  "type": "module",
6
6
  "exports": {
@@ -57,11 +57,19 @@ describe("agent identity grammar", () => {
57
57
  });
58
58
  });
59
59
 
60
+ test("parses 3-segment positional as definitionId.nodeQualifier.workspaceQualifier", () => {
61
+ const result = parseAgentIdentity("@arc.main.dev");
62
+ expect(result).not.toBeNull();
63
+ expect(result!.definitionId).toBe("arc");
64
+ expect(result!.nodeQualifier).toBe("main");
65
+ expect(result!.workspaceQualifier).toBe("dev");
66
+ });
67
+
60
68
  test("rejects invalid and legacy-shaped identities", () => {
61
69
  expect(parseAgentIdentity("")).toBeNull();
62
- expect(parseAgentIdentity("@arc.main.dev")).toBeNull();
63
70
  expect(parseAgentIdentity("@arc.profile:")).toBeNull();
64
71
  expect(parseAgentIdentity("@arc@mini#main")).toBeNull();
72
+ expect(parseAgentIdentity("@arc.a.b.c")).toBeNull(); // 3+ positionals rejected
65
73
  });
66
74
 
67
75
  test("constructs canonical agent identities", () => {
@@ -104,7 +104,8 @@ function parseSegmentedIdentity(raw: string): AgentIdentity | null {
104
104
  definitionId: definitionPart,
105
105
  };
106
106
 
107
- let sawPositionalWorkspace = false;
107
+ // Collect positional (non-keyed) segments
108
+ const positional: string[] = [];
108
109
  for (const part of parts) {
109
110
  if (part.includes(":")) {
110
111
  const [rawKey, rawValue = ""] = part.split(":", 2);
@@ -126,17 +127,24 @@ function parseSegmentedIdentity(raw: string): AgentIdentity | null {
126
127
  continue;
127
128
  }
128
129
 
129
- if (sawPositionalWorkspace) {
130
- return null;
131
- }
132
-
133
- const value = normalizeAgentIdentitySegment(part);
134
- if (!value) {
135
- return null;
136
- }
130
+ positional.push(part);
131
+ }
137
132
 
133
+ // 1 positional → workspaceQualifier (@agent.branch)
134
+ // 2 positionals → nodeQualifier.workspaceQualifier (@agent.node.branch — matches instance ID format)
135
+ // 3+ positionals → invalid
136
+ if (positional.length === 1) {
137
+ const value = normalizeAgentIdentitySegment(positional[0]);
138
+ if (!value) return null;
138
139
  next.workspaceQualifier = value;
139
- sawPositionalWorkspace = true;
140
+ } else if (positional.length === 2) {
141
+ const nodeValue = normalizeAgentIdentitySegment(positional[0]);
142
+ const workspaceValue = normalizeAgentIdentitySegment(positional[1]);
143
+ if (!nodeValue || !workspaceValue) return null;
144
+ if (!next.nodeQualifier) next.nodeQualifier = nodeValue;
145
+ if (!next.workspaceQualifier) next.workspaceQualifier = workspaceValue;
146
+ } else if (positional.length > 2) {
147
+ return null;
140
148
  }
141
149
 
142
150
  return constructAgentIdentity(next, { raw });
@@ -204,21 +212,31 @@ export function constructAgentIdentity(
204
212
  }
205
213
 
206
214
  export function extractAgentIdentities(text: string): AgentIdentity[] {
215
+ return extractAgentMentions(text).parsed;
216
+ }
217
+
218
+ export function extractAgentMentions(text: string): { parsed: AgentIdentity[]; unparsed: string[] } {
207
219
  const matches = Array.from(
208
220
  text.matchAll(/(^|\s)@([a-z0-9][a-z0-9._/:-]*)/gi),
209
221
  );
210
222
  const identities = new Map<string, AgentIdentity>();
223
+ const unparsed: string[] = [];
211
224
 
212
225
  for (const match of matches) {
213
- const candidate = parseAgentIdentity(match[2] ?? "");
226
+ const raw = match[2] ?? "";
227
+ const candidate = parseAgentIdentity(raw);
214
228
  if (!candidate) {
229
+ if (raw) unparsed.push(`@${raw}`);
215
230
  continue;
216
231
  }
217
232
 
218
233
  identities.set(candidate.label, candidate);
219
234
  }
220
235
 
221
- return Array.from(identities.values());
236
+ return {
237
+ parsed: Array.from(identities.values()),
238
+ unparsed: Array.from(new Set(unparsed)),
239
+ };
222
240
  }
223
241
 
224
242
  function candidateAliases(candidate: AgentIdentityCandidate): string[] {
package/src/common.ts CHANGED
@@ -12,8 +12,7 @@ export type AgentState =
12
12
  | "offline"
13
13
  | "idle"
14
14
  | "active"
15
- | "waiting"
16
- | "degraded";
15
+ | "waiting";
17
16
 
18
17
  export type VisibilityScope = "private" | "workspace" | "public" | "system";
19
18