@telorun/runner-core 0.8.2 → 0.10.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 +56 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +36 -0
- package/dist/config.js.map +1 -1
- package/dist/contract.d.ts +210 -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 +18 -13
- package/dist/routes/session-start.d.ts.map +1 -1
- package/dist/routes/session-start.js +95 -14
- 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 +465 -5
- 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/app-catalog.test.ts +49 -1
- package/src/backend.ts +111 -22
- package/src/config.ts +98 -0
- package/src/contract.ts +213 -16
- 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 +115 -27
- package/src/routes/sessions.ts +538 -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":"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,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,MAAM;YAAE,OAAO;QACnB,MAAM,GAAG,IAAI,CAAC;QACd,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"}
|
|
1
|
+
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/sse/channel.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telorun/runner-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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/app-catalog.test.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
coResidentAgentNames,
|
|
5
|
+
loadAppsFromEnv,
|
|
6
|
+
loadResolvedApps,
|
|
7
|
+
RunnerConfigError,
|
|
8
|
+
} from "./config.js";
|
|
4
9
|
|
|
5
10
|
describe("loadAppsFromEnv", () => {
|
|
6
11
|
it("returns undefined when RUNNER_APPS is unset (no apps offered)", () => {
|
|
@@ -31,6 +36,18 @@ describe("loadAppsFromEnv", () => {
|
|
|
31
36
|
loadAppsFromEnv({ RUNNER_APPS: '{"x":{"image":"i","pullPolicy":"sometimes"}}' }),
|
|
32
37
|
).toThrow(/pullPolicy/);
|
|
33
38
|
});
|
|
39
|
+
|
|
40
|
+
it("accepts a port and rejects one that is not a usable tcp port", () => {
|
|
41
|
+
const catalog = loadAppsFromEnv({
|
|
42
|
+
RUNNER_APPS: '{"agent":{"image":"i","port":8080}}',
|
|
43
|
+
})!;
|
|
44
|
+
expect(catalog.agent.port).toBe(8080);
|
|
45
|
+
for (const bad of ["0", "65536", '"8080"', "8080.5"]) {
|
|
46
|
+
expect(() =>
|
|
47
|
+
loadAppsFromEnv({ RUNNER_APPS: `{"agent":{"image":"i","port":${bad}}}` }),
|
|
48
|
+
).toThrow(/invalid 'port'/);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
34
51
|
});
|
|
35
52
|
|
|
36
53
|
describe("loadResolvedApps", () => {
|
|
@@ -47,8 +64,39 @@ describe("loadResolvedApps", () => {
|
|
|
47
64
|
image: "acme/tool:1",
|
|
48
65
|
env: {},
|
|
49
66
|
pullPolicy: "missing",
|
|
67
|
+
port: undefined,
|
|
50
68
|
title: undefined,
|
|
51
69
|
description: undefined,
|
|
52
70
|
});
|
|
53
71
|
});
|
|
72
|
+
|
|
73
|
+
// No default: the catalog is operator configuration and the runner knows
|
|
74
|
+
// nothing about any specific app, so a port it did not declare is absent
|
|
75
|
+
// rather than guessed — which is what makes `agent_port_undeclared` possible.
|
|
76
|
+
it("leaves an undeclared port undefined and carries a declared one", () => {
|
|
77
|
+
const resolved = loadResolvedApps({
|
|
78
|
+
RUNNER_APPS: '{"plain":{"image":"a"},"agent":{"image":"b","port":8080}}',
|
|
79
|
+
});
|
|
80
|
+
expect(resolved.plain.port).toBeUndefined();
|
|
81
|
+
expect(resolved.agent.port).toBe(8080);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("coResidentAgentNames", () => {
|
|
86
|
+
it("names only the entries a session would accept as an agent", () => {
|
|
87
|
+
// The advertised set IS the acceptance condition. Deriving it here rather
|
|
88
|
+
// than in each runner is what stops the two drifting the next time the
|
|
89
|
+
// condition grows — which is exactly how a port-less catalog came to be
|
|
90
|
+
// advertised and then refused on every run.
|
|
91
|
+
const apps = loadResolvedApps({
|
|
92
|
+
RUNNER_APPS: '{"agent":{"image":"a","port":8080},"tool":{"image":"b"}}',
|
|
93
|
+
});
|
|
94
|
+
expect(coResidentAgentNames(apps)).toEqual(["agent"]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("is empty for a catalog predating the port field", () => {
|
|
98
|
+
expect(coResidentAgentNames(loadResolvedApps({ RUNNER_APPS: '{"tool":{"image":"b"}}' }))).toEqual(
|
|
99
|
+
[],
|
|
100
|
+
);
|
|
101
|
+
});
|
|
54
102
|
});
|