@longtable/cli 0.1.57 → 0.1.59
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/dist/cli.js +365 -73
- package/dist/debate.js +19 -31
- package/dist/longtable-codex-native-hook.js +36 -8
- package/dist/panel-runtime.d.ts +1 -0
- package/dist/panel-runtime.js +447 -88
- package/dist/panel.d.ts +13 -1
- package/dist/panel.js +343 -31
- package/dist/project-session.d.ts +0 -1
- package/dist/project-session.js +75 -116
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
-
import { execSync } from "node:child_process";
|
|
4
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { stdin as input, stdout as output, cwd, env, exit } from "node:process";
|
|
@@ -18,7 +18,7 @@ import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodex
|
|
|
18
18
|
import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router.js";
|
|
19
19
|
import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
|
|
20
20
|
import { buildPanelFallback, renderPanelSummary } from "./panel.js";
|
|
21
|
-
import { createPanelWorkerRun, launchPanelWorkerRun, panelWorkerRunPath, readPanelWorkerRun, refreshPanelWorkerRun, requestPanelWorkerStop, resumePanelWorkerRun, waitForPanelWorkerRun } from "./panel-runtime.js";
|
|
21
|
+
import { createPanelWorkerRun, launchPanelWorkerRun, panelWorkerRunPath, readPanelWorkerRun, refreshPanelWorkerRun, requestPanelWorkerStop, resumePanelWorkerRun, shutdownPanelWorkerRun, waitForPanelWorkerRun } from "./panel-runtime.js";
|
|
22
22
|
import { LONGTABLE_MANAGED_HOOK_EVENTS, codexHooksEnabled, enableCodexHooksFeature, getMissingManagedCodexHookEvents, getMissingManagedCodexHookTrustState, mergeCodexHookTrustState, mergeManagedCodexHooksConfig, removeCodexHookTrustState, removeManagedCodexHooks } from "./codex-hooks.js";
|
|
23
23
|
import { appendInvocationRecordToWorkspace, applyResearchSpecificationPatch, assertWorkspaceNotBlocked, answerWorkspaceQuestion, buildQuestionOpportunitySpecs, clearWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, createWorkspaceHandoff, diffResearchSpecifications, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, findUnincorporatedResearchEvidence, proposeResearchSpecificationPatch, pruneWorkspaceQuestions, readResearchSpecificationHistory, recordPanelResultInWorkspace, repairWorkspaceStateConsistency, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
24
24
|
import { buildTeamDebate } from "./debate.js";
|
|
@@ -157,16 +157,16 @@ function usage() {
|
|
|
157
157
|
" longtable sentinel --prompt <text> [--cwd <path>] [--json] [--record]",
|
|
158
158
|
" longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
159
159
|
" longtable clarify --prompt <task-context> [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json] [--force]",
|
|
160
|
-
" longtable question --prompt <decision-context> [--title <text>] [--text <question>] [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json]",
|
|
160
|
+
" longtable question (--prompt <decision-context> | --question <id>) [--title <text>] [--text <question>] [--provider codex|claude] [--required|--advisory] [--surface tmux_popup|terminal_selector|numbered] [--print] [--cwd <path>] [--json]",
|
|
161
161
|
" longtable clear-question --question <id> --reason <text> [--cwd <path>] [--json]",
|
|
162
|
-
" longtable repair-state [--cwd <path>] [--dry-run] [--json]",
|
|
163
162
|
" longtable panel [--prompt <text>] [--role <role[,role]>] [--mode review|critique|draft|commit] [--visibility synthesis_only|show_on_conflict|always_visible] [--provider codex|claude] [--native-workers|--native-subagents] [--wait [ms]] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
164
163
|
" longtable panel status --run <panel_run_id> [--wait [ms]] [--cwd <path>] [--json]",
|
|
165
164
|
" longtable panel stop --run <panel_run_id> [--cwd <path>] [--json]",
|
|
165
|
+
" longtable panel shutdown --run <panel_run_id> [--cwd <path>] [--json]",
|
|
166
166
|
" longtable panel resume --run <panel_run_id> [--wait [ms]] [--cwd <path>] [--json]",
|
|
167
167
|
" longtable panel record [--invocation <id>] --result-file <json> [--surface sequential_fallback|native_subagents|native_workers] [--cwd <path>] [--json]",
|
|
168
168
|
" longtable handoff [--cwd <path>] [--output <file>] [--print] [--json]",
|
|
169
|
-
" longtable decide [--question <id>] --answer <value-or-text> [--rationale <text>] [--provider codex|claude] [--cwd <path>] [--json]",
|
|
169
|
+
" longtable decide [--question <id>] --answer <value-or-text> [--rationale <text>] [--provider codex|claude] [--surface tmux_popup|mcp_elicitation|terminal_selector|numbered] [--cwd <path>] [--json]",
|
|
170
170
|
" longtable explore|review|critique|draft|commit|submit [--prompt <text>] [--role <role[,role]>] [--panel] [--show-conflicts] [--show-deliberation] [--print] [--json] [--stage <stage>] [--setup <path>] [--cwd <path>]",
|
|
171
171
|
" longtable codex persist-init [--answers-json <json> | --stdin | full setup flags] [--install-skills] [--install-prompts] [--json]",
|
|
172
172
|
" longtable codex install-skills [--surface compact|full] [--dir <path>]",
|
|
@@ -201,7 +201,7 @@ function parseArgs(argv) {
|
|
|
201
201
|
const values = {};
|
|
202
202
|
let subcommand = maybeSubcommand;
|
|
203
203
|
const modeCommand = command && VALID_MODES.has(command);
|
|
204
|
-
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "audit", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "clear-question", "
|
|
204
|
+
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "audit", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "clear-question", "prune-questions", "panel", "handoff", "decide", "sentinel", "access", "search", "spec"].includes(command);
|
|
205
205
|
let startIndex = 1;
|
|
206
206
|
if (modeCommand) {
|
|
207
207
|
subcommand = undefined;
|
|
@@ -299,6 +299,27 @@ function parseSkillSurface(args) {
|
|
|
299
299
|
}
|
|
300
300
|
throw new Error("Invalid --surface value. Use compact or full.");
|
|
301
301
|
}
|
|
302
|
+
const QUESTION_SURFACE_VALUES = [
|
|
303
|
+
"native_structured",
|
|
304
|
+
"tmux_popup",
|
|
305
|
+
"mcp_elicitation",
|
|
306
|
+
"numbered",
|
|
307
|
+
"terminal_selector",
|
|
308
|
+
"web_form"
|
|
309
|
+
];
|
|
310
|
+
function parseQuestionSurface(value) {
|
|
311
|
+
if (value === undefined || value === false) {
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
if (value === true) {
|
|
315
|
+
throw new Error(`Invalid --surface value. Use one of: ${QUESTION_SURFACE_VALUES.join(", ")}.`);
|
|
316
|
+
}
|
|
317
|
+
const normalized = value.trim();
|
|
318
|
+
if (QUESTION_SURFACE_VALUES.includes(normalized)) {
|
|
319
|
+
return normalized;
|
|
320
|
+
}
|
|
321
|
+
throw new Error(`Invalid --surface value. Use one of: ${QUESTION_SURFACE_VALUES.join(", ")}.`);
|
|
322
|
+
}
|
|
302
323
|
function stripWrappingQuotes(value) {
|
|
303
324
|
const trimmed = value.trim();
|
|
304
325
|
if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) ||
|
|
@@ -1784,10 +1805,6 @@ function renderDoctorStatus(status) {
|
|
|
1784
1805
|
if (!status.workspace.found) {
|
|
1785
1806
|
nextActions.push("longtable start");
|
|
1786
1807
|
}
|
|
1787
|
-
if ((status.workspace.answerWarnings ?? []).some((warning) => warning.issue.includes("legacy answer shape"))) {
|
|
1788
|
-
const root = status.workspace.rootPath ? ` --cwd "${status.workspace.rootPath}"` : "";
|
|
1789
|
-
nextActions.push(`longtable repair-state${root}`);
|
|
1790
|
-
}
|
|
1791
1808
|
nextActions.push(...status.hardStop.nextActions);
|
|
1792
1809
|
const firstQuestion = status.workspace.pendingQuestions?.[0];
|
|
1793
1810
|
if (firstQuestion && status.hardStop.nextActions.length === 0) {
|
|
@@ -2328,6 +2345,149 @@ function commandAvailable(command) {
|
|
|
2328
2345
|
return false;
|
|
2329
2346
|
}
|
|
2330
2347
|
}
|
|
2348
|
+
function isRecord(value) {
|
|
2349
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2350
|
+
}
|
|
2351
|
+
function stringArrayValue(value) {
|
|
2352
|
+
if (Array.isArray(value)) {
|
|
2353
|
+
return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
2354
|
+
}
|
|
2355
|
+
return typeof value === "string" && value.trim().length > 0 ? [value] : [];
|
|
2356
|
+
}
|
|
2357
|
+
function stringValue(value) {
|
|
2358
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
2359
|
+
}
|
|
2360
|
+
function currentTmuxReturnPane() {
|
|
2361
|
+
const explicit = stringValue(env.OMX_QUESTION_RETURN_PANE);
|
|
2362
|
+
if (explicit) {
|
|
2363
|
+
return explicit;
|
|
2364
|
+
}
|
|
2365
|
+
return stringValue(env.TMUX_PANE);
|
|
2366
|
+
}
|
|
2367
|
+
function codexTmuxPopupAvailable() {
|
|
2368
|
+
return Boolean(currentTmuxReturnPane() && commandAvailable("omx"));
|
|
2369
|
+
}
|
|
2370
|
+
function buildOmxQuestionInput(record) {
|
|
2371
|
+
const options = record.prompt.options.map((option) => ({
|
|
2372
|
+
label: option.label,
|
|
2373
|
+
value: option.value,
|
|
2374
|
+
...(option.description ? { description: option.description } : {})
|
|
2375
|
+
}));
|
|
2376
|
+
const freeText = record.prompt.type === "free_text";
|
|
2377
|
+
const type = record.prompt.type === "multi_choice" ? "multi-answerable" : "single-answerable";
|
|
2378
|
+
return {
|
|
2379
|
+
header: "LongTable",
|
|
2380
|
+
question: record.prompt.question,
|
|
2381
|
+
questions: [{
|
|
2382
|
+
id: record.id,
|
|
2383
|
+
question: record.prompt.question,
|
|
2384
|
+
options,
|
|
2385
|
+
allow_other: freeText ? true : record.prompt.allowOther,
|
|
2386
|
+
other_label: freeText ? "Answer" : record.prompt.otherLabel ?? "Other",
|
|
2387
|
+
type,
|
|
2388
|
+
multi_select: record.prompt.type === "multi_choice"
|
|
2389
|
+
}],
|
|
2390
|
+
options,
|
|
2391
|
+
allow_other: freeText ? true : record.prompt.allowOther,
|
|
2392
|
+
other_label: freeText ? "Answer" : record.prompt.otherLabel ?? "Other",
|
|
2393
|
+
type,
|
|
2394
|
+
multi_select: record.prompt.type === "multi_choice",
|
|
2395
|
+
source: "longtable",
|
|
2396
|
+
session_id: record.id
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
function readOmxAnswerPayload(payload) {
|
|
2400
|
+
const root = isRecord(payload) ? payload : {};
|
|
2401
|
+
const answers = Array.isArray(root.answers) ? root.answers : [];
|
|
2402
|
+
for (const entry of answers) {
|
|
2403
|
+
if (!isRecord(entry)) {
|
|
2404
|
+
continue;
|
|
2405
|
+
}
|
|
2406
|
+
if (entry.answer !== undefined) {
|
|
2407
|
+
return entry.answer;
|
|
2408
|
+
}
|
|
2409
|
+
return entry;
|
|
2410
|
+
}
|
|
2411
|
+
if (root.answer !== undefined) {
|
|
2412
|
+
return root.answer;
|
|
2413
|
+
}
|
|
2414
|
+
return root;
|
|
2415
|
+
}
|
|
2416
|
+
function questionAnswerFromOmxPayload(record, payload) {
|
|
2417
|
+
const answer = readOmxAnswerPayload(payload);
|
|
2418
|
+
if (typeof answer === "string") {
|
|
2419
|
+
return answer;
|
|
2420
|
+
}
|
|
2421
|
+
if (Array.isArray(answer)) {
|
|
2422
|
+
return stringArrayValue(answer);
|
|
2423
|
+
}
|
|
2424
|
+
if (!isRecord(answer)) {
|
|
2425
|
+
throw new Error("OMX question returned an answer payload LongTable cannot parse.");
|
|
2426
|
+
}
|
|
2427
|
+
if (record.prompt.type === "free_text") {
|
|
2428
|
+
const freeText = [
|
|
2429
|
+
stringValue(answer.other_text),
|
|
2430
|
+
stringValue(answer.otherText),
|
|
2431
|
+
stringValue(answer.text),
|
|
2432
|
+
stringValue(answer.value),
|
|
2433
|
+
stringArrayValue(answer.selected_values).join("\n"),
|
|
2434
|
+
stringArrayValue(answer.selectedValues).join("\n")
|
|
2435
|
+
].find((entry) => entry && entry.trim().length > 0);
|
|
2436
|
+
if (freeText) {
|
|
2437
|
+
return freeText;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
const selectedValues = [
|
|
2441
|
+
...stringArrayValue(answer.selected_values),
|
|
2442
|
+
...stringArrayValue(answer.selectedValues)
|
|
2443
|
+
];
|
|
2444
|
+
const otherText = stringValue(answer.other_text) ?? stringValue(answer.otherText) ?? stringValue(answer.text);
|
|
2445
|
+
if (answer.kind === "other" && otherText) {
|
|
2446
|
+
return { selectedValues: ["other"], otherText };
|
|
2447
|
+
}
|
|
2448
|
+
if (selectedValues.length > 0) {
|
|
2449
|
+
return otherText
|
|
2450
|
+
? { selectedValues: selectedValues.includes("other") ? selectedValues : [...selectedValues, "other"], otherText }
|
|
2451
|
+
: selectedValues;
|
|
2452
|
+
}
|
|
2453
|
+
const valueValues = stringArrayValue(answer.value);
|
|
2454
|
+
if (valueValues.length > 0) {
|
|
2455
|
+
return valueValues.length === 1 ? valueValues[0] : valueValues;
|
|
2456
|
+
}
|
|
2457
|
+
if (otherText) {
|
|
2458
|
+
return otherText;
|
|
2459
|
+
}
|
|
2460
|
+
throw new Error("OMX question did not include an answer value.");
|
|
2461
|
+
}
|
|
2462
|
+
function invokeOmxQuestionPopup(record) {
|
|
2463
|
+
const returnPane = currentTmuxReturnPane();
|
|
2464
|
+
if (!returnPane || !commandAvailable("omx")) {
|
|
2465
|
+
throw new Error("LongTable tmux_popup transport requires an attached tmux pane and the `omx` command.");
|
|
2466
|
+
}
|
|
2467
|
+
const child = spawnSync("omx", ["question", "--input", JSON.stringify(buildOmxQuestionInput(record)), "--json"], {
|
|
2468
|
+
cwd: cwd(),
|
|
2469
|
+
env: {
|
|
2470
|
+
...env,
|
|
2471
|
+
OMX_QUESTION_RETURN_PANE: returnPane
|
|
2472
|
+
},
|
|
2473
|
+
encoding: "utf8",
|
|
2474
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2475
|
+
});
|
|
2476
|
+
if (child.error) {
|
|
2477
|
+
throw child.error;
|
|
2478
|
+
}
|
|
2479
|
+
if (child.status !== 0) {
|
|
2480
|
+
const detail = [String(child.stderr ?? "").trim(), String(child.stdout ?? "").trim()].filter(Boolean).join("\n");
|
|
2481
|
+
throw new Error(`LongTable tmux_popup transport failed${detail ? `: ${detail}` : "."}`);
|
|
2482
|
+
}
|
|
2483
|
+
const raw = String(child.stdout ?? "").trim();
|
|
2484
|
+
try {
|
|
2485
|
+
return raw ? JSON.parse(raw) : {};
|
|
2486
|
+
}
|
|
2487
|
+
catch {
|
|
2488
|
+
throw new Error(`LongTable tmux_popup transport returned non-JSON output: ${raw.slice(0, 200)}`);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2331
2491
|
function parseWaitMs(value) {
|
|
2332
2492
|
if (value === undefined || value === false) {
|
|
2333
2493
|
return undefined;
|
|
@@ -2348,6 +2508,7 @@ function panelWorkerNextCommands(context, runId) {
|
|
|
2348
2508
|
return {
|
|
2349
2509
|
status: `longtable panel status ${cwdFlag} --run ${runId}`,
|
|
2350
2510
|
stop: `longtable panel stop ${cwdFlag} --run ${runId}`,
|
|
2511
|
+
shutdown: `longtable panel shutdown ${cwdFlag} --run ${runId}`,
|
|
2351
2512
|
resume: `longtable panel resume ${cwdFlag} --run ${runId}`
|
|
2352
2513
|
};
|
|
2353
2514
|
}
|
|
@@ -2377,6 +2538,32 @@ function summarizePanelRecordOutput(result) {
|
|
|
2377
2538
|
evidenceRecordIds: result.evidenceRecords.map((record) => record.id)
|
|
2378
2539
|
};
|
|
2379
2540
|
}
|
|
2541
|
+
function summarizePanelWorkerExecution(run) {
|
|
2542
|
+
const bridgeStatus = run.bridgeStatus ?? run.status;
|
|
2543
|
+
const bridgeFailureReason = run.bridgeFailureReason ?? (bridgeStatus === "degraded"
|
|
2544
|
+
? "native worker bridge unavailable before execution"
|
|
2545
|
+
: bridgeStatus === "failed" || bridgeStatus === "blocked"
|
|
2546
|
+
? "native worker bridge did not complete all role passes"
|
|
2547
|
+
: undefined);
|
|
2548
|
+
return {
|
|
2549
|
+
requestedSurface: run.requestedSurface,
|
|
2550
|
+
fallbackSurface: run.fallbackSurface,
|
|
2551
|
+
bridgeStatus,
|
|
2552
|
+
bridgeFailureReason,
|
|
2553
|
+
sequentialFallbackExecuted: false,
|
|
2554
|
+
paneIds: run.workers.map((worker) => worker.paneId).filter((paneId) => typeof paneId === "string" && paneId.length > 0),
|
|
2555
|
+
workers: run.workers.map((worker) => ({
|
|
2556
|
+
id: worker.id,
|
|
2557
|
+
role: worker.role,
|
|
2558
|
+
label: worker.label,
|
|
2559
|
+
status: worker.status,
|
|
2560
|
+
paneId: worker.paneId,
|
|
2561
|
+
resultPath: worker.resultPath,
|
|
2562
|
+
diagnostics: worker.diagnostics
|
|
2563
|
+
})),
|
|
2564
|
+
diagnostics: run.diagnostics
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2380
2567
|
async function runPanelStatusCommand(args) {
|
|
2381
2568
|
const context = await requireWorkspaceContext(args);
|
|
2382
2569
|
const runId = requireRunId(args);
|
|
@@ -2386,7 +2573,12 @@ async function runPanelStatusCommand(args) {
|
|
|
2386
2573
|
const recordedPanelResult = await recordTerminalNativeWorkerRun(context, refreshed);
|
|
2387
2574
|
const nextCommands = panelWorkerNextCommands(context, refreshed.id);
|
|
2388
2575
|
if (args.json === true) {
|
|
2389
|
-
console.log(JSON.stringify({
|
|
2576
|
+
console.log(JSON.stringify({
|
|
2577
|
+
...refreshed,
|
|
2578
|
+
execution: summarizePanelWorkerExecution(refreshed),
|
|
2579
|
+
nextCommands,
|
|
2580
|
+
recordedPanelResult: summarizePanelRecordOutput(recordedPanelResult)
|
|
2581
|
+
}, null, 2));
|
|
2390
2582
|
return;
|
|
2391
2583
|
}
|
|
2392
2584
|
console.log("LongTable panel run status");
|
|
@@ -2396,6 +2588,7 @@ async function runPanelStatusCommand(args) {
|
|
|
2396
2588
|
console.log(`- ${worker.label} (${worker.role}): ${worker.status}`);
|
|
2397
2589
|
}
|
|
2398
2590
|
console.log(`- stop: ${nextCommands.stop}`);
|
|
2591
|
+
console.log(`- shutdown: ${nextCommands.shutdown}`);
|
|
2399
2592
|
console.log(`- resume: ${nextCommands.resume}`);
|
|
2400
2593
|
if (recordedPanelResult) {
|
|
2401
2594
|
console.log(`- recorded evidence: ${recordedPanelResult.evidenceRecords.length}`);
|
|
@@ -2416,13 +2609,36 @@ async function runPanelStopCommand(args) {
|
|
|
2416
2609
|
for (const worker of stopped.workers) {
|
|
2417
2610
|
console.log(`- ${worker.label} (${worker.role}): ${worker.status}`);
|
|
2418
2611
|
}
|
|
2612
|
+
console.log(`- shutdown: ${nextCommands.shutdown}`);
|
|
2613
|
+
console.log(`- resume: ${nextCommands.resume}`);
|
|
2614
|
+
}
|
|
2615
|
+
async function runPanelShutdownCommand(args) {
|
|
2616
|
+
const context = await requireWorkspaceContext(args);
|
|
2617
|
+
const runId = requireRunId(args);
|
|
2618
|
+
const shutdown = await shutdownPanelWorkerRun(await readPanelWorkerRun(context.project.projectPath, runId));
|
|
2619
|
+
const nextCommands = panelWorkerNextCommands(context, shutdown.id);
|
|
2620
|
+
if (args.json === true) {
|
|
2621
|
+
console.log(JSON.stringify({
|
|
2622
|
+
...shutdown,
|
|
2623
|
+
execution: summarizePanelWorkerExecution(shutdown),
|
|
2624
|
+
nextCommands
|
|
2625
|
+
}, null, 2));
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2628
|
+
console.log("LongTable panel run shutdown requested");
|
|
2629
|
+
console.log(`- run: ${shutdown.id}`);
|
|
2630
|
+
console.log(`- status: ${shutdown.status}`);
|
|
2631
|
+
for (const worker of shutdown.workers) {
|
|
2632
|
+
console.log(`- ${worker.label} (${worker.role}): ${worker.status}`);
|
|
2633
|
+
}
|
|
2634
|
+
console.log(`- status command: ${nextCommands.status}`);
|
|
2419
2635
|
console.log(`- resume: ${nextCommands.resume}`);
|
|
2420
2636
|
}
|
|
2421
2637
|
async function runPanelResumeCommand(args) {
|
|
2422
2638
|
const context = await requireWorkspaceContext(args);
|
|
2423
2639
|
const runId = requireRunId(args);
|
|
2424
2640
|
const waitMs = parseWaitMs(args.wait);
|
|
2425
|
-
const
|
|
2641
|
+
const run = await readPanelWorkerRun(context.project.projectPath, runId);
|
|
2426
2642
|
const resumed = run.status === "completed" ? run : await launchPanelWorkerRun(await resumePanelWorkerRun(run));
|
|
2427
2643
|
const finalRun = waitMs ? await waitForPanelWorkerRun(resumed, waitMs) : resumed;
|
|
2428
2644
|
const recordedPanelResult = await recordTerminalNativeWorkerRun(context, finalRun);
|
|
@@ -2592,10 +2808,17 @@ async function runPanelCommand(args) {
|
|
|
2592
2808
|
nativeRunCreated: Boolean(finalNativeRun),
|
|
2593
2809
|
waitMs,
|
|
2594
2810
|
degradedReason: nativeWorkersRequested && !finalNativeRun
|
|
2595
|
-
? "native workers require an existing LongTable workspace; sequential fallback
|
|
2811
|
+
? "native workers require an existing LongTable workspace; no native run was created and no sequential fallback was executed"
|
|
2596
2812
|
: finalNativeRun?.status === "degraded"
|
|
2597
|
-
? "native
|
|
2598
|
-
: undefined
|
|
2813
|
+
? "native worker bridge unavailable; sequential fallback remains available but was not executed by this native-workers request"
|
|
2814
|
+
: undefined,
|
|
2815
|
+
bridgeStatus: finalNativeRun?.bridgeStatus ?? finalNativeRun?.status,
|
|
2816
|
+
bridgeFailureReason: finalNativeRun?.bridgeFailureReason ?? (finalNativeRun?.status === "degraded"
|
|
2817
|
+
? "native worker bridge unavailable before execution"
|
|
2818
|
+
: finalNativeRun?.status === "failed" || finalNativeRun?.status === "blocked"
|
|
2819
|
+
? "native worker bridge did not complete all role passes"
|
|
2820
|
+
: undefined),
|
|
2821
|
+
sequentialFallbackExecuted: !nativeWorkersRequested
|
|
2599
2822
|
},
|
|
2600
2823
|
nativeRun: finalNativeRun,
|
|
2601
2824
|
recordedPanelResult: summarizePanelRecordOutput(recordedPanelResult),
|
|
@@ -2613,17 +2836,26 @@ async function runPanelCommand(args) {
|
|
|
2613
2836
|
const nextCommands = panelWorkerNextCommands(nativeRunContext, finalNativeRun.id);
|
|
2614
2837
|
console.log(`- next status: ${nextCommands.status}`);
|
|
2615
2838
|
console.log(`- stop: ${nextCommands.stop}`);
|
|
2839
|
+
console.log(`- shutdown: ${nextCommands.shutdown}`);
|
|
2616
2840
|
console.log(`- resume: ${nextCommands.resume}`);
|
|
2617
2841
|
if (recordedPanelResult) {
|
|
2618
2842
|
console.log(`- recorded evidence: ${recordedPanelResult.evidenceRecords.length}`);
|
|
2619
2843
|
}
|
|
2620
2844
|
if (finalNativeRun.status === "degraded") {
|
|
2621
|
-
console.log("- degraded: native workers are unavailable;
|
|
2845
|
+
console.log("- degraded: native workers are unavailable; sequential fallback is available but was not executed by this native-workers request.");
|
|
2622
2846
|
console.log("");
|
|
2623
2847
|
console.log(fallback.prompt);
|
|
2624
2848
|
}
|
|
2625
2849
|
return;
|
|
2626
2850
|
}
|
|
2851
|
+
if (nativeWorkersRequested) {
|
|
2852
|
+
console.log("LongTable native panel worker run was not created");
|
|
2853
|
+
console.log("- status: failed");
|
|
2854
|
+
console.log("- reason: native workers require an existing LongTable workspace; sequential fallback was not executed by this native-workers request.");
|
|
2855
|
+
console.log("");
|
|
2856
|
+
console.log(fallback.prompt);
|
|
2857
|
+
exit(1);
|
|
2858
|
+
}
|
|
2627
2859
|
const exitCode = await runCodexThinWrapper({
|
|
2628
2860
|
prompt: fallback.prompt,
|
|
2629
2861
|
mode,
|
|
@@ -3325,11 +3557,51 @@ async function runSpec(subcommand, args) {
|
|
|
3325
3557
|
}
|
|
3326
3558
|
throw new Error(`Unknown spec subcommand: ${command}`);
|
|
3327
3559
|
}
|
|
3560
|
+
function questionUsesKorean(record) {
|
|
3561
|
+
return /[가-힣]/.test([
|
|
3562
|
+
record.prompt.title,
|
|
3563
|
+
record.prompt.question,
|
|
3564
|
+
record.prompt.displayReason ?? "",
|
|
3565
|
+
...record.prompt.options.flatMap((option) => [option.label, option.description ?? ""]),
|
|
3566
|
+
record.prompt.otherLabel ?? ""
|
|
3567
|
+
].join("\n"));
|
|
3568
|
+
}
|
|
3569
|
+
function renderQuestionDecisionCard(record) {
|
|
3570
|
+
const korean = questionUsesKorean(record);
|
|
3571
|
+
const lines = [
|
|
3572
|
+
korean ? "LongTable 결정 카드" : "LongTable Decision Card",
|
|
3573
|
+
`${korean ? "체크포인트" : "Checkpoint"}: ${record.prompt.title}`,
|
|
3574
|
+
`${korean ? "무엇이 걸렸나" : "What is blocked"}: ${record.prompt.displayReason ?? record.prompt.rationale[0] ?? record.prompt.title}`,
|
|
3575
|
+
`${korean ? "지금 결정할 것" : "Decision needed"}: ${record.prompt.question}`,
|
|
3576
|
+
korean ? "선택지:" : "Choices:"
|
|
3577
|
+
];
|
|
3578
|
+
record.prompt.options.forEach((option, index) => {
|
|
3579
|
+
const recommended = option.recommended ? (korean ? " (추천)" : " (recommended)") : "";
|
|
3580
|
+
lines.push(`${index + 1}. ${option.label}${recommended}`);
|
|
3581
|
+
if (option.description) {
|
|
3582
|
+
lines.push(` ${option.description}`);
|
|
3583
|
+
}
|
|
3584
|
+
lines.push(` ${korean ? "기록값" : "Record value"}: ${option.value}`);
|
|
3585
|
+
});
|
|
3586
|
+
if (record.prompt.allowOther) {
|
|
3587
|
+
lines.push(`${record.prompt.options.length + 1}. ${record.prompt.otherLabel ?? (korean ? "직접 입력" : "Other")}`);
|
|
3588
|
+
lines.push(` ${korean ? "기록값" : "Record value"}: other`);
|
|
3589
|
+
}
|
|
3590
|
+
return lines.join("\n");
|
|
3591
|
+
}
|
|
3328
3592
|
async function runQuestion(args) {
|
|
3329
3593
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
3330
|
-
const
|
|
3331
|
-
if (
|
|
3332
|
-
|
|
3594
|
+
const requestedSurface = parseQuestionSurface(args.surface);
|
|
3595
|
+
if (requestedSurface &&
|
|
3596
|
+
requestedSurface !== "tmux_popup" &&
|
|
3597
|
+
requestedSurface !== "terminal_selector" &&
|
|
3598
|
+
requestedSurface !== "numbered") {
|
|
3599
|
+
throw new Error("The LongTable question CLI can render tmux_popup, terminal_selector, or numbered surfaces. Use the MCP or provider-native runtime for other surfaces.");
|
|
3600
|
+
}
|
|
3601
|
+
const questionId = typeof args.question === "string" ? args.question.trim() : "";
|
|
3602
|
+
const prompt = questionId ? undefined : await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
3603
|
+
if (!prompt && !questionId) {
|
|
3604
|
+
throw new Error("A decision context or pending question id is required. Pass --prompt <text> or --question <id>.");
|
|
3333
3605
|
}
|
|
3334
3606
|
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
3335
3607
|
if (!context) {
|
|
@@ -3337,17 +3609,73 @@ async function runQuestion(args) {
|
|
|
3337
3609
|
}
|
|
3338
3610
|
const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
|
|
3339
3611
|
const required = args.required === true ? true : args.advisory === true ? false : undefined;
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
question
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3612
|
+
let result;
|
|
3613
|
+
if (questionId) {
|
|
3614
|
+
const state = await loadWorkspaceState(context);
|
|
3615
|
+
const question = state.questionLog.find((record) => record.id === questionId && record.status === "pending");
|
|
3616
|
+
if (!question) {
|
|
3617
|
+
throw new Error(`No pending LongTable question found for ${questionId}.`);
|
|
3618
|
+
}
|
|
3619
|
+
result = { question, state };
|
|
3620
|
+
}
|
|
3621
|
+
else {
|
|
3622
|
+
result = await createWorkspaceQuestion({
|
|
3623
|
+
context,
|
|
3624
|
+
prompt: prompt ?? "",
|
|
3625
|
+
title: typeof args.title === "string" ? args.title : undefined,
|
|
3626
|
+
question: typeof args.text === "string" ? args.text : undefined,
|
|
3627
|
+
provider,
|
|
3628
|
+
required
|
|
3629
|
+
});
|
|
3630
|
+
}
|
|
3348
3631
|
const transport = provider === "claude"
|
|
3349
3632
|
? renderQuestionRecordInput(result.question)
|
|
3350
3633
|
: renderQuestionRecordPrompt(result.question);
|
|
3634
|
+
const shouldTryTmuxPopup = requestedSurface === "tmux_popup" ||
|
|
3635
|
+
(requestedSurface === undefined && provider === "codex" && codexTmuxPopupAvailable() && args.print !== true && args.json !== true);
|
|
3636
|
+
if (shouldTryTmuxPopup) {
|
|
3637
|
+
try {
|
|
3638
|
+
const popupPayload = invokeOmxQuestionPopup(result.question);
|
|
3639
|
+
const answer = questionAnswerFromOmxPayload(result.question, popupPayload);
|
|
3640
|
+
const decision = await answerWorkspaceQuestion({
|
|
3641
|
+
context,
|
|
3642
|
+
questionId: result.question.id,
|
|
3643
|
+
answer,
|
|
3644
|
+
provider,
|
|
3645
|
+
surface: "tmux_popup"
|
|
3646
|
+
});
|
|
3647
|
+
if (args.json === true) {
|
|
3648
|
+
console.log(JSON.stringify({
|
|
3649
|
+
question: decision.question,
|
|
3650
|
+
decision: decision.decision,
|
|
3651
|
+
transport: {
|
|
3652
|
+
surface: "tmux_popup",
|
|
3653
|
+
status: "accepted"
|
|
3654
|
+
},
|
|
3655
|
+
files: {
|
|
3656
|
+
state: context.stateFilePath,
|
|
3657
|
+
current: context.currentFilePath
|
|
3658
|
+
}
|
|
3659
|
+
}, null, 2));
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
console.log("LongTable checkpoint decision recorded");
|
|
3663
|
+
console.log(`- question: ${decision.question.id}`);
|
|
3664
|
+
console.log(`- decision: ${decision.decision.id}`);
|
|
3665
|
+
console.log(`- surface: tmux_popup`);
|
|
3666
|
+
console.log(`- answer: ${decision.decision.selectedOption ?? "recorded"}`);
|
|
3667
|
+
console.log(`- state: ${context.stateFilePath}`);
|
|
3668
|
+
console.log(`- current: ${context.currentFilePath}`);
|
|
3669
|
+
return;
|
|
3670
|
+
}
|
|
3671
|
+
catch (error) {
|
|
3672
|
+
if (requestedSurface === "tmux_popup") {
|
|
3673
|
+
throw error;
|
|
3674
|
+
}
|
|
3675
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3676
|
+
console.error(`LongTable tmux_popup transport unavailable; falling back. ${message}`);
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3351
3679
|
if (args.json === true) {
|
|
3352
3680
|
console.log(JSON.stringify({
|
|
3353
3681
|
question: result.question,
|
|
@@ -3369,7 +3697,7 @@ async function runQuestion(args) {
|
|
|
3369
3697
|
}
|
|
3370
3698
|
return;
|
|
3371
3699
|
}
|
|
3372
|
-
if (isInteractiveTerminal()) {
|
|
3700
|
+
if ((requestedSurface === undefined || requestedSurface === "terminal_selector") && isInteractiveTerminal()) {
|
|
3373
3701
|
console.log(renderBrandBanner("LongTable", "Researcher Checkpoint"));
|
|
3374
3702
|
console.log("");
|
|
3375
3703
|
const answer = await promptChoice(renderQuestionHeader(1, 1, result.question.prompt.title, result.question.prompt.question), questionRecordToChoices(result.question));
|
|
@@ -3389,16 +3717,11 @@ async function runQuestion(args) {
|
|
|
3389
3717
|
console.log(`- current: ${context.currentFilePath}`);
|
|
3390
3718
|
return;
|
|
3391
3719
|
}
|
|
3392
|
-
const optionValues = [
|
|
3393
|
-
...result.question.prompt.options.map((option) => option.value),
|
|
3394
|
-
...(result.question.prompt.allowOther ? ["other"] : [])
|
|
3395
|
-
];
|
|
3396
3720
|
console.log(result.question.prompt.required ? "LongTable required Researcher Checkpoint recorded" : "LongTable advisory Researcher Checkpoint recorded");
|
|
3397
3721
|
console.log(`- question: ${result.question.id}`);
|
|
3398
3722
|
console.log(`- checkpoint: ${result.question.prompt.checkpointKey ?? "manual"}`);
|
|
3399
|
-
console.log(
|
|
3400
|
-
console.log(`-
|
|
3401
|
-
console.log(`- answer: longtable decide --question ${result.question.id} --answer <value>`);
|
|
3723
|
+
console.log(renderQuestionDecisionCard(result.question));
|
|
3724
|
+
console.log(`- answer: longtable decide --question ${result.question.id} --answer <value> --surface numbered`);
|
|
3402
3725
|
console.log(`- current: ${context.currentFilePath}`);
|
|
3403
3726
|
}
|
|
3404
3727
|
async function runClearQuestion(args) {
|
|
@@ -3436,39 +3759,6 @@ async function runClearQuestion(args) {
|
|
|
3436
3759
|
console.log(`- state: ${context.stateFilePath}`);
|
|
3437
3760
|
console.log(`- current: ${context.currentFilePath}`);
|
|
3438
3761
|
}
|
|
3439
|
-
async function runRepairState(args) {
|
|
3440
|
-
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
3441
|
-
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
3442
|
-
if (!context) {
|
|
3443
|
-
throw new Error("No LongTable project workspace was found here. Run this inside a project or pass --cwd.");
|
|
3444
|
-
}
|
|
3445
|
-
const result = await repairWorkspaceStateConsistency({
|
|
3446
|
-
context,
|
|
3447
|
-
dryRun: args["dry-run"] === true
|
|
3448
|
-
});
|
|
3449
|
-
if (args.json === true) {
|
|
3450
|
-
console.log(JSON.stringify({
|
|
3451
|
-
dryRun: args["dry-run"] === true,
|
|
3452
|
-
repaired: result.repaired,
|
|
3453
|
-
files: {
|
|
3454
|
-
state: context.stateFilePath,
|
|
3455
|
-
current: context.currentFilePath
|
|
3456
|
-
}
|
|
3457
|
-
}, null, 2));
|
|
3458
|
-
return;
|
|
3459
|
-
}
|
|
3460
|
-
console.log(args["dry-run"] === true ? "LongTable state repair preview" : "LongTable state repaired");
|
|
3461
|
-
if (result.repaired.length === 0) {
|
|
3462
|
-
console.log("- no repairs needed");
|
|
3463
|
-
}
|
|
3464
|
-
else {
|
|
3465
|
-
for (const item of result.repaired) {
|
|
3466
|
-
console.log(`- ${item}`);
|
|
3467
|
-
}
|
|
3468
|
-
}
|
|
3469
|
-
console.log(`- state: ${context.stateFilePath}`);
|
|
3470
|
-
console.log(`- current: ${context.currentFilePath}`);
|
|
3471
|
-
}
|
|
3472
3762
|
async function runPruneQuestions(args) {
|
|
3473
3763
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
3474
3764
|
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
@@ -3893,12 +4183,14 @@ async function runDecide(args) {
|
|
|
3893
4183
|
throw new Error("No LongTable project workspace was found here. Run this inside a project or pass --cwd.");
|
|
3894
4184
|
}
|
|
3895
4185
|
const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
|
|
4186
|
+
const surface = parseQuestionSurface(args.surface);
|
|
3896
4187
|
const result = await answerWorkspaceQuestion({
|
|
3897
4188
|
context,
|
|
3898
4189
|
questionId: typeof args.question === "string" ? args.question : undefined,
|
|
3899
4190
|
answer,
|
|
3900
4191
|
rationale: typeof args.rationale === "string" ? args.rationale : undefined,
|
|
3901
|
-
provider
|
|
4192
|
+
provider,
|
|
4193
|
+
...(surface ? { surface } : {})
|
|
3902
4194
|
});
|
|
3903
4195
|
if (args.json === true) {
|
|
3904
4196
|
console.log(JSON.stringify({
|
|
@@ -4316,10 +4608,6 @@ async function main() {
|
|
|
4316
4608
|
await runClearQuestion(values);
|
|
4317
4609
|
return;
|
|
4318
4610
|
}
|
|
4319
|
-
if (command === "repair-state") {
|
|
4320
|
-
await runRepairState(values);
|
|
4321
|
-
return;
|
|
4322
|
-
}
|
|
4323
4611
|
if (command === "prune-questions") {
|
|
4324
4612
|
await runPruneQuestions(values);
|
|
4325
4613
|
return;
|
|
@@ -4337,6 +4625,10 @@ async function main() {
|
|
|
4337
4625
|
await runPanelStopCommand(values);
|
|
4338
4626
|
return;
|
|
4339
4627
|
}
|
|
4628
|
+
if (subcommand === "shutdown") {
|
|
4629
|
+
await runPanelShutdownCommand(values);
|
|
4630
|
+
return;
|
|
4631
|
+
}
|
|
4340
4632
|
if (subcommand === "resume") {
|
|
4341
4633
|
await runPanelResumeCommand(values);
|
|
4342
4634
|
return;
|