@longtable/mcp 0.1.32 → 0.1.34
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 +1 -1
- package/dist/first-research-shape.d.ts +25 -0
- package/dist/first-research-shape.js +111 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server.js +190 -33
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { QuestionOption } from "@longtable/core";
|
|
2
|
+
export interface FirstResearchShape {
|
|
3
|
+
handle: string;
|
|
4
|
+
currentGoal: string;
|
|
5
|
+
currentBlocker?: string;
|
|
6
|
+
researchObject?: string;
|
|
7
|
+
gapRisk?: string;
|
|
8
|
+
protectedDecision?: string;
|
|
9
|
+
openQuestions: string[];
|
|
10
|
+
nextAction: string;
|
|
11
|
+
confidence: "low" | "medium" | "high";
|
|
12
|
+
sourceHookId?: string;
|
|
13
|
+
confirmedAt?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface FirstResearchShapeQuestionSpec {
|
|
16
|
+
prompt: string;
|
|
17
|
+
title: string;
|
|
18
|
+
question: string;
|
|
19
|
+
checkpointKey: string;
|
|
20
|
+
options: QuestionOption[];
|
|
21
|
+
displayReason: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function buildFirstResearchShapeQuestion(shape: FirstResearchShape): FirstResearchShapeQuestionSpec;
|
|
24
|
+
export declare function firstResearchShapeAnswerConfirms(answer: string): boolean;
|
|
25
|
+
export declare function firstResearchShapeAnswerStatus(answer: string): "confirmed" | "active" | "deferred";
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
function questionTitle(shape) {
|
|
2
|
+
if (shape.protectedDecision) {
|
|
3
|
+
return "Protected Research Decision";
|
|
4
|
+
}
|
|
5
|
+
if (shape.currentBlocker) {
|
|
6
|
+
return "Main Unresolved Issue";
|
|
7
|
+
}
|
|
8
|
+
return "Emerging Research Direction";
|
|
9
|
+
}
|
|
10
|
+
function questionText(shape) {
|
|
11
|
+
if (shape.protectedDecision) {
|
|
12
|
+
return "Before LongTable moves forward, what protected research decision should stay explicit?";
|
|
13
|
+
}
|
|
14
|
+
if (shape.currentBlocker) {
|
|
15
|
+
return "What should LongTable do with the main unresolved issue in this emerging study?";
|
|
16
|
+
}
|
|
17
|
+
if (shape.openQuestions.length > 0) {
|
|
18
|
+
return "What should LongTable keep explicit as the next research move?";
|
|
19
|
+
}
|
|
20
|
+
return "How should LongTable carry this emerging study forward?";
|
|
21
|
+
}
|
|
22
|
+
function displayReason(shape) {
|
|
23
|
+
if (shape.protectedDecision) {
|
|
24
|
+
return `The interview surfaced a protected decision: ${shape.protectedDecision}. LongTable should not let it settle silently.`;
|
|
25
|
+
}
|
|
26
|
+
if (shape.currentBlocker) {
|
|
27
|
+
return `The main blocker is still open: ${shape.currentBlocker}. The next move should make that uncertainty explicit.`;
|
|
28
|
+
}
|
|
29
|
+
if (shape.openQuestions.length > 0) {
|
|
30
|
+
return `The interview surfaced open research questions. Choose the next move that keeps the most important one visible.`;
|
|
31
|
+
}
|
|
32
|
+
return "LongTable has enough context to propose a provisional research direction, but the next research move should still be explicit.";
|
|
33
|
+
}
|
|
34
|
+
function recommendedValue(shape) {
|
|
35
|
+
if (shape.protectedDecision) {
|
|
36
|
+
return "protect_decision";
|
|
37
|
+
}
|
|
38
|
+
if (shape.confidence === "high" && !shape.currentBlocker) {
|
|
39
|
+
return "stabilize_shape";
|
|
40
|
+
}
|
|
41
|
+
if (shape.currentBlocker || shape.confidence === "low") {
|
|
42
|
+
return "gather_context";
|
|
43
|
+
}
|
|
44
|
+
return "stabilize_shape";
|
|
45
|
+
}
|
|
46
|
+
function baseOptions(shape) {
|
|
47
|
+
const recommended = recommendedValue(shape);
|
|
48
|
+
const options = [];
|
|
49
|
+
if (shape.protectedDecision) {
|
|
50
|
+
options.push({
|
|
51
|
+
value: "protect_decision",
|
|
52
|
+
label: "Keep the protected decision open",
|
|
53
|
+
description: `Treat this as the guarded judgment while the broader direction stays provisional: ${shape.protectedDecision}`,
|
|
54
|
+
recommended: recommended === "protect_decision"
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
options.push({
|
|
58
|
+
value: "stabilize_shape",
|
|
59
|
+
label: "Use this as the provisional direction",
|
|
60
|
+
description: "Carry this research shape forward as the current working direction.",
|
|
61
|
+
recommended: recommended === "stabilize_shape"
|
|
62
|
+
}, {
|
|
63
|
+
value: "gather_context",
|
|
64
|
+
label: "Gather one more concrete case",
|
|
65
|
+
description: "Ask for one more scene, source, dataset, or example before stabilizing it.",
|
|
66
|
+
recommended: recommended === "gather_context"
|
|
67
|
+
}, {
|
|
68
|
+
value: "revise_shape",
|
|
69
|
+
label: "Revise the emerging shape",
|
|
70
|
+
description: "Change the handle, blocker, or next action before LongTable treats it as usable."
|
|
71
|
+
});
|
|
72
|
+
if (!shape.protectedDecision) {
|
|
73
|
+
options.push({
|
|
74
|
+
value: "keep_open",
|
|
75
|
+
label: "Keep the study open longer",
|
|
76
|
+
description: "Do not stabilize this direction yet."
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return options;
|
|
80
|
+
}
|
|
81
|
+
export function buildFirstResearchShapeQuestion(shape) {
|
|
82
|
+
const summaryLines = [
|
|
83
|
+
"LongTable has enough context to propose a provisional research direction.",
|
|
84
|
+
`Handle: ${shape.handle}`,
|
|
85
|
+
`Goal: ${shape.currentGoal}`,
|
|
86
|
+
shape.currentBlocker ? `Blocker: ${shape.currentBlocker}` : undefined,
|
|
87
|
+
shape.protectedDecision ? `Protected decision: ${shape.protectedDecision}` : undefined,
|
|
88
|
+
shape.openQuestions.length > 0 ? `Open question: ${shape.openQuestions[0]}` : undefined,
|
|
89
|
+
`Next action: ${shape.nextAction}`
|
|
90
|
+
].filter(Boolean);
|
|
91
|
+
return {
|
|
92
|
+
prompt: summaryLines.join("\n"),
|
|
93
|
+
title: questionTitle(shape),
|
|
94
|
+
question: questionText(shape),
|
|
95
|
+
checkpointKey: "first_research_shape_confirmation",
|
|
96
|
+
options: baseOptions(shape),
|
|
97
|
+
displayReason: displayReason(shape)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function firstResearchShapeAnswerConfirms(answer) {
|
|
101
|
+
return answer === "stabilize_shape" || answer === "protect_decision";
|
|
102
|
+
}
|
|
103
|
+
export function firstResearchShapeAnswerStatus(answer) {
|
|
104
|
+
if (firstResearchShapeAnswerConfirms(answer)) {
|
|
105
|
+
return "confirmed";
|
|
106
|
+
}
|
|
107
|
+
if (answer === "keep_open") {
|
|
108
|
+
return "deferred";
|
|
109
|
+
}
|
|
110
|
+
return "active";
|
|
111
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/server.js
CHANGED
|
@@ -10,9 +10,10 @@ import { classifyCheckpointTrigger } from "@longtable/checkpoints";
|
|
|
10
10
|
import { renderQuestionRecordInput } from "@longtable/provider-claude";
|
|
11
11
|
import { renderQuestionRecordPrompt } from "@longtable/provider-codex";
|
|
12
12
|
import { loadSetupOutput } from "@longtable/setup";
|
|
13
|
-
import { answerWorkspaceQuestion, createOrUpdateProjectWorkspace, createWorkspaceQuestion, inspectProjectWorkspace, loadProjectContextFromDirectory, loadWorkspaceState, syncCurrentWorkspaceView } from "@longtable/cli";
|
|
13
|
+
import { answerWorkspaceQuestion, clearWorkspaceQuestion, createOrUpdateProjectWorkspace, createWorkspaceQuestion, inspectProjectWorkspace, loadProjectContextFromDirectory, loadWorkspaceState, syncCurrentWorkspaceView } from "@longtable/cli";
|
|
14
|
+
import { buildFirstResearchShapeQuestion, firstResearchShapeAnswerConfirms, firstResearchShapeAnswerStatus } from "./first-research-shape.js";
|
|
14
15
|
const SERVER_NAME = "longtable-state";
|
|
15
|
-
const SERVER_VERSION = "0.1.
|
|
16
|
+
const SERVER_VERSION = "0.1.34";
|
|
16
17
|
const TOOL_NAMES = [
|
|
17
18
|
"read_project",
|
|
18
19
|
"read_session",
|
|
@@ -48,7 +49,9 @@ const firstResearchShapeSchema = z.object({
|
|
|
48
49
|
protectedDecision: z.string().optional(),
|
|
49
50
|
openQuestions: z.array(z.string().min(1)).default([]),
|
|
50
51
|
nextAction: z.string().min(1),
|
|
51
|
-
confidence: z.enum(["low", "medium", "high"]).default("medium")
|
|
52
|
+
confidence: z.enum(["low", "medium", "high"]).default("medium"),
|
|
53
|
+
sourceHookId: z.string().optional(),
|
|
54
|
+
confirmedAt: z.string().optional()
|
|
52
55
|
});
|
|
53
56
|
function textResult(structuredContent) {
|
|
54
57
|
return {
|
|
@@ -88,10 +91,14 @@ function createId(prefix) {
|
|
|
88
91
|
function asInterviewState(state) {
|
|
89
92
|
return state;
|
|
90
93
|
}
|
|
94
|
+
function isInterviewHookRun(hook) {
|
|
95
|
+
return hook?.kind === "longtable_interview";
|
|
96
|
+
}
|
|
91
97
|
function activeInterviewHook(state, hookId) {
|
|
92
98
|
const hooks = state.hooks ?? [];
|
|
93
99
|
if (hookId) {
|
|
94
|
-
|
|
100
|
+
const hook = hooks.find((candidate) => candidate.id === hookId);
|
|
101
|
+
return isInterviewHookRun(hook) ? hook : undefined;
|
|
95
102
|
}
|
|
96
103
|
return [...hooks].reverse().find((hook) => hook.kind === "longtable_interview" &&
|
|
97
104
|
(hook.status === "pending" || hook.status === "active" || hook.status === "ready_to_confirm"));
|
|
@@ -106,6 +113,61 @@ function upsertInterviewHook(state, hook) {
|
|
|
106
113
|
hooks: nextHooks
|
|
107
114
|
};
|
|
108
115
|
}
|
|
116
|
+
function upsertQuestionObligation(state, obligation) {
|
|
117
|
+
const current = state.questionObligations ?? [];
|
|
118
|
+
const next = current.some((entry) => entry.id === obligation.id)
|
|
119
|
+
? current.map((entry) => entry.id === obligation.id ? obligation : entry)
|
|
120
|
+
: [...current, obligation];
|
|
121
|
+
return {
|
|
122
|
+
...state,
|
|
123
|
+
questionObligations: next
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function ensureFirstResearchShapeObligation(state, shape, options) {
|
|
127
|
+
const existing = (state.questionObligations ?? []).find((obligation) => obligation.kind === "first_research_shape_confirmation" &&
|
|
128
|
+
obligation.status === "pending" &&
|
|
129
|
+
obligation.sourceHookId === shape.sourceHookId);
|
|
130
|
+
if (!existing) {
|
|
131
|
+
return upsertQuestionObligation(state, {
|
|
132
|
+
id: createId("question_obligation"),
|
|
133
|
+
kind: "first_research_shape_confirmation",
|
|
134
|
+
status: "pending",
|
|
135
|
+
createdAt: new Date().toISOString(),
|
|
136
|
+
updatedAt: new Date().toISOString(),
|
|
137
|
+
prompt: options.prompt,
|
|
138
|
+
reason: options.reason,
|
|
139
|
+
...(shape.sourceHookId ? { sourceHookId: shape.sourceHookId } : {}),
|
|
140
|
+
...(options.questionId ? { questionId: options.questionId } : {})
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return upsertQuestionObligation(state, {
|
|
144
|
+
...existing,
|
|
145
|
+
updatedAt: new Date().toISOString(),
|
|
146
|
+
prompt: options.prompt,
|
|
147
|
+
reason: options.reason,
|
|
148
|
+
...(options.questionId ? { questionId: options.questionId } : existing.questionId ? { questionId: existing.questionId } : {}),
|
|
149
|
+
...(shape.sourceHookId ? { sourceHookId: shape.sourceHookId } : {})
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function resolveFirstResearchShapeObligation(state, options) {
|
|
153
|
+
return {
|
|
154
|
+
...state,
|
|
155
|
+
questionObligations: (state.questionObligations ?? []).map((obligation) => {
|
|
156
|
+
const matches = obligation.kind === "first_research_shape_confirmation" && ((options.sourceHookId && obligation.sourceHookId === options.sourceHookId) ||
|
|
157
|
+
(options.questionId && obligation.questionId === options.questionId));
|
|
158
|
+
if (!matches || obligation.status !== "pending") {
|
|
159
|
+
return obligation;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
...obligation,
|
|
163
|
+
status: options.status ?? "satisfied",
|
|
164
|
+
updatedAt: new Date().toISOString(),
|
|
165
|
+
...(options.questionId ? { questionId: options.questionId } : obligation.questionId ? { questionId: obligation.questionId } : {}),
|
|
166
|
+
...(options.decisionId ? { decisionId: options.decisionId } : obligation.decisionId ? { decisionId: obligation.decisionId } : {})
|
|
167
|
+
};
|
|
168
|
+
})
|
|
169
|
+
};
|
|
170
|
+
}
|
|
109
171
|
function interviewDepth(turns) {
|
|
110
172
|
const usableTurns = turns.filter((turn) => turn.quality !== "thin").length;
|
|
111
173
|
if (usableTurns >= 3)
|
|
@@ -136,6 +198,19 @@ async function beginInterviewHook(context, options) {
|
|
|
136
198
|
if (existing) {
|
|
137
199
|
return { hook: existing, state };
|
|
138
200
|
}
|
|
201
|
+
const confirmedShape = state.firstResearchShape?.confirmedAt ? state.firstResearchShape : undefined;
|
|
202
|
+
if (confirmedShape) {
|
|
203
|
+
const confirmedHook = [...(state.hooks ?? [])].reverse().find((hook) => isInterviewHookRun(hook) &&
|
|
204
|
+
hook.status === "confirmed" &&
|
|
205
|
+
hook.firstResearchShape?.handle === confirmedShape.handle);
|
|
206
|
+
return {
|
|
207
|
+
hook: confirmedHook,
|
|
208
|
+
state,
|
|
209
|
+
alreadyConfirmed: true,
|
|
210
|
+
shape: confirmedShape,
|
|
211
|
+
nextQuestion: options.openingQuestion ?? confirmedShape.openQuestions[0] ?? confirmedShape.nextAction
|
|
212
|
+
};
|
|
213
|
+
}
|
|
139
214
|
const timestamp = new Date().toISOString();
|
|
140
215
|
const hook = {
|
|
141
216
|
id: createId("hook_interview"),
|
|
@@ -283,6 +358,22 @@ function renderQuestionFallback(record, provider = "codex") {
|
|
|
283
358
|
? renderQuestionRecordInput(record)
|
|
284
359
|
: renderQuestionRecordPrompt(record);
|
|
285
360
|
}
|
|
361
|
+
function compactInterviewHook(hook) {
|
|
362
|
+
if (!hook) {
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
id: hook.id,
|
|
367
|
+
kind: hook.kind,
|
|
368
|
+
status: hook.status,
|
|
369
|
+
depth: hook.depth,
|
|
370
|
+
provider: hook.provider,
|
|
371
|
+
turnCount: hook.turns?.length ?? 0,
|
|
372
|
+
firstResearchShape: hook.firstResearchShape?.handle,
|
|
373
|
+
createdAt: hook.createdAt,
|
|
374
|
+
updatedAt: hook.updatedAt
|
|
375
|
+
};
|
|
376
|
+
}
|
|
286
377
|
async function markQuestionTransport(context, questionId, status, message) {
|
|
287
378
|
const state = asInterviewState(await loadWorkspaceState(context));
|
|
288
379
|
let updatedQuestion = null;
|
|
@@ -355,31 +446,10 @@ function acceptedAnswer(result) {
|
|
|
355
446
|
answer
|
|
356
447
|
};
|
|
357
448
|
}
|
|
358
|
-
function buildFirstResearchShapeQuestion(shape) {
|
|
359
|
-
return {
|
|
360
|
-
prompt: [
|
|
361
|
-
"Confirm the LongTable First Research Shape.",
|
|
362
|
-
`Handle: ${shape.handle}`,
|
|
363
|
-
`Goal: ${shape.currentGoal}`,
|
|
364
|
-
shape.currentBlocker ? `Blocker: ${shape.currentBlocker}` : undefined,
|
|
365
|
-
`Next action: ${shape.nextAction}`
|
|
366
|
-
].filter(Boolean).join("\n"),
|
|
367
|
-
title: "First Research Shape",
|
|
368
|
-
question: "How should LongTable treat this first research handle?",
|
|
369
|
-
checkpointKey: "first_research_shape_confirmation",
|
|
370
|
-
displayReason: "This is the first structured handoff from open interview to durable project state.",
|
|
371
|
-
options: [
|
|
372
|
-
{ value: "confirm", label: "Confirm this handle", description: "Use this as the provisional research handle.", recommended: true },
|
|
373
|
-
{ value: "revise", label: "Revise the handle", description: "Keep interviewing before recording this shape." },
|
|
374
|
-
{ value: "gather_context", label: "Gather more context", description: "Ask for one more scene, case, source, or material first." },
|
|
375
|
-
{ value: "defer", label: "Keep it open", description: "Do not treat the research handle as ready yet." }
|
|
376
|
-
]
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
449
|
async function markFirstResearchShapeConfirmation(context, shape, answer, questionId, decisionId) {
|
|
380
450
|
const state = asInterviewState(await loadWorkspaceState(context));
|
|
381
451
|
const timestamp = new Date().toISOString();
|
|
382
|
-
const confirmedShape = answer
|
|
452
|
+
const confirmedShape = firstResearchShapeAnswerConfirms(answer)
|
|
383
453
|
? { ...shape, confirmedAt: timestamp }
|
|
384
454
|
: shape;
|
|
385
455
|
state.firstResearchShape = confirmedShape;
|
|
@@ -388,12 +458,12 @@ async function markFirstResearchShapeConfirmation(context, shape, answer, questi
|
|
|
388
458
|
firstResearchShape: confirmedShape
|
|
389
459
|
};
|
|
390
460
|
state.hooks = (state.hooks ?? []).map((hook) => {
|
|
391
|
-
if (hook.id !== shape.sourceHookId) {
|
|
461
|
+
if (hook.id !== shape.sourceHookId || !isInterviewHookRun(hook)) {
|
|
392
462
|
return hook;
|
|
393
463
|
}
|
|
394
464
|
return {
|
|
395
465
|
...hook,
|
|
396
|
-
status: answer
|
|
466
|
+
status: firstResearchShapeAnswerStatus(answer),
|
|
397
467
|
updatedAt: timestamp,
|
|
398
468
|
firstResearchShape: confirmedShape,
|
|
399
469
|
linkedQuestionRecordIds: questionId
|
|
@@ -404,6 +474,12 @@ async function markFirstResearchShapeConfirmation(context, shape, answer, questi
|
|
|
404
474
|
: hook.linkedDecisionRecordIds
|
|
405
475
|
};
|
|
406
476
|
});
|
|
477
|
+
const nextState = resolveFirstResearchShapeObligation(state, {
|
|
478
|
+
sourceHookId: shape.sourceHookId,
|
|
479
|
+
questionId,
|
|
480
|
+
decisionId,
|
|
481
|
+
status: "satisfied"
|
|
482
|
+
});
|
|
407
483
|
const session = {
|
|
408
484
|
...context.session,
|
|
409
485
|
firstResearchShape: confirmedShape,
|
|
@@ -411,9 +487,49 @@ async function markFirstResearchShapeConfirmation(context, shape, answer, questi
|
|
|
411
487
|
};
|
|
412
488
|
context.session = session;
|
|
413
489
|
await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
|
|
414
|
-
await writeFile(context.stateFilePath, JSON.stringify(
|
|
490
|
+
await writeFile(context.stateFilePath, JSON.stringify(nextState, null, 2), "utf8");
|
|
415
491
|
await syncCurrentWorkspaceView(context);
|
|
416
|
-
return { state, session, shape: confirmedShape };
|
|
492
|
+
return { state: nextState, session, shape: confirmedShape };
|
|
493
|
+
}
|
|
494
|
+
async function markAlreadyConfirmedFirstResearchShape(context, shape) {
|
|
495
|
+
const state = asInterviewState(await loadWorkspaceState(context));
|
|
496
|
+
const timestamp = new Date().toISOString();
|
|
497
|
+
const confirmedShape = shape.confirmedAt ? shape : { ...shape, confirmedAt: timestamp };
|
|
498
|
+
state.firstResearchShape = confirmedShape;
|
|
499
|
+
state.workingState = {
|
|
500
|
+
...state.workingState,
|
|
501
|
+
firstResearchShape: confirmedShape
|
|
502
|
+
};
|
|
503
|
+
state.hooks = (state.hooks ?? []).map((hook) => {
|
|
504
|
+
if (!isInterviewHookRun(hook)) {
|
|
505
|
+
return hook;
|
|
506
|
+
}
|
|
507
|
+
const matchesSource = shape.sourceHookId && hook.id === shape.sourceHookId;
|
|
508
|
+
const matchesHandle = !shape.sourceHookId && hook.firstResearchShape?.handle === shape.handle;
|
|
509
|
+
if (!matchesSource && !matchesHandle) {
|
|
510
|
+
return hook;
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
...hook,
|
|
514
|
+
status: "confirmed",
|
|
515
|
+
updatedAt: timestamp,
|
|
516
|
+
firstResearchShape: confirmedShape
|
|
517
|
+
};
|
|
518
|
+
});
|
|
519
|
+
const nextState = resolveFirstResearchShapeObligation(state, {
|
|
520
|
+
sourceHookId: confirmedShape.sourceHookId,
|
|
521
|
+
status: "satisfied"
|
|
522
|
+
});
|
|
523
|
+
const session = {
|
|
524
|
+
...context.session,
|
|
525
|
+
firstResearchShape: confirmedShape,
|
|
526
|
+
lastUpdatedAt: timestamp
|
|
527
|
+
};
|
|
528
|
+
context.session = session;
|
|
529
|
+
await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
|
|
530
|
+
await writeFile(context.stateFilePath, JSON.stringify(nextState, null, 2), "utf8");
|
|
531
|
+
await syncCurrentWorkspaceView(context);
|
|
532
|
+
return { state: nextState, session, shape: confirmedShape };
|
|
417
533
|
}
|
|
418
534
|
function statusForElicitationError(error) {
|
|
419
535
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -565,7 +681,18 @@ export function createLongTableMcpServer() {
|
|
|
565
681
|
openingQuestion,
|
|
566
682
|
seedAnswer
|
|
567
683
|
});
|
|
568
|
-
return textResult(
|
|
684
|
+
return textResult({
|
|
685
|
+
hook: compactInterviewHook(result.hook),
|
|
686
|
+
alreadyConfirmed: "alreadyConfirmed" in result ? Boolean(result.alreadyConfirmed) : false,
|
|
687
|
+
shape: "shape" in result ? result.shape : result.hook?.firstResearchShape,
|
|
688
|
+
nextQuestion: "nextQuestion" in result ? result.nextQuestion : openingQuestion,
|
|
689
|
+
workspace: {
|
|
690
|
+
projectName: context.project.projectName,
|
|
691
|
+
currentGoal: context.session.currentGoal,
|
|
692
|
+
currentBlocker: context.session.currentBlocker,
|
|
693
|
+
nextAction: context.session.nextAction
|
|
694
|
+
}
|
|
695
|
+
});
|
|
569
696
|
}
|
|
570
697
|
catch (error) {
|
|
571
698
|
return errorResult(error instanceof Error ? error.message : String(error));
|
|
@@ -617,7 +744,16 @@ export function createLongTableMcpServer() {
|
|
|
617
744
|
hookId,
|
|
618
745
|
shape: shape
|
|
619
746
|
});
|
|
620
|
-
return textResult(
|
|
747
|
+
return textResult({
|
|
748
|
+
hook: compactInterviewHook(result.hook),
|
|
749
|
+
shape: result.shape,
|
|
750
|
+
session: {
|
|
751
|
+
currentGoal: result.session.currentGoal,
|
|
752
|
+
currentBlocker: result.session.currentBlocker,
|
|
753
|
+
nextAction: result.session.nextAction,
|
|
754
|
+
firstResearchShape: result.session.firstResearchShape
|
|
755
|
+
}
|
|
756
|
+
});
|
|
621
757
|
}
|
|
622
758
|
catch (error) {
|
|
623
759
|
return errorResult(error instanceof Error ? error.message : String(error));
|
|
@@ -639,6 +775,13 @@ export function createLongTableMcpServer() {
|
|
|
639
775
|
if (!shape) {
|
|
640
776
|
return errorResult("No First Research Shape was found to confirm. Run summarize_interview first.");
|
|
641
777
|
}
|
|
778
|
+
if (shape.confirmedAt) {
|
|
779
|
+
const confirmation = await markAlreadyConfirmedFirstResearchShape(context, shape);
|
|
780
|
+
return textResult({
|
|
781
|
+
shape: confirmation.shape,
|
|
782
|
+
elicitation: { attempted: false, reason: "already_confirmed" }
|
|
783
|
+
});
|
|
784
|
+
}
|
|
642
785
|
const spec = buildFirstResearchShapeQuestion(shape);
|
|
643
786
|
const created = await createWorkspaceQuestion({
|
|
644
787
|
context,
|
|
@@ -651,6 +794,13 @@ export function createLongTableMcpServer() {
|
|
|
651
794
|
provider: provider,
|
|
652
795
|
required: true
|
|
653
796
|
});
|
|
797
|
+
const createdState = ensureFirstResearchShapeObligation(asInterviewState(await loadWorkspaceState(context)), shape, {
|
|
798
|
+
prompt: spec.question,
|
|
799
|
+
reason: spec.displayReason,
|
|
800
|
+
questionId: created.question.id
|
|
801
|
+
});
|
|
802
|
+
await writeFile(context.stateFilePath, JSON.stringify(createdState, null, 2), "utf8");
|
|
803
|
+
await syncCurrentWorkspaceView(context);
|
|
654
804
|
const fallback = renderQuestionFallback(created.question, provider);
|
|
655
805
|
if (fallbackOnly) {
|
|
656
806
|
const marked = await markQuestionTransport(context, created.question.id, "fallback_rendered", "MCP elicitation skipped by fallbackOnly.");
|
|
@@ -670,8 +820,15 @@ export function createLongTableMcpServer() {
|
|
|
670
820
|
? "declined"
|
|
671
821
|
: "fallback_rendered";
|
|
672
822
|
const marked = await markQuestionTransport(context, created.question.id, status, `MCP elicitation returned action: ${elicited.action}.`);
|
|
823
|
+
const cleared = status === "declined"
|
|
824
|
+
? await clearWorkspaceQuestion({
|
|
825
|
+
context,
|
|
826
|
+
questionId: created.question.id,
|
|
827
|
+
reason: `MCP elicitation returned action: ${elicited.action}; confirmation was deferred without a research-direction decision.`
|
|
828
|
+
})
|
|
829
|
+
: undefined;
|
|
673
830
|
return textResult({
|
|
674
|
-
question: marked ?? created.question,
|
|
831
|
+
question: cleared?.question ?? marked ?? created.question,
|
|
675
832
|
shape,
|
|
676
833
|
elicitation: { attempted: true, action: elicited.action },
|
|
677
834
|
fallback
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "LongTable MCP transport for workspace state and Researcher Checkpoints",
|
|
6
6
|
"type": "module",
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"self-test": "node ./dist/server.js --self-test"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@longtable/checkpoints": "0.1.
|
|
30
|
-
"@longtable/cli": "0.1.
|
|
31
|
-
"@longtable/core": "0.1.
|
|
32
|
-
"@longtable/provider-claude": "0.1.
|
|
33
|
-
"@longtable/provider-codex": "0.1.
|
|
34
|
-
"@longtable/setup": "0.1.
|
|
29
|
+
"@longtable/checkpoints": "0.1.34",
|
|
30
|
+
"@longtable/cli": "0.1.34",
|
|
31
|
+
"@longtable/core": "0.1.34",
|
|
32
|
+
"@longtable/provider-claude": "0.1.34",
|
|
33
|
+
"@longtable/provider-codex": "0.1.34",
|
|
34
|
+
"@longtable/setup": "0.1.34",
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
36
36
|
"zod": "^4.0.0"
|
|
37
37
|
},
|