@openscout/protocol 0.2.109 → 0.3.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.
Files changed (101) hide show
  1. package/dist/actors.d.ts +1 -1
  2. package/dist/actors.d.ts.map +1 -1
  3. package/dist/agent-notifications.d.ts +137 -0
  4. package/dist/agent-notifications.d.ts.map +1 -0
  5. package/dist/agent-notifications.js +148 -0
  6. package/dist/channel-invites.d.ts +3 -1
  7. package/dist/channel-invites.d.ts.map +1 -1
  8. package/dist/channel-membership.d.ts +10 -0
  9. package/dist/channel-membership.d.ts.map +1 -0
  10. package/dist/channel-membership.js +16 -0
  11. package/dist/chat-mentions.d.ts +4 -0
  12. package/dist/chat-mentions.d.ts.map +1 -0
  13. package/dist/chat-mentions.js +11 -0
  14. package/dist/chat-message-corrections.d.ts +28 -0
  15. package/dist/chat-message-corrections.d.ts.map +1 -0
  16. package/dist/chat-message-corrections.js +46 -0
  17. package/dist/chat-pins.d.ts +14 -0
  18. package/dist/chat-pins.d.ts.map +1 -0
  19. package/dist/chat-pins.js +37 -0
  20. package/dist/common.d.ts +1 -1
  21. package/dist/common.d.ts.map +1 -1
  22. package/dist/events.d.ts +4 -1
  23. package/dist/events.d.ts.map +1 -1
  24. package/dist/herdr-workspace-digest.d.ts +8 -4
  25. package/dist/herdr-workspace-digest.d.ts.map +1 -1
  26. package/dist/herdr-workspace-digest.js +18 -3
  27. package/dist/host-integrations.js +6 -6
  28. package/dist/index.d.ts +6 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +6 -0
  31. package/dist/invocations.d.ts +17 -2
  32. package/dist/invocations.d.ts.map +1 -1
  33. package/dist/invocations.js +1 -1
  34. package/dist/mcp-core-tools.d.ts +137 -0
  35. package/dist/mcp-core-tools.d.ts.map +1 -0
  36. package/dist/mcp-core-tools.js +27 -0
  37. package/dist/message-reactions.d.ts +5 -0
  38. package/dist/message-reactions.d.ts.map +1 -1
  39. package/dist/message-reactions.js +10 -1
  40. package/dist/orchestration-provider-map.d.ts.map +1 -1
  41. package/dist/orchestration-provider-map.js +9 -1
  42. package/dist/read-receipts.d.ts +38 -0
  43. package/dist/read-receipts.d.ts.map +1 -1
  44. package/dist/read-receipts.js +43 -1
  45. package/dist/runtime-catalog-contract.d.ts +14 -0
  46. package/dist/runtime-catalog-contract.d.ts.map +1 -1
  47. package/dist/runtime-catalog.d.ts.map +1 -1
  48. package/dist/runtime-catalog.generated.d.ts +88 -2
  49. package/dist/runtime-catalog.generated.d.ts.map +1 -1
  50. package/dist/runtime-catalog.generated.js +114 -12
  51. package/dist/runtime-catalog.js +28 -0
  52. package/dist/runtime-execution.d.ts +33 -2
  53. package/dist/runtime-execution.d.ts.map +1 -1
  54. package/dist/runtime-execution.js +34 -17
  55. package/dist/scout-agent-card.d.ts +2 -0
  56. package/dist/scout-agent-card.d.ts.map +1 -1
  57. package/dist/scout-agent-card.js +3 -0
  58. package/dist/scout-composer.d.ts.map +1 -1
  59. package/dist/scout-composer.js +23 -0
  60. package/dist/scout-delivery.d.ts +3 -1
  61. package/dist/scout-delivery.d.ts.map +1 -1
  62. package/dist/scout-dispatch.d.ts +13 -0
  63. package/dist/scout-dispatch.d.ts.map +1 -1
  64. package/dist/session-handle.d.ts +37 -0
  65. package/dist/session-handle.d.ts.map +1 -1
  66. package/dist/session-handle.js +58 -0
  67. package/dist/terminal-host-topology.d.ts +5 -2
  68. package/dist/terminal-host-topology.d.ts.map +1 -1
  69. package/dist/terminal-host-topology.js +2 -1
  70. package/dist/transports.d.ts +8 -1
  71. package/dist/transports.d.ts.map +1 -1
  72. package/package.json +4 -4
  73. package/src/actors.ts +2 -0
  74. package/src/agent-notifications.ts +241 -0
  75. package/src/channel-invites.ts +4 -1
  76. package/src/channel-membership.ts +22 -0
  77. package/src/chat-mentions.ts +11 -0
  78. package/src/chat-message-corrections.ts +40 -0
  79. package/src/chat-pins.ts +40 -0
  80. package/src/common.ts +2 -0
  81. package/src/events.ts +3 -0
  82. package/src/herdr-workspace-digest.ts +23 -6
  83. package/src/host-integrations.ts +6 -6
  84. package/src/index.ts +10 -0
  85. package/src/invocations.ts +18 -3
  86. package/src/mcp-core-tools.ts +46 -0
  87. package/src/message-reactions.ts +16 -2
  88. package/src/orchestration-provider-map.ts +12 -1
  89. package/src/read-receipts.ts +71 -0
  90. package/src/runtime-catalog-contract.ts +16 -0
  91. package/src/runtime-catalog.generated.ts +114 -12
  92. package/src/runtime-catalog.ts +32 -0
  93. package/src/runtime-catalog.v1.json +20 -13
  94. package/src/runtime-execution.ts +70 -16
  95. package/src/scout-agent-card.ts +5 -0
  96. package/src/scout-composer.ts +26 -0
  97. package/src/scout-delivery.ts +3 -1
  98. package/src/scout-dispatch.ts +13 -0
  99. package/src/session-handle.ts +88 -0
  100. package/src/terminal-host-topology.ts +5 -2
  101. package/src/transports.ts +9 -0
@@ -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 aliases: Record<string, string> = {
338
- fable: "claude-fable-5",
339
- opus: "claude-opus-5",
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
- if (harness && !isScoutLaunchableHarness(harness)) {
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: ${SCOUT_LAUNCHABLE_HARNESSES.join(", ")}`,
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
- const modelSelectableHarness = harness === "claude"
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",
@@ -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
  }
@@ -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
 
@@ -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 needs-you surface.
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;
@@ -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
 
@@ -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, plus the per-tab layout geometry from
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 stable terminal id; `herdr agent <verb>` accepts it as a target. */
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