@mindfoldhq/trellis 0.6.0-beta.17 → 0.6.0-beta.18

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 (70) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/channel/adapters/claude.d.ts +7 -16
  3. package/dist/commands/channel/adapters/claude.d.ts.map +1 -1
  4. package/dist/commands/channel/adapters/claude.js +19 -25
  5. package/dist/commands/channel/adapters/claude.js.map +1 -1
  6. package/dist/commands/channel/adapters/codex.d.ts +5 -1
  7. package/dist/commands/channel/adapters/codex.d.ts.map +1 -1
  8. package/dist/commands/channel/adapters/codex.js +8 -15
  9. package/dist/commands/channel/adapters/codex.js.map +1 -1
  10. package/dist/commands/channel/adapters/index.d.ts +6 -1
  11. package/dist/commands/channel/adapters/index.d.ts.map +1 -1
  12. package/dist/commands/channel/adapters/index.js +12 -6
  13. package/dist/commands/channel/adapters/index.js.map +1 -1
  14. package/dist/commands/channel/guard.d.ts +150 -0
  15. package/dist/commands/channel/guard.d.ts.map +1 -0
  16. package/dist/commands/channel/guard.js +474 -0
  17. package/dist/commands/channel/guard.js.map +1 -0
  18. package/dist/commands/channel/index.d.ts +1 -1
  19. package/dist/commands/channel/index.d.ts.map +1 -1
  20. package/dist/commands/channel/index.js +38 -10
  21. package/dist/commands/channel/index.js.map +1 -1
  22. package/dist/commands/channel/interrupt.d.ts +10 -0
  23. package/dist/commands/channel/interrupt.d.ts.map +1 -0
  24. package/dist/commands/channel/interrupt.js +22 -0
  25. package/dist/commands/channel/interrupt.js.map +1 -0
  26. package/dist/commands/channel/messages.d.ts +0 -1
  27. package/dist/commands/channel/messages.d.ts.map +1 -1
  28. package/dist/commands/channel/messages.js +2 -6
  29. package/dist/commands/channel/messages.js.map +1 -1
  30. package/dist/commands/channel/run.d.ts +0 -1
  31. package/dist/commands/channel/run.d.ts.map +1 -1
  32. package/dist/commands/channel/run.js +5 -12
  33. package/dist/commands/channel/run.js.map +1 -1
  34. package/dist/commands/channel/send.d.ts +0 -2
  35. package/dist/commands/channel/send.d.ts.map +1 -1
  36. package/dist/commands/channel/send.js +0 -2
  37. package/dist/commands/channel/send.js.map +1 -1
  38. package/dist/commands/channel/spawn.d.ts +10 -0
  39. package/dist/commands/channel/spawn.d.ts.map +1 -1
  40. package/dist/commands/channel/spawn.js +57 -7
  41. package/dist/commands/channel/spawn.js.map +1 -1
  42. package/dist/commands/channel/supervisor/idle.d.ts +46 -0
  43. package/dist/commands/channel/supervisor/idle.d.ts.map +1 -0
  44. package/dist/commands/channel/supervisor/idle.js +72 -0
  45. package/dist/commands/channel/supervisor/idle.js.map +1 -0
  46. package/dist/commands/channel/supervisor/inbox.d.ts +4 -4
  47. package/dist/commands/channel/supervisor/inbox.d.ts.map +1 -1
  48. package/dist/commands/channel/supervisor/inbox.js +22 -22
  49. package/dist/commands/channel/supervisor/inbox.js.map +1 -1
  50. package/dist/commands/channel/supervisor/shutdown.d.ts +3 -1
  51. package/dist/commands/channel/supervisor/shutdown.d.ts.map +1 -1
  52. package/dist/commands/channel/supervisor/shutdown.js +4 -1
  53. package/dist/commands/channel/supervisor/shutdown.js.map +1 -1
  54. package/dist/commands/channel/supervisor/turns.d.ts +11 -0
  55. package/dist/commands/channel/supervisor/turns.d.ts.map +1 -1
  56. package/dist/commands/channel/supervisor/turns.js +19 -2
  57. package/dist/commands/channel/supervisor/turns.js.map +1 -1
  58. package/dist/commands/channel/supervisor.d.ts +6 -0
  59. package/dist/commands/channel/supervisor.d.ts.map +1 -1
  60. package/dist/commands/channel/supervisor.js +43 -3
  61. package/dist/commands/channel/supervisor.js.map +1 -1
  62. package/dist/commands/channel/wait.d.ts +0 -1
  63. package/dist/commands/channel/wait.d.ts.map +1 -1
  64. package/dist/commands/channel/wait.js +0 -1
  65. package/dist/commands/channel/wait.js.map +1 -1
  66. package/dist/migrations/manifests/0.5.16.json +9 -0
  67. package/dist/migrations/manifests/0.6.0-beta.18.json +16 -0
  68. package/dist/templates/trellis/config.yaml +20 -0
  69. package/dist/templates/trellis/scripts/common/task_store.py +24 -7
  70. package/package.json +2 -2
