input-kanban 0.0.8 → 0.0.9
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/LICENSE +21 -0
- package/PROJECT_GUIDE.md +114 -4
- package/README.en.md +11 -5
- package/README.md +11 -5
- package/RELEASE_NOTES.md +3 -1
- package/bin/input-kanban.js +256 -40
- package/package.json +4 -2
- package/public/index.html +74 -3
- package/src/orchestrator.js +358 -175
- package/src/server.js +8 -4
- package/src/utils.js +5 -0
package/bin/input-kanban.js
CHANGED
|
@@ -4,9 +4,10 @@ import path from 'node:path';
|
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
7
|
+
const PACKAGE_VERSION = JSON.parse(await fsp.readFile(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
7
8
|
const VALID_RUNNERS = ['headless', 'tmux'];
|
|
8
9
|
const VALID_SANDBOXES = ['read-only', 'workspace-write', 'danger-full-access'];
|
|
9
|
-
const COMMANDS = new Set(['serve', 'submit', 'status', 'result', 'stop', 'auto']);
|
|
10
|
+
const COMMANDS = new Set(['serve', 'submit', 'runs', 'status', 'result', 'retry', 'stop', 'auto']);
|
|
10
11
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
11
12
|
const STATUS_TEXT = {
|
|
12
13
|
created: '已创建', planning: '拆分中', plan_failed: '拆分失败', plan_empty: '拆分为空', planned: '已拆分',
|
|
@@ -28,16 +29,26 @@ function validateSandbox(value, source) {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
function splitCommand(argv) {
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
let index = 0;
|
|
33
|
+
const globals = { json: false };
|
|
34
|
+
while (index < argv.length) {
|
|
35
|
+
const arg = argv[index];
|
|
36
|
+
if (arg === '--json' || arg === '-j') { globals.json = true; index++; continue; }
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
const rest = argv.slice(index);
|
|
40
|
+
if (rest[0] === '--version' || rest[0] === '-v' || rest[0] === 'version') return { command: 'version', rest: rest.slice(1), globals };
|
|
41
|
+
if (rest[0] && COMMANDS.has(rest[0])) return { command: rest[0], rest: rest.slice(1), globals };
|
|
42
|
+
return { command: 'serve', rest, globals };
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
function parseServeArgs(argv) {
|
|
36
|
-
const args = { host: '127.0.0.1', port: undefined, repo: undefined, runsDir: undefined, codexBin: undefined, runner: undefined, open: false, help: false };
|
|
46
|
+
const args = { host: '127.0.0.1', port: undefined, repo: undefined, runsDir: undefined, codexBin: undefined, runner: undefined, open: false, json: false, help: false };
|
|
37
47
|
for (let i = 0; i < argv.length; i++) {
|
|
38
48
|
const arg = argv[i];
|
|
39
49
|
const next = () => argv[++i];
|
|
40
50
|
if (arg === '--help' || arg === '-h') args.help = true;
|
|
51
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
41
52
|
else if (arg === '--open') args.open = true;
|
|
42
53
|
else if (arg === '--no-open') args.open = false;
|
|
43
54
|
else if (arg === '--host') args.host = next();
|
|
@@ -51,12 +62,29 @@ function parseServeArgs(argv) {
|
|
|
51
62
|
return args;
|
|
52
63
|
}
|
|
53
64
|
|
|
65
|
+
function parseRunsArgs(argv) {
|
|
66
|
+
const args = { runsDir: undefined, active: false, includeArchived: false, limit: 20, json: false, help: false };
|
|
67
|
+
for (let i = 0; i < argv.length; i++) {
|
|
68
|
+
const arg = argv[i];
|
|
69
|
+
const next = () => argv[++i];
|
|
70
|
+
if (arg === '--help' || arg === '-h') args.help = true;
|
|
71
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
72
|
+
else if (arg === '--runs-dir') args.runsDir = next();
|
|
73
|
+
else if (arg === '--active') args.active = true;
|
|
74
|
+
else if (arg === '--include-archived') args.includeArchived = true;
|
|
75
|
+
else if (arg === '--limit') args.limit = Number(next());
|
|
76
|
+
else throw new Error(`unknown runs argument: ${arg}`);
|
|
77
|
+
}
|
|
78
|
+
return args;
|
|
79
|
+
}
|
|
80
|
+
|
|
54
81
|
function parseStatusArgs(argv) {
|
|
55
|
-
const args = { host: '127.0.0.1', port: 8787, runsDir: undefined, runId: undefined, watch: false, pollMs: 3000, help: false };
|
|
82
|
+
const args = { host: '127.0.0.1', port: 8787, runsDir: undefined, runId: undefined, watch: false, json: false, pollMs: 3000, help: false };
|
|
56
83
|
for (let i = 0; i < argv.length; i++) {
|
|
57
84
|
const arg = argv[i];
|
|
58
85
|
const next = () => argv[++i];
|
|
59
86
|
if (arg === '--help' || arg === '-h') args.help = true;
|
|
87
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
60
88
|
else if (arg === '--host') args.host = next();
|
|
61
89
|
else if (arg === '--port' || arg === '-p') args.port = Number(next());
|
|
62
90
|
else if (arg === '--runs-dir') args.runsDir = next();
|
|
@@ -69,11 +97,12 @@ function parseStatusArgs(argv) {
|
|
|
69
97
|
}
|
|
70
98
|
|
|
71
99
|
function parseResultArgs(argv) {
|
|
72
|
-
const args = { runsDir: undefined, runId: undefined, copy: false, help: false };
|
|
100
|
+
const args = { runsDir: undefined, runId: undefined, copy: false, json: false, help: false };
|
|
73
101
|
for (let i = 0; i < argv.length; i++) {
|
|
74
102
|
const arg = argv[i];
|
|
75
103
|
const next = () => argv[++i];
|
|
76
104
|
if (arg === '--help' || arg === '-h') args.help = true;
|
|
105
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
77
106
|
else if (arg === '--runs-dir') args.runsDir = next();
|
|
78
107
|
else if (arg === '--copy') args.copy = true;
|
|
79
108
|
else if (!arg.startsWith('-') && !args.runId) args.runId = arg;
|
|
@@ -82,12 +111,30 @@ function parseResultArgs(argv) {
|
|
|
82
111
|
return args;
|
|
83
112
|
}
|
|
84
113
|
|
|
114
|
+
function parseRetryArgs(argv) {
|
|
115
|
+
const args = { runsDir: undefined, runId: undefined, taskId: undefined, reason: 'manual retry from CLI', maxRetries: 1, json: false, help: false };
|
|
116
|
+
for (let i = 0; i < argv.length; i++) {
|
|
117
|
+
const arg = argv[i];
|
|
118
|
+
const next = () => argv[++i];
|
|
119
|
+
if (arg === '--help' || arg === '-h') args.help = true;
|
|
120
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
121
|
+
else if (arg === '--runs-dir') args.runsDir = next();
|
|
122
|
+
else if (arg === '--reason') args.reason = next();
|
|
123
|
+
else if (arg === '--max-retries') args.maxRetries = Number(next());
|
|
124
|
+
else if (!arg.startsWith('-') && !args.runId) args.runId = arg;
|
|
125
|
+
else if (!arg.startsWith('-') && !args.taskId) args.taskId = arg;
|
|
126
|
+
else throw new Error(`unknown retry argument: ${arg}`);
|
|
127
|
+
}
|
|
128
|
+
return args;
|
|
129
|
+
}
|
|
130
|
+
|
|
85
131
|
function parseStopArgs(argv) {
|
|
86
|
-
const args = { runsDir: undefined, runId: undefined, reason: 'stopped from CLI', help: false };
|
|
132
|
+
const args = { runsDir: undefined, runId: undefined, reason: 'stopped from CLI', json: false, help: false };
|
|
87
133
|
for (let i = 0; i < argv.length; i++) {
|
|
88
134
|
const arg = argv[i];
|
|
89
135
|
const next = () => argv[++i];
|
|
90
136
|
if (arg === '--help' || arg === '-h') args.help = true;
|
|
137
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
91
138
|
else if (arg === '--runs-dir') args.runsDir = next();
|
|
92
139
|
else if (arg === '--reason') args.reason = next();
|
|
93
140
|
else if (!arg.startsWith('-') && !args.runId) args.runId = arg;
|
|
@@ -97,17 +144,19 @@ function parseStopArgs(argv) {
|
|
|
97
144
|
}
|
|
98
145
|
|
|
99
146
|
function parseAutoArgs(argv) {
|
|
100
|
-
const args = { host: '127.0.0.1', port: 8787, runsDir: undefined, codexBin: undefined, runner: undefined, runId: undefined, pollMs: 3000, help: false };
|
|
147
|
+
const args = { host: '127.0.0.1', port: 8787, runsDir: undefined, codexBin: undefined, runner: undefined, runId: undefined, json: false, pollMs: 3000, maxRetries: 1, help: false };
|
|
101
148
|
for (let i = 0; i < argv.length; i++) {
|
|
102
149
|
const arg = argv[i];
|
|
103
150
|
const next = () => argv[++i];
|
|
104
151
|
if (arg === '--help' || arg === '-h') args.help = true;
|
|
152
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
105
153
|
else if (arg === '--host') args.host = next();
|
|
106
154
|
else if (arg === '--port' || arg === '-p') args.port = Number(next());
|
|
107
155
|
else if (arg === '--runs-dir') args.runsDir = next();
|
|
108
156
|
else if (arg === '--codex-bin') args.codexBin = next();
|
|
109
157
|
else if (arg === '--runner') args.runner = validateRunner(next(), '--runner');
|
|
110
158
|
else if (arg === '--poll-ms') args.pollMs = Number(next());
|
|
159
|
+
else if (arg === '--max-retries') args.maxRetries = Number(next());
|
|
111
160
|
else if (!arg.startsWith('-') && !args.runId) args.runId = arg;
|
|
112
161
|
else throw new Error(`unknown auto argument: ${arg}`);
|
|
113
162
|
}
|
|
@@ -118,12 +167,13 @@ function parseSubmitArgs(argv) {
|
|
|
118
167
|
const args = {
|
|
119
168
|
host: '127.0.0.1', port: 8787, repo: undefined, runsDir: undefined, codexBin: undefined,
|
|
120
169
|
runner: undefined, label: undefined, taskText: undefined, taskFile: undefined, maxParallel: 3,
|
|
121
|
-
workerSandbox: 'workspace-write', auto: true, detach: false, watch: true, pollMs: 3000, help: false
|
|
170
|
+
workerSandbox: 'workspace-write', auto: true, detach: false, watch: true, json: false, pollMs: 3000, maxRetries: 1, help: false
|
|
122
171
|
};
|
|
123
172
|
for (let i = 0; i < argv.length; i++) {
|
|
124
173
|
const arg = argv[i];
|
|
125
174
|
const next = () => argv[++i];
|
|
126
175
|
if (arg === '--help' || arg === '-h') args.help = true;
|
|
176
|
+
else if (arg === '--json' || arg === '-j') args.json = true;
|
|
127
177
|
else if (arg === '--host') args.host = next();
|
|
128
178
|
else if (arg === '--port' || arg === '-p') args.port = Number(next());
|
|
129
179
|
else if (arg === '--repo' || arg === '-r') args.repo = next();
|
|
@@ -140,6 +190,7 @@ function parseSubmitArgs(argv) {
|
|
|
140
190
|
else if (arg === '--detach' || arg === '-d') args.detach = true;
|
|
141
191
|
else if (arg === '--watch') args.watch = true;
|
|
142
192
|
else if (arg === '--poll-ms') args.pollMs = Number(next());
|
|
193
|
+
else if (arg === '--max-retries') args.maxRetries = Number(next());
|
|
143
194
|
else throw new Error(`unknown submit argument: ${arg}`);
|
|
144
195
|
}
|
|
145
196
|
return args;
|
|
@@ -155,15 +206,26 @@ function applyRuntimeEnv(args) {
|
|
|
155
206
|
if (args.runner) process.env.KANBAN_RUNNER = args.runner;
|
|
156
207
|
}
|
|
157
208
|
|
|
209
|
+
function printJson(value) {
|
|
210
|
+
console.log(JSON.stringify(value, null, 2));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function printVersion() {
|
|
214
|
+
console.log(`input-kanban v${PACKAGE_VERSION}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
158
217
|
function printHelp() {
|
|
159
|
-
console.log(`input-kanban
|
|
218
|
+
console.log(`input-kanban v${PACKAGE_VERSION}
|
|
160
219
|
|
|
161
220
|
Usage:
|
|
162
221
|
input-kanban [options]
|
|
163
222
|
input-kanban serve [options]
|
|
164
223
|
input-kanban submit [options]
|
|
224
|
+
input-kanban --version
|
|
225
|
+
input-kanban runs [options]
|
|
165
226
|
input-kanban status [runId] [options]
|
|
166
227
|
input-kanban result [runId] [options]
|
|
228
|
+
input-kanban retry <runId> [taskId] [options]
|
|
167
229
|
input-kanban stop <runId> [options]
|
|
168
230
|
|
|
169
231
|
Serve options:
|
|
@@ -173,21 +235,39 @@ Serve options:
|
|
|
173
235
|
--runs-dir <path> Runtime runs directory, default ~/.input-kanban/runs
|
|
174
236
|
--codex-bin <path> Codex CLI executable, default codex
|
|
175
237
|
--runner <mode> Runner mode: headless or tmux, default headless
|
|
238
|
+
-j, --json Emit JSON startup output
|
|
239
|
+
-v, --version Print version and exit
|
|
176
240
|
--open Open browser after starting
|
|
177
241
|
--no-open Do not open browser, default
|
|
178
242
|
|
|
243
|
+
Runs options:
|
|
244
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
245
|
+
--active Show only active or pending-action runs
|
|
246
|
+
--include-archived Include archived runs
|
|
247
|
+
--limit <n> Maximum rows to print, default 20
|
|
248
|
+
-j, --json Emit JSON output instead of human text
|
|
249
|
+
|
|
179
250
|
Status options:
|
|
180
251
|
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
181
252
|
--watch Keep printing status until the run reaches a terminal state
|
|
182
253
|
--poll-ms <ms> Watch poll interval, default 3000
|
|
254
|
+
-j, --json Emit JSON output instead of human text
|
|
183
255
|
|
|
184
256
|
Result options:
|
|
185
257
|
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
186
258
|
--copy Copy final result to clipboard
|
|
259
|
+
-j, --json Emit JSON output instead of human text
|
|
260
|
+
|
|
261
|
+
Retry options:
|
|
262
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
263
|
+
--reason <text> Retry reason stored in task retry history
|
|
264
|
+
--max-retries <n> Retry limit for automatic retry policy, default 1
|
|
265
|
+
-j, --json Emit JSON output instead of human text
|
|
187
266
|
|
|
188
267
|
Stop options:
|
|
189
268
|
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
190
269
|
--reason <text> Stop reason stored in run state
|
|
270
|
+
-j, --json Emit JSON output instead of human text
|
|
191
271
|
|
|
192
272
|
Submit options:
|
|
193
273
|
-r, --repo <path> Target Git work tree, default current directory
|
|
@@ -203,6 +283,7 @@ Submit options:
|
|
|
203
283
|
-d, --detach Run the default auto loop in a background supervisor
|
|
204
284
|
--watch Watch status after starting the planner
|
|
205
285
|
--poll-ms <ms> Watch poll interval, default 3000
|
|
286
|
+
-j, --json Emit JSON output instead of human text
|
|
206
287
|
-h, --help Show help
|
|
207
288
|
`);
|
|
208
289
|
}
|
|
@@ -232,6 +313,23 @@ Options:
|
|
|
232
313
|
`);
|
|
233
314
|
}
|
|
234
315
|
|
|
316
|
+
function printRunsHelp() {
|
|
317
|
+
console.log(`input-kanban runs
|
|
318
|
+
|
|
319
|
+
Usage:
|
|
320
|
+
input-kanban runs
|
|
321
|
+
input-kanban runs --active
|
|
322
|
+
input-kanban --json runs --active
|
|
323
|
+
|
|
324
|
+
Options:
|
|
325
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
326
|
+
--active Show only active or pending-action runs
|
|
327
|
+
--include-archived Include archived runs
|
|
328
|
+
--limit <n> Maximum rows to print, default 20
|
|
329
|
+
-j, --json Emit JSON output instead of human text
|
|
330
|
+
`);
|
|
331
|
+
}
|
|
332
|
+
|
|
235
333
|
function printStatusHelp() {
|
|
236
334
|
console.log(`input-kanban status
|
|
237
335
|
|
|
@@ -245,6 +343,7 @@ Options:
|
|
|
245
343
|
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
246
344
|
--watch Keep printing status until the run reaches a terminal state
|
|
247
345
|
--poll-ms <ms> Watch poll interval, default 3000
|
|
346
|
+
-j, --json Emit JSON output instead of human text
|
|
248
347
|
`);
|
|
249
348
|
}
|
|
250
349
|
|
|
@@ -259,6 +358,23 @@ Usage:
|
|
|
259
358
|
Options:
|
|
260
359
|
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
261
360
|
--copy Copy final result to clipboard
|
|
361
|
+
-j, --json Emit JSON output instead of human text
|
|
362
|
+
`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function printRetryHelp() {
|
|
366
|
+
console.log(`input-kanban retry
|
|
367
|
+
|
|
368
|
+
Usage:
|
|
369
|
+
input-kanban retry <runId>
|
|
370
|
+
input-kanban retry <runId> <taskId>
|
|
371
|
+
input-kanban --json retry <runId> <taskId>
|
|
372
|
+
|
|
373
|
+
Options:
|
|
374
|
+
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
375
|
+
--reason <text> Retry reason stored in task retry history
|
|
376
|
+
--max-retries <n> Retry limit for automatic retry policy, default 1
|
|
377
|
+
-j, --json Emit JSON output instead of human text
|
|
262
378
|
`);
|
|
263
379
|
}
|
|
264
380
|
|
|
@@ -271,6 +387,7 @@ Usage:
|
|
|
271
387
|
Options:
|
|
272
388
|
--runs-dir <path> Runtime runs directory shared with the Web UI
|
|
273
389
|
--reason <text> Stop reason stored in run state
|
|
390
|
+
-j, --json Emit JSON output instead of human text
|
|
274
391
|
`);
|
|
275
392
|
}
|
|
276
393
|
|
|
@@ -285,6 +402,7 @@ Options:
|
|
|
285
402
|
--codex-bin <path> Codex CLI executable, default codex
|
|
286
403
|
--runner <mode> Runner mode: headless or tmux
|
|
287
404
|
--poll-ms <ms> Watch poll interval, default 3000
|
|
405
|
+
-j, --json Emit JSON output instead of human text
|
|
288
406
|
`);
|
|
289
407
|
}
|
|
290
408
|
|
|
@@ -308,8 +426,12 @@ async function readTaskText(args) {
|
|
|
308
426
|
throw new Error('submit requires --task or --task-file');
|
|
309
427
|
}
|
|
310
428
|
|
|
429
|
+
function baseUrl(args) {
|
|
430
|
+
return `http://${args.host || '127.0.0.1'}:${Number(args.port || 8787)}`;
|
|
431
|
+
}
|
|
432
|
+
|
|
311
433
|
function webUrl(args, runId = '') {
|
|
312
|
-
return
|
|
434
|
+
return `${baseUrl(args)}${runId ? ` (runId: ${runId})` : ''}`;
|
|
313
435
|
}
|
|
314
436
|
|
|
315
437
|
function displayStatus(status) {
|
|
@@ -351,6 +473,13 @@ function printRunStatus(state) {
|
|
|
351
473
|
if (state.judge?.status && state.judge.status !== 'pending') console.log(`验收: ${displayStatus(state.judge.status)}`);
|
|
352
474
|
}
|
|
353
475
|
|
|
476
|
+
function printRunsTable(runs) {
|
|
477
|
+
if (!runs.length) { console.log('没有找到任务批次'); return; }
|
|
478
|
+
for (const run of runs) {
|
|
479
|
+
console.log(`${run.runId}|${run.label || '-'}|${displayStatus(run.status)}|进度 ${run.completed}/${run.total}|执行中 ${run.running}|失败 ${run.failed}|runner ${run.runner || '-'}|沙箱 ${run.workerSandbox || '-'}|仓库 ${run.repo || '-'}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
354
483
|
function isTerminal(state) {
|
|
355
484
|
return ['judged', 'judge_failed', 'batch_blocked', 'plan_failed', 'plan_empty', 'stopped'].includes(state.status);
|
|
356
485
|
}
|
|
@@ -359,6 +488,12 @@ function isFailureTerminal(state) {
|
|
|
359
488
|
return ['judge_failed', 'batch_blocked', 'plan_failed', 'plan_empty', 'stopped'].includes(state.status);
|
|
360
489
|
}
|
|
361
490
|
|
|
491
|
+
function isActiveRunSummary(run) {
|
|
492
|
+
if (!run) return false;
|
|
493
|
+
if (Number(run.running) > 0) return true;
|
|
494
|
+
return !['judged', 'judge_failed', 'batch_blocked', 'plan_failed', 'plan_empty', 'stopped'].includes(run.status);
|
|
495
|
+
}
|
|
496
|
+
|
|
362
497
|
function hasRecoverableUnknownTask(state) {
|
|
363
498
|
return (state.tasks || []).some(task => task.status === 'unknown' && (task.exitCode === undefined || task.exitCode === 0));
|
|
364
499
|
}
|
|
@@ -374,8 +509,8 @@ async function confirmFailureTerminal(runId, state, refreshRun, pollMs) {
|
|
|
374
509
|
return { confirmed: true, state: confirmed };
|
|
375
510
|
}
|
|
376
511
|
|
|
377
|
-
async function watchRun(runId, { auto = false, pollMs = 3000 } = {}) {
|
|
378
|
-
const { dispatchRun, refreshRun, startJudge } = await import('../src/orchestrator.js');
|
|
512
|
+
async function watchRun(runId, { auto = false, pollMs = 3000, quiet = false, maxRetries = 1 } = {}) {
|
|
513
|
+
const { dispatchRun, refreshRun, retryRun, startJudge } = await import('../src/orchestrator.js');
|
|
379
514
|
let lastStatus = '';
|
|
380
515
|
let judgeStarted = false;
|
|
381
516
|
while (true) {
|
|
@@ -383,15 +518,26 @@ async function watchRun(runId, { auto = false, pollMs = 3000 } = {}) {
|
|
|
383
518
|
if (!state) throw new Error(`run not found: ${runId}`);
|
|
384
519
|
const line = statusLine(state);
|
|
385
520
|
if (line !== lastStatus) {
|
|
386
|
-
console.log(`[${new Date().toLocaleTimeString()}] ${line}`);
|
|
521
|
+
if (!quiet) console.log(`[${new Date().toLocaleTimeString()}] ${line}`);
|
|
387
522
|
lastStatus = line;
|
|
388
523
|
}
|
|
389
524
|
|
|
525
|
+
if (auto && state.status === 'batch_blocked') {
|
|
526
|
+
try {
|
|
527
|
+
const retryState = await retryRun(runId, { reason: 'auto retry from CLI', maxRetries, auto: true });
|
|
528
|
+
if (!quiet) console.log(`自动重试任务: ${retryState.retriedTaskIds?.join(', ') || '-'}`);
|
|
529
|
+
lastStatus = '';
|
|
530
|
+
continue;
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (!quiet) console.log(`自动重试跳过: ${error.message || String(error)}`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
390
536
|
if (auto && state.status === 'planned') {
|
|
391
|
-
console.log('自动派发任务...');
|
|
537
|
+
if (!quiet) console.log('自动派发任务...');
|
|
392
538
|
await dispatchRun(runId);
|
|
393
539
|
} else if (auto && state.status === 'batches_completed' && state.judge?.status !== 'running' && !judgeStarted) {
|
|
394
|
-
console.log('自动发起最终验收...');
|
|
540
|
+
if (!quiet) console.log('自动发起最终验收...');
|
|
395
541
|
judgeStarted = true;
|
|
396
542
|
await startJudge(runId);
|
|
397
543
|
}
|
|
@@ -415,11 +561,15 @@ async function serve(args) {
|
|
|
415
561
|
applyRuntimeEnv(args);
|
|
416
562
|
const { startServer } = await import('../src/server.js');
|
|
417
563
|
const instance = await startServer({ host: process.env.HOST, port: Number(process.env.PORT || 8787), log: false });
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
564
|
+
if (args.json) {
|
|
565
|
+
printJson({ ok: true, command: 'serve', version: instance.version, url: instance.url, repo: instance.defaultRepo, runsDir: instance.runsDir, runner: instance.runner });
|
|
566
|
+
} else {
|
|
567
|
+
console.log(`Input Kanban v${PACKAGE_VERSION} started`);
|
|
568
|
+
console.log(`URL: ${instance.url}`);
|
|
569
|
+
console.log(`Repo: ${instance.defaultRepo}`);
|
|
570
|
+
console.log(`Runs: ${instance.runsDir}`);
|
|
571
|
+
console.log(`Runner: ${instance.runner}`);
|
|
572
|
+
}
|
|
423
573
|
if (args.open) openBrowser(instance.url);
|
|
424
574
|
const shutdown = () => { instance.stop().finally(() => process.exit(0)); };
|
|
425
575
|
process.on('SIGINT', shutdown);
|
|
@@ -428,7 +578,7 @@ async function serve(args) {
|
|
|
428
578
|
|
|
429
579
|
function detachedAutoArgs(runId, args) {
|
|
430
580
|
const cliPath = fileURLToPath(import.meta.url);
|
|
431
|
-
const values = [cliPath, 'auto', runId, '--host', args.host || '127.0.0.1', '--port', String(args.port || 8787), '--poll-ms', String(args.pollMs || 3000)];
|
|
581
|
+
const values = [cliPath, 'auto', runId, '--host', args.host || '127.0.0.1', '--port', String(args.port || 8787), '--poll-ms', String(args.pollMs || 3000), '--max-retries', String(args.maxRetries ?? 1)];
|
|
432
582
|
if (args.runsDir) values.push('--runs-dir', path.resolve(args.runsDir));
|
|
433
583
|
if (args.codexBin) values.push('--codex-bin', args.codexBin);
|
|
434
584
|
if (args.runner) values.push('--runner', args.runner);
|
|
@@ -452,16 +602,32 @@ async function latestRunId() {
|
|
|
452
602
|
return runs[0].runId;
|
|
453
603
|
}
|
|
454
604
|
|
|
605
|
+
async function runs(args) {
|
|
606
|
+
applyRuntimeEnv(args);
|
|
607
|
+
const { listRuns } = await import('../src/orchestrator.js');
|
|
608
|
+
const limit = Number.isFinite(Number(args.limit)) && Number(args.limit) > 0 ? Number(args.limit) : 20;
|
|
609
|
+
const allRuns = await listRuns({ includeArchived: !!args.includeArchived });
|
|
610
|
+
const filtered = (args.active ? allRuns.filter(isActiveRunSummary) : allRuns).slice(0, limit);
|
|
611
|
+
if (args.json) {
|
|
612
|
+
printJson({ ok: true, command: 'runs', active: !!args.active, includeArchived: !!args.includeArchived, limit, count: filtered.length, runs: filtered });
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
printRunsTable(filtered);
|
|
616
|
+
}
|
|
617
|
+
|
|
455
618
|
async function status(args) {
|
|
456
619
|
applyRuntimeEnv(args);
|
|
457
620
|
const runId = args.runId || await latestRunId();
|
|
621
|
+
const { refreshRun, summaryOfRun } = await import('../src/orchestrator.js');
|
|
458
622
|
if (args.watch) {
|
|
459
|
-
await watchRun(runId, { auto: false, pollMs: args.pollMs });
|
|
623
|
+
const finalState = await watchRun(runId, { auto: false, pollMs: args.pollMs, quiet: args.json });
|
|
624
|
+
if (isFailureTerminal(finalState)) process.exitCode = 1;
|
|
625
|
+
if (args.json) printJson({ ok: true, command: 'status', run: summaryOfRun(finalState) });
|
|
460
626
|
return;
|
|
461
627
|
}
|
|
462
|
-
const { refreshRun } = await import('../src/orchestrator.js');
|
|
463
628
|
const state = await refreshRun(runId);
|
|
464
629
|
if (!state) throw new Error(`run not found: ${runId}`);
|
|
630
|
+
if (args.json) { printJson({ ok: true, command: 'status', run: summaryOfRun(state) }); return; }
|
|
465
631
|
printRunStatus(state);
|
|
466
632
|
}
|
|
467
633
|
|
|
@@ -469,9 +635,17 @@ async function readFinalResult(runId) {
|
|
|
469
635
|
const { loadRun, readRunFile } = await import('../src/orchestrator.js');
|
|
470
636
|
const state = await loadRun(runId);
|
|
471
637
|
if (!state) throw new Error(`run not found: ${runId}`);
|
|
472
|
-
try {
|
|
638
|
+
try {
|
|
639
|
+
const text = await readRunFile(runId, 'judge', 'verdict.json');
|
|
640
|
+
let parsed = null;
|
|
641
|
+
try { parsed = JSON.parse(text); } catch {}
|
|
642
|
+
return { state, source: 'judge/verdict.json', text, parsed };
|
|
643
|
+
}
|
|
473
644
|
catch {}
|
|
474
|
-
try {
|
|
645
|
+
try {
|
|
646
|
+
const text = await readRunFile(runId, 'judge', 'last_message.md');
|
|
647
|
+
return { state, source: 'judge/last_message.md', text, parsed: null };
|
|
648
|
+
}
|
|
475
649
|
catch {}
|
|
476
650
|
throw new Error(`最终结果尚未生成:当前状态 ${displayStatus(state.status)}`);
|
|
477
651
|
}
|
|
@@ -503,39 +677,56 @@ async function copyToClipboard(text) {
|
|
|
503
677
|
async function result(args) {
|
|
504
678
|
applyRuntimeEnv(args);
|
|
505
679
|
const runId = args.runId || await latestRunId();
|
|
506
|
-
const text = await readFinalResult(runId);
|
|
680
|
+
const { state, source, text, parsed } = await readFinalResult(runId);
|
|
681
|
+
const { summaryOfRun } = await import('../src/orchestrator.js');
|
|
507
682
|
if (args.copy) {
|
|
508
683
|
await copyToClipboard(text);
|
|
684
|
+
if (args.json) { printJson({ ok: true, command: 'result', run: summaryOfRun(state), source, copied: true }); return; }
|
|
509
685
|
console.log(`已复制最终结果: ${runId}`);
|
|
510
686
|
return;
|
|
511
687
|
}
|
|
688
|
+
if (args.json) {
|
|
689
|
+
printJson({ ok: true, command: 'result', run: summaryOfRun(state), source, result: parsed, text: parsed ? null : text });
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
512
692
|
console.log(text);
|
|
513
693
|
}
|
|
514
694
|
|
|
695
|
+
async function retry(args) {
|
|
696
|
+
applyRuntimeEnv(args);
|
|
697
|
+
if (!args.runId) throw new Error('retry requires a runId');
|
|
698
|
+
const { retryRun, summaryOfRun } = await import('../src/orchestrator.js');
|
|
699
|
+
const state = await retryRun(args.runId, { taskId: args.taskId, reason: args.reason, maxRetries: args.maxRetries });
|
|
700
|
+
if (args.json) { printJson({ ok: true, command: 'retry', run: summaryOfRun(state), retriedTaskIds: state.retriedTaskIds || [] }); return; }
|
|
701
|
+
console.log(`已重试任务: ${(state.retriedTaskIds || []).join(', ') || '-'}`);
|
|
702
|
+
}
|
|
703
|
+
|
|
515
704
|
async function stop(args) {
|
|
516
705
|
applyRuntimeEnv(args);
|
|
517
706
|
if (!args.runId) throw new Error('stop requires a runId');
|
|
518
|
-
const { stopRun } = await import('../src/orchestrator.js');
|
|
707
|
+
const { stopRun, summaryOfRun } = await import('../src/orchestrator.js');
|
|
519
708
|
const state = await stopRun(args.runId, { reason: args.reason });
|
|
709
|
+
if (args.json) { printJson({ ok: true, command: 'stop', run: summaryOfRun(state), reason: args.reason }); return; }
|
|
520
710
|
console.log(`已停止任务批次: ${state.runId}`);
|
|
521
711
|
}
|
|
522
712
|
|
|
523
713
|
async function autoRun(args) {
|
|
524
714
|
applyRuntimeEnv(args);
|
|
525
715
|
if (!args.runId) throw new Error('auto requires a runId');
|
|
526
|
-
const { loadRun, startPlanner } = await import('../src/orchestrator.js');
|
|
716
|
+
const { loadRun, startPlanner, summaryOfRun } = await import('../src/orchestrator.js');
|
|
527
717
|
const state = await loadRun(args.runId);
|
|
528
718
|
if (!state) throw new Error(`run not found: ${args.runId}`);
|
|
529
719
|
if (state.status === 'created') await startPlanner(args.runId);
|
|
530
|
-
const finalState = await watchRun(args.runId, { auto: true, pollMs: args.pollMs });
|
|
720
|
+
const finalState = await watchRun(args.runId, { auto: true, pollMs: args.pollMs, quiet: args.json, maxRetries: args.maxRetries });
|
|
531
721
|
if (isFailureTerminal(finalState)) process.exitCode = 1;
|
|
722
|
+
if (args.json) { printJson({ ok: true, command: 'auto', run: summaryOfRun(finalState) }); return; }
|
|
532
723
|
}
|
|
533
724
|
|
|
534
725
|
async function submit(args) {
|
|
535
726
|
if (args.detach && !args.auto) throw new Error('--detach requires auto mode; remove --no-auto');
|
|
536
727
|
applyRuntimeEnv(args);
|
|
537
728
|
const taskText = await readTaskText(args);
|
|
538
|
-
const { createRun, startPlanner } = await import('../src/orchestrator.js');
|
|
729
|
+
const { createRun, startPlanner, summaryOfRun } = await import('../src/orchestrator.js');
|
|
539
730
|
const state = await createRun({
|
|
540
731
|
label: args.label,
|
|
541
732
|
taskText,
|
|
@@ -543,46 +734,71 @@ async function submit(args) {
|
|
|
543
734
|
maxParallel: args.maxParallel,
|
|
544
735
|
workerSandbox: args.workerSandbox
|
|
545
736
|
});
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
737
|
+
if (!args.json) {
|
|
738
|
+
console.log(`已创建任务批次: ${state.runId}`);
|
|
739
|
+
console.log(`看板地址: ${webUrl(args, state.runId)}`);
|
|
740
|
+
console.log(`终端查看: input-kanban status ${state.runId} --watch`);
|
|
741
|
+
}
|
|
549
742
|
if (args.detach) {
|
|
550
743
|
const pid = startDetachedAuto(state.runId, args);
|
|
744
|
+
if (args.json) { printJson({ ok: true, command: 'submit', phase: 'detached', url: baseUrl(args), supervisorPid: pid, run: summaryOfRun(state) }); return; }
|
|
551
745
|
console.log(`后台执行中: supervisor pid ${pid}`);
|
|
552
746
|
return;
|
|
553
747
|
}
|
|
554
|
-
console.log('发起任务拆分...');
|
|
555
|
-
await startPlanner(state.runId);
|
|
556
|
-
if (!args.watch && !args.auto)
|
|
557
|
-
|
|
558
|
-
|
|
748
|
+
if (!args.json) console.log('发起任务拆分...');
|
|
749
|
+
const plannedState = await startPlanner(state.runId);
|
|
750
|
+
if (!args.watch && !args.auto) {
|
|
751
|
+
if (args.json) { printJson({ ok: true, command: 'submit', phase: 'planned', url: baseUrl(args), auto: args.auto, watch: args.watch, run: summaryOfRun(plannedState || state) }); }
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const finalState = await watchRun(state.runId, { auto: args.auto, pollMs: args.pollMs, quiet: args.json, maxRetries: args.maxRetries });
|
|
559
755
|
if (isFailureTerminal(finalState)) process.exitCode = 1;
|
|
756
|
+
if (args.json) { printJson({ ok: true, command: 'submit', phase: 'final', url: baseUrl(args), auto: args.auto, watch: args.watch, run: summaryOfRun(finalState) }); return; }
|
|
757
|
+
console.log(`最终状态: ${finalState.status}`);
|
|
560
758
|
}
|
|
561
759
|
|
|
562
760
|
try {
|
|
563
|
-
const { command, rest } = splitCommand(process.argv.slice(2));
|
|
761
|
+
const { command, rest, globals = {} } = splitCommand(process.argv.slice(2));
|
|
564
762
|
if (command === 'serve') {
|
|
565
763
|
const args = parseServeArgs(rest);
|
|
764
|
+
args.json = args.json || globals.json;
|
|
566
765
|
if (args.help) { printHelp(); process.exit(0); }
|
|
567
766
|
await serve(args);
|
|
767
|
+
} else if (command === 'version') {
|
|
768
|
+
printVersion();
|
|
568
769
|
} else if (command === 'submit') {
|
|
569
770
|
const args = parseSubmitArgs(rest);
|
|
771
|
+
args.json = args.json || globals.json;
|
|
570
772
|
if (args.help) { printSubmitHelp(); process.exit(0); }
|
|
571
773
|
await submit(args);
|
|
774
|
+
} else if (command === 'runs') {
|
|
775
|
+
const args = parseRunsArgs(rest);
|
|
776
|
+
args.json = args.json || globals.json;
|
|
777
|
+
if (args.help) { printRunsHelp(); process.exit(0); }
|
|
778
|
+
await runs(args);
|
|
572
779
|
} else if (command === 'status') {
|
|
573
780
|
const args = parseStatusArgs(rest);
|
|
781
|
+
args.json = args.json || globals.json;
|
|
574
782
|
if (args.help) { printStatusHelp(); process.exit(0); }
|
|
575
783
|
await status(args);
|
|
576
784
|
} else if (command === 'result') {
|
|
577
785
|
const args = parseResultArgs(rest);
|
|
786
|
+
args.json = args.json || globals.json;
|
|
578
787
|
if (args.help) { printResultHelp(); process.exit(0); }
|
|
579
788
|
await result(args);
|
|
789
|
+
} else if (command === 'retry') {
|
|
790
|
+
const args = parseRetryArgs(rest);
|
|
791
|
+
args.json = args.json || globals.json;
|
|
792
|
+
if (args.help) { printRetryHelp(); process.exit(0); }
|
|
793
|
+
await retry(args);
|
|
580
794
|
} else if (command === 'stop') {
|
|
581
795
|
const args = parseStopArgs(rest);
|
|
796
|
+
args.json = args.json || globals.json;
|
|
582
797
|
if (args.help) { printStopHelp(); process.exit(0); }
|
|
583
798
|
await stop(args);
|
|
584
799
|
} else if (command === 'auto') {
|
|
585
800
|
const args = parseAutoArgs(rest);
|
|
801
|
+
args.json = args.json || globals.json;
|
|
586
802
|
if (args.help) { printAutoHelp(); process.exit(0); }
|
|
587
803
|
await autoRun(args);
|
|
588
804
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "input-kanban",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"input-kanban": "bin/input-kanban.js"
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"check": "node --check bin/input-kanban.js && node --check bin/input-kanban-format-events.js && node --check bin/input-kanban-timestamp-events.js && node --check bin/input-kanban-tmux-overview.js && node --check src/server.js && node --check src/orchestrator.js && node --check src/appServerClient.js && node --check src/utils.js && node --check src/eventFormatter.js && node --check src/runners/index.js && node --check src/runners/headlessRunner.js && node --check src/runners/tmuxRunner.js && node --check src/runners/tmuxUtils.js && node --check src/tmux.js && node --test"
|
|
11
11
|
},
|
|
12
12
|
"description": "A local Codex orchestration kanban dashboard",
|
|
13
|
+
"license": "MIT",
|
|
13
14
|
"files": [
|
|
14
15
|
"bin",
|
|
15
16
|
"src",
|
|
@@ -18,7 +19,8 @@
|
|
|
18
19
|
"README.en.md",
|
|
19
20
|
"RELEASE_NOTES.md",
|
|
20
21
|
"PROJECT_GUIDE.md",
|
|
21
|
-
"ENVIRONMENT.md"
|
|
22
|
+
"ENVIRONMENT.md",
|
|
23
|
+
"LICENSE"
|
|
22
24
|
],
|
|
23
25
|
"keywords": [
|
|
24
26
|
"codex",
|