@longtable/cli 0.1.15 → 0.1.17
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/README.md +34 -18
- package/dist/cli.js +526 -84
- package/dist/debate.d.ts +21 -0
- package/dist/debate.js +380 -0
- package/dist/panel.d.ts +1 -3
- package/dist/panel.js +4 -10
- package/dist/project-session.d.ts +0 -2
- package/dist/project-session.js +12 -10
- package/dist/prompt-aliases.js +1 -1
- package/package.json +8 -10
- package/scripts/postinstall.mjs +0 -85
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
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 { execFileSync, execSync } from "node:child_process";
|
|
5
5
|
import { emitKeypressEvents } from "node:readline";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
import { stdin as input, stdout as output, cwd, exit } from "node:process";
|
|
8
|
-
import { dirname, resolve } from "node:path";
|
|
8
|
+
import { dirname, join, resolve } from "node:path";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
|
+
import { classifyCheckpointTrigger } from "@longtable/checkpoints";
|
|
10
11
|
import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, serializeSetupOutput, writeRuntimeConfig } from "@longtable/setup";
|
|
11
12
|
import { buildCodexSkillSpecs, buildCodexThinWrappedPrompt, installCodexSkills, listInstalledCodexSkills, renderQuestionRecordPrompt, removeCodexSkills, resolveCodexSkillsDir, runCodexThinWrapper } from "@longtable/provider-codex";
|
|
12
13
|
import { buildClaudeSkillSpecs, installClaudeSkills, listInstalledClaudeSkills, renderQuestionRecordInput, removeClaudeSkills, resolveClaudeSkillsDir } from "@longtable/provider-claude";
|
|
@@ -14,7 +15,8 @@ import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodex
|
|
|
14
15
|
import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router.js";
|
|
15
16
|
import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
|
|
16
17
|
import { buildPanelFallback, renderPanelSummary } from "./panel.js";
|
|
17
|
-
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, createWorkspaceClarificationCard, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadProjectContextFromDirectory, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
18
|
+
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, createWorkspaceClarificationCard, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
19
|
+
import { buildTeamDebate, renderTeamDebateSummary } from "./debate.js";
|
|
18
20
|
const VALID_MODES = new Set([
|
|
19
21
|
"explore",
|
|
20
22
|
"review",
|
|
@@ -40,7 +42,7 @@ const ANSI = {
|
|
|
40
42
|
green: "\u001B[32m"
|
|
41
43
|
};
|
|
42
44
|
const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
|
|
43
|
-
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.
|
|
45
|
+
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.17";
|
|
44
46
|
const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
|
|
45
47
|
const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
|
|
46
48
|
function style(text, prefix) {
|
|
@@ -76,7 +78,8 @@ function usage() {
|
|
|
76
78
|
" Run `longtable ...` in your terminal, not inside the Codex chat box.",
|
|
77
79
|
" After `longtable start`, move into the created project directory and open `codex` there.",
|
|
78
80
|
"",
|
|
79
|
-
" longtable init [--flow quickstart|interview] [--provider codex|claude] [--field <field>] [--career-stage <stage>] [--experience novice|intermediate|advanced] [--checkpoint low|balanced|high] [--authorship-signal <text>] [--entry-mode explore|review|critique|draft|commit] [--weakest-domain theory|methodology|measurement|analysis|writing] [--panel-preference synthesis_only|show_on_conflict|always_visible] [--
|
|
81
|
+
" longtable init [--flow quickstart|interview] [--provider codex|claude] [--field <field>] [--career-stage <stage>] [--experience novice|intermediate|advanced] [--checkpoint low|balanced|high] [--authorship-signal <text>] [--entry-mode explore|review|critique|draft|commit] [--weakest-domain theory|methodology|measurement|analysis|writing] [--panel-preference synthesis_only|show_on_conflict|always_visible] [--json] [--no-install] [--install-skills] [--install-prompts]",
|
|
82
|
+
" longtable setup [--provider codex|claude] [--json] [--dir <path>] [--skills-dir <path>] [--runtime-path <file>] [--setup-path <file>]",
|
|
80
83
|
" longtable start [--path <dir>] [--name <project>] [--goal <text>] [--blocker <text>] [--perspectives <role[,role]>] [--disagreement synthesis_only|show_on_conflict|always_visible] [--setup <path>] [--json]",
|
|
81
84
|
" longtable resume [--cwd <path>] [--json]",
|
|
82
85
|
" longtable doctor [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
|
|
@@ -85,6 +88,9 @@ function usage() {
|
|
|
85
88
|
" longtable show [--json] [--path <file>]",
|
|
86
89
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
87
90
|
" longtable mcp install [--provider codex|claude|all] [--write] [--json] [--codex-config <path>] [--claude-settings <path>] [--package <spec>]",
|
|
91
|
+
" longtable hud [--watch] [--tmux] [--preset minimal|full] [--cwd <path>] [--json]",
|
|
92
|
+
" longtable sentinel --prompt <text> [--cwd <path>] [--json] [--record]",
|
|
93
|
+
" longtable team --prompt <text> [--role <role[,role]>] [--tmux] [--debate] [--rounds 5] [--cwd <path>] [--json]",
|
|
88
94
|
" longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
89
95
|
" longtable clarify --prompt <task-context> [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json] [--force]",
|
|
90
96
|
" longtable question --prompt <decision-context> [--title <text>] [--text <question>] [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json]",
|
|
@@ -120,7 +126,7 @@ function parseArgs(argv) {
|
|
|
120
126
|
const values = {};
|
|
121
127
|
let subcommand = maybeSubcommand;
|
|
122
128
|
const modeCommand = command && VALID_MODES.has(command);
|
|
123
|
-
const directCommand = command && ["init", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "panel", "decide"].includes(command);
|
|
129
|
+
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "panel", "decide", "hud", "sentinel", "team"].includes(command);
|
|
124
130
|
let startIndex = 1;
|
|
125
131
|
if (modeCommand) {
|
|
126
132
|
subcommand = undefined;
|
|
@@ -197,12 +203,9 @@ function questionSection(questionId) {
|
|
|
197
203
|
if (questionId === "preferredCheckpointIntensity" || questionId === "preferredEntryMode") {
|
|
198
204
|
return "Interaction style";
|
|
199
205
|
}
|
|
200
|
-
if (questionId === "weakestDomain" || questionId === "panelPreference"
|
|
206
|
+
if (questionId === "weakestDomain" || questionId === "panelPreference") {
|
|
201
207
|
return "How LongTable should challenge you";
|
|
202
208
|
}
|
|
203
|
-
if (questionId === "mcpPreference") {
|
|
204
|
-
return "Provider integration";
|
|
205
|
-
}
|
|
206
209
|
return "Authorship and voice";
|
|
207
210
|
}
|
|
208
211
|
function formatModeLabel(mode) {
|
|
@@ -499,12 +502,6 @@ function toSetupAnswers(args) {
|
|
|
499
502
|
: undefined,
|
|
500
503
|
panelPreference: typeof args["panel-preference"] === "string"
|
|
501
504
|
? String(args["panel-preference"])
|
|
502
|
-
: undefined,
|
|
503
|
-
agentTeamPreference: typeof args["agent-team"] === "string"
|
|
504
|
-
? String(args["agent-team"])
|
|
505
|
-
: undefined,
|
|
506
|
-
mcpPreference: typeof args.mcp === "string"
|
|
507
|
-
? String(args.mcp)
|
|
508
505
|
: undefined
|
|
509
506
|
};
|
|
510
507
|
}
|
|
@@ -552,11 +549,6 @@ async function collectInteractiveAnswers(initialFlow) {
|
|
|
552
549
|
answers.weakestDomain = value;
|
|
553
550
|
if (question.id === "panelPreference")
|
|
554
551
|
answers.panelPreference = value;
|
|
555
|
-
if (question.id === "agentTeamPreference") {
|
|
556
|
-
answers.agentTeamPreference = value;
|
|
557
|
-
}
|
|
558
|
-
if (question.id === "mcpPreference")
|
|
559
|
-
answers.mcpPreference = value;
|
|
560
552
|
}
|
|
561
553
|
return {
|
|
562
554
|
flow,
|
|
@@ -568,6 +560,172 @@ async function collectInteractiveAnswers(initialFlow) {
|
|
|
568
560
|
rl.close();
|
|
569
561
|
}
|
|
570
562
|
}
|
|
563
|
+
function buildPermissionSetupChoices() {
|
|
564
|
+
return {
|
|
565
|
+
surfaces: [
|
|
566
|
+
{
|
|
567
|
+
id: "cli_only",
|
|
568
|
+
label: "CLI only",
|
|
569
|
+
description: "Why: least invasive. Tradeoff: no natural in-provider LongTable entrypoints."
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
id: "skills",
|
|
573
|
+
label: "Skills",
|
|
574
|
+
description: "Why: enables natural LongTable skill routing. Tradeoff: writes provider skill files."
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
id: "skills_mcp",
|
|
578
|
+
label: "Skills + MCP",
|
|
579
|
+
description: "Why: adds structured state access. Tradeoff: writes provider config for MCP transport."
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
id: "skills_mcp_sentinel",
|
|
583
|
+
label: "Skills + MCP + Sentinel",
|
|
584
|
+
description: "Why: prepares advisory gap/tacit monitoring. Tradeoff: LongTable may nudge research turns."
|
|
585
|
+
}
|
|
586
|
+
],
|
|
587
|
+
intervention: [
|
|
588
|
+
{
|
|
589
|
+
id: "advisory",
|
|
590
|
+
label: "Advisory",
|
|
591
|
+
description: "Why: notices gaps without blocking. Tradeoff: you may still miss hard commitments."
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
id: "balanced",
|
|
595
|
+
label: "Balanced",
|
|
596
|
+
description: "Why: blocks clear theory, measurement, method, or evidence commitments. Tradeoff: occasional stops."
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
id: "strong",
|
|
600
|
+
label: "Strong",
|
|
601
|
+
description: "Why: maximizes judgment protection. Tradeoff: more interruption before closure."
|
|
602
|
+
}
|
|
603
|
+
],
|
|
604
|
+
tmux: [
|
|
605
|
+
{
|
|
606
|
+
id: "standard",
|
|
607
|
+
label: "Standard chat",
|
|
608
|
+
description: "Why: portable default. Tradeoff: checkpoints and gaps are less persistently visible."
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
id: "hud",
|
|
612
|
+
label: "Research HUD",
|
|
613
|
+
description: "Why: keeps goals, blockers, and pending checkpoints visible. Requires tmux."
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
id: "console",
|
|
617
|
+
label: "Research console",
|
|
618
|
+
description: "Why: enables a richer tmux layout for HUD and team discussion. Requires tmux."
|
|
619
|
+
}
|
|
620
|
+
],
|
|
621
|
+
team: [
|
|
622
|
+
{
|
|
623
|
+
id: "off",
|
|
624
|
+
label: "Off",
|
|
625
|
+
description: "Why: simplest. Tradeoff: panel disagreement stays inside one LongTable response."
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
id: "panel",
|
|
629
|
+
label: "Structured panel",
|
|
630
|
+
description: "Why: role disagreement is visible without tmux. Tradeoff: not parallel."
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
id: "tmux_team",
|
|
634
|
+
label: "Tmux team discussion",
|
|
635
|
+
description: "Why: opens role panes for parallel debate. Tradeoff: terminal complexity and cleanup."
|
|
636
|
+
}
|
|
637
|
+
]
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function checkpointIntensityFromIntervention(choice) {
|
|
641
|
+
if (choice === "strong")
|
|
642
|
+
return "high";
|
|
643
|
+
if (choice === "advisory")
|
|
644
|
+
return "low";
|
|
645
|
+
return "balanced";
|
|
646
|
+
}
|
|
647
|
+
async function runSetup(args) {
|
|
648
|
+
const json = args.json === true;
|
|
649
|
+
const rl = createInterface({ input, output });
|
|
650
|
+
try {
|
|
651
|
+
const provider = (typeof args.provider === "string"
|
|
652
|
+
? (args.provider === "claude" ? "claude" : "codex")
|
|
653
|
+
: await promptChoice(rl, "Which provider should LongTable configure?", buildProviderChoices()));
|
|
654
|
+
const choices = buildPermissionSetupChoices();
|
|
655
|
+
const surfaces = await promptChoice(rl, [
|
|
656
|
+
"Which LongTable runtime surfaces should be enabled?",
|
|
657
|
+
"This is a permission choice because skills, MCP, and sentinel support write provider-facing runtime files."
|
|
658
|
+
].join("\n"), choices.surfaces);
|
|
659
|
+
const intervention = await promptChoice(rl, "How strongly may LongTable interrupt research decisions?", choices.intervention);
|
|
660
|
+
const tmuxMode = await promptChoice(rl, "Should LongTable recommend a tmux-based research interface?", choices.tmux);
|
|
661
|
+
const teamMode = await promptChoice(rl, "Should LongTable enable agent/team discussion mode?", choices.team);
|
|
662
|
+
const outputValue = createPersistedSetupOutput({
|
|
663
|
+
field: "unspecified",
|
|
664
|
+
careerStage: "unspecified",
|
|
665
|
+
experienceLevel: "advanced",
|
|
666
|
+
preferredCheckpointIntensity: checkpointIntensityFromIntervention(intervention),
|
|
667
|
+
preferredEntryMode: "explore",
|
|
668
|
+
panelPreference: teamMode === "off" ? "show_on_conflict" : "always_visible"
|
|
669
|
+
}, provider, "quickstart");
|
|
670
|
+
outputValue.initialState.explicitState = {
|
|
671
|
+
...outputValue.initialState.explicitState,
|
|
672
|
+
runtimeSurfaces: surfaces,
|
|
673
|
+
interventionPosture: intervention,
|
|
674
|
+
tmuxMode,
|
|
675
|
+
teamMode
|
|
676
|
+
};
|
|
677
|
+
if (surfaces === "skills_mcp_sentinel") {
|
|
678
|
+
outputValue.initialState.inferredHypotheses.push({
|
|
679
|
+
hypothesis: "Researcher approved advisory Gap/Tacit Sentinel setup.",
|
|
680
|
+
confidence: 0.95,
|
|
681
|
+
evidence: ["Selected Skills + MCP + Sentinel during permission-first setup."],
|
|
682
|
+
status: "confirmed"
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
const result = await saveSetupAndRuntimeConfig(outputValue, {
|
|
686
|
+
setupPath: typeof args["setup-path"] === "string" ? args["setup-path"] : undefined,
|
|
687
|
+
runtimePath: typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined
|
|
688
|
+
});
|
|
689
|
+
const installedSkills = surfaces === "cli_only"
|
|
690
|
+
? []
|
|
691
|
+
: provider === "codex"
|
|
692
|
+
? await installCodexSkills(listRoleDefinitions(), typeof args["skills-dir"] === "string" ? args["skills-dir"] : typeof args.dir === "string" ? args.dir : undefined)
|
|
693
|
+
: await installClaudeSkills(listRoleDefinitions(), typeof args["skills-dir"] === "string" ? args["skills-dir"] : typeof args.dir === "string" ? args.dir : undefined);
|
|
694
|
+
const mcpRequested = surfaces === "skills_mcp" || surfaces === "skills_mcp_sentinel";
|
|
695
|
+
if (mcpRequested && !json) {
|
|
696
|
+
console.log("");
|
|
697
|
+
console.log("MCP setup is approved. To write provider config now, run:");
|
|
698
|
+
console.log(`- longtable mcp install --provider ${provider} --write`);
|
|
699
|
+
}
|
|
700
|
+
if (json) {
|
|
701
|
+
console.log(JSON.stringify({
|
|
702
|
+
setup: outputValue,
|
|
703
|
+
runtime: result,
|
|
704
|
+
installedSkills: installedSkills.map((skill) => skill.name),
|
|
705
|
+
mcpRequested,
|
|
706
|
+
tmuxMode,
|
|
707
|
+
teamMode
|
|
708
|
+
}, null, 2));
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
console.log("");
|
|
712
|
+
console.log(renderSetupSummary(outputValue));
|
|
713
|
+
console.log("");
|
|
714
|
+
console.log(renderInstallSummary(result));
|
|
715
|
+
console.log(`Installed skills: ${installedSkills.length}`);
|
|
716
|
+
if (tmuxMode !== "standard") {
|
|
717
|
+
console.log("");
|
|
718
|
+
console.log("Tmux recommendation:");
|
|
719
|
+
console.log("- macOS: brew install tmux");
|
|
720
|
+
console.log("- Ubuntu/Debian: sudo apt install tmux");
|
|
721
|
+
console.log("- Start HUD in an existing tmux session: longtable hud --tmux");
|
|
722
|
+
console.log("- Start a discussion team: longtable team --tmux --prompt \"...\"");
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
finally {
|
|
726
|
+
rl.close();
|
|
727
|
+
}
|
|
728
|
+
}
|
|
571
729
|
function perspectiveChoices() {
|
|
572
730
|
return PERSONA_DEFINITIONS.map((persona) => ({
|
|
573
731
|
id: persona.key,
|
|
@@ -671,12 +829,6 @@ function normalizePersistAnswers(raw) {
|
|
|
671
829
|
: {}),
|
|
672
830
|
...(raw.panelPreference
|
|
673
831
|
? { panelPreference: raw.panelPreference }
|
|
674
|
-
: {}),
|
|
675
|
-
...(raw.agentTeamPreference
|
|
676
|
-
? { agentTeamPreference: raw.agentTeamPreference }
|
|
677
|
-
: {}),
|
|
678
|
-
...(raw.mcpPreference
|
|
679
|
-
? { mcpPreference: raw.mcpPreference }
|
|
680
832
|
: {})
|
|
681
833
|
}
|
|
682
834
|
};
|
|
@@ -705,15 +857,6 @@ async function readPersistAnswers(args) {
|
|
|
705
857
|
}
|
|
706
858
|
throw new Error("persist-init requires either --answers-json, --stdin, or the full set of setup flags.");
|
|
707
859
|
}
|
|
708
|
-
async function applySetupMcpPreference(provider, preference) {
|
|
709
|
-
if (!preference || preference === "skip") {
|
|
710
|
-
return undefined;
|
|
711
|
-
}
|
|
712
|
-
return installMcpConfig({
|
|
713
|
-
provider: preference === "write_all" ? "all" : provider,
|
|
714
|
-
write: preference === "write_provider" || preference === "write_all"
|
|
715
|
-
});
|
|
716
|
-
}
|
|
717
860
|
async function runInit(args) {
|
|
718
861
|
const json = args.json === true;
|
|
719
862
|
const installRuntime = args["no-install"] !== true;
|
|
@@ -746,17 +889,15 @@ async function runInit(args) {
|
|
|
746
889
|
if (provider === "claude" && installSkills) {
|
|
747
890
|
installedSkills = await installClaudeSkills(listRoleDefinitions(), skillsDir);
|
|
748
891
|
}
|
|
749
|
-
const mcpInstall = await applySetupMcpPreference(provider, answers.mcpPreference);
|
|
750
892
|
if (json) {
|
|
751
|
-
if (installedPrompts.length === 0 && installedSkills.length === 0
|
|
893
|
+
if (installedPrompts.length === 0 && installedSkills.length === 0) {
|
|
752
894
|
console.log(serializeSetupOutput(outputValue));
|
|
753
895
|
return;
|
|
754
896
|
}
|
|
755
897
|
console.log(JSON.stringify({
|
|
756
898
|
setup: outputValue,
|
|
757
899
|
installedPrompts: installedPrompts.map((prompt) => prompt.name),
|
|
758
|
-
installedSkills: installedSkills.map((skill) => skill.name)
|
|
759
|
-
mcpInstall
|
|
900
|
+
installedSkills: installedSkills.map((skill) => skill.name)
|
|
760
901
|
}, null, 2));
|
|
761
902
|
return;
|
|
762
903
|
}
|
|
@@ -781,10 +922,6 @@ async function runInit(args) {
|
|
|
781
922
|
}
|
|
782
923
|
console.log(" Use these by naming LongTable naturally, e.g. `lt panel: ...`.");
|
|
783
924
|
}
|
|
784
|
-
if (mcpInstall) {
|
|
785
|
-
console.log("");
|
|
786
|
-
console.log(renderMcpInstallSummary(mcpInstall));
|
|
787
|
-
}
|
|
788
925
|
if (provider === "codex") {
|
|
789
926
|
console.log("");
|
|
790
927
|
console.log("Next step:");
|
|
@@ -918,43 +1055,40 @@ function renderMcpInstallSummary(result) {
|
|
|
918
1055
|
}
|
|
919
1056
|
return lines.join("\n").trimEnd();
|
|
920
1057
|
}
|
|
921
|
-
async function installMcpConfig(args) {
|
|
922
|
-
const serverName = typeof args.name === "string" && args.name.trim()
|
|
923
|
-
? args.name.trim()
|
|
924
|
-
: LONGTABLE_MCP_SERVER_NAME;
|
|
925
|
-
const packageSpec = resolveMcpPackageSpec(args);
|
|
926
|
-
const command = typeof args.command === "string" && args.command.trim() ? args.command.trim() : "npx";
|
|
927
|
-
const mcpArgs = command === "npx" ? ["-y", packageSpec] : [packageSpec];
|
|
928
|
-
const providers = resolveMcpProviders(args.provider);
|
|
929
|
-
const write = args.write === true;
|
|
930
|
-
const targets = [];
|
|
931
|
-
for (const provider of providers) {
|
|
932
|
-
if (provider === "codex") {
|
|
933
|
-
const path = resolveCodexMcpConfigPath(args);
|
|
934
|
-
const block = renderCodexMcpBlock(serverName, command, mcpArgs);
|
|
935
|
-
const content = write ? await writeCodexMcpConfig(path, block, serverName) : block;
|
|
936
|
-
targets.push({ provider, path, format: "toml", content });
|
|
937
|
-
}
|
|
938
|
-
if (provider === "claude") {
|
|
939
|
-
const path = resolveClaudeMcpSettingsPath(args);
|
|
940
|
-
const content = write
|
|
941
|
-
? await writeClaudeMcpSettings(path, serverName, command, mcpArgs)
|
|
942
|
-
: renderClaudeMcpJson(serverName, command, mcpArgs);
|
|
943
|
-
targets.push({ provider, path, format: "json", content });
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
return {
|
|
947
|
-
serverName,
|
|
948
|
-
packageSpec,
|
|
949
|
-
command,
|
|
950
|
-
args: mcpArgs,
|
|
951
|
-
write,
|
|
952
|
-
targets
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
1058
|
async function runMcpSubcommand(subcommand, args) {
|
|
956
1059
|
if (!subcommand || subcommand === "install" || subcommand === "print-config") {
|
|
957
|
-
const
|
|
1060
|
+
const serverName = typeof args.name === "string" && args.name.trim()
|
|
1061
|
+
? args.name.trim()
|
|
1062
|
+
: LONGTABLE_MCP_SERVER_NAME;
|
|
1063
|
+
const packageSpec = resolveMcpPackageSpec(args);
|
|
1064
|
+
const command = typeof args.command === "string" && args.command.trim() ? args.command.trim() : "npx";
|
|
1065
|
+
const mcpArgs = command === "npx" ? ["-y", packageSpec] : [packageSpec];
|
|
1066
|
+
const providers = resolveMcpProviders(args.provider);
|
|
1067
|
+
const write = args.write === true;
|
|
1068
|
+
const targets = [];
|
|
1069
|
+
for (const provider of providers) {
|
|
1070
|
+
if (provider === "codex") {
|
|
1071
|
+
const path = resolveCodexMcpConfigPath(args);
|
|
1072
|
+
const block = renderCodexMcpBlock(serverName, command, mcpArgs);
|
|
1073
|
+
const content = write ? await writeCodexMcpConfig(path, block, serverName) : block;
|
|
1074
|
+
targets.push({ provider, path, format: "toml", content });
|
|
1075
|
+
}
|
|
1076
|
+
if (provider === "claude") {
|
|
1077
|
+
const path = resolveClaudeMcpSettingsPath(args);
|
|
1078
|
+
const content = write
|
|
1079
|
+
? await writeClaudeMcpSettings(path, serverName, command, mcpArgs)
|
|
1080
|
+
: renderClaudeMcpJson(serverName, command, mcpArgs);
|
|
1081
|
+
targets.push({ provider, path, format: "json", content });
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
const result = {
|
|
1085
|
+
serverName,
|
|
1086
|
+
packageSpec,
|
|
1087
|
+
command,
|
|
1088
|
+
args: mcpArgs,
|
|
1089
|
+
write,
|
|
1090
|
+
targets
|
|
1091
|
+
};
|
|
958
1092
|
if (args.json === true) {
|
|
959
1093
|
console.log(JSON.stringify(result, null, 2));
|
|
960
1094
|
return;
|
|
@@ -1428,9 +1562,6 @@ async function runModeCommand(mode, args) {
|
|
|
1428
1562
|
}
|
|
1429
1563
|
const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
|
|
1430
1564
|
const projectContext = await loadProjectContextFromDirectory(workingDirectory);
|
|
1431
|
-
if (projectContext) {
|
|
1432
|
-
await assertWorkspaceNotBlocked(projectContext);
|
|
1433
|
-
}
|
|
1434
1565
|
const projectAware = await buildProjectAwarePrompt(prompt, workingDirectory);
|
|
1435
1566
|
const panelPreference = setup?.profileSeed.panelPreference;
|
|
1436
1567
|
const panelRequested = args.panel === true ||
|
|
@@ -1487,8 +1618,7 @@ async function runPanelCommand(args) {
|
|
|
1487
1618
|
mode,
|
|
1488
1619
|
roleFlag: typeof args.role === "string" ? args.role : undefined,
|
|
1489
1620
|
provider,
|
|
1490
|
-
visibility
|
|
1491
|
-
agentTeamPreference: setup?.profileSeed.agentTeamPreference
|
|
1621
|
+
visibility
|
|
1492
1622
|
});
|
|
1493
1623
|
if (projectAware.projectContextFound) {
|
|
1494
1624
|
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
@@ -1809,6 +1939,302 @@ async function runAsk(args) {
|
|
|
1809
1939
|
}
|
|
1810
1940
|
await runModeCommand(mode, delegatedArgs);
|
|
1811
1941
|
}
|
|
1942
|
+
function localId(prefix) {
|
|
1943
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1944
|
+
}
|
|
1945
|
+
function shellEscape(value) {
|
|
1946
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
1947
|
+
}
|
|
1948
|
+
async function writeJsonFile(path, value) {
|
|
1949
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
1950
|
+
}
|
|
1951
|
+
async function writeTeamDebateArtifacts(bundle, teamDir, prompt) {
|
|
1952
|
+
await mkdir(teamDir, { recursive: true });
|
|
1953
|
+
await writeFile(join(teamDir, "prompt.txt"), prompt, "utf8");
|
|
1954
|
+
await writeJsonFile(join(teamDir, "plan.json"), bundle.plan);
|
|
1955
|
+
await writeJsonFile(join(teamDir, "run.json"), bundle.run);
|
|
1956
|
+
for (const round of bundle.run.rounds) {
|
|
1957
|
+
await mkdir(round.artifactDir, { recursive: true });
|
|
1958
|
+
await writeJsonFile(join(round.artifactDir, "round.json"), round);
|
|
1959
|
+
for (const contribution of round.contributions) {
|
|
1960
|
+
await writeJsonFile(join(teamDir, contribution.artifactPath), contribution);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
await writeJsonFile(join(teamDir, "synthesis.json"), bundle.run.synthesis);
|
|
1964
|
+
await writeJsonFile(join(teamDir, "checkpoint.json"), bundle.questionRecord);
|
|
1965
|
+
await writeJsonFile(join(teamDir, "invocation.json"), bundle.invocationRecord);
|
|
1966
|
+
}
|
|
1967
|
+
function sentinelSummary(prompt, workingDirectory) {
|
|
1968
|
+
const trigger = classifyCheckpointTrigger(prompt, {
|
|
1969
|
+
fallbackMode: "explore",
|
|
1970
|
+
unresolvedTensions: []
|
|
1971
|
+
});
|
|
1972
|
+
const normalized = prompt.toLowerCase();
|
|
1973
|
+
const signals = [];
|
|
1974
|
+
if (/measure|measurement|scale|validity|reliability|측정|척도|타당도|신뢰도/.test(normalized)) {
|
|
1975
|
+
signals.push("measurement gap or commitment");
|
|
1976
|
+
}
|
|
1977
|
+
if (/theory|theoretical|framework|construct|이론|프레임워크|개념/.test(normalized)) {
|
|
1978
|
+
signals.push("theory or construct commitment");
|
|
1979
|
+
}
|
|
1980
|
+
if (/method|design|sample|participant|방법|설계|표본|참여자/.test(normalized)) {
|
|
1981
|
+
signals.push("method/design gap");
|
|
1982
|
+
}
|
|
1983
|
+
if (/citation|reference|source|evidence|doi|문헌|인용|근거|출처/.test(normalized)) {
|
|
1984
|
+
signals.push("evidence gap");
|
|
1985
|
+
}
|
|
1986
|
+
if (/voice|authorship|narrative|저자성|서사|문체|목소리/.test(normalized)) {
|
|
1987
|
+
signals.push("authorship or narrative-trace risk");
|
|
1988
|
+
}
|
|
1989
|
+
if (/assumption|implicit|tacit|암묵|전제|가정/.test(normalized)) {
|
|
1990
|
+
signals.push("tacit assumption risk");
|
|
1991
|
+
}
|
|
1992
|
+
return {
|
|
1993
|
+
cwd: workingDirectory,
|
|
1994
|
+
checkpoint: trigger.signal.checkpointKey,
|
|
1995
|
+
family: trigger.family,
|
|
1996
|
+
confidence: trigger.confidence,
|
|
1997
|
+
requiresQuestionBeforeClosure: trigger.requiresQuestionBeforeClosure,
|
|
1998
|
+
signals: signals.length > 0 ? signals : ["no specific gap/tacit signal beyond checkpoint classifier"],
|
|
1999
|
+
rationale: trigger.rationale
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
async function runSentinel(args) {
|
|
2003
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2004
|
+
const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
2005
|
+
if (!prompt) {
|
|
2006
|
+
throw new Error("A prompt is required.");
|
|
2007
|
+
}
|
|
2008
|
+
const summary = sentinelSummary(prompt, workingDirectory);
|
|
2009
|
+
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
2010
|
+
if (args.record === true && context) {
|
|
2011
|
+
const state = await loadWorkspaceState(context);
|
|
2012
|
+
state.inferredHypotheses.push({
|
|
2013
|
+
hypothesis: `Sentinel detected: ${summary.signals.join(", ")}.`,
|
|
2014
|
+
confidence: summary.confidence === "high" ? 0.85 : summary.confidence === "medium" ? 0.65 : 0.4,
|
|
2015
|
+
evidence: [`Prompt: ${prompt}`],
|
|
2016
|
+
status: "unconfirmed"
|
|
2017
|
+
});
|
|
2018
|
+
if (summary.requiresQuestionBeforeClosure) {
|
|
2019
|
+
state.openTensions.push(`Pending sentinel risk: ${summary.checkpoint}`);
|
|
2020
|
+
}
|
|
2021
|
+
await writeFile(context.stateFilePath, JSON.stringify(state, null, 2), "utf8");
|
|
2022
|
+
await syncCurrentWorkspaceView(context);
|
|
2023
|
+
}
|
|
2024
|
+
if (args.json === true) {
|
|
2025
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
console.log("LongTable Sentinel");
|
|
2029
|
+
console.log(`- checkpoint: ${summary.checkpoint}`);
|
|
2030
|
+
console.log(`- family: ${summary.family}`);
|
|
2031
|
+
console.log(`- confidence: ${summary.confidence}`);
|
|
2032
|
+
console.log(`- question before closure: ${summary.requiresQuestionBeforeClosure ? "yes" : "no"}`);
|
|
2033
|
+
console.log("- detected signals:");
|
|
2034
|
+
for (const signal of summary.signals) {
|
|
2035
|
+
console.log(` - ${signal}`);
|
|
2036
|
+
}
|
|
2037
|
+
if (args.record === true) {
|
|
2038
|
+
console.log(context ? `- recorded in: ${context.stateFilePath}` : "- record skipped: no LongTable workspace found");
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
function renderHudText(inspection, preset) {
|
|
2042
|
+
if (!inspection.found) {
|
|
2043
|
+
return [
|
|
2044
|
+
"LongTable HUD",
|
|
2045
|
+
"- workspace: not found",
|
|
2046
|
+
"- run `longtable start` for durable research state"
|
|
2047
|
+
].join("\n");
|
|
2048
|
+
}
|
|
2049
|
+
const lines = [
|
|
2050
|
+
"LongTable HUD",
|
|
2051
|
+
`- project: ${inspection.project?.name}`,
|
|
2052
|
+
`- goal: ${inspection.session?.currentGoal}`,
|
|
2053
|
+
...(inspection.session?.currentBlocker ? [`- blocker: ${inspection.session.currentBlocker}`] : []),
|
|
2054
|
+
`- questions: ${inspection.counts?.pendingQuestions ?? 0} pending / ${inspection.counts?.questions ?? 0} total`,
|
|
2055
|
+
`- decisions: ${inspection.counts?.decisions ?? 0}`,
|
|
2056
|
+
`- invocations: ${inspection.counts?.invocations ?? 0}`
|
|
2057
|
+
];
|
|
2058
|
+
if (preset !== "minimal") {
|
|
2059
|
+
lines.push("- pending checkpoints:");
|
|
2060
|
+
for (const question of inspection.pendingQuestions ?? []) {
|
|
2061
|
+
lines.push(` - ${question.required ? "required" : "advisory"}: ${question.question}`);
|
|
2062
|
+
}
|
|
2063
|
+
lines.push("- recent decisions:");
|
|
2064
|
+
for (const decision of inspection.recentDecisions ?? []) {
|
|
2065
|
+
lines.push(` - ${decision.summary}`);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
return lines.join("\n");
|
|
2069
|
+
}
|
|
2070
|
+
async function runHud(args) {
|
|
2071
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2072
|
+
const preset = typeof args.preset === "string" ? args.preset : "full";
|
|
2073
|
+
if (args.tmux === true) {
|
|
2074
|
+
if (!process.env.TMUX) {
|
|
2075
|
+
throw new Error("`longtable hud --tmux` must be run inside an existing tmux session.");
|
|
2076
|
+
}
|
|
2077
|
+
const launcher = process.argv[1] ?? "longtable";
|
|
2078
|
+
const command = `node ${shellEscape(launcher)} hud --watch --preset ${shellEscape(preset)} --cwd ${shellEscape(workingDirectory)}`;
|
|
2079
|
+
execFileSync("tmux", ["split-window", "-v", "-l", "10", command], { stdio: "inherit" });
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
while (true) {
|
|
2083
|
+
const inspection = await inspectProjectWorkspace(workingDirectory);
|
|
2084
|
+
if (args.json === true) {
|
|
2085
|
+
console.log(JSON.stringify(inspection, null, 2));
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
if (args.watch === true) {
|
|
2089
|
+
process.stdout.write("\u001Bc");
|
|
2090
|
+
}
|
|
2091
|
+
console.log(renderHudText(inspection, preset));
|
|
2092
|
+
if (args.watch !== true) {
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 1500));
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
async function runTeam(args) {
|
|
2099
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2100
|
+
const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
2101
|
+
if (!prompt) {
|
|
2102
|
+
throw new Error("A prompt is required.");
|
|
2103
|
+
}
|
|
2104
|
+
const rounds = typeof args.rounds === "string" ? Number(args.rounds) : 5;
|
|
2105
|
+
if (!Number.isInteger(rounds) || rounds !== 5) {
|
|
2106
|
+
throw new Error("LongTable team debate v1 supports `--rounds 5` only.");
|
|
2107
|
+
}
|
|
2108
|
+
const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
|
|
2109
|
+
const projectContext = await loadProjectContextFromDirectory(workingDirectory);
|
|
2110
|
+
if (projectContext) {
|
|
2111
|
+
await assertWorkspaceNotBlocked(projectContext);
|
|
2112
|
+
}
|
|
2113
|
+
const projectAware = await buildProjectAwarePrompt(prompt, workingDirectory);
|
|
2114
|
+
const fallback = buildPanelFallback({
|
|
2115
|
+
prompt,
|
|
2116
|
+
mode: "review",
|
|
2117
|
+
roleFlag: typeof args.role === "string" ? args.role : undefined,
|
|
2118
|
+
provider: setup?.providerSelection.provider,
|
|
2119
|
+
visibility: "always_visible"
|
|
2120
|
+
});
|
|
2121
|
+
const teamId = localId("team");
|
|
2122
|
+
const teamDir = join(workingDirectory, ".longtable", "team", teamId);
|
|
2123
|
+
if (args.debate === true) {
|
|
2124
|
+
const debate = buildTeamDebate({
|
|
2125
|
+
teamId,
|
|
2126
|
+
teamDir,
|
|
2127
|
+
prompt: projectAware.prompt,
|
|
2128
|
+
roleFlag: typeof args.role === "string" ? args.role : undefined,
|
|
2129
|
+
provider: setup?.providerSelection.provider,
|
|
2130
|
+
visibility: "always_visible",
|
|
2131
|
+
roundCount: rounds,
|
|
2132
|
+
tmux: args.tmux === true
|
|
2133
|
+
});
|
|
2134
|
+
await writeTeamDebateArtifacts(debate, teamDir, prompt);
|
|
2135
|
+
const canRecordWorkspace = projectAware.projectContextFound && projectContext && existsSync(projectContext.stateFilePath);
|
|
2136
|
+
if (canRecordWorkspace) {
|
|
2137
|
+
await appendInvocationRecordToWorkspace(projectContext, debate.invocationRecord, [debate.questionRecord]);
|
|
2138
|
+
}
|
|
2139
|
+
if (args.json === true) {
|
|
2140
|
+
console.log(JSON.stringify({
|
|
2141
|
+
teamId,
|
|
2142
|
+
teamDir,
|
|
2143
|
+
plan: debate.plan,
|
|
2144
|
+
run: debate.run,
|
|
2145
|
+
questionRecord: debate.questionRecord,
|
|
2146
|
+
invocationRecord: debate.invocationRecord,
|
|
2147
|
+
execution: {
|
|
2148
|
+
status: "completed",
|
|
2149
|
+
surface: debate.run.surface,
|
|
2150
|
+
projectContextFound: projectAware.projectContextFound,
|
|
2151
|
+
invocationLogged: canRecordWorkspace
|
|
2152
|
+
}
|
|
2153
|
+
}, null, 2));
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
if (args.tmux === true) {
|
|
2157
|
+
const sessionName = `longtable-${teamId.replaceAll("_", "-")}`;
|
|
2158
|
+
const shell = process.env.SHELL || "/bin/sh";
|
|
2159
|
+
const launcher = process.argv[1] ?? "longtable";
|
|
2160
|
+
const leaderCommand = [
|
|
2161
|
+
`echo ${shellEscape(`LongTable debate ${teamId}`)}`,
|
|
2162
|
+
`echo ${shellEscape(`Artifacts: ${teamDir}`)}`,
|
|
2163
|
+
`echo ${shellEscape("Fixed rounds are recorded. Role panes can add live review logs.")}`,
|
|
2164
|
+
`echo ${shellEscape(`Checkpoint: ${debate.questionRecord.id}`)}`,
|
|
2165
|
+
`exec ${shellEscape(shell)}`
|
|
2166
|
+
].join("; ");
|
|
2167
|
+
execFileSync("tmux", ["new-session", "-d", "-s", sessionName, "-c", workingDirectory, leaderCommand], { stdio: "inherit" });
|
|
2168
|
+
for (const member of debate.plan.members) {
|
|
2169
|
+
const rolePrompt = [
|
|
2170
|
+
`LongTable autonomous debate role: ${member.label} (${member.role}).`,
|
|
2171
|
+
"Use the fixed debate artifacts as the shared record. Add live notes only; do not answer the researcher checkpoint.",
|
|
2172
|
+
`Artifacts: ${teamDir}`,
|
|
2173
|
+
"",
|
|
2174
|
+
projectAware.prompt
|
|
2175
|
+
].join("\n");
|
|
2176
|
+
const logPath = join(teamDir, `${member.role}.debate.log`);
|
|
2177
|
+
const command = [
|
|
2178
|
+
`node ${shellEscape(launcher)} review --role ${shellEscape(member.role)} --prompt ${shellEscape(rolePrompt)} --cwd ${shellEscape(workingDirectory)} 2>&1 | tee ${shellEscape(logPath)}`,
|
|
2179
|
+
`echo ${shellEscape(`Debate role log written to ${logPath}`)}`,
|
|
2180
|
+
`exec ${shellEscape(shell)}`
|
|
2181
|
+
].join("; ");
|
|
2182
|
+
execFileSync("tmux", ["split-window", "-t", sessionName, "-c", workingDirectory, command], { stdio: "inherit" });
|
|
2183
|
+
execFileSync("tmux", ["select-layout", "-t", sessionName, "tiled"], { stdio: "ignore" });
|
|
2184
|
+
}
|
|
2185
|
+
console.log(`LongTable tmux debate launched: ${sessionName}`);
|
|
2186
|
+
console.log(`Attach with: tmux attach -t ${sessionName}`);
|
|
2187
|
+
console.log(`Artifacts: ${teamDir}`);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
console.log(renderTeamDebateSummary(debate.run));
|
|
2191
|
+
console.log(`- checkpoint: ${debate.questionRecord.id}`);
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
await mkdir(teamDir, { recursive: true });
|
|
2195
|
+
await writeFile(join(teamDir, "prompt.txt"), prompt, "utf8");
|
|
2196
|
+
await writeFile(join(teamDir, "plan.json"), JSON.stringify(fallback.plan, null, 2), "utf8");
|
|
2197
|
+
if (args.json === true) {
|
|
2198
|
+
console.log(JSON.stringify({ teamId, teamDir, plan: fallback.plan }, null, 2));
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
if (args.tmux !== true) {
|
|
2202
|
+
console.log(renderPanelSummary(fallback.plan));
|
|
2203
|
+
console.log("");
|
|
2204
|
+
console.log("Run with `--tmux` to launch role panes for parallel discussion.");
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
const sessionName = `longtable-${teamId.replaceAll("_", "-")}`;
|
|
2208
|
+
const shell = process.env.SHELL || "/bin/sh";
|
|
2209
|
+
const launcher = process.argv[1] ?? "longtable";
|
|
2210
|
+
const leaderCommand = [
|
|
2211
|
+
`echo ${shellEscape(`LongTable team ${teamId}`)}`,
|
|
2212
|
+
`echo ${shellEscape(`Logs: ${teamDir}`)}`,
|
|
2213
|
+
"echo 'Role panes are running. Review logs, then run:'",
|
|
2214
|
+
`echo ${shellEscape(`longtable panel --role ${fallback.plan.members.map((member) => member.role).join(",")} --prompt ${JSON.stringify(prompt)}`)}`,
|
|
2215
|
+
`exec ${shellEscape(shell)}`
|
|
2216
|
+
].join("; ");
|
|
2217
|
+
execFileSync("tmux", ["new-session", "-d", "-s", sessionName, "-c", workingDirectory, leaderCommand], { stdio: "inherit" });
|
|
2218
|
+
for (const member of fallback.plan.members) {
|
|
2219
|
+
const rolePrompt = [
|
|
2220
|
+
`LongTable team discussion role: ${member.label} (${member.role}).`,
|
|
2221
|
+
"Give claims, objections, open questions, and evidence needs. Address likely disagreement with other roles.",
|
|
2222
|
+
"",
|
|
2223
|
+
prompt
|
|
2224
|
+
].join("\n");
|
|
2225
|
+
const logPath = join(teamDir, `${member.role}.log`);
|
|
2226
|
+
const command = [
|
|
2227
|
+
`node ${shellEscape(launcher)} review --role ${shellEscape(member.role)} --prompt ${shellEscape(rolePrompt)} --cwd ${shellEscape(workingDirectory)} 2>&1 | tee ${shellEscape(logPath)}`,
|
|
2228
|
+
`echo ${shellEscape(`Role log written to ${logPath}`)}`,
|
|
2229
|
+
`exec ${shellEscape(shell)}`
|
|
2230
|
+
].join("; ");
|
|
2231
|
+
execFileSync("tmux", ["split-window", "-t", sessionName, "-c", workingDirectory, command], { stdio: "inherit" });
|
|
2232
|
+
execFileSync("tmux", ["select-layout", "-t", sessionName, "tiled"], { stdio: "ignore" });
|
|
2233
|
+
}
|
|
2234
|
+
console.log(`LongTable tmux team launched: ${sessionName}`);
|
|
2235
|
+
console.log(`Attach with: tmux attach -t ${sessionName}`);
|
|
2236
|
+
console.log(`Logs: ${teamDir}`);
|
|
2237
|
+
}
|
|
1812
2238
|
async function runDecide(args) {
|
|
1813
2239
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
1814
2240
|
const answer = typeof args.answer === "string" ? args.answer.trim() : "";
|
|
@@ -2095,6 +2521,10 @@ async function main() {
|
|
|
2095
2521
|
await runInit(values);
|
|
2096
2522
|
return;
|
|
2097
2523
|
}
|
|
2524
|
+
if (command === "setup") {
|
|
2525
|
+
await runSetup(values);
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2098
2528
|
if (command === "start") {
|
|
2099
2529
|
await runStart(values);
|
|
2100
2530
|
return;
|
|
@@ -2139,6 +2569,18 @@ async function main() {
|
|
|
2139
2569
|
await runPanelCommand(values);
|
|
2140
2570
|
return;
|
|
2141
2571
|
}
|
|
2572
|
+
if (command === "hud") {
|
|
2573
|
+
await runHud(values);
|
|
2574
|
+
return;
|
|
2575
|
+
}
|
|
2576
|
+
if (command === "sentinel") {
|
|
2577
|
+
await runSentinel(values);
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
if (command === "team") {
|
|
2581
|
+
await runTeam(values);
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2142
2584
|
if (command === "decide") {
|
|
2143
2585
|
await runDecide(values);
|
|
2144
2586
|
return;
|