parallel-codex-tui 0.1.4 → 0.1.6
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/.parallel-codex/config.example.toml +46 -0
- package/README.md +96 -19
- package/dist/cli-startup-preflight.js +18 -0
- package/dist/cli-startup-recovery.js +13 -1
- package/dist/cli.js +19 -7
- package/dist/core/clipboard.js +97 -0
- package/dist/core/collaboration-timeline.js +9 -2
- package/dist/core/config.js +161 -103
- package/dist/core/router.js +1 -1
- package/dist/core/session-index.js +234 -61
- package/dist/core/session-manager.js +25 -1
- package/dist/core/task-session-details.js +175 -0
- package/dist/core/task-state-machine.js +10 -9
- package/dist/doctor.js +58 -39
- package/dist/domain/schemas.js +15 -1
- package/dist/orchestrator/collaboration-channel.js +35 -3
- package/dist/orchestrator/final-acceptance.js +86 -0
- package/dist/orchestrator/orchestrator.js +405 -69
- package/dist/orchestrator/prompts.js +42 -3
- package/dist/orchestrator/workspace-sandbox.js +16 -0
- package/dist/tui/App.js +514 -56
- package/dist/tui/AppShell.js +9 -3
- package/dist/tui/FeatureBoardView.js +7 -2
- package/dist/tui/InputBar.js +87 -15
- package/dist/tui/StatusBar.js +1 -1
- package/dist/tui/StatusDetailView.js +164 -0
- package/dist/tui/TaskSessionDetailView.js +222 -0
- package/dist/tui/TaskSessionsView.js +5 -2
- package/dist/tui/WorkerOutputView.js +6 -1
- package/dist/tui/WorkerOverviewView.js +23 -4
- package/dist/tui/keyboard.js +6 -0
- package/dist/tui/status-line.js +42 -0
- package/dist/version.js +1 -1
- package/dist/workers/capabilities.js +4 -3
- package/dist/workers/live-probe.js +4 -3
- package/dist/workers/mock-adapter.js +37 -5
- package/dist/workers/native-attach.js +32 -17
- package/dist/workers/process-adapter.js +2 -0
- package/dist/workers/provider.js +26 -0
- package/dist/workers/registry.js +12 -22
- package/package.json +7 -1
package/dist/core/config.js
CHANGED
|
@@ -2,6 +2,7 @@ import { parse } from "@iarna/toml";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { EngineNameSchema } from "../domain/schemas.js";
|
|
5
6
|
import { pathExists, readTextIfExists, writeText } from "./file-store.js";
|
|
6
7
|
import { normalizeTuiThemeColorValue, normalizeTuiThemeName, TUI_THEME_FIELDS, TUI_THEME_NAMES } from "../tui/theme.js";
|
|
7
8
|
const NativeSessionConfigSchema = z.object({
|
|
@@ -38,7 +39,16 @@ const WorkerCapabilitiesConfigSchema = z.object({
|
|
|
38
39
|
});
|
|
39
40
|
const InteractiveCommandSchema = z.object({
|
|
40
41
|
command: z.string().min(1),
|
|
41
|
-
args: z.array(z.string()).default([])
|
|
42
|
+
args: z.array(z.string()).default([]),
|
|
43
|
+
forkArgs: z.array(z.string()).default([])
|
|
44
|
+
}).superRefine((value, context) => {
|
|
45
|
+
if (value.forkArgs.length > 0 && !value.forkArgs.some((arg) => arg.includes("{sessionId}"))) {
|
|
46
|
+
context.addIssue({
|
|
47
|
+
code: z.ZodIssueCode.custom,
|
|
48
|
+
path: ["forkArgs"],
|
|
49
|
+
message: "forkArgs must include a {sessionId} template"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
42
52
|
});
|
|
43
53
|
const RolePromptConfigSchema = z.object({
|
|
44
54
|
title: z.string().min(1),
|
|
@@ -75,7 +85,8 @@ const CodexRouterConfigSchema = z.object({
|
|
|
75
85
|
env: z.record(z.string()).default({})
|
|
76
86
|
});
|
|
77
87
|
const OrchestrationConfigSchema = z.object({
|
|
78
|
-
maxParallelFeatures: z.number().int().min(1).max(8)
|
|
88
|
+
maxParallelFeatures: z.number().int().min(1).max(8),
|
|
89
|
+
maxRevisionRounds: z.number().int().min(1).max(10)
|
|
79
90
|
}).strict();
|
|
80
91
|
const UiConfigSchema = z.object({
|
|
81
92
|
showStatusBar: z.boolean(),
|
|
@@ -86,6 +97,7 @@ const UiConfigSchema = z.object({
|
|
|
86
97
|
const WorkerCommandSchema = z.object({
|
|
87
98
|
command: z.string().min(1),
|
|
88
99
|
args: z.array(z.string()).default([]),
|
|
100
|
+
assignable: z.boolean().default(true),
|
|
89
101
|
timeoutMs: z.number().int().positive().optional(),
|
|
90
102
|
idleTimeoutMs: z.number().int().positive().optional(),
|
|
91
103
|
firstOutputTimeoutMs: z.number().int().positive().optional(),
|
|
@@ -106,12 +118,12 @@ const AppConfigSchema = z.object({
|
|
|
106
118
|
codex: WorkerCommandSchema,
|
|
107
119
|
claude: WorkerCommandSchema,
|
|
108
120
|
mock: WorkerCommandSchema
|
|
109
|
-
}),
|
|
121
|
+
}).catchall(WorkerCommandSchema),
|
|
110
122
|
pairing: z.object({
|
|
111
|
-
main:
|
|
112
|
-
judge:
|
|
113
|
-
actor:
|
|
114
|
-
critic:
|
|
123
|
+
main: EngineNameSchema,
|
|
124
|
+
judge: EngineNameSchema,
|
|
125
|
+
actor: EngineNameSchema,
|
|
126
|
+
critic: EngineNameSchema
|
|
115
127
|
}),
|
|
116
128
|
roles: z.object({
|
|
117
129
|
main: RolePromptConfigSchema,
|
|
@@ -120,6 +132,16 @@ const AppConfigSchema = z.object({
|
|
|
120
132
|
critic: RolePromptConfigSchema
|
|
121
133
|
}),
|
|
122
134
|
ui: UiConfigSchema
|
|
135
|
+
}).superRefine((config, context) => {
|
|
136
|
+
for (const [role, workerId] of Object.entries(config.pairing)) {
|
|
137
|
+
if (!config.workers[workerId]) {
|
|
138
|
+
context.addIssue({
|
|
139
|
+
code: z.ZodIssueCode.custom,
|
|
140
|
+
path: ["pairing", role],
|
|
141
|
+
message: `Unknown Worker profile: ${workerId}`
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
123
145
|
});
|
|
124
146
|
export function defaultConfig(projectRoot) {
|
|
125
147
|
return {
|
|
@@ -154,12 +176,14 @@ export function defaultConfig(projectRoot) {
|
|
|
154
176
|
}
|
|
155
177
|
},
|
|
156
178
|
orchestration: {
|
|
157
|
-
maxParallelFeatures: 3
|
|
179
|
+
maxParallelFeatures: 3,
|
|
180
|
+
maxRevisionRounds: 3
|
|
158
181
|
},
|
|
159
182
|
workers: {
|
|
160
183
|
codex: {
|
|
161
184
|
command: "codex",
|
|
162
185
|
args: ["exec", "--skip-git-repo-check", "--sandbox", "workspace-write", "--color", "never", "-"],
|
|
186
|
+
assignable: true,
|
|
163
187
|
timeoutMs: 45 * 60 * 1000,
|
|
164
188
|
idleTimeoutMs: 5 * 60 * 1000,
|
|
165
189
|
firstOutputTimeoutMs: 2 * 60 * 1000,
|
|
@@ -182,12 +206,14 @@ export function defaultConfig(projectRoot) {
|
|
|
182
206
|
},
|
|
183
207
|
interactive: {
|
|
184
208
|
command: "codex",
|
|
185
|
-
args: ["resume", "{sessionId}"]
|
|
209
|
+
args: ["resume", "{sessionId}"],
|
|
210
|
+
forkArgs: ["fork", "{sessionId}"]
|
|
186
211
|
}
|
|
187
212
|
},
|
|
188
213
|
claude: {
|
|
189
214
|
command: "claude",
|
|
190
215
|
args: ["--print", "--permission-mode", "acceptEdits", "--output-format", "text"],
|
|
216
|
+
assignable: true,
|
|
191
217
|
timeoutMs: 45 * 60 * 1000,
|
|
192
218
|
idleTimeoutMs: 5 * 60 * 1000,
|
|
193
219
|
firstOutputTimeoutMs: 2 * 60 * 1000,
|
|
@@ -210,12 +236,14 @@ export function defaultConfig(projectRoot) {
|
|
|
210
236
|
},
|
|
211
237
|
interactive: {
|
|
212
238
|
command: "claude",
|
|
213
|
-
args: ["--resume", "{sessionId}"]
|
|
239
|
+
args: ["--resume", "{sessionId}"],
|
|
240
|
+
forkArgs: ["--resume", "{sessionId}", "--fork-session"]
|
|
214
241
|
}
|
|
215
242
|
},
|
|
216
243
|
mock: {
|
|
217
244
|
command: "mock",
|
|
218
245
|
args: [],
|
|
246
|
+
assignable: false,
|
|
219
247
|
model: {
|
|
220
248
|
name: "",
|
|
221
249
|
provider: "",
|
|
@@ -235,7 +263,8 @@ export function defaultConfig(projectRoot) {
|
|
|
235
263
|
},
|
|
236
264
|
interactive: {
|
|
237
265
|
command: "mock",
|
|
238
|
-
args: ["resume", "{sessionId}"]
|
|
266
|
+
args: ["resume", "{sessionId}"],
|
|
267
|
+
forkArgs: []
|
|
239
268
|
}
|
|
240
269
|
}
|
|
241
270
|
},
|
|
@@ -302,80 +331,7 @@ export async function loadConfig(projectRoot) {
|
|
|
302
331
|
...base.orchestration,
|
|
303
332
|
...(parsed.orchestration ?? {})
|
|
304
333
|
},
|
|
305
|
-
workers: {
|
|
306
|
-
codex: {
|
|
307
|
-
...base.workers.codex,
|
|
308
|
-
...(parsed.workers?.codex ?? {}),
|
|
309
|
-
model: {
|
|
310
|
-
...base.workers.codex.model,
|
|
311
|
-
...(parsed.workers?.codex?.model ?? {}),
|
|
312
|
-
env: {
|
|
313
|
-
...base.workers.codex.model.env,
|
|
314
|
-
...(parsed.workers?.codex?.model?.env ?? {})
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
capabilities: {
|
|
318
|
-
...base.workers.codex.capabilities,
|
|
319
|
-
...(parsed.workers?.codex?.capabilities ?? {})
|
|
320
|
-
},
|
|
321
|
-
nativeSession: {
|
|
322
|
-
...base.workers.codex.nativeSession,
|
|
323
|
-
...(parsed.workers?.codex?.nativeSession ?? {})
|
|
324
|
-
},
|
|
325
|
-
interactive: {
|
|
326
|
-
...base.workers.codex.interactive,
|
|
327
|
-
...(parsed.workers?.codex?.interactive ?? {})
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
claude: {
|
|
331
|
-
...base.workers.claude,
|
|
332
|
-
...(parsed.workers?.claude ?? {}),
|
|
333
|
-
model: {
|
|
334
|
-
...base.workers.claude.model,
|
|
335
|
-
...(parsed.workers?.claude?.model ?? {}),
|
|
336
|
-
env: {
|
|
337
|
-
...base.workers.claude.model.env,
|
|
338
|
-
...(parsed.workers?.claude?.model?.env ?? {})
|
|
339
|
-
}
|
|
340
|
-
},
|
|
341
|
-
capabilities: {
|
|
342
|
-
...base.workers.claude.capabilities,
|
|
343
|
-
...(parsed.workers?.claude?.capabilities ?? {})
|
|
344
|
-
},
|
|
345
|
-
nativeSession: {
|
|
346
|
-
...base.workers.claude.nativeSession,
|
|
347
|
-
...(parsed.workers?.claude?.nativeSession ?? {})
|
|
348
|
-
},
|
|
349
|
-
interactive: {
|
|
350
|
-
...base.workers.claude.interactive,
|
|
351
|
-
...(parsed.workers?.claude?.interactive ?? {})
|
|
352
|
-
}
|
|
353
|
-
},
|
|
354
|
-
mock: {
|
|
355
|
-
...base.workers.mock,
|
|
356
|
-
...(parsed.workers?.mock ?? {}),
|
|
357
|
-
model: {
|
|
358
|
-
...base.workers.mock.model,
|
|
359
|
-
...(parsed.workers?.mock?.model ?? {}),
|
|
360
|
-
env: {
|
|
361
|
-
...base.workers.mock.model.env,
|
|
362
|
-
...(parsed.workers?.mock?.model?.env ?? {})
|
|
363
|
-
}
|
|
364
|
-
},
|
|
365
|
-
capabilities: {
|
|
366
|
-
...base.workers.mock.capabilities,
|
|
367
|
-
...(parsed.workers?.mock?.capabilities ?? {})
|
|
368
|
-
},
|
|
369
|
-
nativeSession: {
|
|
370
|
-
...base.workers.mock.nativeSession,
|
|
371
|
-
...(parsed.workers?.mock?.nativeSession ?? {})
|
|
372
|
-
},
|
|
373
|
-
interactive: {
|
|
374
|
-
...base.workers.mock.interactive,
|
|
375
|
-
...(parsed.workers?.mock?.interactive ?? {})
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
},
|
|
334
|
+
workers: resolveWorkerConfigs(base.workers, parsed.workers ?? {}),
|
|
379
335
|
pairing: {
|
|
380
336
|
...base.pairing,
|
|
381
337
|
...(parsed.pairing ?? {})
|
|
@@ -409,6 +365,113 @@ export async function loadConfig(projectRoot) {
|
|
|
409
365
|
};
|
|
410
366
|
return AppConfigSchema.parse(merged);
|
|
411
367
|
}
|
|
368
|
+
function resolveWorkerConfigs(builtins, configured) {
|
|
369
|
+
const resolved = new Map();
|
|
370
|
+
const resolving = new Set();
|
|
371
|
+
const ids = [...new Set([...Object.keys(builtins), ...Object.keys(configured)])];
|
|
372
|
+
const resolve = (id) => {
|
|
373
|
+
const cached = resolved.get(id);
|
|
374
|
+
if (cached) {
|
|
375
|
+
return cached;
|
|
376
|
+
}
|
|
377
|
+
if (!EngineNameSchema.safeParse(id).success) {
|
|
378
|
+
throw new Error(`Invalid Worker profile id: ${id}`);
|
|
379
|
+
}
|
|
380
|
+
if (resolving.has(id)) {
|
|
381
|
+
throw new Error(`Circular Worker profile inheritance: ${[...resolving, id].join(" -> ")}`);
|
|
382
|
+
}
|
|
383
|
+
resolving.add(id);
|
|
384
|
+
try {
|
|
385
|
+
const override = configured[id] ?? {};
|
|
386
|
+
const builtin = builtins[id];
|
|
387
|
+
if (builtin && override.extends) {
|
|
388
|
+
throw new Error(`Built-in Worker profile ${id} cannot declare extends`);
|
|
389
|
+
}
|
|
390
|
+
let parent = builtin;
|
|
391
|
+
if (!parent) {
|
|
392
|
+
const parentId = override.extends?.trim();
|
|
393
|
+
if (!parentId || parentId === "generic") {
|
|
394
|
+
parent = genericWorkerConfig(id, override.command);
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
if (!builtins[parentId] && !configured[parentId]) {
|
|
398
|
+
throw new Error(`Unknown Worker profile inherited by ${id}: ${parentId}`);
|
|
399
|
+
}
|
|
400
|
+
parent = resolve(parentId);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const worker = mergeWorkerConfig(parent, override);
|
|
404
|
+
resolved.set(id, worker);
|
|
405
|
+
return worker;
|
|
406
|
+
}
|
|
407
|
+
finally {
|
|
408
|
+
resolving.delete(id);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
for (const id of ids) {
|
|
412
|
+
resolve(id);
|
|
413
|
+
}
|
|
414
|
+
return Object.fromEntries(resolved);
|
|
415
|
+
}
|
|
416
|
+
function genericWorkerConfig(id, command = id) {
|
|
417
|
+
return {
|
|
418
|
+
command,
|
|
419
|
+
args: [],
|
|
420
|
+
assignable: true,
|
|
421
|
+
timeoutMs: 45 * 60 * 1000,
|
|
422
|
+
idleTimeoutMs: 5 * 60 * 1000,
|
|
423
|
+
firstOutputTimeoutMs: 2 * 60 * 1000,
|
|
424
|
+
model: {
|
|
425
|
+
name: "",
|
|
426
|
+
provider: "",
|
|
427
|
+
args: [],
|
|
428
|
+
env: {}
|
|
429
|
+
},
|
|
430
|
+
capabilities: {
|
|
431
|
+
profile: "generic",
|
|
432
|
+
writableDirArgs: [],
|
|
433
|
+
freshSessionArgs: []
|
|
434
|
+
},
|
|
435
|
+
nativeSession: {
|
|
436
|
+
enabled: false,
|
|
437
|
+
resumeArgs: [],
|
|
438
|
+
detectSessionId: false,
|
|
439
|
+
fallback: "fail"
|
|
440
|
+
},
|
|
441
|
+
interactive: {
|
|
442
|
+
command,
|
|
443
|
+
args: [],
|
|
444
|
+
forkArgs: []
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function mergeWorkerConfig(base, override) {
|
|
449
|
+
const { extends: _extends, ...values } = override;
|
|
450
|
+
return {
|
|
451
|
+
...base,
|
|
452
|
+
...values,
|
|
453
|
+
model: {
|
|
454
|
+
...base.model,
|
|
455
|
+
...(override.model ?? {}),
|
|
456
|
+
env: {
|
|
457
|
+
...base.model.env,
|
|
458
|
+
...(override.model?.env ?? {})
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
capabilities: {
|
|
462
|
+
...base.capabilities,
|
|
463
|
+
...(override.capabilities ?? {})
|
|
464
|
+
},
|
|
465
|
+
nativeSession: {
|
|
466
|
+
...base.nativeSession,
|
|
467
|
+
...(override.nativeSession ?? {})
|
|
468
|
+
},
|
|
469
|
+
interactive: {
|
|
470
|
+
...base.interactive,
|
|
471
|
+
...(override.interactive ?? {})
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
}
|
|
412
475
|
export function withUiThemeOverride(config, theme) {
|
|
413
476
|
if (!theme) {
|
|
414
477
|
return config;
|
|
@@ -428,24 +491,6 @@ function assertObjectSections(parsed) {
|
|
|
428
491
|
["router.codex.env", parsed.router?.codex?.env],
|
|
429
492
|
["orchestration", parsed.orchestration],
|
|
430
493
|
["workers", parsed.workers],
|
|
431
|
-
["workers.codex", parsed.workers?.codex],
|
|
432
|
-
["workers.codex.model", parsed.workers?.codex?.model],
|
|
433
|
-
["workers.codex.model.env", parsed.workers?.codex?.model?.env],
|
|
434
|
-
["workers.codex.capabilities", parsed.workers?.codex?.capabilities],
|
|
435
|
-
["workers.codex.nativeSession", parsed.workers?.codex?.nativeSession],
|
|
436
|
-
["workers.codex.interactive", parsed.workers?.codex?.interactive],
|
|
437
|
-
["workers.claude", parsed.workers?.claude],
|
|
438
|
-
["workers.claude.model", parsed.workers?.claude?.model],
|
|
439
|
-
["workers.claude.model.env", parsed.workers?.claude?.model?.env],
|
|
440
|
-
["workers.claude.capabilities", parsed.workers?.claude?.capabilities],
|
|
441
|
-
["workers.claude.nativeSession", parsed.workers?.claude?.nativeSession],
|
|
442
|
-
["workers.claude.interactive", parsed.workers?.claude?.interactive],
|
|
443
|
-
["workers.mock", parsed.workers?.mock],
|
|
444
|
-
["workers.mock.model", parsed.workers?.mock?.model],
|
|
445
|
-
["workers.mock.model.env", parsed.workers?.mock?.model?.env],
|
|
446
|
-
["workers.mock.capabilities", parsed.workers?.mock?.capabilities],
|
|
447
|
-
["workers.mock.nativeSession", parsed.workers?.mock?.nativeSession],
|
|
448
|
-
["workers.mock.interactive", parsed.workers?.mock?.interactive],
|
|
449
494
|
["pairing", parsed.pairing],
|
|
450
495
|
["roles", parsed.roles],
|
|
451
496
|
["roles.main", parsed.roles?.main],
|
|
@@ -455,6 +500,19 @@ function assertObjectSections(parsed) {
|
|
|
455
500
|
["ui", parsed.ui],
|
|
456
501
|
["ui.colors", parsed.ui?.colors]
|
|
457
502
|
];
|
|
503
|
+
if (isPlainObject(parsed.workers)) {
|
|
504
|
+
for (const [id, value] of Object.entries(parsed.workers)) {
|
|
505
|
+
sections.push([`workers.${id}`, value]);
|
|
506
|
+
if (!isPlainObject(value)) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
const worker = value;
|
|
510
|
+
sections.push([`workers.${id}.model`, worker.model], [`workers.${id}.capabilities`, worker.capabilities], [`workers.${id}.nativeSession`, worker.nativeSession], [`workers.${id}.interactive`, worker.interactive]);
|
|
511
|
+
if (isPlainObject(worker.model)) {
|
|
512
|
+
sections.push([`workers.${id}.model.env`, worker.model.env]);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
458
516
|
for (const [path, value] of sections) {
|
|
459
517
|
if (value !== undefined && !isPlainObject(value)) {
|
|
460
518
|
throw new Error(`Invalid config section [${path}]: expected a table`);
|
package/dist/core/router.js
CHANGED
|
@@ -93,7 +93,7 @@ function annotateRoute(route, source, startedAt, telemetry, proxyContext, router
|
|
|
93
93
|
...routerProxyRouteFields(proxyContext),
|
|
94
94
|
...(routerCommand ? { router_command: routerCommand } : {}),
|
|
95
95
|
source,
|
|
96
|
-
duration_ms: Math.max(0, Date.now() - startedAt)
|
|
96
|
+
duration_ms: source === "forced" ? 0 : Math.max(0, Date.now() - startedAt)
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
export function routerCommandLabel(command) {
|