@relay-harness/coding-agent 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +27 -6
  2. package/LICENSE +22 -0
  3. package/README.md +14 -16
  4. package/dist/bundle/chunks/{chunk-H6S6DQTM.js → chunk-6AEKWHDP.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-3QND5D7W.js → chunk-7ZBTLURV.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-WMTXXI7H.js → chunk-FUPU6X73.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-XDAWU2J5.js → chunk-IDUCLYG3.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-XSLAMYCK.js → chunk-LVFZRSZQ.js} +556 -62
  9. package/dist/bundle/chunks/{chunk-ISNO4JTR.js → chunk-PJWDW5WR.js} +2 -2
  10. package/dist/bundle/chunks/{chunk-3XNI2SB6.js → chunk-TZ2ZVU7E.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-MP5FU7MG.js → chunk-UVFWV6YH.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-PTRF7IZB.js → chunk-WU7MEKPF.js} +1 -1
  13. package/dist/bundle/chunks/{cli-K4CNHWOI.js → cli-2LN4J4IN.js} +1 -1
  14. package/dist/bundle/chunks/{easter-egg-3d-TGRCIHA4.js → easter-egg-3d-URCOHV3B.js} +1 -1
  15. package/dist/bundle/chunks/{execute-6E5DA2QI.js → execute-JAHDBPR2.js} +1 -1
  16. package/dist/bundle/chunks/{runtime-O5HLCH3V.js → runtime-TTSK6Y5T.js} +1 -1
  17. package/dist/bundle/chunks/{virtual-modules-FNVEVB65.js → virtual-modules-2G2P7CK2.js} +1 -1
  18. package/dist/bundle/cli-runtime.js +1 -1
  19. package/dist/bundle/index.js +1 -1
  20. package/dist/bundle/rpc-entry.js +1 -1
  21. package/dist/core/settings-manager.d.ts +5 -3
  22. package/dist/core/settings-manager.d.ts.map +1 -1
  23. package/dist/core/settings-manager.js.map +1 -1
  24. package/dist/extensions/laya/assessment.d.ts +7 -1
  25. package/dist/extensions/laya/assessment.d.ts.map +1 -1
  26. package/dist/extensions/laya/assessment.js +21 -1
  27. package/dist/extensions/laya/assessment.js.map +1 -1
  28. package/dist/extensions/laya/docker.d.ts +19 -0
  29. package/dist/extensions/laya/docker.d.ts.map +1 -0
  30. package/dist/extensions/laya/docker.js +44 -0
  31. package/dist/extensions/laya/docker.js.map +1 -0
  32. package/dist/extensions/laya/index.d.ts +12 -3
  33. package/dist/extensions/laya/index.d.ts.map +1 -1
  34. package/dist/extensions/laya/index.js +331 -67
  35. package/dist/extensions/laya/index.js.map +1 -1
  36. package/dist/extensions/laya/learn.d.ts +67 -0
  37. package/dist/extensions/laya/learn.d.ts.map +1 -0
  38. package/dist/extensions/laya/learn.js +263 -0
  39. package/dist/extensions/laya/learn.js.map +1 -0
  40. package/dist/extensions/laya/memory.d.ts +48 -0
  41. package/dist/extensions/laya/memory.d.ts.map +1 -0
  42. package/dist/extensions/laya/memory.js +107 -0
  43. package/dist/extensions/laya/memory.js.map +1 -0
  44. package/dist/extensions/laya/model-manifest.d.ts +1 -1
  45. package/dist/extensions/laya/model-manifest.js +1 -1
  46. package/dist/extensions/laya/model-manifest.js.map +1 -1
  47. package/dist/extensions/laya/routers.d.ts +8 -0
  48. package/dist/extensions/laya/routers.d.ts.map +1 -1
  49. package/dist/extensions/laya/routers.js +2 -0
  50. package/dist/extensions/laya/routers.js.map +1 -1
  51. package/dist/extensions/laya/runtime.d.ts +39 -49
  52. package/dist/extensions/laya/runtime.d.ts.map +1 -1
  53. package/dist/extensions/laya/runtime.js +93 -145
  54. package/dist/extensions/laya/runtime.js.map +1 -1
  55. package/dist/extensions/laya/server.d.ts +39 -15
  56. package/dist/extensions/laya/server.d.ts.map +1 -1
  57. package/dist/extensions/laya/server.js +219 -78
  58. package/dist/extensions/laya/server.js.map +1 -1
  59. package/dist/extensions/laya/train-script.d.ts +8 -0
  60. package/dist/extensions/laya/train-script.d.ts.map +1 -0
  61. package/dist/extensions/laya/train-script.js +422 -0
  62. package/dist/extensions/laya/train-script.js.map +1 -0
  63. package/dist/extensions/laya/training.d.ts +176 -0
  64. package/dist/extensions/laya/training.d.ts.map +1 -0
  65. package/dist/extensions/laya/training.js +337 -0
  66. package/dist/extensions/laya/training.js.map +1 -0
  67. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  68. package/dist/modes/interactive/components/config-selector.js +3 -2
  69. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  70. package/dist/modes/interactive/components/custom-message.js +1 -1
  71. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  72. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  73. package/dist/modes/interactive/interactive-mode.js +4 -4
  74. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  75. package/dist/modes/interactive/theme/dark.json +9 -9
  76. package/dist/modes/interactive/theme/light.json +9 -9
  77. package/dist/modes/interactive/theme/system-theme.d.ts.map +1 -1
  78. package/dist/modes/interactive/theme/system-theme.js +11 -10
  79. package/dist/modes/interactive/theme/system-theme.js.map +1 -1
  80. package/dist/utils/version-check.d.ts.map +1 -1
  81. package/dist/utils/version-check.js +10 -6
  82. package/dist/utils/version-check.js.map +1 -1
  83. package/docs/laya.md +59 -19
  84. package/docs/quickstart.md +19 -6
  85. package/docs/settings.md +4 -3
  86. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  87. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  88. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  89. package/examples/extensions/sandbox/package-lock.json +2 -2
  90. package/examples/extensions/sandbox/package.json +1 -1
  91. package/examples/extensions/with-deps/package-lock.json +2 -2
  92. package/examples/extensions/with-deps/package.json +1 -1
  93. package/package.json +15 -11
