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.
Files changed (78) hide show
  1. package/.parallel-codex/config.example.toml +136 -3
  2. package/README.md +299 -21
  3. package/dist/bootstrap.js +37 -17
  4. package/dist/cli-args.js +34 -3
  5. package/dist/cli-help.js +20 -0
  6. package/dist/cli-startup-preflight.js +18 -0
  7. package/dist/cli-startup-recovery.js +82 -0
  8. package/dist/cli-workspace-picker.js +330 -0
  9. package/dist/cli-workspace-transition.js +33 -0
  10. package/dist/cli-workspace.js +7 -71
  11. package/dist/cli.js +234 -24
  12. package/dist/core/collaboration-timeline.js +268 -0
  13. package/dist/core/config-errors.js +14 -0
  14. package/dist/core/config.js +297 -109
  15. package/dist/core/file-store.js +119 -6
  16. package/dist/core/lease-finalization.js +22 -0
  17. package/dist/core/paths.js +7 -0
  18. package/dist/core/process-identity.js +48 -0
  19. package/dist/core/process-mutation-turn.js +128 -0
  20. package/dist/core/process-ownership.js +276 -0
  21. package/dist/core/process-tree.js +90 -0
  22. package/dist/core/router-audit.js +155 -0
  23. package/dist/core/router-redaction.js +31 -0
  24. package/dist/core/router.js +462 -35
  25. package/dist/core/session-index.js +412 -88
  26. package/dist/core/session-manager.js +1110 -40
  27. package/dist/core/task-session-details.js +175 -0
  28. package/dist/core/task-state-machine.js +18 -0
  29. package/dist/core/workspace-commit-recovery.js +118 -0
  30. package/dist/core/workspace.js +19 -11
  31. package/dist/doctor.js +373 -34
  32. package/dist/domain/schemas.js +142 -7
  33. package/dist/orchestrator/collaboration-channel.js +289 -6
  34. package/dist/orchestrator/feature-plan.js +70 -0
  35. package/dist/orchestrator/final-acceptance.js +86 -0
  36. package/dist/orchestrator/judge-artifacts.js +236 -0
  37. package/dist/orchestrator/orchestrator.js +2086 -203
  38. package/dist/orchestrator/prompts.js +168 -5
  39. package/dist/orchestrator/supervisor-summary.js +56 -2
  40. package/dist/orchestrator/workspace-sandbox.js +927 -0
  41. package/dist/tui/App.js +3187 -161
  42. package/dist/tui/AppShell.js +196 -25
  43. package/dist/tui/CollaborationTimelineView.js +327 -0
  44. package/dist/tui/FeatureBoardView.js +232 -0
  45. package/dist/tui/InputBar.js +581 -57
  46. package/dist/tui/RouterDiagnosticsView.js +469 -0
  47. package/dist/tui/StatusBar.js +610 -57
  48. package/dist/tui/StatusDetailView.js +164 -0
  49. package/dist/tui/TaskSessionDetailView.js +222 -0
  50. package/dist/tui/TaskSessionsView.js +210 -0
  51. package/dist/tui/TerminalOutput.js +53 -9
  52. package/dist/tui/WorkerOutputView.js +1404 -161
  53. package/dist/tui/WorkerOverviewView.js +269 -0
  54. package/dist/tui/chat-history.js +25 -0
  55. package/dist/tui/chat-input.js +67 -19
  56. package/dist/tui/chat-paste.js +76 -0
  57. package/dist/tui/display-width.js +41 -3
  58. package/dist/tui/incremental-text-file.js +101 -0
  59. package/dist/tui/keyboard.js +49 -0
  60. package/dist/tui/markdown-text.js +14 -0
  61. package/dist/tui/raw-input-decoder.js +3 -0
  62. package/dist/tui/scrolling.js +2 -1
  63. package/dist/tui/status-line.js +360 -11
  64. package/dist/tui/task-memory.js +13 -1
  65. package/dist/tui/task-result.js +105 -0
  66. package/dist/tui/terminal-screen.js +13 -1
  67. package/dist/tui/theme-contrast.js +144 -0
  68. package/dist/tui/theme-preview.js +109 -0
  69. package/dist/tui/theme.js +158 -0
  70. package/dist/version.js +1 -1
  71. package/dist/workers/capabilities.js +213 -0
  72. package/dist/workers/live-probe.js +177 -0
  73. package/dist/workers/mock-adapter.js +73 -8
  74. package/dist/workers/native-attach.js +106 -16
  75. package/dist/workers/process-adapter.js +572 -77
  76. package/dist/workers/provider.js +26 -0
  77. package/dist/workers/registry.js +12 -20
  78. 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
+ }