@openscout/protocol 0.2.63 → 0.2.82

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 (216) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +103 -52
  3. package/dist/a2a.d.ts +224 -0
  4. package/dist/a2a.d.ts.map +1 -0
  5. package/dist/a2a.js +225 -0
  6. package/dist/activity.d.ts +70 -0
  7. package/dist/activity.d.ts.map +1 -0
  8. package/dist/activity.js +1 -0
  9. package/dist/actors.d.ts +7 -3
  10. package/dist/actors.d.ts.map +1 -0
  11. package/dist/actors.js +15 -1
  12. package/dist/agent-broker-feed.d.ts +84 -0
  13. package/dist/agent-broker-feed.d.ts.map +1 -0
  14. package/dist/agent-broker-feed.js +1 -0
  15. package/dist/agent-identity.d.ts +23 -0
  16. package/dist/agent-identity.d.ts.map +1 -0
  17. package/dist/agent-identity.js +122 -8
  18. package/dist/agent-runs.d.ts +191 -0
  19. package/dist/agent-runs.d.ts.map +1 -0
  20. package/dist/agent-runs.js +365 -0
  21. package/dist/assigned-roles.d.ts +205 -0
  22. package/dist/assigned-roles.d.ts.map +1 -0
  23. package/dist/assigned-roles.js +228 -0
  24. package/dist/broker-routes.d.ts +40 -0
  25. package/dist/broker-routes.d.ts.map +1 -0
  26. package/dist/broker-routes.js +56 -0
  27. package/dist/budget.d.ts +65 -0
  28. package/dist/budget.d.ts.map +1 -0
  29. package/dist/budget.js +19 -0
  30. package/dist/capability-matrix.d.ts +230 -0
  31. package/dist/capability-matrix.d.ts.map +1 -0
  32. package/dist/capability-matrix.js +304 -0
  33. package/dist/channel-identity.d.ts +40 -0
  34. package/dist/channel-identity.d.ts.map +1 -0
  35. package/dist/channel-identity.js +108 -0
  36. package/dist/collaboration.d.ts +45 -16
  37. package/dist/collaboration.d.ts.map +1 -0
  38. package/dist/collaboration.js +39 -23
  39. package/dist/common.d.ts +4 -3
  40. package/dist/common.d.ts.map +1 -0
  41. package/dist/conversations.d.ts +1 -0
  42. package/dist/conversations.d.ts.map +1 -0
  43. package/dist/cursor-transport.d.ts +22 -0
  44. package/dist/cursor-transport.d.ts.map +1 -0
  45. package/dist/cursor-transport.js +10 -0
  46. package/dist/debug-attach.d.ts +29 -0
  47. package/dist/debug-attach.d.ts.map +1 -0
  48. package/dist/debug-attach.js +6 -0
  49. package/dist/deliveries.d.ts +16 -2
  50. package/dist/deliveries.d.ts.map +1 -0
  51. package/dist/durable-actions.d.ts +94 -0
  52. package/dist/durable-actions.d.ts.map +1 -0
  53. package/dist/durable-actions.js +1 -0
  54. package/dist/events.d.ts +27 -1
  55. package/dist/events.d.ts.map +1 -0
  56. package/dist/events.js +14 -1
  57. package/dist/harness-feature-support.d.ts +76 -0
  58. package/dist/harness-feature-support.d.ts.map +1 -0
  59. package/dist/harness-feature-support.js +29 -0
  60. package/dist/host-integrations.d.ts +22 -0
  61. package/dist/host-integrations.d.ts.map +1 -0
  62. package/dist/host-integrations.js +64 -0
  63. package/dist/inbox.d.ts +50 -0
  64. package/dist/inbox.d.ts.map +1 -0
  65. package/dist/inbox.js +9 -0
  66. package/dist/index.d.ts +36 -2
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +35 -2
  69. package/dist/invocations.d.ts +121 -0
  70. package/dist/invocations.d.ts.map +1 -0
  71. package/dist/invocations.js +201 -1
  72. package/dist/mesh.d.ts +98 -0
  73. package/dist/mesh.d.ts.map +1 -0
  74. package/dist/mesh.js +18 -1
  75. package/dist/messages.d.ts +3 -0
  76. package/dist/messages.d.ts.map +1 -0
  77. package/dist/native-interactions.d.ts +30 -0
  78. package/dist/native-interactions.d.ts.map +1 -0
  79. package/dist/native-interactions.js +51 -0
  80. package/dist/observed-status.d.ts +33 -0
  81. package/dist/observed-status.d.ts.map +1 -0
  82. package/dist/observed-status.js +6 -0
  83. package/dist/orchestrator-prompt.d.ts +10 -0
  84. package/dist/orchestrator-prompt.d.ts.map +1 -0
  85. package/dist/orchestrator-prompt.js +36 -0
  86. package/dist/permission-policy.d.ts +5 -0
  87. package/dist/permission-policy.d.ts.map +1 -0
  88. package/dist/permission-policy.js +15 -0
  89. package/dist/provisional-agent-names.d.ts +30 -0
  90. package/dist/provisional-agent-names.d.ts.map +1 -0
  91. package/dist/provisional-agent-names.js +224 -0
  92. package/dist/read-receipts.d.ts +12 -0
  93. package/dist/read-receipts.d.ts.map +1 -0
  94. package/dist/read-receipts.js +1 -0
  95. package/dist/relay-agent-card.d.ts +10 -0
  96. package/dist/relay-agent-card.d.ts.map +1 -0
  97. package/dist/rendezvous.d.ts +97 -0
  98. package/dist/rendezvous.d.ts.map +1 -0
  99. package/dist/rendezvous.js +35 -0
  100. package/dist/reserved-vocabulary.d.ts +21 -0
  101. package/dist/reserved-vocabulary.d.ts.map +1 -0
  102. package/dist/reserved-vocabulary.js +101 -0
  103. package/dist/route-aliases.d.ts +112 -0
  104. package/dist/route-aliases.d.ts.map +1 -0
  105. package/dist/route-aliases.js +1 -0
  106. package/dist/runtime-catalog-contract.d.ts +39 -0
  107. package/dist/runtime-catalog-contract.d.ts.map +1 -0
  108. package/dist/runtime-catalog-contract.js +17 -0
  109. package/dist/runtime-catalog.d.ts +11 -0
  110. package/dist/runtime-catalog.d.ts.map +1 -0
  111. package/dist/runtime-catalog.generated.d.ts +230 -0
  112. package/dist/runtime-catalog.generated.d.ts.map +1 -0
  113. package/dist/runtime-catalog.generated.js +299 -0
  114. package/dist/runtime-catalog.js +192 -0
  115. package/dist/runtime-execution.d.ts +161 -0
  116. package/dist/runtime-execution.d.ts.map +1 -0
  117. package/dist/runtime-execution.js +327 -0
  118. package/dist/runtime-profiles.d.ts +12 -0
  119. package/dist/runtime-profiles.d.ts.map +1 -0
  120. package/dist/runtime-profiles.js +20 -0
  121. package/dist/scout-agent-card.d.ts +10 -0
  122. package/dist/scout-agent-card.d.ts.map +1 -0
  123. package/dist/scout-composer.d.ts +27 -0
  124. package/dist/scout-composer.d.ts.map +1 -0
  125. package/dist/scout-composer.js +192 -0
  126. package/dist/scout-delivery.d.ts +101 -4
  127. package/dist/scout-delivery.d.ts.map +1 -0
  128. package/dist/scout-dispatch.d.ts +44 -3
  129. package/dist/scout-dispatch.d.ts.map +1 -0
  130. package/dist/scout-reply-context.d.ts +14 -0
  131. package/dist/scout-reply-context.d.ts.map +1 -0
  132. package/dist/scout-reply-context.js +1 -0
  133. package/dist/session-display-state.d.ts +132 -0
  134. package/dist/session-display-state.d.ts.map +1 -0
  135. package/dist/session-display-state.js +94 -0
  136. package/dist/terminal-host-topology.d.ts +119 -0
  137. package/dist/terminal-host-topology.d.ts.map +1 -0
  138. package/dist/terminal-host-topology.js +21 -0
  139. package/dist/terminal-sessions.d.ts +142 -0
  140. package/dist/terminal-sessions.d.ts.map +1 -0
  141. package/dist/terminal-sessions.js +81 -0
  142. package/dist/terminal-surface-id.d.ts +73 -0
  143. package/dist/terminal-surface-id.d.ts.map +1 -0
  144. package/dist/terminal-surface-id.js +162 -0
  145. package/dist/terminal-workspaces.d.ts +199 -0
  146. package/dist/terminal-workspaces.d.ts.map +1 -0
  147. package/dist/terminal-workspaces.js +287 -0
  148. package/dist/thread-events.d.ts +1 -0
  149. package/dist/thread-events.d.ts.map +1 -0
  150. package/dist/time.d.ts +16 -0
  151. package/dist/time.d.ts.map +1 -0
  152. package/dist/time.js +33 -0
  153. package/dist/transports.d.ts +1 -0
  154. package/dist/transports.d.ts.map +1 -0
  155. package/package.json +37 -9
  156. package/src/a2a.ts +471 -0
  157. package/src/activity.ts +113 -0
  158. package/src/actors.ts +42 -8
  159. package/src/agent-broker-feed.ts +102 -0
  160. package/src/agent-identity.ts +143 -8
  161. package/src/agent-runs.ts +675 -0
  162. package/src/assigned-roles.ts +437 -0
  163. package/src/broker-routes.ts +62 -0
  164. package/src/budget.ts +88 -0
  165. package/src/capability-matrix.ts +648 -0
  166. package/src/channel-identity.ts +147 -0
  167. package/src/collaboration.ts +78 -48
  168. package/src/common.ts +24 -5
  169. package/src/cursor-transport.ts +34 -0
  170. package/src/debug-attach.ts +38 -0
  171. package/src/deliveries.ts +16 -1
  172. package/src/durable-actions.ts +125 -0
  173. package/src/events.ts +31 -0
  174. package/src/harness-feature-support.ts +116 -0
  175. package/src/host-integrations.ts +104 -0
  176. package/src/inbox.ts +68 -0
  177. package/src/index.ts +35 -2
  178. package/src/invocations.ts +353 -0
  179. package/src/mesh.ts +130 -0
  180. package/src/messages.ts +2 -0
  181. package/src/native-interactions.ts +100 -0
  182. package/src/observed-status.ts +90 -0
  183. package/src/orchestrator-prompt.ts +47 -0
  184. package/src/permission-policy.ts +21 -0
  185. package/src/provisional-agent-names.ts +272 -0
  186. package/src/read-receipts.ts +12 -0
  187. package/src/relay-agent-card.ts +73 -63
  188. package/src/rendezvous.ts +154 -0
  189. package/src/reserved-vocabulary.ts +135 -0
  190. package/src/route-aliases.ts +130 -0
  191. package/src/runtime-catalog-contract.ts +60 -0
  192. package/src/runtime-catalog.generated.ts +300 -0
  193. package/src/runtime-catalog.md +21 -0
  194. package/src/runtime-catalog.ts +193 -0
  195. package/src/runtime-catalog.v1.json +81 -0
  196. package/src/runtime-execution.ts +545 -0
  197. package/src/runtime-profiles.ts +37 -0
  198. package/src/scout-agent-card.ts +10 -0
  199. package/src/scout-composer.ts +249 -0
  200. package/src/scout-delivery.ts +117 -4
  201. package/src/scout-dispatch.ts +34 -2
  202. package/src/scout-reply-context.ts +15 -0
  203. package/src/session-display-state.ts +202 -0
  204. package/src/terminal-host-topology.ts +132 -0
  205. package/src/terminal-sessions.ts +187 -0
  206. package/src/terminal-surface-id.ts +189 -0
  207. package/src/terminal-workspaces.ts +455 -0
  208. package/src/time.ts +49 -0
  209. package/dist/agent-address.d.ts +0 -1
  210. package/dist/agent-address.js +0 -1
  211. package/dist/agent-selectors.d.ts +0 -1
  212. package/dist/agent-selectors.js +0 -1
  213. package/src/agent-address.ts +0 -1
  214. package/src/agent-identity.test.ts +0 -435
  215. package/src/agent-selectors.ts +0 -1
  216. package/src/scout-agent-card.test.ts +0 -54
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Terminal session registry.
3
+ *
4
+ * The durable noun is a HARNESS SESSION — a stable agent session identified by
5
+ * its harness-native source id and resumable via a resume command. A harness
6
+ * session is *materialized* through one or more disposable TERMINAL SURFACES
7
+ * (tmux, zellij, future ssh/host-control). Backends are interchangeable: the
8
+ * source session id is the stable key; the surface is the throwaway shell.
9
+ *
10
+ * This module defines the shared contract used by `scout session intake`
11
+ * (which writes the record), the runtime store (which persists it), the
12
+ * web/runtime APIs (which list it + materialize surfaces), and the app
13
+ * terminal UI (which reads it to attach/observe/take over).
14
+ *
15
+ * Terminal scrollback is never imported as Scout messages — a surface is a
16
+ * relay target, not a message source.
17
+ */
18
+ import type { TerminalHostId, TerminalSurfaceId } from "./terminal-surface-id.js";
19
+ /** Lifecycle of a single materialized surface. */
20
+ export type TerminalSurfaceState = "live" | "detached" | "exited";
21
+ /**
22
+ * Relay descriptor for one surface.
23
+ *
24
+ * The per-backend optional fields are legacy: they are what a relay built
25
+ * before host adapters expects to find, and they are only populated for the
26
+ * hosts that predate the registry. A new host adds an adapter, not a field —
27
+ * `backend` plus `sessionName` is the whole contract.
28
+ */
29
+ export type TerminalSurfaceRelay = {
30
+ backend: TerminalHostId;
31
+ sessionName: string;
32
+ tmuxSession?: string;
33
+ zellijSession?: string;
34
+ zellijPaneId?: string;
35
+ };
36
+ /** One disposable terminal surface a harness session has been materialized through. */
37
+ export type TerminalSurface = {
38
+ /**
39
+ * Opaque durable handle for this surface. Absent on records written before
40
+ * surface ids existed; derive one with `terminalSurfaceIdForSurface`.
41
+ */
42
+ surfaceId?: TerminalSurfaceId;
43
+ /** Host that owns the surface. Registered adapters may add hosts the union does not name. */
44
+ backend: TerminalHostId;
45
+ /** Backend session name (e.g. tmux target, zellij session). Secondary metadata. */
46
+ sessionName: string;
47
+ paneId: string | null;
48
+ attachCommand: string[];
49
+ observeCommand: string[] | null;
50
+ relay: TerminalSurfaceRelay;
51
+ /** Lifecycle state; absent means unknown / not yet observed. */
52
+ state?: TerminalSurfaceState;
53
+ /**
54
+ * Zellij requires a short socket dir on macOS (the default $TMPDIR exceeds the
55
+ * Unix socket-path length limit). Any relay attaching to a Scout-created zellij
56
+ * surface must preserve this, or it lands in a different server namespace.
57
+ */
58
+ socketDir?: string;
59
+ };
60
+ /**
61
+ * Durable registry record: a stable harness session plus the disposable
62
+ * terminal surfaces it owns. Re-materializing in another backend appends a
63
+ * surface; it never changes the record identity.
64
+ */
65
+ export type TerminalSessionRecord = {
66
+ /** Stable Scout record id (derived from harness + sourceSessionId by default). */
67
+ id: string;
68
+ harness: string;
69
+ /** Harness-native session id — the stable identity across backends. */
70
+ sourceSessionId: string;
71
+ cwd: string;
72
+ resumeCommand: string;
73
+ /**
74
+ * Where the record came from. `registry` records are Scout-owned harness
75
+ * sessions with a real harness and resume command; `discovered` records are
76
+ * live multiplexer sessions observed on the host, for which both are unknown
77
+ * and must be left empty rather than filled with the backend and attach argv.
78
+ * Absent means `registry`.
79
+ */
80
+ origin?: "registry" | "discovered";
81
+ surfaces: TerminalSurface[];
82
+ createdAt: number;
83
+ updatedAt: number;
84
+ metadata?: Record<string, unknown>;
85
+ };
86
+ /** Upsert input for a registry record. Timestamps + id are assigned by the store. */
87
+ export type TerminalSessionRecordInput = {
88
+ /** Optional explicit id; defaults to a deterministic id from harness + sourceSessionId. */
89
+ id?: string;
90
+ harness: string;
91
+ sourceSessionId: string;
92
+ cwd: string;
93
+ resumeCommand: string;
94
+ surfaces?: TerminalSurface[];
95
+ metadata?: Record<string, unknown>;
96
+ };
97
+ /**
98
+ * The durable handle for a surface. Prefers the stored id and derives the same
99
+ * value from the surface's address otherwise, so a record written before
100
+ * surface ids existed resolves to the id it would be issued today.
101
+ */
102
+ export declare function terminalSurfaceIdForSurface(surface: Pick<TerminalSurface, "surfaceId" | "backend" | "sessionName" | "paneId">, options?: {
103
+ nodeId?: string | null;
104
+ }): TerminalSurfaceId;
105
+ /** Where a match is being evaluated, for the parts of matching that are node-scoped. */
106
+ export type TerminalSurfaceMatchScope = {
107
+ /**
108
+ * This host's own Scout node id, when the caller knows it. A surface observed
109
+ * with no node in its id was observed HERE, so this is the only node whose
110
+ * handles may claim it.
111
+ */
112
+ localNodeId?: string | null;
113
+ };
114
+ /**
115
+ * Whether a handle scoped to `handleNodeId` may address a surface scoped to
116
+ * `surfaceNodeId`.
117
+ *
118
+ * Three cases, and the third is the one a review reproduced:
119
+ *
120
+ * - Both sides name a node: they must name the same one. Without this a handle
121
+ * for one node matched an identically named session on another.
122
+ * - The handle names none: it is a legacy key, which names no machine and must
123
+ * keep matching whatever it finds.
124
+ * - The handle names a node and the OBSERVATION does not. A node-less
125
+ * observation is by definition local — it is a session this host just listed
126
+ * — so only the local node's handle can be the one it belongs to. Letting any
127
+ * node-scoped handle match it meant handles for `node-a` and `node-b` both
128
+ * bound to one discovered local session, and both workspaces reported it
129
+ * Running: one session proving two machines live. A foreign handle never
130
+ * matches, and when this host does not know its own node id it cannot prove
131
+ * the handle is local either, so it does not claim it.
132
+ */
133
+ export declare function terminalSurfaceNodeScopeMatches(input: {
134
+ handleNodeId: string | null;
135
+ surfaceNodeId: string | null;
136
+ localNodeId?: string | null;
137
+ }): boolean;
138
+ /** The node a surface belongs to, which lives inside its own opaque id. */
139
+ export declare function terminalSurfaceNodeId(surface: Pick<TerminalSurface, "surfaceId">): string | null;
140
+ /** True when a handle addresses this surface, in either the opaque or legacy form. */
141
+ export declare function terminalSurfaceMatchesId(surface: Pick<TerminalSurface, "surfaceId" | "backend" | "sessionName" | "paneId">, handle: string | null | undefined, scope?: TerminalSurfaceMatchScope): boolean;
142
+ //# sourceMappingURL=terminal-sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-sessions.d.ts","sourceRoot":"","sources":["../src/terminal-sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Terminal session registry.
3
+ *
4
+ * The durable noun is a HARNESS SESSION — a stable agent session identified by
5
+ * its harness-native source id and resumable via a resume command. A harness
6
+ * session is *materialized* through one or more disposable TERMINAL SURFACES
7
+ * (tmux, zellij, future ssh/host-control). Backends are interchangeable: the
8
+ * source session id is the stable key; the surface is the throwaway shell.
9
+ *
10
+ * This module defines the shared contract used by `scout session intake`
11
+ * (which writes the record), the runtime store (which persists it), the
12
+ * web/runtime APIs (which list it + materialize surfaces), and the app
13
+ * terminal UI (which reads it to attach/observe/take over).
14
+ *
15
+ * Terminal scrollback is never imported as Scout messages — a surface is a
16
+ * relay target, not a message source.
17
+ */
18
+ import { formatTerminalSurfaceId, parseTerminalSurfaceId } from "./terminal-surface-id.js";
19
+ /**
20
+ * The durable handle for a surface. Prefers the stored id and derives the same
21
+ * value from the surface's address otherwise, so a record written before
22
+ * surface ids existed resolves to the id it would be issued today.
23
+ */
24
+ export function terminalSurfaceIdForSurface(surface, options = {}) {
25
+ if (surface.surfaceId)
26
+ return surface.surfaceId;
27
+ return formatTerminalSurfaceId({
28
+ backend: surface.backend,
29
+ hostSession: surface.sessionName,
30
+ paneId: surface.paneId,
31
+ nodeId: options.nodeId ?? null,
32
+ });
33
+ }
34
+ /**
35
+ * Whether a handle scoped to `handleNodeId` may address a surface scoped to
36
+ * `surfaceNodeId`.
37
+ *
38
+ * Three cases, and the third is the one a review reproduced:
39
+ *
40
+ * - Both sides name a node: they must name the same one. Without this a handle
41
+ * for one node matched an identically named session on another.
42
+ * - The handle names none: it is a legacy key, which names no machine and must
43
+ * keep matching whatever it finds.
44
+ * - The handle names a node and the OBSERVATION does not. A node-less
45
+ * observation is by definition local — it is a session this host just listed
46
+ * — so only the local node's handle can be the one it belongs to. Letting any
47
+ * node-scoped handle match it meant handles for `node-a` and `node-b` both
48
+ * bound to one discovered local session, and both workspaces reported it
49
+ * Running: one session proving two machines live. A foreign handle never
50
+ * matches, and when this host does not know its own node id it cannot prove
51
+ * the handle is local either, so it does not claim it.
52
+ */
53
+ export function terminalSurfaceNodeScopeMatches(input) {
54
+ if (input.handleNodeId === null)
55
+ return true;
56
+ if (input.surfaceNodeId !== null)
57
+ return input.handleNodeId === input.surfaceNodeId;
58
+ const localNodeId = input.localNodeId?.trim() || null;
59
+ return localNodeId !== null && input.handleNodeId === localNodeId;
60
+ }
61
+ /** The node a surface belongs to, which lives inside its own opaque id. */
62
+ export function terminalSurfaceNodeId(surface) {
63
+ return parseTerminalSurfaceId(surface.surfaceId)?.nodeId ?? null;
64
+ }
65
+ /** True when a handle addresses this surface, in either the opaque or legacy form. */
66
+ export function terminalSurfaceMatchesId(surface, handle, scope = {}) {
67
+ const address = parseTerminalSurfaceId(handle);
68
+ if (!address)
69
+ return false;
70
+ if (address.backend !== surface.backend || address.hostSession !== surface.sessionName)
71
+ return false;
72
+ // A legacy key carries no pane, so it matches any pane on the session; an
73
+ // opaque id that names a pane must match it exactly.
74
+ if (address.paneId !== null && address.paneId !== (surface.paneId ?? null))
75
+ return false;
76
+ return terminalSurfaceNodeScopeMatches({
77
+ handleNodeId: address.nodeId ?? null,
78
+ surfaceNodeId: terminalSurfaceNodeId(surface),
79
+ localNodeId: scope.localNodeId,
80
+ });
81
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Terminal surface identity.
3
+ *
4
+ * A surface id is an opaque token that names one materialized terminal surface:
5
+ * a host session (and optionally a pane) on a host backend, on a node. It is
6
+ * the durable handle a saved workspace cell, a deep link, or a route alias
7
+ * holds onto.
8
+ *
9
+ * Two rules make it durable, and both are the point of this module:
10
+ *
11
+ * 1. Identity comes from (node, backend, host session, pane) — never from the
12
+ * display name. Renaming a session used to change its Scout id, because ids
13
+ * were `sha1(name)`.
14
+ * 2. There is exactly ONE constructor ({@link formatTerminalSurfaceId}) and ONE
15
+ * parser ({@link parseTerminalSurfaceId}). Nothing else may split a surface
16
+ * id on a separator. Four different separator conventions had grown across
17
+ * the server, the web client, and the macOS app, each with its own
18
+ * assumptions about which characters could appear in a session name; the
19
+ * only reason they worked was an unrelated validator that happened to
20
+ * exclude `:`.
21
+ *
22
+ * The token is opaque to consumers but not to this module: it encodes its parts
23
+ * so a server can resolve one without a lookup table, and so a client that
24
+ * receives an id it has never seen can still be routed. Legacy `backend:name`
25
+ * keys parse too, so persisted workspaces and in-flight URLs keep working.
26
+ */
27
+ /** Backends with first-class protocol support. */
28
+ export type TerminalBackend = "tmux" | "zellij";
29
+ /**
30
+ * Any terminal host Scout can address. {@link TerminalBackend} is the subset the
31
+ * protocol types name explicitly; adapters may register further hosts (herdr,
32
+ * ssh, host-control) without a protocol change.
33
+ */
34
+ export type TerminalHostId = TerminalBackend | (string & {});
35
+ /** The parts a surface id is built from. */
36
+ export type TerminalSurfaceAddress = {
37
+ /** Host adapter that owns the surface. */
38
+ backend: TerminalHostId;
39
+ /** Backend-native session name. Mutable display metadata, not identity on its own. */
40
+ hostSession: string;
41
+ /** Pane or tab within the host session, when the host addresses one. */
42
+ paneId?: string | null;
43
+ /** Scout node the surface lives on. Null/absent means the local node. */
44
+ nodeId?: string | null;
45
+ };
46
+ /** Opaque surface handle. Never split, slice, or interpolate into a pattern. */
47
+ export type TerminalSurfaceId = string;
48
+ export declare function isTerminalSurfaceId(value: string | null | undefined): boolean;
49
+ /**
50
+ * Build the surface id for an address. Deterministic: the same address always
51
+ * yields the same id, on any node, in any process, so two writers converge on
52
+ * one row instead of forking one.
53
+ */
54
+ export declare function formatTerminalSurfaceId(address: TerminalSurfaceAddress): TerminalSurfaceId;
55
+ /**
56
+ * Resolve a surface id back to its address. Accepts the opaque form and the
57
+ * legacy `backend:sessionName` key. Returns null for anything else rather than
58
+ * guessing — a caller holding an unparseable handle must say so, not fabricate
59
+ * a target.
60
+ */
61
+ export declare function parseTerminalSurfaceId(value: string | null | undefined): TerminalSurfaceAddress | null;
62
+ /**
63
+ * Compare two handles by what they address, not by how they were written, so a
64
+ * legacy key and its opaque replacement resolve to the same tile.
65
+ */
66
+ export declare function terminalSurfaceIdsEqual(left: string | null | undefined, right: string | null | undefined): boolean;
67
+ /**
68
+ * The legacy `backend:sessionName` key for an address. Kept so ids issued by
69
+ * this build stay readable by clients that have not adopted the opaque form
70
+ * (macOS, iOS). Delete once every surface reads {@link TerminalSurfaceId}.
71
+ */
72
+ export declare function legacyTerminalSurfaceKey(address: TerminalSurfaceAddress): string;
73
+ //# sourceMappingURL=terminal-surface-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-surface-id.d.ts","sourceRoot":"","sources":["../src/terminal-surface-id.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,kDAAkD;AAClD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEhD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE7D,4CAA4C;AAC5C,MAAM,MAAM,sBAAsB,GAAG;IACnC,0CAA0C;IAC1C,OAAO,EAAE,cAAc,CAAC;IACxB,sFAAsF;IACtF,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAEF,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAOvC,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE7E;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,sBAAsB,GAAG,iBAAiB,CAS1F;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,sBAAsB,GAAG,IAAI,CAmC/B;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC/B,OAAO,CAQT;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAEhF"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Terminal surface identity.
3
+ *
4
+ * A surface id is an opaque token that names one materialized terminal surface:
5
+ * a host session (and optionally a pane) on a host backend, on a node. It is
6
+ * the durable handle a saved workspace cell, a deep link, or a route alias
7
+ * holds onto.
8
+ *
9
+ * Two rules make it durable, and both are the point of this module:
10
+ *
11
+ * 1. Identity comes from (node, backend, host session, pane) — never from the
12
+ * display name. Renaming a session used to change its Scout id, because ids
13
+ * were `sha1(name)`.
14
+ * 2. There is exactly ONE constructor ({@link formatTerminalSurfaceId}) and ONE
15
+ * parser ({@link parseTerminalSurfaceId}). Nothing else may split a surface
16
+ * id on a separator. Four different separator conventions had grown across
17
+ * the server, the web client, and the macOS app, each with its own
18
+ * assumptions about which characters could appear in a session name; the
19
+ * only reason they worked was an unrelated validator that happened to
20
+ * exclude `:`.
21
+ *
22
+ * The token is opaque to consumers but not to this module: it encodes its parts
23
+ * so a server can resolve one without a lookup table, and so a client that
24
+ * receives an id it has never seen can still be routed. Legacy `backend:name`
25
+ * keys parse too, so persisted workspaces and in-flight URLs keep working.
26
+ */
27
+ const SURFACE_ID_PREFIX = "srf1.";
28
+ /** Legacy `backend:sessionName` keys, still written by older clients and links. */
29
+ const LEGACY_SURFACE_KEY = /^(?<backend>[A-Za-z][A-Za-z0-9_-]*):(?<hostSession>.+)$/u;
30
+ const BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
31
+ export function isTerminalSurfaceId(value) {
32
+ return typeof value === "string" && value.startsWith(SURFACE_ID_PREFIX);
33
+ }
34
+ /**
35
+ * Build the surface id for an address. Deterministic: the same address always
36
+ * yields the same id, on any node, in any process, so two writers converge on
37
+ * one row instead of forking one.
38
+ */
39
+ export function formatTerminalSurfaceId(address) {
40
+ const backend = address.backend.trim();
41
+ const hostSession = address.hostSession.trim();
42
+ if (!backend || !hostSession) {
43
+ throw new Error("a terminal surface id needs a backend and a host session");
44
+ }
45
+ const paneId = address.paneId?.trim() || null;
46
+ const nodeId = address.nodeId?.trim() || null;
47
+ return `${SURFACE_ID_PREFIX}${encodeBase64Url(JSON.stringify([nodeId, backend, hostSession, paneId]))}`;
48
+ }
49
+ /**
50
+ * Resolve a surface id back to its address. Accepts the opaque form and the
51
+ * legacy `backend:sessionName` key. Returns null for anything else rather than
52
+ * guessing — a caller holding an unparseable handle must say so, not fabricate
53
+ * a target.
54
+ */
55
+ export function parseTerminalSurfaceId(value) {
56
+ const trimmed = value?.trim();
57
+ if (!trimmed)
58
+ return null;
59
+ if (trimmed.startsWith(SURFACE_ID_PREFIX)) {
60
+ const decoded = decodeBase64Url(trimmed.slice(SURFACE_ID_PREFIX.length));
61
+ if (decoded === null)
62
+ return null;
63
+ let parsed;
64
+ try {
65
+ parsed = JSON.parse(decoded);
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ if (!Array.isArray(parsed) || parsed.length < 3)
71
+ return null;
72
+ const [nodeId, backend, hostSession, paneId] = parsed;
73
+ if (typeof backend !== "string" || !backend.trim())
74
+ return null;
75
+ if (typeof hostSession !== "string" || !hostSession.trim())
76
+ return null;
77
+ return {
78
+ backend: backend.trim(),
79
+ hostSession: hostSession.trim(),
80
+ paneId: typeof paneId === "string" && paneId.trim() ? paneId.trim() : null,
81
+ nodeId: typeof nodeId === "string" && nodeId.trim() ? nodeId.trim() : null,
82
+ };
83
+ }
84
+ const legacy = LEGACY_SURFACE_KEY.exec(trimmed);
85
+ if (!legacy?.groups)
86
+ return null;
87
+ const hostSession = legacy.groups.hostSession.trim();
88
+ if (!hostSession)
89
+ return null;
90
+ return {
91
+ backend: legacy.groups.backend,
92
+ hostSession,
93
+ paneId: null,
94
+ nodeId: null,
95
+ };
96
+ }
97
+ /**
98
+ * Compare two handles by what they address, not by how they were written, so a
99
+ * legacy key and its opaque replacement resolve to the same tile.
100
+ */
101
+ export function terminalSurfaceIdsEqual(left, right) {
102
+ const leftAddress = parseTerminalSurfaceId(left);
103
+ const rightAddress = parseTerminalSurfaceId(right);
104
+ if (!leftAddress || !rightAddress)
105
+ return false;
106
+ return leftAddress.backend === rightAddress.backend
107
+ && leftAddress.hostSession === rightAddress.hostSession
108
+ && (leftAddress.paneId ?? null) === (rightAddress.paneId ?? null)
109
+ && (leftAddress.nodeId ?? null) === (rightAddress.nodeId ?? null);
110
+ }
111
+ /**
112
+ * The legacy `backend:sessionName` key for an address. Kept so ids issued by
113
+ * this build stay readable by clients that have not adopted the opaque form
114
+ * (macOS, iOS). Delete once every surface reads {@link TerminalSurfaceId}.
115
+ */
116
+ export function legacyTerminalSurfaceKey(address) {
117
+ return `${address.backend}:${address.hostSession}`;
118
+ }
119
+ // Base64url over UTF-8, hand-rolled so this module runs unchanged in a browser
120
+ // bundle, a Bun server, and a Node relay without Buffer or atob/btoa.
121
+ function encodeBase64Url(value) {
122
+ const bytes = new TextEncoder().encode(value);
123
+ let out = "";
124
+ for (let index = 0; index < bytes.length; index += 3) {
125
+ const byte0 = bytes[index];
126
+ const byte1 = bytes[index + 1];
127
+ const byte2 = bytes[index + 2];
128
+ out += BASE64URL_ALPHABET[byte0 >> 2];
129
+ out += BASE64URL_ALPHABET[((byte0 & 0b11) << 4) | ((byte1 ?? 0) >> 4)];
130
+ if (byte1 === undefined)
131
+ break;
132
+ out += BASE64URL_ALPHABET[((byte1 & 0b1111) << 2) | ((byte2 ?? 0) >> 6)];
133
+ if (byte2 === undefined)
134
+ break;
135
+ out += BASE64URL_ALPHABET[byte2 & 0b111111];
136
+ }
137
+ return out;
138
+ }
139
+ function decodeBase64Url(value) {
140
+ if (!value)
141
+ return null;
142
+ const bytes = [];
143
+ let buffer = 0;
144
+ let bits = 0;
145
+ for (const character of value) {
146
+ const index = BASE64URL_ALPHABET.indexOf(character);
147
+ if (index < 0)
148
+ return null;
149
+ buffer = (buffer << 6) | index;
150
+ bits += 6;
151
+ if (bits >= 8) {
152
+ bits -= 8;
153
+ bytes.push((buffer >> bits) & 0xff);
154
+ }
155
+ }
156
+ try {
157
+ return new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes));
158
+ }
159
+ catch {
160
+ return null;
161
+ }
162
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Durable terminal workspaces.
3
+ *
4
+ * A workspace is a named, server-owned arrangement of agent-CLI tiles. It is
5
+ * the Scout object the three clients had each invented privately —
6
+ * `openscout.terminal.workspaces.v1` in localStorage,
7
+ * `scout.terminals.workspaces.v1` in UserDefaults, and nothing at all on iOS —
8
+ * with the same version number, the same idea, and no synchronization.
9
+ *
10
+ * Where this stands TODAY, so nobody reads more into it than is built: the web
11
+ * client treats the record as its source of truth, seeding its deck from it and
12
+ * writing changes back. macOS still keeps its own UserDefaults store and has no
13
+ * reference to `/api/terminal-workspaces`, and iOS has nothing. "One workspace
14
+ * on all three clients" is the direction this type exists to make possible, not
15
+ * something it has delivered; the other two clients follow.
16
+ *
17
+ * The load-bearing design decision is that a cell stores INTENT, not just a
18
+ * binding. A saved cell that only remembers "tmux session scout-tmux-cell-7"
19
+ * is worth nothing after a reboot: tmux is empty and the name resolves to
20
+ * nothing. A cell that also remembers which host to use, what directory, and
21
+ * how to resume the harness can be rebuilt. This is the same conclusion
22
+ * tmux-resurrect/continuum reached — persist intent and replay it, never
23
+ * process state — and what macOS's `restoreCommandLine` already does in a
24
+ * cruder form.
25
+ */
26
+ import type { TerminalSessionRecord, TerminalSurface } from "./terminal-sessions.js";
27
+ import type { TerminalHostId, TerminalSurfaceId } from "./terminal-surface-id.js";
28
+ /**
29
+ * Everything needed to re-materialize a cell when nothing live matches it.
30
+ * Every field is optional because a workspace authored before a given field
31
+ * existed must still resolve; what a cell cannot say, reconciliation refuses to
32
+ * invent.
33
+ */
34
+ export type TerminalWorkspaceCellIntent = {
35
+ /** Host to materialize on. Absent means "whatever the operator's default is". */
36
+ hostId?: TerminalHostId | null;
37
+ /** Durable per-cell host session name. This is what makes a tile reattach. */
38
+ sessionName?: string | null;
39
+ /** Working directory to open in. */
40
+ cwd?: string | null;
41
+ /** Harness to resume, when the cell is an agent rather than a shell. */
42
+ harness?: string | null;
43
+ /** Harness-native resume command, e.g. `claude --resume <id>`. */
44
+ resumeCommand?: string | null;
45
+ };
46
+ export type TerminalWorkspaceCell = {
47
+ /** Stable cell id. Minted once, at authoring; never derived from a name. */
48
+ id: string;
49
+ /** Durable handle for the surface this cell last bound to. */
50
+ surfaceId?: TerminalSurfaceId | null;
51
+ /** Registry record that surface belonged to, when it had one. */
52
+ terminalSessionId?: string | null;
53
+ intent: TerminalWorkspaceCellIntent;
54
+ };
55
+ /**
56
+ * How a workspace arranges its tiles.
57
+ *
58
+ * Three shapes, not a list of named presets: one tile, N side-by-side lanes, or
59
+ * a 2D grid. The old SOLO/SPLIT/TRIO/QUAD taxonomy conflated the shape with a
60
+ * particular tile count, which is why authoring was capped at four and a
61
+ * nine-tile workspace silently truncated — "Quad" was both a layout and a size.
62
+ * Size is now just how many cells exist.
63
+ */
64
+ export type TerminalWorkspaceLayoutMode = "solo" | "lanes" | "grid";
65
+ /**
66
+ * `"dynamic"` fits the column count to the number of tiles instead of pinning
67
+ * it, so adding a tile re-flows the workspace rather than leaving a hole.
68
+ */
69
+ export type TerminalWorkspaceColumnCount = number | "dynamic";
70
+ export type TerminalWorkspaceLayout = {
71
+ mode: TerminalWorkspaceLayoutMode;
72
+ columns?: TerminalWorkspaceColumnCount;
73
+ };
74
+ export type TerminalWorkspaceRecord = {
75
+ id: string;
76
+ name: string;
77
+ purpose: string;
78
+ /**
79
+ * Resolved column count at the time of writing. Kept for records and clients
80
+ * that predate {@link layout}; `layout` wins when both are present.
81
+ */
82
+ columns: number;
83
+ layout?: TerminalWorkspaceLayout;
84
+ cells: TerminalWorkspaceCell[];
85
+ createdAt: number;
86
+ updatedAt: number;
87
+ metadata?: Record<string, unknown>;
88
+ };
89
+ export type TerminalWorkspaceRecordInput = {
90
+ id?: string;
91
+ name: string;
92
+ purpose?: string;
93
+ columns?: number;
94
+ layout?: TerminalWorkspaceLayout;
95
+ cells?: TerminalWorkspaceCell[];
96
+ metadata?: Record<string, unknown>;
97
+ };
98
+ /** Highest column count a workspace may reopen with. */
99
+ export declare const TERMINAL_WORKSPACE_MAX_COLUMNS = 6;
100
+ export declare const TERMINAL_WORKSPACE_DEFAULT_COLUMNS = 2;
101
+ /**
102
+ * The layout a workspace should open with, folding a pre-layout record forward.
103
+ *
104
+ * A record written before layouts existed carries only a column count, so the
105
+ * mode is inferred from how that count relates to the cells: one cell is solo,
106
+ * a count that fits every cell on one row is lanes, anything else was a grid.
107
+ */
108
+ export declare function terminalWorkspaceLayoutOf(input: {
109
+ layout?: TerminalWorkspaceLayout | null;
110
+ columns?: number | null;
111
+ cellCount?: number;
112
+ }): TerminalWorkspaceLayout;
113
+ /**
114
+ * Column count to render with.
115
+ *
116
+ * Solo is always one. A dynamic lane count is the tile count, so every tile
117
+ * gets its own lane; a dynamic grid is the squarest arrangement that holds
118
+ * them. Both clamp, because past a handful of columns a terminal is too narrow
119
+ * to read.
120
+ */
121
+ export declare function resolveTerminalWorkspaceColumns(layout: TerminalWorkspaceLayout, input: {
122
+ tileCount: number;
123
+ }): number;
124
+ export declare function normalizeTerminalWorkspaceColumnCount(value: unknown): TerminalWorkspaceColumnCount;
125
+ /**
126
+ * Read a stored `layout_json` column back, or null when there is nothing
127
+ * trustworthy in it.
128
+ *
129
+ * Every store that persists a workspace decodes the layout through this, and
130
+ * that is the point: `layout_json` was added to the schema and then read by one
131
+ * handle and ignored by the other, so a layout written through the web server
132
+ * came back as `null` through the runtime store. One decoder means one answer.
133
+ *
134
+ * The mode is validated rather than trusted. It flows straight into the
135
+ * client's grid resolver, so a value that is not one of the three shapes must
136
+ * become "no stored layout" — which folds forward from the column count — and
137
+ * not a mode nothing can render.
138
+ */
139
+ export declare function parseTerminalWorkspaceLayoutJson(value: string | null | undefined): TerminalWorkspaceLayout | null;
140
+ export declare function terminalWorkspaceLayoutLabel(layout: TerminalWorkspaceLayout): string;
141
+ export type TerminalWorkspaceCellStatus = "live" | "revivable" | "unavailable";
142
+ /** What reconciliation needs to know about a host, with no host access. */
143
+ export type TerminalWorkspaceHostState = {
144
+ id: TerminalHostId;
145
+ installed: boolean;
146
+ /** Whether Scout can materialize a session on this host headlessly. */
147
+ canCreate: boolean;
148
+ };
149
+ export type TerminalWorkspaceRevivePlan = {
150
+ hostId: TerminalHostId;
151
+ sessionName: string;
152
+ cwd: string | null;
153
+ /** Harness resume command to run inside the revived surface, when known. */
154
+ resumeCommand: string | null;
155
+ };
156
+ export type TerminalWorkspaceCellResolution = {
157
+ cellId: string;
158
+ status: TerminalWorkspaceCellStatus;
159
+ surfaceId: TerminalSurfaceId | null;
160
+ terminalSessionId: string | null;
161
+ surface: TerminalSurface | null;
162
+ /** Operator-facing reason, in product language. */
163
+ detail: string;
164
+ /** Present exactly when the status is `revivable`. */
165
+ revive: TerminalWorkspaceRevivePlan | null;
166
+ };
167
+ export type TerminalWorkspaceResolution = {
168
+ workspaceId: string;
169
+ cells: TerminalWorkspaceCellResolution[];
170
+ liveCount: number;
171
+ revivableCount: number;
172
+ unavailableCount: number;
173
+ };
174
+ /**
175
+ * Map every saved cell to a live surface, a restorable-but-dead one, or an
176
+ * unavailable one.
177
+ *
178
+ * This is the only honest answer to a reboot. tmux and zellij sessions do not
179
+ * survive a restart; a workspace that stored intent can rebuild itself, and one
180
+ * that stored only a session name cannot. Judgement lives here, on the server
181
+ * side of the wire, so all three clients inherit the same answer instead of
182
+ * each re-deriving it — and it never fabricates a record: a cell with nothing
183
+ * to rebuild from reports `unavailable` and says why.
184
+ */
185
+ export declare function reconcileTerminalWorkspace(workspace: TerminalWorkspaceRecord, input: {
186
+ sessions: readonly TerminalSessionRecord[];
187
+ hosts: readonly TerminalWorkspaceHostState[];
188
+ /** Host used when a cell's intent names none. */
189
+ defaultHostId?: TerminalHostId | null;
190
+ /**
191
+ * This host's own Scout node id. The observations passed in were made HERE,
192
+ * so this is the node whose cells they can prove live; a cell scoped to any
193
+ * other node is not answered by them. Absent means the caller cannot say,
194
+ * and then no node-scoped cell is claimed by a node-less observation.
195
+ */
196
+ localNodeId?: string | null;
197
+ }): TerminalWorkspaceResolution;
198
+ export declare function normalizeTerminalWorkspaceColumns(value: unknown): number;
199
+ //# sourceMappingURL=terminal-workspaces.d.ts.map