@@ -0,0 +1,337 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { dockerError } from "./docker.js";
4
+ import { layaDecisionsFile } from "./questions.js";
5
+ import { LAYA_IMAGE_PATHS, LAYA_MODELS_VOLUME, LAYA_TRAIN_CONTAINER, trainedModelPath, } from "./runtime.js";
6
+ import { generateSeed } from "./seed.js";
7
+ /** Exercises labeled from sessions. Training focuses on them; the rest is replayed. */
8
+ export const SESSION_SOURCE = "session";
9
+ /** The seed size of the shipped model: the same rows, so the same held-out test split. */
10
+ export const SEED_ROWS = 1100;
11
+ /** A new model is activated unless it answers more than this fraction fewer test questions. */
12
+ export const REGRESSION_TOLERANCE = 0.01;
13
+ /** Trained models kept besides the active one, newest first. Older ones are deleted. */
14
+ const KEEP_INACTIVE_MODELS = 2;
15
+ export function trainingWorkspace(home) {
16
+ const dir = join(home, "training");
17
+ return {
18
+ dir,
19
+ decisions: join(dir, "decisions.json"),
20
+ dataset: join(dir, "data", "dataset.jsonl"),
21
+ registry: join(dir, "models.json"),
22
+ };
23
+ }
24
+ /** Writes the current questions and, on first use, the seed exercises. */
25
+ export function ensureWorkspace(workspace) {
26
+ mkdirSync(dirname(workspace.dataset), { recursive: true });
27
+ writeFileSync(workspace.decisions, `${JSON.stringify(layaDecisionsFile(), null, 2)}\n`, "utf8");
28
+ if (!existsSync(workspace.dataset)) {
29
+ const rows = generateSeed(SEED_ROWS).map((row, i) => ({ id: `r${String(i + 1).padStart(5, "0")}`, ...row }));
30
+ writeRows(workspace.dataset, rows);
31
+ }
32
+ }
33
+ export function readRows(path) {
34
+ if (!existsSync(path))
35
+ return [];
36
+ const rows = [];
37
+ for (const line of readFileSync(path, "utf8").split("\n")) {
38
+ if (!line.trim())
39
+ continue;
40
+ try {
41
+ rows.push(JSON.parse(line));
42
+ }
43
+ catch {
44
+ // A line broken by hand; the training script skips it too.
45
+ }
46
+ }
47
+ return rows;
48
+ }
49
+ export function writeRows(path, rows) {
50
+ mkdirSync(dirname(path), { recursive: true });
51
+ writeFileSync(path, rows.map((row) => `${JSON.stringify(row)}\n`).join(""), "utf8");
52
+ }
53
+ /** Requests that differ only in case and spacing are the same exercise. */
54
+ export function requestKey(request) {
55
+ return request.trim().toLowerCase().replace(/\s+/g, " ");
56
+ }
57
+ /**
58
+ * Adds rows, replacing the labels of an exercise with the same request: a later session labels it
59
+ * with newer evidence.
60
+ */
61
+ export function mergeRows(existing, incoming) {
62
+ const rows = [...existing];
63
+ const byKey = new Map(rows.map((row, index) => [requestKey(row.state.request), index]));
64
+ let next = Math.max(0, ...rows.map((row) => Number(/^s(\d+)$/.exec(row.id)?.[1] ?? 0))) + 1;
65
+ let added = 0;
66
+ let updated = 0;
67
+ for (const row of incoming) {
68
+ const key = requestKey(row.state.request);
69
+ const index = byKey.get(key);
70
+ if (index === undefined) {
71
+ byKey.set(key, rows.length);
72
+ rows.push({ id: `s${String(next++).padStart(5, "0")}`, ...row });
73
+ added++;
74
+ }
75
+ else {
76
+ rows[index] = { id: rows[index].id, ...row };
77
+ updated++;
78
+ }
79
+ }
80
+ return { rows, added, updated };
81
+ }
82
+ export function readRegistry(workspace) {
83
+ try {
84
+ const registry = JSON.parse(readFileSync(workspace.registry, "utf8"));
85
+ return { active: registry.active, models: Array.isArray(registry.models) ? registry.models : [] };
86
+ }
87
+ catch {
88
+ return { models: [] };
89
+ }
90
+ }
91
+ export function writeRegistry(workspace, registry) {
92
+ mkdirSync(workspace.dir, { recursive: true });
93
+ writeFileSync(workspace.registry, `${JSON.stringify(registry, null, 2)}\n`, "utf8");
94
+ }
95
+ export const accuracy = (score) => (score.n > 0 ? score.correct / score.n : 0);
96
+ /** Whether the new model answers the test split well enough to replace the current one. */
97
+ export function passesGate(test) {
98
+ return accuracy(test.candidate) >= accuracy(test.current) - REGRESSION_TOLERANCE;
99
+ }
100
+ /** Keeps the newest inactive models and the active one; returns the names to delete. */
101
+ export function pruneModels(registry) {
102
+ // The registry lists models in the order they were trained.
103
+ const inactive = registry.models.filter((model) => model.name !== registry.active).reverse();
104
+ const removed = inactive.slice(KEEP_INACTIVE_MODELS).map((model) => model.name);
105
+ return {
106
+ registry: { ...registry, models: registry.models.filter((model) => !removed.includes(model.name)) },
107
+ removed,
108
+ };
109
+ }
110
+ export function describeProgress(progress) {
111
+ if (progress.phase === "evaluate") {
112
+ return progress.model === "current" ? "comparing with the current model" : "testing the new model";
113
+ }
114
+ if (!progress.epoch)
115
+ return `starting on ${progress.device ?? "the device"}`;
116
+ const minutes = progress.eta_s === undefined ? undefined : Math.max(1, Math.round(progress.eta_s / 60));
117
+ return `round ${progress.epoch}/${progress.epochs} ${Math.round((progress.done ?? 0) * 100)}%${minutes && progress.done !== 1 ? `, about ${minutes} min left` : ""}`;
118
+ }
119
+ /** Runs a Python one-liner in the image, with the models volume mounted. */
120
+ function inImage(image, code, extra = [], gpu = false) {
121
+ return [
122
+ "run",
123
+ "--rm",
124
+ ...(gpu ? ["--gpus", "all"] : []),
125
+ "--volume",
126
+ `${LAYA_MODELS_VOLUME}:${LAYA_IMAGE_PATHS.trainedModels}`,
127
+ image,
128
+ "python",
129
+ "-c",
130
+ code,
131
+ ...extra,
132
+ ];
133
+ }
134
+ /** Whether a container of the image sees an NVIDIA GPU: needs the CUDA image and Docker GPU support. */
135
+ export async function dockerHasGpu(docker, image) {
136
+ const result = await docker(inImage(image, "import sys, torch; sys.exit(0 if torch.cuda.is_available() else 1)", [], true), { timeout: 120_000 });
137
+ return result.code === 0;
138
+ }
139
+ /**
140
+ * Runs one training at a time. It belongs to the process, not to a session runtime, so training
141
+ * goes on across `/new` and `/resume`; `listener` is the latest runtime that wants its events.
142
+ */
143
+ export class LayaTrainer {
144
+ workspace;
145
+ options;
146
+ abort;
147
+ job;
148
+ /** Latest progress of the running training. */
149
+ progress;
150
+ listener;
151
+ constructor(options) {
152
+ this.options = options;
153
+ this.workspace = trainingWorkspace(options.home);
154
+ }
155
+ get running() {
156
+ return this.job !== undefined;
157
+ }
158
+ /** The model that routes requests, with its path inside the containers: the active trained model, or the shipped one. */
159
+ activeModel() {
160
+ const registry = readRegistry(this.workspace);
161
+ const active = registry.active;
162
+ if (active && registry.models.some((model) => model.name === active)) {
163
+ return { name: active, dir: trainedModelPath(active) };
164
+ }
165
+ return { name: this.options.manifest.version, dir: LAYA_IMAGE_PATHS.shippedModel };
166
+ }
167
+ /** Selects the model that routes requests; the shipped model's version selects it. */
168
+ use(name) {
169
+ const registry = readRegistry(this.workspace);
170
+ if (name === this.options.manifest.version) {
171
+ writeRegistry(this.workspace, { ...registry, active: undefined });
172
+ return;
173
+ }
174
+ if (!registry.models.some((model) => model.name === name)) {
175
+ throw new Error(`No trained model named ${name}. Models: ${[this.options.manifest.version, ...registry.models.map((model) => model.name)].join(", ")}`);
176
+ }
177
+ writeRegistry(this.workspace, { ...registry, active: name });
178
+ }
179
+ /** Exercises in the workspace, by source. */
180
+ counts() {
181
+ const counts = {};
182
+ for (const row of readRows(this.workspace.dataset))
183
+ counts[row.source] = (counts[row.source] ?? 0) + 1;
184
+ return counts;
185
+ }
186
+ /** Adds labeled session tasks to the workspace. */
187
+ addExercises(rows) {
188
+ ensureWorkspace(this.workspace);
189
+ const merged = mergeRows(readRows(this.workspace.dataset), rows);
190
+ writeRows(this.workspace.dataset, merged.rows);
191
+ return { added: merged.added, updated: merged.updated };
192
+ }
193
+ /**
194
+ * Trains a new model from the active one and activates it when it passes the test gate. The
195
+ * listener hears the result too, so a caller that does not wait only needs to catch.
196
+ */
197
+ train() {
198
+ if (this.job)
199
+ return Promise.reject(new Error("Laya is already training"));
200
+ this.abort = new AbortController();
201
+ this.job = this.run(this.abort.signal)
202
+ .finally(() => {
203
+ this.job = undefined;
204
+ this.abort = undefined;
205
+ this.progress = undefined;
206
+ })
207
+ .then((outcome) => {
208
+ this.listener?.finished({ outcome });
209
+ return outcome;
210
+ }, (error) => {
211
+ const failure = error instanceof Error ? error : new Error(String(error));
212
+ this.listener?.finished({ error: failure });
213
+ throw failure;
214
+ });
215
+ return this.job;
216
+ }
217
+ /** Stops a running training and removes its container. */
218
+ stop() {
219
+ if (!this.abort)
220
+ return;
221
+ this.abort.abort();
222
+ void this.options.docker(["rm", "--force", LAYA_TRAIN_CONTAINER], { timeout: 60_000 });
223
+ }
224
+ async run(signal) {
225
+ ensureWorkspace(this.workspace);
226
+ if (!readRows(this.workspace.dataset).some((row) => row.source === SESSION_SOURCE)) {
227
+ throw new Error("There are no session tasks to learn yet. Run /laya learn in a session first.");
228
+ }
229
+ const image = await this.options.image();
230
+ const gpu = await this.options.gpu();
231
+ const registry = readRegistry(this.workspace);
232
+ const base = this.activeModel();
233
+ const number = Math.max(0, ...registry.models.map((model) => Number(/^local-(\d+)$/.exec(model.name)?.[1] ?? 0)));
234
+ const name = `local-${number + 1}`;
235
+ const out = trainedModelPath(name);
236
+ // A container left by a training that was killed would block the name.
237
+ await this.options.docker(["rm", "--force", LAYA_TRAIN_CONTAINER], { timeout: 60_000 });
238
+ const args = [
239
+ "run",
240
+ "--rm",
241
+ "--name",
242
+ LAYA_TRAIN_CONTAINER,
243
+ ...(gpu ? ["--gpus", "all"] : []),
244
+ "--volume",
245
+ `${LAYA_MODELS_VOLUME}:${LAYA_IMAGE_PATHS.trainedModels}`,
246
+ "--mount",
247
+ `type=bind,source=${this.workspace.dir},target=${LAYA_IMAGE_PATHS.workspace},readonly`,
248
+ image,
249
+ "python",
250
+ LAYA_IMAGE_PATHS.trainScript,
251
+ "learn",
252
+ "--workspace",
253
+ LAYA_IMAGE_PATHS.workspace,
254
+ "--init",
255
+ base.dir,
256
+ "--out",
257
+ out,
258
+ "--focus-source",
259
+ SESSION_SOURCE,
260
+ ];
261
+ let result = await this.runScript(args, signal);
262
+ if (!result.ok && result.error === "out_of_memory") {
263
+ // The smallest batch that still trains; the GPU may have been shared with another process.
264
+ result = await this.runScript([...args, "--micro-batch", "1", "--grad-accum", "16", "--low-memory", "on"], signal);
265
+ }
266
+ if (!result.ok) {
267
+ await this.removeModels(image, [name]);
268
+ throw new Error(result.message);
269
+ }
270
+ const model = {
271
+ name,
272
+ createdAt: new Date().toISOString(),
273
+ basedOn: base.name,
274
+ sessionTasks: result.rows.focus,
275
+ test: result.test,
276
+ session: result.focus,
277
+ seconds: result.seconds,
278
+ device: result.device,
279
+ };
280
+ const activated = passesGate(model.test);
281
+ const pruned = pruneModels({ active: activated ? name : registry.active, models: [...registry.models, model] });
282
+ writeRegistry(this.workspace, pruned.registry);
283
+ await this.removeModels(image, pruned.removed);
284
+ return { model, activated, previous: base.name };
285
+ }
286
+ /** Deletes trained models from the volume. */
287
+ async removeModels(image, names) {
288
+ if (names.length === 0)
289
+ return;
290
+ await this.options.docker(inImage(image, "import shutil, sys; [shutil.rmtree(path, ignore_errors=True) for path in sys.argv[1:]]", names.map(trainedModelPath)), { timeout: 120_000 });
291
+ }
292
+ async runScript(args, signal) {
293
+ const stderr = [];
294
+ const result = await this.options.docker(args, {
295
+ signal,
296
+ onLine: (line, stream) => {
297
+ if (stream === "stdout")
298
+ return;
299
+ if (line.startsWith("PROGRESS ")) {
300
+ try {
301
+ this.progress = JSON.parse(line.slice("PROGRESS ".length));
302
+ this.listener?.progress(this.progress);
303
+ }
304
+ catch {
305
+ // Not a progress line after all.
306
+ }
307
+ }
308
+ else if (line.trim()) {
309
+ stderr.push(line);
310
+ if (stderr.length > 20)
311
+ stderr.shift();
312
+ }
313
+ },
314
+ });
315
+ if (signal.aborted)
316
+ return { ok: false, message: "Training was stopped" };
317
+ const last = result.stdout.trim().split("\n").at(-1) ?? "";
318
+ try {
319
+ return JSON.parse(last);
320
+ }
321
+ catch {
322
+ const detail = stderr.slice(-4).join(" ").trim() || dockerError(result);
323
+ return { ok: false, message: `The training container exited with code ${result.code}: ${detail}` };
324
+ }
325
+ }
326
+ }
327
+ const trainers = new Map();
328
+ /** The trainer of a Laya home, shared by every session runtime of the process. */
329
+ export function sharedTrainer(options) {
330
+ let trainer = trainers.get(options.home);
331
+ if (!trainer) {
332
+ trainer = new LayaTrainer(options);
333
+ trainers.set(options.home, trainer);
334
+ }
335
+ return trainer;
336
+ }
337
+ //# sourceMappingURL=training.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"training.js","sourceRoot":"","sources":["../../../src/extensions/laya/training.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { type DockerRun, dockerError } from \"./docker.ts\";\nimport { layaDecisionsFile } from \"./questions.ts\";\nimport {\n\tLAYA_IMAGE_PATHS,\n\tLAYA_MODELS_VOLUME,\n\tLAYA_TRAIN_CONTAINER,\n\ttype LayaModelManifest,\n\ttrainedModelPath,\n} from \"./runtime.ts\";\nimport { generateSeed } from \"./seed.ts\";\n\n/**\n * Training of the routing model, in a Docker container. Exercises live in a workspace in the Laya\n * home on the host: the synthetic seed the shipped model was trained on, plus the tasks labeled from\n * sessions (`/laya learn`). The container reads the workspace and writes the new model to the models\n * volume. Training starts from the model that routes today, and the new model replaces it only when\n * it answers the held-out test split about as well.\n */\n\n/** One exercise in laya-trainer's dataset format. */\nexport interface TrainingRow {\n\tid: string;\n\tstate: { request: string };\n\texpected: Record<string, string | number | boolean>;\n\tsource: string;\n\tsplit?: \"train\" | \"val\" | \"test\";\n\t/** Session the task came from. */\n\tsession?: string;\n\tlabel_source?: string;\n\tnote?: string;\n\t/** What to know when doing a similar task. Shown to the model, never trained on. */\n\tlesson?: string;\n\tcreated?: string;\n}\n\nexport interface TrainingWorkspace {\n\tdir: string;\n\tdecisions: string;\n\tdataset: string;\n\tregistry: string;\n}\n\n/** Exercises labeled from sessions. Training focuses on them; the rest is replayed. */\nexport const SESSION_SOURCE = \"session\";\n/** The seed size of the shipped model: the same rows, so the same held-out test split. */\nexport const SEED_ROWS = 1100;\n/** A new model is activated unless it answers more than this fraction fewer test questions. */\nexport const REGRESSION_TOLERANCE = 0.01;\n/** Trained models kept besides the active one, newest first. Older ones are deleted. */\nconst KEEP_INACTIVE_MODELS = 2;\n\nexport function trainingWorkspace(home: string): TrainingWorkspace {\n\tconst dir = join(home, \"training\");\n\treturn {\n\t\tdir,\n\t\tdecisions: join(dir, \"decisions.json\"),\n\t\tdataset: join(dir, \"data\", \"dataset.jsonl\"),\n\t\tregistry: join(dir, \"models.json\"),\n\t};\n}\n\n/** Writes the current questions and, on first use, the seed exercises. */\nexport function ensureWorkspace(workspace: TrainingWorkspace): void {\n\tmkdirSync(dirname(workspace.dataset), { recursive: true });\n\twriteFileSync(workspace.decisions, `${JSON.stringify(layaDecisionsFile(), null, 2)}\\n`, \"utf8\");\n\tif (!existsSync(workspace.dataset)) {\n\t\tconst rows = generateSeed(SEED_ROWS).map((row, i) => ({ id: `r${String(i + 1).padStart(5, \"0\")}`, ...row }));\n\t\twriteRows(workspace.dataset, rows);\n\t}\n}\n\nexport function readRows(path: string): TrainingRow[] {\n\tif (!existsSync(path)) return [];\n\tconst rows: TrainingRow[] = [];\n\tfor (const line of readFileSync(path, \"utf8\").split(\"\\n\")) {\n\t\tif (!line.trim()) continue;\n\t\ttry {\n\t\t\trows.push(JSON.parse(line) as TrainingRow);\n\t\t} catch {\n\t\t\t// A line broken by hand; the training script skips it too.\n\t\t}\n\t}\n\treturn rows;\n}\n\nexport function writeRows(path: string, rows: readonly TrainingRow[]): void {\n\tmkdirSync(dirname(path), { recursive: true });\n\twriteFileSync(path, rows.map((row) => `${JSON.stringify(row)}\\n`).join(\"\"), \"utf8\");\n}\n\n/** Requests that differ only in case and spacing are the same exercise. */\nexport function requestKey(request: string): string {\n\treturn request.trim().toLowerCase().replace(/\\s+/g, \" \");\n}\n\n/**\n * Adds rows, replacing the labels of an exercise with the same request: a later session labels it\n * with newer evidence.\n */\nexport function mergeRows(\n\texisting: readonly TrainingRow[],\n\tincoming: ReadonlyArray<Omit<TrainingRow, \"id\">>,\n): { rows: TrainingRow[]; added: number; updated: number } {\n\tconst rows = [...existing];\n\tconst byKey = new Map(rows.map((row, index) => [requestKey(row.state.request), index]));\n\tlet next = Math.max(0, ...rows.map((row) => Number(/^s(\\d+)$/.exec(row.id)?.[1] ?? 0))) + 1;\n\tlet added = 0;\n\tlet updated = 0;\n\tfor (const row of incoming) {\n\t\tconst key = requestKey(row.state.request);\n\t\tconst index = byKey.get(key);\n\t\tif (index === undefined) {\n\t\t\tbyKey.set(key, rows.length);\n\t\t\trows.push({ id: `s${String(next++).padStart(5, \"0\")}`, ...row });\n\t\t\tadded++;\n\t\t} else {\n\t\t\trows[index] = { id: rows[index].id, ...row };\n\t\t\tupdated++;\n\t\t}\n\t}\n\treturn { rows, added, updated };\n}\n\n/** Correct answers on a set of exercises, as the training script reports them. */\nexport interface Score {\n\tn: number;\n\tcorrect: number;\n\tper_question: Record<string, { n: number; correct: number }>;\n}\n\nexport interface TrainedModel {\n\tname: string;\n\tcreatedAt: string;\n\t/** Model the training started from. */\n\tbasedOn: string;\n\tsessionTasks: number;\n\t/** Test split: the new model and the one it started from. */\n\ttest: { candidate: Score; current: Score };\n\t/** Session exercises: answers the new model and the one it started from get right. */\n\tsession: { candidate: Score; current: Score };\n\tseconds: number;\n\tdevice: string;\n}\n\nexport interface ModelRegistry {\n\t/** Trained model that routes requests. Absent: the shipped model. */\n\tactive?: string;\n\tmodels: TrainedModel[];\n}\n\nexport function readRegistry(workspace: TrainingWorkspace): ModelRegistry {\n\ttry {\n\t\tconst registry = JSON.parse(readFileSync(workspace.registry, \"utf8\")) as ModelRegistry;\n\t\treturn { active: registry.active, models: Array.isArray(registry.models) ? registry.models : [] };\n\t} catch {\n\t\treturn { models: [] };\n\t}\n}\n\nexport function writeRegistry(workspace: TrainingWorkspace, registry: ModelRegistry): void {\n\tmkdirSync(workspace.dir, { recursive: true });\n\twriteFileSync(workspace.registry, `${JSON.stringify(registry, null, 2)}\\n`, \"utf8\");\n}\n\nexport const accuracy = (score: Score): number => (score.n > 0 ? score.correct / score.n : 0);\n\n/** Whether the new model answers the test split well enough to replace the current one. */\nexport function passesGate(test: TrainedModel[\"test\"]): boolean {\n\treturn accuracy(test.candidate) >= accuracy(test.current) - REGRESSION_TOLERANCE;\n}\n\n/** Keeps the newest inactive models and the active one; returns the names to delete. */\nexport function pruneModels(registry: ModelRegistry): { registry: ModelRegistry; removed: string[] } {\n\t// The registry lists models in the order they were trained.\n\tconst inactive = registry.models.filter((model) => model.name !== registry.active).reverse();\n\tconst removed = inactive.slice(KEEP_INACTIVE_MODELS).map((model) => model.name);\n\treturn {\n\t\tregistry: { ...registry, models: registry.models.filter((model) => !removed.includes(model.name)) },\n\t\tremoved,\n\t};\n}\n\nexport interface TrainingProgress {\n\tphase: \"train\" | \"evaluate\";\n\tepoch?: number;\n\tepochs?: number;\n\tdone?: number;\n\teta_s?: number;\n\tdevice?: string;\n\tmodel?: \"new\" | \"current\";\n}\n\nexport function describeProgress(progress: TrainingProgress): string {\n\tif (progress.phase === \"evaluate\") {\n\t\treturn progress.model === \"current\" ? \"comparing with the current model\" : \"testing the new model\";\n\t}\n\tif (!progress.epoch) return `starting on ${progress.device ?? \"the device\"}`;\n\tconst minutes = progress.eta_s === undefined ? undefined : Math.max(1, Math.round(progress.eta_s / 60));\n\treturn `round ${progress.epoch}/${progress.epochs} ${Math.round((progress.done ?? 0) * 100)}%${\n\t\tminutes && progress.done !== 1 ? `, about ${minutes} min left` : \"\"\n\t}`;\n}\n\ntype ScriptResult =\n\t| {\n\t\t\tok: true;\n\t\t\tseconds: number;\n\t\t\tdevice: string;\n\t\t\trows: { focus: number };\n\t\t\ttest: TrainedModel[\"test\"];\n\t\t\tfocus: TrainedModel[\"session\"];\n\t }\n\t| { ok: false; error?: string; message: string };\n\nexport interface TrainingOutcome {\n\tmodel: TrainedModel;\n\t/** Whether the new model now routes requests. */\n\tactivated: boolean;\n\t/** Model that routed before. */\n\tprevious: string;\n}\n\nexport interface TrainingListener {\n\tprogress(progress: TrainingProgress): void;\n\tfinished(result: { outcome: TrainingOutcome } | { error: Error }): void;\n}\n\nexport interface LayaTrainerOptions {\n\thome: string;\n\tmanifest: LayaModelManifest;\n\tdocker: DockerRun;\n\t/** Image the training container runs; the server's image. */\n\timage: () => Promise<string>;\n\t/** Whether the training container can use an NVIDIA GPU. */\n\tgpu: () => Promise<boolean>;\n}\n\n/** Runs a Python one-liner in the image, with the models volume mounted. */\nfunction inImage(image: string, code: string, extra: string[] = [], gpu = false): string[] {\n\treturn [\n\t\t\"run\",\n\t\t\"--rm\",\n\t\t...(gpu ? [\"--gpus\", \"all\"] : []),\n\t\t\"--volume\",\n\t\t`${LAYA_MODELS_VOLUME}:${LAYA_IMAGE_PATHS.trainedModels}`,\n\t\timage,\n\t\t\"python\",\n\t\t\"-c\",\n\t\tcode,\n\t\t...extra,\n\t];\n}\n\n/** Whether a container of the image sees an NVIDIA GPU: needs the CUDA image and Docker GPU support. */\nexport async function dockerHasGpu(docker: DockerRun, image: string): Promise<boolean> {\n\tconst result = await docker(\n\t\tinImage(image, \"import sys, torch; sys.exit(0 if torch.cuda.is_available() else 1)\", [], true),\n\t\t{ timeout: 120_000 },\n\t);\n\treturn result.code === 0;\n}\n\n/**\n * Runs one training at a time. It belongs to the process, not to a session runtime, so training\n * goes on across `/new` and `/resume`; `listener` is the latest runtime that wants its events.\n */\nexport class LayaTrainer {\n\treadonly workspace: TrainingWorkspace;\n\tprivate readonly options: LayaTrainerOptions;\n\tprivate abort: AbortController | undefined;\n\tprivate job: Promise<TrainingOutcome> | undefined;\n\t/** Latest progress of the running training. */\n\tprogress: TrainingProgress | undefined;\n\tlistener: TrainingListener | undefined;\n\n\tconstructor(options: LayaTrainerOptions) {\n\t\tthis.options = options;\n\t\tthis.workspace = trainingWorkspace(options.home);\n\t}\n\n\tget running(): boolean {\n\t\treturn this.job !== undefined;\n\t}\n\n\t/** The model that routes requests, with its path inside the containers: the active trained model, or the shipped one. */\n\tactiveModel(): { name: string; dir: string } {\n\t\tconst registry = readRegistry(this.workspace);\n\t\tconst active = registry.active;\n\t\tif (active && registry.models.some((model) => model.name === active)) {\n\t\t\treturn { name: active, dir: trainedModelPath(active) };\n\t\t}\n\t\treturn { name: this.options.manifest.version, dir: LAYA_IMAGE_PATHS.shippedModel };\n\t}\n\n\t/** Selects the model that routes requests; the shipped model's version selects it. */\n\tuse(name: string): void {\n\t\tconst registry = readRegistry(this.workspace);\n\t\tif (name === this.options.manifest.version) {\n\t\t\twriteRegistry(this.workspace, { ...registry, active: undefined });\n\t\t\treturn;\n\t\t}\n\t\tif (!registry.models.some((model) => model.name === name)) {\n\t\t\tthrow new Error(\n\t\t\t\t`No trained model named ${name}. Models: ${[this.options.manifest.version, ...registry.models.map((model) => model.name)].join(\", \")}`,\n\t\t\t);\n\t\t}\n\t\twriteRegistry(this.workspace, { ...registry, active: name });\n\t}\n\n\t/** Exercises in the workspace, by source. */\n\tcounts(): Record<string, number> {\n\t\tconst counts: Record<string, number> = {};\n\t\tfor (const row of readRows(this.workspace.dataset)) counts[row.source] = (counts[row.source] ?? 0) + 1;\n\t\treturn counts;\n\t}\n\n\t/** Adds labeled session tasks to the workspace. */\n\taddExercises(rows: ReadonlyArray<Omit<TrainingRow, \"id\">>): { added: number; updated: number } {\n\t\tensureWorkspace(this.workspace);\n\t\tconst merged = mergeRows(readRows(this.workspace.dataset), rows);\n\t\twriteRows(this.workspace.dataset, merged.rows);\n\t\treturn { added: merged.added, updated: merged.updated };\n\t}\n\n\t/**\n\t * Trains a new model from the active one and activates it when it passes the test gate. The\n\t * listener hears the result too, so a caller that does not wait only needs to catch.\n\t */\n\ttrain(): Promise<TrainingOutcome> {\n\t\tif (this.job) return Promise.reject(new Error(\"Laya is already training\"));\n\t\tthis.abort = new AbortController();\n\t\tthis.job = this.run(this.abort.signal)\n\t\t\t.finally(() => {\n\t\t\t\tthis.job = undefined;\n\t\t\t\tthis.abort = undefined;\n\t\t\t\tthis.progress = undefined;\n\t\t\t})\n\t\t\t.then(\n\t\t\t\t(outcome) => {\n\t\t\t\t\tthis.listener?.finished({ outcome });\n\t\t\t\t\treturn outcome;\n\t\t\t\t},\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\tconst failure = error instanceof Error ? error : new Error(String(error));\n\t\t\t\t\tthis.listener?.finished({ error: failure });\n\t\t\t\t\tthrow failure;\n\t\t\t\t},\n\t\t\t);\n\t\treturn this.job;\n\t}\n\n\t/** Stops a running training and removes its container. */\n\tstop(): void {\n\t\tif (!this.abort) return;\n\t\tthis.abort.abort();\n\t\tvoid this.options.docker([\"rm\", \"--force\", LAYA_TRAIN_CONTAINER], { timeout: 60_000 });\n\t}\n\n\tprivate async run(signal: AbortSignal): Promise<TrainingOutcome> {\n\t\tensureWorkspace(this.workspace);\n\t\tif (!readRows(this.workspace.dataset).some((row) => row.source === SESSION_SOURCE)) {\n\t\t\tthrow new Error(\"There are no session tasks to learn yet. Run /laya learn in a session first.\");\n\t\t}\n\t\tconst image = await this.options.image();\n\t\tconst gpu = await this.options.gpu();\n\t\tconst registry = readRegistry(this.workspace);\n\t\tconst base = this.activeModel();\n\t\tconst number = Math.max(0, ...registry.models.map((model) => Number(/^local-(\\d+)$/.exec(model.name)?.[1] ?? 0)));\n\t\tconst name = `local-${number + 1}`;\n\t\tconst out = trainedModelPath(name);\n\t\t// A container left by a training that was killed would block the name.\n\t\tawait this.options.docker([\"rm\", \"--force\", LAYA_TRAIN_CONTAINER], { timeout: 60_000 });\n\n\t\tconst args = [\n\t\t\t\"run\",\n\t\t\t\"--rm\",\n\t\t\t\"--name\",\n\t\t\tLAYA_TRAIN_CONTAINER,\n\t\t\t...(gpu ? [\"--gpus\", \"all\"] : []),\n\t\t\t\"--volume\",\n\t\t\t`${LAYA_MODELS_VOLUME}:${LAYA_IMAGE_PATHS.trainedModels}`,\n\t\t\t\"--mount\",\n\t\t\t`type=bind,source=${this.workspace.dir},target=${LAYA_IMAGE_PATHS.workspace},readonly`,\n\t\t\timage,\n\t\t\t\"python\",\n\t\t\tLAYA_IMAGE_PATHS.trainScript,\n\t\t\t\"learn\",\n\t\t\t\"--workspace\",\n\t\t\tLAYA_IMAGE_PATHS.workspace,\n\t\t\t\"--init\",\n\t\t\tbase.dir,\n\t\t\t\"--out\",\n\t\t\tout,\n\t\t\t\"--focus-source\",\n\t\t\tSESSION_SOURCE,\n\t\t];\n\t\tlet result = await this.runScript(args, signal);\n\t\tif (!result.ok && result.error === \"out_of_memory\") {\n\t\t\t// The smallest batch that still trains; the GPU may have been shared with another process.\n\t\t\tresult = await this.runScript(\n\t\t\t\t[...args, \"--micro-batch\", \"1\", \"--grad-accum\", \"16\", \"--low-memory\", \"on\"],\n\t\t\t\tsignal,\n\t\t\t);\n\t\t}\n\t\tif (!result.ok) {\n\t\t\tawait this.removeModels(image, [name]);\n\t\t\tthrow new Error(result.message);\n\t\t}\n\n\t\tconst model: TrainedModel = {\n\t\t\tname,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\tbasedOn: base.name,\n\t\t\tsessionTasks: result.rows.focus,\n\t\t\ttest: result.test,\n\t\t\tsession: result.focus,\n\t\t\tseconds: result.seconds,\n\t\t\tdevice: result.device,\n\t\t};\n\t\tconst activated = passesGate(model.test);\n\t\tconst pruned = pruneModels({ active: activated ? name : registry.active, models: [...registry.models, model] });\n\t\twriteRegistry(this.workspace, pruned.registry);\n\t\tawait this.removeModels(image, pruned.removed);\n\t\treturn { model, activated, previous: base.name };\n\t}\n\n\t/** Deletes trained models from the volume. */\n\tprivate async removeModels(image: string, names: string[]): Promise<void> {\n\t\tif (names.length === 0) return;\n\t\tawait this.options.docker(\n\t\t\tinImage(\n\t\t\t\timage,\n\t\t\t\t\"import shutil, sys; [shutil.rmtree(path, ignore_errors=True) for path in sys.argv[1:]]\",\n\t\t\t\tnames.map(trainedModelPath),\n\t\t\t),\n\t\t\t{ timeout: 120_000 },\n\t\t);\n\t}\n\n\tprivate async runScript(args: string[], signal: AbortSignal): Promise<ScriptResult> {\n\t\tconst stderr: string[] = [];\n\t\tconst result = await this.options.docker(args, {\n\t\t\tsignal,\n\t\t\tonLine: (line, stream) => {\n\t\t\t\tif (stream === \"stdout\") return;\n\t\t\t\tif (line.startsWith(\"PROGRESS \")) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.progress = JSON.parse(line.slice(\"PROGRESS \".length)) as TrainingProgress;\n\t\t\t\t\t\tthis.listener?.progress(this.progress);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Not a progress line after all.\n\t\t\t\t\t}\n\t\t\t\t} else if (line.trim()) {\n\t\t\t\t\tstderr.push(line);\n\t\t\t\t\tif (stderr.length > 20) stderr.shift();\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tif (signal.aborted) return { ok: false, message: \"Training was stopped\" };\n\t\tconst last = result.stdout.trim().split(\"\\n\").at(-1) ?? \"\";\n\t\ttry {\n\t\t\treturn JSON.parse(last) as ScriptResult;\n\t\t} catch {\n\t\t\tconst detail = stderr.slice(-4).join(\" \").trim() || dockerError(result);\n\t\t\treturn { ok: false, message: `The training container exited with code ${result.code}: ${detail}` };\n\t\t}\n\t}\n}\n\nconst trainers = new Map<string, LayaTrainer>();\n\n/** The trainer of a Laya home, shared by every session runtime of the process. */\nexport function sharedTrainer(options: LayaTrainerOptions): LayaTrainer {\n\tlet trainer = trainers.get(options.home);\n\tif (!trainer) {\n\t\ttrainer = new LayaTrainer(options);\n\t\ttrainers.set(options.home, trainer);\n\t}\n\treturn trainer;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"config-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/config-selector.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/config-selector.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * TUI component for managing package resources (enable/disable)
3
3
  */
4
4
  import { homedir } from "node:os";
5
- import { basename, dirname, join, relative } from "node:path";
5
+ import { basename, dirname, join, relative, sep } from "node:path";
6
6
  import { Container, getKeybindings, Input, matchesKey, Spacer, truncateToWidth, visibleWidth, } from "@relay-harness/tui";
7
7
  import { CONFIG_DIR_NAME } from "../../../config.js";
8
8
  import { BUILTIN_PATH_PREFIX } from "../../../core/source-info.js";
@@ -714,7 +714,8 @@ class ResourceList {
714
714
  }
715
715
  getPackageResourcePattern(item) {
716
716
  const baseDir = item.metadata.baseDir ?? dirname(item.path);
717
- return relative(baseDir, item.path);
717
+ // Package filters are shared settings; keep them portable instead of writing Windows separators.
718
+ return relative(baseDir, item.path).split(sep).join("/");
718
719
  }
719
720
  }
720
721
  export class ConfigSelectorComponent extends Container {