@openscout/protocol 0.2.109 → 0.2.110
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/dist/actors.d.ts +1 -1
- package/dist/actors.d.ts.map +1 -1
- package/dist/agent-notifications.d.ts +137 -0
- package/dist/agent-notifications.d.ts.map +1 -0
- package/dist/agent-notifications.js +148 -0
- package/dist/channel-invites.d.ts +3 -1
- package/dist/channel-invites.d.ts.map +1 -1
- package/dist/channel-membership.d.ts +10 -0
- package/dist/channel-membership.d.ts.map +1 -0
- package/dist/channel-membership.js +16 -0
- package/dist/chat-mentions.d.ts +4 -0
- package/dist/chat-mentions.d.ts.map +1 -0
- package/dist/chat-mentions.js +11 -0
- package/dist/chat-message-corrections.d.ts +28 -0
- package/dist/chat-message-corrections.d.ts.map +1 -0
- package/dist/chat-message-corrections.js +46 -0
- package/dist/chat-pins.d.ts +14 -0
- package/dist/chat-pins.d.ts.map +1 -0
- package/dist/chat-pins.js +37 -0
- package/dist/common.d.ts +1 -1
- package/dist/common.d.ts.map +1 -1
- package/dist/events.d.ts +4 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/herdr-workspace-digest.d.ts +8 -4
- package/dist/herdr-workspace-digest.d.ts.map +1 -1
- package/dist/herdr-workspace-digest.js +18 -3
- package/dist/host-integrations.js +6 -6
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/invocations.d.ts +17 -2
- package/dist/invocations.d.ts.map +1 -1
- package/dist/invocations.js +1 -1
- package/dist/mcp-core-tools.d.ts +137 -0
- package/dist/mcp-core-tools.d.ts.map +1 -0
- package/dist/mcp-core-tools.js +27 -0
- package/dist/message-reactions.d.ts +5 -0
- package/dist/message-reactions.d.ts.map +1 -1
- package/dist/message-reactions.js +10 -1
- package/dist/orchestration-provider-map.d.ts.map +1 -1
- package/dist/orchestration-provider-map.js +9 -1
- package/dist/read-receipts.d.ts +38 -0
- package/dist/read-receipts.d.ts.map +1 -1
- package/dist/read-receipts.js +43 -1
- package/dist/runtime-catalog-contract.d.ts +14 -0
- package/dist/runtime-catalog-contract.d.ts.map +1 -1
- package/dist/runtime-catalog.d.ts.map +1 -1
- package/dist/runtime-catalog.generated.d.ts +88 -2
- package/dist/runtime-catalog.generated.d.ts.map +1 -1
- package/dist/runtime-catalog.generated.js +114 -12
- package/dist/runtime-catalog.js +28 -0
- package/dist/runtime-execution.d.ts +33 -2
- package/dist/runtime-execution.d.ts.map +1 -1
- package/dist/runtime-execution.js +34 -17
- package/dist/scout-agent-card.d.ts +2 -0
- package/dist/scout-agent-card.d.ts.map +1 -1
- package/dist/scout-agent-card.js +3 -0
- package/dist/scout-composer.d.ts.map +1 -1
- package/dist/scout-composer.js +23 -0
- package/dist/scout-delivery.d.ts +3 -1
- package/dist/scout-delivery.d.ts.map +1 -1
- package/dist/scout-dispatch.d.ts +13 -0
- package/dist/scout-dispatch.d.ts.map +1 -1
- package/dist/session-handle.d.ts +37 -0
- package/dist/session-handle.d.ts.map +1 -1
- package/dist/session-handle.js +58 -0
- package/dist/terminal-host-topology.d.ts +5 -2
- package/dist/terminal-host-topology.d.ts.map +1 -1
- package/dist/terminal-host-topology.js +2 -1
- package/dist/transports.d.ts +8 -1
- package/dist/transports.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/actors.ts +2 -0
- package/src/agent-notifications.ts +241 -0
- package/src/channel-invites.ts +4 -1
- package/src/channel-membership.ts +22 -0
- package/src/chat-mentions.ts +11 -0
- package/src/chat-message-corrections.ts +40 -0
- package/src/chat-pins.ts +40 -0
- package/src/common.ts +2 -0
- package/src/events.ts +3 -0
- package/src/herdr-workspace-digest.ts +23 -6
- package/src/host-integrations.ts +6 -6
- package/src/index.ts +10 -0
- package/src/invocations.ts +18 -3
- package/src/mcp-core-tools.ts +46 -0
- package/src/message-reactions.ts +16 -2
- package/src/orchestration-provider-map.ts +12 -1
- package/src/read-receipts.ts +71 -0
- package/src/runtime-catalog-contract.ts +16 -0
- package/src/runtime-catalog.generated.ts +114 -12
- package/src/runtime-catalog.ts +32 -0
- package/src/runtime-catalog.v1.json +20 -13
- package/src/runtime-execution.ts +70 -16
- package/src/scout-agent-card.ts +5 -0
- package/src/scout-composer.ts +26 -0
- package/src/scout-delivery.ts +3 -1
- package/src/scout-dispatch.ts +13 -0
- package/src/session-handle.ts +88 -0
- package/src/terminal-host-topology.ts +5 -2
- package/src/transports.ts +9 -0
package/src/runtime-execution.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
type ScoutOwnedRuntimeCatalog,
|
|
8
8
|
type ScoutOwnedRuntimeHarness,
|
|
9
9
|
type ScoutReasoningEffort,
|
|
10
|
+
type ScoutRuntimeHarnessPresentation,
|
|
11
|
+
type ScoutRuntimeModelPresentation,
|
|
10
12
|
} from "./runtime-catalog-contract.js";
|
|
11
13
|
export * from "./runtime-catalog-contract.js";
|
|
12
14
|
|
|
@@ -46,6 +48,15 @@ export function isScoutRuntimeHarnessListed(
|
|
|
46
48
|
return entry?.enabled === true && entry.listed !== false;
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
/** A harness accepts a model dimension iff the owned catalog lists at least one enabled model for it. */
|
|
52
|
+
export function isModelSelectableHarness(
|
|
53
|
+
harness: string,
|
|
54
|
+
catalog: ScoutOwnedRuntimeCatalog = SCOUT_RUNTIME_CATALOG,
|
|
55
|
+
): boolean {
|
|
56
|
+
const entry = scoutRuntimeHarness(harness, catalog);
|
|
57
|
+
return entry?.enabled === true && entry.models.some((model) => model.enabled);
|
|
58
|
+
}
|
|
59
|
+
|
|
49
60
|
export function scoutRuntimeDefaultHarness(
|
|
50
61
|
catalog: ScoutOwnedRuntimeCatalog = SCOUT_RUNTIME_CATALOG,
|
|
51
62
|
): ScoutLaunchableHarness | null {
|
|
@@ -147,6 +158,24 @@ export interface ScoutRuntimeDimensionResolution {
|
|
|
147
158
|
drift: ScoutRuntimeDriftState;
|
|
148
159
|
}
|
|
149
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Which Codex app-server executed the session: one Scout spawned for itself,
|
|
163
|
+
* or an already-running server Scout attached to over its control socket.
|
|
164
|
+
* Harness-owned evidence from `initialize` and `codex app-server daemon version`.
|
|
165
|
+
*/
|
|
166
|
+
export interface ScoutCodexAppServerIdentity {
|
|
167
|
+
connection: "spawned" | "attached";
|
|
168
|
+
socketPath: string | null;
|
|
169
|
+
pid: number | null;
|
|
170
|
+
userAgent: string | null;
|
|
171
|
+
appServerVersion: string | null;
|
|
172
|
+
codexHome: string | null;
|
|
173
|
+
platformOs?: string | null;
|
|
174
|
+
/** Scout features that do not apply to this server (e.g. scout_mcp_injection). */
|
|
175
|
+
capabilityGaps: string[];
|
|
176
|
+
connectedAt: number;
|
|
177
|
+
}
|
|
178
|
+
|
|
150
179
|
/**
|
|
151
180
|
* Durable execution truth for one concrete session. `requested` is caller
|
|
152
181
|
* intent, `resolved` is the spawn value after the launch ladder, and
|
|
@@ -160,6 +189,8 @@ export interface ScoutExecutionResolution {
|
|
|
160
189
|
sessionId?: string;
|
|
161
190
|
resolvedAt?: number;
|
|
162
191
|
observedAt?: number;
|
|
192
|
+
/** Present for Codex sessions once the app-server has been reached. */
|
|
193
|
+
codexAppServer?: ScoutCodexAppServerIdentity;
|
|
163
194
|
}
|
|
164
195
|
|
|
165
196
|
export interface ScoutRuntimeModelOption {
|
|
@@ -170,6 +201,9 @@ export interface ScoutRuntimeModelOption {
|
|
|
170
201
|
source: "catalog" | "observed" | "configured" | "default";
|
|
171
202
|
family?: string;
|
|
172
203
|
version?: string;
|
|
204
|
+
/** True when the owned catalog marks this model as its harness's default. */
|
|
205
|
+
isDefault?: boolean;
|
|
206
|
+
presentation?: ScoutRuntimeModelPresentation;
|
|
173
207
|
}
|
|
174
208
|
|
|
175
209
|
export interface ScoutRuntimeEffortOption {
|
|
@@ -195,6 +229,7 @@ export interface ScoutRuntimeHarnessOption {
|
|
|
195
229
|
state?: "ready" | "configured" | "installed" | "missing" | null;
|
|
196
230
|
ready?: boolean | null;
|
|
197
231
|
detail?: string | null;
|
|
232
|
+
presentation?: ScoutRuntimeHarnessPresentation;
|
|
198
233
|
}
|
|
199
234
|
|
|
200
235
|
export interface ScoutRuntimeCapabilityCatalog {
|
|
@@ -238,6 +273,8 @@ export function scoutRuntimeModelCatalog(
|
|
|
238
273
|
source: "default",
|
|
239
274
|
...(model.family ? { family: model.family } : {}),
|
|
240
275
|
...(model.version ? { version: model.version } : {}),
|
|
276
|
+
...(model.default === true ? { isDefault: true } : {}),
|
|
277
|
+
...(model.presentation ? { presentation: model.presentation } : {}),
|
|
241
278
|
});
|
|
242
279
|
}
|
|
243
280
|
}
|
|
@@ -310,6 +347,28 @@ export type ScoutRuntimeModelNormalization =
|
|
|
310
347
|
| { ok: true; requested: string; resolved: string }
|
|
311
348
|
| { ok: false; requested: string; error: string; candidates?: string[] };
|
|
312
349
|
|
|
350
|
+
const CLAUDE_FAMILY_ALIASES: Record<string, string> = {
|
|
351
|
+
fable: "Fable",
|
|
352
|
+
opus: "Opus",
|
|
353
|
+
sonnet: "Sonnet",
|
|
354
|
+
haiku: "Haiku",
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* The newest enabled model in a catalog family: the harness default when it is
|
|
359
|
+
* in the family, otherwise the first in catalog order (newest first). A family
|
|
360
|
+
* alias like `opus` follows the catalog instead of pinning a release.
|
|
361
|
+
*/
|
|
362
|
+
export function scoutRuntimeLatestModelInFamily(
|
|
363
|
+
harness: string,
|
|
364
|
+
family: string,
|
|
365
|
+
catalog: ScoutOwnedRuntimeCatalog = SCOUT_RUNTIME_CATALOG,
|
|
366
|
+
): string | undefined {
|
|
367
|
+
const models = (scoutRuntimeHarness(harness, catalog)?.models ?? [])
|
|
368
|
+
.filter((model) => model.enabled && model.family === family);
|
|
369
|
+
return (models.find((model) => model.default) ?? models[0])?.id;
|
|
370
|
+
}
|
|
371
|
+
|
|
313
372
|
/** Canonical model aliases shared by every request boundary and spawn path. */
|
|
314
373
|
export function normalizeScoutRuntimeModel(
|
|
315
374
|
harness: string,
|
|
@@ -334,13 +393,9 @@ export function normalizeScoutRuntimeModel(
|
|
|
334
393
|
return { ok: true, requested, resolved: requested };
|
|
335
394
|
}
|
|
336
395
|
if (normalizedHarness === "claude") {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
sonnet: "claude-sonnet-4-6",
|
|
341
|
-
haiku: "claude-haiku-4-5",
|
|
342
|
-
};
|
|
343
|
-
return { ok: true, requested, resolved: aliases[lower] ?? requested };
|
|
396
|
+
const family = CLAUDE_FAMILY_ALIASES[lower];
|
|
397
|
+
const resolved = family ? scoutRuntimeLatestModelInFamily("claude", family) : undefined;
|
|
398
|
+
return { ok: true, requested, resolved: resolved ?? requested };
|
|
344
399
|
}
|
|
345
400
|
return { ok: true, requested, resolved: requested };
|
|
346
401
|
}
|
|
@@ -485,6 +540,7 @@ export function createScoutExecutionResolution(input: {
|
|
|
485
540
|
export function validateScoutRuntimeTuple(
|
|
486
541
|
input: ScoutRuntimeTuple,
|
|
487
542
|
catalog?: Pick<ScoutRuntimeCapabilityCatalog, "models" | "efforts">,
|
|
543
|
+
ownedCatalog: ScoutOwnedRuntimeCatalog = SCOUT_RUNTIME_CATALOG,
|
|
488
544
|
): ScoutRuntimeTupleIssue[] {
|
|
489
545
|
const rawHarness = input.harness?.trim().toLowerCase();
|
|
490
546
|
const harness = rawHarness === "oc" ? "opencode" : rawHarness;
|
|
@@ -492,11 +548,14 @@ export function validateScoutRuntimeTuple(
|
|
|
492
548
|
const effortRaw = input.reasoningEffort?.trim();
|
|
493
549
|
const issues: ScoutRuntimeTupleIssue[] = [];
|
|
494
550
|
|
|
495
|
-
|
|
551
|
+
// Harness membership and model selectability come from the owned catalog —
|
|
552
|
+
// the live broker snapshot when one is supplied, the bundled default
|
|
553
|
+
// otherwise — never a hand-maintained list.
|
|
554
|
+
if (harness && !ownedCatalog.harnesses.some((entry) => entry.id === harness)) {
|
|
496
555
|
issues.push({
|
|
497
556
|
code: "unsupported_harness",
|
|
498
557
|
dimension: "harness",
|
|
499
|
-
message: `unsupported harness "${input.harness}"; expected one of: ${
|
|
558
|
+
message: `unsupported harness "${input.harness}"; expected one of: ${ownedCatalog.harnesses.map((entry) => entry.id).join(", ")}`,
|
|
500
559
|
});
|
|
501
560
|
return issues;
|
|
502
561
|
}
|
|
@@ -512,7 +571,7 @@ export function validateScoutRuntimeTuple(
|
|
|
512
571
|
const catalogEffort = catalog?.efforts.find((candidate) => candidate.id === effort);
|
|
513
572
|
let supported = catalogEffort
|
|
514
573
|
? catalogEffort.harnesses.includes(harness as ScoutLaunchableHarness)
|
|
515
|
-
: (scoutRuntimeReasoningEfforts(harness, model) ?? []).includes(effort);
|
|
574
|
+
: (scoutRuntimeReasoningEfforts(harness, model, ownedCatalog) ?? []).includes(effort);
|
|
516
575
|
if (supported && catalogEffort?.models?.length && model) {
|
|
517
576
|
const harnessModels = new Set(
|
|
518
577
|
catalog?.models
|
|
@@ -535,12 +594,7 @@ export function validateScoutRuntimeTuple(
|
|
|
535
594
|
}
|
|
536
595
|
}
|
|
537
596
|
|
|
538
|
-
|
|
539
|
-
|| harness === "codex"
|
|
540
|
-
|| harness === "grok"
|
|
541
|
-
|| harness === "grok-acp"
|
|
542
|
-
|| harness === "opencode";
|
|
543
|
-
if (model && harness && !modelSelectableHarness) {
|
|
597
|
+
if (model && harness && !isModelSelectableHarness(harness, ownedCatalog)) {
|
|
544
598
|
issues.push({
|
|
545
599
|
code: "unsupported_model_dimension",
|
|
546
600
|
dimension: "model",
|
package/src/scout-agent-card.ts
CHANGED
|
@@ -42,6 +42,8 @@ export interface ScoutReturnAddress {
|
|
|
42
42
|
nodeId?: ScoutId;
|
|
43
43
|
projectRoot?: string;
|
|
44
44
|
sessionId?: string;
|
|
45
|
+
/** Copyable `sess.<token>@<host>` for the exact session replies should continue. */
|
|
46
|
+
sessionAddress?: string;
|
|
45
47
|
metadata?: MetadataMap;
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -125,6 +127,9 @@ export function buildScoutReturnAddress(input: ScoutReturnAddress): ScoutReturnA
|
|
|
125
127
|
if (input.sessionId?.trim()) {
|
|
126
128
|
next.sessionId = input.sessionId.trim();
|
|
127
129
|
}
|
|
130
|
+
if (input.sessionAddress?.trim()) {
|
|
131
|
+
next.sessionAddress = input.sessionAddress.trim();
|
|
132
|
+
}
|
|
128
133
|
if (input.metadata && Object.keys(input.metadata).length > 0) {
|
|
129
134
|
next.metadata = input.metadata;
|
|
130
135
|
}
|
package/src/scout-composer.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseAgentIdentity } from "./agent-identity.js";
|
|
2
2
|
import { AGENT_HARNESSES, type AgentHarness } from "./actors.js";
|
|
3
3
|
import type { ScoutRouteTarget } from "./scout-dispatch.js";
|
|
4
|
+
import { parseScoutSessionHostAddress } from "./session-handle.js";
|
|
4
5
|
|
|
5
6
|
export const SCOUT_COMPOSER_ROUTE_OPERATOR = ">>" as const;
|
|
6
7
|
export const SCOUT_TARGET_HANDLE_SHORTHAND = "⌖" as const;
|
|
@@ -100,12 +101,30 @@ function parseRouteAliasTarget(rawValue: string): ScoutRouteTarget | null {
|
|
|
100
101
|
return { kind: "route_alias", alias, value: `alias:${alias}` };
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
function parseSessionHostAddressTarget(value: string): ScoutRouteTarget | null {
|
|
105
|
+
const address = parseScoutSessionHostAddress(value);
|
|
106
|
+
if (!address) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
kind: "session_id",
|
|
111
|
+
sessionId: address.handle,
|
|
112
|
+
host: address.host,
|
|
113
|
+
value: `session:${address.handle}@${address.host}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
103
117
|
function parseSessionRouteTarget(rawValue: string): ScoutRouteTarget | null {
|
|
104
118
|
const value = rawValue.replace(/^@+/, "");
|
|
105
119
|
if (!isRouteValue(value)) {
|
|
106
120
|
return null;
|
|
107
121
|
}
|
|
108
122
|
|
|
123
|
+
const hostAddress = parseSessionHostAddressTarget(value);
|
|
124
|
+
if (hostAddress) {
|
|
125
|
+
return hostAddress;
|
|
126
|
+
}
|
|
127
|
+
|
|
109
128
|
const harnessSeparator = value.indexOf(":");
|
|
110
129
|
if (harnessSeparator > 0) {
|
|
111
130
|
const maybeHarness = value.slice(0, harnessSeparator) as AgentHarness;
|
|
@@ -181,6 +200,13 @@ export function parseScoutComposerRouteTarget(value: string): ScoutRouteTarget |
|
|
|
181
200
|
}
|
|
182
201
|
}
|
|
183
202
|
|
|
203
|
+
// Bare `sess.<token>@<host>` — the copyable session address. Agent labels
|
|
204
|
+
// never contain `@`, so this cannot shadow an existing selector.
|
|
205
|
+
const hostAddress = parseSessionHostAddressTarget(token);
|
|
206
|
+
if (hostAddress) {
|
|
207
|
+
return hostAddress;
|
|
208
|
+
}
|
|
209
|
+
|
|
184
210
|
return parseAgentLabelTarget(token);
|
|
185
211
|
}
|
|
186
212
|
|
package/src/scout-delivery.ts
CHANGED
|
@@ -34,7 +34,7 @@ export type ScoutOperatorSignalKind = "notify" | "consult" | "need";
|
|
|
34
34
|
* do without it (`defaultAction`). Silence is a valid answer.
|
|
35
35
|
* need — the agent cannot proceed. Silence is NOT an answer, so this is
|
|
36
36
|
* the only kind that is blocking, and the only one that raises the
|
|
37
|
-
* operator's
|
|
37
|
+
* operator's requests surface.
|
|
38
38
|
*
|
|
39
39
|
* `notify` and `consult` stay conversational side effects: they never create a
|
|
40
40
|
* flight and never imply the agent is waiting. `need` is the exception on
|
|
@@ -142,6 +142,8 @@ export interface ScoutDeliveryReceipt {
|
|
|
142
142
|
requesterNodeId: ScoutId;
|
|
143
143
|
targetAgentId?: ScoutId;
|
|
144
144
|
targetSessionId?: ScoutId;
|
|
145
|
+
/** Copyable `sess.<token>@<host>` for exact continuation of the target session. */
|
|
146
|
+
targetSessionAddress?: string;
|
|
145
147
|
targetLabel?: string;
|
|
146
148
|
/** Provisional routable pointer (e.g. project-chopin) when target is cardless. */
|
|
147
149
|
sessionAlias?: string;
|
package/src/scout-dispatch.ts
CHANGED
|
@@ -48,6 +48,17 @@ export type ScoutRouteTarget =
|
|
|
48
48
|
/** Canonical writes use a broker `sess.*` handle. Native ids are legacy resolver inputs only. */
|
|
49
49
|
sessionId: ScoutId;
|
|
50
50
|
harness?: AgentHarness;
|
|
51
|
+
/**
|
|
52
|
+
* Opt-in for native ids: if the session is live outside Scout (e.g. open
|
|
53
|
+
* in the operator's terminal), continue it as a new Scout-owned fork
|
|
54
|
+
* rather than refusing. The receipt's targetSessionId names the fork.
|
|
55
|
+
*/
|
|
56
|
+
forkIfLive?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Host part of a `sess.<token>@<host>` address: the broker authority that
|
|
59
|
+
* owns the session. Scopes resolution to that host; it never widens it.
|
|
60
|
+
*/
|
|
61
|
+
host?: string;
|
|
51
62
|
value?: string;
|
|
52
63
|
}
|
|
53
64
|
| { kind: "binding_ref"; ref: string; value?: string }
|
|
@@ -108,6 +119,8 @@ export interface ScoutDispatchEnvelope {
|
|
|
108
119
|
dispatchedAt: number;
|
|
109
120
|
dispatcherNodeId: ScoutId;
|
|
110
121
|
diagnosticCode?: RouteAliasDiagnosticCode;
|
|
122
|
+
/** Exact-session wake refusal code (session_unknown, session_live_unbound, …). */
|
|
123
|
+
sessionWakeReason?: string;
|
|
111
124
|
aliasResolution?: RouteAliasResolutionProof;
|
|
112
125
|
}
|
|
113
126
|
|
package/src/session-handle.ts
CHANGED
|
@@ -35,3 +35,91 @@ export function parseScoutSessionAddress(
|
|
|
35
35
|
export function formatScoutSessionAddress(handle: ScoutSessionHandle): string {
|
|
36
36
|
return `${SCOUT_SESSION_ADDRESS_PREFIX}${handle}`;
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Email-like session address: `sess.<token>@<host>`.
|
|
41
|
+
*
|
|
42
|
+
* The local part is the exact harness conversation (the opaque broker handle);
|
|
43
|
+
* the domain is the stable broker authority that owns it — a mesh node's
|
|
44
|
+
* stable qualifier, never a network address. It is derived for every known
|
|
45
|
+
* session, so first contact needs no agent card. Addressability says nothing
|
|
46
|
+
* about reachability: resolving an address may still report the session as
|
|
47
|
+
* offline, ended, or on a host this broker cannot reach.
|
|
48
|
+
*
|
|
49
|
+
* `session:sess.<token>@<host>` is the typed form; a bare `sess.<token>@<host>`
|
|
50
|
+
* is accepted wherever a route target is. Only canonical `sess.*` handles take
|
|
51
|
+
* a host; legacy native session ids keep their existing grammar untouched.
|
|
52
|
+
*/
|
|
53
|
+
export interface ScoutSessionHostAddress {
|
|
54
|
+
handle: ScoutSessionHandle;
|
|
55
|
+
host: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Normalized host label: lowercase DNS-ish label, no `.local`, no trailing dot. */
|
|
59
|
+
export function normalizeScoutSessionHost(value: string | null | undefined): string {
|
|
60
|
+
if (typeof value !== "string") return "";
|
|
61
|
+
return value
|
|
62
|
+
.trim()
|
|
63
|
+
.toLowerCase()
|
|
64
|
+
.replace(/\.+$/, "")
|
|
65
|
+
.replace(/\.local$/, "")
|
|
66
|
+
.replace(/[^a-z0-9.-]+/g, "-")
|
|
67
|
+
.replace(/-+/g, "-")
|
|
68
|
+
.replace(/^[-.]+|[-.]+$/g, "");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const SCOUT_SESSION_HOST_PATTERN = /^[a-z0-9](?:[a-z0-9.-]{0,251}[a-z0-9])?$/;
|
|
72
|
+
|
|
73
|
+
export function parseScoutSessionHostAddress(
|
|
74
|
+
value: string | null | undefined,
|
|
75
|
+
): ScoutSessionHostAddress | null {
|
|
76
|
+
if (typeof value !== "string") return null;
|
|
77
|
+
const trimmed = value.trim();
|
|
78
|
+
const bare = trimmed.startsWith(SCOUT_SESSION_ADDRESS_PREFIX)
|
|
79
|
+
? trimmed.slice(SCOUT_SESSION_ADDRESS_PREFIX.length)
|
|
80
|
+
: trimmed;
|
|
81
|
+
const at = bare.indexOf("@");
|
|
82
|
+
if (at <= 0 || at !== bare.lastIndexOf("@")) return null;
|
|
83
|
+
const handle = bare.slice(0, at);
|
|
84
|
+
const host = normalizeScoutSessionHost(bare.slice(at + 1));
|
|
85
|
+
if (!isScoutSessionHandle(handle) || !SCOUT_SESSION_HOST_PATTERN.test(host)) return null;
|
|
86
|
+
return { handle, host };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function formatScoutSessionHostAddress(address: ScoutSessionHostAddress): string {
|
|
90
|
+
return `${address.handle}@${normalizeScoutSessionHost(address.host)}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The node facts a host label is derived from (a subset of NodeDefinition). */
|
|
94
|
+
export interface ScoutSessionHostNode {
|
|
95
|
+
id: string;
|
|
96
|
+
meshId?: string;
|
|
97
|
+
name?: string;
|
|
98
|
+
hostName?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Derive the stable host label for a mesh node. Node ids are minted as
|
|
103
|
+
* `<stable-qualifier>-<mesh>`, so stripping the mesh suffix yields the
|
|
104
|
+
* persisted machine qualifier; display names are a fallback only.
|
|
105
|
+
*/
|
|
106
|
+
export function scoutSessionHostForNode(node: ScoutSessionHostNode): string {
|
|
107
|
+
const id = normalizeScoutSessionHost(node.id);
|
|
108
|
+
const mesh = normalizeScoutSessionHost(node.meshId);
|
|
109
|
+
if (mesh && id.endsWith(`-${mesh}`) && id.length > mesh.length + 1) {
|
|
110
|
+
return id.slice(0, -(mesh.length + 1));
|
|
111
|
+
}
|
|
112
|
+
return normalizeScoutSessionHost(node.name) || normalizeScoutSessionHost(node.hostName) || id;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** True when `host` names this node by its derived label, id, name, or host name. */
|
|
116
|
+
export function scoutNodeMatchesSessionHost(node: ScoutSessionHostNode, host: string): boolean {
|
|
117
|
+
const wanted = normalizeScoutSessionHost(host);
|
|
118
|
+
if (!wanted) return false;
|
|
119
|
+
return [
|
|
120
|
+
scoutSessionHostForNode(node),
|
|
121
|
+
normalizeScoutSessionHost(node.id),
|
|
122
|
+
normalizeScoutSessionHost(node.name),
|
|
123
|
+
normalizeScoutSessionHost(node.hostName),
|
|
124
|
+
].includes(wanted);
|
|
125
|
+
}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* session attach`, `herdr agent focus`), not a Scout control.
|
|
12
12
|
*
|
|
13
13
|
* The server builds this from `herdr --session <n> workspace list`,
|
|
14
|
-
* `tab list`, and `pane list` JSON,
|
|
14
|
+
* `tab list`, and `pane list` JSON, with assigned names from `agent list`
|
|
15
|
+
* joined by terminal + pane id, plus the per-tab layout geometry from
|
|
15
16
|
* `herdr --session <n> api snapshot`. A session whose server is stopped
|
|
16
17
|
* projects from its persisted `session.json` instead — same shape, marked by
|
|
17
18
|
* `running: false` and `savedAt`; geometry, terminal ids, and live agent
|
|
@@ -36,10 +37,12 @@ export type HerdrAgentSessionRef = {
|
|
|
36
37
|
|
|
37
38
|
export type HerdrPaneProjection = {
|
|
38
39
|
paneId: string;
|
|
39
|
-
/** Herdr's
|
|
40
|
+
/** Herdr's terminal identity; CLI read commands use the separately carried pane id. */
|
|
40
41
|
terminalId: string | null;
|
|
41
42
|
tabId: string;
|
|
42
43
|
workspaceId: string;
|
|
44
|
+
/** Operator-assigned Herdr name; distinct from the changing terminal title. */
|
|
45
|
+
name?: string | null;
|
|
43
46
|
label: string | null;
|
|
44
47
|
/** Detected/reported agent label, e.g. "claude". Null for plain shells. */
|
|
45
48
|
agent: string | null;
|
package/src/transports.ts
CHANGED
|
@@ -112,6 +112,14 @@ export interface ChannelInviteRevokeCommand {
|
|
|
112
112
|
revokedAt: number;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
export interface ChannelMemberRemoveCommand {
|
|
116
|
+
kind: "channel.member.remove";
|
|
117
|
+
channelId: ScoutId;
|
|
118
|
+
actorId: ScoutId;
|
|
119
|
+
removedByActorId: ScoutId;
|
|
120
|
+
removedAt: number;
|
|
121
|
+
}
|
|
122
|
+
|
|
115
123
|
export interface ChannelInviteRedeemCommand {
|
|
116
124
|
kind: "channel.invite.redeem";
|
|
117
125
|
channelId: ScoutId;
|
|
@@ -134,6 +142,7 @@ export type ControlCommand =
|
|
|
134
142
|
| ChannelInviteCreateCommand
|
|
135
143
|
| ChannelInviteRevokeCommand
|
|
136
144
|
| ChannelInviteRedeemCommand
|
|
145
|
+
| ChannelMemberRemoveCommand
|
|
137
146
|
| PostMessageCommand
|
|
138
147
|
| InvokeAgentCommand
|
|
139
148
|
| EnsureAwakeCommand
|