@crouton-kit/crouter 0.3.3 → 0.3.11
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 +2 -2
- package/dist/builtin-skills/skills/crouter-development/marketplaces/SKILL.md +1 -1
- package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +3 -3
- package/dist/cli.js +16 -26
- package/dist/commands/__tests__/skill.test.js +24 -28
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.js +585 -0
- package/dist/commands/debug.d.ts +1 -1
- package/dist/commands/debug.js +20 -7
- package/dist/commands/human.js +51 -19
- package/dist/commands/job.d.ts +9 -0
- package/dist/commands/job.js +100 -385
- package/dist/commands/{flow.d.ts → mode.d.ts} +1 -1
- package/dist/commands/mode.js +231 -0
- package/dist/commands/pkg.js +5 -0
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/plan.js +24 -11
- package/dist/commands/skill.js +130 -107
- package/dist/commands/spec.d.ts +1 -1
- package/dist/commands/spec.js +24 -11
- package/dist/commands/sys.js +5 -0
- package/dist/core/__tests__/job.test.js +38 -74
- package/dist/core/__tests__/jobs.test.d.ts +1 -0
- package/dist/core/__tests__/jobs.test.js +98 -0
- package/dist/core/__tests__/resolver.test.d.ts +1 -0
- package/dist/core/__tests__/resolver.test.js +181 -0
- package/dist/core/__tests__/spawn.test.d.ts +1 -0
- package/dist/core/__tests__/spawn.test.js +138 -0
- package/dist/core/__tests__/subagents.test.d.ts +1 -0
- package/dist/core/__tests__/subagents.test.js +75 -0
- package/dist/core/__tests__/unknown-path.test.d.ts +1 -0
- package/dist/core/__tests__/unknown-path.test.js +52 -0
- package/dist/core/bootstrap.d.ts +2 -0
- package/dist/core/bootstrap.js +66 -0
- package/dist/core/command.d.ts +58 -2
- package/dist/core/command.js +62 -14
- package/dist/core/config.js +20 -2
- package/dist/core/frontmatter.d.ts +10 -0
- package/dist/core/frontmatter.js +24 -9
- package/dist/core/help.d.ts +39 -8
- package/dist/core/help.js +64 -32
- package/dist/core/jobs.d.ts +33 -13
- package/dist/core/jobs.js +259 -47
- package/dist/core/resolver.d.ts +1 -2
- package/dist/core/resolver.js +111 -47
- package/dist/core/spawn.d.ts +150 -10
- package/dist/core/spawn.js +493 -41
- package/dist/core/subagents.d.ts +18 -0
- package/dist/core/subagents.js +163 -0
- package/dist/prompts/agent.d.ts +12 -3
- package/dist/prompts/agent.js +51 -18
- package/dist/prompts/debug.js +14 -7
- package/dist/prompts/skill.js +16 -16
- package/dist/types.d.ts +22 -1
- package/dist/types.js +5 -2
- package/package.json +2 -2
- package/dist/commands/flow.js +0 -24
package/dist/commands/debug.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// `crtr
|
|
1
|
+
// `crtr mode debug` leaf — reproduce-first root-cause workflow.
|
|
2
2
|
//
|
|
3
3
|
// Running it spawns a reproduction-only agent in a sibling tmux pane (the same
|
|
4
|
-
// spawn + job-handle shape as `crtr
|
|
4
|
+
// spawn + job-handle shape as `crtr agent new`) and returns a job handle
|
|
5
5
|
// plus a follow_up. The orchestrator-side methodology lives in FLOW_DEBUG_GUIDE
|
|
6
|
-
// (the leaf's help.guide), loaded via `crtr
|
|
6
|
+
// (the leaf's help.guide), loaded via `crtr mode debug -h` after the repro
|
|
7
7
|
// agent returns. Methodology stays in the CLI guide field, like PLAN_NEW_GUIDE;
|
|
8
8
|
// no builtin skill.
|
|
9
9
|
export const FLOW_DEBUG_GUIDE = `## Debug workflow — reproduce first
|
|
10
10
|
|
|
11
|
-
Audience: the agent that ran \`crtr
|
|
11
|
+
Audience: the agent that ran \`crtr mode debug\`. A reproduction agent is
|
|
12
12
|
already spawned in a sibling pane. It writes ONE failing integration test and
|
|
13
13
|
never fixes anything. You do everything after: gate on the repro, root-cause,
|
|
14
14
|
fix, verify against that same test.
|
|
@@ -82,7 +82,7 @@ function assertTmux() {
|
|
|
82
82
|
if (!isInTmux()) {
|
|
83
83
|
throw new InputError({
|
|
84
84
|
error: 'not_in_tmux',
|
|
85
|
-
message: 'crtr
|
|
85
|
+
message: 'crtr mode debug requires tmux (TMUX env var not set).',
|
|
86
86
|
next: 'Run inside a tmux session.',
|
|
87
87
|
});
|
|
88
88
|
}
|
|
@@ -91,7 +91,7 @@ export function registerDebug() {
|
|
|
91
91
|
return defineLeaf({
|
|
92
92
|
name: 'debug',
|
|
93
93
|
help: {
|
|
94
|
-
name: '
|
|
94
|
+
name: 'mode debug',
|
|
95
95
|
summary: 'reproduce-first root-cause workflow: spawns a reproduction agent, then you root-cause and fix',
|
|
96
96
|
guide: FLOW_DEBUG_GUIDE,
|
|
97
97
|
params: [
|
|
@@ -137,6 +137,19 @@ export function registerDebug() {
|
|
|
137
137
|
'On completion, result writes atomically to result.json.',
|
|
138
138
|
],
|
|
139
139
|
},
|
|
140
|
+
slash: {
|
|
141
|
+
name: 'debug',
|
|
142
|
+
description: 'Debug mode — reproduce-first root-cause investigation.',
|
|
143
|
+
argumentHint: '<symptom or failing test>',
|
|
144
|
+
body: `You are entering **debug mode**: a reproduce-first, root-cause investigation.
|
|
145
|
+
|
|
146
|
+
1. Run \`crtr mode debug -h\` to load the debugging workflow and output schema.
|
|
147
|
+
2. Follow it — reproduce the failure first, then isolate the root cause before proposing a fix.
|
|
148
|
+
|
|
149
|
+
The issue: $ARGUMENTS
|
|
150
|
+
|
|
151
|
+
If no issue was given, ask the user for the symptom or failing test before starting.`,
|
|
152
|
+
},
|
|
140
153
|
run: async (input) => {
|
|
141
154
|
assertTmux();
|
|
142
155
|
const stepsToReproduce = input['steps_to_reproduce'];
|
|
@@ -172,7 +185,7 @@ export function registerDebug() {
|
|
|
172
185
|
});
|
|
173
186
|
return {
|
|
174
187
|
job_id: jobId,
|
|
175
|
-
follow_up: `Await the reproduction agent: crtr job read result ${jobId} --wait. Then run \`crtr
|
|
188
|
+
follow_up: `Await the reproduction agent: crtr job read result ${jobId} --wait. Then run \`crtr mode debug -h\` and follow the workflow from Phase 1.`,
|
|
176
189
|
};
|
|
177
190
|
},
|
|
178
191
|
});
|
package/dist/commands/human.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// run.json, never stdin.
|
|
14
14
|
import { defineBranch, defineLeaf } from '../core/command.js';
|
|
15
15
|
import { InputError } from '../core/io.js';
|
|
16
|
-
import { createJob, writeResult, recordJobPane, appendEvent } from '../core/jobs.js';
|
|
16
|
+
import { createJob, writeResult, recordJobPane, appendEvent, readResult as jobsReadResult } from '../core/jobs.js';
|
|
17
17
|
import { spawnAndDetach, shellQuote, isInTmux, countPanesInCurrentWindow } from '../core/spawn.js';
|
|
18
18
|
import { interactionsRoot, interactionDir } from '../core/artifact.js';
|
|
19
19
|
import { paginate } from '../core/pagination.js';
|
|
@@ -45,9 +45,10 @@ function followUpDrain(jobId) {
|
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Spawn the detached `_run` pane for a job-backed kickoff, record the pane for
|
|
48
|
-
* cancellation, log the start, and return the
|
|
49
|
-
* to the inbox-drain follow_up (job still
|
|
50
|
-
* fails — kickoffs are intentionally
|
|
48
|
+
* cancellation, log the start, and return whether the pane spawned plus the
|
|
49
|
+
* appropriate follow_up. Degrades to the inbox-drain follow_up (job still
|
|
50
|
+
* created) when not in tmux / spawn fails — kickoffs are intentionally
|
|
51
|
+
* non-fatal off-tmux.
|
|
51
52
|
*/
|
|
52
53
|
function spawnHumanJob(jobId, idir, cwd) {
|
|
53
54
|
const spawn = spawnAndDetach({
|
|
@@ -59,7 +60,7 @@ function spawnHumanJob(jobId, idir, cwd) {
|
|
|
59
60
|
failGuard: true,
|
|
60
61
|
});
|
|
61
62
|
if (spawn.status !== 'spawned') {
|
|
62
|
-
return followUpDrain(jobId);
|
|
63
|
+
return { spawned: false, follow_up: followUpDrain(jobId) };
|
|
63
64
|
}
|
|
64
65
|
if (spawn.paneId !== undefined)
|
|
65
66
|
recordJobPane(jobId, spawn.paneId);
|
|
@@ -69,7 +70,7 @@ function spawnHumanJob(jobId, idir, cwd) {
|
|
|
69
70
|
event: 'worker_started',
|
|
70
71
|
message: `human pane ${paneLabel} spawned`,
|
|
71
72
|
});
|
|
72
|
-
return followUpResult(jobId);
|
|
73
|
+
return { spawned: true, follow_up: followUpResult(jobId) };
|
|
73
74
|
}
|
|
74
75
|
// ---------------------------------------------------------------------------
|
|
75
76
|
// ask
|
|
@@ -78,7 +79,8 @@ const humanAsk = defineLeaf({
|
|
|
78
79
|
name: 'ask',
|
|
79
80
|
help: {
|
|
80
81
|
name: 'human ask',
|
|
81
|
-
summary: 'put a humanloop decision deck in front of a person; returns a job handle immediately.
|
|
82
|
+
summary: 'put a humanloop decision deck in front of a person; returns a job handle immediately. This is the default, expected channel for posing ANY question or decision to the user — reach for it instead of writing the question as prose in your reply.',
|
|
83
|
+
guide: 'Use this for quick, open-ended, and nuanced asks alike — not just "formal" multiple-choice. Set `allowFreetext: true` (with `freetextLabel`) when the answer is open-ended; offer a few `options` as starting points even for judgment calls. The kickoff is instant and NEVER blocks — "never block on the result" refers only to not busy-waiting on the job; the human answering on their own time is not a reason to avoid asking or to fall back to inline prose. The deck body is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid) — see `termrender doc -h` for the directive set before authoring one.',
|
|
82
84
|
params: [
|
|
83
85
|
{ kind: 'context-file', name: 'deck', required: true, constraint: 'Contains a humanloop deck. Validated before any job is created.', shape: DECK_SCHEMA_HINT },
|
|
84
86
|
{ kind: 'flag', name: 'wait', type: 'bool', required: false, constraint: 'Accepted for symmetry with the job contract; the kickoff never blocks.' },
|
|
@@ -114,7 +116,7 @@ const humanAsk = defineLeaf({
|
|
|
114
116
|
atomicWriteJson(deckPath(idir), deck);
|
|
115
117
|
const rc = { mode: 'ask', job_id: jobId };
|
|
116
118
|
atomicWriteJson(join(idir, 'run.json'), rc);
|
|
117
|
-
const follow_up = spawnHumanJob(jobId, idir, cwd);
|
|
119
|
+
const { follow_up } = spawnHumanJob(jobId, idir, cwd);
|
|
118
120
|
return { job_id: jobId, dir: idir, follow_up };
|
|
119
121
|
},
|
|
120
122
|
});
|
|
@@ -125,7 +127,8 @@ const humanApprove = defineLeaf({
|
|
|
125
127
|
name: 'approve',
|
|
126
128
|
help: {
|
|
127
129
|
name: 'human approve',
|
|
128
|
-
summary: 'a Yes/No approval gate; returns a job handle immediately.
|
|
130
|
+
summary: 'a Yes/No approval gate; returns a job handle immediately. The standard way to gate a handoff on human sign-off. Kickoff never blocks — peek at the result later rather than busy-waiting; the human answering on their own time is not a reason to skip the gate.',
|
|
131
|
+
guide: 'The body is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid) — see `termrender doc -h` for the directive set before authoring one.',
|
|
129
132
|
params: [
|
|
130
133
|
{ kind: 'positional', name: 'title', type: 'string', required: true, constraint: 'The question shown to the human.' },
|
|
131
134
|
{ kind: 'flag', name: 'subtitle', type: 'string', required: false, constraint: 'Optional one-line context.' },
|
|
@@ -157,7 +160,7 @@ const humanApprove = defineLeaf({
|
|
|
157
160
|
atomicWriteJson(deckPath(idir), deck);
|
|
158
161
|
const rc = { mode: 'approve', job_id: jobId, approve_iid: 'approve' };
|
|
159
162
|
atomicWriteJson(join(idir, 'run.json'), rc);
|
|
160
|
-
const follow_up = spawnHumanJob(jobId, idir, cwd);
|
|
163
|
+
const { follow_up } = spawnHumanJob(jobId, idir, cwd);
|
|
161
164
|
return { job_id: jobId, dir: idir, follow_up };
|
|
162
165
|
},
|
|
163
166
|
});
|
|
@@ -168,20 +171,25 @@ const humanReview = defineLeaf({
|
|
|
168
171
|
name: 'review',
|
|
169
172
|
help: {
|
|
170
173
|
name: 'human review',
|
|
171
|
-
summary: 'open a .md in a read-only review editor for anchored comments;
|
|
174
|
+
summary: 'open a .md in a read-only review editor for anchored comments; BLOCKS until the human submits the review. Humans respond on human time (often >10 min) — if you want to keep working, background this call (your harness will notify you when it finishes).',
|
|
175
|
+
guide: 'Unlike ask/approve, this call does not return a job handle and walk away — it blocks until the human finishes reviewing and submits (or closes the pane). Run it in the background when you have other work to do; the harness surfaces the result on completion. The returned `result` is the humanloop FeedbackResult (anchored comments). The .md you point at is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid) — see `termrender doc -h` for the directive set before authoring one.',
|
|
172
176
|
params: [
|
|
173
177
|
{ kind: 'positional', name: 'file', type: 'path', required: true, constraint: 'Absolute path to an existing .md file.' },
|
|
174
178
|
{ kind: 'flag', name: 'output', type: 'path', required: false, constraint: 'Where the FeedbackResult JSON is written. Default: <dir>/feedback.json.' },
|
|
175
179
|
],
|
|
176
180
|
output: [
|
|
177
|
-
{ name: 'job_id', type: 'string', required: true, constraint: '
|
|
181
|
+
{ name: 'job_id', type: 'string', required: true, constraint: 'The kind:"human" job backing this review. Cancel with `crtr job cancel`.' },
|
|
178
182
|
{ name: 'output', type: 'string', required: true, constraint: 'Path the FeedbackResult JSON is autosaved to.' },
|
|
179
|
-
{ name: '
|
|
183
|
+
{ name: 'status', type: 'string', required: true, constraint: 'Terminal state once the call unblocks: done (submitted), failed, canceled, or closed (pane went away before submit).' },
|
|
184
|
+
{ name: 'result', type: 'object', required: false, constraint: 'The humanloop FeedbackResult (anchored comments). Present when status is done.' },
|
|
185
|
+
{ name: 'reason', type: 'string', required: false, constraint: 'Short explanation when status is failed or closed.' },
|
|
186
|
+
{ name: 'follow_up', type: 'string', required: false, constraint: 'Present only when off-tmux: a human must drain the review via `crtr human inbox`, then read the result.' },
|
|
180
187
|
],
|
|
181
188
|
outputKind: 'object',
|
|
182
189
|
effects: [
|
|
183
190
|
'Creates a kind:"human" job and writes run.json to the interaction dir.',
|
|
184
191
|
'Spawns a read-only nvim/vim review session in a detached tmux pane (when in tmux).',
|
|
192
|
+
'Blocks the calling process until the human submits, the pane closes, or the job is canceled.',
|
|
185
193
|
],
|
|
186
194
|
},
|
|
187
195
|
run: async (input) => {
|
|
@@ -211,8 +219,22 @@ const humanReview = defineLeaf({
|
|
|
211
219
|
const output = outputArg !== undefined ? outputArg : join(idir, 'feedback.json');
|
|
212
220
|
const rc = { mode: 'review', job_id: jobId, file: abs, output };
|
|
213
221
|
atomicWriteJson(join(idir, 'run.json'), rc);
|
|
214
|
-
const follow_up = spawnHumanJob(jobId, idir, cwd);
|
|
215
|
-
|
|
222
|
+
const { spawned, follow_up } = spawnHumanJob(jobId, idir, cwd);
|
|
223
|
+
// Off-tmux: no pane to block on — fall back to the non-blocking handle the
|
|
224
|
+
// way ask/approve do, so the review can still be drained from the inbox.
|
|
225
|
+
if (!spawned) {
|
|
226
|
+
return { job_id: jobId, output, status: 'live', follow_up };
|
|
227
|
+
}
|
|
228
|
+
// In tmux: block until the human submits, the pane closes, or the job is
|
|
229
|
+
// canceled. Infinity = no timeout (the human owns the clock); the poll in
|
|
230
|
+
// readResult still reaps a dead pane, so this never hangs on a closed pane.
|
|
231
|
+
const r = await jobsReadResult(jobId, { waitMs: Infinity });
|
|
232
|
+
const out = { job_id: jobId, output, status: r.status };
|
|
233
|
+
if (r.result !== undefined)
|
|
234
|
+
out['result'] = r.result;
|
|
235
|
+
if (r.reason !== undefined)
|
|
236
|
+
out['reason'] = r.reason;
|
|
237
|
+
return out;
|
|
216
238
|
},
|
|
217
239
|
});
|
|
218
240
|
// ---------------------------------------------------------------------------
|
|
@@ -223,6 +245,7 @@ const humanNotify = defineLeaf({
|
|
|
223
245
|
help: {
|
|
224
246
|
name: 'human notify',
|
|
225
247
|
summary: 'show a fire-and-forget acknowledgement; creates no job',
|
|
248
|
+
guide: 'The body is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid) — see `termrender doc -h` for the directive set before authoring one.',
|
|
226
249
|
params: [
|
|
227
250
|
{ kind: 'positional', name: 'title', type: 'string', required: true, constraint: 'The notification headline.' },
|
|
228
251
|
{ kind: 'flag', name: 'body', type: 'string', required: false, constraint: 'Optional markdown body.' },
|
|
@@ -270,9 +293,9 @@ const humanShow = defineLeaf({
|
|
|
270
293
|
help: {
|
|
271
294
|
name: 'human show',
|
|
272
295
|
summary: 'put a file live on screen in a tmux pane via humanloop display',
|
|
296
|
+
guide: 'The pane always watches the file and live-updates on every save — a displayed doc is a live view by definition, so point it at a file something keeps rewriting (a status board, a running summary) and it stays current. The file is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid) — see `termrender doc -h` for the directive set before authoring one.',
|
|
273
297
|
params: [
|
|
274
298
|
{ kind: 'positional', name: 'path', type: 'path', required: true, constraint: 'Path to the file to render.' },
|
|
275
|
-
{ kind: 'flag', name: 'watch', type: 'bool', required: false, constraint: 'When present, live-update the pane on edits. Default off.' },
|
|
276
299
|
{ kind: 'flag', name: 'window', type: 'enum', choices: ['auto', 'split', 'new'], required: false, default: 'auto', constraint: 'Placement. Default auto.' },
|
|
277
300
|
],
|
|
278
301
|
output: [
|
|
@@ -284,14 +307,13 @@ const humanShow = defineLeaf({
|
|
|
284
307
|
},
|
|
285
308
|
run: async (input) => {
|
|
286
309
|
const path = input['path'];
|
|
287
|
-
const watch = input['watch'] === true;
|
|
288
310
|
const windowArg = input['window'];
|
|
289
311
|
const window = windowArg !== undefined ? windowArg : 'auto';
|
|
290
312
|
// `human show` must never fail the caller: any display error degrades to
|
|
291
313
|
// {pane_id:null, reason} with exit 0 (matches humanloop display semantics).
|
|
292
314
|
let paneId;
|
|
293
315
|
try {
|
|
294
|
-
const r = display(path, {
|
|
316
|
+
const r = display(path, { window, maxPanes: resolveMaxPanes() });
|
|
295
317
|
paneId = r.paneId;
|
|
296
318
|
}
|
|
297
319
|
catch {
|
|
@@ -415,8 +437,13 @@ const humanRun = defineLeaf({
|
|
|
415
437
|
// notify: no job — nothing to write
|
|
416
438
|
}
|
|
417
439
|
else if (rc.mode === 'review') {
|
|
440
|
+
// The _run worker is already its own dedicated tmux pane with a TTY, so
|
|
441
|
+
// run nvim directly in it (noTmux) instead of letting launchReview
|
|
442
|
+
// split off a SECOND pane and sit polling. This matches how ask/approve
|
|
443
|
+
// render in-place and avoids the redundant side pane.
|
|
418
444
|
const res = await launchReview(rc.file, {
|
|
419
445
|
output: rc.output,
|
|
446
|
+
noTmux: true,
|
|
420
447
|
});
|
|
421
448
|
writeResult(rc.job_id, res, 'done');
|
|
422
449
|
}
|
|
@@ -434,10 +461,15 @@ const humanRun = defineLeaf({
|
|
|
434
461
|
export function registerHuman() {
|
|
435
462
|
return defineBranch({
|
|
436
463
|
name: 'human',
|
|
464
|
+
rootEntry: {
|
|
465
|
+
concept: 'human-in-the-loop decisions, document review, and live display: ask puts a structured choice to a person, approve gates a handoff on a Yes/No sign-off, review collects anchored comments on a plan or spec, notify informs without blocking, show puts a file live on screen',
|
|
466
|
+
desc: 'ask, approve, review, notify, show, inbox, list',
|
|
467
|
+
useWhen: 'you have a question for the user or want their feedback — always reach for human instead of guessing or assuming when a person can decide',
|
|
468
|
+
},
|
|
437
469
|
help: {
|
|
438
470
|
name: 'human',
|
|
439
471
|
summary: 'human-in-the-loop decisions, document review, and live display',
|
|
440
|
-
model: "
|
|
472
|
+
model: "Reach for human whenever you have a question for the user or want their feedback — never guess or assume when a person can decide. ask puts a structured choice in front of them; approve gates a handoff on a Yes/No sign-off; review collects anchored comments on a plan or spec; notify informs without blocking; show puts a file live on screen. Every body and displayed file is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid) — see `termrender doc -h` for the directive set before authoring one. ask/approve/review are the DEFAULT channel for questions, sign-offs, and feedback — reach for them even for quick or open-ended asks (use `allowFreetext`), and don't substitute prose in your reply. ask and approve are kickoffs: they create kind:'human' jobs and return instantly, never blocking — peek later with `crtr job read result|status` (no `wait`). review is different: it BLOCKS until the human submits, so background the call if you want to keep working (your harness notifies you when it finishes). 'Humans respond on human time' describes response latency only — it is never a reason to avoid asking. Cancel with `crtr job cancel`. notify/show create no job.",
|
|
441
473
|
children: [
|
|
442
474
|
{ name: 'ask', desc: 'put a decision deck to a person', useWhen: 'a structured choice needs a human' },
|
|
443
475
|
{ name: 'approve', desc: 'a Yes/No approval gate', useWhen: 'gating a handoff on human sign-off' },
|
package/dist/commands/job.d.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
import type { BranchDef } from '../core/command.js';
|
|
2
|
+
/** Count of jobs currently in the live state, or null when listing fails.
|
|
3
|
+
* Backs the always-on "Workers running" signal on root -h so an agent never
|
|
4
|
+
* forgets it has in-flight workers to collect. */
|
|
5
|
+
export declare function liveJobCount(): number | null;
|
|
6
|
+
/** The job subtree's root-level dynamic block. A bounded aggregate (running
|
|
7
|
+
* count + how to collect), never an enumeration: live jobs are volatile and
|
|
8
|
+
* unbounded, so listing them in root -h would balloon (cli-design rule 15).
|
|
9
|
+
* Omitted when nothing is running. */
|
|
10
|
+
export declare function buildJobRootBlock(): string | null;
|
|
2
11
|
export declare function registerJob(): BranchDef;
|