@longtable/cli 0.1.58 → 0.1.60
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 +358 -30
- package/dist/debate.js +19 -31
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/longtable-codex-native-hook.js +36 -8
- package/dist/panel.d.ts +13 -1
- package/dist/panel.js +339 -27
- package/dist/project-session.js +88 -5
- package/dist/search/index.d.ts +1 -6
- package/dist/search/index.js +1 -6
- package/package.json +8 -7
- package/dist/search/publisher-access.d.ts +0 -21
- package/dist/search/publisher-access.js +0 -564
- package/dist/search/query.d.ts +0 -6
- package/dist/search/query.js +0 -179
- package/dist/search/rank.d.ts +0 -2
- package/dist/search/rank.js +0 -173
- package/dist/search/run.d.ts +0 -2
- package/dist/search/run.js +0 -114
- package/dist/search/sources.d.ts +0 -5
- package/dist/search/sources.js +0 -537
- package/dist/search/types.d.ts +0 -181
- package/dist/search/types.js +0 -16
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";
|
|
@@ -10,7 +10,7 @@ import { homedir } from "node:os";
|
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { collectHardStopBlockers } from "@longtable/core";
|
|
12
12
|
import { classifyCheckpointTrigger } from "@longtable/checkpoints";
|
|
13
|
-
import { assessSearchSourceCapabilities, buildResearchSearchIntent, parsePublisherTarget, probePublisherAccess, publisherConfigs, runResearchSearch, SEARCH_SOURCES, summarizeConfiguredPublisherAccess } from "./search/index.js";
|
|
13
|
+
import { assessSearchSourceCapabilities, assessScholarResearchReadiness, buildResearchSearchIntent, buildScholarResearchSmokeFixture, parsePublisherTarget, probePublisherAccess, publisherConfigs, runResearchSearch, SEARCH_SOURCES, writeScholarResearchRunScaffold, summarizeConfiguredPublisherAccess } from "./search/index.js";
|
|
14
14
|
import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupOutput, saveSetupAndRuntimeConfig, serializeSetupOutput, writeRuntimeConfig } from "@longtable/setup";
|
|
15
15
|
import { buildCodexSkillSpecs, buildCodexThinWrappedPrompt, installCodexSkills, listInstalledCodexSkills, renderQuestionRecordPrompt, removeCodexSkills, resolveCodexSkillsDir, runCodexThinWrapper } from "@longtable/provider-codex";
|
|
16
16
|
import { buildClaudeSkillSpecs, installClaudeSkills, listInstalledClaudeSkills, renderQuestionRecordInput, removeClaudeSkills, resolveClaudeSkillsDir } from "@longtable/provider-claude";
|
|
@@ -154,10 +154,13 @@ function usage() {
|
|
|
154
154
|
" longtable access doctor [--doi <doi>] [--publisher auto|elsevier|springer_nature|wiley|taylor_francis|all] [--json]",
|
|
155
155
|
" longtable access probe --doi <doi> [--publisher auto|elsevier|springer_nature|wiley|taylor_francis] [--json]",
|
|
156
156
|
" longtable search --query <text> [--intent literature|theory|measurement|citation|metadata|venue] [--field <text>] [--source all|crossref,arxiv,openalex,semantic_scholar,pubmed,eric,doaj,unpaywall] [--must <term[,term]>] [--exclude <term[,term]>] [--limit <n>] [--allow-partial] [--publisher-access] [--record] [--cwd <path>] [--json]",
|
|
157
|
+
" longtable scholar-research doctor [--json]",
|
|
158
|
+
" longtable scholar-research scaffold [--cwd <path>] [--run-id <id>] [--json]",
|
|
159
|
+
" longtable scholar-research smoke-fixture [--json]",
|
|
157
160
|
" longtable sentinel --prompt <text> [--cwd <path>] [--json] [--record]",
|
|
158
161
|
" longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
159
162
|
" 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]",
|
|
163
|
+
" 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
164
|
" longtable clear-question --question <id> --reason <text> [--cwd <path>] [--json]",
|
|
162
165
|
" 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>]",
|
|
163
166
|
" longtable panel status --run <panel_run_id> [--wait [ms]] [--cwd <path>] [--json]",
|
|
@@ -166,7 +169,7 @@ function usage() {
|
|
|
166
169
|
" longtable panel resume --run <panel_run_id> [--wait [ms]] [--cwd <path>] [--json]",
|
|
167
170
|
" longtable panel record [--invocation <id>] --result-file <json> [--surface sequential_fallback|native_subagents|native_workers] [--cwd <path>] [--json]",
|
|
168
171
|
" 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]",
|
|
172
|
+
" 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
173
|
" 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
174
|
" longtable codex persist-init [--answers-json <json> | --stdin | full setup flags] [--install-skills] [--install-prompts] [--json]",
|
|
172
175
|
" longtable codex install-skills [--surface compact|full] [--dir <path>]",
|
|
@@ -201,7 +204,7 @@ function parseArgs(argv) {
|
|
|
201
204
|
const values = {};
|
|
202
205
|
let subcommand = maybeSubcommand;
|
|
203
206
|
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", "prune-questions", "panel", "handoff", "decide", "sentinel", "access", "search", "spec"].includes(command);
|
|
207
|
+
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", "scholar-research", "spec"].includes(command);
|
|
205
208
|
let startIndex = 1;
|
|
206
209
|
if (modeCommand) {
|
|
207
210
|
subcommand = undefined;
|
|
@@ -210,7 +213,7 @@ function parseArgs(argv) {
|
|
|
210
213
|
else if (command === "codex" || command === "claude" || command === "mcp") {
|
|
211
214
|
startIndex = 2;
|
|
212
215
|
}
|
|
213
|
-
else if ((command === "access" || command === "search" || command === "spec" || command === "panel") && maybeSubcommand && !maybeSubcommand.startsWith("--")) {
|
|
216
|
+
else if ((command === "access" || command === "search" || command === "scholar-research" || command === "spec" || command === "panel") && maybeSubcommand && !maybeSubcommand.startsWith("--")) {
|
|
214
217
|
subcommand = maybeSubcommand;
|
|
215
218
|
startIndex = 2;
|
|
216
219
|
}
|
|
@@ -299,6 +302,27 @@ function parseSkillSurface(args) {
|
|
|
299
302
|
}
|
|
300
303
|
throw new Error("Invalid --surface value. Use compact or full.");
|
|
301
304
|
}
|
|
305
|
+
const QUESTION_SURFACE_VALUES = [
|
|
306
|
+
"native_structured",
|
|
307
|
+
"tmux_popup",
|
|
308
|
+
"mcp_elicitation",
|
|
309
|
+
"numbered",
|
|
310
|
+
"terminal_selector",
|
|
311
|
+
"web_form"
|
|
312
|
+
];
|
|
313
|
+
function parseQuestionSurface(value) {
|
|
314
|
+
if (value === undefined || value === false) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
if (value === true) {
|
|
318
|
+
throw new Error(`Invalid --surface value. Use one of: ${QUESTION_SURFACE_VALUES.join(", ")}.`);
|
|
319
|
+
}
|
|
320
|
+
const normalized = value.trim();
|
|
321
|
+
if (QUESTION_SURFACE_VALUES.includes(normalized)) {
|
|
322
|
+
return normalized;
|
|
323
|
+
}
|
|
324
|
+
throw new Error(`Invalid --surface value. Use one of: ${QUESTION_SURFACE_VALUES.join(", ")}.`);
|
|
325
|
+
}
|
|
302
326
|
function stripWrappingQuotes(value) {
|
|
303
327
|
const trimmed = value.trim();
|
|
304
328
|
if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) ||
|
|
@@ -970,15 +994,25 @@ async function collectProjectInterview(setup, args) {
|
|
|
970
994
|
]));
|
|
971
995
|
console.log("");
|
|
972
996
|
}
|
|
973
|
-
const
|
|
997
|
+
const projectNameInput = (typeof args.name === "string" && args.name.trim()) ||
|
|
974
998
|
(await promptText(renderQuestionHeader(1, 2, "Workspace", "What should this project be called?"), true));
|
|
999
|
+
if (!projectNameInput) {
|
|
1000
|
+
throw new Error("LongTable start needs a project name.");
|
|
1001
|
+
}
|
|
1002
|
+
const projectName = projectNameInput;
|
|
975
1003
|
const suggestedParentDir = typeof args.path === "string" && args.path.trim()
|
|
976
1004
|
? normalizeUserPath(args.path.trim())
|
|
977
1005
|
: homedir();
|
|
978
1006
|
const suggestedPath = resolveInteractiveProjectPath(suggestedParentDir, projectName);
|
|
979
|
-
const
|
|
1007
|
+
const projectPathInput = typeof args.path === "string" && args.path.trim()
|
|
980
1008
|
? normalizeUserPath(args.path.trim())
|
|
981
|
-
:
|
|
1009
|
+
: await promptText(renderQuestionHeader(2, 2, "Workspace", `Which parent directory should contain this project?\nLongTable will create this folder:\n${suggestedPath}`), true);
|
|
1010
|
+
if (!projectPathInput) {
|
|
1011
|
+
throw new Error("LongTable start needs a project path.");
|
|
1012
|
+
}
|
|
1013
|
+
const projectPath = typeof args.path === "string" && args.path.trim()
|
|
1014
|
+
? projectPathInput
|
|
1015
|
+
: resolveInteractiveProjectPath(projectPathInput, projectName);
|
|
982
1016
|
const adaptive = skipResearchInterview
|
|
983
1017
|
? {}
|
|
984
1018
|
: await collectAdaptiveStartInterview({
|
|
@@ -1616,6 +1650,7 @@ async function collectDoctorStatus(args) {
|
|
|
1616
1650
|
return {
|
|
1617
1651
|
setupPath,
|
|
1618
1652
|
setupExists: existsSync(setupPath),
|
|
1653
|
+
scholarResearch: assessScholarResearchReadiness(env),
|
|
1619
1654
|
providers: {
|
|
1620
1655
|
codex: {
|
|
1621
1656
|
command: "codex",
|
|
@@ -1679,6 +1714,8 @@ function renderDoctorStatus(status) {
|
|
|
1679
1714
|
const lines = [
|
|
1680
1715
|
"LongTable doctor",
|
|
1681
1716
|
`- setup: ${status.setupExists ? "present" : "missing"} (${status.setupPath})`,
|
|
1717
|
+
`- scholar-research connectors: ${status.scholarResearch.connectors.filter((connector) => connector.status === "ready").length}/${status.scholarResearch.connectors.length} ready`,
|
|
1718
|
+
`- scholar-research safety: paywall bypass ${status.scholarResearch.safety.paywallBypassAllowed ? "allowed" : "disabled"}, institution login automation ${status.scholarResearch.safety.institutionLoginAutomationAllowed ? "allowed" : "disabled"}`,
|
|
1682
1719
|
"",
|
|
1683
1720
|
...renderProviderDoctorBlock("Codex", status.providers.codex),
|
|
1684
1721
|
`- legacy prompt files: ${status.providers.codex.legacyPromptFilesInstalled.length}`,
|
|
@@ -2062,12 +2099,14 @@ function buildRoleAuditEntry(provider, spec) {
|
|
|
2062
2099
|
}
|
|
2063
2100
|
function runRoleAudit() {
|
|
2064
2101
|
const baseSkillNames = new Set([
|
|
2102
|
+
"critical-interview",
|
|
2065
2103
|
"longtable",
|
|
2066
2104
|
"longtable-start",
|
|
2067
2105
|
"longtable-interview",
|
|
2068
2106
|
"longtable-panel",
|
|
2069
2107
|
"longtable-explore",
|
|
2070
|
-
"longtable-review"
|
|
2108
|
+
"longtable-review",
|
|
2109
|
+
"scholar-research"
|
|
2071
2110
|
]);
|
|
2072
2111
|
const roles = [
|
|
2073
2112
|
...buildCodexSkillSpecs(listRoleDefinitions(), "full")
|
|
@@ -2324,6 +2363,149 @@ function commandAvailable(command) {
|
|
|
2324
2363
|
return false;
|
|
2325
2364
|
}
|
|
2326
2365
|
}
|
|
2366
|
+
function isRecord(value) {
|
|
2367
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2368
|
+
}
|
|
2369
|
+
function stringArrayValue(value) {
|
|
2370
|
+
if (Array.isArray(value)) {
|
|
2371
|
+
return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
2372
|
+
}
|
|
2373
|
+
return typeof value === "string" && value.trim().length > 0 ? [value] : [];
|
|
2374
|
+
}
|
|
2375
|
+
function stringValue(value) {
|
|
2376
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
2377
|
+
}
|
|
2378
|
+
function currentTmuxReturnPane() {
|
|
2379
|
+
const explicit = stringValue(env.OMX_QUESTION_RETURN_PANE);
|
|
2380
|
+
if (explicit) {
|
|
2381
|
+
return explicit;
|
|
2382
|
+
}
|
|
2383
|
+
return stringValue(env.TMUX_PANE);
|
|
2384
|
+
}
|
|
2385
|
+
function codexTmuxPopupAvailable() {
|
|
2386
|
+
return Boolean(currentTmuxReturnPane() && commandAvailable("omx"));
|
|
2387
|
+
}
|
|
2388
|
+
function buildOmxQuestionInput(record) {
|
|
2389
|
+
const options = record.prompt.options.map((option) => ({
|
|
2390
|
+
label: option.label,
|
|
2391
|
+
value: option.value,
|
|
2392
|
+
...(option.description ? { description: option.description } : {})
|
|
2393
|
+
}));
|
|
2394
|
+
const freeText = record.prompt.type === "free_text";
|
|
2395
|
+
const type = record.prompt.type === "multi_choice" ? "multi-answerable" : "single-answerable";
|
|
2396
|
+
return {
|
|
2397
|
+
header: "LongTable",
|
|
2398
|
+
question: record.prompt.question,
|
|
2399
|
+
questions: [{
|
|
2400
|
+
id: record.id,
|
|
2401
|
+
question: record.prompt.question,
|
|
2402
|
+
options,
|
|
2403
|
+
allow_other: freeText ? true : record.prompt.allowOther,
|
|
2404
|
+
other_label: freeText ? "Answer" : record.prompt.otherLabel ?? "Other",
|
|
2405
|
+
type,
|
|
2406
|
+
multi_select: record.prompt.type === "multi_choice"
|
|
2407
|
+
}],
|
|
2408
|
+
options,
|
|
2409
|
+
allow_other: freeText ? true : record.prompt.allowOther,
|
|
2410
|
+
other_label: freeText ? "Answer" : record.prompt.otherLabel ?? "Other",
|
|
2411
|
+
type,
|
|
2412
|
+
multi_select: record.prompt.type === "multi_choice",
|
|
2413
|
+
source: "longtable",
|
|
2414
|
+
session_id: record.id
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
function readOmxAnswerPayload(payload) {
|
|
2418
|
+
const root = isRecord(payload) ? payload : {};
|
|
2419
|
+
const answers = Array.isArray(root.answers) ? root.answers : [];
|
|
2420
|
+
for (const entry of answers) {
|
|
2421
|
+
if (!isRecord(entry)) {
|
|
2422
|
+
continue;
|
|
2423
|
+
}
|
|
2424
|
+
if (entry.answer !== undefined) {
|
|
2425
|
+
return entry.answer;
|
|
2426
|
+
}
|
|
2427
|
+
return entry;
|
|
2428
|
+
}
|
|
2429
|
+
if (root.answer !== undefined) {
|
|
2430
|
+
return root.answer;
|
|
2431
|
+
}
|
|
2432
|
+
return root;
|
|
2433
|
+
}
|
|
2434
|
+
function questionAnswerFromOmxPayload(record, payload) {
|
|
2435
|
+
const answer = readOmxAnswerPayload(payload);
|
|
2436
|
+
if (typeof answer === "string") {
|
|
2437
|
+
return answer;
|
|
2438
|
+
}
|
|
2439
|
+
if (Array.isArray(answer)) {
|
|
2440
|
+
return stringArrayValue(answer);
|
|
2441
|
+
}
|
|
2442
|
+
if (!isRecord(answer)) {
|
|
2443
|
+
throw new Error("OMX question returned an answer payload LongTable cannot parse.");
|
|
2444
|
+
}
|
|
2445
|
+
if (record.prompt.type === "free_text") {
|
|
2446
|
+
const freeText = [
|
|
2447
|
+
stringValue(answer.other_text),
|
|
2448
|
+
stringValue(answer.otherText),
|
|
2449
|
+
stringValue(answer.text),
|
|
2450
|
+
stringValue(answer.value),
|
|
2451
|
+
stringArrayValue(answer.selected_values).join("\n"),
|
|
2452
|
+
stringArrayValue(answer.selectedValues).join("\n")
|
|
2453
|
+
].find((entry) => entry && entry.trim().length > 0);
|
|
2454
|
+
if (freeText) {
|
|
2455
|
+
return freeText;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
const selectedValues = [
|
|
2459
|
+
...stringArrayValue(answer.selected_values),
|
|
2460
|
+
...stringArrayValue(answer.selectedValues)
|
|
2461
|
+
];
|
|
2462
|
+
const otherText = stringValue(answer.other_text) ?? stringValue(answer.otherText) ?? stringValue(answer.text);
|
|
2463
|
+
if (answer.kind === "other" && otherText) {
|
|
2464
|
+
return { selectedValues: ["other"], otherText };
|
|
2465
|
+
}
|
|
2466
|
+
if (selectedValues.length > 0) {
|
|
2467
|
+
return otherText
|
|
2468
|
+
? { selectedValues: selectedValues.includes("other") ? selectedValues : [...selectedValues, "other"], otherText }
|
|
2469
|
+
: selectedValues;
|
|
2470
|
+
}
|
|
2471
|
+
const valueValues = stringArrayValue(answer.value);
|
|
2472
|
+
if (valueValues.length > 0) {
|
|
2473
|
+
return valueValues.length === 1 ? valueValues[0] : valueValues;
|
|
2474
|
+
}
|
|
2475
|
+
if (otherText) {
|
|
2476
|
+
return otherText;
|
|
2477
|
+
}
|
|
2478
|
+
throw new Error("OMX question did not include an answer value.");
|
|
2479
|
+
}
|
|
2480
|
+
function invokeOmxQuestionPopup(record) {
|
|
2481
|
+
const returnPane = currentTmuxReturnPane();
|
|
2482
|
+
if (!returnPane || !commandAvailable("omx")) {
|
|
2483
|
+
throw new Error("LongTable tmux_popup transport requires an attached tmux pane and the `omx` command.");
|
|
2484
|
+
}
|
|
2485
|
+
const child = spawnSync("omx", ["question", "--input", JSON.stringify(buildOmxQuestionInput(record)), "--json"], {
|
|
2486
|
+
cwd: cwd(),
|
|
2487
|
+
env: {
|
|
2488
|
+
...env,
|
|
2489
|
+
OMX_QUESTION_RETURN_PANE: returnPane
|
|
2490
|
+
},
|
|
2491
|
+
encoding: "utf8",
|
|
2492
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2493
|
+
});
|
|
2494
|
+
if (child.error) {
|
|
2495
|
+
throw child.error;
|
|
2496
|
+
}
|
|
2497
|
+
if (child.status !== 0) {
|
|
2498
|
+
const detail = [String(child.stderr ?? "").trim(), String(child.stdout ?? "").trim()].filter(Boolean).join("\n");
|
|
2499
|
+
throw new Error(`LongTable tmux_popup transport failed${detail ? `: ${detail}` : "."}`);
|
|
2500
|
+
}
|
|
2501
|
+
const raw = String(child.stdout ?? "").trim();
|
|
2502
|
+
try {
|
|
2503
|
+
return raw ? JSON.parse(raw) : {};
|
|
2504
|
+
}
|
|
2505
|
+
catch {
|
|
2506
|
+
throw new Error(`LongTable tmux_popup transport returned non-JSON output: ${raw.slice(0, 200)}`);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2327
2509
|
function parseWaitMs(value) {
|
|
2328
2510
|
if (value === undefined || value === false) {
|
|
2329
2511
|
return undefined;
|
|
@@ -2665,6 +2847,9 @@ async function runPanelCommand(args) {
|
|
|
2665
2847
|
console.log(renderPanelSummary(fallback.plan));
|
|
2666
2848
|
console.log("");
|
|
2667
2849
|
if (finalNativeRun) {
|
|
2850
|
+
if (!nativeRunContext) {
|
|
2851
|
+
throw new Error("Native panel worker run finished without a run context.");
|
|
2852
|
+
}
|
|
2668
2853
|
console.log("LongTable native panel worker run created");
|
|
2669
2854
|
console.log(`- run: ${finalNativeRun.id}`);
|
|
2670
2855
|
console.log(`- status: ${finalNativeRun.status}`);
|
|
@@ -3291,6 +3476,52 @@ async function runSearch(subcommand, args) {
|
|
|
3291
3476
|
}
|
|
3292
3477
|
console.log(renderEvidenceRunSummary(run, recordedPath));
|
|
3293
3478
|
}
|
|
3479
|
+
async function runScholarResearch(subcommand, args) {
|
|
3480
|
+
const json = args.json === true;
|
|
3481
|
+
if (subcommand === "doctor" || subcommand === "status" || !subcommand) {
|
|
3482
|
+
const readiness = assessScholarResearchReadiness(env);
|
|
3483
|
+
if (json) {
|
|
3484
|
+
console.log(JSON.stringify(readiness, null, 2));
|
|
3485
|
+
return;
|
|
3486
|
+
}
|
|
3487
|
+
console.log("LongTable scholar-research doctor");
|
|
3488
|
+
for (const connector of readiness.connectors) {
|
|
3489
|
+
const missing = connector.missingEnv.length > 0 ? ` (missing ${connector.missingEnv.join(", ")})` : "";
|
|
3490
|
+
console.log(`- ${connector.name}: ${connector.status}${missing}`);
|
|
3491
|
+
}
|
|
3492
|
+
console.log("- safety: paywall bypass disabled; institution-login automation disabled");
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
if (subcommand === "scaffold") {
|
|
3496
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
3497
|
+
const runId = typeof args["run-id"] === "string" ? args["run-id"] : undefined;
|
|
3498
|
+
const scaffold = await writeScholarResearchRunScaffold({
|
|
3499
|
+
cwd: workingDirectory,
|
|
3500
|
+
...(runId ? { runId } : {})
|
|
3501
|
+
});
|
|
3502
|
+
if (json) {
|
|
3503
|
+
console.log(JSON.stringify(scaffold, null, 2));
|
|
3504
|
+
return;
|
|
3505
|
+
}
|
|
3506
|
+
console.log("LongTable scholar-research scaffold");
|
|
3507
|
+
console.log(`- run: ${scaffold.runId}`);
|
|
3508
|
+
console.log(`- dir: ${scaffold.runDir}`);
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
if (subcommand === "smoke-fixture") {
|
|
3512
|
+
const fixture = buildScholarResearchSmokeFixture();
|
|
3513
|
+
if (json) {
|
|
3514
|
+
console.log(JSON.stringify({ fixture }, null, 2));
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
console.log("LongTable scholar-research smoke fixture");
|
|
3518
|
+
for (const item of fixture) {
|
|
3519
|
+
console.log(`- ${item.id}: ${item.category} - ${item.label}`);
|
|
3520
|
+
}
|
|
3521
|
+
return;
|
|
3522
|
+
}
|
|
3523
|
+
throw new Error(`Unknown scholar-research subcommand: ${subcommand}`);
|
|
3524
|
+
}
|
|
3294
3525
|
async function requireWorkspaceContext(args) {
|
|
3295
3526
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
3296
3527
|
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
@@ -3393,11 +3624,51 @@ async function runSpec(subcommand, args) {
|
|
|
3393
3624
|
}
|
|
3394
3625
|
throw new Error(`Unknown spec subcommand: ${command}`);
|
|
3395
3626
|
}
|
|
3627
|
+
function questionUsesKorean(record) {
|
|
3628
|
+
return /[가-힣]/.test([
|
|
3629
|
+
record.prompt.title,
|
|
3630
|
+
record.prompt.question,
|
|
3631
|
+
record.prompt.displayReason ?? "",
|
|
3632
|
+
...record.prompt.options.flatMap((option) => [option.label, option.description ?? ""]),
|
|
3633
|
+
record.prompt.otherLabel ?? ""
|
|
3634
|
+
].join("\n"));
|
|
3635
|
+
}
|
|
3636
|
+
function renderQuestionDecisionCard(record) {
|
|
3637
|
+
const korean = questionUsesKorean(record);
|
|
3638
|
+
const lines = [
|
|
3639
|
+
korean ? "LongTable 결정 카드" : "LongTable Decision Card",
|
|
3640
|
+
`${korean ? "체크포인트" : "Checkpoint"}: ${record.prompt.title}`,
|
|
3641
|
+
`${korean ? "무엇이 걸렸나" : "What is blocked"}: ${record.prompt.displayReason ?? record.prompt.rationale[0] ?? record.prompt.title}`,
|
|
3642
|
+
`${korean ? "지금 결정할 것" : "Decision needed"}: ${record.prompt.question}`,
|
|
3643
|
+
korean ? "선택지:" : "Choices:"
|
|
3644
|
+
];
|
|
3645
|
+
record.prompt.options.forEach((option, index) => {
|
|
3646
|
+
const recommended = option.recommended ? (korean ? " (추천)" : " (recommended)") : "";
|
|
3647
|
+
lines.push(`${index + 1}. ${option.label}${recommended}`);
|
|
3648
|
+
if (option.description) {
|
|
3649
|
+
lines.push(` ${option.description}`);
|
|
3650
|
+
}
|
|
3651
|
+
lines.push(` ${korean ? "기록값" : "Record value"}: ${option.value}`);
|
|
3652
|
+
});
|
|
3653
|
+
if (record.prompt.allowOther) {
|
|
3654
|
+
lines.push(`${record.prompt.options.length + 1}. ${record.prompt.otherLabel ?? (korean ? "직접 입력" : "Other")}`);
|
|
3655
|
+
lines.push(` ${korean ? "기록값" : "Record value"}: other`);
|
|
3656
|
+
}
|
|
3657
|
+
return lines.join("\n");
|
|
3658
|
+
}
|
|
3396
3659
|
async function runQuestion(args) {
|
|
3397
3660
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
3398
|
-
const
|
|
3399
|
-
if (
|
|
3400
|
-
|
|
3661
|
+
const requestedSurface = parseQuestionSurface(args.surface);
|
|
3662
|
+
if (requestedSurface &&
|
|
3663
|
+
requestedSurface !== "tmux_popup" &&
|
|
3664
|
+
requestedSurface !== "terminal_selector" &&
|
|
3665
|
+
requestedSurface !== "numbered") {
|
|
3666
|
+
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.");
|
|
3667
|
+
}
|
|
3668
|
+
const questionId = typeof args.question === "string" ? args.question.trim() : "";
|
|
3669
|
+
const prompt = questionId ? undefined : await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
3670
|
+
if (!prompt && !questionId) {
|
|
3671
|
+
throw new Error("A decision context or pending question id is required. Pass --prompt <text> or --question <id>.");
|
|
3401
3672
|
}
|
|
3402
3673
|
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
3403
3674
|
if (!context) {
|
|
@@ -3405,17 +3676,73 @@ async function runQuestion(args) {
|
|
|
3405
3676
|
}
|
|
3406
3677
|
const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
|
|
3407
3678
|
const required = args.required === true ? true : args.advisory === true ? false : undefined;
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
question
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3679
|
+
let result;
|
|
3680
|
+
if (questionId) {
|
|
3681
|
+
const state = await loadWorkspaceState(context);
|
|
3682
|
+
const question = state.questionLog.find((record) => record.id === questionId && record.status === "pending");
|
|
3683
|
+
if (!question) {
|
|
3684
|
+
throw new Error(`No pending LongTable question found for ${questionId}.`);
|
|
3685
|
+
}
|
|
3686
|
+
result = { question, state };
|
|
3687
|
+
}
|
|
3688
|
+
else {
|
|
3689
|
+
result = await createWorkspaceQuestion({
|
|
3690
|
+
context,
|
|
3691
|
+
prompt: prompt ?? "",
|
|
3692
|
+
title: typeof args.title === "string" ? args.title : undefined,
|
|
3693
|
+
question: typeof args.text === "string" ? args.text : undefined,
|
|
3694
|
+
provider,
|
|
3695
|
+
required
|
|
3696
|
+
});
|
|
3697
|
+
}
|
|
3416
3698
|
const transport = provider === "claude"
|
|
3417
3699
|
? renderQuestionRecordInput(result.question)
|
|
3418
3700
|
: renderQuestionRecordPrompt(result.question);
|
|
3701
|
+
const shouldTryTmuxPopup = requestedSurface === "tmux_popup" ||
|
|
3702
|
+
(requestedSurface === undefined && provider === "codex" && codexTmuxPopupAvailable() && args.print !== true && args.json !== true);
|
|
3703
|
+
if (shouldTryTmuxPopup) {
|
|
3704
|
+
try {
|
|
3705
|
+
const popupPayload = invokeOmxQuestionPopup(result.question);
|
|
3706
|
+
const answer = questionAnswerFromOmxPayload(result.question, popupPayload);
|
|
3707
|
+
const decision = await answerWorkspaceQuestion({
|
|
3708
|
+
context,
|
|
3709
|
+
questionId: result.question.id,
|
|
3710
|
+
answer,
|
|
3711
|
+
provider,
|
|
3712
|
+
surface: "tmux_popup"
|
|
3713
|
+
});
|
|
3714
|
+
if (args.json === true) {
|
|
3715
|
+
console.log(JSON.stringify({
|
|
3716
|
+
question: decision.question,
|
|
3717
|
+
decision: decision.decision,
|
|
3718
|
+
transport: {
|
|
3719
|
+
surface: "tmux_popup",
|
|
3720
|
+
status: "accepted"
|
|
3721
|
+
},
|
|
3722
|
+
files: {
|
|
3723
|
+
state: context.stateFilePath,
|
|
3724
|
+
current: context.currentFilePath
|
|
3725
|
+
}
|
|
3726
|
+
}, null, 2));
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3729
|
+
console.log("LongTable checkpoint decision recorded");
|
|
3730
|
+
console.log(`- question: ${decision.question.id}`);
|
|
3731
|
+
console.log(`- decision: ${decision.decision.id}`);
|
|
3732
|
+
console.log(`- surface: tmux_popup`);
|
|
3733
|
+
console.log(`- answer: ${decision.decision.selectedOption ?? "recorded"}`);
|
|
3734
|
+
console.log(`- state: ${context.stateFilePath}`);
|
|
3735
|
+
console.log(`- current: ${context.currentFilePath}`);
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
catch (error) {
|
|
3739
|
+
if (requestedSurface === "tmux_popup") {
|
|
3740
|
+
throw error;
|
|
3741
|
+
}
|
|
3742
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3743
|
+
console.error(`LongTable tmux_popup transport unavailable; falling back. ${message}`);
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3419
3746
|
if (args.json === true) {
|
|
3420
3747
|
console.log(JSON.stringify({
|
|
3421
3748
|
question: result.question,
|
|
@@ -3437,7 +3764,7 @@ async function runQuestion(args) {
|
|
|
3437
3764
|
}
|
|
3438
3765
|
return;
|
|
3439
3766
|
}
|
|
3440
|
-
if (isInteractiveTerminal()) {
|
|
3767
|
+
if ((requestedSurface === undefined || requestedSurface === "terminal_selector") && isInteractiveTerminal()) {
|
|
3441
3768
|
console.log(renderBrandBanner("LongTable", "Researcher Checkpoint"));
|
|
3442
3769
|
console.log("");
|
|
3443
3770
|
const answer = await promptChoice(renderQuestionHeader(1, 1, result.question.prompt.title, result.question.prompt.question), questionRecordToChoices(result.question));
|
|
@@ -3457,16 +3784,11 @@ async function runQuestion(args) {
|
|
|
3457
3784
|
console.log(`- current: ${context.currentFilePath}`);
|
|
3458
3785
|
return;
|
|
3459
3786
|
}
|
|
3460
|
-
const optionValues = [
|
|
3461
|
-
...result.question.prompt.options.map((option) => option.value),
|
|
3462
|
-
...(result.question.prompt.allowOther ? ["other"] : [])
|
|
3463
|
-
];
|
|
3464
3787
|
console.log(result.question.prompt.required ? "LongTable required Researcher Checkpoint recorded" : "LongTable advisory Researcher Checkpoint recorded");
|
|
3465
3788
|
console.log(`- question: ${result.question.id}`);
|
|
3466
3789
|
console.log(`- checkpoint: ${result.question.prompt.checkpointKey ?? "manual"}`);
|
|
3467
|
-
console.log(
|
|
3468
|
-
console.log(`-
|
|
3469
|
-
console.log(`- answer: longtable decide --question ${result.question.id} --answer <value>`);
|
|
3790
|
+
console.log(renderQuestionDecisionCard(result.question));
|
|
3791
|
+
console.log(`- answer: longtable decide --question ${result.question.id} --answer <value> --surface numbered`);
|
|
3470
3792
|
console.log(`- current: ${context.currentFilePath}`);
|
|
3471
3793
|
}
|
|
3472
3794
|
async function runClearQuestion(args) {
|
|
@@ -3928,12 +4250,14 @@ async function runDecide(args) {
|
|
|
3928
4250
|
throw new Error("No LongTable project workspace was found here. Run this inside a project or pass --cwd.");
|
|
3929
4251
|
}
|
|
3930
4252
|
const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
|
|
4253
|
+
const surface = parseQuestionSurface(args.surface);
|
|
3931
4254
|
const result = await answerWorkspaceQuestion({
|
|
3932
4255
|
context,
|
|
3933
4256
|
questionId: typeof args.question === "string" ? args.question : undefined,
|
|
3934
4257
|
answer,
|
|
3935
4258
|
rationale: typeof args.rationale === "string" ? args.rationale : undefined,
|
|
3936
|
-
provider
|
|
4259
|
+
provider,
|
|
4260
|
+
...(surface ? { surface } : {})
|
|
3937
4261
|
});
|
|
3938
4262
|
if (args.json === true) {
|
|
3939
4263
|
console.log(JSON.stringify({
|
|
@@ -4331,6 +4655,10 @@ async function main() {
|
|
|
4331
4655
|
await runSearch(subcommand, values);
|
|
4332
4656
|
return;
|
|
4333
4657
|
}
|
|
4658
|
+
if (command === "scholar-research") {
|
|
4659
|
+
await runScholarResearch(subcommand, values);
|
|
4660
|
+
return;
|
|
4661
|
+
}
|
|
4334
4662
|
if (command === "spec") {
|
|
4335
4663
|
await runSpec(subcommand, values);
|
|
4336
4664
|
return;
|
package/dist/debate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { buildInvocationIntent, buildPanelPlan } from "./panel.js";
|
|
2
|
+
import { buildInvocationIntent, buildPanelDecisionContext, buildPanelPlan } from "./panel.js";
|
|
3
3
|
import { getPersonaDefinition, parsePersonaKey } from "./personas.js";
|
|
4
4
|
function nowIso() {
|
|
5
5
|
return new Date().toISOString();
|
|
@@ -167,6 +167,10 @@ function buildSynthesis(plan, artifactPath, kind) {
|
|
|
167
167
|
const labels = plan.members.map((member) => member.label);
|
|
168
168
|
const highSensitivity = plan.checkpointSensitivity === "high";
|
|
169
169
|
const runLabel = kind === "debate" ? "panel debate" : "panel review";
|
|
170
|
+
const decisionContext = buildPanelDecisionContext(plan.prompt);
|
|
171
|
+
const localizedRunLabel = decisionContext.language === "ko"
|
|
172
|
+
? (kind === "debate" ? "패널 토론" : "패널 리뷰")
|
|
173
|
+
: runLabel;
|
|
170
174
|
return {
|
|
171
175
|
artifactPath,
|
|
172
176
|
summary: `The ${runLabel} completed across ${labels.join(", ")}. It should slow closure by turning role disagreement into an explicit researcher decision.`,
|
|
@@ -187,13 +191,16 @@ function buildSynthesis(plan, artifactPath, kind) {
|
|
|
187
191
|
"Choose whether the debate should affect the current artifact, the research design, or only the decision log."
|
|
188
192
|
],
|
|
189
193
|
recommendedCheckpoint: highSensitivity
|
|
190
|
-
?
|
|
191
|
-
|
|
194
|
+
? (decisionContext.language === "ko"
|
|
195
|
+
? `${localizedRunLabel}에서 ${decisionContext.focus}에 대한 높은 민감도의 불일치가 드러났습니다. ${decisionContext.decisionQuestion}`
|
|
196
|
+
: `The ${runLabel} surfaced high-sensitivity disagreement about ${decisionContext.focus}. ${decisionContext.decisionQuestion}`)
|
|
197
|
+
: decisionContext.decisionQuestion
|
|
192
198
|
};
|
|
193
199
|
}
|
|
194
200
|
export function createTeamDebateQuestionRecord(run, provider) {
|
|
195
201
|
const createdAt = nowIso();
|
|
196
202
|
const isDebate = run.interactionDepth === "debated";
|
|
203
|
+
const decisionContext = buildPanelDecisionContext(run.prompt);
|
|
197
204
|
return {
|
|
198
205
|
id: createId("question_record"),
|
|
199
206
|
createdAt,
|
|
@@ -202,51 +209,32 @@ export function createTeamDebateQuestionRecord(run, provider) {
|
|
|
202
209
|
prompt: {
|
|
203
210
|
id: createId("question_prompt"),
|
|
204
211
|
checkpointKey: "panel_debate_next_decision",
|
|
205
|
-
title:
|
|
212
|
+
title: decisionContext.language === "ko"
|
|
213
|
+
? (isDebate ? "패널 토론 후속 결정" : "패널 리뷰 후속 결정")
|
|
214
|
+
: (isDebate ? "Panel debate follow-up decision" : "Panel review follow-up decision"),
|
|
206
215
|
question: run.synthesis.recommendedCheckpoint,
|
|
207
216
|
type: "single_choice",
|
|
208
|
-
options:
|
|
209
|
-
{
|
|
210
|
-
value: "revise",
|
|
211
|
-
label: "Revise before proceeding",
|
|
212
|
-
description: "Use the debate result to revise the claim, design, or draft first."
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
value: "evidence",
|
|
216
|
-
label: "Gather or verify evidence first",
|
|
217
|
-
description: "Check source, data, or local artifact support before proceeding."
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
value: "proceed",
|
|
221
|
-
label: "Proceed with current direction",
|
|
222
|
-
description: "Accept the risk profile and continue with the current direction."
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
value: "defer",
|
|
226
|
-
label: "Keep this open",
|
|
227
|
-
description: "Do not commit yet; keep the debate issue visible as an open tension."
|
|
228
|
-
}
|
|
229
|
-
],
|
|
217
|
+
options: decisionContext.options,
|
|
230
218
|
allowOther: true,
|
|
231
|
-
otherLabel:
|
|
219
|
+
otherLabel: decisionContext.otherLabel,
|
|
232
220
|
required: run.roles.some((member) => {
|
|
233
221
|
const key = parsePersonaKey(member.role);
|
|
234
222
|
return key ? getPersonaDefinition(key).checkpointSensitivity === "high" : false;
|
|
235
223
|
}),
|
|
236
224
|
source: "runtime_guidance",
|
|
237
|
-
displayReason:
|
|
238
|
-
? "Role rebuttals and convergence should connect to an explicit researcher decision."
|
|
239
|
-
: "Cross-review created role disagreement that should connect to an explicit researcher decision.",
|
|
225
|
+
displayReason: decisionContext.displayReason,
|
|
240
226
|
rationale: [
|
|
241
227
|
"LongTable panel orchestration is a research harness surface, not a substitute for researcher judgment.",
|
|
242
228
|
isDebate
|
|
243
229
|
? "The fixed debate rounds created disagreement that should connect to an explicit researcher decision."
|
|
244
230
|
: "The cross-review round created disagreement that should connect to an explicit researcher decision.",
|
|
231
|
+
`Panel decision focus: ${decisionContext.focus}.`,
|
|
232
|
+
"Panel follow-up choices are compact by default; unlisted decisions should use Other.",
|
|
245
233
|
`LongTable panel run: ${run.id}.`
|
|
246
234
|
],
|
|
247
235
|
preferredSurfaces: provider === "claude"
|
|
248
236
|
? ["native_structured", "numbered"]
|
|
249
|
-
: ["mcp_elicitation", "numbered"]
|
|
237
|
+
: ["tmux_popup", "mcp_elicitation", "numbered"]
|
|
250
238
|
}
|
|
251
239
|
};
|
|
252
240
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED