parallel-codex-tui 0.1.3 → 0.1.5
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 +136 -3
- package/README.md +299 -21
- package/dist/bootstrap.js +37 -17
- package/dist/cli-args.js +34 -3
- package/dist/cli-help.js +20 -0
- package/dist/cli-startup-preflight.js +18 -0
- package/dist/cli-startup-recovery.js +82 -0
- package/dist/cli-workspace-picker.js +330 -0
- package/dist/cli-workspace-transition.js +33 -0
- package/dist/cli-workspace.js +7 -71
- package/dist/cli.js +234 -24
- package/dist/core/collaboration-timeline.js +268 -0
- package/dist/core/config-errors.js +14 -0
- package/dist/core/config.js +297 -109
- package/dist/core/file-store.js +119 -6
- package/dist/core/lease-finalization.js +22 -0
- package/dist/core/paths.js +7 -0
- package/dist/core/process-identity.js +48 -0
- package/dist/core/process-mutation-turn.js +128 -0
- package/dist/core/process-ownership.js +276 -0
- package/dist/core/process-tree.js +90 -0
- package/dist/core/router-audit.js +155 -0
- package/dist/core/router-redaction.js +31 -0
- package/dist/core/router.js +462 -35
- package/dist/core/session-index.js +412 -88
- package/dist/core/session-manager.js +1110 -40
- package/dist/core/task-session-details.js +175 -0
- package/dist/core/task-state-machine.js +18 -0
- package/dist/core/workspace-commit-recovery.js +118 -0
- package/dist/core/workspace.js +19 -11
- package/dist/doctor.js +373 -34
- package/dist/domain/schemas.js +142 -7
- package/dist/orchestrator/collaboration-channel.js +289 -6
- package/dist/orchestrator/feature-plan.js +70 -0
- package/dist/orchestrator/final-acceptance.js +86 -0
- package/dist/orchestrator/judge-artifacts.js +236 -0
- package/dist/orchestrator/orchestrator.js +2086 -203
- package/dist/orchestrator/prompts.js +168 -5
- package/dist/orchestrator/supervisor-summary.js +56 -2
- package/dist/orchestrator/workspace-sandbox.js +927 -0
- package/dist/tui/App.js +3187 -161
- package/dist/tui/AppShell.js +196 -25
- package/dist/tui/CollaborationTimelineView.js +327 -0
- package/dist/tui/FeatureBoardView.js +232 -0
- package/dist/tui/InputBar.js +581 -57
- package/dist/tui/RouterDiagnosticsView.js +469 -0
- package/dist/tui/StatusBar.js +610 -57
- package/dist/tui/StatusDetailView.js +164 -0
- package/dist/tui/TaskSessionDetailView.js +222 -0
- package/dist/tui/TaskSessionsView.js +210 -0
- package/dist/tui/TerminalOutput.js +53 -9
- package/dist/tui/WorkerOutputView.js +1404 -161
- package/dist/tui/WorkerOverviewView.js +269 -0
- package/dist/tui/chat-history.js +25 -0
- package/dist/tui/chat-input.js +67 -19
- package/dist/tui/chat-paste.js +76 -0
- package/dist/tui/display-width.js +41 -3
- package/dist/tui/incremental-text-file.js +101 -0
- package/dist/tui/keyboard.js +49 -0
- package/dist/tui/markdown-text.js +14 -0
- package/dist/tui/raw-input-decoder.js +3 -0
- package/dist/tui/scrolling.js +2 -1
- package/dist/tui/status-line.js +360 -11
- package/dist/tui/task-memory.js +13 -1
- package/dist/tui/task-result.js +105 -0
- package/dist/tui/terminal-screen.js +13 -1
- package/dist/tui/theme-contrast.js +144 -0
- package/dist/tui/theme-preview.js +109 -0
- package/dist/tui/theme.js +158 -0
- package/dist/version.js +1 -1
- package/dist/workers/capabilities.js +213 -0
- package/dist/workers/live-probe.js +177 -0
- package/dist/workers/mock-adapter.js +73 -8
- package/dist/workers/native-attach.js +106 -16
- package/dist/workers/process-adapter.js +572 -77
- package/dist/workers/provider.js +26 -0
- package/dist/workers/registry.js +12 -20
- package/package.json +11 -2
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { EventRecordSchema, FeatureAssignmentSchema, FeatureStatusSchema } from "../domain/schemas.js";
|
|
5
|
+
import { readTextIfExists } from "./file-store.js";
|
|
6
|
+
const FeatureDialogueSchema = z.object({
|
|
7
|
+
time: z.string().datetime(),
|
|
8
|
+
feature_id: z.string().min(1),
|
|
9
|
+
turn_id: z.string().min(1),
|
|
10
|
+
type: z.string().min(1),
|
|
11
|
+
role: z.enum(["actor", "critic"]),
|
|
12
|
+
message: z.string().default(""),
|
|
13
|
+
paths: z.record(z.string()).default({})
|
|
14
|
+
});
|
|
15
|
+
const FindingResolutionSchema = z.object({
|
|
16
|
+
version: z.literal(1),
|
|
17
|
+
decision: z.enum(["pending", "approved", "inconsistent"]),
|
|
18
|
+
fixed_ids: z.array(z.string().min(1)),
|
|
19
|
+
unresolved_ids: z.array(z.string().min(1))
|
|
20
|
+
});
|
|
21
|
+
export async function loadCollaborationTimeline(taskId, taskDir) {
|
|
22
|
+
const [features, dialogueText, taskEventsText] = await Promise.all([
|
|
23
|
+
readCollaborationFeatures(taskDir),
|
|
24
|
+
readTextIfExists(join(taskDir, "dialogue", "actor-critic.jsonl")),
|
|
25
|
+
readTextIfExists(join(taskDir, "events.jsonl"))
|
|
26
|
+
]);
|
|
27
|
+
const featureById = new Map(features.map((feature) => [feature.id, feature]));
|
|
28
|
+
const dialogue = parseJsonLines(dialogueText, FeatureDialogueSchema);
|
|
29
|
+
const taskEvents = parseJsonLines(taskEventsText, EventRecordSchema)
|
|
30
|
+
.filter((event) => event.type.startsWith("feature.wave_"));
|
|
31
|
+
const events = [
|
|
32
|
+
...dialogue.map((event, index) => {
|
|
33
|
+
const feature = featureById.get(event.feature_id);
|
|
34
|
+
const artifactRefs = collaborationArtifactRefs(event.paths ?? {});
|
|
35
|
+
return {
|
|
36
|
+
id: `dialogue-${index}-${event.time}`,
|
|
37
|
+
time: event.time,
|
|
38
|
+
type: event.type,
|
|
39
|
+
role: event.role,
|
|
40
|
+
action: collaborationDialogueAction(event.type),
|
|
41
|
+
message: event.type === "critic.revision_requested" && feature?.latestFinding
|
|
42
|
+
? appendCollaborationEvidence(event.message ?? "", feature.latestFinding)
|
|
43
|
+
: event.message ?? "",
|
|
44
|
+
turnId: event.turn_id,
|
|
45
|
+
featureId: event.feature_id,
|
|
46
|
+
featureTitle: feature?.title ?? event.feature_id,
|
|
47
|
+
findings: feature?.findings ?? 0,
|
|
48
|
+
replies: feature?.replies ?? 0,
|
|
49
|
+
...(typeof feature?.resolvedFindings === "number"
|
|
50
|
+
? { resolvedFindings: feature.resolvedFindings }
|
|
51
|
+
: {}),
|
|
52
|
+
...(typeof feature?.unresolvedFindings === "number"
|
|
53
|
+
? { unresolvedFindings: feature.unresolvedFindings }
|
|
54
|
+
: {}),
|
|
55
|
+
artifacts: artifactRefs.map((artifact) => artifact.label),
|
|
56
|
+
artifactRefs
|
|
57
|
+
};
|
|
58
|
+
}),
|
|
59
|
+
...taskEvents.map((event, index) => ({
|
|
60
|
+
id: `task-${index}-${event.time}`,
|
|
61
|
+
time: event.time,
|
|
62
|
+
type: event.type,
|
|
63
|
+
role: "supervisor",
|
|
64
|
+
action: collaborationWaveAction(event.type),
|
|
65
|
+
message: event.message ?? "",
|
|
66
|
+
artifacts: ["task events"],
|
|
67
|
+
artifactRefs: [{ label: "task events", path: join(taskDir, "events.jsonl") }]
|
|
68
|
+
})),
|
|
69
|
+
...features.map((feature) => ({
|
|
70
|
+
id: `state-${feature.id}-${feature.updatedAt}`,
|
|
71
|
+
time: feature.updatedAt,
|
|
72
|
+
type: "feature.state",
|
|
73
|
+
role: "supervisor",
|
|
74
|
+
action: collaborationFeatureStateAction(feature.state),
|
|
75
|
+
message: [
|
|
76
|
+
`${feature.title} · ${humanizeFeatureState(feature.state)}`,
|
|
77
|
+
...(feature.latestFinding ? [`finding: ${feature.latestFinding}`] : []),
|
|
78
|
+
...(feature.latestReply ? [`reply: ${feature.latestReply}`] : [])
|
|
79
|
+
].join(" · "),
|
|
80
|
+
turnId: feature.turnId,
|
|
81
|
+
featureId: feature.id,
|
|
82
|
+
featureTitle: feature.title,
|
|
83
|
+
findings: feature.findings,
|
|
84
|
+
replies: feature.replies,
|
|
85
|
+
...(typeof feature.resolvedFindings === "number"
|
|
86
|
+
? { resolvedFindings: feature.resolvedFindings }
|
|
87
|
+
: {}),
|
|
88
|
+
...(typeof feature.unresolvedFindings === "number"
|
|
89
|
+
? { unresolvedFindings: feature.unresolvedFindings }
|
|
90
|
+
: {}),
|
|
91
|
+
artifacts: feature.artifactRefs.map((artifact) => artifact.label),
|
|
92
|
+
artifactRefs: feature.artifactRefs
|
|
93
|
+
}))
|
|
94
|
+
].sort((left, right) => left.time.localeCompare(right.time) || left.id.localeCompare(right.id));
|
|
95
|
+
return { taskId, features, events };
|
|
96
|
+
}
|
|
97
|
+
async function readCollaborationFeatures(taskDir) {
|
|
98
|
+
const root = join(taskDir, "features");
|
|
99
|
+
let entries;
|
|
100
|
+
try {
|
|
101
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
const features = await Promise.all(entries
|
|
107
|
+
.filter((entry) => entry.isDirectory())
|
|
108
|
+
.map(async (entry) => {
|
|
109
|
+
const dir = join(root, entry.name);
|
|
110
|
+
const [statusText, assignmentText, spec, findings, replies, resolutionText] = await Promise.all([
|
|
111
|
+
readTextIfExists(join(dir, "status.json")),
|
|
112
|
+
readTextIfExists(join(dir, "assignment.json")),
|
|
113
|
+
readTextIfExists(join(dir, "spec.md")),
|
|
114
|
+
readTextIfExists(join(dir, "critic-findings.jsonl")),
|
|
115
|
+
readTextIfExists(join(dir, "actor-replies.jsonl")),
|
|
116
|
+
readTextIfExists(join(dir, "finding-resolution.json"))
|
|
117
|
+
]);
|
|
118
|
+
const status = parseJsonValue(statusText, FeatureStatusSchema);
|
|
119
|
+
if (!status) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
const findingEvidence = readMailboxEvidence(findings);
|
|
123
|
+
const replyEvidence = readMailboxEvidence(replies);
|
|
124
|
+
const resolution = parseJsonValue(resolutionText, FindingResolutionSchema);
|
|
125
|
+
const assignment = parseJsonValue(assignmentText, FeatureAssignmentSchema);
|
|
126
|
+
return {
|
|
127
|
+
id: status.feature_id,
|
|
128
|
+
title: status.title?.trim() || featureSpecTitle(spec) || status.feature_id,
|
|
129
|
+
description: status.description?.trim() ?? "",
|
|
130
|
+
dependsOn: status.depends_on ?? [],
|
|
131
|
+
turnId: status.turn_id,
|
|
132
|
+
state: status.state,
|
|
133
|
+
updatedAt: status.updated_at,
|
|
134
|
+
...(assignment ? {
|
|
135
|
+
actorEngine: assignment.actor_engine,
|
|
136
|
+
criticEngine: assignment.critic_engine
|
|
137
|
+
} : {}),
|
|
138
|
+
findings: findingEvidence.count,
|
|
139
|
+
replies: replyEvidence.count,
|
|
140
|
+
artifactRefs: [
|
|
141
|
+
{ label: "status", path: join(dir, "status.json") },
|
|
142
|
+
{ label: "spec", path: join(dir, "spec.md") },
|
|
143
|
+
...(assignment ? [{ label: "assignment", path: join(dir, "assignment.json") }] : []),
|
|
144
|
+
{ label: "critic findings", path: join(dir, "critic-findings.jsonl") },
|
|
145
|
+
{ label: "actor replies", path: join(dir, "actor-replies.jsonl") },
|
|
146
|
+
...(resolution ? [{ label: "finding resolution", path: join(dir, "finding-resolution.json") }] : [])
|
|
147
|
+
],
|
|
148
|
+
...(resolution
|
|
149
|
+
? {
|
|
150
|
+
resolvedFindings: new Set(resolution.fixed_ids).size,
|
|
151
|
+
unresolvedFindings: new Set(resolution.unresolved_ids).size
|
|
152
|
+
}
|
|
153
|
+
: {}),
|
|
154
|
+
...(findingEvidence.latest ? { latestFinding: findingEvidence.latest } : {}),
|
|
155
|
+
...(replyEvidence.latest ? { latestReply: replyEvidence.latest } : {})
|
|
156
|
+
};
|
|
157
|
+
}));
|
|
158
|
+
return features
|
|
159
|
+
.filter((feature) => feature !== null)
|
|
160
|
+
.sort((left, right) => left.turnId.localeCompare(right.turnId) || left.id.localeCompare(right.id));
|
|
161
|
+
}
|
|
162
|
+
function collaborationArtifactRefs(paths) {
|
|
163
|
+
return Object.entries(paths)
|
|
164
|
+
.map(([label, path]) => ({ label: label.trim(), path: path.trim() }))
|
|
165
|
+
.filter((artifact) => artifact.label && artifact.path)
|
|
166
|
+
.sort((left, right) => left.label.localeCompare(right.label) || left.path.localeCompare(right.path));
|
|
167
|
+
}
|
|
168
|
+
function parseJsonLines(text, schema) {
|
|
169
|
+
const records = [];
|
|
170
|
+
for (const line of text.split(/\r?\n/)) {
|
|
171
|
+
if (!line.trim()) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const parsed = schema.safeParse(JSON.parse(line));
|
|
176
|
+
if (parsed.success) {
|
|
177
|
+
records.push(parsed.data);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// A partial mailbox write must not hide earlier collaboration evidence.
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return records;
|
|
185
|
+
}
|
|
186
|
+
function parseJsonValue(text, schema) {
|
|
187
|
+
try {
|
|
188
|
+
const parsed = schema.safeParse(JSON.parse(text));
|
|
189
|
+
return parsed.success ? parsed.data : null;
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function readMailboxEvidence(text) {
|
|
196
|
+
let count = 0;
|
|
197
|
+
let latest = null;
|
|
198
|
+
for (const line of text.split(/\r?\n/)) {
|
|
199
|
+
if (!line.trim()) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const value = JSON.parse(line);
|
|
204
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
205
|
+
count += 1;
|
|
206
|
+
latest = mailboxRecordSummary(value) || latest;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// Ignore partial or malformed mailbox rows.
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { count, latest };
|
|
214
|
+
}
|
|
215
|
+
function mailboxRecordSummary(value) {
|
|
216
|
+
for (const key of ["message", "summary", "notes", "resolution", "title", "description", "issue", "status"]) {
|
|
217
|
+
const field = value[key];
|
|
218
|
+
if (typeof field === "string" && field.trim()) {
|
|
219
|
+
return field.replace(/\s+/g, " ").trim();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return "";
|
|
223
|
+
}
|
|
224
|
+
function appendCollaborationEvidence(message, evidence) {
|
|
225
|
+
const base = message.trim();
|
|
226
|
+
if (!base || base.includes(evidence)) {
|
|
227
|
+
return base || evidence;
|
|
228
|
+
}
|
|
229
|
+
return `${base} · ${evidence}`;
|
|
230
|
+
}
|
|
231
|
+
function featureSpecTitle(spec) {
|
|
232
|
+
const title = spec.match(/^Title:\s*(.+)$/m)?.[1]?.trim();
|
|
233
|
+
return title || null;
|
|
234
|
+
}
|
|
235
|
+
function collaborationDialogueAction(type) {
|
|
236
|
+
if (type === "feature.created") {
|
|
237
|
+
return "mailbox created";
|
|
238
|
+
}
|
|
239
|
+
if (type === "actor.completed") {
|
|
240
|
+
return "implementation completed";
|
|
241
|
+
}
|
|
242
|
+
if (type === "critic.completed") {
|
|
243
|
+
return "review completed";
|
|
244
|
+
}
|
|
245
|
+
if (type === "critic.revision_requested") {
|
|
246
|
+
return "revision requested";
|
|
247
|
+
}
|
|
248
|
+
return humanizeCollaborationType(type);
|
|
249
|
+
}
|
|
250
|
+
function collaborationWaveAction(type) {
|
|
251
|
+
const action = type.replace(/^feature\.wave_/, "wave ");
|
|
252
|
+
return humanizeCollaborationType(action);
|
|
253
|
+
}
|
|
254
|
+
function collaborationFeatureStateAction(state) {
|
|
255
|
+
if (state === "approved") {
|
|
256
|
+
return "feature approved";
|
|
257
|
+
}
|
|
258
|
+
if (state === "revision_needed") {
|
|
259
|
+
return "revision pending";
|
|
260
|
+
}
|
|
261
|
+
return `feature ${humanizeFeatureState(state)}`;
|
|
262
|
+
}
|
|
263
|
+
function humanizeFeatureState(state) {
|
|
264
|
+
return state.replaceAll("_", " ");
|
|
265
|
+
}
|
|
266
|
+
function humanizeCollaborationType(type) {
|
|
267
|
+
return type.replace(/[._-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
268
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ZodError } from "zod";
|
|
2
|
+
export function formatConfigErrorMessage(error) {
|
|
3
|
+
if (error instanceof ZodError) {
|
|
4
|
+
return error.issues
|
|
5
|
+
.map((issue) => ({
|
|
6
|
+
message: issue.message,
|
|
7
|
+
path: issue.path.map(String).join(".") || "config"
|
|
8
|
+
}))
|
|
9
|
+
.sort((left, right) => left.path.localeCompare(right.path))
|
|
10
|
+
.map((issue) => `${issue.path}: ${issue.message}`)
|
|
11
|
+
.join("\n");
|
|
12
|
+
}
|
|
13
|
+
return error instanceof Error ? error.message : String(error);
|
|
14
|
+
}
|