@crouton-kit/crouter 0.3.8 → 0.3.12
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/bin/crtrd +2 -0
- package/dist/builtin-personas/design/base.md +9 -0
- package/dist/builtin-personas/design/orchestrator.md +10 -0
- package/dist/builtin-personas/developer/base.md +9 -0
- package/dist/builtin-personas/developer/orchestrator.md +12 -0
- package/dist/builtin-personas/explore/base.md +9 -0
- package/dist/builtin-personas/explore/orchestrator.md +9 -0
- package/dist/builtin-personas/general/base.md +5 -0
- package/dist/builtin-personas/general/orchestrator.md +7 -0
- package/dist/builtin-personas/orchestration-kernel.md +71 -0
- package/dist/builtin-personas/plan/base.md +7 -0
- package/dist/builtin-personas/plan/orchestrator.md +12 -0
- package/dist/builtin-personas/review/base.md +7 -0
- package/dist/builtin-personas/review/orchestrator.md +9 -0
- package/dist/builtin-personas/runtime-base.md +39 -0
- package/dist/builtin-personas/spec/base.md +7 -0
- package/dist/builtin-personas/spec/orchestrator.md +10 -0
- package/dist/builtin-skills/skills/design/SKILL.md +51 -0
- package/dist/builtin-skills/skills/development/SKILL.md +109 -0
- package/dist/builtin-skills/skills/planning/SKILL.md +59 -0
- package/dist/builtin-skills/skills/spec/SKILL.md +83 -0
- package/dist/cli.js +25 -27
- package/dist/commands/{job.d.ts → attention.d.ts} +1 -1
- package/dist/commands/attention.js +152 -0
- package/dist/commands/canvas.d.ts +2 -0
- package/dist/commands/canvas.js +35 -0
- package/dist/commands/{agent.d.ts → daemon.d.ts} +1 -1
- package/dist/commands/daemon.js +111 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +65 -0
- package/dist/commands/human/prompts.d.ts +5 -0
- package/dist/commands/human/prompts.js +269 -0
- package/dist/commands/human/queue.d.ts +3 -0
- package/dist/commands/human/queue.js +133 -0
- package/dist/commands/human/shared.d.ts +43 -0
- package/dist/commands/human/shared.js +107 -0
- package/dist/commands/human.js +15 -427
- package/dist/commands/node.d.ts +2 -0
- package/dist/commands/node.js +354 -0
- package/dist/commands/pkg/market-inspect.d.ts +1 -0
- package/dist/commands/pkg/market-inspect.js +157 -0
- package/dist/commands/pkg/market-manage.d.ts +1 -0
- package/dist/commands/pkg/market-manage.js +316 -0
- package/dist/commands/pkg/market.d.ts +1 -0
- package/dist/commands/pkg/market.js +16 -0
- package/dist/commands/pkg/plugin-inspect.d.ts +1 -0
- package/dist/commands/pkg/plugin-inspect.js +142 -0
- package/dist/commands/pkg/plugin-manage.d.ts +1 -0
- package/dist/commands/pkg/plugin-manage.js +294 -0
- package/dist/commands/pkg/plugin.d.ts +1 -0
- package/dist/commands/pkg/plugin.js +16 -0
- package/dist/commands/pkg/shared.d.ts +5 -0
- package/dist/commands/pkg/shared.js +61 -0
- package/dist/commands/pkg.js +8 -1004
- package/dist/commands/push.d.ts +3 -0
- package/dist/commands/push.js +159 -0
- package/dist/commands/revive.d.ts +2 -0
- package/dist/commands/revive.js +64 -0
- package/dist/commands/skill/author.d.ts +3 -0
- package/dist/commands/skill/author.js +147 -0
- package/dist/commands/skill/find.d.ts +4 -0
- package/dist/commands/skill/find.js +254 -0
- package/dist/commands/skill/read.d.ts +1 -0
- package/dist/commands/skill/read.js +89 -0
- package/dist/commands/skill/shared.d.ts +19 -0
- package/dist/commands/skill/shared.js +207 -0
- package/dist/commands/skill/state.d.ts +3 -0
- package/dist/commands/skill/state.js +69 -0
- package/dist/commands/skill.js +12 -681
- package/dist/commands/sys/config.d.ts +1 -0
- package/dist/commands/sys/config.js +186 -0
- package/dist/commands/sys/doctor.d.ts +1 -0
- package/dist/commands/sys/doctor.js +369 -0
- package/dist/commands/sys/shared.d.ts +3 -0
- package/dist/commands/sys/shared.js +24 -0
- package/dist/commands/sys/update.d.ts +2 -0
- package/dist/commands/sys/update.js +114 -0
- package/dist/commands/sys.js +9 -694
- package/dist/core/__tests__/argv-parser.test.js +19 -1
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +100 -0
- package/dist/core/__tests__/canvas.test.js +154 -0
- package/dist/core/__tests__/reset.test.js +105 -0
- package/dist/core/__tests__/resolver.test.js +69 -1
- 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/canvas/attention.d.ts +24 -0
- package/dist/core/canvas/attention.js +94 -0
- package/dist/core/canvas/canvas.d.ts +40 -0
- package/dist/core/canvas/canvas.js +210 -0
- package/dist/core/canvas/db.d.ts +7 -0
- package/dist/core/canvas/db.js +61 -0
- package/dist/core/canvas/index.d.ts +4 -0
- package/dist/core/canvas/index.js +6 -0
- package/dist/core/canvas/paths.d.ts +16 -0
- package/dist/core/canvas/paths.js +62 -0
- package/dist/core/canvas/render.d.ts +30 -0
- package/dist/core/canvas/render.js +186 -0
- package/dist/core/canvas/types.d.ts +87 -0
- package/dist/core/canvas/types.js +8 -0
- package/dist/core/command.d.ts +63 -2
- package/dist/core/command.js +97 -24
- package/dist/core/feed/feed.d.ts +43 -0
- package/dist/core/feed/feed.js +116 -0
- package/dist/core/feed/inbox.d.ts +50 -0
- package/dist/core/feed/inbox.js +124 -0
- 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 +69 -35
- package/dist/core/io.d.ts +15 -1
- package/dist/core/io.js +56 -6
- package/dist/core/personas/index.d.ts +12 -0
- package/dist/core/personas/index.js +10 -0
- package/dist/core/personas/loader.d.ts +44 -0
- package/dist/core/personas/loader.js +157 -0
- package/dist/core/personas/resolve.d.ts +36 -0
- package/dist/core/personas/resolve.js +110 -0
- package/dist/core/render.d.ts +11 -0
- package/dist/core/render.js +126 -0
- package/dist/core/resolver.d.ts +10 -0
- package/dist/core/resolver.js +160 -2
- package/dist/core/runtime/front-door.d.ts +10 -0
- package/dist/core/runtime/front-door.js +97 -0
- package/dist/core/runtime/kickoff.d.ts +23 -0
- package/dist/core/runtime/kickoff.js +134 -0
- package/dist/core/runtime/launch.d.ts +34 -0
- package/dist/core/runtime/launch.js +85 -0
- package/dist/core/runtime/nodes.d.ts +38 -0
- package/dist/core/runtime/nodes.js +95 -0
- package/dist/core/runtime/presence.d.ts +38 -0
- package/dist/core/runtime/presence.js +152 -0
- package/dist/core/runtime/promote.d.ts +30 -0
- package/dist/core/runtime/promote.js +105 -0
- package/dist/core/runtime/reset.d.ts +13 -0
- package/dist/core/runtime/reset.js +97 -0
- package/dist/core/runtime/revive.d.ts +26 -0
- package/dist/core/runtime/revive.js +89 -0
- package/dist/core/runtime/roadmap.d.ts +12 -0
- package/dist/core/runtime/roadmap.js +52 -0
- package/dist/core/runtime/spawn.d.ts +33 -0
- package/dist/core/runtime/spawn.js +118 -0
- package/dist/core/runtime/stop-guard.d.ts +18 -0
- package/dist/core/runtime/stop-guard.js +33 -0
- package/dist/core/runtime/tmux.d.ts +88 -0
- package/dist/core/runtime/tmux.js +198 -0
- package/dist/core/spawn.d.ts +17 -80
- package/dist/core/spawn.js +15 -219
- package/dist/daemon/crtrd-cli.d.ts +1 -0
- package/dist/daemon/crtrd-cli.js +4 -0
- package/dist/daemon/crtrd.d.ts +20 -0
- package/dist/daemon/crtrd.js +200 -0
- package/dist/daemon/manage.d.ts +17 -0
- package/dist/daemon/manage.js +57 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +16 -0
- package/dist/pi-extensions/canvas-inbox-watcher.js +229 -0
- package/dist/pi-extensions/canvas-nav.d.ts +32 -0
- package/dist/pi-extensions/canvas-nav.js +536 -0
- package/dist/pi-extensions/canvas-stophook.d.ts +17 -0
- package/dist/pi-extensions/canvas-stophook.js +373 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +3 -0
- package/package.json +6 -5
- package/dist/commands/agent.js +0 -384
- package/dist/commands/debug.d.ts +0 -3
- package/dist/commands/debug.js +0 -179
- package/dist/commands/job.js +0 -344
- package/dist/commands/plan.d.ts +0 -4
- package/dist/commands/plan.js +0 -309
- package/dist/commands/spec.d.ts +0 -3
- package/dist/commands/spec.js +0 -286
- package/dist/core/__tests__/flow-leaves.test.js +0 -248
- package/dist/core/__tests__/job.test.js +0 -310
- package/dist/core/__tests__/jobs.test.js +0 -66
- package/dist/core/jobs.d.ts +0 -101
- package/dist/core/jobs.js +0 -462
- package/dist/prompts/agent.d.ts +0 -18
- package/dist/prompts/agent.js +0 -153
- package/dist/prompts/debug.d.ts +0 -8
- package/dist/prompts/debug.js +0 -44
- /package/dist/core/__tests__/{flow-leaves.test.d.ts → canvas-inbox-watcher.test.d.ts} +0 -0
- /package/dist/core/__tests__/{job.test.d.ts → canvas.test.d.ts} +0 -0
- /package/dist/core/__tests__/{jobs.test.d.ts → reset.test.d.ts} +0 -0
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
// Tests for the job subtree (monitoring) and agent new * (spawning) argv
|
|
2
|
-
// migration. Exercises parseArgv against each leaf's param schema directly —
|
|
3
|
-
// no subprocess, no tmux, no filesystem side-effects from the handler.
|
|
4
|
-
//
|
|
5
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/job.test.ts
|
|
6
|
-
import { test, describe } from 'node:test';
|
|
7
|
-
import assert from 'node:assert/strict';
|
|
8
|
-
import { parseArgv } from '../command.js';
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Param schemas extracted from each leaf (mirrors job.ts exactly)
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
const startPromptParams = [
|
|
13
|
-
{ kind: 'stdin', name: 'prompt', required: true, constraint: '' },
|
|
14
|
-
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
15
|
-
];
|
|
16
|
-
const startForkParams = [
|
|
17
|
-
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
18
|
-
];
|
|
19
|
-
const startPlannerParams = [
|
|
20
|
-
{ kind: 'positional', name: 'spec_path', type: 'path', required: true, constraint: '' },
|
|
21
|
-
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
22
|
-
];
|
|
23
|
-
const startImplementerParams = [
|
|
24
|
-
{ kind: 'positional', name: 'plan_path', type: 'path', required: true, constraint: '' },
|
|
25
|
-
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
26
|
-
];
|
|
27
|
-
const startReviewerParams = [
|
|
28
|
-
{ kind: 'positional', name: 'artifact_path', type: 'path', required: true, constraint: '' },
|
|
29
|
-
{ kind: 'flag', name: 'kind', type: 'enum', choices: ['plan', 'spec'], required: true, constraint: '' },
|
|
30
|
-
{ kind: 'flag', name: 'spec-path', type: 'path', required: false, constraint: '' },
|
|
31
|
-
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
32
|
-
];
|
|
33
|
-
const readListParams = [
|
|
34
|
-
{ kind: 'flag', name: 'limit', type: 'int', required: false, default: 20, constraint: '' },
|
|
35
|
-
{ kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: '' },
|
|
36
|
-
];
|
|
37
|
-
const readStatusParams = [
|
|
38
|
-
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
39
|
-
];
|
|
40
|
-
const readResultParams = [
|
|
41
|
-
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
42
|
-
{ kind: 'flag', name: 'wait', type: 'bool', required: false, constraint: '' },
|
|
43
|
-
];
|
|
44
|
-
const readLogsParams = [
|
|
45
|
-
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
46
|
-
{ kind: 'flag', name: 'since', type: 'string', required: false, constraint: '' },
|
|
47
|
-
{ kind: 'flag', name: 'until', type: 'string', required: false, constraint: '' },
|
|
48
|
-
{ kind: 'flag', name: 'level', type: 'enum', choices: ['debug', 'info', 'warn', 'error'], required: false, default: 'info', constraint: '' },
|
|
49
|
-
{ kind: 'flag', name: 'follow', type: 'bool', required: false, constraint: '' },
|
|
50
|
-
];
|
|
51
|
-
// NOTE: the real leaf also declares a `stdin` param for `body`. We omit it
|
|
52
|
-
// from the test schema because parseArgv reads stdin to EOF whenever a stdin
|
|
53
|
-
// param is declared — and under `node --test`, stdin is piped with no EOF, so
|
|
54
|
-
// the call hangs forever. The body-required-on-status=done check lives in the
|
|
55
|
-
// leaf's `run` handler, not in parseArgv, so the schema tests below cover
|
|
56
|
-
// everything parseArgv can see without needing stdin.
|
|
57
|
-
const submitParams = [
|
|
58
|
-
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
59
|
-
{ kind: 'flag', name: 'status', type: 'enum', choices: ['done', 'failed'], required: false, default: 'done', constraint: '' },
|
|
60
|
-
{ kind: 'flag', name: 'reason', type: 'string', required: false, constraint: '' },
|
|
61
|
-
{ kind: 'flag', name: 'kill-pane', type: 'bool', required: false, constraint: '' },
|
|
62
|
-
];
|
|
63
|
-
const cancelParams = [
|
|
64
|
-
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
65
|
-
];
|
|
66
|
-
const failParams = [
|
|
67
|
-
{ kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
|
|
68
|
-
];
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// agent new prompt (formerly job start prompt)
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
describe('agent new prompt', () => {
|
|
73
|
-
// stdin is handled by readStdinRaw() which requires actual stdin — we only
|
|
74
|
-
// test the non-stdin flag parsing here.
|
|
75
|
-
test('--cwd flag parsed as string', async () => {
|
|
76
|
-
// stdin param will be read but we can't pipe here — skip stdin assertion,
|
|
77
|
-
// test that cwd parses correctly alongside other tokens.
|
|
78
|
-
// We test the flag-only shape: no stdin params in isolation.
|
|
79
|
-
const flagOnlyParams = [
|
|
80
|
-
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
81
|
-
];
|
|
82
|
-
const result = await parseArgv(flagOnlyParams, ['--cwd', '/tmp/mydir']);
|
|
83
|
-
assert.equal(result['cwd'], '/tmp/mydir');
|
|
84
|
-
});
|
|
85
|
-
test('cwd absent → undefined', async () => {
|
|
86
|
-
const flagOnlyParams = [
|
|
87
|
-
{ kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
|
|
88
|
-
];
|
|
89
|
-
const result = await parseArgv(flagOnlyParams, []);
|
|
90
|
-
assert.equal(result['cwd'], undefined);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
// agent new fork (formerly job start fork)
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
describe('agent new fork', () => {
|
|
97
|
-
test('no args parses cleanly', async () => {
|
|
98
|
-
const result = await parseArgv(startForkParams, []);
|
|
99
|
-
assert.equal(result['cwd'], undefined);
|
|
100
|
-
});
|
|
101
|
-
test('--cwd parsed', async () => {
|
|
102
|
-
const result = await parseArgv(startForkParams, ['--cwd', '/workspace']);
|
|
103
|
-
assert.equal(result['cwd'], '/workspace');
|
|
104
|
-
});
|
|
105
|
-
test('unknown positional rejected', async () => {
|
|
106
|
-
await assert.rejects(() => parseArgv(startForkParams, ['extra-pos']), (err) => { assert.match(err.message, /takes no positional/); return true; });
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
// agent new planner (formerly job start planner)
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
describe('agent new planner', () => {
|
|
113
|
-
test('positional spec_path required', async () => {
|
|
114
|
-
await assert.rejects(() => parseArgv(startPlannerParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
115
|
-
});
|
|
116
|
-
test('positional parsed as spec_path (camelCase: specPath? no — underscore stays as-is)', async () => {
|
|
117
|
-
const result = await parseArgv(startPlannerParams, ['/tmp/spec.md']);
|
|
118
|
-
// flagNameToKey converts kebab to camel; underscores are unaffected
|
|
119
|
-
assert.equal(result['spec_path'], '/tmp/spec.md');
|
|
120
|
-
});
|
|
121
|
-
test('--cwd optional', async () => {
|
|
122
|
-
const result = await parseArgv(startPlannerParams, ['/tmp/spec.md', '--cwd', '/src']);
|
|
123
|
-
assert.equal(result['cwd'], '/src');
|
|
124
|
-
assert.equal(result['spec_path'], '/tmp/spec.md');
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
// agent new implementer (formerly job start implementer)
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
|
-
describe('agent new implementer', () => {
|
|
131
|
-
test('positional plan_path required', async () => {
|
|
132
|
-
await assert.rejects(() => parseArgv(startImplementerParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
133
|
-
});
|
|
134
|
-
test('positional parsed', async () => {
|
|
135
|
-
const result = await parseArgv(startImplementerParams, ['/tmp/plan.md']);
|
|
136
|
-
assert.equal(result['plan_path'], '/tmp/plan.md');
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
// ---------------------------------------------------------------------------
|
|
140
|
-
// agent new reviewer (formerly job start reviewer)
|
|
141
|
-
// ---------------------------------------------------------------------------
|
|
142
|
-
describe('agent new reviewer', () => {
|
|
143
|
-
test('positional + --kind required', async () => {
|
|
144
|
-
await assert.rejects(() => parseArgv(startReviewerParams, ['/tmp/artifact.md']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
145
|
-
});
|
|
146
|
-
test('valid kind: plan', async () => {
|
|
147
|
-
const result = await parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'plan']);
|
|
148
|
-
assert.equal(result['artifact_path'], '/tmp/artifact.md');
|
|
149
|
-
assert.equal(result['kind'], 'plan');
|
|
150
|
-
});
|
|
151
|
-
test('valid kind: spec', async () => {
|
|
152
|
-
const result = await parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'spec']);
|
|
153
|
-
assert.equal(result['kind'], 'spec');
|
|
154
|
-
});
|
|
155
|
-
test('invalid kind throws invalid_type', async () => {
|
|
156
|
-
await assert.rejects(() => parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'bad']), (err) => { assert.match(err.message, /must be one of/); return true; });
|
|
157
|
-
});
|
|
158
|
-
test('--spec-path optional, becomes specPath', async () => {
|
|
159
|
-
const result = await parseArgv(startReviewerParams, [
|
|
160
|
-
'/tmp/artifact.md', '--kind', 'plan', '--spec-path', '/tmp/spec.md',
|
|
161
|
-
]);
|
|
162
|
-
assert.equal(result['specPath'], '/tmp/spec.md');
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
// job read list
|
|
167
|
-
// ---------------------------------------------------------------------------
|
|
168
|
-
describe('job read list', () => {
|
|
169
|
-
test('defaults: limit=20, cursor=undefined', async () => {
|
|
170
|
-
const result = await parseArgv(readListParams, []);
|
|
171
|
-
assert.equal(result['limit'], 20);
|
|
172
|
-
assert.equal(result['cursor'], undefined);
|
|
173
|
-
});
|
|
174
|
-
test('--limit N parsed as int', async () => {
|
|
175
|
-
const result = await parseArgv(readListParams, ['--limit', '50']);
|
|
176
|
-
assert.equal(result['limit'], 50);
|
|
177
|
-
});
|
|
178
|
-
test('--limit with non-integer throws', async () => {
|
|
179
|
-
await assert.rejects(() => parseArgv(readListParams, ['--limit', 'abc']), (err) => { assert.match(err.message, /must be an integer/); return true; });
|
|
180
|
-
});
|
|
181
|
-
test('--cursor opaque token', async () => {
|
|
182
|
-
const result = await parseArgv(readListParams, ['--cursor', 'tok_abc123']);
|
|
183
|
-
assert.equal(result['cursor'], 'tok_abc123');
|
|
184
|
-
});
|
|
185
|
-
test('--limit and --cursor together', async () => {
|
|
186
|
-
const result = await parseArgv(readListParams, ['--limit', '5', '--cursor', 'next_page']);
|
|
187
|
-
assert.equal(result['limit'], 5);
|
|
188
|
-
assert.equal(result['cursor'], 'next_page');
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
// ---------------------------------------------------------------------------
|
|
192
|
-
// job read status
|
|
193
|
-
// ---------------------------------------------------------------------------
|
|
194
|
-
describe('job read status', () => {
|
|
195
|
-
test('positional job_id required', async () => {
|
|
196
|
-
await assert.rejects(() => parseArgv(readStatusParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
197
|
-
});
|
|
198
|
-
test('positional job_id parsed', async () => {
|
|
199
|
-
const result = await parseArgv(readStatusParams, ['job-abc-123']);
|
|
200
|
-
assert.equal(result['job_id'], 'job-abc-123');
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
// ---------------------------------------------------------------------------
|
|
204
|
-
// job read result
|
|
205
|
-
// ---------------------------------------------------------------------------
|
|
206
|
-
describe('job read result', () => {
|
|
207
|
-
test('positional job_id required', async () => {
|
|
208
|
-
await assert.rejects(() => parseArgv(readResultParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
209
|
-
});
|
|
210
|
-
test('positional job_id without --wait', async () => {
|
|
211
|
-
const result = await parseArgv(readResultParams, ['job-xyz']);
|
|
212
|
-
assert.equal(result['job_id'], 'job-xyz');
|
|
213
|
-
assert.equal(result['wait'], false);
|
|
214
|
-
});
|
|
215
|
-
test('--wait presence = true', async () => {
|
|
216
|
-
const result = await parseArgv(readResultParams, ['job-xyz', '--wait']);
|
|
217
|
-
assert.equal(result['wait'], true);
|
|
218
|
-
});
|
|
219
|
-
test('--wait=value rejected (bool takes no value)', async () => {
|
|
220
|
-
await assert.rejects(() => parseArgv(readResultParams, ['job-xyz', '--wait=true']), (err) => { assert.match(err.message, /takes no value/); return true; });
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
// ---------------------------------------------------------------------------
|
|
224
|
-
// job read logs
|
|
225
|
-
// ---------------------------------------------------------------------------
|
|
226
|
-
describe('job read logs', () => {
|
|
227
|
-
test('positional job_id required', async () => {
|
|
228
|
-
await assert.rejects(() => parseArgv(readLogsParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
229
|
-
});
|
|
230
|
-
test('defaults: level=info, follow=false', async () => {
|
|
231
|
-
const result = await parseArgv(readLogsParams, ['job-abc']);
|
|
232
|
-
assert.equal(result['job_id'], 'job-abc');
|
|
233
|
-
assert.equal(result['level'], 'info');
|
|
234
|
-
assert.equal(result['follow'], false);
|
|
235
|
-
});
|
|
236
|
-
test('--follow presence = true', async () => {
|
|
237
|
-
const result = await parseArgv(readLogsParams, ['job-abc', '--follow']);
|
|
238
|
-
assert.equal(result['follow'], true);
|
|
239
|
-
});
|
|
240
|
-
test('--follow=value rejected', async () => {
|
|
241
|
-
await assert.rejects(() => parseArgv(readLogsParams, ['job-abc', '--follow=true']), (err) => { assert.match(err.message, /takes no value/); return true; });
|
|
242
|
-
});
|
|
243
|
-
test('--level valid enum', async () => {
|
|
244
|
-
const result = await parseArgv(readLogsParams, ['job-abc', '--level', 'debug']);
|
|
245
|
-
assert.equal(result['level'], 'debug');
|
|
246
|
-
});
|
|
247
|
-
test('--level invalid enum throws', async () => {
|
|
248
|
-
await assert.rejects(() => parseArgv(readLogsParams, ['job-abc', '--level', 'trace']), (err) => { assert.match(err.message, /must be one of/); return true; });
|
|
249
|
-
});
|
|
250
|
-
test('--since and --until optional strings', async () => {
|
|
251
|
-
const result = await parseArgv(readLogsParams, [
|
|
252
|
-
'job-abc', '--since', '2025-01-01T00:00:00Z', '--until', '2025-01-02T00:00:00Z',
|
|
253
|
-
]);
|
|
254
|
-
assert.equal(result['since'], '2025-01-01T00:00:00Z');
|
|
255
|
-
assert.equal(result['until'], '2025-01-02T00:00:00Z');
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
// ---------------------------------------------------------------------------
|
|
259
|
-
// job submit
|
|
260
|
-
// ---------------------------------------------------------------------------
|
|
261
|
-
describe('job submit', () => {
|
|
262
|
-
test('positional job_id + defaults: status=done, killPane=false', async () => {
|
|
263
|
-
const result = await parseArgv(submitParams, ['job-abc']);
|
|
264
|
-
assert.equal(result['job_id'], 'job-abc');
|
|
265
|
-
assert.equal(result['status'], 'done');
|
|
266
|
-
assert.equal(result['killPane'], false);
|
|
267
|
-
});
|
|
268
|
-
test('--status failed parsed', async () => {
|
|
269
|
-
const result = await parseArgv(submitParams, ['job-abc', '--status', 'failed', '--reason', 'broken']);
|
|
270
|
-
assert.equal(result['status'], 'failed');
|
|
271
|
-
assert.equal(result['reason'], 'broken');
|
|
272
|
-
});
|
|
273
|
-
test('--status invalid enum throws', async () => {
|
|
274
|
-
await assert.rejects(() => parseArgv(submitParams, ['job-abc', '--status', 'bogus']), (err) => { assert.match(err.message, /must be one of/); return true; });
|
|
275
|
-
});
|
|
276
|
-
test('--kill-pane presence = true, killPane key', async () => {
|
|
277
|
-
const result = await parseArgv(submitParams, ['job-abc', '--kill-pane']);
|
|
278
|
-
assert.equal(result['killPane'], true);
|
|
279
|
-
});
|
|
280
|
-
test('missing positional job_id throws missing_parameter', async () => {
|
|
281
|
-
await assert.rejects(() => parseArgv(submitParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
282
|
-
});
|
|
283
|
-
test('--context-file no longer accepted', async () => {
|
|
284
|
-
await assert.rejects(() => parseArgv(submitParams, ['job-abc', '--context-file', '/tmp/anything']), (err) => { assert.match(err.message, /unknown flag: --context-file/); return true; });
|
|
285
|
-
});
|
|
286
|
-
});
|
|
287
|
-
// ---------------------------------------------------------------------------
|
|
288
|
-
// job cancel
|
|
289
|
-
// ---------------------------------------------------------------------------
|
|
290
|
-
describe('job cancel', () => {
|
|
291
|
-
test('positional job_id required', async () => {
|
|
292
|
-
await assert.rejects(() => parseArgv(cancelParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
293
|
-
});
|
|
294
|
-
test('positional job_id parsed', async () => {
|
|
295
|
-
const result = await parseArgv(cancelParams, ['job-abc-123']);
|
|
296
|
-
assert.equal(result['job_id'], 'job-abc-123');
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
// ---------------------------------------------------------------------------
|
|
300
|
-
// job _fail
|
|
301
|
-
// ---------------------------------------------------------------------------
|
|
302
|
-
describe('job _fail', () => {
|
|
303
|
-
test('positional job_id required', async () => {
|
|
304
|
-
await assert.rejects(() => parseArgv(failParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
|
|
305
|
-
});
|
|
306
|
-
test('positional job_id parsed', async () => {
|
|
307
|
-
const result = await parseArgv(failParams, ['job-fail-001']);
|
|
308
|
-
assert.equal(result['job_id'], 'job-fail-001');
|
|
309
|
-
});
|
|
310
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// Tests for jobs.ts result-file storage (markdown vs json paths).
|
|
2
|
-
//
|
|
3
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/jobs.test.ts
|
|
4
|
-
import { test, describe, before, after } from 'node:test';
|
|
5
|
-
import assert from 'node:assert/strict';
|
|
6
|
-
import { mkdirSync, rmSync, existsSync, readFileSync } from 'node:fs';
|
|
7
|
-
import { tmpdir } from 'node:os';
|
|
8
|
-
import { join } from 'node:path';
|
|
9
|
-
import { createJob, writeResult, writeMarkdownResult, readResult, } from '../jobs.js';
|
|
10
|
-
let stateDir;
|
|
11
|
-
let origXdg;
|
|
12
|
-
before(() => {
|
|
13
|
-
stateDir = join(tmpdir(), `crtr-jobs-test-${Date.now()}`);
|
|
14
|
-
mkdirSync(stateDir, { recursive: true });
|
|
15
|
-
origXdg = process.env['XDG_STATE_HOME'];
|
|
16
|
-
process.env['XDG_STATE_HOME'] = stateDir;
|
|
17
|
-
});
|
|
18
|
-
after(() => {
|
|
19
|
-
if (origXdg === undefined) {
|
|
20
|
-
delete process.env['XDG_STATE_HOME'];
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
process.env['XDG_STATE_HOME'] = origXdg;
|
|
24
|
-
}
|
|
25
|
-
rmSync(stateDir, { recursive: true, force: true });
|
|
26
|
-
});
|
|
27
|
-
describe('writeMarkdownResult + readResult round-trip', () => {
|
|
28
|
-
test('done with body writes result.md, parses frontmatter back', async () => {
|
|
29
|
-
const { jobId, dir } = createJob('prompt', { cwd: '/tmp' });
|
|
30
|
-
const body = '**Summary:** all good\n\nMore details on the next line.\n';
|
|
31
|
-
writeMarkdownResult(jobId, body, 'done');
|
|
32
|
-
assert.ok(existsSync(join(dir, 'result.md')), 'result.md should exist');
|
|
33
|
-
assert.ok(!existsSync(join(dir, 'result.json')), 'result.json should NOT exist on md path');
|
|
34
|
-
const raw = readFileSync(join(dir, 'result.md'), 'utf8');
|
|
35
|
-
assert.match(raw, /^---\nstatus: done\nwritten_at: \d{4}-\d{2}-\d{2}T/);
|
|
36
|
-
assert.ok(raw.endsWith(body), 'body preserved at end of file');
|
|
37
|
-
const r = await readResult(jobId, { waitMs: 0 });
|
|
38
|
-
assert.equal(r.status, 'done');
|
|
39
|
-
assert.equal(r.result_md, body);
|
|
40
|
-
assert.equal(r.reason, undefined);
|
|
41
|
-
assert.equal(r.result, undefined);
|
|
42
|
-
});
|
|
43
|
-
test('failed with reason writes reason into frontmatter and reads it back', async () => {
|
|
44
|
-
const { jobId } = createJob('prompt', { cwd: '/tmp' });
|
|
45
|
-
writeMarkdownResult(jobId, '', 'failed', 'broke: had "quoted" parts and a\nnewline');
|
|
46
|
-
const r = await readResult(jobId, { waitMs: 0 });
|
|
47
|
-
assert.equal(r.status, 'failed');
|
|
48
|
-
assert.equal(r.result_md, '');
|
|
49
|
-
assert.equal(r.reason, 'broke: had "quoted" parts and a\nnewline');
|
|
50
|
-
});
|
|
51
|
-
test('writeResult (JSON) writes result.json and read still works', async () => {
|
|
52
|
-
const { jobId, dir } = createJob('prompt', { cwd: '/tmp' });
|
|
53
|
-
writeResult(jobId, { feedback: 'approved', n: 3 }, 'done');
|
|
54
|
-
assert.ok(existsSync(join(dir, 'result.json')));
|
|
55
|
-
assert.ok(!existsSync(join(dir, 'result.md')));
|
|
56
|
-
const r = await readResult(jobId, { waitMs: 0 });
|
|
57
|
-
assert.equal(r.status, 'done');
|
|
58
|
-
assert.deepEqual(r.result, { feedback: 'approved', n: 3 });
|
|
59
|
-
assert.equal(r.result_md, undefined);
|
|
60
|
-
});
|
|
61
|
-
test('readResult with no result file and waitMs=0 returns timeout', async () => {
|
|
62
|
-
const { jobId } = createJob('prompt', { cwd: '/tmp' });
|
|
63
|
-
const r = await readResult(jobId, { waitMs: 0 });
|
|
64
|
-
assert.equal(r.status, 'timeout');
|
|
65
|
-
});
|
|
66
|
-
});
|
package/dist/core/jobs.d.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
type TerminalStatus = 'done' | 'failed' | 'canceled';
|
|
2
|
-
type JobState = 'live' | TerminalStatus;
|
|
3
|
-
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
4
|
-
/**
|
|
5
|
-
* Allocate a new job directory and write meta.json atomically.
|
|
6
|
-
* Returns the job_id and the absolute directory path.
|
|
7
|
-
*/
|
|
8
|
-
export declare function createJob(kind: string, opts: {
|
|
9
|
-
cwd: string;
|
|
10
|
-
pid?: number;
|
|
11
|
-
}): {
|
|
12
|
-
jobId: string;
|
|
13
|
-
dir: string;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Record the tmux pane hosting a detached worker so `cancelJob` can kill it.
|
|
17
|
-
*/
|
|
18
|
-
export declare function recordJobPane(jobId: string, paneId: string): void;
|
|
19
|
-
/**
|
|
20
|
-
* Append one event line to log.jsonl. Does NOT throw if jobId doesn't exist —
|
|
21
|
-
* a crashed writer should not further corrupt state; use a guard at the call site.
|
|
22
|
-
*/
|
|
23
|
-
export declare function appendEvent(jobId: string, event: {
|
|
24
|
-
level: LogLevel;
|
|
25
|
-
event: string;
|
|
26
|
-
message: string;
|
|
27
|
-
data?: object;
|
|
28
|
-
}): void;
|
|
29
|
-
/**
|
|
30
|
-
* Atomically write result.json (structured object) and update meta.json status.
|
|
31
|
-
* Used by programmatic callers (human, sys) that produce object results.
|
|
32
|
-
* The result file's appearance is the completion signal — never inferred from log content.
|
|
33
|
-
*/
|
|
34
|
-
export declare function writeResult(jobId: string, result: object, terminalStatus: TerminalStatus): void;
|
|
35
|
-
/**
|
|
36
|
-
* Atomically write result.md (YAML frontmatter + markdown body) and update meta.json status.
|
|
37
|
-
* Used by `crtr job submit` for agent-driven markdown results.
|
|
38
|
-
*/
|
|
39
|
-
export declare function writeMarkdownResult(jobId: string, body: string, terminalStatus: TerminalStatus, reason?: string): void;
|
|
40
|
-
/**
|
|
41
|
-
* Read whichever result file exists (result.md or result.json). If neither
|
|
42
|
-
* exists and waitMs is given, block via fs.watch until one appears or the
|
|
43
|
-
* timeout elapses.
|
|
44
|
-
*
|
|
45
|
-
* Race safety: registers the watcher THEN re-stats. If a result file appeared
|
|
46
|
-
* between the first stat and the watch registration, the re-stat catches it
|
|
47
|
-
* before the watcher has a chance to miss it.
|
|
48
|
-
*
|
|
49
|
-
* Returns shape:
|
|
50
|
-
* - JSON path: { status, result: object }
|
|
51
|
-
* - Markdown path: { status, result_md: string, reason?: string }
|
|
52
|
-
* - Timeout: { status: 'timeout' }
|
|
53
|
-
*/
|
|
54
|
-
export interface ReadResultResponse {
|
|
55
|
-
status: 'done' | 'failed' | 'canceled' | 'timeout';
|
|
56
|
-
result?: object;
|
|
57
|
-
result_md?: string;
|
|
58
|
-
reason?: string;
|
|
59
|
-
}
|
|
60
|
-
export declare function readResult(jobId: string, opts?: {
|
|
61
|
-
waitMs?: number;
|
|
62
|
-
}): Promise<ReadResultResponse>;
|
|
63
|
-
/**
|
|
64
|
-
* Derive job state from meta.json, the result file, and the tail of log.jsonl.
|
|
65
|
-
* If a pid is recorded, is not alive, and no result file exists → 'failed'.
|
|
66
|
-
*/
|
|
67
|
-
export declare function jobStatus(jobId: string): {
|
|
68
|
-
state: JobState;
|
|
69
|
-
age_s: number;
|
|
70
|
-
last_event: {
|
|
71
|
-
event: string;
|
|
72
|
-
ts: string;
|
|
73
|
-
} | null;
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
|
-
* List all jobs sorted by created_at ascending. Pagination is applied by the
|
|
77
|
-
* caller, not here.
|
|
78
|
-
*/
|
|
79
|
-
export declare function listJobs(): {
|
|
80
|
-
job_id: string;
|
|
81
|
-
kind: string;
|
|
82
|
-
state: JobState;
|
|
83
|
-
created_at: string;
|
|
84
|
-
}[];
|
|
85
|
-
/**
|
|
86
|
-
* Read and filter log events. Ordering preserved. sinceTs/untilTs are ISO8601
|
|
87
|
-
* strings; minLevel filters by severity rank (inclusive).
|
|
88
|
-
*/
|
|
89
|
-
export declare function readLog(jobId: string, opts?: {
|
|
90
|
-
sinceTs?: string;
|
|
91
|
-
untilTs?: string;
|
|
92
|
-
minLevel?: LogLevel;
|
|
93
|
-
}): object[];
|
|
94
|
-
/**
|
|
95
|
-
* Best-effort cancel: send SIGTERM to the recorded pid (if any), mark meta
|
|
96
|
-
* canceled. Success means the signal was delivered, not that execution stopped.
|
|
97
|
-
*/
|
|
98
|
-
export declare function cancelJob(jobId: string): {
|
|
99
|
-
canceled: boolean;
|
|
100
|
-
};
|
|
101
|
-
export {};
|