@@ -0,0 +1,474 @@
1
+ /**
2
+ * Channel worker OOM guard policy.
3
+ *
4
+ * Resolves idle-cleanup TTL and live-worker budget from CLI flag /
5
+ * environment / project config / built-in defaults (in that precedence
6
+ * order), then scans the live worker registry inside a project bucket
7
+ * to enforce both constraints at spawn time.
8
+ *
9
+ * Boundary: this module lives in the CLI runtime layer. Core owns
10
+ * worker activity / `idleSince` projection; the supervisor owns process
11
+ * launch + signals. The guard only consumes that durable state and
12
+ * sends OS signals through pid files.
13
+ */
14
+ import { execFileSync } from "node:child_process";
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+ import { isTerminalLifecycle, reduceWorkerRegistry, } from "@mindfoldhq/trellis-core/channel";
18
+ import { DIR_NAMES } from "../../constants/paths.js";
19
+ import { channelRoot, currentProjectKey, projectDir, workerFile, } from "./store/paths.js";
20
+ import { parseDuration } from "./wait.js";
21
+ /** Built-in default idle-cleanup TTL for spawned workers (5 minutes). */
22
+ export const DEFAULT_IDLE_TTL_MS = 5 * 60 * 1000;
23
+ /** Built-in default live-worker budget per project/scope. */
24
+ export const DEFAULT_MAX_LIVE_WORKERS = 6;
25
+ /** Env var override for the idle-cleanup TTL. */
26
+ export const ENV_IDLE_TIMEOUT = "TRELLIS_CHANNEL_WORKER_IDLE_TIMEOUT";
27
+ /** Env var override for the live-worker budget. */
28
+ export const ENV_MAX_LIVE_WORKERS = "TRELLIS_CHANNEL_MAX_LIVE_WORKERS";
29
+ /**
30
+ * Resolve the effective guard policy. Precedence:
31
+ * 1. CLI flag (`flag*Ms` / `flagMaxLiveWorkers`)
32
+ * 2. environment variable
33
+ * 3. `.trellis/config.yaml` `channel.worker_guard`
34
+ * 4. built-in default constant
35
+ */
36
+ export function resolveWorkerGuardConfig(opts = {}) {
37
+ const cwd = opts.cwd ?? process.cwd();
38
+ const env = opts.env ?? process.env;
39
+ const fromConfig = loadWorkerGuardConfig(cwd);
40
+ const idleTimeoutMs = pickNonNegativeMs(opts.flagIdleTimeoutMs, parseEnvDuration(env[ENV_IDLE_TIMEOUT], ENV_IDLE_TIMEOUT), fromConfig?.idleTimeoutMs, DEFAULT_IDLE_TTL_MS);
41
+ const maxLiveWorkers = pickNonNegativeInt(opts.flagMaxLiveWorkers, parseEnvInt(env[ENV_MAX_LIVE_WORKERS], ENV_MAX_LIVE_WORKERS), fromConfig?.maxLiveWorkers, DEFAULT_MAX_LIVE_WORKERS);
42
+ return { idleTimeoutMs, maxLiveWorkers };
43
+ }
44
+ function pickNonNegativeMs(...candidates) {
45
+ for (const c of candidates) {
46
+ if (c === undefined)
47
+ continue;
48
+ if (!Number.isFinite(c) || c < 0) {
49
+ throw new Error(`Idle timeout must be a non-negative duration (got ${c})`);
50
+ }
51
+ return c;
52
+ }
53
+ return DEFAULT_IDLE_TTL_MS;
54
+ }
55
+ function pickNonNegativeInt(...candidates) {
56
+ for (const c of candidates) {
57
+ if (c === undefined)
58
+ continue;
59
+ if (!Number.isInteger(c) || c < 0) {
60
+ throw new Error(`Max live workers must be a non-negative integer (got ${c})`);
61
+ }
62
+ return c;
63
+ }
64
+ return DEFAULT_MAX_LIVE_WORKERS;
65
+ }
66
+ function parseEnvDuration(raw, envName) {
67
+ if (raw === undefined || raw === "")
68
+ return undefined;
69
+ try {
70
+ return parseDuration(raw);
71
+ }
72
+ catch (err) {
73
+ throw new Error(`${envName}: ${err instanceof Error ? err.message : String(err)}`);
74
+ }
75
+ }
76
+ function parseEnvInt(raw, envName) {
77
+ if (raw === undefined || raw === "")
78
+ return undefined;
79
+ const n = Number(raw);
80
+ if (!Number.isInteger(n) || n < 0) {
81
+ throw new Error(`${envName} must be a non-negative integer (got '${raw}')`);
82
+ }
83
+ return n;
84
+ }
85
+ /**
86
+ * Parse the `channel.worker_guard` section out of `.trellis/config.yaml`.
87
+ * Mirrors the lightweight line-scanner used elsewhere in update.ts so we
88
+ * don't pull in a YAML dependency just for this two-field section.
89
+ */
90
+ export function loadWorkerGuardConfig(cwd) {
91
+ const configPath = path.join(cwd, DIR_NAMES.WORKFLOW, "config.yaml");
92
+ if (!fs.existsSync(configPath))
93
+ return undefined;
94
+ let content;
95
+ try {
96
+ content = fs.readFileSync(configPath, "utf-8");
97
+ }
98
+ catch {
99
+ return undefined;
100
+ }
101
+ return parseWorkerGuardSection(content);
102
+ }
103
+ /** Exposed for unit tests. */
104
+ export function parseWorkerGuardSection(content) {
105
+ const lines = content.split("\n");
106
+ let inChannel = false;
107
+ let inGuard = false;
108
+ const found = {};
109
+ let any = false;
110
+ for (const raw of lines) {
111
+ const line = raw.replace(/\r$/, "");
112
+ const trimmed = line.trimEnd();
113
+ if (trimmed === "" || trimmed.trimStart().startsWith("#"))
114
+ continue;
115
+ if (/^channel:\s*$/.test(trimmed)) {
116
+ inChannel = true;
117
+ inGuard = false;
118
+ continue;
119
+ }
120
+ if (inChannel && /^ {2}worker_guard:\s*$/.test(trimmed)) {
121
+ inGuard = true;
122
+ continue;
123
+ }
124
+ if (inGuard) {
125
+ const idle = trimmed.match(/^ {4}idle_timeout:\s*(.+)$/);
126
+ if (idle) {
127
+ const val = stripValue(idle[1]);
128
+ found.idleTimeoutMs = parseGuardDuration(val, "idle_timeout");
129
+ any = true;
130
+ continue;
131
+ }
132
+ const max = trimmed.match(/^ {4}max_live_workers:\s*(.+)$/);
133
+ if (max) {
134
+ const val = stripValue(max[1]);
135
+ const n = Number(val);
136
+ if (!Number.isInteger(n) || n < 0) {
137
+ throw new Error(`channel.worker_guard.max_live_workers must be a non-negative integer (got '${val}')`);
138
+ }
139
+ found.maxLiveWorkers = n;
140
+ any = true;
141
+ continue;
142
+ }
143
+ // Anything else at the same indent (or shallower) ends the section.
144
+ if (!/^ {4}\S/.test(line)) {
145
+ inGuard = false;
146
+ }
147
+ }
148
+ if (inChannel && !/^ {2}\S/.test(line) && /^\S/.test(line)) {
149
+ inChannel = false;
150
+ inGuard = false;
151
+ }
152
+ }
153
+ return any ? found : undefined;
154
+ }
155
+ function stripValue(s) {
156
+ return s
157
+ .trim()
158
+ .replace(/\s*#.*$/, "")
159
+ .trim()
160
+ .replace(/^['"]|['"]$/g, "");
161
+ }
162
+ function parseGuardDuration(raw, key) {
163
+ // Allow bare integer = milliseconds (so `0` disables cleanly).
164
+ const asInt = Number(raw);
165
+ if (Number.isFinite(asInt) && /^\d+$/.test(raw)) {
166
+ if (asInt < 0) {
167
+ throw new Error(`channel.worker_guard.${key} must be non-negative (got '${raw}')`);
168
+ }
169
+ // Bare integer 0 = disabled; >0 with no unit = milliseconds.
170
+ return asInt;
171
+ }
172
+ try {
173
+ return parseDuration(raw) ?? 0;
174
+ }
175
+ catch (err) {
176
+ throw new Error(`channel.worker_guard.${key}: ${err instanceof Error ? err.message : String(err)}`);
177
+ }
178
+ }
179
+ /**
180
+ * Enumerate live (non-terminal + supervisor-pid alive) workers across
181
+ * every channel in the given project bucket.
182
+ */
183
+ export function scanLiveWorkers(opts = {}) {
184
+ const project = opts.projectKey ?? currentProjectKey();
185
+ const bucket = opts.root
186
+ ? path.join(opts.root, project)
187
+ : projectDir(project);
188
+ if (!fs.existsSync(bucket))
189
+ return [];
190
+ let entries;
191
+ try {
192
+ entries = fs.readdirSync(bucket);
193
+ }
194
+ catch {
195
+ return [];
196
+ }
197
+ const out = [];
198
+ for (const entry of entries) {
199
+ if (entry.startsWith("."))
200
+ continue;
201
+ const dir = path.join(bucket, entry);
202
+ try {
203
+ if (!fs.statSync(dir).isDirectory())
204
+ continue;
205
+ }
206
+ catch {
207
+ continue;
208
+ }
209
+ const events = path.join(dir, "events.jsonl");
210
+ if (!fs.existsSync(events))
211
+ continue;
212
+ let workers;
213
+ try {
214
+ const all = readFileEventsSync(events);
215
+ workers = reduceWorkerRegistry(all).workers;
216
+ }
217
+ catch {
218
+ continue;
219
+ }
220
+ for (const state of workers) {
221
+ if (state.terminal || isTerminalLifecycle(state.lifecycle))
222
+ continue;
223
+ const supervisorPid = readPid(workerFile(entry, state.workerId, "pid", project));
224
+ if (supervisorPid === undefined || !pidAlive(supervisorPid)) {
225
+ // Supervisor pid file missing or dead → not a live OS process,
226
+ // even if durable state still shows running. Reconciler / future
227
+ // CLI cleanup will catch up; the guard ignores it.
228
+ continue;
229
+ }
230
+ const supervisorVerified = opts.isSupervisorProcess
231
+ ? opts.isSupervisorProcess(supervisorPid, entry, state.workerId)
232
+ : isSupervisorProcess(supervisorPid, entry, state.workerId);
233
+ const workerPid = readPid(workerFile(entry, state.workerId, "worker-pid", project));
234
+ out.push({
235
+ channel: entry,
236
+ workerId: state.workerId,
237
+ state,
238
+ supervisorPid,
239
+ supervisorVerified,
240
+ ...(workerPid !== undefined ? { workerPid } : {}),
241
+ });
242
+ }
243
+ for (const state of readReservationWorkers(entry, project)) {
244
+ if (out.some((w) => w.channel === entry && w.workerId === state.workerId)) {
245
+ continue;
246
+ }
247
+ const supervisorPid = readPid(workerFile(entry, state.workerId, "pid", project));
248
+ if (supervisorPid === undefined || !pidAlive(supervisorPid))
249
+ continue;
250
+ const supervisorVerified = opts.isSupervisorProcess
251
+ ? opts.isSupervisorProcess(supervisorPid, entry, state.workerId)
252
+ : isSupervisorProcess(supervisorPid, entry, state.workerId);
253
+ out.push({
254
+ channel: entry,
255
+ workerId: state.workerId,
256
+ state,
257
+ supervisorPid,
258
+ supervisorVerified,
259
+ });
260
+ }
261
+ }
262
+ return out;
263
+ }
264
+ function readReservationWorkers(channel, project) {
265
+ const dir = path.join(projectDir(project), channel);
266
+ let files;
267
+ try {
268
+ files = fs.readdirSync(dir);
269
+ }
270
+ catch {
271
+ return [];
272
+ }
273
+ const workers = [];
274
+ for (const file of files) {
275
+ if (!file.endsWith(".reservation"))
276
+ continue;
277
+ const worker = file.slice(0, -".reservation".length);
278
+ workers.push({
279
+ workerId: worker,
280
+ lifecycle: "starting",
281
+ terminal: false,
282
+ activity: "idle",
283
+ pendingMessageCount: 0,
284
+ inboxPolicy: "explicitOnly",
285
+ updatedAt: new Date(0).toISOString(),
286
+ lastSeq: 0,
287
+ });
288
+ }
289
+ return workers;
290
+ }
291
+ function readFileEventsSync(file) {
292
+ const text = fs.readFileSync(file, "utf-8");
293
+ const events = [];
294
+ for (const line of text.split("\n")) {
295
+ if (!line.trim())
296
+ continue;
297
+ try {
298
+ events.push(JSON.parse(line));
299
+ }
300
+ catch {
301
+ continue;
302
+ }
303
+ }
304
+ return events;
305
+ }
306
+ function readPid(p) {
307
+ try {
308
+ const n = Number(fs.readFileSync(p, "utf-8").trim());
309
+ return Number.isFinite(n) && n > 0 ? n : undefined;
310
+ }
311
+ catch {
312
+ return undefined;
313
+ }
314
+ }
315
+ function pidAlive(pid) {
316
+ try {
317
+ process.kill(pid, 0);
318
+ return true;
319
+ }
320
+ catch {
321
+ return false;
322
+ }
323
+ }
324
+ export function isSupervisorProcess(pid, channel, worker) {
325
+ if (process.platform === "win32")
326
+ return false;
327
+ try {
328
+ const command = execFileSync("ps", ["-p", String(pid), "-o", "command="], {
329
+ encoding: "utf-8",
330
+ timeout: 1000,
331
+ stdio: ["ignore", "pipe", "ignore"],
332
+ }).trim();
333
+ const pattern = new RegExp([
334
+ "(?:^|\\s)channel\\s+__supervisor\\s+",
335
+ escapeRegExp(channel),
336
+ "\\s+",
337
+ escapeRegExp(worker),
338
+ "(?:\\s|$)",
339
+ ].join(""));
340
+ return pattern.test(command);
341
+ }
342
+ catch {
343
+ return false;
344
+ }
345
+ }
346
+ function escapeRegExp(s) {
347
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
348
+ }
349
+ /**
350
+ * Pure predicate: is this live worker eligible for idle-cleanup right
351
+ * now? Mid-turn workers and workers without `idleSince` (e.g. they
352
+ * never spawned cleanly) are never killed by the guard.
353
+ */
354
+ export function isIdleCleanupEligible(live, idleTimeoutMs, now) {
355
+ if (idleTimeoutMs <= 0)
356
+ return false;
357
+ const { state } = live;
358
+ if (state.activity !== "idle")
359
+ return false;
360
+ if (!state.idleSince)
361
+ return false;
362
+ if (state.terminal)
363
+ return false;
364
+ const idleSinceMs = Date.parse(state.idleSince);
365
+ if (!Number.isFinite(idleSinceMs))
366
+ return false;
367
+ return now - idleSinceMs >= idleTimeoutMs;
368
+ }
369
+ /**
370
+ * Kill workers whose idle TTL has expired. Writes a one-shot shutdown
371
+ * reason sidecar before signalling the supervisor so the supervisor's
372
+ * existing shutdown funnel emits the single terminal `killed` event.
373
+ *
374
+ * Returns the workers that were signalled. Failures are collected — a
375
+ * dead pid or read race is not fatal; the next scan re-evaluates.
376
+ */
377
+ export async function cleanupExpiredIdleWorkers(candidates, idleTimeoutMs, opts = {}) {
378
+ const result = { killed: [], failed: [] };
379
+ if (idleTimeoutMs <= 0)
380
+ return result;
381
+ const now = opts.now ?? Date.now();
382
+ for (const live of candidates) {
383
+ if (!isIdleCleanupEligible(live, idleTimeoutMs, now))
384
+ continue;
385
+ try {
386
+ const project = opts.project ?? currentProjectKey();
387
+ if (live.supervisorPid === undefined ||
388
+ live.supervisorVerified !== true ||
389
+ !pidAlive(live.supervisorPid)) {
390
+ continue;
391
+ }
392
+ const reasonFile = workerFile(live.channel, live.workerId, "shutdown-reason", project);
393
+ fs.writeFileSync(reasonFile, "idle-timeout\n", "utf-8");
394
+ try {
395
+ process.kill(live.supervisorPid, "SIGTERM");
396
+ }
397
+ catch (err) {
398
+ try {
399
+ fs.unlinkSync(reasonFile);
400
+ }
401
+ catch {
402
+ // already gone
403
+ }
404
+ throw err;
405
+ }
406
+ result.killed.push(live);
407
+ }
408
+ catch (err) {
409
+ result.failed.push({
410
+ worker: live,
411
+ error: err instanceof Error ? err.message : String(err),
412
+ });
413
+ }
414
+ }
415
+ return result;
416
+ }
417
+ /**
418
+ * Run the spawn-time guard for a project bucket. Cleans expired idle
419
+ * workers, re-scans, then decides whether the live worker budget has
420
+ * room for one more spawn.
421
+ */
422
+ export async function enforceSpawnBudget(input) {
423
+ const project = input.projectKey ?? currentProjectKey();
424
+ const scanOpts = {
425
+ projectKey: project,
426
+ ...(input.root !== undefined ? { root: input.root } : {}),
427
+ ...(input.isSupervisorProcess !== undefined
428
+ ? { isSupervisorProcess: input.isSupervisorProcess }
429
+ : {}),
430
+ };
431
+ const initial = scanLiveWorkers(scanOpts);
432
+ const cleanup = await cleanupExpiredIdleWorkers(initial, input.policy.idleTimeoutMs, { project, ...(input.now !== undefined ? { now: input.now } : {}) });
433
+ // Re-probe after cleanup so we don't double-count workers that have
434
+ // been signalled but haven't actually torn down their pid files yet.
435
+ // Wait briefly for the SIGTERM to translate into pid-file removal; if
436
+ // a worker is taking its grace period, just exclude killed workers
437
+ // from the count.
438
+ const killedIds = new Set(cleanup.killed.map((w) => `${w.channel}::${w.workerId}`));
439
+ const remaining = scanLiveWorkers(scanOpts).filter((w) => !killedIds.has(`${w.channel}::${w.workerId}`));
440
+ const allowed = input.policy.maxLiveWorkers <= 0 ||
441
+ remaining.length < input.policy.maxLiveWorkers;
442
+ return { cleaned: cleanup.killed, remaining, allowed };
443
+ }
444
+ /** Build a multi-line, actionable overflow error string. */
445
+ export function formatBudgetOverflowError(args) {
446
+ const { projectKey, live, limit } = args;
447
+ const header = `Live worker budget exhausted for project '${projectKey}': ${live.length}/${limit} live worker(s).`;
448
+ const rows = live
449
+ .map((w) => {
450
+ const provider = w.state.provider ?? "?";
451
+ const lifecycle = w.state.lifecycle;
452
+ const activity = w.state.activity;
453
+ const pid = w.supervisorPid ?? "?";
454
+ const verified = w.supervisorVerified === false ? " supervisor=unverified" : "";
455
+ return ` • channel='${w.channel}' worker='${w.workerId}' provider=${provider} lifecycle=${lifecycle} activity=${activity} pid=${pid}${verified}`;
456
+ })
457
+ .join("\n");
458
+ const hint = [
459
+ "Free a slot before spawning, e.g.:",
460
+ ` trellis channel kill <channel> --as <worker>`,
461
+ "Or override per spawn:",
462
+ ` trellis channel spawn ... --max-live-workers ${live.length + 1}`,
463
+ "Or raise the default in .trellis/config.yaml under channel.worker_guard.max_live_workers.",
464
+ ].join("\n");
465
+ return [header, rows, hint].join("\n");
466
+ }
467
+ /**
468
+ * Convenience helper: ensure `channelRoot()` is initialised before scan
469
+ * (otherwise the project dir may not yet exist on first spawn).
470
+ */
471
+ export function ensureRootExists() {
472
+ fs.mkdirSync(channelRoot(), { recursive: true });
473
+ }
474
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../../../src/commands/channel/guard.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import type { Command } from "commander";
1
+ import { type Command } from "commander";
2
2
  export declare function registerChannelCommand(program: Command): void;
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/channel/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6BzC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA01B7D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/channel/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAwB,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAuC/D,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAo4B7D"}
@@ -1,9 +1,11 @@
1
1
  import chalk from "chalk";
2
+ import { InvalidArgumentError } from "commander";
2
3
  import { isProvider, listProviders } from "./adapters/index.js";
3
4
  import { channelContextAdd, channelContextDelete, channelContextList, } from "./context.js";
4
5
  import { createChannel } from "./create.js";
5
6
  import { parseTrace } from "./dev-parse-trace.js";
6
7
  import { channelKill } from "./kill.js";
8
+ import { channelInterrupt } from "./interrupt.js";
7
9
  import { channelList } from "./list.js";
8
10
  import { channelMessages } from "./messages.js";
9
11
  import { channelPrune, channelRm } from "./rm.js";
@@ -16,6 +18,12 @@ import { runSupervisor } from "./supervisor.js";
16
18
  import { channelWait, parseDuration } from "./wait.js";
17
19
  import { parseCsv } from "./store/schema.js";
18
20
  import { parseInboxPolicy } from "@mindfoldhq/trellis-core/channel";
21
+ function parseNonNegativeInteger(value) {
22
+ if (!/^\d+$/.test(value)) {
23
+ throw new InvalidArgumentError(`expected a non-negative integer, got '${value}'`);
24
+ }
25
+ return Number(value);
26
+ }
19
27
  export function registerChannelCommand(program) {
20
28
  const channel = program
21
29
  .command("channel")
@@ -51,8 +59,6 @@ export function registerChannelCommand(program) {
51
59
  .description("Send a message into the channel")
52
60
  .requiredOption("--as <agent>", "agent name sending")
53
61
  .option("--scope <scope>", "channel scope: project | global")
54
- .option("--tag <tag>", "tag (e.g. interrupt / phase_done / question)")
55
- .option("--kind <tag>", "legacy alias for --tag")
56
62
  .option("--to <agents>", "comma-separated target agents (default: broadcast)")
57
63
  .option("--stdin", "read message body from stdin")
58
64
  .option("--text-file <path>", "read message body from file")
@@ -67,8 +73,6 @@ export function registerChannelCommand(program) {
67
73
  stdin: opts.stdin,
68
74
  textFile: opts.textFile,
69
75
  scope: opts.scope,
70
- tag: opts.tag,
71
- kind: opts.kind,
72
76
  to: opts.to,
73
77
  deliveryMode: opts.deliveryMode,
74
78
  });
@@ -86,7 +90,6 @@ export function registerChannelCommand(program) {
86
90
  .option("--timeout <duration>", "max wait (e.g. 30s, 2m, 1h)")
87
91
  .option("--from <agents>", "only wake on events from these agents (CSV)")
88
92
  .option("--kind <kind[,kind...]>", "only wake on these event kinds (CSV, OR semantics)")
89
- .option("--tag <tag>", "only wake on this user tag")
90
93
  .option("--thread <key>", "only wake on this thread key")
91
94
  .option("--action <action>", "only wake on this thread action")
92
95
  .option("--to <target>", "only wake on events targeted to this name (default: own agent)")
@@ -100,7 +103,6 @@ export function registerChannelCommand(program) {
100
103
  timeoutMs: parseDuration(opts.timeout),
101
104
  from: opts.from,
102
105
  kind: opts.kind,
103
- tag: opts.tag,
104
106
  scope: opts.scope,
105
107
  thread: opts.thread,
106
108
  action: opts.action,
@@ -114,6 +116,32 @@ export function registerChannelCommand(program) {
114
116
  process.exit(1);
115
117
  }
116
118
  });
119
+ channel
120
+ .command("interrupt <name>")
121
+ .description("Interrupt a worker turn and send a replacement instruction")
122
+ .requiredOption("--as <agent>", "agent name requesting the interrupt")
123
+ .requiredOption("--to <agent>", "target worker name")
124
+ .option("--scope <scope>", "channel scope: project | global")
125
+ .option("--stdin", "read interrupt message body from stdin")
126
+ .option("--text-file <path>", "read interrupt message body from file")
127
+ .argument("[text]", "inline interrupt message (otherwise use --stdin / --text-file)")
128
+ .action(async (name, text, raw) => {
129
+ const opts = raw;
130
+ try {
131
+ await channelInterrupt(name, {
132
+ as: opts.as,
133
+ to: opts.to,
134
+ text,
135
+ stdin: opts.stdin,
136
+ textFile: opts.textFile,
137
+ scope: opts.scope,
138
+ });
139
+ }
140
+ catch (err) {
141
+ console.error(chalk.red("Error:"), err instanceof Error ? err.message : err);
142
+ process.exit(1);
143
+ }
144
+ });
117
145
  channel
118
146
  .command("spawn <name>")
119
147
  .description("Register a worker (claude/codex) into the channel — the worker stays idle until the first `channel send --to <worker>` arrives")
@@ -130,6 +158,8 @@ export function registerChannelCommand(program) {
130
158
  .option("--jsonl <path>", "parse a Trellis jsonl manifest ({file, reason} per line) and include each referenced file (repeatable)", (val, prev) => [...(prev ?? []), val], [])
131
159
  .option("--by <agent>", "identity recorded as the spawn author (defaults to TRELLIS_CHANNEL_AS env or 'main')")
132
160
  .option("--inbox-policy <policy>", "worker inbox delivery policy: explicitOnly | broadcastAndExplicit (default explicitOnly)")
161
+ .option("--idle-timeout <duration>", "OOM-guard idle-cleanup TTL for this worker (default 5m; 0 disables)")
162
+ .option("--max-live-workers <n>", "spawn-time live-worker budget for this project/scope (default 6; 0 disables)", parseNonNegativeInteger)
133
163
  .action(async (name, raw) => {
134
164
  const opts = raw;
135
165
  if (opts.provider !== undefined && !isProvider(opts.provider)) {
@@ -151,6 +181,8 @@ export function registerChannelCommand(program) {
151
181
  by: opts.by,
152
182
  scope: opts.scope,
153
183
  inboxPolicy: parseInboxPolicy(opts.inboxPolicy),
184
+ idleTimeoutMs: parseDuration(opts.idleTimeout),
185
+ maxLiveWorkers: opts.maxLiveWorkers,
154
186
  });
155
187
  }
156
188
  catch (err) {
@@ -171,7 +203,6 @@ export function registerChannelCommand(program) {
171
203
  .option("--message <text>", "inline prompt text")
172
204
  .option("--message-file <path>", "read prompt body from file")
173
205
  .option("--stdin", "read prompt body from stdin")
174
- .option("--tag <tag>", "user tag (e.g. interrupt / phase_done / question)")
175
206
  .option("--timeout <duration>", "max time to wait for done (e.g. 30s, 5m, 1h; default 5m)")
176
207
  .action(async (name, raw) => {
177
208
  const opts = raw;
@@ -192,7 +223,6 @@ export function registerChannelCommand(program) {
192
223
  message: opts.message,
193
224
  textFile: opts.messageFile,
194
225
  stdin: opts.stdin,
195
- tag: opts.tag,
196
226
  timeoutMs: parseDuration(opts.timeout),
197
227
  });
198
228
  }
@@ -273,7 +303,6 @@ export function registerChannelCommand(program) {
273
303
  .option("--kind <kind>", "filter by event kind (e.g. message, done, killed)")
274
304
  .option("--from <agents>", "filter by author (CSV)")
275
305
  .option("--to <target>", "filter by routing target")
276
- .option("--tag <tag>", "filter by user tag (e.g. interrupt, final_answer)")
277
306
  .option("--thread <key>", "filter by thread key")
278
307
  .option("--action <action>", "filter by thread action")
279
308
  .option("--no-progress", "hide progress events (tool calls, deltas)")
@@ -288,7 +317,6 @@ export function registerChannelCommand(program) {
288
317
  kind: opts.kind,
289
318
  from: opts.from,
290
319
  to: opts.to,
291
- tag: opts.tag,
292
320
  scope: opts.scope,
293
321
  thread: opts.thread,
294
322
  action: opts.action,