@telorun/runner-core 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -2
- package/dist/backend.d.ts +107 -23
- package/dist/backend.d.ts.map +1 -1
- package/dist/config.d.ts +27 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -1
- package/dist/contract.d.ts +199 -15
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +9 -0
- package/dist/contract.js.map +1 -1
- package/dist/debug/ports-resolved.d.ts +26 -0
- package/dist/debug/ports-resolved.d.ts.map +1 -0
- package/dist/debug/ports-resolved.js +42 -0
- package/dist/debug/ports-resolved.js.map +1 -0
- package/dist/debug/relay.d.ts.map +1 -1
- package/dist/debug/relay.js +34 -6
- package/dist/debug/relay.js.map +1 -1
- package/dist/debug/run-projection.d.ts +53 -0
- package/dist/debug/run-projection.d.ts.map +1 -0
- package/dist/debug/run-projection.js +115 -0
- package/dist/debug/run-projection.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/routes/apps.d.ts +1 -1
- package/dist/routes/apps.d.ts.map +1 -1
- package/dist/routes/apps.js +14 -4
- package/dist/routes/apps.js.map +1 -1
- package/dist/routes/io.d.ts.map +1 -1
- package/dist/routes/io.js +42 -9
- package/dist/routes/io.js.map +1 -1
- package/dist/routes/session-start.d.ts +12 -12
- package/dist/routes/session-start.d.ts.map +1 -1
- package/dist/routes/session-start.js +86 -13
- package/dist/routes/session-start.js.map +1 -1
- package/dist/routes/sessions.d.ts +11 -1
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +429 -4
- package/dist/routes/sessions.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +21 -1
- package/dist/server.js.map +1 -1
- package/dist/session/byte-ring-buffer.d.ts +6 -1
- package/dist/session/byte-ring-buffer.d.ts.map +1 -1
- package/dist/session/byte-ring-buffer.js +2 -2
- package/dist/session/byte-ring-buffer.js.map +1 -1
- package/dist/session/registry.d.ts +144 -13
- package/dist/session/registry.d.ts.map +1 -1
- package/dist/session/registry.js +166 -25
- package/dist/session/registry.js.map +1 -1
- package/dist/session/watch-supervisor.d.ts +42 -0
- package/dist/session/watch-supervisor.d.ts.map +1 -0
- package/dist/session/watch-supervisor.js +99 -0
- package/dist/session/watch-supervisor.js.map +1 -0
- package/dist/session/workspace-app.d.ts +4 -0
- package/dist/session/workspace-app.d.ts.map +1 -0
- package/dist/session/workspace-app.js +26 -0
- package/dist/session/workspace-app.js.map +1 -0
- package/dist/session/workspace-client.d.ts +33 -0
- package/dist/session/workspace-client.d.ts.map +1 -0
- package/dist/session/workspace-client.js +62 -0
- package/dist/session/workspace-client.js.map +1 -0
- package/dist/session/workspace-marker.d.ts +25 -0
- package/dist/session/workspace-marker.d.ts.map +1 -0
- package/dist/session/workspace-marker.js +35 -0
- package/dist/session/workspace-marker.js.map +1 -0
- package/dist/sse/channel.d.ts.map +1 -1
- package/dist/sse/channel.js +4 -0
- package/dist/sse/channel.js.map +1 -1
- package/package.json +4 -3
- package/src/backend.ts +111 -22
- package/src/config.ts +59 -0
- package/src/contract.ts +197 -15
- package/src/debug/ports-resolved.test.ts +66 -0
- package/src/debug/ports-resolved.ts +43 -0
- package/src/debug/relay.ts +32 -4
- package/src/debug/run-projection.test.ts +155 -0
- package/src/debug/run-projection.ts +122 -0
- package/src/index.ts +20 -1
- package/src/routes/apps.ts +14 -5
- package/src/routes/io.ts +58 -12
- package/src/routes/session-start.ts +106 -27
- package/src/routes/sessions.ts +498 -7
- package/src/server.ts +22 -1
- package/src/session/byte-ring-buffer.ts +8 -2
- package/src/session/registry.ts +288 -28
- package/src/session/ring-buffer.test.ts +4 -3
- package/src/session/watch-lifetime.test.ts +203 -0
- package/src/session/watch-supervisor.ts +112 -0
- package/src/session/workspace-app.ts +27 -0
- package/src/session/workspace-client.ts +80 -0
- package/src/session/workspace-marker.test.ts +35 -0
- package/src/session/workspace-marker.ts +39 -0
- package/src/sse/channel.ts +5 -0
- package/workspace-app/telo.yaml +228 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/** How often the supervisor wakes. Both of its jobs are coarse — a checkpoint
|
|
2
|
+
* cadence and an idle window, both measured in minutes — so a fine tick would
|
|
3
|
+
* buy nothing but wakeups. */
|
|
4
|
+
const TICK_MS = 5_000;
|
|
5
|
+
/**
|
|
6
|
+
* The two background jobs that make a watch session affordable, and they only
|
|
7
|
+
* work as a pair: a periodic workspace checkpoint, and an idle reap that deletes
|
|
8
|
+
* the pod while keeping the session record and that checkpoint.
|
|
9
|
+
*
|
|
10
|
+
* Aggressive reaping is what makes per-visitor watch sessions affordable, and
|
|
11
|
+
* the checkpoint is what makes aggressive reaping safe to do.
|
|
12
|
+
*
|
|
13
|
+
* The checkpoint is a CACHE, never the only copy. The runner is a single replica
|
|
14
|
+
* with an in-memory registry, so a redeploy or crash drops every suspended
|
|
15
|
+
* session — the editor holds the authoritative workspace, already diffs its own
|
|
16
|
+
* files against `GET /workspace`, and on a `404` at resume creates a new session
|
|
17
|
+
* and re-seeds from its own copy in one change set. What the checkpoint saves is
|
|
18
|
+
* that upload.
|
|
19
|
+
*/
|
|
20
|
+
export class WatchSupervisor {
|
|
21
|
+
deps;
|
|
22
|
+
timer = null;
|
|
23
|
+
/** Sessions with a checkpoint or suspend in flight, so a slow snapshot cannot
|
|
24
|
+
* have a second one started on top of it every tick. */
|
|
25
|
+
busy = new Set();
|
|
26
|
+
constructor(deps) {
|
|
27
|
+
this.deps = deps;
|
|
28
|
+
}
|
|
29
|
+
start() {
|
|
30
|
+
if (this.timer)
|
|
31
|
+
return;
|
|
32
|
+
this.timer = setInterval(() => void this.tick(), TICK_MS);
|
|
33
|
+
this.timer.unref?.();
|
|
34
|
+
}
|
|
35
|
+
stop() {
|
|
36
|
+
if (!this.timer)
|
|
37
|
+
return;
|
|
38
|
+
clearInterval(this.timer);
|
|
39
|
+
this.timer = null;
|
|
40
|
+
}
|
|
41
|
+
/** Exposed for tests, which drive the clock rather than waiting on it. */
|
|
42
|
+
async tick() {
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
for (const entry of this.deps.registry.list()) {
|
|
45
|
+
if (entry.mode !== "watch")
|
|
46
|
+
continue;
|
|
47
|
+
if (entry.status.kind !== "running")
|
|
48
|
+
continue;
|
|
49
|
+
if (this.busy.has(entry.sessionId))
|
|
50
|
+
continue;
|
|
51
|
+
const idle = entry.subscribers === 0 &&
|
|
52
|
+
entry.idleSince !== null &&
|
|
53
|
+
now - entry.idleSince >= this.deps.idleMs;
|
|
54
|
+
if (idle) {
|
|
55
|
+
void this.run(entry, () => this.suspend(entry));
|
|
56
|
+
}
|
|
57
|
+
else if (entry.checkpointedAt === null ||
|
|
58
|
+
now - entry.checkpointedAt >= this.deps.checkpointMs) {
|
|
59
|
+
void this.run(entry, () => this.checkpoint(entry));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async run(entry, job) {
|
|
64
|
+
this.busy.add(entry.sessionId);
|
|
65
|
+
try {
|
|
66
|
+
await job();
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
// A failed checkpoint is not a failed session: the editor still holds the
|
|
70
|
+
// authoritative copy, and the next tick retries. Reported rather than
|
|
71
|
+
// swallowed so an operator sees a workspace that has stopped answering.
|
|
72
|
+
this.deps.log?.warn({ err, sessionId: entry.sessionId }, "watch session checkpoint/suspend failed");
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
this.busy.delete(entry.sessionId);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async checkpoint(entry) {
|
|
79
|
+
const workspace = entry.session?.workspace;
|
|
80
|
+
if (!workspace)
|
|
81
|
+
return;
|
|
82
|
+
const files = await workspace.snapshot();
|
|
83
|
+
entry.checkpoint = { takenAt: new Date(), files };
|
|
84
|
+
entry.checkpointedAt = Date.now();
|
|
85
|
+
}
|
|
86
|
+
async suspend(entry) {
|
|
87
|
+
const session = entry.session;
|
|
88
|
+
if (!session?.suspend)
|
|
89
|
+
return;
|
|
90
|
+
// Snapshot BEFORE the pod goes: the volume dies with it. A snapshot that
|
|
91
|
+
// fails aborts the suspend rather than taking the pod down with an older
|
|
92
|
+
// checkpoint — the session stays up and the next tick tries again.
|
|
93
|
+
await this.checkpoint(entry);
|
|
94
|
+
await session.suspend();
|
|
95
|
+
entry.session = null;
|
|
96
|
+
this.deps.registry.emit(entry.sessionId, { type: "status", status: { kind: "suspended" } });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=watch-supervisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch-supervisor.js","sourceRoot":"","sources":["../../src/session/watch-supervisor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-app.d.ts","sourceRoot":"","sources":["../../src/session/workspace-app.ts"],"names":[],"mappings":"AAIA,gEAAgE;AAChE,eAAO,MAAM,sBAAsB,cAAc,CAAC;AAclD,wBAAgB,oBAAoB,IAAI,MAAM,CAO7C"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
/** The name the manifest is mounted under, in every backend. */
|
|
5
|
+
export const WORKSPACE_APP_FILENAME = "telo.yaml";
|
|
6
|
+
/**
|
|
7
|
+
* The workspace application's manifest, read from the package rather than
|
|
8
|
+
* inlined as a string: it is a Telo manifest, so it must stay a `.yaml` file the
|
|
9
|
+
* repo's own `telo check` and formatter see. It lives in runner-core because the
|
|
10
|
+
* workspace surface is part of the `/v1` session contract — both backends mount
|
|
11
|
+
* the same bytes, and a copy per backend would be two manifests to hold in
|
|
12
|
+
* agreement.
|
|
13
|
+
*
|
|
14
|
+
* Read once and memoized: it never changes within a process.
|
|
15
|
+
*/
|
|
16
|
+
let cached;
|
|
17
|
+
export function workspaceAppManifest() {
|
|
18
|
+
if (cached !== undefined)
|
|
19
|
+
return cached;
|
|
20
|
+
// `dist/session/` at runtime, `src/session/` under a source run — three levels
|
|
21
|
+
// up is the package root either way.
|
|
22
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
cached = readFileSync(join(here, "..", "..", "workspace-app", WORKSPACE_APP_FILENAME), "utf8");
|
|
24
|
+
return cached;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=workspace-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-app.js","sourceRoot":"","sources":["../../src/session/workspace-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,gEAAgE;AAChE,MAAM,CAAC,MAAM,sBAAsB,GAAG,WAAW,CAAC;AAElD;;;;;;;;;GASG;AACH,IAAI,MAA0B,CAAC;AAE/B,MAAM,UAAU,oBAAoB;IAClC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACxC,+EAA+E;IAC/E,qCAAqC;IACrC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,sBAAsB,CAAC,EAAE,MAAM,CAAC,CAAC;IAC/F,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { WorkspaceAccess } from "../backend.js";
|
|
2
|
+
import type { WorkspaceChangeSet, WorkspaceCheckpointFile, WorkspaceTree } from "../contract.js";
|
|
3
|
+
/**
|
|
4
|
+
* HTTP client for the `workspace` container's surface, shared by both backends:
|
|
5
|
+
* the container is the same image running the same manifest whether it is a
|
|
6
|
+
* sibling container on a docker network or a container in a kubernetes pod, so
|
|
7
|
+
* only the base URL differs.
|
|
8
|
+
*
|
|
9
|
+
* The runner reaches it over its own network (a pod IP, a container address) and
|
|
10
|
+
* proxies it outward. It is never published.
|
|
11
|
+
*/
|
|
12
|
+
export declare class WorkspaceClient implements WorkspaceAccess {
|
|
13
|
+
private readonly baseUrl;
|
|
14
|
+
private readonly timeoutMs;
|
|
15
|
+
constructor(baseUrl: string, timeoutMs?: number);
|
|
16
|
+
tree(): Promise<WorkspaceTree>;
|
|
17
|
+
readFile(path: string): Promise<{
|
|
18
|
+
content: string;
|
|
19
|
+
size: number;
|
|
20
|
+
}>;
|
|
21
|
+
apply(changes: WorkspaceChangeSet): Promise<{
|
|
22
|
+
written: number;
|
|
23
|
+
deleted: number;
|
|
24
|
+
}>;
|
|
25
|
+
snapshot(): Promise<WorkspaceCheckpointFile[]>;
|
|
26
|
+
/** Re-run one app with no file change: rewrite its entry manifest with the
|
|
27
|
+
* bytes it already holds, through the same write path everything else takes.
|
|
28
|
+
* That is what makes `reload` need no signalling into the container, no
|
|
29
|
+
* shared PID namespace and no `exec` — RBAC is unchanged. */
|
|
30
|
+
touch(path: string): Promise<void>;
|
|
31
|
+
private request;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=workspace-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-client.d.ts","sourceRoot":"","sources":["../../src/session/workspace-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EACV,kBAAkB,EAClB,uBAAuB,EACvB,aAAa,EACd,MAAM,gBAAgB,CAAC;AAExB;;;;;;;;GAQG;AACH,qBAAa,eAAgB,YAAW,eAAe;IAEnD,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,OAAO,EAAE,MAAM,EACf,SAAS,SAAS;IAG/B,IAAI,IAAI,OAAO,CAAC,aAAa,CAAC;IAI9B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAOlE,KAAK,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAIjF,QAAQ,IAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAQpD;;;kEAG8D;IACxD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAQ1B,OAAO;CAsBtB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the `workspace` container's surface, shared by both backends:
|
|
3
|
+
* the container is the same image running the same manifest whether it is a
|
|
4
|
+
* sibling container on a docker network or a container in a kubernetes pod, so
|
|
5
|
+
* only the base URL differs.
|
|
6
|
+
*
|
|
7
|
+
* The runner reaches it over its own network (a pod IP, a container address) and
|
|
8
|
+
* proxies it outward. It is never published.
|
|
9
|
+
*/
|
|
10
|
+
export class WorkspaceClient {
|
|
11
|
+
baseUrl;
|
|
12
|
+
timeoutMs;
|
|
13
|
+
constructor(baseUrl, timeoutMs = 30_000) {
|
|
14
|
+
this.baseUrl = baseUrl;
|
|
15
|
+
this.timeoutMs = timeoutMs;
|
|
16
|
+
}
|
|
17
|
+
async tree() {
|
|
18
|
+
return this.request("GET", "/workspace");
|
|
19
|
+
}
|
|
20
|
+
async readFile(path) {
|
|
21
|
+
return this.request("GET", `/workspace/file?path=${encodeURIComponent(path)}`);
|
|
22
|
+
}
|
|
23
|
+
async apply(changes) {
|
|
24
|
+
return this.request("POST", "/workspace", changes);
|
|
25
|
+
}
|
|
26
|
+
async snapshot() {
|
|
27
|
+
const body = await this.request("GET", "/workspace/snapshot");
|
|
28
|
+
return body.files;
|
|
29
|
+
}
|
|
30
|
+
/** Re-run one app with no file change: rewrite its entry manifest with the
|
|
31
|
+
* bytes it already holds, through the same write path everything else takes.
|
|
32
|
+
* That is what makes `reload` need no signalling into the container, no
|
|
33
|
+
* shared PID namespace and no `exec` — RBAC is unchanged. */
|
|
34
|
+
async touch(path) {
|
|
35
|
+
const file = await this.request("GET", `/workspace/file?path=${encodeURIComponent(path)}&encoding=base64`);
|
|
36
|
+
await this.apply({ write: [{ path, content: file.content, encoding: "base64" }] });
|
|
37
|
+
}
|
|
38
|
+
async request(method, path, body) {
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
43
|
+
method,
|
|
44
|
+
signal: controller.signal,
|
|
45
|
+
...(body === undefined
|
|
46
|
+
? {}
|
|
47
|
+
: { headers: { "content-type": "application/json" }, body: JSON.stringify(body) }),
|
|
48
|
+
});
|
|
49
|
+
const text = await response.text();
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
// Surfaced, never swallowed: a workspace that has stopped answering is
|
|
52
|
+
// the one failure that makes every later edit silently do nothing.
|
|
53
|
+
throw new Error(`workspace ${method} ${path} failed: ${response.status} ${text.slice(0, 500)}`);
|
|
54
|
+
}
|
|
55
|
+
return JSON.parse(text);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=workspace-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-client.js","sourceRoot":"","sources":["../../src/session/workspace-client.ts"],"names":[],"mappings":"AAOA;;;;;;;;GAQG;AACH,MAAM,OAAO,eAAe;IAEP;IACA;IAFnB,YACmB,OAAe,EACf,YAAY,MAAM;QADlB,YAAO,GAAP,OAAO,CAAQ;QACf,cAAS,GAAT,SAAS,CAAS;IAClC,CAAC;IAEJ,KAAK,CAAC,IAAI;QACR,OAAO,IAAI,CAAC,OAAO,CAAgB,KAAK,EAAE,YAAY,CAAC,CAAC;IAC1D,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY;QACzB,OAAO,IAAI,CAAC,OAAO,CACjB,KAAK,EACL,wBAAwB,kBAAkB,CAAC,IAAI,CAAC,EAAE,CACnD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,OAA2B;QACrC,OAAO,IAAI,CAAC,OAAO,CAAuC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IAC3F,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAC7B,KAAK,EACL,qBAAqB,CACtB,CAAC;QACF,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED;;;kEAG8D;IAC9D,KAAK,CAAC,KAAK,CAAC,IAAY;QACtB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAC7B,KAAK,EACL,wBAAwB,kBAAkB,CAAC,IAAI,CAAC,kBAAkB,CACnE,CAAC;QACF,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IACrF,CAAC;IAEO,KAAK,CAAC,OAAO,CAAI,MAAc,EAAE,IAAY,EAAE,IAAc;QACnE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACnE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,EAAE;gBACrD,MAAM;gBACN,MAAM,EAAE,UAAU,CAAC,MAAM;gBACzB,GAAG,CAAC,IAAI,KAAK,SAAS;oBACpB,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;aACrF,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,uEAAuE;gBACvE,mEAAmE;gBACnE,MAAM,IAAI,KAAK,CAAC,aAAa,MAAM,IAAI,IAAI,YAAY,QAAQ,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YAClG,CAAC;YACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;QAC/B,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RunBundle, WorkspaceChangeSet } from "../contract.js";
|
|
2
|
+
/** The kernel anchors its `.telo` cache at the directory holding this file. */
|
|
3
|
+
export declare const WORKSPACE_MARKER_FILENAME = "telo-workspace.yaml";
|
|
4
|
+
/**
|
|
5
|
+
* The marker a session's workspace root carries.
|
|
6
|
+
*
|
|
7
|
+
* Its LOCATION is what matters here: the kernel walks up from an app's entry
|
|
8
|
+
* manifest looking for it and anchors the `.telo` cache at the directory that
|
|
9
|
+
* holds it. Without one, each app anchors on its OWN entry directory, so two
|
|
10
|
+
* apps in one workspace resolve the same module twice into two caches — and an
|
|
11
|
+
* app in a subdirectory gets a third.
|
|
12
|
+
*
|
|
13
|
+
* `modules:` is release scope and is read by `telo release` alone, which never
|
|
14
|
+
* runs in a session. It is written anyway because an empty list is a hard error
|
|
15
|
+
* in the parser, and a file this repo's own tooling would reject is not one to
|
|
16
|
+
* seed into a user's workspace.
|
|
17
|
+
*/
|
|
18
|
+
export declare const WORKSPACE_MARKER_CONTENTS = "# Marks the root of this session's workspace.\n#\n# The Telo kernel anchors its module cache (.telo) at the directory holding this\n# file, so every application in this workspace resolves its imports once into one\n# cache instead of once per app.\nmodules: [\"*\"]\n";
|
|
19
|
+
/**
|
|
20
|
+
* The marker, unless the workspace already brings its own. A user whose project
|
|
21
|
+
* really is a Telo workspace has a marker with a real \`modules:\` list, and
|
|
22
|
+
* overwriting it would silently change what \`telo release\` discovers.
|
|
23
|
+
*/
|
|
24
|
+
export declare function workspaceMarkerWrite(bundle: RunBundle): NonNullable<WorkspaceChangeSet["write"]>;
|
|
25
|
+
//# sourceMappingURL=workspace-marker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-marker.d.ts","sourceRoot":"","sources":["../../src/session/workspace-marker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpE,+EAA+E;AAC/E,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAE/D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,gRAMrC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,GAChB,WAAW,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAI1C"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** The kernel anchors its `.telo` cache at the directory holding this file. */
|
|
2
|
+
export const WORKSPACE_MARKER_FILENAME = "telo-workspace.yaml";
|
|
3
|
+
/**
|
|
4
|
+
* The marker a session's workspace root carries.
|
|
5
|
+
*
|
|
6
|
+
* Its LOCATION is what matters here: the kernel walks up from an app's entry
|
|
7
|
+
* manifest looking for it and anchors the `.telo` cache at the directory that
|
|
8
|
+
* holds it. Without one, each app anchors on its OWN entry directory, so two
|
|
9
|
+
* apps in one workspace resolve the same module twice into two caches — and an
|
|
10
|
+
* app in a subdirectory gets a third.
|
|
11
|
+
*
|
|
12
|
+
* `modules:` is release scope and is read by `telo release` alone, which never
|
|
13
|
+
* runs in a session. It is written anyway because an empty list is a hard error
|
|
14
|
+
* in the parser, and a file this repo's own tooling would reject is not one to
|
|
15
|
+
* seed into a user's workspace.
|
|
16
|
+
*/
|
|
17
|
+
export const WORKSPACE_MARKER_CONTENTS = `# Marks the root of this session's workspace.
|
|
18
|
+
#
|
|
19
|
+
# The Telo kernel anchors its module cache (.telo) at the directory holding this
|
|
20
|
+
# file, so every application in this workspace resolves its imports once into one
|
|
21
|
+
# cache instead of once per app.
|
|
22
|
+
modules: ["*"]
|
|
23
|
+
`;
|
|
24
|
+
/**
|
|
25
|
+
* The marker, unless the workspace already brings its own. A user whose project
|
|
26
|
+
* really is a Telo workspace has a marker with a real \`modules:\` list, and
|
|
27
|
+
* overwriting it would silently change what \`telo release\` discovers.
|
|
28
|
+
*/
|
|
29
|
+
export function workspaceMarkerWrite(bundle) {
|
|
30
|
+
const provided = bundle.files.some((f) => f.relativePath === WORKSPACE_MARKER_FILENAME);
|
|
31
|
+
if (provided)
|
|
32
|
+
return [];
|
|
33
|
+
return [{ path: WORKSPACE_MARKER_FILENAME, content: WORKSPACE_MARKER_CONTENTS }];
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=workspace-marker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-marker.js","sourceRoot":"","sources":["../../src/session/workspace-marker.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,yBAAyB,GAAG,qBAAqB,CAAC;AAE/D;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;CAMxC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAiB;IAEjB,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,yBAAyB,CAAC,CAAC;IACxF,IAAI,QAAQ;QAAE,OAAO,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;AACnF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/sse/channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG5D,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAKnF,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,cAAc,CAAC;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;CAC7B;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/sse/channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG5D,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAKnF,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,cAAc,CAAC;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;CAC7B;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA4F5E"}
|
package/dist/sse/channel.js
CHANGED
|
@@ -44,10 +44,14 @@ export async function streamSessionEvents(args) {
|
|
|
44
44
|
let unsubscribe = null;
|
|
45
45
|
let heartbeat = null;
|
|
46
46
|
let closed = false;
|
|
47
|
+
// An attached event stream is a live client. Idleness — no subscriber on
|
|
48
|
+
// either channel for the configured window — is what suspends a watch session.
|
|
49
|
+
const releaseSubscriber = registry.addSubscriber(sessionId);
|
|
47
50
|
const cleanup = () => {
|
|
48
51
|
if (closed)
|
|
49
52
|
return;
|
|
50
53
|
closed = true;
|
|
54
|
+
releaseSubscriber();
|
|
51
55
|
if (heartbeat)
|
|
52
56
|
clearInterval(heartbeat);
|
|
53
57
|
if (unsubscribe)
|
package/dist/sse/channel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/sse/channel.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/sse/channel.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAwB,MAAM,wBAAwB,CAAC;AAGnF,MAAM,YAAY,GAAG,MAAM,CAAC;AAU5B,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAmB;IAC3D,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IAC9D,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,SAAS,mBAAmB,EAAE,CAAC,CAAC;QAChG,OAAO;IACT,CAAC;IAED,MAAM,WAAW,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;IAEtB,2EAA2E;IAC3E,2EAA2E;IAC3E,+DAA+D;IAC/D,MAAM,OAAO,GAA2B;QACtC,cAAc,EAAE,mBAAmB;QACnC,eAAe,EAAE,wBAAwB;QACzC,UAAU,EAAE,YAAY;QACxB,mBAAmB,EAAE,IAAI;KAC1B,CAAC;IACF,MAAM,WAAW,GAAG,kBAAkB,CAAC,WAAW,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxE,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,CAAC,6BAA6B,CAAC,GAAG,WAAW,CAAC;QACrD,OAAO,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC;IAC7B,CAAC;IAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAE5B,2EAA2E;IAC3E,4EAA4E;IAC5E,cAAc;IACd,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC7D,IAAI,MAAM,EAAE,CAAC;QACX,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,OAAO,EAAE,CAAC;QAC/B,kBAAkB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,yEAAyE;IACzE,0EAA0E;IAC1E,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,GAAG,EAAE,CAAC;QACV,OAAO;IACT,CAAC;IAED,IAAI,WAAW,GAAwB,IAAI,CAAC;IAC5C,IAAI,SAAS,GAA0B,IAAI,CAAC;IAC5C,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,yEAAyE;IACzE,+EAA+E;IAC/E,MAAM,iBAAiB,GAAG,QAAQ,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IAE5D,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,MAAM;YAAE,OAAO;QACnB,MAAM,GAAG,IAAI,CAAC;QACd,iBAAiB,EAAE,CAAC;QACpB,IAAI,SAAS;YAAE,aAAa,CAAC,SAAS,CAAC,CAAC;QACxC,IAAI,WAAW;YAAE,WAAW,EAAE,CAAC;QAC/B,IAAI,CAAC,GAAG,CAAC,aAAa;YAAE,GAAG,CAAC,GAAG,EAAE,CAAC;IACpC,CAAC,CAAC;IAEF,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;QAC3B,IAAI,CAAC,GAAG,CAAC,aAAa;YAAE,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACvD,CAAC,EAAE,YAAY,CAAC,CAAC;IACjB,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;IAEpB,IAAI,CAAC;QACH,WAAW,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE;YACvD,kBAAkB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YAClC,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1E,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,mBAAmB,EAAE,CAAC;YACvC,OAAO,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7B,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAE3B,wEAAwE;IACxE,mDAAmD;IACnD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAClC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACzB,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAmB;IAC7C,sEAAsE;IACtE,4EAA4E;IAC5E,oDAAoD;IACpD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACvE,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,UAAU,CAAC;IAE3C,MAAM,KAAK,GAAG,GAAG,CAAC,KAA6C,CAAC;IAChE,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IAC9C,OAAO,SAAS,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,kBAAkB,CACzB,WAA2B,EAC3B,aAA4C;IAE5C,IAAI,WAAW,KAAK,GAAG;QAAE,OAAO,GAAG,CAAC;IACpC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;IAC/E,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,OAAO,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACtD,CAAC;AAED,SAAS,OAAO,CAAC,GAAuB;IACtC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACnC,2EAA2E;IAC3E,4EAA4E;IAC5E,qEAAqE;IACrE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED,SAAS,kBAAkB,CAAC,GAA0B,EAAE,QAAuB;IAC7E,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,UAAU,CACjB,GAA0B,EAC1B,KAAa,EACb,IAAa,EACb,EAAW;IAEX,IAAI,CAAC,CAAC,UAAU,IAAI,GAAG,CAAC,IAAK,GAAmC,CAAC,aAAa;QAAE,OAAO;IACvF,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,EAAE,KAAK,SAAS;QAAE,KAAK,IAAI,OAAO,EAAE,IAAI,CAAC;IAC7C,KAAK,IAAI,UAAU,KAAK,IAAI,CAAC;IAC7B,KAAK,IAAI,SAAS,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;IAC7C,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/runner-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Backend-neutral core for Telo runners: the /v1 session contract, the RunnerBackend interface, the session registry, SSE/byte ring buffers, and bundle handling. Shared by docker-runner and k8s-runner.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telo",
|
|
@@ -24,14 +24,15 @@
|
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"dist/**",
|
|
27
|
-
"src/**"
|
|
27
|
+
"src/**",
|
|
28
|
+
"workspace-app/**"
|
|
28
29
|
],
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@fastify/cors": "^11.0.1",
|
|
31
32
|
"@fastify/websocket": "^11.0.1",
|
|
32
33
|
"fastify": "^5.12.1",
|
|
33
34
|
"yaml": "^2.8.3",
|
|
34
|
-
"@telorun/debug-wire": "0.4.
|
|
35
|
+
"@telorun/debug-wire": "0.4.1"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@types/node": "^20.0.0",
|
package/src/backend.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import type { DebugFrame } from "@telorun/debug-wire";
|
|
2
2
|
|
|
3
|
+
import type { ResolvedRunnerApp } from "./config.js";
|
|
3
4
|
import type {
|
|
4
5
|
AvailabilityReport,
|
|
6
|
+
ByteStreamTag,
|
|
7
|
+
IoMode,
|
|
5
8
|
PortMapping,
|
|
6
9
|
ProbeConfig,
|
|
7
10
|
ReachabilityState,
|
|
8
11
|
RunBundle,
|
|
12
|
+
RunnerEndpoint,
|
|
9
13
|
RunPhase,
|
|
10
14
|
RunStatus,
|
|
11
15
|
SessionConfig,
|
|
16
|
+
SessionMode,
|
|
17
|
+
WorkspaceChangeSet,
|
|
18
|
+
WorkspaceCheckpointFile,
|
|
19
|
+
WorkspaceTree,
|
|
12
20
|
} from "./contract.js";
|
|
13
21
|
|
|
14
22
|
/**
|
|
@@ -40,59 +48,140 @@ export interface RunnerBackend {
|
|
|
40
48
|
reapOrphans?(): Promise<void>;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
/** Bundle-relative entry path, already traversal-normalized by core.
|
|
51
|
+
/** One application container the backend must stand up. */
|
|
52
|
+
export interface BackendAppSpec {
|
|
53
|
+
name: string;
|
|
54
|
+
/** Bundle-relative entry path, already traversal-normalized by core. Empty for
|
|
55
|
+
* a self-contained app session, where the image's own entrypoint runs. */
|
|
47
56
|
entryRelativePath: string;
|
|
48
|
-
env: Record<string, string>;
|
|
49
57
|
ports: PortMapping[];
|
|
58
|
+
io: IoMode;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The DATA half of a workload launch — everything a start needs that is not a
|
|
63
|
+
* callback. Retained on the session entry so `resume` can build a fresh pod
|
|
64
|
+
* under the same session id from the same description, rather than a second,
|
|
65
|
+
* drifting copy of it.
|
|
66
|
+
*/
|
|
67
|
+
export interface WorkloadLaunch {
|
|
68
|
+
bundle: RunBundle;
|
|
69
|
+
env: Record<string, string>;
|
|
50
70
|
config: SessionConfig;
|
|
51
71
|
/** True for an operator-predefined app session (`POST /v1/apps/:name/sessions`):
|
|
52
72
|
* `config.image` is self-contained (app + controllers baked in), so the
|
|
53
73
|
* backend runs the image's own entrypoint and stages no bundle — `bundle`
|
|
54
74
|
* is an empty placeholder. */
|
|
55
75
|
selfContained: boolean;
|
|
56
|
-
/** When true, launch
|
|
76
|
+
/** When true, launch each app with `--inspect` and relay its kernel debug
|
|
57
77
|
* stream via `onDebug`. The inspect endpoint stays reachable only by the
|
|
58
|
-
* runner — never published outward.
|
|
78
|
+
* runner — never published outward. Always true for a watch session: that
|
|
79
|
+
* stream is where `run` events are projected from. */
|
|
59
80
|
inspect: boolean;
|
|
81
|
+
/** `run` (one pod, terminal on exit, today's shape) or `watch` (a workspace
|
|
82
|
+
* volume, `telo run --watch` per app, the pod outliving its runs). */
|
|
83
|
+
mode: SessionMode;
|
|
84
|
+
/** The applications to run, one container each. Never empty — core defaults a
|
|
85
|
+
* request with no `apps` to a single entry. */
|
|
86
|
+
apps: BackendAppSpec[];
|
|
87
|
+
/** The resolved catalog entry for a co-resident agent container, when one was
|
|
88
|
+
* requested. Its operator env goes on THAT container and nowhere else — the
|
|
89
|
+
* credential boundary used to be structural (two pods) and is now a code
|
|
90
|
+
* invariant (containers in one pod). */
|
|
91
|
+
agent?: ResolvedRunnerApp;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface BackendStartSpec extends WorkloadLaunch {
|
|
95
|
+
sessionId: string;
|
|
60
96
|
|
|
61
97
|
/** Emit a lifecycle status. The backend drives `starting` → `running` →
|
|
62
|
-
* terminal (`exited`/`failed`/`stopped`). */
|
|
98
|
+
* terminal (`exited`/`failed`/`stopped`), or `suspended` on an idle reap. */
|
|
63
99
|
onStatus(status: RunStatus): void;
|
|
64
100
|
/** Emit a progress message for a coming-up phase (build / provision / boot).
|
|
65
101
|
* Additive to status — surfaces what's happening while the session is still
|
|
66
|
-
* `starting
|
|
67
|
-
onProgress(phase: RunPhase, message: string, done?: boolean): void;
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
102
|
+
* `starting`. `app` is omitted for session-scoped provisioning. */
|
|
103
|
+
onProgress(phase: RunPhase, message: string, done?: boolean, app?: string): void;
|
|
104
|
+
/** Bytes from one app's terminal. `stream` is `tty` under a terminal attach
|
|
105
|
+
* and `stdout`/`stderr` only where the transport genuinely separated them. */
|
|
106
|
+
onOutput(app: string, chunk: Buffer, stream: ByteStreamTag): void;
|
|
107
|
+
/** A frame relayed from one app's kernel debug stream. Only called when
|
|
108
|
+
* `inspect` is true and the backend has connected to that app's endpoint. */
|
|
109
|
+
onDebug(app: string, frame: DebugFrame): void;
|
|
73
110
|
/** Report a declared port's reachability from the runner network — `checking`
|
|
74
111
|
* while the workload comes up, then `reachable`, or `unreachable` after a
|
|
75
112
|
* timeout. Surfaced on the editor's endpoint badge, not the log stream. */
|
|
76
|
-
onReachability(port: number, state: ReachabilityState): void;
|
|
113
|
+
onReachability(app: string, port: number, state: ReachabilityState): void;
|
|
114
|
+
/** One app's workload ended on its own. A run session's workload ending IS the
|
|
115
|
+
* session ending, so this is a watch-session concern: under `--watch` a
|
|
116
|
+
* finished run leaves the container up, and a container that goes away has
|
|
117
|
+
* died. Reported through the contract rather than by synthesizing a kernel
|
|
118
|
+
* frame — that stream's contract is "frames relayed from the workload", and a
|
|
119
|
+
* backend writing into it puts an event on the wire no kernel emitted. */
|
|
120
|
+
onRunEnded(app: string, outcome: { code?: number; reason?: string }): void;
|
|
121
|
+
/** An app's routable endpoint set changed after a reload re-read its declared
|
|
122
|
+
* ports. */
|
|
123
|
+
onEndpoints(
|
|
124
|
+
app: string,
|
|
125
|
+
change: {
|
|
126
|
+
added?: RunnerEndpoint[];
|
|
127
|
+
removed?: RunnerEndpoint[];
|
|
128
|
+
rejected?: Array<{ port: number; reason: string }>;
|
|
129
|
+
},
|
|
130
|
+
): void;
|
|
77
131
|
/** True once a user stop / shutdown has been requested — lets the backend
|
|
78
132
|
* classify a kill as `stopped` rather than `failed`. */
|
|
79
133
|
isUserStopped(): boolean;
|
|
80
134
|
}
|
|
81
135
|
|
|
82
136
|
export interface BackendSession {
|
|
83
|
-
/** Write bytes to
|
|
84
|
-
*
|
|
85
|
-
writeStdin(bytes: Uint8Array): void;
|
|
137
|
+
/** Write bytes to one app's stdin. A no-op once that workload has terminated,
|
|
138
|
+
* so callers need not track liveness. */
|
|
139
|
+
writeStdin(app: string, bytes: Uint8Array): void;
|
|
86
140
|
|
|
87
|
-
/** Resize
|
|
88
|
-
resize
|
|
141
|
+
/** Resize one app's PTY. A no-op for an app running under `io: "streams"` —
|
|
142
|
+
* the route rejects such a resize before it reaches here. */
|
|
143
|
+
resize(app: string, cols: number, rows: number): void;
|
|
89
144
|
|
|
90
145
|
/** Resolves after the workload terminates and its terminal status has been
|
|
91
146
|
* emitted via `onStatus`. Never rejects — terminal failures surface as a
|
|
92
|
-
* `failed` status.
|
|
147
|
+
* `failed` status. A watch session's workload outlives its runs, so this
|
|
148
|
+
* settles only on stop, suspend, or the pod deadline. */
|
|
93
149
|
readonly done: Promise<void>;
|
|
94
150
|
|
|
95
151
|
/** Force-stop the workload. Idempotent and safe to call after natural exit
|
|
96
152
|
* (a backend should treat an already-gone workload as a no-op). */
|
|
97
153
|
stop(): Promise<void>;
|
|
154
|
+
|
|
155
|
+
/** The workspace surface, present only on a watch session. Everything outside
|
|
156
|
+
* the pod writes through here; the agent (inside it) writes the volume with
|
|
157
|
+
* its own filesystem tools. */
|
|
158
|
+
readonly workspace?: WorkspaceAccess;
|
|
159
|
+
|
|
160
|
+
/** Re-run one app with no file change (`POST /v1/sessions/:id/reload`), by
|
|
161
|
+
* touching its entry manifest through the same path every other write takes.
|
|
162
|
+
* No signalling into the container, no shared PID namespace, no `exec` — RBAC
|
|
163
|
+
* is unchanged. Watch sessions only. */
|
|
164
|
+
reload?(app: string): Promise<void>;
|
|
165
|
+
|
|
166
|
+
/** Replace the running app set. A pod's container list is fixed at creation,
|
|
167
|
+
* so this checkpoints, deletes the pod and creates one with the new set —
|
|
168
|
+
* the suspend/resume path, reused because it has to be. */
|
|
169
|
+
setApps?(apps: BackendAppSpec[]): Promise<void>;
|
|
170
|
+
|
|
171
|
+
/** Delete the workload, keeping nothing but what the caller already
|
|
172
|
+
* checkpointed. `resume` builds a fresh one from that checkpoint. */
|
|
173
|
+
suspend?(): Promise<void>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The `workspace` container's routes, proxied by the runner. Reading and
|
|
177
|
+
* applying a change set are the only two shapes: a single-file write route
|
|
178
|
+
* would be a second set of concurrency rules over the same directory. */
|
|
179
|
+
export interface WorkspaceAccess {
|
|
180
|
+
tree(): Promise<WorkspaceTree>;
|
|
181
|
+
readFile(path: string): Promise<{ content: string; size: number }>;
|
|
182
|
+
apply(changes: WorkspaceChangeSet): Promise<{ written: number; deleted: number }>;
|
|
183
|
+
/** Whole-tree pull for the checkpoint timer and for suspend. Whole tree rather
|
|
184
|
+
* than a delta log: a manifest workspace is small, and one shape is easier to
|
|
185
|
+
* reason about than a replay that has to be correct. */
|
|
186
|
+
snapshot(): Promise<WorkspaceCheckpointFile[]>;
|
|
98
187
|
}
|