@longtable/cli 0.1.24 → 0.1.26
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 +22 -19
- package/dist/cli.js +112 -112
- package/dist/debate.d.ts +1 -1
- package/dist/debate.js +72 -47
- package/dist/panel.js +3 -0
- package/dist/persona-router.d.ts +1 -0
- package/dist/persona-router.js +4 -1
- package/dist/project-session.d.ts +5 -2
- package/dist/project-session.js +23 -21
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -20,8 +20,7 @@ npm install -g @longtable/cli
|
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
The npm install only installs the CLI. It does not write Codex skills, MCP
|
|
23
|
-
config, hooks,
|
|
24
|
-
approval.
|
|
23
|
+
config, hooks, or provider runtime files without explicit setup approval.
|
|
25
24
|
|
|
26
25
|
## Primary Flow
|
|
27
26
|
|
|
@@ -91,8 +90,7 @@ longtable roles
|
|
|
91
90
|
longtable ask --cwd "<project-path>" --prompt "..."
|
|
92
91
|
longtable panel --prompt "..."
|
|
93
92
|
longtable sentinel --prompt "Should I define a new measurement construct?"
|
|
94
|
-
longtable
|
|
95
|
-
longtable team --tmux --prompt "Review this measurement plan."
|
|
93
|
+
longtable team --prompt "Review this measurement plan." --role editor,measurement_auditor --json
|
|
96
94
|
longtable team --debate --prompt "Review this measurement plan." --role editor,measurement_auditor --json
|
|
97
95
|
longtable codex install-skills
|
|
98
96
|
longtable claude install-skills
|
|
@@ -110,6 +108,16 @@ longtable ask --prompt "lt panel: show the disagreement before I commit" --json
|
|
|
110
108
|
|
|
111
109
|
Natural language should be the default.
|
|
112
110
|
|
|
111
|
+
Codex UI Researcher Checkpoints are a core LongTable feature when enabled:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
longtable setup --provider codex --surfaces skills_mcp --checkpoint-ui strong
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
That setup writes the MCP configuration and Codex elicitation approval needed
|
|
118
|
+
for form-style checkpoint prompts. Without it, LongTable keeps the same
|
|
119
|
+
`QuestionRecord` pending and falls back to numbered text.
|
|
120
|
+
|
|
113
121
|
Explicit short forms are available when needed:
|
|
114
122
|
|
|
115
123
|
```text
|
|
@@ -151,10 +159,6 @@ Inside a LongTable project workspace, panel planning also appends an
|
|
|
151
159
|
`InvocationRecord` to `.longtable/state.json`, creates a pending follow-up
|
|
152
160
|
`QuestionRecord`, and refreshes `CURRENT.md`.
|
|
153
161
|
|
|
154
|
-
```bash
|
|
155
|
-
longtable decide --answer evidence --rationale "Need citation support before continuing."
|
|
156
|
-
```
|
|
157
|
-
|
|
158
162
|
Default panel roles include:
|
|
159
163
|
|
|
160
164
|
- `reviewer`
|
|
@@ -164,26 +168,25 @@ Default panel roles include:
|
|
|
164
168
|
|
|
165
169
|
Use `--role` to constrain the panel when the research problem is already clear.
|
|
166
170
|
|
|
167
|
-
## Sentinel
|
|
171
|
+
## Sentinel And Agent Team
|
|
168
172
|
|
|
169
173
|
`longtable sentinel` is an explicit gap/tacit check for prompts that may contain
|
|
170
174
|
measurement, theory, method, evidence, authorship, or tacit-assumption risks.
|
|
171
175
|
Use `--record` inside a LongTable workspace to store the finding as an
|
|
172
176
|
unconfirmed inferred hypothesis.
|
|
173
177
|
|
|
174
|
-
`longtable
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
`longtable team --tmux` opens role-specific panes for research discussion and
|
|
179
|
-
writes logs under `.longtable/team/<id>/`. This is panel discussion, not merely
|
|
180
|
-
parallel execution: role panes are prompted to state claims, objections, open
|
|
181
|
-
questions, and likely disagreement.
|
|
178
|
+
`longtable team` creates a file-backed agent-team review under
|
|
179
|
+
`.longtable/team/<id>/`: independent review, cross-review, and
|
|
180
|
+
synthesis/checkpoint. Use it when roles should inspect each other's concerns
|
|
181
|
+
before LongTable proposes a researcher decision.
|
|
182
182
|
|
|
183
183
|
`longtable team --debate` creates a fixed five-round debate record under
|
|
184
184
|
`.longtable/team/<id>/`: independent review, cross-review, rebuttal,
|
|
185
|
-
convergence, and synthesis/checkpoint.
|
|
186
|
-
|
|
185
|
+
convergence, and synthesis/checkpoint. The file-backed artifact directory is
|
|
186
|
+
the source of truth.
|
|
187
|
+
|
|
188
|
+
See `docs/AGENT-TEAM-README.md` in the repository for a user-facing guide to
|
|
189
|
+
panel, team, and debate surfaces.
|
|
187
190
|
|
|
188
191
|
## Evidence And Search Direction
|
|
189
192
|
|
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 {
|
|
4
|
+
import { 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";
|
|
@@ -15,8 +15,8 @@ import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodex
|
|
|
15
15
|
import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router.js";
|
|
16
16
|
import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
|
|
17
17
|
import { buildPanelFallback, renderPanelSummary } from "./panel.js";
|
|
18
|
-
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion,
|
|
19
|
-
import { buildTeamDebate, renderTeamDebateSummary } from "./debate.js";
|
|
18
|
+
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
19
|
+
import { buildTeamDebate, buildTeamReview, renderTeamDebateSummary } from "./debate.js";
|
|
20
20
|
const VALID_MODES = new Set([
|
|
21
21
|
"explore",
|
|
22
22
|
"review",
|
|
@@ -42,7 +42,7 @@ const ANSI = {
|
|
|
42
42
|
green: "\u001B[32m"
|
|
43
43
|
};
|
|
44
44
|
const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
|
|
45
|
-
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.
|
|
45
|
+
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.26";
|
|
46
46
|
const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
|
|
47
47
|
const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
|
|
48
48
|
function style(text, prefix) {
|
|
@@ -89,7 +89,7 @@ function usage() {
|
|
|
89
89
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
90
90
|
" longtable mcp install [--provider codex|claude|all] [--write] [--checkpoint-ui off|interactive|strong] [--json] [--codex-config <path>] [--claude-settings <path>] [--package <spec>]",
|
|
91
91
|
" longtable sentinel --prompt <text> [--cwd <path>] [--json] [--record]",
|
|
92
|
-
" longtable team --prompt <text> [--role <role[,role]>] [--
|
|
92
|
+
" longtable team --prompt <text> [--role <role[,role]>] [--debate] [--rounds 3|5] [--cwd <path>] [--json]",
|
|
93
93
|
" longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
94
94
|
" longtable clarify --prompt <task-context> [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json] [--force]",
|
|
95
95
|
" longtable question --prompt <decision-context> [--title <text>] [--text <question>] [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json]",
|
|
@@ -622,12 +622,12 @@ function buildPermissionSetupChoices() {
|
|
|
622
622
|
{
|
|
623
623
|
id: "strong",
|
|
624
624
|
label: "Strong Researcher Checkpoint UI",
|
|
625
|
-
description: "Why:
|
|
625
|
+
description: "Why: makes Codex UI checkpoints the default for high-responsibility research decisions. What you get: MCP form checkpoints with a single decision field. Tradeoff: requires Codex MCP elicitation approval."
|
|
626
626
|
},
|
|
627
627
|
{
|
|
628
628
|
id: "interactive",
|
|
629
629
|
label: "Interactive checkpoint UI",
|
|
630
|
-
description: "Why: keeps UI prompts available for required checkpoints. What you get: MCP form checkpoints when supported. Tradeoff: requires Codex MCP elicitation approval."
|
|
630
|
+
description: "Why: keeps Codex UI prompts available for required checkpoints. What you get: MCP form checkpoints when supported. Tradeoff: requires Codex MCP elicitation approval."
|
|
631
631
|
},
|
|
632
632
|
{
|
|
633
633
|
id: "off",
|
|
@@ -768,7 +768,6 @@ async function runSetup(args) {
|
|
|
768
768
|
interventionPosture: effectiveIntervention,
|
|
769
769
|
checkpointUiMode: checkpointUi,
|
|
770
770
|
workspaceCreationPreference: workspacePreference,
|
|
771
|
-
tmuxMode: "standard",
|
|
772
771
|
teamMode: "panel"
|
|
773
772
|
};
|
|
774
773
|
if (surfaces === "skills_mcp_sentinel") {
|
|
@@ -1769,6 +1768,38 @@ function inferModeFromPrompt(prompt) {
|
|
|
1769
1768
|
}
|
|
1770
1769
|
return "explore";
|
|
1771
1770
|
}
|
|
1771
|
+
function inferCollaborationRoute(prompt) {
|
|
1772
|
+
const normalized = prompt.toLowerCase();
|
|
1773
|
+
const explicitDebate = /\bdebate\b|\bdebated\b|\brebuttal\b|\bconvergence\b|\bargue both sides\b/i.test(prompt) ||
|
|
1774
|
+
/토론|논쟁|반박|재반박|수렴/.test(prompt);
|
|
1775
|
+
if (explicitDebate) {
|
|
1776
|
+
return "debate";
|
|
1777
|
+
}
|
|
1778
|
+
const explicitTeam = /\bagent team\b|\bresearch team\b|\bteam review\b|\bteam-style\b|\buse a team\b/i.test(prompt) ||
|
|
1779
|
+
/에이전트\s*팀|연구\s*팀|팀\s*(리뷰|검토)|팀으로/.test(prompt);
|
|
1780
|
+
if (explicitTeam) {
|
|
1781
|
+
return "team";
|
|
1782
|
+
}
|
|
1783
|
+
const panelCue = /\bpanel\b|\bmulti[- ]?role\b|\bmultiple perspectives\b|\brole disagreement\b|\bdisagreement\b|\bconflict\b/i.test(prompt) ||
|
|
1784
|
+
/패널|여러\s*관점|복수\s*관점|역할.*불일치|불일치|충돌/.test(prompt);
|
|
1785
|
+
const multiPerspectiveCue = panelCue ||
|
|
1786
|
+
/\bperspectives?\b|\broles?\b|\breviewer and editor\b|\beditor and reviewer\b|\bmethods and measurement\b/i.test(prompt) ||
|
|
1787
|
+
/관점|역할|리뷰어.*에디터|에디터.*리뷰어|방법.*측정|측정.*방법/.test(prompt);
|
|
1788
|
+
if (!multiPerspectiveCue) {
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
const trigger = classifyCheckpointTrigger(prompt, {});
|
|
1792
|
+
const highStakes = trigger.signal.artifactStakes === "external_submission" ||
|
|
1793
|
+
trigger.signal.artifactStakes === "study_protocol" ||
|
|
1794
|
+
trigger.requiresQuestionBeforeClosure;
|
|
1795
|
+
if (panelCue && trigger.signal.artifactStakes === "external_submission") {
|
|
1796
|
+
return "debate";
|
|
1797
|
+
}
|
|
1798
|
+
if (highStakes) {
|
|
1799
|
+
return "team";
|
|
1800
|
+
}
|
|
1801
|
+
return "panel";
|
|
1802
|
+
}
|
|
1772
1803
|
function parsePanelVisibility(value) {
|
|
1773
1804
|
if (value === "synthesis_only" ||
|
|
1774
1805
|
value === "show_on_conflict" ||
|
|
@@ -1820,6 +1851,9 @@ async function runModeCommand(mode, args) {
|
|
|
1820
1851
|
}
|
|
1821
1852
|
const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
|
|
1822
1853
|
const projectContext = await loadProjectContextFromDirectory(workingDirectory);
|
|
1854
|
+
if (projectContext) {
|
|
1855
|
+
await assertWorkspaceNotBlocked(projectContext);
|
|
1856
|
+
}
|
|
1823
1857
|
const projectAware = await buildProjectAwarePrompt(prompt, workingDirectory);
|
|
1824
1858
|
const panelPreference = setup?.profileSeed.panelPreference;
|
|
1825
1859
|
const panelRequested = args.panel === true ||
|
|
@@ -2019,9 +2053,9 @@ function questionRecordToChoices(record) {
|
|
|
2019
2053
|
: [])
|
|
2020
2054
|
];
|
|
2021
2055
|
}
|
|
2022
|
-
function
|
|
2056
|
+
function renderFollowUpQuestions(questions) {
|
|
2023
2057
|
if (questions.length === 0) {
|
|
2024
|
-
return "No new
|
|
2058
|
+
return "No new follow-up questions are pending for this prompt.";
|
|
2025
2059
|
}
|
|
2026
2060
|
const width = 44;
|
|
2027
2061
|
const boxLine = (text = "") => `│ ${text.padEnd(width, " ")} │`;
|
|
@@ -2072,13 +2106,13 @@ function renderClarificationCard(questions) {
|
|
|
2072
2106
|
lines.push("Answer in a terminal with `longtable clarify --prompt ...`, or record choices with `longtable decide --question <id> --answer <value>`.");
|
|
2073
2107
|
return lines.join("\n");
|
|
2074
2108
|
}
|
|
2075
|
-
async function
|
|
2109
|
+
async function answerFollowUpQuestionsInTerminal(context, questions, provider) {
|
|
2076
2110
|
if (questions.length === 0) {
|
|
2077
2111
|
return;
|
|
2078
2112
|
}
|
|
2079
2113
|
const rl = createInterface({ input, output });
|
|
2080
2114
|
try {
|
|
2081
|
-
console.log(renderBrandBanner("LongTable", "
|
|
2115
|
+
console.log(renderBrandBanner("LongTable", "Follow-up Questions"));
|
|
2082
2116
|
console.log("");
|
|
2083
2117
|
for (let index = 0; index < questions.length; index += 1) {
|
|
2084
2118
|
const question = questions[index];
|
|
@@ -2109,7 +2143,7 @@ async function runClarify(args) {
|
|
|
2109
2143
|
}
|
|
2110
2144
|
const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
|
|
2111
2145
|
const required = args.required === true ? true : args.advisory === true ? false : undefined;
|
|
2112
|
-
const result = await
|
|
2146
|
+
const result = await createWorkspaceFollowUpQuestions({
|
|
2113
2147
|
context,
|
|
2114
2148
|
prompt,
|
|
2115
2149
|
provider,
|
|
@@ -2129,17 +2163,17 @@ async function runClarify(args) {
|
|
|
2129
2163
|
return;
|
|
2130
2164
|
}
|
|
2131
2165
|
if (args.print === true || !isInteractiveTerminal()) {
|
|
2132
|
-
console.log(
|
|
2166
|
+
console.log(renderFollowUpQuestions(result.questions));
|
|
2133
2167
|
return;
|
|
2134
2168
|
}
|
|
2135
|
-
await
|
|
2169
|
+
await answerFollowUpQuestionsInTerminal(context, result.questions, provider);
|
|
2136
2170
|
console.log("");
|
|
2137
|
-
console.log("LongTable
|
|
2171
|
+
console.log("LongTable follow-up decisions recorded");
|
|
2138
2172
|
console.log(`- answered: ${result.questions.length}`);
|
|
2139
2173
|
console.log(`- state: ${context.stateFilePath}`);
|
|
2140
2174
|
console.log(`- current: ${context.currentFilePath}`);
|
|
2141
2175
|
}
|
|
2142
|
-
async function
|
|
2176
|
+
async function runAutomaticFollowUpIfNeeded(prompt, args) {
|
|
2143
2177
|
if (args["no-clarify"] === true || args.print === true || args.json === true) {
|
|
2144
2178
|
return false;
|
|
2145
2179
|
}
|
|
@@ -2149,7 +2183,7 @@ async function runAutomaticClarificationIfNeeded(prompt, args) {
|
|
|
2149
2183
|
return false;
|
|
2150
2184
|
}
|
|
2151
2185
|
const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
|
|
2152
|
-
const result = await
|
|
2186
|
+
const result = await createWorkspaceFollowUpQuestions({
|
|
2153
2187
|
context,
|
|
2154
2188
|
prompt,
|
|
2155
2189
|
provider,
|
|
@@ -2159,17 +2193,22 @@ async function runAutomaticClarificationIfNeeded(prompt, args) {
|
|
|
2159
2193
|
return false;
|
|
2160
2194
|
}
|
|
2161
2195
|
if (!isInteractiveTerminal()) {
|
|
2162
|
-
console.log(
|
|
2196
|
+
console.log(renderFollowUpQuestions(result.questions));
|
|
2163
2197
|
return true;
|
|
2164
2198
|
}
|
|
2165
|
-
await
|
|
2199
|
+
await answerFollowUpQuestionsInTerminal(context, result.questions, provider);
|
|
2166
2200
|
return false;
|
|
2167
2201
|
}
|
|
2168
2202
|
async function runAsk(args) {
|
|
2203
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2169
2204
|
const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
2170
2205
|
if (!prompt) {
|
|
2171
2206
|
throw new Error("A prompt is required.");
|
|
2172
2207
|
}
|
|
2208
|
+
const projectContext = await loadProjectContextFromDirectory(workingDirectory);
|
|
2209
|
+
if (projectContext) {
|
|
2210
|
+
await assertWorkspaceNotBlocked(projectContext);
|
|
2211
|
+
}
|
|
2173
2212
|
const directive = parseInvocationDirective(prompt);
|
|
2174
2213
|
const effectivePrompt = directive.cleanedPrompt;
|
|
2175
2214
|
const inferred = directive.mode ?? inferModeFromPrompt(effectivePrompt);
|
|
@@ -2178,7 +2217,7 @@ async function runAsk(args) {
|
|
|
2178
2217
|
return;
|
|
2179
2218
|
}
|
|
2180
2219
|
const mode = inferred === "panel" ? "review" : inferred;
|
|
2181
|
-
if (await
|
|
2220
|
+
if (await runAutomaticFollowUpIfNeeded(effectivePrompt, args)) {
|
|
2182
2221
|
return;
|
|
2183
2222
|
}
|
|
2184
2223
|
const delegatedArgs = {
|
|
@@ -2188,7 +2227,18 @@ async function runAsk(args) {
|
|
|
2188
2227
|
if (directive.roles.length > 0 && typeof delegatedArgs.role !== "string") {
|
|
2189
2228
|
delegatedArgs.role = directive.roles.join(",");
|
|
2190
2229
|
}
|
|
2191
|
-
|
|
2230
|
+
const collaborationRoute = directive.collaboration ??
|
|
2231
|
+
(directive.panel || delegatedArgs.panel === true
|
|
2232
|
+
? "panel"
|
|
2233
|
+
: inferCollaborationRoute(effectivePrompt) ?? (inferred === "panel" ? "panel" : null));
|
|
2234
|
+
if (collaborationRoute === "team" || collaborationRoute === "debate") {
|
|
2235
|
+
await runTeam({
|
|
2236
|
+
...delegatedArgs,
|
|
2237
|
+
debate: collaborationRoute === "debate"
|
|
2238
|
+
});
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
if (collaborationRoute === "panel") {
|
|
2192
2242
|
await runPanelCommand({
|
|
2193
2243
|
...delegatedArgs,
|
|
2194
2244
|
visibility: "always_visible"
|
|
@@ -2200,9 +2250,6 @@ async function runAsk(args) {
|
|
|
2200
2250
|
function localId(prefix) {
|
|
2201
2251
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2202
2252
|
}
|
|
2203
|
-
function shellEscape(value) {
|
|
2204
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
2205
|
-
}
|
|
2206
2253
|
async function writeJsonFile(path, value) {
|
|
2207
2254
|
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
2208
2255
|
}
|
|
@@ -2305,9 +2352,13 @@ async function runTeam(args) {
|
|
|
2305
2352
|
if (!prompt) {
|
|
2306
2353
|
throw new Error("A prompt is required.");
|
|
2307
2354
|
}
|
|
2308
|
-
const
|
|
2309
|
-
|
|
2310
|
-
|
|
2355
|
+
const isDebate = args.debate === true;
|
|
2356
|
+
const expectedRounds = isDebate ? 5 : 3;
|
|
2357
|
+
const rounds = typeof args.rounds === "string" ? Number(args.rounds) : expectedRounds;
|
|
2358
|
+
if (!Number.isInteger(rounds) || rounds !== expectedRounds) {
|
|
2359
|
+
throw new Error(isDebate
|
|
2360
|
+
? "LongTable team debate v1 supports `--rounds 5` only."
|
|
2361
|
+
: "LongTable team v1 supports `--rounds 3` cross-review only.");
|
|
2311
2362
|
}
|
|
2312
2363
|
const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
|
|
2313
2364
|
const projectContext = await loadProjectContextFromDirectory(workingDirectory);
|
|
@@ -2315,16 +2366,9 @@ async function runTeam(args) {
|
|
|
2315
2366
|
await assertWorkspaceNotBlocked(projectContext);
|
|
2316
2367
|
}
|
|
2317
2368
|
const projectAware = await buildProjectAwarePrompt(prompt, workingDirectory);
|
|
2318
|
-
const fallback = buildPanelFallback({
|
|
2319
|
-
prompt,
|
|
2320
|
-
mode: "review",
|
|
2321
|
-
roleFlag: typeof args.role === "string" ? args.role : undefined,
|
|
2322
|
-
provider: setup?.providerSelection.provider,
|
|
2323
|
-
visibility: "always_visible"
|
|
2324
|
-
});
|
|
2325
2369
|
const teamId = localId("team");
|
|
2326
2370
|
const teamDir = join(workingDirectory, ".longtable", "team", teamId);
|
|
2327
|
-
if (
|
|
2371
|
+
if (isDebate) {
|
|
2328
2372
|
const debate = buildTeamDebate({
|
|
2329
2373
|
teamId,
|
|
2330
2374
|
teamDir,
|
|
@@ -2332,8 +2376,7 @@ async function runTeam(args) {
|
|
|
2332
2376
|
roleFlag: typeof args.role === "string" ? args.role : undefined,
|
|
2333
2377
|
provider: setup?.providerSelection.provider,
|
|
2334
2378
|
visibility: "always_visible",
|
|
2335
|
-
roundCount: rounds
|
|
2336
|
-
tmux: args.tmux === true
|
|
2379
|
+
roundCount: rounds
|
|
2337
2380
|
});
|
|
2338
2381
|
await writeTeamDebateArtifacts(debate, teamDir, prompt);
|
|
2339
2382
|
const canRecordWorkspace = projectAware.projectContextFound && projectContext && existsSync(projectContext.stateFilePath);
|
|
@@ -2357,87 +2400,44 @@ async function runTeam(args) {
|
|
|
2357
2400
|
}, null, 2));
|
|
2358
2401
|
return;
|
|
2359
2402
|
}
|
|
2360
|
-
if (args.tmux === true) {
|
|
2361
|
-
const sessionName = `longtable-${teamId.replaceAll("_", "-")}`;
|
|
2362
|
-
const shell = process.env.SHELL || "/bin/sh";
|
|
2363
|
-
const launcher = process.argv[1] ?? "longtable";
|
|
2364
|
-
const leaderCommand = [
|
|
2365
|
-
`echo ${shellEscape(`LongTable debate ${teamId}`)}`,
|
|
2366
|
-
`echo ${shellEscape(`Artifacts: ${teamDir}`)}`,
|
|
2367
|
-
`echo ${shellEscape("Fixed rounds are recorded. Role panes can add live review logs.")}`,
|
|
2368
|
-
`echo ${shellEscape(`Checkpoint: ${debate.questionRecord.id}`)}`,
|
|
2369
|
-
`exec ${shellEscape(shell)}`
|
|
2370
|
-
].join("; ");
|
|
2371
|
-
execFileSync("tmux", ["new-session", "-d", "-s", sessionName, "-c", workingDirectory, leaderCommand], { stdio: "inherit" });
|
|
2372
|
-
for (const member of debate.plan.members) {
|
|
2373
|
-
const rolePrompt = [
|
|
2374
|
-
`LongTable autonomous debate role: ${member.label} (${member.role}).`,
|
|
2375
|
-
"Use the fixed debate artifacts as the shared record. Add live notes only; do not answer the researcher checkpoint.",
|
|
2376
|
-
`Artifacts: ${teamDir}`,
|
|
2377
|
-
"",
|
|
2378
|
-
projectAware.prompt
|
|
2379
|
-
].join("\n");
|
|
2380
|
-
const logPath = join(teamDir, `${member.role}.debate.log`);
|
|
2381
|
-
const command = [
|
|
2382
|
-
`node ${shellEscape(launcher)} review --role ${shellEscape(member.role)} --prompt ${shellEscape(rolePrompt)} --cwd ${shellEscape(workingDirectory)} 2>&1 | tee ${shellEscape(logPath)}`,
|
|
2383
|
-
`echo ${shellEscape(`Debate role log written to ${logPath}`)}`,
|
|
2384
|
-
`exec ${shellEscape(shell)}`
|
|
2385
|
-
].join("; ");
|
|
2386
|
-
execFileSync("tmux", ["split-window", "-t", sessionName, "-c", workingDirectory, command], { stdio: "inherit" });
|
|
2387
|
-
execFileSync("tmux", ["select-layout", "-t", sessionName, "tiled"], { stdio: "ignore" });
|
|
2388
|
-
}
|
|
2389
|
-
console.log(`LongTable tmux debate launched: ${sessionName}`);
|
|
2390
|
-
console.log(`Attach with: tmux attach -t ${sessionName}`);
|
|
2391
|
-
console.log(`Artifacts: ${teamDir}`);
|
|
2392
|
-
return;
|
|
2393
|
-
}
|
|
2394
2403
|
console.log(renderTeamDebateSummary(debate.run));
|
|
2395
2404
|
console.log(`- checkpoint: ${debate.questionRecord.id}`);
|
|
2396
2405
|
return;
|
|
2397
2406
|
}
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2407
|
+
const team = buildTeamReview({
|
|
2408
|
+
teamId,
|
|
2409
|
+
teamDir,
|
|
2410
|
+
prompt: projectAware.prompt,
|
|
2411
|
+
roleFlag: typeof args.role === "string" ? args.role : undefined,
|
|
2412
|
+
provider: setup?.providerSelection.provider,
|
|
2413
|
+
visibility: "always_visible",
|
|
2414
|
+
roundCount: rounds
|
|
2415
|
+
});
|
|
2416
|
+
await writeTeamDebateArtifacts(team, teamDir, prompt);
|
|
2417
|
+
const canRecordWorkspace = projectAware.projectContextFound && projectContext && existsSync(projectContext.stateFilePath);
|
|
2418
|
+
if (canRecordWorkspace) {
|
|
2419
|
+
await appendInvocationRecordToWorkspace(projectContext, team.invocationRecord, [team.questionRecord]);
|
|
2404
2420
|
}
|
|
2405
|
-
if (args.
|
|
2406
|
-
console.log(
|
|
2407
|
-
|
|
2408
|
-
|
|
2421
|
+
if (args.json === true) {
|
|
2422
|
+
console.log(JSON.stringify({
|
|
2423
|
+
teamId,
|
|
2424
|
+
teamDir,
|
|
2425
|
+
plan: team.plan,
|
|
2426
|
+
run: team.run,
|
|
2427
|
+
questionRecord: team.questionRecord,
|
|
2428
|
+
invocationRecord: team.invocationRecord,
|
|
2429
|
+
execution: {
|
|
2430
|
+
status: "completed",
|
|
2431
|
+
surface: team.run.surface,
|
|
2432
|
+
interactionDepth: team.run.interactionDepth,
|
|
2433
|
+
projectContextFound: projectAware.projectContextFound,
|
|
2434
|
+
invocationLogged: canRecordWorkspace
|
|
2435
|
+
}
|
|
2436
|
+
}, null, 2));
|
|
2409
2437
|
return;
|
|
2410
2438
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
const launcher = process.argv[1] ?? "longtable";
|
|
2414
|
-
const leaderCommand = [
|
|
2415
|
-
`echo ${shellEscape(`LongTable team ${teamId}`)}`,
|
|
2416
|
-
`echo ${shellEscape(`Logs: ${teamDir}`)}`,
|
|
2417
|
-
"echo 'Role panes are running. Review logs, then run:'",
|
|
2418
|
-
`echo ${shellEscape(`longtable panel --role ${fallback.plan.members.map((member) => member.role).join(",")} --prompt ${JSON.stringify(prompt)}`)}`,
|
|
2419
|
-
`exec ${shellEscape(shell)}`
|
|
2420
|
-
].join("; ");
|
|
2421
|
-
execFileSync("tmux", ["new-session", "-d", "-s", sessionName, "-c", workingDirectory, leaderCommand], { stdio: "inherit" });
|
|
2422
|
-
for (const member of fallback.plan.members) {
|
|
2423
|
-
const rolePrompt = [
|
|
2424
|
-
`LongTable team discussion role: ${member.label} (${member.role}).`,
|
|
2425
|
-
"Give claims, objections, open questions, and evidence needs. Address likely disagreement with other roles.",
|
|
2426
|
-
"",
|
|
2427
|
-
prompt
|
|
2428
|
-
].join("\n");
|
|
2429
|
-
const logPath = join(teamDir, `${member.role}.log`);
|
|
2430
|
-
const command = [
|
|
2431
|
-
`node ${shellEscape(launcher)} review --role ${shellEscape(member.role)} --prompt ${shellEscape(rolePrompt)} --cwd ${shellEscape(workingDirectory)} 2>&1 | tee ${shellEscape(logPath)}`,
|
|
2432
|
-
`echo ${shellEscape(`Role log written to ${logPath}`)}`,
|
|
2433
|
-
`exec ${shellEscape(shell)}`
|
|
2434
|
-
].join("; ");
|
|
2435
|
-
execFileSync("tmux", ["split-window", "-t", sessionName, "-c", workingDirectory, command], { stdio: "inherit" });
|
|
2436
|
-
execFileSync("tmux", ["select-layout", "-t", sessionName, "tiled"], { stdio: "ignore" });
|
|
2437
|
-
}
|
|
2438
|
-
console.log(`LongTable tmux team launched: ${sessionName}`);
|
|
2439
|
-
console.log(`Attach with: tmux attach -t ${sessionName}`);
|
|
2440
|
-
console.log(`Logs: ${teamDir}`);
|
|
2439
|
+
console.log(renderTeamDebateSummary(team.run));
|
|
2440
|
+
console.log(`- checkpoint: ${team.questionRecord.id}`);
|
|
2441
2441
|
}
|
|
2442
2442
|
async function runDecide(args) {
|
|
2443
2443
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
package/dist/debate.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ export interface BuildTeamDebateOptions {
|
|
|
7
7
|
provider?: ProviderKind;
|
|
8
8
|
visibility?: PanelVisibility;
|
|
9
9
|
roundCount?: number;
|
|
10
|
-
tmux?: boolean;
|
|
11
10
|
}
|
|
12
11
|
export interface TeamDebateBundle {
|
|
13
12
|
plan: PanelPlan;
|
|
@@ -17,5 +16,6 @@ export interface TeamDebateBundle {
|
|
|
17
16
|
questionRecord: QuestionRecord;
|
|
18
17
|
}
|
|
19
18
|
export declare function createTeamDebateQuestionRecord(run: TeamDebateRun, provider?: ProviderKind): QuestionRecord;
|
|
19
|
+
export declare function buildTeamReview(options: BuildTeamDebateOptions): TeamDebateBundle;
|
|
20
20
|
export declare function buildTeamDebate(options: BuildTeamDebateOptions): TeamDebateBundle;
|
|
21
21
|
export declare function renderTeamDebateSummary(run: TeamDebateRun): string;
|
package/dist/debate.js
CHANGED
|
@@ -68,15 +68,18 @@ function independentContribution(roundId, plan, role, label, artifactPath) {
|
|
|
68
68
|
checkpointTriggers: tags.map((tag) => `${tag}_commitment`)
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
|
-
function crossReviewContribution(roundId, plan, role, label, targetRole, targetLabel, artifactPath) {
|
|
71
|
+
function crossReviewContribution(roundId, plan, role, label, targetRole, targetLabel, targetContribution, artifactPath) {
|
|
72
72
|
return contribution({
|
|
73
73
|
roundId,
|
|
74
74
|
role,
|
|
75
75
|
label,
|
|
76
76
|
targetRole,
|
|
77
|
+
respondsToContributionId: targetContribution.id,
|
|
78
|
+
stance: "conditional",
|
|
77
79
|
artifactPath,
|
|
78
|
-
summary: `${label}
|
|
80
|
+
summary: `${label} cross-reviews ${targetLabel}'s independent contribution (${targetContribution.id}) before synthesis.`,
|
|
79
81
|
claims: [
|
|
82
|
+
`Responds to ${targetLabel}'s claim: ${targetContribution.claims[0]}`,
|
|
80
83
|
`${targetLabel}'s concern is useful only if it does not erase ${label}'s domain-specific risk.`,
|
|
81
84
|
"The debate should expose disagreement as a researcher decision point rather than normalize it away."
|
|
82
85
|
],
|
|
@@ -160,12 +163,13 @@ function convergenceContribution(roundId, plan, role, label, artifactPath) {
|
|
|
160
163
|
checkpointTriggers: ["panel_next_decision"]
|
|
161
164
|
});
|
|
162
165
|
}
|
|
163
|
-
function buildSynthesis(plan, artifactPath) {
|
|
166
|
+
function buildSynthesis(plan, artifactPath, kind) {
|
|
164
167
|
const labels = plan.members.map((member) => member.label);
|
|
165
168
|
const highSensitivity = plan.checkpointSensitivity === "high";
|
|
169
|
+
const runLabel = kind === "debate" ? "debate" : "team review";
|
|
166
170
|
return {
|
|
167
171
|
artifactPath,
|
|
168
|
-
summary: `The
|
|
172
|
+
summary: `The ${runLabel} completed across ${labels.join(", ")}. It should slow closure by turning role disagreement into an explicit researcher decision.`,
|
|
169
173
|
consensus: [
|
|
170
174
|
"The researcher should see role disagreement before LongTable drafts, commits, or submits a conclusion.",
|
|
171
175
|
"Evidence gaps and tacit assumptions should remain visible instead of being smoothed into fluent prose."
|
|
@@ -183,12 +187,13 @@ function buildSynthesis(plan, artifactPath) {
|
|
|
183
187
|
"Choose whether the debate should affect the current artifact, the research design, or only the decision log."
|
|
184
188
|
],
|
|
185
189
|
recommendedCheckpoint: highSensitivity
|
|
186
|
-
?
|
|
187
|
-
:
|
|
190
|
+
? `The ${runLabel} surfaced high-sensitivity disagreement. What should LongTable treat as the next human decision before closure?`
|
|
191
|
+
: `The ${runLabel} surfaced role disagreement. Should LongTable revise, verify evidence, proceed, or keep the tension open?`
|
|
188
192
|
};
|
|
189
193
|
}
|
|
190
194
|
export function createTeamDebateQuestionRecord(run, provider) {
|
|
191
195
|
const createdAt = nowIso();
|
|
196
|
+
const isDebate = run.interactionDepth === "debated";
|
|
192
197
|
return {
|
|
193
198
|
id: createId("question_record"),
|
|
194
199
|
createdAt,
|
|
@@ -197,7 +202,7 @@ export function createTeamDebateQuestionRecord(run, provider) {
|
|
|
197
202
|
prompt: {
|
|
198
203
|
id: createId("question_prompt"),
|
|
199
204
|
checkpointKey: "team_debate_next_decision",
|
|
200
|
-
title: "Team debate follow-up decision",
|
|
205
|
+
title: isDebate ? "Team debate follow-up decision" : "Agent team follow-up decision",
|
|
201
206
|
question: run.synthesis.recommendedCheckpoint,
|
|
202
207
|
type: "single_choice",
|
|
203
208
|
options: [
|
|
@@ -229,10 +234,15 @@ export function createTeamDebateQuestionRecord(run, provider) {
|
|
|
229
234
|
return key ? getPersonaDefinition(key).checkpointSensitivity === "high" : false;
|
|
230
235
|
}),
|
|
231
236
|
source: "runtime_guidance",
|
|
237
|
+
displayReason: isDebate
|
|
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.",
|
|
232
240
|
rationale: [
|
|
233
|
-
"
|
|
234
|
-
|
|
235
|
-
|
|
241
|
+
"Agent team orchestration is a research harness surface, not a substitute for researcher judgment.",
|
|
242
|
+
isDebate
|
|
243
|
+
? "The fixed debate rounds created disagreement that should connect to an explicit researcher decision."
|
|
244
|
+
: "The cross-review round created disagreement that should connect to an explicit researcher decision.",
|
|
245
|
+
`Agent team run: ${run.id}.`
|
|
236
246
|
],
|
|
237
247
|
preferredSurfaces: provider === "claude"
|
|
238
248
|
? ["native_structured", "numbered"]
|
|
@@ -240,10 +250,13 @@ export function createTeamDebateQuestionRecord(run, provider) {
|
|
|
240
250
|
}
|
|
241
251
|
};
|
|
242
252
|
}
|
|
243
|
-
|
|
244
|
-
const roundCount = options.roundCount ?? 5;
|
|
245
|
-
|
|
246
|
-
|
|
253
|
+
function buildTeamBundle(options, kind) {
|
|
254
|
+
const roundCount = options.roundCount ?? (kind === "debate" ? 5 : 3);
|
|
255
|
+
const expectedRounds = kind === "debate" ? 5 : 3;
|
|
256
|
+
if (roundCount !== expectedRounds) {
|
|
257
|
+
throw new Error(kind === "debate"
|
|
258
|
+
? "LongTable debate v1 supports fixed 5-round debate only."
|
|
259
|
+
: "LongTable team v1 supports fixed 3-round cross-review only.");
|
|
247
260
|
}
|
|
248
261
|
const createdAt = nowIso();
|
|
249
262
|
const plan = buildPanelPlan({
|
|
@@ -255,6 +268,7 @@ export function buildTeamDebate(options) {
|
|
|
255
268
|
});
|
|
256
269
|
const rounds = [];
|
|
257
270
|
const round1Id = createId("team_round");
|
|
271
|
+
const independentContributions = plan.members.map((member) => independentContribution(round1Id, plan, member.role, member.label, join("round-1-independent", `${member.role}.json`)));
|
|
258
272
|
rounds.push({
|
|
259
273
|
id: round1Id,
|
|
260
274
|
index: 1,
|
|
@@ -262,12 +276,12 @@ export function buildTeamDebate(options) {
|
|
|
262
276
|
title: "Independent review",
|
|
263
277
|
status: "completed",
|
|
264
278
|
artifactDir: join(options.teamDir, "round-1-independent"),
|
|
265
|
-
contributions:
|
|
279
|
+
contributions: independentContributions
|
|
266
280
|
});
|
|
267
281
|
const round2Id = createId("team_round");
|
|
268
282
|
const crossContributions = plan.members.flatMap((member) => plan.members
|
|
269
283
|
.filter((target) => target.role !== member.role)
|
|
270
|
-
.map((target) => crossReviewContribution(round2Id, plan, member.role, member.label, target.role, target.label, join("round-2-cross-review", `${member.role}-on-${target.role}.json`))));
|
|
284
|
+
.map((target) => crossReviewContribution(round2Id, plan, member.role, member.label, target.role, target.label, independentContributions.find((contribution) => contribution.role === target.role), join("round-2-cross-review", `${member.role}-on-${target.role}.json`))));
|
|
271
285
|
rounds.push({
|
|
272
286
|
id: round2Id,
|
|
273
287
|
index: 2,
|
|
@@ -277,27 +291,29 @@ export function buildTeamDebate(options) {
|
|
|
277
291
|
artifactDir: join(options.teamDir, "round-2-cross-review"),
|
|
278
292
|
contributions: crossContributions
|
|
279
293
|
});
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
294
|
+
if (kind === "debate") {
|
|
295
|
+
const round3Id = createId("team_round");
|
|
296
|
+
rounds.push({
|
|
297
|
+
id: round3Id,
|
|
298
|
+
index: 3,
|
|
299
|
+
kind: "rebuttal",
|
|
300
|
+
title: "Rebuttal and revision",
|
|
301
|
+
status: "completed",
|
|
302
|
+
artifactDir: join(options.teamDir, "round-3-rebuttal"),
|
|
303
|
+
contributions: plan.members.map((member) => rebuttalContribution(round3Id, member.role, member.label, join("round-3-rebuttal", `${member.role}.json`)))
|
|
304
|
+
});
|
|
305
|
+
const round4Id = createId("team_round");
|
|
306
|
+
rounds.push({
|
|
307
|
+
id: round4Id,
|
|
308
|
+
index: 4,
|
|
309
|
+
kind: "convergence",
|
|
310
|
+
title: "Convergence and unresolved gaps",
|
|
311
|
+
status: "completed",
|
|
312
|
+
artifactDir: join(options.teamDir, "round-4-convergence"),
|
|
313
|
+
contributions: plan.members.map((member) => convergenceContribution(round4Id, plan, member.role, member.label, join("round-4-convergence", `${member.role}.json`)))
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const synthesis = buildSynthesis(plan, "synthesis.json", kind);
|
|
301
317
|
const run = {
|
|
302
318
|
id: createId("team_debate_run"),
|
|
303
319
|
teamId: options.teamId,
|
|
@@ -306,15 +322,16 @@ export function buildTeamDebate(options) {
|
|
|
306
322
|
prompt: options.prompt,
|
|
307
323
|
roles: plan.members,
|
|
308
324
|
status: "completed",
|
|
309
|
-
surface:
|
|
310
|
-
|
|
325
|
+
surface: "file_backed_debate",
|
|
326
|
+
interactionDepth: kind === "debate" ? "debated" : "cross_reviewed",
|
|
327
|
+
roundPolicy: kind === "debate" ? "fixed" : "team_cross_review",
|
|
311
328
|
roundCount,
|
|
312
329
|
artifactRoot: options.teamDir,
|
|
313
330
|
rounds: [
|
|
314
331
|
...rounds,
|
|
315
332
|
{
|
|
316
333
|
id: createId("team_round"),
|
|
317
|
-
index:
|
|
334
|
+
index: roundCount,
|
|
318
335
|
kind: "synthesis",
|
|
319
336
|
title: "Coordinator synthesis and checkpoint",
|
|
320
337
|
status: "completed",
|
|
@@ -335,11 +352,13 @@ export function buildTeamDebate(options) {
|
|
|
335
352
|
visibility: plan.visibility,
|
|
336
353
|
checkpointSensitivity: plan.checkpointSensitivity,
|
|
337
354
|
rationale: [
|
|
338
|
-
|
|
339
|
-
|
|
355
|
+
kind === "debate"
|
|
356
|
+
? "Autonomous debate requested through LongTable team orchestration."
|
|
357
|
+
: "Agent team cross-review requested through LongTable team orchestration.",
|
|
358
|
+
"File-backed rounds keep disagreement inspectable before researcher closure."
|
|
340
359
|
]
|
|
341
360
|
});
|
|
342
|
-
intent.kind = "team_debate";
|
|
361
|
+
intent.kind = kind === "debate" ? "team_debate" : "team";
|
|
343
362
|
intent.requestedSurface = run.surface;
|
|
344
363
|
const invocationRecord = {
|
|
345
364
|
id: createId("invocation_record"),
|
|
@@ -349,11 +368,10 @@ export function buildTeamDebate(options) {
|
|
|
349
368
|
status: "completed",
|
|
350
369
|
provider: options.provider,
|
|
351
370
|
surface: run.surface,
|
|
371
|
+
interactionDepth: run.interactionDepth,
|
|
352
372
|
panelPlan: plan,
|
|
353
373
|
teamDebateRun: run,
|
|
354
|
-
degradationReason:
|
|
355
|
-
? undefined
|
|
356
|
-
: "Tmux was not requested; file-backed debate artifacts are the canonical execution record."
|
|
374
|
+
degradationReason: "File-backed team artifacts are the canonical execution record."
|
|
357
375
|
};
|
|
358
376
|
return {
|
|
359
377
|
plan,
|
|
@@ -363,12 +381,19 @@ export function buildTeamDebate(options) {
|
|
|
363
381
|
questionRecord
|
|
364
382
|
};
|
|
365
383
|
}
|
|
384
|
+
export function buildTeamReview(options) {
|
|
385
|
+
return buildTeamBundle(options, "team");
|
|
386
|
+
}
|
|
387
|
+
export function buildTeamDebate(options) {
|
|
388
|
+
return buildTeamBundle(options, "debate");
|
|
389
|
+
}
|
|
366
390
|
export function renderTeamDebateSummary(run) {
|
|
367
391
|
return [
|
|
368
392
|
"LongTable Team Debate",
|
|
369
393
|
`- team: ${run.teamId}`,
|
|
370
394
|
`- surface: ${run.surface}`,
|
|
371
|
-
`-
|
|
395
|
+
`- interaction depth: ${run.interactionDepth}`,
|
|
396
|
+
`- rounds: ${run.roundCount} ${run.roundPolicy}`,
|
|
372
397
|
`- roles: ${run.roles.map((role) => `${role.label} (${role.role})`).join(", ")}`,
|
|
373
398
|
`- artifacts: ${run.artifactRoot}`,
|
|
374
399
|
"",
|
package/dist/panel.js
CHANGED
|
@@ -143,6 +143,7 @@ export function createPlannedPanelQuestionRecord(plan, provider) {
|
|
|
143
143
|
"Panel review creates disagreement or risk visibility that should connect to an explicit researcher decision.",
|
|
144
144
|
`Panel checkpoint sensitivity: ${plan.checkpointSensitivity}.`
|
|
145
145
|
],
|
|
146
|
+
displayReason: "Panel review can surface role disagreement that should not be collapsed without researcher approval.",
|
|
146
147
|
preferredSurfaces: provider === "claude"
|
|
147
148
|
? ["native_structured", "numbered"]
|
|
148
149
|
: ["mcp_elicitation", "numbered"]
|
|
@@ -159,6 +160,7 @@ export function createPlannedPanelResult(plan, provider, linkedQuestionRecordIds
|
|
|
159
160
|
provider,
|
|
160
161
|
surface: "sequential_fallback",
|
|
161
162
|
status: "planned",
|
|
163
|
+
interactionDepth: "independent",
|
|
162
164
|
memberResults: plan.members.map((member) => ({
|
|
163
165
|
role: member.role,
|
|
164
166
|
label: member.label,
|
|
@@ -178,6 +180,7 @@ export function createPlannedInvocationRecord(options) {
|
|
|
178
180
|
status: "planned",
|
|
179
181
|
provider: options.provider,
|
|
180
182
|
surface: "sequential_fallback",
|
|
183
|
+
interactionDepth: "independent",
|
|
181
184
|
panelPlan: options.plan,
|
|
182
185
|
panelResult: options.result,
|
|
183
186
|
degradationReason: "Native provider team execution is optional; sequential fallback is the stable LongTable surface."
|
package/dist/persona-router.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface LongTableInvocationDirective {
|
|
|
12
12
|
explicit: boolean;
|
|
13
13
|
cleanedPrompt: string;
|
|
14
14
|
mode?: InteractionMode | "panel" | "status";
|
|
15
|
+
collaboration?: "panel" | "team" | "debate";
|
|
15
16
|
roles: CanonicalPersona[];
|
|
16
17
|
panel: boolean;
|
|
17
18
|
showConflicts: boolean;
|
package/dist/persona-router.js
CHANGED
|
@@ -7,7 +7,9 @@ const DIRECTIVE_MAP = [
|
|
|
7
7
|
{ key: "critique", mode: "critique" },
|
|
8
8
|
{ key: "draft", mode: "draft" },
|
|
9
9
|
{ key: "commit", mode: "commit" },
|
|
10
|
-
{ key: "panel", mode: "panel", panel: true, showConflicts: true },
|
|
10
|
+
{ key: "panel", mode: "panel", collaboration: "panel", panel: true, showConflicts: true },
|
|
11
|
+
{ key: "team", mode: "review", collaboration: "team", panel: true, showConflicts: true },
|
|
12
|
+
{ key: "debate", mode: "review", collaboration: "debate", panel: true, showConflicts: true },
|
|
11
13
|
{ key: "status", mode: "status" },
|
|
12
14
|
{ key: "editor", mode: "review", roles: ["editor"] },
|
|
13
15
|
{ key: "reviewer", mode: "review", roles: ["reviewer"] },
|
|
@@ -72,6 +74,7 @@ export function parseInvocationDirective(prompt) {
|
|
|
72
74
|
explicit: true,
|
|
73
75
|
cleanedPrompt: body || prompt,
|
|
74
76
|
mode: directive.mode,
|
|
77
|
+
collaboration: directive.collaboration,
|
|
75
78
|
roles: directive.roles ?? [],
|
|
76
79
|
panel: directive.panel === true,
|
|
77
80
|
showConflicts: directive.showConflicts === true
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DecisionRecord, InvocationRecord, ProviderKind, QuestionSurface, QuestionRecord, ResearchState } from "@longtable/core";
|
|
1
|
+
import type { DecisionRecord, InvocationRecord, ProviderKind, QuestionOption, QuestionSurface, QuestionRecord, ResearchState } from "@longtable/core";
|
|
2
2
|
import type { SetupPersistedOutput } from "@longtable/setup";
|
|
3
3
|
export type ProjectDisagreementPreference = "synthesis_only" | "show_on_conflict" | "always_visible";
|
|
4
4
|
export interface LongTableProjectRecord {
|
|
@@ -113,7 +113,7 @@ export declare function syncCurrentWorkspaceView(context: LongTableProjectContex
|
|
|
113
113
|
export declare function appendInvocationRecordToWorkspace(context: LongTableProjectContext, invocation: InvocationRecord, questions?: QuestionRecord[]): Promise<ResearchState>;
|
|
114
114
|
export declare function listBlockingWorkspaceQuestions(context: LongTableProjectContext): Promise<QuestionRecord[]>;
|
|
115
115
|
export declare function assertWorkspaceNotBlocked(context: LongTableProjectContext): Promise<void>;
|
|
116
|
-
export declare function
|
|
116
|
+
export declare function createWorkspaceFollowUpQuestions(options: {
|
|
117
117
|
context: LongTableProjectContext;
|
|
118
118
|
prompt: string;
|
|
119
119
|
provider?: ProviderKind;
|
|
@@ -130,6 +130,9 @@ export declare function createWorkspaceQuestion(options: {
|
|
|
130
130
|
prompt: string;
|
|
131
131
|
title?: string;
|
|
132
132
|
question?: string;
|
|
133
|
+
checkpointKey?: string;
|
|
134
|
+
questionOptions?: QuestionOption[];
|
|
135
|
+
displayReason?: string;
|
|
133
136
|
provider?: ProviderKind;
|
|
134
137
|
required?: boolean;
|
|
135
138
|
}): Promise<{
|
package/dist/project-session.js
CHANGED
|
@@ -647,10 +647,10 @@ function optionsForCheckpointTrigger(family, checkpointKey) {
|
|
|
647
647
|
function includesAny(prompt, patterns) {
|
|
648
648
|
return patterns.some((pattern) => pattern.test(prompt));
|
|
649
649
|
}
|
|
650
|
-
function
|
|
650
|
+
function followUpQuestionOptions(first, second, third, fourth) {
|
|
651
651
|
return [first, second, third, ...(fourth ? [fourth] : [])];
|
|
652
652
|
}
|
|
653
|
-
function
|
|
653
|
+
function buildFollowUpQuestionSpecs(prompt) {
|
|
654
654
|
const normalized = prompt.toLowerCase();
|
|
655
655
|
const specs = [];
|
|
656
656
|
function push(spec) {
|
|
@@ -664,7 +664,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
664
664
|
title: "Rubric update basis",
|
|
665
665
|
question: "How should LongTable use the available materials to update the rubric?",
|
|
666
666
|
whyNow: "Rubric updates can silently change grading criteria if LongTable guesses the calibration basis.",
|
|
667
|
-
options:
|
|
667
|
+
options: followUpQuestionOptions({ value: "calibrate_to_exemplars", label: "Calibrate criteria to exemplars", description: "Use strong submissions to refine what each criterion means.", recommended: true }, { value: "polish_existing", label: "Polish existing rubric only", description: "Keep criteria stable and improve wording or consistency." }, { value: "rewrite_structure", label: "Restructure the rubric", description: "Change categories or levels where the materials suggest a better structure." })
|
|
668
668
|
});
|
|
669
669
|
}
|
|
670
670
|
if (includesAny(normalized, [/\bexemplar\b/, /\bbest submission\b/, /\bselected submission\b/, /\bTA\b/i, /우수\s*답안|예시|선정|조교/])) {
|
|
@@ -673,7 +673,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
673
673
|
title: "Exemplar use",
|
|
674
674
|
question: "How should LongTable use selected exemplars or TA guidance?",
|
|
675
675
|
whyNow: "Exemplars can either calibrate criteria privately or become visible evidence inside the output.",
|
|
676
|
-
options:
|
|
676
|
+
options: followUpQuestionOptions({ value: "calibrate_only", label: "Use as private calibration", description: "Adjust criteria using exemplars without quoting them.", recommended: true }, { value: "include_deidentified_excerpts", label: "Include de-identified excerpts", description: "Add short anonymized examples where they clarify quality." }, { value: "separate_notes", label: "Keep examples in separate notes", description: "Use exemplars outside the main artifact." })
|
|
677
677
|
});
|
|
678
678
|
}
|
|
679
679
|
if (includesAny(normalized, [/\binstruction/, /\bguidance\b/, /\bsource\b/, /\bfile\b/, /\bdocx?\b/, /지침|가이드|문서|파일|자료/])) {
|
|
@@ -682,7 +682,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
682
682
|
title: "Source authority",
|
|
683
683
|
question: "If sources conflict or leave gaps, which source should LongTable privilege?",
|
|
684
684
|
whyNow: "Without an authority rule, LongTable may resolve conflicts by convenience rather than researcher intent.",
|
|
685
|
-
options:
|
|
685
|
+
options: followUpQuestionOptions({ value: "explicit_user_instruction", label: "Your explicit instruction", description: "Use the researcher's current instruction as the highest authority.", recommended: true }, { value: "project_files", label: "Project files", description: "Treat supplied files or existing artifacts as authoritative." }, { value: "external_guidance", label: "TA or external guidance", description: "Prioritize instructor, TA, venue, or policy guidance." })
|
|
686
686
|
});
|
|
687
687
|
}
|
|
688
688
|
if (includesAny(normalized, [/\bdeliver\b/, /\boutput\b/, /\btracked?[- ]?change/, /\bdocx?\b/, /\bmarkdown\b/, /\btable\b/, /전달|산출물|결과물|수정\s*표시|트랙|형식|포맷/])) {
|
|
@@ -691,7 +691,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
691
691
|
title: "Delivery format",
|
|
692
692
|
question: "How should LongTable deliver the clarified output?",
|
|
693
693
|
whyNow: "Format and change-tracking choices affect whether the result is usable for review or handoff.",
|
|
694
|
-
options:
|
|
694
|
+
options: followUpQuestionOptions({ value: "tracked_changes", label: "Tracked-change artifact", description: "Produce a reviewable changed version where possible.", recommended: true }, { value: "clean_final", label: "Clean final artifact", description: "Deliver the final version without change markup." }, { value: "summary_plus_artifact", label: "Summary plus artifact", description: "Include a concise change summary with the output." })
|
|
695
695
|
});
|
|
696
696
|
}
|
|
697
697
|
if (includesAny(normalized, [/\bupdate\b/, /\bchange\b/, /\bedit\b/, /\bfix\b/, /\bimplement\b/, /\bbuild\b/, /\bcreate\b/, /업데이트|수정|변경|구현|만들|고쳐/])) {
|
|
@@ -700,7 +700,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
700
700
|
title: "Autonomy boundary",
|
|
701
701
|
question: "How much should LongTable do before checking back with you?",
|
|
702
702
|
whyNow: "Execution requests can move from advice to authorship or artifact ownership unless the boundary is explicit.",
|
|
703
|
-
options:
|
|
703
|
+
options: followUpQuestionOptions({ value: "ask_then_act", label: "Clarify first, then act", description: "Ask needed questions before changing the artifact.", recommended: true }, { value: "act_with_defaults", label: "Act with visible defaults", description: "Proceed using recommended defaults and record them." }, { value: "recommend_only", label: "Recommend only", description: "Describe changes but do not alter artifacts." })
|
|
704
704
|
});
|
|
705
705
|
}
|
|
706
706
|
if (includesAny(normalized, [/\bperformance\b/, /\btest\b/, /\bevaluate\b/, /\bcheck\b/, /\bbenchmark\b/, /성능|테스트|평가|체크|검증/])) {
|
|
@@ -709,7 +709,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
709
709
|
title: "Evaluation target",
|
|
710
710
|
question: "What should LongTable treat as the main performance target?",
|
|
711
711
|
whyNow: "Performance checks can optimize for UX, correctness, trigger sensitivity, or delivery reliability.",
|
|
712
|
-
options:
|
|
712
|
+
options: followUpQuestionOptions({ value: "question_sensitivity", label: "Question sensitivity", description: "Check whether LongTable asks at the right knowledge-gap moments.", recommended: true }, { value: "renderer_convenience", label: "Renderer convenience", description: "Check whether the most convenient question UI is used." }, { value: "state_reliability", label: "State reliability", description: "Check whether questions and answers persist correctly." })
|
|
713
713
|
});
|
|
714
714
|
}
|
|
715
715
|
if (specs.length === 0) {
|
|
@@ -718,19 +718,19 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
718
718
|
title: "Missing context",
|
|
719
719
|
question: "What should LongTable clarify before proceeding?",
|
|
720
720
|
whyNow: "The request can be answered in multiple ways, and choosing silently would hide a researcher judgment.",
|
|
721
|
-
options:
|
|
721
|
+
options: followUpQuestionOptions({ value: "scope", label: "Clarify scope first", description: "Ask what is included and excluded before acting.", recommended: true }, { value: "criteria", label: "Clarify success criteria", description: "Ask what would count as a good result." }, { value: "proceed", label: "Proceed with visible assumptions", description: "Continue, but make assumptions explicit." })
|
|
722
722
|
});
|
|
723
723
|
}
|
|
724
724
|
return specs;
|
|
725
725
|
}
|
|
726
|
-
const
|
|
727
|
-
function
|
|
728
|
-
return record.prompt.rationale.includes(`${
|
|
726
|
+
const FOLLOW_UP_PROMPT_PREFIX = "Follow-up prompt:";
|
|
727
|
+
function hasFollowUpPrompt(record, prompt) {
|
|
728
|
+
return record.prompt.rationale.includes(`${FOLLOW_UP_PROMPT_PREFIX} ${prompt}`);
|
|
729
729
|
}
|
|
730
|
-
export async function
|
|
730
|
+
export async function createWorkspaceFollowUpQuestions(options) {
|
|
731
731
|
const state = await loadResearchState(options.context.stateFilePath);
|
|
732
732
|
if (!options.force) {
|
|
733
|
-
const existing = (state.questionLog ?? []).filter((record) =>
|
|
733
|
+
const existing = (state.questionLog ?? []).filter((record) => hasFollowUpPrompt(record, options.prompt));
|
|
734
734
|
const pending = existing.filter((record) => record.status === "pending");
|
|
735
735
|
if (pending.length > 0) {
|
|
736
736
|
return { questions: pending, state, created: false, alreadyAnswered: false };
|
|
@@ -743,14 +743,14 @@ export async function createWorkspaceClarificationCard(options) {
|
|
|
743
743
|
const preferredSurfaces = options.provider === "claude"
|
|
744
744
|
? ["native_structured", "terminal_selector", "numbered"]
|
|
745
745
|
: ["mcp_elicitation", "terminal_selector", "numbered"];
|
|
746
|
-
const questions =
|
|
746
|
+
const questions = buildFollowUpQuestionSpecs(options.prompt).map((spec) => ({
|
|
747
747
|
id: createId("question_record"),
|
|
748
748
|
createdAt,
|
|
749
749
|
updatedAt: createdAt,
|
|
750
750
|
status: "pending",
|
|
751
751
|
prompt: {
|
|
752
752
|
id: createId("question_prompt"),
|
|
753
|
-
checkpointKey: `
|
|
753
|
+
checkpointKey: `follow_up_${spec.key}`,
|
|
754
754
|
title: spec.title,
|
|
755
755
|
question: spec.question,
|
|
756
756
|
type: "single_choice",
|
|
@@ -761,7 +761,7 @@ export async function createWorkspaceClarificationCard(options) {
|
|
|
761
761
|
source: "runtime_guidance",
|
|
762
762
|
rationale: [
|
|
763
763
|
spec.whyNow,
|
|
764
|
-
`${
|
|
764
|
+
`${FOLLOW_UP_PROMPT_PREFIX} ${options.prompt}`
|
|
765
765
|
],
|
|
766
766
|
preferredSurfaces: preferredSurfaces
|
|
767
767
|
}
|
|
@@ -777,6 +777,7 @@ export async function createWorkspaceQuestion(options) {
|
|
|
777
777
|
unresolvedTensions: state.openTensions ?? [],
|
|
778
778
|
studyContract: state.studyContract
|
|
779
779
|
});
|
|
780
|
+
const checkpointKey = options.checkpointKey ?? trigger.signal.checkpointKey;
|
|
780
781
|
const createdAt = nowIso();
|
|
781
782
|
const question = {
|
|
782
783
|
id: createId("question_record"),
|
|
@@ -785,15 +786,16 @@ export async function createWorkspaceQuestion(options) {
|
|
|
785
786
|
status: "pending",
|
|
786
787
|
prompt: {
|
|
787
788
|
id: createId("question_prompt"),
|
|
788
|
-
checkpointKey
|
|
789
|
-
title: options.title ?? questionTitleForCheckpoint(trigger.family,
|
|
790
|
-
question: options.question ?? questionTextForCheckpoint(trigger.family, options.prompt,
|
|
789
|
+
checkpointKey,
|
|
790
|
+
title: options.title ?? questionTitleForCheckpoint(trigger.family, checkpointKey),
|
|
791
|
+
question: options.question ?? questionTextForCheckpoint(trigger.family, options.prompt, checkpointKey),
|
|
791
792
|
type: "single_choice",
|
|
792
|
-
options: optionsForCheckpointTrigger(trigger.family,
|
|
793
|
+
options: options.questionOptions ?? optionsForCheckpointTrigger(trigger.family, checkpointKey),
|
|
793
794
|
allowOther: true,
|
|
794
795
|
otherLabel: "Other decision",
|
|
795
796
|
required: options.required ?? trigger.requiresQuestionBeforeClosure,
|
|
796
797
|
source: "checkpoint",
|
|
798
|
+
displayReason: options.displayReason ?? trigger.rationale[0],
|
|
797
799
|
rationale: [
|
|
798
800
|
...trigger.rationale,
|
|
799
801
|
`Trigger family: ${trigger.family}.`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Researcher-facing LongTable CLI",
|
|
6
6
|
"type": "module",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@longtable/checkpoints": "0.1.
|
|
32
|
-
"@longtable/core": "0.1.
|
|
33
|
-
"@longtable/memory": "0.1.
|
|
34
|
-
"@longtable/provider-claude": "0.1.
|
|
35
|
-
"@longtable/provider-codex": "0.1.
|
|
36
|
-
"@longtable/setup": "0.1.
|
|
31
|
+
"@longtable/checkpoints": "0.1.26",
|
|
32
|
+
"@longtable/core": "0.1.26",
|
|
33
|
+
"@longtable/memory": "0.1.26",
|
|
34
|
+
"@longtable/provider-claude": "0.1.26",
|
|
35
|
+
"@longtable/provider-codex": "0.1.26",
|
|
36
|
+
"@longtable/setup": "0.1.26"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.10.1",
|