@actana/sdk 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/core-client.d.ts +616 -0
- package/dist/core-client.d.ts.map +1 -0
- package/dist/core-client.js +1036 -0
- package/dist/core-client.js.map +1 -0
- package/dist/core-link-cursor-storage.d.ts +32 -0
- package/dist/core-link-cursor-storage.d.ts.map +1 -0
- package/dist/core-link-cursor-storage.js +66 -0
- package/dist/core-link-cursor-storage.js.map +1 -0
- package/dist/core-link-frames.d.ts +1123 -0
- package/dist/core-link-frames.d.ts.map +1 -0
- package/dist/core-link-frames.js +349 -0
- package/dist/core-link-frames.js.map +1 -0
- package/dist/core-link-socket.d.ts +54 -0
- package/dist/core-link-socket.d.ts.map +1 -0
- package/dist/core-link-socket.js +74 -0
- package/dist/core-link-socket.js.map +1 -0
- package/dist/core-link-transport.d.ts +177 -0
- package/dist/core-link-transport.d.ts.map +1 -0
- package/dist/core-link-transport.js +432 -0
- package/dist/core-link-transport.js.map +1 -0
- package/dist/core-registration-blob.d.ts +52 -0
- package/dist/core-registration-blob.d.ts.map +1 -0
- package/dist/core-registration-blob.js +61 -0
- package/dist/core-registration-blob.js.map +1 -0
- package/dist/core-session.d.ts +321 -0
- package/dist/core-session.d.ts.map +1 -0
- package/dist/core-session.js +660 -0
- package/dist/core-session.js.map +1 -0
- package/dist/durable-core-client.d.ts +172 -0
- package/dist/durable-core-client.d.ts.map +1 -0
- package/dist/durable-core-client.js +264 -0
- package/dist/durable-core-client.js.map +1 -0
- package/dist/terminal-screen.d.ts +139 -0
- package/dist/terminal-screen.d.ts.map +1 -0
- package/dist/terminal-screen.js +807 -0
- package/dist/terminal-screen.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,1123 @@
|
|
|
1
|
+
export type CoreLinkPtySpawnHarness = "claude-code" | "codex" | "cursor-cli" | "opencode";
|
|
2
|
+
export type CoreLinkBaseSpawnOptions = {
|
|
3
|
+
taskId: string;
|
|
4
|
+
cwd: string;
|
|
5
|
+
command: string;
|
|
6
|
+
args?: string[];
|
|
7
|
+
cols?: number;
|
|
8
|
+
rows?: number;
|
|
9
|
+
mcEnv?: {
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
token?: string;
|
|
12
|
+
};
|
|
13
|
+
missionControlTheme?: "dark" | "light";
|
|
14
|
+
/**
|
|
15
|
+
* Discriminant for the VM Shell Session mode (issue 06). `never` here keeps
|
|
16
|
+
* agent/shell spawns out of that branch; {@link CoreLinkShellSessionSpawnOptions}
|
|
17
|
+
* sets it to `true`. Declared on the base so `opts.shellSession` is readable
|
|
18
|
+
* on the whole {@link CoreLinkPtySpawnOptions} union for a type-safe
|
|
19
|
+
* discriminated narrow (no `as` cast needed at the dispatch site).
|
|
20
|
+
*/
|
|
21
|
+
shellSession?: never;
|
|
22
|
+
};
|
|
23
|
+
export type CoreLinkHarnessSpawnOptions = CoreLinkBaseSpawnOptions & {
|
|
24
|
+
agent: CoreLinkPtySpawnHarness;
|
|
25
|
+
dangerouslySkipPermissions?: boolean;
|
|
26
|
+
shell?: never;
|
|
27
|
+
initialInput?: string;
|
|
28
|
+
};
|
|
29
|
+
export type CoreLinkShellSpawnOptions = CoreLinkBaseSpawnOptions & {
|
|
30
|
+
shell: true;
|
|
31
|
+
agent?: never;
|
|
32
|
+
dangerouslySkipPermissions?: never;
|
|
33
|
+
home?: boolean;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* A VM Shell Session spawn (issue 06) — a free-form interactive shell on the
|
|
37
|
+
* Core's machine, distinct from agent workspaces. `shellSession: true` is
|
|
38
|
+
* its own spawn mode: no `agent`, no `cwd`/project-root requirement (a VM shell
|
|
39
|
+
* has no project folder). The Core skips the project-root validation it
|
|
40
|
+
* applies to agent spawns and starts a login shell at its own home. Gated by
|
|
41
|
+
* core-link auth (mTLS + bearer), never auto-spawned — opened by an explicit
|
|
42
|
+
* Panel gesture. Streamed back over the same multiplexed core-link; replayable
|
|
43
|
+
* on reconnect like any other PTY.
|
|
44
|
+
*/
|
|
45
|
+
export type CoreLinkShellSessionSpawnOptions = {
|
|
46
|
+
shellSession: true;
|
|
47
|
+
taskId: string;
|
|
48
|
+
/** Optional starting command; empty (or omitted) → interactive login shell. */
|
|
49
|
+
command?: string;
|
|
50
|
+
cols?: number;
|
|
51
|
+
rows?: number;
|
|
52
|
+
missionControlTheme?: "dark" | "light";
|
|
53
|
+
};
|
|
54
|
+
export type CoreLinkPtySpawnOptions = CoreLinkHarnessSpawnOptions | CoreLinkShellSpawnOptions | CoreLinkShellSessionSpawnOptions;
|
|
55
|
+
/**
|
|
56
|
+
* A window of a PTY's replay ring — the answer to a `replay` request. `from` is
|
|
57
|
+
* the seq of the first chunk in `data`, absent when `data` is empty; a caller
|
|
58
|
+
* that asked for a `sinceSeq` and is handed a larger `from` has a hole in front
|
|
59
|
+
* of the tail (see the `replayResult` frame).
|
|
60
|
+
*/
|
|
61
|
+
export type CoreLinkPtyReplay = {
|
|
62
|
+
data: string;
|
|
63
|
+
nextSeq: number;
|
|
64
|
+
from?: number;
|
|
65
|
+
};
|
|
66
|
+
export type CoreLinkPortKillResult = {
|
|
67
|
+
port: number;
|
|
68
|
+
pids: number[];
|
|
69
|
+
killed: number[];
|
|
70
|
+
errors: string[];
|
|
71
|
+
};
|
|
72
|
+
export type CoreLinkLaunchProcessKillResult = {
|
|
73
|
+
ptyCount: number;
|
|
74
|
+
ports: CoreLinkPortKillResult[];
|
|
75
|
+
};
|
|
76
|
+
export type CoreLinkEvent = {
|
|
77
|
+
/** Sequential, monotonic, per-Core. Never 0. */
|
|
78
|
+
eventId: number;
|
|
79
|
+
/** Wall-clock ms when the event was appended. */
|
|
80
|
+
ts: number;
|
|
81
|
+
/**
|
|
82
|
+
* Stable kind string: `task:created`, `task:updated`, `session:finished`,
|
|
83
|
+
* `task:question`, `pty:spawn`, `pty:exit`, … Mirrors the server's AppEvent
|
|
84
|
+
* type names so the Panel can route by kind without a translation layer.
|
|
85
|
+
*/
|
|
86
|
+
kind: string;
|
|
87
|
+
/** The PTY this event belongs to, if any (PTY spawn/exit). */
|
|
88
|
+
ptyId: string | null;
|
|
89
|
+
/** The Task this event belongs to, if any (task status, session finish). */
|
|
90
|
+
taskId: string | null;
|
|
91
|
+
/** JSON-serialized event-specific body. Shape is kind-specific. */
|
|
92
|
+
payload: string;
|
|
93
|
+
};
|
|
94
|
+
export type CoreLinkTaskStatus = string;
|
|
95
|
+
/**
|
|
96
|
+
* A task mutation — `create` (a new task under an existing project), `update`
|
|
97
|
+
* (patch an existing task row), or `delete` (remove one). The discriminant lets
|
|
98
|
+
* the Core dispatch to `createTask` / `updateTask` / `deleteTask` without a
|
|
99
|
+
* nullable-id sniff.
|
|
100
|
+
*
|
|
101
|
+
* On `create`, `projectId`, `title`, and `agent` are required — everything
|
|
102
|
+
* else defaults on the Core (status → `ready`, pinned/archived → false).
|
|
103
|
+
* `taskId` is optional; when omitted the Core generates one.
|
|
104
|
+
*
|
|
105
|
+
* On `update`, `taskId` is required and identifies the row; any of
|
|
106
|
+
* `status`/`title`/`pinned`/`archived` may be set. Fields omitted are left
|
|
107
|
+
* untouched (partial patch, mirroring the Panel server's PATCH shape).
|
|
108
|
+
*
|
|
109
|
+
* On `delete`, `taskId` is required and the row is removed outright — the
|
|
110
|
+
* Core's SQLite cascades the rows hanging off it, mirroring the Panel
|
|
111
|
+
* server's own DELETE. A missing row comes back as `task: null`, the same way
|
|
112
|
+
* a missing row on `update` does; it is not an error frame.
|
|
113
|
+
*/
|
|
114
|
+
export type CoreLinkTaskMutation = {
|
|
115
|
+
op: "create";
|
|
116
|
+
taskId?: string;
|
|
117
|
+
projectId: string;
|
|
118
|
+
title: string;
|
|
119
|
+
agent: string;
|
|
120
|
+
status?: CoreLinkTaskStatus;
|
|
121
|
+
/** Optional session icon id at creation time; usually null and set later. */
|
|
122
|
+
icon?: string | null;
|
|
123
|
+
} | {
|
|
124
|
+
op: "update";
|
|
125
|
+
taskId: string;
|
|
126
|
+
status?: CoreLinkTaskStatus;
|
|
127
|
+
title?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Whether the `title` on this patch is an operator's rename (issue 84).
|
|
130
|
+
* Omitted, a title reads as a rename and pins the row's
|
|
131
|
+
* manually-set-title flag — the shape every Panel-driven rename has
|
|
132
|
+
* always had. The Core's own title generator is the one caller that
|
|
133
|
+
* sends `false`, so a generated name never claims to be a rename and a
|
|
134
|
+
* rename is never overwritten by a generator that finished after it.
|
|
135
|
+
* Meaningless without `title`; ignored there.
|
|
136
|
+
*/
|
|
137
|
+
titleManuallySet?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* The harness's own session id for this Task (issue 84). Captured by the
|
|
140
|
+
* Core when a hook reports one, and by the Panel when a resumed session
|
|
141
|
+
* hands back a fresh id — a Core-owned row's session id is Core state
|
|
142
|
+
* like every other column, and writing it to the Panel's database left
|
|
143
|
+
* the Core's row blank. `null` clears it.
|
|
144
|
+
*/
|
|
145
|
+
claudeSessionId?: string | null;
|
|
146
|
+
pinned?: boolean;
|
|
147
|
+
archived?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Session icon id (issue 09). `undefined` leaves the row untouched (partial
|
|
150
|
+
* patch); a string sets it; `null` clears it. Icon is Core-owned
|
|
151
|
+
* metadata (ADR 0005) — the Panel never stores it and every icon change
|
|
152
|
+
* routes through this frame.
|
|
153
|
+
*/
|
|
154
|
+
icon?: string | null;
|
|
155
|
+
} | {
|
|
156
|
+
op: "delete";
|
|
157
|
+
taskId: string;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* A project mutation — `create`, `rename`, or `archive`. The Core validates
|
|
161
|
+
* the VM path on `create` (absolute, resolvable, not a file) and rejects with
|
|
162
|
+
* an `error` frame if invalid — a Project's path is a VM path and only the
|
|
163
|
+
* Core can validate it (CONTEXT.md "Project").
|
|
164
|
+
*
|
|
165
|
+
* `archive` deletes the project row (SQLite cascades tasks under
|
|
166
|
+
* this project via ON DELETE CASCADE — that is the shared-DB shape). The word
|
|
167
|
+
* "archive" is used at the protocol layer to match the ticket's language and
|
|
168
|
+
* to leave room for a future soft-archive column without changing the frame
|
|
169
|
+
* shape; today it is destructive.
|
|
170
|
+
*/
|
|
171
|
+
export type CoreLinkProjectMutation = {
|
|
172
|
+
op: "create";
|
|
173
|
+
projectId?: string;
|
|
174
|
+
name: string;
|
|
175
|
+
path: string;
|
|
176
|
+
icon?: string;
|
|
177
|
+
iconColor?: string;
|
|
178
|
+
pinned?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* The remembered session settings the Create Project dialog collected
|
|
181
|
+
* (issue 22). Omitted fields fall back to the column defaults, so a
|
|
182
|
+
* caller that only names the project still creates a valid row.
|
|
183
|
+
*/
|
|
184
|
+
rememberHarnessSettings?: boolean;
|
|
185
|
+
savedHarness?: string | null;
|
|
186
|
+
savedSkipPermissions?: boolean;
|
|
187
|
+
savedBareSession?: boolean;
|
|
188
|
+
defaultGridView?: boolean;
|
|
189
|
+
} | {
|
|
190
|
+
op: "rename";
|
|
191
|
+
projectId: string;
|
|
192
|
+
name: string;
|
|
193
|
+
} | {
|
|
194
|
+
op: "archive";
|
|
195
|
+
projectId: string;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Pin / unpin a project (issue 10). Pin state is a Core fact stored on
|
|
199
|
+
* the project row; every Panel connected to the same Core sees the same
|
|
200
|
+
* value. Dedicated op (rather than piggy-backing on a generic
|
|
201
|
+
* `updateProject` patch) so the event kind can be `project:pinnedChanged`
|
|
202
|
+
* — a reconnecting Panel replays pin flips distinctly from other project
|
|
203
|
+
* edits.
|
|
204
|
+
*/
|
|
205
|
+
| {
|
|
206
|
+
op: "pin";
|
|
207
|
+
projectId: string;
|
|
208
|
+
pinned: boolean;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Patch a project's remembered session settings (issue 22) — the "Remember
|
|
212
|
+
* settings for this project" checkbox and the grid-view default. These are
|
|
213
|
+
* Core facts on the project row, so every Panel connected to the same Core
|
|
214
|
+
* converges on them, exactly as pin state does.
|
|
215
|
+
*
|
|
216
|
+
* Follows the `pin` precedent rather than becoming a generic field patch: a
|
|
217
|
+
* dedicated op earns its own `project:settingsChanged` event kind, so a
|
|
218
|
+
* reconnecting Panel replays a settings change distinctly from a rename.
|
|
219
|
+
* Fields left `undefined` are untouched; `savedHarness: null` clears it.
|
|
220
|
+
*/
|
|
221
|
+
| {
|
|
222
|
+
op: "settings";
|
|
223
|
+
projectId: string;
|
|
224
|
+
rememberHarnessSettings?: boolean;
|
|
225
|
+
savedHarness?: string | null;
|
|
226
|
+
savedSkipPermissions?: boolean;
|
|
227
|
+
savedBareSession?: boolean;
|
|
228
|
+
defaultGridView?: boolean;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Patch a project's icon and icon colour (issue 98). Both live on the Core's
|
|
232
|
+
* project row and already travel in {@link CoreLinkProjectSnapshot}, and
|
|
233
|
+
* `create` accepts them — but until this op nothing could change them
|
|
234
|
+
* afterwards, so the Edit-project dialog PATCHed them at a Panel row a
|
|
235
|
+
* Core-owned project does not have, and 404'd.
|
|
236
|
+
*
|
|
237
|
+
* Follows the `pin` / `settings` precedent rather than widening `rename` into
|
|
238
|
+
* a generic field patch: a dedicated op earns its own
|
|
239
|
+
* `project:appearanceChanged` event kind, so a reconnecting Panel replays an
|
|
240
|
+
* icon change distinctly from a rename. Fields left `undefined` are
|
|
241
|
+
* untouched; a blank string is not an erase (both columns are NOT NULL) and
|
|
242
|
+
* is ignored.
|
|
243
|
+
*/
|
|
244
|
+
| {
|
|
245
|
+
op: "appearance";
|
|
246
|
+
projectId: string;
|
|
247
|
+
icon?: string;
|
|
248
|
+
iconColor?: string;
|
|
249
|
+
};
|
|
250
|
+
export type CoreLinkHookOp = {
|
|
251
|
+
op: "list";
|
|
252
|
+
taskId?: string;
|
|
253
|
+
} | {
|
|
254
|
+
op: "enable";
|
|
255
|
+
hookId: string;
|
|
256
|
+
taskId?: string;
|
|
257
|
+
} | {
|
|
258
|
+
op: "disable";
|
|
259
|
+
hookId: string;
|
|
260
|
+
taskId?: string;
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* Availability of one managed Harness on a single Core. `status` mirrors the
|
|
264
|
+
* Panel's `CliAvailability` shape so no translation is needed at the store
|
|
265
|
+
* boundary. `path` / `version` / `label` / `requiredVersion` / `packageUrl` /
|
|
266
|
+
* `updateCommands` are copied from the probe's `CliCheckResult` where known so
|
|
267
|
+
* the update-required and outdated flows keep working across every Core.
|
|
268
|
+
*/
|
|
269
|
+
export type CoreLinkHarnessAvailability = {
|
|
270
|
+
status: "checking" | "available" | "missing" | "outdated";
|
|
271
|
+
path?: string;
|
|
272
|
+
reason?: string;
|
|
273
|
+
label?: string;
|
|
274
|
+
version?: string;
|
|
275
|
+
requiredVersion?: string;
|
|
276
|
+
packageUrl?: string;
|
|
277
|
+
updateCommands?: readonly string[];
|
|
278
|
+
};
|
|
279
|
+
/** Per-agent availability map. Keys are the `Harness` id strings. */
|
|
280
|
+
export type CoreLinkHarnessAvailabilityMap = Record<string, CoreLinkHarnessAvailability>;
|
|
281
|
+
/**
|
|
282
|
+
* The kind string appended to the event log when the availability map changes.
|
|
283
|
+
* Payload is the JSON-serialized full {@link CoreLinkHarnessAvailabilityMap}
|
|
284
|
+
* (self-contained; a Panel that misses N intermediate changes and replays only
|
|
285
|
+
* the tail lands on the latest state without stitching).
|
|
286
|
+
*/
|
|
287
|
+
export declare const HARNESSES_AVAILABILITY_EVENT_KIND = "agents:availabilityChanged";
|
|
288
|
+
/**
|
|
289
|
+
* Answer to a `harnessInstall` request. `accepted` means the Core validated the
|
|
290
|
+
* Harness id and started the install — nothing about whether it succeeded.
|
|
291
|
+
* `accepted: false` is a refusal to start at all (an unknown id, or a Core with
|
|
292
|
+
* no install path wired) and carries the operator-facing reason.
|
|
293
|
+
*/
|
|
294
|
+
export type CoreLinkHarnessInstallAck = {
|
|
295
|
+
accepted: boolean;
|
|
296
|
+
message?: string;
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* The kind appended to the event log when an install ends without the Harness
|
|
300
|
+
* becoming available. Payload is `{harness, message}` — the message is written
|
|
301
|
+
* for the operator, in the same register the `dirCreate` / `dirList` failures
|
|
302
|
+
* use. This is the only definitive failure signal: no failure is ever cached in
|
|
303
|
+
* the availability map, so a Panel that missed the event sees a plain `missing`
|
|
304
|
+
* Harness it can try again, never a permanently disabled one.
|
|
305
|
+
*/
|
|
306
|
+
export declare const HARNESS_INSTALL_FAILED_EVENT_KIND = "harness:installFailed";
|
|
307
|
+
/** Payload of a {@link HARNESS_INSTALL_FAILED_EVENT_KIND} event. */
|
|
308
|
+
export type CoreLinkHarnessInstallFailedPayload = {
|
|
309
|
+
/** The Harness id the install was for. */
|
|
310
|
+
harness: string;
|
|
311
|
+
/** Operator-facing reason, e.g. "Installing Claude Code failed (exit 1)." */
|
|
312
|
+
message: string;
|
|
313
|
+
};
|
|
314
|
+
/** One directory inside a listing. `childCount` is 0 for a leaf. */
|
|
315
|
+
export type CoreLinkDirEntry = {
|
|
316
|
+
name: string;
|
|
317
|
+
/** Visible (non-hidden) subdirectory count — powers the drill-in affordance. */
|
|
318
|
+
childCount: number;
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* One directory's worth of listing, as the Core sees its own disk. `path`
|
|
322
|
+
* is the resolved absolute directory; `parent` is null at the filesystem root.
|
|
323
|
+
* `roots` are shortcut chips (home plus the standard folders that exist on
|
|
324
|
+
* that machine) so the picker opens somewhere useful on a VM whose layout the
|
|
325
|
+
* Panel knows nothing about.
|
|
326
|
+
*/
|
|
327
|
+
export type CoreLinkDirListing = {
|
|
328
|
+
path: string;
|
|
329
|
+
parent: string | null;
|
|
330
|
+
home: string;
|
|
331
|
+
roots: Array<{
|
|
332
|
+
label: string;
|
|
333
|
+
path: string;
|
|
334
|
+
}>;
|
|
335
|
+
entries: CoreLinkDirEntry[];
|
|
336
|
+
/** True when the listing was capped and some folders are not shown. */
|
|
337
|
+
truncated: boolean;
|
|
338
|
+
};
|
|
339
|
+
/**
|
|
340
|
+
* The `code` on an `error` frame refusing a mutation because another connection
|
|
341
|
+
* holds that Session's lock (ADR 0024 D4/D6).
|
|
342
|
+
*
|
|
343
|
+
* A code rather than a message match, because the client has to tell this apart
|
|
344
|
+
* from the other way a mutation fails to land: "that Session is gone". Those two
|
|
345
|
+
* answers arrive on different frames entirely — a refusal is this `error`, while
|
|
346
|
+
* a Session that no longer exists is the ordinary `writeResult { ok: false }` /
|
|
347
|
+
* `killResult { ok: false }` / `tasksMutateResult { task: null }` the Core has
|
|
348
|
+
* always sent — so a client can distinguish them without parsing prose, and one
|
|
349
|
+
* of them is worth retrying after a claim while the other never is.
|
|
350
|
+
*/
|
|
351
|
+
export declare const SESSION_LOCKED_ERROR_CODE = "session-locked";
|
|
352
|
+
/**
|
|
353
|
+
* Machine-readable `error` codes. Open by construction (a plain string union
|
|
354
|
+
* with one member today) so a later refusal can name itself without every
|
|
355
|
+
* client having to learn the whole set: an unrecognised code reads as a plain
|
|
356
|
+
* error, which is what a client that ignores the field already does.
|
|
357
|
+
*/
|
|
358
|
+
export type CoreLinkErrorCode = typeof SESSION_LOCKED_ERROR_CODE;
|
|
359
|
+
/**
|
|
360
|
+
* Who lost a Session to a `forceTakeover`.
|
|
361
|
+
*
|
|
362
|
+
* A takeover always succeeds — that is D7, and a result field saying "yes it
|
|
363
|
+
* worked" would carry no information. What varies is whether anybody was
|
|
364
|
+
* actually evicted, and a client should not report having taken a Session off
|
|
365
|
+
* another client when the Session was sitting unlocked.
|
|
366
|
+
*/
|
|
367
|
+
export type CoreLinkSessionTakenFrom = "nobody" | "another-connection" | "this-connection";
|
|
368
|
+
/**
|
|
369
|
+
* How a Session's lock looks **to the one connection this snapshot was sent
|
|
370
|
+
* to** (ADR 0024 D8).
|
|
371
|
+
*
|
|
372
|
+
* This field is *addressed*, not global: two connections listing the same
|
|
373
|
+
* Session are told different things about it, and that is the whole point. A
|
|
374
|
+
* snapshot that reported "locked: true" and left the recipient to work out
|
|
375
|
+
* whether the holder was itself would make every client carry the comparison,
|
|
376
|
+
* and every client would need an identity to compare against — which is exactly
|
|
377
|
+
* the identity D10 says the lock does not have and D3 keeps below the socket.
|
|
378
|
+
*
|
|
379
|
+
* So it says **whether you hold it**, never **who holds it**. `held-by-another`
|
|
380
|
+
* is the whole of what a watcher learns about a holder that is not itself: no
|
|
381
|
+
* client id, no connection id, no label, nothing that a second client could
|
|
382
|
+
* correlate a person or a program with.
|
|
383
|
+
*
|
|
384
|
+
* `writable` is the answer and `state` is why. The two are not redundant: which
|
|
385
|
+
* states are writable is a Core-side rule — an *unlocked* Session is writable by
|
|
386
|
+
* anybody, which is the D11 compatibility promise (see
|
|
387
|
+
* {@link SESSION_LOCKED_ERROR_CODE}) rather than an obvious property of the
|
|
388
|
+
* word "unlocked" — and publishing the answer keeps that rule in the one place
|
|
389
|
+
* that enforces it. A client rendering an editable terminal reads `writable`; a
|
|
390
|
+
* client choosing between a "Claim" and a "Take over" affordance reads `state`.
|
|
391
|
+
*/
|
|
392
|
+
export type CoreLinkSessionLock = {
|
|
393
|
+
/**
|
|
394
|
+
* May the connection that was sent this snapshot mutate this Session right
|
|
395
|
+
* now? True when the Session is unlocked as well as when this connection
|
|
396
|
+
* holds it — the gate refuses only a Session **another** connection holds.
|
|
397
|
+
*/
|
|
398
|
+
writable: boolean;
|
|
399
|
+
/** Which of the three states this Session is in, from that connection's side. */
|
|
400
|
+
state: CoreLinkSessionLockState;
|
|
401
|
+
};
|
|
402
|
+
/**
|
|
403
|
+
* The three states a Session's lock can be in for one connection.
|
|
404
|
+
*
|
|
405
|
+
* There is no fourth, and deliberately no name for the holder. `unlocked` is a
|
|
406
|
+
* real state (D5) and not an absence: a Session starts there, returns there on
|
|
407
|
+
* release, on the holder's connection dropping, and on a Core restart (D12).
|
|
408
|
+
*/
|
|
409
|
+
export type CoreLinkSessionLockState = "unlocked" | "held-by-you" | "held-by-another";
|
|
410
|
+
/**
|
|
411
|
+
* The kind appended to the event log on every Session-lock change (ADR 0024 D8).
|
|
412
|
+
*
|
|
413
|
+
* A **dedicated kind**, following the precedent ADR 0022 set for
|
|
414
|
+
* `project:appearanceChanged` — and for the same reason both ADR 0017 and ADR
|
|
415
|
+
* 0022 rejected the alternative. Widening an existing mutation event to carry
|
|
416
|
+
* lock state would stop the frame documenting what changed: every client would
|
|
417
|
+
* have to refetch on every `task:updated` to find out whether this one was
|
|
418
|
+
* about the lock, and a reconnecting client replaying a tail could not tell a
|
|
419
|
+
* takeover it needs to react to from a title edit it does not.
|
|
420
|
+
*
|
|
421
|
+
* Payload is {@link CoreLinkSessionLockChangedPayload}. It rides the ordinary
|
|
422
|
+
* event log, so it replays by cursor exactly like every other event: a client
|
|
423
|
+
* that was away comes back, sends `subscribe { lastEventId }`, and reads the
|
|
424
|
+
* lock changes it missed in order, distinctly, alongside everything else.
|
|
425
|
+
*/
|
|
426
|
+
export declare const SESSION_LOCK_CHANGED_EVENT_KIND = "session:lockChanged";
|
|
427
|
+
/**
|
|
428
|
+
* What happened to a Session's lock, in the vocabulary of the lock rather than
|
|
429
|
+
* of the frame that caused it.
|
|
430
|
+
*
|
|
431
|
+
* A `forceTakeover` against a Session **nobody** held is a `claimed`, not a
|
|
432
|
+
* `taken-over`: the wire says what happened to the lock, so no client reports an
|
|
433
|
+
* eviction that did not occur. The same distinction `CoreLinkSessionTakenFrom`
|
|
434
|
+
* draws for the requester, drawn once more for everybody watching.
|
|
435
|
+
*
|
|
436
|
+
* Open by construction — a reader that meets an unfamiliar transition falls back
|
|
437
|
+
* to `locked` on the payload, which is why that field is there.
|
|
438
|
+
*/
|
|
439
|
+
export type CoreLinkSessionLockTransition = "claimed" | "released" | "taken-over";
|
|
440
|
+
/**
|
|
441
|
+
* Payload of a {@link SESSION_LOCK_CHANGED_EVENT_KIND} event.
|
|
442
|
+
*
|
|
443
|
+
* **It names no client, and it must not.** This row goes into a shared log that
|
|
444
|
+
* every connection replays: anything identifying in it would be broadcast to
|
|
445
|
+
* every watcher of the Core, which is the identity leak D10 (the lock is
|
|
446
|
+
* coordination, not security) and D3 (the holder is a connection, not a person)
|
|
447
|
+
* exist to prevent. A takeover therefore names neither winner nor loser, and
|
|
448
|
+
* needs to name neither: the winner learns it won on its own
|
|
449
|
+
* `forceTakeoverResult`, and every other connection reads `taken-over` as "the
|
|
450
|
+
* holder is now somebody who is not me" — which is the entire fact a loser needs
|
|
451
|
+
* and the only one an identity could have added.
|
|
452
|
+
*
|
|
453
|
+
* "Can *I* write to it now" is deliberately not answered here. It cannot be: one
|
|
454
|
+
* logged row is read by every connection, and the answer differs per connection.
|
|
455
|
+
* That question is the snapshot's ({@link CoreLinkSessionLock}), and this event
|
|
456
|
+
* is what tells a client the answer has moved.
|
|
457
|
+
*/
|
|
458
|
+
export type CoreLinkSessionLockChangedPayload = {
|
|
459
|
+
/** The Session whose lock changed. Also on the event row's `taskId` column. */
|
|
460
|
+
taskId: string;
|
|
461
|
+
/** What happened to the lock. */
|
|
462
|
+
transition: CoreLinkSessionLockTransition;
|
|
463
|
+
/**
|
|
464
|
+
* Is the Session held by some connection now? Derivable from every transition
|
|
465
|
+
* this build defines, and carried anyway: a client can act on the state
|
|
466
|
+
* without knowing the whole transition vocabulary, which is what keeps a
|
|
467
|
+
* transition added later from silently reading as "no change" to a client that
|
|
468
|
+
* predates it.
|
|
469
|
+
*/
|
|
470
|
+
locked: boolean;
|
|
471
|
+
};
|
|
472
|
+
export type CoreLinkRequestFrame = {
|
|
473
|
+
type: "spawn";
|
|
474
|
+
reqId: string;
|
|
475
|
+
opts: CoreLinkPtySpawnOptions;
|
|
476
|
+
} | {
|
|
477
|
+
type: "write";
|
|
478
|
+
reqId: string;
|
|
479
|
+
ptyId: string;
|
|
480
|
+
data: string;
|
|
481
|
+
} | {
|
|
482
|
+
type: "resize";
|
|
483
|
+
reqId: string;
|
|
484
|
+
ptyId: string;
|
|
485
|
+
cols: number;
|
|
486
|
+
rows: number;
|
|
487
|
+
} | {
|
|
488
|
+
type: "kill";
|
|
489
|
+
reqId: string;
|
|
490
|
+
ptyId: string;
|
|
491
|
+
} | {
|
|
492
|
+
type: "killLaunchProcesses";
|
|
493
|
+
reqId: string;
|
|
494
|
+
cwd: string;
|
|
495
|
+
commands: string[];
|
|
496
|
+
ports?: number[];
|
|
497
|
+
} | {
|
|
498
|
+
type: "findByTask";
|
|
499
|
+
reqId: string;
|
|
500
|
+
taskId: string;
|
|
501
|
+
} | {
|
|
502
|
+
type: "replay";
|
|
503
|
+
reqId: string;
|
|
504
|
+
ptyId: string;
|
|
505
|
+
sinceSeq?: number;
|
|
506
|
+
} | {
|
|
507
|
+
type: "subscribe";
|
|
508
|
+
reqId: string;
|
|
509
|
+
lastEventId: number;
|
|
510
|
+
} | {
|
|
511
|
+
type: "ptySubscribe";
|
|
512
|
+
reqId: string;
|
|
513
|
+
ptyId: string;
|
|
514
|
+
catchUp?: boolean;
|
|
515
|
+
} | {
|
|
516
|
+
type: "ptyUnsubscribe";
|
|
517
|
+
reqId: string;
|
|
518
|
+
ptyId: string;
|
|
519
|
+
} | {
|
|
520
|
+
type: "claim";
|
|
521
|
+
reqId: string;
|
|
522
|
+
taskId: string;
|
|
523
|
+
} | {
|
|
524
|
+
type: "release";
|
|
525
|
+
reqId: string;
|
|
526
|
+
taskId: string;
|
|
527
|
+
} | {
|
|
528
|
+
type: "forceTakeover";
|
|
529
|
+
reqId: string;
|
|
530
|
+
taskId: string;
|
|
531
|
+
} | {
|
|
532
|
+
type: "reclaim";
|
|
533
|
+
reqId: string;
|
|
534
|
+
clientId: string;
|
|
535
|
+
} | {
|
|
536
|
+
type: "tasksList";
|
|
537
|
+
reqId: string;
|
|
538
|
+
projectId?: string;
|
|
539
|
+
} | {
|
|
540
|
+
type: "archivedTasksList";
|
|
541
|
+
reqId: string;
|
|
542
|
+
projectId?: string;
|
|
543
|
+
} | {
|
|
544
|
+
type: "tasksMutate";
|
|
545
|
+
reqId: string;
|
|
546
|
+
mutation: CoreLinkTaskMutation;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* A prompt the operator submitted, for a harness whose hooks do not report
|
|
550
|
+
* one (issue 84).
|
|
551
|
+
*
|
|
552
|
+
* Cursor's `beforeSubmitPrompt` never fires in cursor-agent, so the Panel
|
|
553
|
+
* reads the prompt off the terminal instead — and that is the only copy
|
|
554
|
+
* anyone has. Without a way to hand it to the owning Core, a Core-owned
|
|
555
|
+
* Cursor Session could never be named at all: the Core's title generator
|
|
556
|
+
* had nothing to name it from, and the Panel's names only Panel rows.
|
|
557
|
+
*
|
|
558
|
+
* It patches no column. The Core takes it as the trigger its own hook
|
|
559
|
+
* receiver would otherwise have supplied, and the same rules apply — a
|
|
560
|
+
* Session that already has a real title, or one the operator renamed, is
|
|
561
|
+
* left alone.
|
|
562
|
+
*/
|
|
563
|
+
| {
|
|
564
|
+
type: "harnessPrompt";
|
|
565
|
+
reqId: string;
|
|
566
|
+
taskId: string;
|
|
567
|
+
prompt: string;
|
|
568
|
+
} | {
|
|
569
|
+
type: "projectsList";
|
|
570
|
+
reqId: string;
|
|
571
|
+
} | {
|
|
572
|
+
type: "projectsMutate";
|
|
573
|
+
reqId: string;
|
|
574
|
+
mutation: CoreLinkProjectMutation;
|
|
575
|
+
} | {
|
|
576
|
+
type: "sessionsList";
|
|
577
|
+
reqId: string;
|
|
578
|
+
projectId?: string;
|
|
579
|
+
} | {
|
|
580
|
+
type: "hooksOp";
|
|
581
|
+
reqId: string;
|
|
582
|
+
hook: CoreLinkHookOp;
|
|
583
|
+
} | {
|
|
584
|
+
type: "auth";
|
|
585
|
+
reqId: string;
|
|
586
|
+
bearer: string;
|
|
587
|
+
} | {
|
|
588
|
+
type: "agentsAvailabilityList";
|
|
589
|
+
reqId: string;
|
|
590
|
+
} | {
|
|
591
|
+
type: "harnessInstall";
|
|
592
|
+
reqId: string;
|
|
593
|
+
harness: string;
|
|
594
|
+
} | {
|
|
595
|
+
type: "dirList";
|
|
596
|
+
reqId: string;
|
|
597
|
+
path?: string | null;
|
|
598
|
+
} | {
|
|
599
|
+
type: "dirCreate";
|
|
600
|
+
reqId: string;
|
|
601
|
+
parent: string;
|
|
602
|
+
name: string;
|
|
603
|
+
};
|
|
604
|
+
/** Unsolicited stream frame — pushed by the Core whenever a PTY emits or exits. */
|
|
605
|
+
export type CoreLinkStreamFrame = {
|
|
606
|
+
type: "data";
|
|
607
|
+
ptyId: string;
|
|
608
|
+
data: string;
|
|
609
|
+
seq: number;
|
|
610
|
+
} | {
|
|
611
|
+
type: "exit";
|
|
612
|
+
ptyId: string;
|
|
613
|
+
exitCode: number;
|
|
614
|
+
signal?: number;
|
|
615
|
+
};
|
|
616
|
+
/**
|
|
617
|
+
* Unsolicited event frame — pushed by the Core for every domain event in
|
|
618
|
+
* the monotonic event log (task status, hook, session finish, PTY spawn/exit).
|
|
619
|
+
* Carries the sequential {@link CoreLinkEvent} envelope. During a replay the
|
|
620
|
+
* Core streams these back-to-back; live push uses the same frame shape.
|
|
621
|
+
*/
|
|
622
|
+
export type CoreLinkEventFrame = {
|
|
623
|
+
type: "event";
|
|
624
|
+
event: CoreLinkEvent;
|
|
625
|
+
};
|
|
626
|
+
/**
|
|
627
|
+
* End-of-replay marker. Sent once after the `subscribe` tail has been fully
|
|
628
|
+
* streamed as `event` frames. `lastEventId` is the highest eventId the Panel
|
|
629
|
+
* has now seen — it persists it as its new cursor. After this frame the
|
|
630
|
+
* Core resumes live `event` push for any events appended after the cursor.
|
|
631
|
+
*/
|
|
632
|
+
export type CoreLinkEventsReplayedFrame = {
|
|
633
|
+
type: "eventsReplayed";
|
|
634
|
+
lastEventId: number;
|
|
635
|
+
};
|
|
636
|
+
/**
|
|
637
|
+
* The `multiConnection` capability, announced on `ready` (ADR 0024 D11).
|
|
638
|
+
*
|
|
639
|
+
* `version` is the capability's own number, independent of
|
|
640
|
+
* {@link CORE_LINK_PROTOCOL_VERSION} — it moves when the multi-connection
|
|
641
|
+
* surface itself changes shape, and nothing about it marks a Core stale.
|
|
642
|
+
*/
|
|
643
|
+
export type CoreLinkMultiConnectionCapability = {
|
|
644
|
+
version: 1;
|
|
645
|
+
};
|
|
646
|
+
/** Response frame — correlates to a request via `reqId`. */
|
|
647
|
+
export type CoreLinkResponseFrame = {
|
|
648
|
+
type: "ready";
|
|
649
|
+
version: string;
|
|
650
|
+
/**
|
|
651
|
+
* Present on a Core that serves many core-link connections at once
|
|
652
|
+
* (ADR 0024 D1). Absent means the single-connection Core: it evicts the
|
|
653
|
+
* previous socket on connect, holds no Session locks and has no
|
|
654
|
+
* per-connection subscriptions.
|
|
655
|
+
*
|
|
656
|
+
* Absence is a supported state, not a fault. Every frame that only makes
|
|
657
|
+
* sense against a multi-connection Core is withheld by the client when
|
|
658
|
+
* this is absent (see `MULTI_CONNECTION_ONLY_FRAME_TYPES` in the Panel's
|
|
659
|
+
* core-link client); everything else behaves exactly as it does today, so
|
|
660
|
+
* such a Core is connected and fully usable, never "needs update".
|
|
661
|
+
*/
|
|
662
|
+
multiConnection?: CoreLinkMultiConnectionCapability;
|
|
663
|
+
} | {
|
|
664
|
+
type: "spawned";
|
|
665
|
+
reqId: string;
|
|
666
|
+
ptyId: string;
|
|
667
|
+
/**
|
|
668
|
+
* Will a hook report the START of a turn for this Session (issue 84)?
|
|
669
|
+
*
|
|
670
|
+
* The Panel's terminal-input fallback stands down on this and nothing
|
|
671
|
+
* else. It is deliberately narrower than "hooks were installed": Cursor
|
|
672
|
+
* takes our hooks file but never fires `beforeSubmitPrompt`, and Codex
|
|
673
|
+
* refuses to run newly-installed hooks until the operator reviews them
|
|
674
|
+
* with `/hooks` — both would report a turn's END and leave its start
|
|
675
|
+
* unannounced. Suppressing the fallback on either is a Session with no
|
|
676
|
+
* `running` signal at all, which is the bug this replaced.
|
|
677
|
+
*
|
|
678
|
+
* Absent from an older Core's answer, which reads as "no" — a redundant
|
|
679
|
+
* `running` write, never a silent card.
|
|
680
|
+
*/
|
|
681
|
+
hooksReportTurnStart?: boolean;
|
|
682
|
+
} | {
|
|
683
|
+
type: "spawnError";
|
|
684
|
+
reqId: string;
|
|
685
|
+
message: string;
|
|
686
|
+
} | {
|
|
687
|
+
type: "writeResult";
|
|
688
|
+
reqId: string;
|
|
689
|
+
ok: boolean;
|
|
690
|
+
} | {
|
|
691
|
+
type: "resizeResult";
|
|
692
|
+
reqId: string;
|
|
693
|
+
ok: boolean;
|
|
694
|
+
} | {
|
|
695
|
+
type: "killResult";
|
|
696
|
+
reqId: string;
|
|
697
|
+
ok: boolean;
|
|
698
|
+
} | {
|
|
699
|
+
type: "killLaunchProcessesResult";
|
|
700
|
+
reqId: string;
|
|
701
|
+
result: CoreLinkLaunchProcessKillResult;
|
|
702
|
+
} | {
|
|
703
|
+
type: "findByTaskResult";
|
|
704
|
+
reqId: string;
|
|
705
|
+
ptyId: string | null;
|
|
706
|
+
} | {
|
|
707
|
+
type: "replayResult";
|
|
708
|
+
reqId: string;
|
|
709
|
+
data: string;
|
|
710
|
+
nextSeq: number;
|
|
711
|
+
from?: number;
|
|
712
|
+
} | {
|
|
713
|
+
type: "subscribeAck";
|
|
714
|
+
reqId: string;
|
|
715
|
+
fromEventId: number;
|
|
716
|
+
} | {
|
|
717
|
+
type: "ptySubscribeAck";
|
|
718
|
+
reqId: string;
|
|
719
|
+
ptyId: string;
|
|
720
|
+
subscribed: true;
|
|
721
|
+
holding: boolean;
|
|
722
|
+
} | {
|
|
723
|
+
type: "ptyUnsubscribeAck";
|
|
724
|
+
reqId: string;
|
|
725
|
+
ptyId: string;
|
|
726
|
+
subscribed: false;
|
|
727
|
+
} | {
|
|
728
|
+
type: "claimResult";
|
|
729
|
+
reqId: string;
|
|
730
|
+
taskId: string;
|
|
731
|
+
granted: boolean;
|
|
732
|
+
} | {
|
|
733
|
+
type: "releaseResult";
|
|
734
|
+
reqId: string;
|
|
735
|
+
taskId: string;
|
|
736
|
+
released: boolean;
|
|
737
|
+
} | {
|
|
738
|
+
type: "forceTakeoverResult";
|
|
739
|
+
reqId: string;
|
|
740
|
+
taskId: string;
|
|
741
|
+
takenFrom: CoreLinkSessionTakenFrom;
|
|
742
|
+
} | {
|
|
743
|
+
type: "reclaimResult";
|
|
744
|
+
reqId: string;
|
|
745
|
+
clientId: string;
|
|
746
|
+
replaced: boolean;
|
|
747
|
+
taskIds: string[];
|
|
748
|
+
} | {
|
|
749
|
+
type: "tasksListResult";
|
|
750
|
+
reqId: string;
|
|
751
|
+
tasks: CoreLinkTaskSnapshot[];
|
|
752
|
+
archivedCount: number;
|
|
753
|
+
} | {
|
|
754
|
+
type: "archivedTasksListResult";
|
|
755
|
+
reqId: string;
|
|
756
|
+
tasks: CoreLinkTaskSnapshot[];
|
|
757
|
+
} | {
|
|
758
|
+
type: "tasksMutateResult";
|
|
759
|
+
reqId: string;
|
|
760
|
+
task: CoreLinkTaskSnapshot | null;
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Was the prompt taken up? `false` means this Core has no title generator
|
|
764
|
+
* wired — not that the Session was left unnamed for a reason, which is a
|
|
765
|
+
* normal outcome the Core does not report on.
|
|
766
|
+
*/
|
|
767
|
+
| {
|
|
768
|
+
type: "harnessPromptResult";
|
|
769
|
+
reqId: string;
|
|
770
|
+
accepted: boolean;
|
|
771
|
+
} | {
|
|
772
|
+
type: "projectsListResult";
|
|
773
|
+
reqId: string;
|
|
774
|
+
projects: CoreLinkProjectSnapshot[];
|
|
775
|
+
} | {
|
|
776
|
+
type: "projectsMutateResult";
|
|
777
|
+
reqId: string;
|
|
778
|
+
project: CoreLinkProjectSnapshot | null;
|
|
779
|
+
} | {
|
|
780
|
+
type: "sessionsListResult";
|
|
781
|
+
reqId: string;
|
|
782
|
+
sessions: CoreLinkSessionSnapshot[];
|
|
783
|
+
} | {
|
|
784
|
+
type: "hooksOpResult";
|
|
785
|
+
reqId: string;
|
|
786
|
+
hooks: CoreLinkHookEntry[];
|
|
787
|
+
} | {
|
|
788
|
+
type: "authOk";
|
|
789
|
+
reqId: string;
|
|
790
|
+
coreId: string;
|
|
791
|
+
exp: number;
|
|
792
|
+
} | {
|
|
793
|
+
type: "authError";
|
|
794
|
+
reqId?: string;
|
|
795
|
+
reason: "expired" | "bad-signature" | "malformed";
|
|
796
|
+
} | {
|
|
797
|
+
type: "agentsAvailabilityListResult";
|
|
798
|
+
reqId: string;
|
|
799
|
+
availability: CoreLinkHarnessAvailabilityMap;
|
|
800
|
+
} | ({
|
|
801
|
+
type: "harnessInstallAck";
|
|
802
|
+
reqId: string;
|
|
803
|
+
} & CoreLinkHarnessInstallAck) | {
|
|
804
|
+
type: "dirListResult";
|
|
805
|
+
reqId: string;
|
|
806
|
+
listing: CoreLinkDirListing;
|
|
807
|
+
} | {
|
|
808
|
+
type: "dirCreateResult";
|
|
809
|
+
reqId: string;
|
|
810
|
+
path: string;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* `code` is optional and additive (issue 144): it exists so a client can act
|
|
814
|
+
* on *why* a request failed without matching on prose. Absent on every error
|
|
815
|
+
* that shipped before it, which is why nothing may require it — a client
|
|
816
|
+
* reads the code when it is there and falls back to the message when it is
|
|
817
|
+
* not. See {@link SESSION_LOCKED_ERROR_CODE}, its first and so far only value.
|
|
818
|
+
*/
|
|
819
|
+
| {
|
|
820
|
+
type: "error";
|
|
821
|
+
reqId?: string;
|
|
822
|
+
message: string;
|
|
823
|
+
code?: CoreLinkErrorCode;
|
|
824
|
+
};
|
|
825
|
+
/**
|
|
826
|
+
* A flattened project snapshot carried over the core-link (issue 07). The
|
|
827
|
+
* Core is the source of truth for projects; the Panel holds none. The shape
|
|
828
|
+
* mirrors the server's project row so the Panel can render per-Core navigation
|
|
829
|
+
* without a separate HTTP round-trip per project.
|
|
830
|
+
*
|
|
831
|
+
* `path` is a VM path — only the Core can validate it (CONTEXT.md
|
|
832
|
+
* "Project": "A Project's path is a VM path. Only the Core can validate
|
|
833
|
+
* it."). The Panel renders it as-is and never assumes it exists locally.
|
|
834
|
+
*/
|
|
835
|
+
export type CoreLinkProjectSnapshot = {
|
|
836
|
+
projectId: string;
|
|
837
|
+
name: string;
|
|
838
|
+
/** Absolute path on the Core's machine (a VM path, not a Panel path). */
|
|
839
|
+
path: string;
|
|
840
|
+
/** 2-letter monogram shown in the Panel (mirrors the projects table). */
|
|
841
|
+
icon: string;
|
|
842
|
+
/** Hex color for the icon background. */
|
|
843
|
+
iconColor: string;
|
|
844
|
+
pinned: boolean;
|
|
845
|
+
/**
|
|
846
|
+
* Remembered session settings (issue 22). Core facts on the project row —
|
|
847
|
+
* the Panel holds no copy, so a second Panel on the same Core reads the
|
|
848
|
+
* same values, the way pin state already behaves.
|
|
849
|
+
*
|
|
850
|
+
* `savedSkipPermissions` is carried for symmetry with the column that
|
|
851
|
+
* already exists; nothing on the launch path reads it. Auto-mode is the
|
|
852
|
+
* unconditional default for every Harness that has such a flag, so wiring
|
|
853
|
+
* this back into a session launch would reintroduce the removed control.
|
|
854
|
+
*/
|
|
855
|
+
rememberHarnessSettings: boolean;
|
|
856
|
+
savedHarness: string | null;
|
|
857
|
+
savedSkipPermissions: boolean;
|
|
858
|
+
savedBareSession: boolean;
|
|
859
|
+
defaultGridView: boolean;
|
|
860
|
+
updatedAt: number;
|
|
861
|
+
};
|
|
862
|
+
/**
|
|
863
|
+
* A flattened task snapshot carried over the core-link. The Core is the
|
|
864
|
+
* source of truth for tasks; the Panel holds none. The shape mirrors the
|
|
865
|
+
* server's task row so the Panel can render a fleet view without a separate
|
|
866
|
+
* HTTP round-trip per task.
|
|
867
|
+
*/
|
|
868
|
+
export type CoreLinkTaskSnapshot = {
|
|
869
|
+
taskId: string;
|
|
870
|
+
projectId: string;
|
|
871
|
+
title: string;
|
|
872
|
+
/**
|
|
873
|
+
* True once an operator has renamed this Session (issue 84). The Core's
|
|
874
|
+
* title generator refuses to write over a row carrying it, and the flag
|
|
875
|
+
* lives on the row rather than in Panel memory so the protection survives a
|
|
876
|
+
* Panel reload and a generator that finishes after the rename. The Panel
|
|
877
|
+
* renders from this field instead of synthesizing `false`, which made every
|
|
878
|
+
* Core-owned Session look un-renamed.
|
|
879
|
+
*/
|
|
880
|
+
titleManuallySet: boolean;
|
|
881
|
+
/**
|
|
882
|
+
* The harness's own session id for this Task, or `null` before one has been
|
|
883
|
+
* observed (issue 84). The Core's hook pipeline reads it to tell a hook from
|
|
884
|
+
* this Session apart from one belonging to a session that has since been
|
|
885
|
+
* replaced.
|
|
886
|
+
*/
|
|
887
|
+
claudeSessionId: string | null;
|
|
888
|
+
agent: string;
|
|
889
|
+
status: string;
|
|
890
|
+
pinned: boolean;
|
|
891
|
+
archived: boolean;
|
|
892
|
+
/**
|
|
893
|
+
* Session icon id — a stable string drawn from the {@link SESSION_ICON_OPTIONS}
|
|
894
|
+
* list (issue 09). `null` means the row has no user- or generator-assigned
|
|
895
|
+
* icon and the Panel should fall back to `DEFAULT_SESSION_ICON`. Icon is
|
|
896
|
+
* Core-owned metadata (ADR 0005); the Panel never persists it.
|
|
897
|
+
*/
|
|
898
|
+
icon: string | null;
|
|
899
|
+
updatedAt: number;
|
|
900
|
+
/**
|
|
901
|
+
* This Session's lock, as it looks to the connection this snapshot was sent
|
|
902
|
+
* to (issue 145, ADR 0024 D8). See {@link CoreLinkSessionLock} — it says
|
|
903
|
+
* whether *you* may write, never who else holds it.
|
|
904
|
+
*
|
|
905
|
+
* Optional, and absent means "this Core does not publish lock state", which
|
|
906
|
+
* is the single-connection Core that shipped before the capability. Absence
|
|
907
|
+
* therefore yields exactly today's behaviour — no read-only rendering,
|
|
908
|
+
* because there is no lock table to be a Reader of — which is what lets this
|
|
909
|
+
* ride the `multiConnection` capability instead of moving
|
|
910
|
+
* {@link CORE_LINK_PROTOCOL_VERSION}, as #142, #143 and #144 did before it.
|
|
911
|
+
* A Core that announces `multiConnection` always sets it.
|
|
912
|
+
*
|
|
913
|
+
* Stamped by the server as the snapshot goes out, not read from a row: the
|
|
914
|
+
* lock table is in memory (D12) and the answer depends on who is asking, so
|
|
915
|
+
* no query port can produce it and no two recipients need get the same value.
|
|
916
|
+
*/
|
|
917
|
+
lock?: CoreLinkSessionLock;
|
|
918
|
+
};
|
|
919
|
+
/** A session snapshot — a task's live or replayable conversation. */
|
|
920
|
+
export type CoreLinkSessionSnapshot = {
|
|
921
|
+
taskId: string;
|
|
922
|
+
ptyId: string | null;
|
|
923
|
+
status: string;
|
|
924
|
+
updatedAt: number;
|
|
925
|
+
/**
|
|
926
|
+
* This Session's lock, as it looks to the connection this snapshot was sent
|
|
927
|
+
* to (issue 145, ADR 0024 D8) — the same field, on the same terms, as the one
|
|
928
|
+
* on {@link CoreLinkTaskSnapshot}, because a client reading either of them is
|
|
929
|
+
* asking the same question about the same Session.
|
|
930
|
+
*
|
|
931
|
+
* This is the one `actana session ls` reads: it lists Sessions, and "can I
|
|
932
|
+
* write to this" is what it has to show about each. Optional for the reason
|
|
933
|
+
* given above — absent is the Core that does not publish lock state.
|
|
934
|
+
*/
|
|
935
|
+
lock?: CoreLinkSessionLock;
|
|
936
|
+
};
|
|
937
|
+
/** A hook entry returned by `hooksOp`. */
|
|
938
|
+
export type CoreLinkHookEntry = {
|
|
939
|
+
hookId: string;
|
|
940
|
+
taskId: string | null;
|
|
941
|
+
enabled: boolean;
|
|
942
|
+
};
|
|
943
|
+
export type CoreLinkServerFrame = CoreLinkStreamFrame | CoreLinkEventFrame | CoreLinkEventsReplayedFrame | CoreLinkResponseFrame;
|
|
944
|
+
/**
|
|
945
|
+
* Protocol version advertised in the `ready` frame. Bumped on breaking changes.
|
|
946
|
+
* Issue 02 adds the event-cursor replay (`subscribe` / `event` /
|
|
947
|
+
* `eventsReplayed`) and the task/session/hook op frames → 0.2.0. Issue 04 adds
|
|
948
|
+
* the mTLS bearer `auth` / `authOk` / `authError` frames → 0.3.0. Issue 06 adds
|
|
949
|
+
* the `shellSession: true` VM-shell spawn mode (no `agent`, no project-root) →
|
|
950
|
+
* 0.4.0. Issue 07 adds the `projectsList` / `projectsListResult` frames for
|
|
951
|
+
* per-Core navigation (additive — same 0.4.0). Issue 04 lands the write path
|
|
952
|
+
* (`projectsMutate` + real `tasksMutate` handlers + real `sessionsList`) and
|
|
953
|
+
* discriminant-typed mutation shapes → 0.5.0. `tasksMutate`'s payload shape
|
|
954
|
+
* changed from a flat `{taskId, projectId?, status?, ...}` to a discriminated
|
|
955
|
+
* `{op: "create"|"update", ...}` union. `parseCoreLinkRequestFrame` only
|
|
956
|
+
* validates the outer `type`, so a stale-shape mutation payload lands at the
|
|
957
|
+
* mutation store's runtime `op` check and comes back as an actionable `error`
|
|
958
|
+
* frame ("unknown task mutation op: undefined"). No shipped Panel yet routes
|
|
959
|
+
* writes through this frame (the loopback API still owns local writes; remote
|
|
960
|
+
* writes were stubbed as `task: null`), so no live caller regresses. Issue 09
|
|
961
|
+
* adds the `icon` field to {@link CoreLinkTaskSnapshot} and the create/update
|
|
962
|
+
* variants of {@link CoreLinkTaskMutation}, plus a dedicated
|
|
963
|
+
* `task:iconChanged` event kind so replays surface icon-only edits distinctly
|
|
964
|
+
* from other task updates → 0.6.0. Issue 11 adds the `agentsAvailabilityList`
|
|
965
|
+
* request/response + the `agents:availabilityChanged` event kind → 0.7.0.
|
|
966
|
+
* Additive on both sides — a Core that has not been upgraded ignores the
|
|
967
|
+
* new request frame (the outer `parseCoreLinkRequestFrame` rejects unknown
|
|
968
|
+
* types), and a Panel hydrating from an older Core sees an empty
|
|
969
|
+
* availability map and falls back to the same "checking…" affordance a fresh
|
|
970
|
+
* boot shows. Issue 10 adds a dedicated `pin` op to
|
|
971
|
+
* {@link CoreLinkProjectMutation} plus the `project:pinnedChanged` /
|
|
972
|
+
* `task:pinnedChanged` event kinds (task pin-only updates now surface
|
|
973
|
+
* distinctly, mirroring the icon-only path from issue 09) → 0.8.0. Web-panel
|
|
974
|
+
* issue 06 adds the `dirList` / `dirCreate` request frames and their results,
|
|
975
|
+
* so the browser's folder picker browses the Core's disk instead of a
|
|
976
|
+
* machine-local dialog that no longer exists → 0.9.0. Additive: a Core that
|
|
977
|
+
* has not been upgraded rejects the unknown request type, which the Panel
|
|
978
|
+
* surfaces as the same actionable error any other failed listing produces.
|
|
979
|
+
* Issue 22 adds a `settings` op to {@link CoreLinkProjectMutation}, the same
|
|
980
|
+
* remembered-settings fields on its `create` variant, those fields on
|
|
981
|
+
* {@link CoreLinkProjectSnapshot}, and the `project:settingsChanged` event
|
|
982
|
+
* kind → 0.10.0. Every column they land in already exists in the shared
|
|
983
|
+
* schema bootstrap, so no migration rides along. Unlike the additive bumps
|
|
984
|
+
* above, there is no partial-compatibility story to describe here and none is
|
|
985
|
+
* wanted: the minor moved, so a Core still speaking 0.9.0 is incompatible by
|
|
986
|
+
* the major.minor rule below and renders as "needs update" (ADR 0005). It
|
|
987
|
+
* never reaches the mutation store's runtime `op` check.
|
|
988
|
+
* Issue 63 adds a `delete` op to {@link CoreLinkTaskMutation} and the
|
|
989
|
+
* `task:deleted` event kind the Core appends for it → 0.11.0. Deleting a
|
|
990
|
+
* Core-owned Session had no operation to carry, so every delete call site fell
|
|
991
|
+
* through to the Panel's own endpoint and 404'd. Same rule as above: the minor
|
|
992
|
+
* moved, so a Core on 0.10.0 is "needs update" rather than a Core that accepts
|
|
993
|
+
* the frame and silently drops the op.
|
|
994
|
+
* Issue 62 adds the `archivedTasksList` / `archivedTasksListResult` frames and
|
|
995
|
+
* an unconditional `archivedCount` on {@link CoreLinkServerFrame}'s
|
|
996
|
+
* `tasksListResult` → 0.12.0 (ADR 0019). Archived rows had no way across the
|
|
997
|
+
* link at all, so the Panel's Archived view was permanently empty for a Core
|
|
998
|
+
* and restore could not be invoked. The count is required rather than
|
|
999
|
+
* optional: a Core on 0.11.0 would answer without it and the Archived tab
|
|
1000
|
+
* would silently never appear, which is exactly the bug — so the minor moves
|
|
1001
|
+
* and such a Core renders as "needs update".
|
|
1002
|
+
* Issue 84 adds `titleManuallySet` and `claudeSessionId` to
|
|
1003
|
+
* {@link CoreLinkTaskSnapshot} and to the `update` variant of
|
|
1004
|
+
* {@link CoreLinkTaskMutation}, plus `hooksReportTurnStart` on the `spawned`
|
|
1005
|
+
* response, and the `harnessPrompt` frame that carries a terminal-captured
|
|
1006
|
+
* prompt to the Core that can act on it → 0.13.0 (ADR 0020). The snapshot
|
|
1007
|
+
* fields are the load-bearing part: a Core that grew them without this bump
|
|
1008
|
+
* would hand a Panel that predates it a snapshot whose rename protection is
|
|
1009
|
+
* silently absent, and a Panel that predates the Core would keep synthesizing
|
|
1010
|
+
* `false` — both render a Session the generator is free to rename out from
|
|
1011
|
+
* under its operator. Same rule as above: the minor moved, so either side on
|
|
1012
|
+
* 0.12.0 is "needs update" rather than a partial snapshot nobody notices. No
|
|
1013
|
+
* migration rides along — the columns (`title_manually_set`,
|
|
1014
|
+
* `claude_session_id`) have been in the shared schema bootstrap since the
|
|
1015
|
+
* fork; only the wire and the Core's readers/writers of them are new.
|
|
1016
|
+
* Issue 83 adds the `harnessInstall` request frame, its `harnessInstallAck`
|
|
1017
|
+
* response, and the `harness:installFailed` event kind → 0.14.0 (ADR 0021).
|
|
1018
|
+
* Availability crossed the link Core→Panel only, so a missing CLI was a wall:
|
|
1019
|
+
* the picker greyed the row out and told the operator to go run a command on
|
|
1020
|
+
* another machine. This is the trigger that was absent — the Core still owns
|
|
1021
|
+
* probing and installing, and still publishes availability as the one source of
|
|
1022
|
+
* truth. Same rule as the bumps above: the minor moved, so a Core on 0.13.0
|
|
1023
|
+
* renders as "needs update" rather than one that silently drops the frame.
|
|
1024
|
+
* Issue 98 adds an `appearance` op to {@link CoreLinkProjectMutation} and the
|
|
1025
|
+
* `project:appearanceChanged` event kind the Core appends for it → 0.15.0
|
|
1026
|
+
* (ADR 0022). A project's icon and icon colour are Core facts that `create`
|
|
1027
|
+
* could set and nothing could change afterwards, so the Edit-project dialog
|
|
1028
|
+
* PATCHed them at a Panel row a Core-owned project does not have, and 404'd with
|
|
1029
|
+
* the rename already applied. Same rule as above: the minor moved, so a Core on
|
|
1030
|
+
* 0.14.0 is "needs update" rather than one that takes the frame and drops the
|
|
1031
|
+
* op. The columns (`icon`, `icon_color`) have been in the shared schema
|
|
1032
|
+
* bootstrap since the fork, so no migration rides along on the Core's side.
|
|
1033
|
+
*
|
|
1034
|
+
* Issue 142 adds the `ptySubscribe` / `ptyUnsubscribe` frames and their acks —
|
|
1035
|
+
* and deliberately **does not move this version** (ADR 0024 D11). Every bump
|
|
1036
|
+
* above marks every Core in every fleet needs-update, and multi-connection is a
|
|
1037
|
+
* capability a one-Panel fleet never exercises. It is announced on `ready`
|
|
1038
|
+
* instead, by #143 below, which also gates the Panel's sends on it, so these
|
|
1039
|
+
* frames are never put on the wire to a Core that cannot answer them.
|
|
1040
|
+
*
|
|
1041
|
+
* Issue 143 adds the optional `multiConnection` capability to the `ready`
|
|
1042
|
+
* frame and deliberately does NOT move this version (ADR 0024 D11). It is the
|
|
1043
|
+
* first entry in this log that does not: the field is additive and its absence
|
|
1044
|
+
* is the single-connection Core that shipped before it, which is exactly
|
|
1045
|
+
* today's behaviour rather than a lesser one. Moving the minor would mark every
|
|
1046
|
+
* Core in every fleet "needs update" to buy a capability a one-Panel fleet
|
|
1047
|
+
* never exercises. A capability that changed what an existing frame means
|
|
1048
|
+
* would not qualify, and the minor would move as it does above.
|
|
1049
|
+
*
|
|
1050
|
+
* Issue 144 adds the Session lock's `claim` / `release` / `forceTakeover`
|
|
1051
|
+
* frames, their results, and the optional `code` on `error` — and does NOT move
|
|
1052
|
+
* this version either (ADR 0024 D11), for the same reason #142 and #143 did not.
|
|
1053
|
+
* The three frames are gated by the `multiConnection` capability, so they are
|
|
1054
|
+
* never put on the wire to a Core that has no lock table to address. The gating
|
|
1055
|
+
* of `write` / `kill` / `tasksMutate` is additive in the sense the rule
|
|
1056
|
+
* requires: it can only refuse a Session that some connection has explicitly
|
|
1057
|
+
* claimed, and a client that never claims — every client that predates this —
|
|
1058
|
+
* meets an unlocked Session and is served exactly as it is today. `code` is
|
|
1059
|
+
* optional and absent from every error frame that shipped before it, so a client
|
|
1060
|
+
* that has never heard of it reads the same `message` it always did.
|
|
1061
|
+
*
|
|
1062
|
+
* Issue 145 publishes that lock state — the optional `lock` on
|
|
1063
|
+
* {@link CoreLinkTaskSnapshot} and {@link CoreLinkSessionSnapshot}, and the
|
|
1064
|
+
* {@link SESSION_LOCK_CHANGED_EVENT_KIND} event — and does NOT move this version
|
|
1065
|
+
* either (ADR 0024 D11), for the fourth time and the same reason. Note that
|
|
1066
|
+
* issue 84 *did* bump for snapshot fields, and the difference is the rule
|
|
1067
|
+
* itself: a Panel that predated `titleManuallySet` synthesized `false` and
|
|
1068
|
+
* rendered a Session its Core's generator could rename out from under the
|
|
1069
|
+
* operator — the absence yielded something *lesser* than today. A Panel that
|
|
1070
|
+
* predates `lock` ignores an unknown field and renders exactly what it renders
|
|
1071
|
+
* today, because read-only rendering is new surface (#147) rather than a
|
|
1072
|
+
* protection quietly going missing; a new Panel against a Core that omits the
|
|
1073
|
+
* field reads "no lock table here", which is that Core, truthfully. The event
|
|
1074
|
+
* kind is additive in the way every kind before it was: a client routes on kinds
|
|
1075
|
+
* it knows and ignores the rest.
|
|
1076
|
+
*
|
|
1077
|
+
* Issue 146 adds the `reclaim` frame and its `reclaimResult` — and does NOT
|
|
1078
|
+
* move this version either (ADR 0024 D11), for the fifth time and the same
|
|
1079
|
+
* reason. The frame is gated by the `multiConnection` capability, as #142 and
|
|
1080
|
+
* #144 are, so it is never put on the wire to a Core with no lock table to
|
|
1081
|
+
* transfer out of — and a Core that predates the capability evicts every client
|
|
1082
|
+
* but one on connect, which is the reclaim the frame would have asked it for.
|
|
1083
|
+
* The client that presents no id — every client that shipped before this — is
|
|
1084
|
+
* served exactly as it is today: its lost socket is reaped by the heartbeat 45s
|
|
1085
|
+
* later, still the backstop for a client that never comes back. What the frame
|
|
1086
|
+
* buys is those 45 seconds for the client that does come back, so its absence
|
|
1087
|
+
* is slower rather than lesser, which is the rule. `clientId` is not identity
|
|
1088
|
+
* and carries no authority a connection did not already have (D10), so there is
|
|
1089
|
+
* nothing here for an older client to be missing.
|
|
1090
|
+
*/
|
|
1091
|
+
export declare const CORE_LINK_PROTOCOL_VERSION = "0.15.0";
|
|
1092
|
+
/**
|
|
1093
|
+
* Does a Core advertising `reported` speak this build's core-link?
|
|
1094
|
+
*
|
|
1095
|
+
* The rule is major.minor equality, and it is deliberately blunt: ADR 0005 says
|
|
1096
|
+
* the Panel carries no feature detection, so there is no "mostly compatible"
|
|
1097
|
+
* state to describe. A Core is either speaking this vocabulary or it is a chore
|
|
1098
|
+
* — one command on that machine — and the Panel says so rather than degrading.
|
|
1099
|
+
* Patch is ignored because the version above only moves on wire changes;
|
|
1100
|
+
* reserving the patch segment for fixes that touch no frame keeps a
|
|
1101
|
+
* behaviour-only release from grounding a fleet.
|
|
1102
|
+
*
|
|
1103
|
+
* A missing or unparseable version is incompatible: every Core that speaks
|
|
1104
|
+
* a version the Panel could accept says so in its `ready` frame, so silence is
|
|
1105
|
+
* evidence of something older than the frame that carries it.
|
|
1106
|
+
*/
|
|
1107
|
+
export declare function coreLinkProtocolCompatible(reported: string | null | undefined): boolean;
|
|
1108
|
+
/**
|
|
1109
|
+
* Read the `multiConnection` capability off a raw `ready` frame, or null.
|
|
1110
|
+
*
|
|
1111
|
+
* Null for every shape that is not exactly `{ version: 1 }` — absent, a
|
|
1112
|
+
* non-object, or a version this build does not know. An unrecognised future
|
|
1113
|
+
* version reads as absent on purpose: the safe fallback is the single-connection
|
|
1114
|
+
* behaviour that predates the capability, never a guess at what a higher version
|
|
1115
|
+
* means. Unlike {@link coreLinkProtocolCompatible}, nothing here can mark a Core
|
|
1116
|
+
* incompatible — a null answer withholds frames, it does not fail a Core.
|
|
1117
|
+
*/
|
|
1118
|
+
export declare function readMultiConnectionCapability(raw: unknown): CoreLinkMultiConnectionCapability | null;
|
|
1119
|
+
/** Parse and validate a raw WS message into a known request frame, or null. */
|
|
1120
|
+
export declare function parseCoreLinkRequestFrame(raw: string): CoreLinkRequestFrame | null;
|
|
1121
|
+
/** Serialize a server frame for sending over the WebSocket. */
|
|
1122
|
+
export declare function serializeCoreLinkFrame(frame: CoreLinkServerFrame): string;
|
|
1123
|
+
//# sourceMappingURL=core-link-frames.d.ts.map
|