@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,52 @@
|
|
|
1
|
+
import type { CoreLinkTlsMaterial } from "./core-link-socket.ts";
|
|
2
|
+
/**
|
|
3
|
+
* A decoded registration blob. `endpoint` is the Core's `wss://host:port` core
|
|
4
|
+
* link; `label` is the machine's own suggestion for an alias and is not used by
|
|
5
|
+
* anything here.
|
|
6
|
+
*/
|
|
7
|
+
export type CoreRegistrationBlob = {
|
|
8
|
+
endpoint: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
caCert: string;
|
|
11
|
+
clientCert: string;
|
|
12
|
+
clientKey: string;
|
|
13
|
+
bearer: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Everything a blob says about how to reach one Core, unpacked into the parts a
|
|
17
|
+
* client dials with — and **exposed rather than merely used**.
|
|
18
|
+
*
|
|
19
|
+
* `httpsBaseUrl` is the reason this type exists instead of a private helper.
|
|
20
|
+
* A Core's file surface will mount on the same mTLS server the WebSocket
|
|
21
|
+
* already listens on (#129 F2), and the same `tls` material authenticates both,
|
|
22
|
+
* so the connection a client holds is "a Core connection" and not "a socket"
|
|
23
|
+
* (#129's phase-3 guardrail 1). Naming the base URL now costs nothing and keeps
|
|
24
|
+
* that from being a breaking change to a published surface later. Nothing in
|
|
25
|
+
* this phase reads it.
|
|
26
|
+
*/
|
|
27
|
+
export type CoreConnection = {
|
|
28
|
+
/** The `wss://` (or `ws://`) core-link URL to dial. */
|
|
29
|
+
url: string;
|
|
30
|
+
/**
|
|
31
|
+
* The `https://` origin of the same Core, derived by swapping the scheme —
|
|
32
|
+
* the server that terminates the WebSocket is the server that will answer
|
|
33
|
+
* `/v1/…`. No path, no trailing slash.
|
|
34
|
+
*/
|
|
35
|
+
httpsBaseUrl: string;
|
|
36
|
+
/** The PEM material for the mTLS handshake, or null for a plain `ws://` dial. */
|
|
37
|
+
tls: CoreLinkTlsMaterial | null;
|
|
38
|
+
/** The signed bearer presented in the `auth` frame right after the handshake. */
|
|
39
|
+
bearer: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Unpack a registration blob into a {@link CoreConnection}.
|
|
43
|
+
*
|
|
44
|
+
* `ws://` is accepted here even though a blob's endpoint must be `wss://` for a
|
|
45
|
+
* real Core (ADR 0002, and the Panel's decoder refuses anything else): this
|
|
46
|
+
* function converts a shape, it does not police one, and a loopback endpoint is
|
|
47
|
+
* how the Core's own tests and a local rig dial. A `ws://` endpoint yields
|
|
48
|
+
* `tls: null` — there is no handshake to pin — and the bearer is still
|
|
49
|
+
* presented, because the `auth` frame is the app-layer session either way.
|
|
50
|
+
*/
|
|
51
|
+
export declare function coreConnectionFromBlob(blob: CoreRegistrationBlob): CoreConnection;
|
|
52
|
+
//# sourceMappingURL=core-registration-blob.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-registration-blob.d.ts","sourceRoot":"","sources":["../src/core-registration-blob.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,GAAG,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAChC,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,cAAc,CAWjF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The registration blob, as a Core client reads it.
|
|
2
|
+
//
|
|
3
|
+
// `actana setup` emits one base64 artifact per machine — `{endpoint, caCert,
|
|
4
|
+
// clientCert, clientKey, bearer}` (CONTEXT.md "Registration blob"). It is the
|
|
5
|
+
// whole of what a Core client needs to reach a Core: the endpoint to dial, the
|
|
6
|
+
// PEM material for the mTLS handshake, and the bearer for the `auth` frame
|
|
7
|
+
// (#129 D1).
|
|
8
|
+
//
|
|
9
|
+
// Two deliberate boundaries.
|
|
10
|
+
//
|
|
11
|
+
// **The SDK takes a blob object, never a file** (#129 D9). Where the blob is
|
|
12
|
+
// kept — `~/.config/actana/cores/<name>.txt`, an environment variable, a
|
|
13
|
+
// Panel's encrypted store — is the caller's business, and the CLI's config
|
|
14
|
+
// resolution is its own ticket. Nothing here reads a path or base64-decodes a
|
|
15
|
+
// paste.
|
|
16
|
+
//
|
|
17
|
+
// **The shape is declared here, not imported from `@actana/shared`.**
|
|
18
|
+
// `packages/shared` is private and stays private ([ADR 0025][adr] D4), so an
|
|
19
|
+
// SDK that imported `RegistrationBlob` from it would be a published package
|
|
20
|
+
// with a dependency nobody outside this repository can resolve — the exact
|
|
21
|
+
// alternative that ADR rejected. The two declarations are structurally
|
|
22
|
+
// identical, so a Panel holding a `RegistrationBlob` passes it here with no
|
|
23
|
+
// conversion, and the field names are the wire's rather than either side's.
|
|
24
|
+
//
|
|
25
|
+
// [adr]: ../../docs/adr/0025-the-protocol-ships-with-the-client.md
|
|
26
|
+
/**
|
|
27
|
+
* Unpack a registration blob into a {@link CoreConnection}.
|
|
28
|
+
*
|
|
29
|
+
* `ws://` is accepted here even though a blob's endpoint must be `wss://` for a
|
|
30
|
+
* real Core (ADR 0002, and the Panel's decoder refuses anything else): this
|
|
31
|
+
* function converts a shape, it does not police one, and a loopback endpoint is
|
|
32
|
+
* how the Core's own tests and a local rig dial. A `ws://` endpoint yields
|
|
33
|
+
* `tls: null` — there is no handshake to pin — and the bearer is still
|
|
34
|
+
* presented, because the `auth` frame is the app-layer session either way.
|
|
35
|
+
*/
|
|
36
|
+
export function coreConnectionFromBlob(blob) {
|
|
37
|
+
const url = blob.endpoint.trim();
|
|
38
|
+
const secure = url.startsWith("wss://");
|
|
39
|
+
return {
|
|
40
|
+
url,
|
|
41
|
+
httpsBaseUrl: httpsBaseUrlFor(url),
|
|
42
|
+
tls: secure
|
|
43
|
+
? { ca: blob.caCert, cert: blob.clientCert, key: blob.clientKey }
|
|
44
|
+
: null,
|
|
45
|
+
bearer: blob.bearer,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* `wss://host:port` → `https://host:port`, `ws://…` → `http://…`. Anything
|
|
50
|
+
* else is returned unchanged rather than guessed at — a caller that handed this
|
|
51
|
+
* a URL with no WebSocket scheme knows something about its Core that this
|
|
52
|
+
* function does not.
|
|
53
|
+
*/
|
|
54
|
+
function httpsBaseUrlFor(url) {
|
|
55
|
+
if (url.startsWith("wss://"))
|
|
56
|
+
return `https://${url.slice("wss://".length)}`.replace(/\/+$/, "");
|
|
57
|
+
if (url.startsWith("ws://"))
|
|
58
|
+
return `http://${url.slice("ws://".length)}`.replace(/\/+$/, "");
|
|
59
|
+
return url;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=core-registration-blob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-registration-blob.js","sourceRoot":"","sources":["../src/core-registration-blob.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,+EAA+E;AAC/E,2EAA2E;AAC3E,aAAa;AACb,EAAE;AACF,6BAA6B;AAC7B,EAAE;AACF,6EAA6E;AAC7E,yEAAyE;AACzE,2EAA2E;AAC3E,8EAA8E;AAC9E,SAAS;AACT,EAAE;AACF,sEAAsE;AACtE,6EAA6E;AAC7E,4EAA4E;AAC5E,2EAA2E;AAC3E,uEAAuE;AACvE,4EAA4E;AAC5E,4EAA4E;AAC5E,EAAE;AACF,mEAAmE;AA6CnE;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAA0B;IAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACxC,OAAO;QACL,GAAG;QACH,YAAY,EAAE,eAAe,CAAC,GAAG,CAAC;QAClC,GAAG,EAAE,MAAM;YACT,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE;YACjE,CAAC,CAAC,IAAI;QACR,MAAM,EAAE,IAAI,CAAC,MAAM;KACpB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,GAAW;IAClC,IAAI,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,WAAW,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACjG,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,UAAU,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9F,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import type { CoreLinkPtySpawnHarness } from "./core-link-frames.ts";
|
|
2
|
+
import type { CoreClient } from "./core-client.ts";
|
|
3
|
+
type Unsubscribe = () => void;
|
|
4
|
+
/**
|
|
5
|
+
* The command a fresh Session starts with, per harness, when the caller names
|
|
6
|
+
* none.
|
|
7
|
+
*
|
|
8
|
+
* The first token has to be that harness's canonical binary or the Core refuses
|
|
9
|
+
* the spawn — that is the allow-list, and it is enforced there, not here. What
|
|
10
|
+
* this table adds beyond the binary is the one flag a harness needs for the
|
|
11
|
+
* Core to hear about it at all: `codex` reports its lifecycle through hooks only
|
|
12
|
+
* when started with `--enable hooks`, and a Session that never reports is a
|
|
13
|
+
* Session {@link CoreSession.waitForIdle} waits on forever.
|
|
14
|
+
*
|
|
15
|
+
* A caller wanting anything else — a model, a resumed session id — passes
|
|
16
|
+
* `command` and takes the Core's answer on it.
|
|
17
|
+
*/
|
|
18
|
+
export declare const HARNESS_LAUNCH_COMMANDS: Readonly<Record<CoreLinkPtySpawnHarness, string>>;
|
|
19
|
+
/**
|
|
20
|
+
* The statuses that mean the harness has stopped and is waiting on a human.
|
|
21
|
+
*
|
|
22
|
+
* `finished` is a completed turn, `needs-input` a permission prompt or a
|
|
23
|
+
* question, `interrupted` an escape, `terminated` a dead process, and
|
|
24
|
+
* `disconnected` a Core that restarted underneath the Session. Every one of them
|
|
25
|
+
* is a state that does not leave on its own, which is the property
|
|
26
|
+
* {@link CoreSession.waitForIdle} is waiting for — not "finished", which would
|
|
27
|
+
* hang on the question a caller could have answered.
|
|
28
|
+
*/
|
|
29
|
+
export declare const SETTLED_SESSION_STATUSES: ReadonlySet<string>;
|
|
30
|
+
/**
|
|
31
|
+
* How a failed status read is re-asked: this many further attempts, this long
|
|
32
|
+
* apart.
|
|
33
|
+
*
|
|
34
|
+
* A retry of a *read*, and only of a read. It re-asks the Core a question whose
|
|
35
|
+
* answer the Core already settled on — it does not retry a prompt, a keystroke
|
|
36
|
+
* or a spawn, and it cannot make a Session look idle sooner than the Core says
|
|
37
|
+
* it is. The reason it has to exist: `needs-input`, `interrupted` and
|
|
38
|
+
* `terminated` reach this layer only as `task:updated`, and that event is
|
|
39
|
+
* appended once. Swallowing the read that failed on it leaves
|
|
40
|
+
* {@link CoreSession.waitForIdle} waiting for a report that will not be made
|
|
41
|
+
* again, and by design there is no deadline to end that wait.
|
|
42
|
+
*
|
|
43
|
+
* Bounded rather than indefinite: a link that is still down after three tries a
|
|
44
|
+
* quarter-second apart is not going to be talked round by a fourth, and a
|
|
45
|
+
* Session that polls forever is the busy-loop version of the timer #191 deleted.
|
|
46
|
+
*/
|
|
47
|
+
export declare const STATUS_READ_RETRIES = 3;
|
|
48
|
+
export declare const STATUS_READ_RETRY_MS = 250;
|
|
49
|
+
export type CoreSessionStartOptions = {
|
|
50
|
+
/**
|
|
51
|
+
* The Project to start this Session in. Either this or {@link taskId}: with a
|
|
52
|
+
* `projectId` a Task row is created on the Core first, because a Session's
|
|
53
|
+
* status lives on that row and a spawn naming a row that does not exist
|
|
54
|
+
* reports nothing back.
|
|
55
|
+
*/
|
|
56
|
+
projectId?: string;
|
|
57
|
+
/** An existing Task to start a Session for. Either this or {@link projectId}. */
|
|
58
|
+
taskId?: string;
|
|
59
|
+
/** Title for the Task created from {@link projectId}. Ignored with a `taskId`. */
|
|
60
|
+
title?: string;
|
|
61
|
+
/**
|
|
62
|
+
* The working directory on the **Core's** machine.
|
|
63
|
+
*
|
|
64
|
+
* A machine path, validatable only there: the Core resolves it through
|
|
65
|
+
* `realpath` and refuses it unless it lands inside a registered Project root.
|
|
66
|
+
* Nothing here checks it — this process may not even be on that machine — so a
|
|
67
|
+
* bad path comes back as a rejected {@link start}, which is the design.
|
|
68
|
+
*/
|
|
69
|
+
cwd: string;
|
|
70
|
+
/** Which harness to run. */
|
|
71
|
+
harness: CoreLinkPtySpawnHarness;
|
|
72
|
+
/**
|
|
73
|
+
* The starting prompt, handed to the Core as `initialInput`.
|
|
74
|
+
*
|
|
75
|
+
* Text, and no timing with it. The Core waits for the harness's TUI to settle,
|
|
76
|
+
* answers whatever dialog it opened, writes this, and sends the carriage
|
|
77
|
+
* return separately (ADR 0026). Omit it to start a Session with no prompt and
|
|
78
|
+
* drive it with {@link CoreSession.send}.
|
|
79
|
+
*/
|
|
80
|
+
prompt?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Override the launch command. Defaults to {@link HARNESS_LAUNCH_COMMANDS}.
|
|
83
|
+
* The Core allow-lists the binary and every flag; a command it does not accept
|
|
84
|
+
* rejects the spawn rather than being trimmed here.
|
|
85
|
+
*/
|
|
86
|
+
command?: string;
|
|
87
|
+
/** PTY width. The screen is built at the same size, or the two disagree about wrapping. */
|
|
88
|
+
cols?: number;
|
|
89
|
+
/** PTY height. Same. */
|
|
90
|
+
rows?: number;
|
|
91
|
+
/** How many scrolled-off lines {@link CoreSession.screen} keeps. */
|
|
92
|
+
scrollback?: number;
|
|
93
|
+
/** Start the harness with permission prompts disabled. See {@link HARNESS_SKIP_PERMISSION_FLAGS}. */
|
|
94
|
+
dangerouslySkipPermissions?: boolean;
|
|
95
|
+
/** The Core's theme hint for the harness, so its output matches a Panel's. */
|
|
96
|
+
theme?: "dark" | "light";
|
|
97
|
+
/**
|
|
98
|
+
* Subscribe this client to the Core's event log if nothing has yet, so
|
|
99
|
+
* {@link CoreSession.waitForIdle} and {@link CoreSession.onStatus} have
|
|
100
|
+
* something to work from. Default true.
|
|
101
|
+
*
|
|
102
|
+
* Set it false when the events are already arriving by another route and the
|
|
103
|
+
* replay tail is not wanted — a {@link DurableCoreClient} subscribes itself,
|
|
104
|
+
* and this checks before sending anything, so the flag is for the case where a
|
|
105
|
+
* caller is doing something the check cannot see.
|
|
106
|
+
*/
|
|
107
|
+
subscribeToEvents?: boolean;
|
|
108
|
+
};
|
|
109
|
+
/** What a Session settled on. */
|
|
110
|
+
export type CoreSessionIdle = {
|
|
111
|
+
/** The Core's status for this Session — one of {@link SETTLED_SESSION_STATUSES}. */
|
|
112
|
+
status: string;
|
|
113
|
+
/** True when the harness's process exited rather than settling on a status. */
|
|
114
|
+
exited: boolean;
|
|
115
|
+
/** The process's exit code, when it exited. */
|
|
116
|
+
exitCode?: number;
|
|
117
|
+
};
|
|
118
|
+
export type CoreSessionWaitOptions = {
|
|
119
|
+
/**
|
|
120
|
+
* Give up after this long, in ms. **No deadline by default**, and that is not
|
|
121
|
+
* an oversight: a turn takes as long as the work takes, and a default that
|
|
122
|
+
* fires would report a healthy Session as broken — the same lie the flat
|
|
123
|
+
* timer #191 deleted used to tell. A caller that needs a deadline knows what
|
|
124
|
+
* its own is.
|
|
125
|
+
*/
|
|
126
|
+
timeoutMs?: number;
|
|
127
|
+
};
|
|
128
|
+
/** The Core refused to start this Session. Carries the Core's own reason. */
|
|
129
|
+
export declare class CoreSessionStartError extends Error {
|
|
130
|
+
constructor(message: string, options?: {
|
|
131
|
+
cause?: unknown;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* One Session on one Core, driven programmatically.
|
|
136
|
+
*
|
|
137
|
+
* Built by {@link start}. Holds a screen fed from the Session's PTY, the Core's
|
|
138
|
+
* last reported status for it, and the listeners that keep both current — so
|
|
139
|
+
* it must be released with {@link dispose} (or {@link kill}, which disposes)
|
|
140
|
+
* when the caller is done, or those listeners outlive it on the client.
|
|
141
|
+
*/
|
|
142
|
+
export declare class CoreSession {
|
|
143
|
+
/** The Task this Session belongs to. Its status is the Session's status. */
|
|
144
|
+
readonly taskId: string;
|
|
145
|
+
/** The Core's id for this Session's PTY. */
|
|
146
|
+
readonly ptyId: string;
|
|
147
|
+
/** The harness running in it. */
|
|
148
|
+
readonly harness: CoreLinkPtySpawnHarness;
|
|
149
|
+
/** The command the Core was asked to start, after defaulting. */
|
|
150
|
+
readonly command: string;
|
|
151
|
+
private readonly client;
|
|
152
|
+
private readonly terminal;
|
|
153
|
+
private readonly unsubscribes;
|
|
154
|
+
private readonly dataListeners;
|
|
155
|
+
private readonly exitListeners;
|
|
156
|
+
private readonly statusListeners;
|
|
157
|
+
private readonly idleWaiters;
|
|
158
|
+
/**
|
|
159
|
+
* The Core's last reported status, or null before one has been *observed*.
|
|
160
|
+
*
|
|
161
|
+
* Null rather than the status the Task carried when this Session started, and
|
|
162
|
+
* the distinction is what makes {@link waitForIdle} correct: a caller starting
|
|
163
|
+
* a Session on a Task that was already `finished` is waiting for the next turn
|
|
164
|
+
* to end, not being told about the last one. Only a status learned from an
|
|
165
|
+
* event after {@link start} lands here.
|
|
166
|
+
*/
|
|
167
|
+
private lastStatus;
|
|
168
|
+
private exit;
|
|
169
|
+
private disposed;
|
|
170
|
+
/** A status read is in flight; another event arrived while it was. */
|
|
171
|
+
private statusReadInFlight;
|
|
172
|
+
private statusReadAgain;
|
|
173
|
+
/** Re-asks left for the read that failed, and the timer carrying the next. */
|
|
174
|
+
private statusReadRetriesLeft;
|
|
175
|
+
private statusRetryTimer;
|
|
176
|
+
private constructor();
|
|
177
|
+
/**
|
|
178
|
+
* Start a Session and return once the Core has one running.
|
|
179
|
+
*
|
|
180
|
+
* What happens, in order: a Task row is created when the caller named a
|
|
181
|
+
* Project rather than a Task; the client is subscribed to the Core's event log
|
|
182
|
+
* if nothing has done that yet; the PTY's byte stream is wired up *before* the
|
|
183
|
+
* spawn goes out; and the spawn carries the prompt as `initialInput` for the
|
|
184
|
+
* Core to deliver.
|
|
185
|
+
*
|
|
186
|
+
* The stream is wired first on purpose. A harness starts printing its banner
|
|
187
|
+
* immediately, and on a Core that fans output out by subscription the
|
|
188
|
+
* connection that spawned a PTY is subscribed to it before the answer is even
|
|
189
|
+
* written — so the first bytes can be on the wire before this side knows the
|
|
190
|
+
* PTY's id. They are held and replayed into the screen once it is known, which
|
|
191
|
+
* is the difference between a transcript that starts at the beginning and one
|
|
192
|
+
* that starts wherever the round trip happened to end.
|
|
193
|
+
*
|
|
194
|
+
* Rejects with the Core's own message when the Core refuses: a working
|
|
195
|
+
* directory outside every registered Project root, a command whose binary is
|
|
196
|
+
* not that harness's, a flag that is not allow-listed, a harness that is not
|
|
197
|
+
* installed on that machine.
|
|
198
|
+
*/
|
|
199
|
+
static start(client: CoreClient, opts: CoreSessionStartOptions): Promise<CoreSession>;
|
|
200
|
+
/**
|
|
201
|
+
* Write to the Session, exactly these bytes and nothing else.
|
|
202
|
+
*
|
|
203
|
+
* The equivalent of typing, not of prompting. Nothing is appended: no carriage
|
|
204
|
+
* return, no delay, no waiting for the harness to look ready. That restraint
|
|
205
|
+
* is the rule rather than a gap — a client that decided when to press Enter
|
|
206
|
+
* would be doing prompt delivery, which is the Core's (ADR 0026), and would do
|
|
207
|
+
* it differently from every other client. A *starting* prompt goes through
|
|
208
|
+
* {@link CoreSessionStartOptions.prompt}, where the Core owns the schedule.
|
|
209
|
+
*
|
|
210
|
+
* What this is for is everything after: answering the numbered option of a
|
|
211
|
+
* question the harness asked (`send("2")` then `send("\r")`), an escape
|
|
212
|
+
* (`send("\u001B")`), a follow-up typed into a harness already at its prompt.
|
|
213
|
+
*
|
|
214
|
+
* Resolves false when the Core did not accept the write — a PTY that has
|
|
215
|
+
* exited. Rejects when another Core client holds this Session's lock.
|
|
216
|
+
*/
|
|
217
|
+
send(text: string): Promise<boolean>;
|
|
218
|
+
/**
|
|
219
|
+
* Every chunk of this Session's output, as it arrives.
|
|
220
|
+
*
|
|
221
|
+
* Raw PTY bytes, escape sequences included — the same stream the screen is
|
|
222
|
+
* built from. A caller wanting the rendered text wants {@link screen}; this is
|
|
223
|
+
* for one that is streaming somewhere else.
|
|
224
|
+
*/
|
|
225
|
+
onData(cb: (chunk: string) => void): Unsubscribe;
|
|
226
|
+
/** The harness's process exited. Fires once. */
|
|
227
|
+
onExit(cb: (exit: {
|
|
228
|
+
exitCode: number;
|
|
229
|
+
signal?: number;
|
|
230
|
+
}) => void): Unsubscribe;
|
|
231
|
+
/** The Core reported a new status for this Session. */
|
|
232
|
+
onStatus(cb: (status: string) => void): Unsubscribe;
|
|
233
|
+
/**
|
|
234
|
+
* What a terminal would be showing for this Session, **including the lines
|
|
235
|
+
* that have scrolled off the top of it**.
|
|
236
|
+
*
|
|
237
|
+
* The scrolled-off part is not a bonus: a harness's conversation left the
|
|
238
|
+
* screen long ago, so the transcript is the scrollback and a caller reading
|
|
239
|
+
* only the visible rows reads a status bar. See `terminal-screen.ts` for what
|
|
240
|
+
* is emulated and what an erase costs.
|
|
241
|
+
*
|
|
242
|
+
* **Read it while the Session is alive.** A harness that runs full-screen
|
|
243
|
+
* leaves the alternate screen when it quits, and what a terminal shows after
|
|
244
|
+
* that is the main buffer — which is where nothing was ever printed. Reading
|
|
245
|
+
* before {@link kill}, not after, is the difference between the transcript and
|
|
246
|
+
* an empty string. (Claude Code 2.1.228 runs full-screen, so this is the
|
|
247
|
+
* ordinary case rather than an exotic one.)
|
|
248
|
+
*/
|
|
249
|
+
screen(): string;
|
|
250
|
+
/** Only the rows on screen right now — for reading a dialog rather than a transcript. */
|
|
251
|
+
viewport(): string;
|
|
252
|
+
/** The screen as an array of lines, scrollback first. */
|
|
253
|
+
lines(): string[];
|
|
254
|
+
/** The Core's last reported status, or null before one has been observed. */
|
|
255
|
+
status(): string | null;
|
|
256
|
+
/** How the harness's process ended, or null while it is running. */
|
|
257
|
+
exitStatus(): {
|
|
258
|
+
exitCode: number;
|
|
259
|
+
signal?: number;
|
|
260
|
+
} | null;
|
|
261
|
+
/**
|
|
262
|
+
* Wait until the Core reports this Session settled — the harness finished its
|
|
263
|
+
* turn, asked a question, was interrupted, or died.
|
|
264
|
+
*
|
|
265
|
+
* **The Core's report, not a guess from the byte stream.** The harness's own
|
|
266
|
+
* lifecycle hooks move the Session's status on the Core, the change lands in
|
|
267
|
+
* the event log, and this is watching for it. Nothing here inspects output for
|
|
268
|
+
* quietness: that is a timing decision, it belongs to the Core, and the flat
|
|
269
|
+
* timer that used to make it here is what #191 deleted.
|
|
270
|
+
*
|
|
271
|
+
* Only statuses observed *after* this Session started count, so starting one
|
|
272
|
+
* on a Task that was already `finished` waits for this turn rather than
|
|
273
|
+
* returning last turn's answer. An exit resolves it too — a harness that died
|
|
274
|
+
* is not going to report anything else.
|
|
275
|
+
*
|
|
276
|
+
* Resolves as soon as it can: if the Session has already settled by the time
|
|
277
|
+
* this is called, it answers from what it saw.
|
|
278
|
+
*
|
|
279
|
+
* With no {@link CoreSessionWaitOptions.timeoutMs} this waits indefinitely, on
|
|
280
|
+
* purpose. A status read that fails is re-asked, so a link that blinks does
|
|
281
|
+
* not cost the report; a link that stays down does, and nothing here invents a
|
|
282
|
+
* status the Core never sent. A caller that must not hang on a broken Core
|
|
283
|
+
* passes a deadline it chose itself.
|
|
284
|
+
*/
|
|
285
|
+
waitForIdle(opts?: CoreSessionWaitOptions): Promise<CoreSessionIdle>;
|
|
286
|
+
/** Resize the PTY and the screen together, so both agree about wrapping. */
|
|
287
|
+
resize(cols: number, rows: number): Promise<boolean>;
|
|
288
|
+
/** Kill the harness's process, then release this Session's listeners. */
|
|
289
|
+
kill(): Promise<boolean>;
|
|
290
|
+
/**
|
|
291
|
+
* Release the listeners this Session holds on the client, leaving the harness
|
|
292
|
+
* running. The screen stops advancing; the Core carries on.
|
|
293
|
+
*/
|
|
294
|
+
dispose(): void;
|
|
295
|
+
private ingest;
|
|
296
|
+
private ingestExit;
|
|
297
|
+
/** @internal — called by {@link start} for events held during the spawn. */
|
|
298
|
+
private onCoreEvent;
|
|
299
|
+
/**
|
|
300
|
+
* Read this Session's status back off the Core.
|
|
301
|
+
*
|
|
302
|
+
* The `task:updated` event says a row moved, not what it moved to, and the
|
|
303
|
+
* Core owns the answer — so it is asked. Coalesced, because a turn's worth of
|
|
304
|
+
* hook events arrives in a burst and each one would otherwise be its own round
|
|
305
|
+
* trip: a read already in flight is re-run once at the end rather than queued
|
|
306
|
+
* behind itself.
|
|
307
|
+
*
|
|
308
|
+
* A read that fails is re-asked ({@link STATUS_READ_RETRIES}), because on
|
|
309
|
+
* `needs-input`, `interrupted` and `terminated` there is no second event to
|
|
310
|
+
* carry the news.
|
|
311
|
+
*/
|
|
312
|
+
private readStatus;
|
|
313
|
+
/** The next re-ask of a read that failed. Cleared by {@link dispose}. */
|
|
314
|
+
private scheduleStatusRetry;
|
|
315
|
+
private noteStatus;
|
|
316
|
+
/** What {@link waitForIdle} would answer right now, or null if it must wait. */
|
|
317
|
+
private settledNow;
|
|
318
|
+
private releaseWaiters;
|
|
319
|
+
}
|
|
320
|
+
export {};
|
|
321
|
+
//# sourceMappingURL=core-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-session.d.ts","sourceRoot":"","sources":["../src/core-session.ts"],"names":[],"mappings":"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"}
|