@longtable/cli 0.1.48 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +35 -12
- package/dist/codex-hooks.d.ts +9 -2
- package/dist/codex-hooks.js +175 -7
- package/dist/longtable-codex-native-hook.js +36 -0
- package/dist/project-session.d.ts +12 -2
- package/dist/project-session.js +133 -25
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodex
|
|
|
17
17
|
import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router.js";
|
|
18
18
|
import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
|
|
19
19
|
import { buildPanelFallback, renderPanelSummary } from "./panel.js";
|
|
20
|
-
import { LONGTABLE_MANAGED_HOOK_EVENTS, codexHooksEnabled, enableCodexHooksFeature, getMissingManagedCodexHookEvents, mergeManagedCodexHooksConfig, removeManagedCodexHooks } from "./codex-hooks.js";
|
|
20
|
+
import { LONGTABLE_MANAGED_HOOK_EVENTS, codexHooksEnabled, enableCodexHooksFeature, getMissingManagedCodexHookEvents, getMissingManagedCodexHookTrustState, mergeCodexHookTrustState, mergeManagedCodexHooksConfig, removeCodexHookTrustState, removeManagedCodexHooks } from "./codex-hooks.js";
|
|
21
21
|
import { appendInvocationRecordToWorkspace, applyResearchSpecificationPatch, assertWorkspaceNotBlocked, answerWorkspaceQuestion, buildQuestionOpportunitySpecs, clearWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, diffResearchSpecifications, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, findUnincorporatedResearchEvidence, proposeResearchSpecificationPatch, pruneWorkspaceQuestions, readResearchSpecificationHistory, repairWorkspaceStateConsistency, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
22
22
|
import { buildTeamDebate, buildTeamReview, renderTeamDebateSummary } from "./debate.js";
|
|
23
23
|
import { createPromptRenderer } from "./prompt-renderer.js";
|
|
@@ -1325,8 +1325,9 @@ async function installCodexNativeHooks(args) {
|
|
|
1325
1325
|
const packageRoot = resolveCliPackageRoot();
|
|
1326
1326
|
const existingConfig = existsSync(configPath) ? await readFile(configPath, "utf8") : "";
|
|
1327
1327
|
const existingHooks = existsSync(hooksPath) ? await readFile(hooksPath, "utf8") : "";
|
|
1328
|
-
const nextConfig = enableCodexHooksFeature(existingConfig);
|
|
1329
1328
|
const nextHooks = mergeManagedCodexHooksConfig(existingHooks, packageRoot);
|
|
1329
|
+
const configWithHooksFeature = enableCodexHooksFeature(existingConfig, "hooks");
|
|
1330
|
+
const nextConfig = mergeCodexHookTrustState(configWithHooksFeature, hooksPath, nextHooks);
|
|
1330
1331
|
await mkdir(dirname(configPath), { recursive: true });
|
|
1331
1332
|
await mkdir(dirname(hooksPath), { recursive: true });
|
|
1332
1333
|
await writeFile(configPath, nextConfig, "utf8");
|
|
@@ -1336,14 +1337,21 @@ async function installCodexNativeHooks(args) {
|
|
|
1336
1337
|
hooksPath,
|
|
1337
1338
|
codexHooksEnabled: codexHooksEnabled(nextConfig),
|
|
1338
1339
|
managedEvents: [...LONGTABLE_MANAGED_HOOK_EVENTS],
|
|
1340
|
+
managedTrustEntries: getMissingManagedCodexHookTrustState("", hooksPath, nextHooks).length,
|
|
1339
1341
|
write: true
|
|
1340
1342
|
};
|
|
1341
1343
|
}
|
|
1342
1344
|
async function removeCodexNativeHooks(args) {
|
|
1343
1345
|
const configPath = resolveCodexMcpConfigPath(args);
|
|
1344
1346
|
const hooksPath = resolveCodexHooksPath(args);
|
|
1347
|
+
const configContent = existsSync(configPath) ? await readFile(configPath, "utf8") : "";
|
|
1345
1348
|
const existingHooks = existsSync(hooksPath) ? await readFile(hooksPath, "utf8") : "";
|
|
1349
|
+
const nextConfig = existingHooks
|
|
1350
|
+
? removeCodexHookTrustState(configContent, hooksPath, existingHooks)
|
|
1351
|
+
: configContent;
|
|
1346
1352
|
const removed = existingHooks ? removeManagedCodexHooks(existingHooks) : { nextContent: null, removedCount: 0 };
|
|
1353
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
1354
|
+
await writeFile(configPath, nextConfig, "utf8");
|
|
1347
1355
|
if (removed.nextContent === null) {
|
|
1348
1356
|
await rm(hooksPath, { force: true });
|
|
1349
1357
|
}
|
|
@@ -1351,12 +1359,12 @@ async function removeCodexNativeHooks(args) {
|
|
|
1351
1359
|
await mkdir(dirname(hooksPath), { recursive: true });
|
|
1352
1360
|
await writeFile(hooksPath, removed.nextContent, "utf8");
|
|
1353
1361
|
}
|
|
1354
|
-
const configContent = existsSync(configPath) ? await readFile(configPath, "utf8") : "";
|
|
1355
1362
|
return {
|
|
1356
1363
|
configPath,
|
|
1357
1364
|
hooksPath,
|
|
1358
|
-
codexHooksEnabled: codexHooksEnabled(
|
|
1365
|
+
codexHooksEnabled: codexHooksEnabled(nextConfig),
|
|
1359
1366
|
managedEvents: removed.removedCount > 0 ? [...LONGTABLE_MANAGED_HOOK_EVENTS] : [],
|
|
1367
|
+
managedTrustEntries: 0,
|
|
1360
1368
|
write: true
|
|
1361
1369
|
};
|
|
1362
1370
|
}
|
|
@@ -1365,8 +1373,9 @@ function renderCodexHookInstallSummary(result) {
|
|
|
1365
1373
|
"LongTable Codex hooks",
|
|
1366
1374
|
`- config: ${result.configPath}`,
|
|
1367
1375
|
`- hooks: ${result.hooksPath}`,
|
|
1368
|
-
`-
|
|
1369
|
-
`- managed events: ${result.managedEvents.length > 0 ? result.managedEvents.join(", ") : "none"}
|
|
1376
|
+
`- hooks feature: ${result.codexHooksEnabled ? "enabled" : "missing"}`,
|
|
1377
|
+
`- managed events: ${result.managedEvents.length > 0 ? result.managedEvents.join(", ") : "none"}`,
|
|
1378
|
+
`- managed trust entries: ${result.managedTrustEntries}`
|
|
1370
1379
|
].join("\n");
|
|
1371
1380
|
}
|
|
1372
1381
|
function renderMcpInstallSummary(result) {
|
|
@@ -1565,6 +1574,9 @@ async function collectDoctorStatus(args) {
|
|
|
1565
1574
|
const missingManagedHookEvents = codexHooksContent
|
|
1566
1575
|
? (getMissingManagedCodexHookEvents(codexHooksContent) ?? [...LONGTABLE_MANAGED_HOOK_EVENTS])
|
|
1567
1576
|
: [...LONGTABLE_MANAGED_HOOK_EVENTS];
|
|
1577
|
+
const missingManagedHookTrustState = codexHooksContent
|
|
1578
|
+
? getMissingManagedCodexHookTrustState(codexMcpConfig, codexHooksPath, codexHooksContent)
|
|
1579
|
+
: [];
|
|
1568
1580
|
const expectedCodexSkills = buildCodexSkillSpecs(roles, skillSurface).map((skill) => skill.name);
|
|
1569
1581
|
const expectedClaudeSkills = buildClaudeSkillSpecs(roles, skillSurface).map((skill) => skill.name);
|
|
1570
1582
|
const [codexSkills, claudeSkills, codexAliases, workspace] = await Promise.all([
|
|
@@ -1601,7 +1613,8 @@ async function collectDoctorStatus(args) {
|
|
|
1601
1613
|
hooksPath: codexHooksPath,
|
|
1602
1614
|
hooksExists: existsSync(codexHooksPath),
|
|
1603
1615
|
codexHooksEnabled: codexHooksEnabled(codexMcpConfig),
|
|
1604
|
-
missingManagedHookEvents
|
|
1616
|
+
missingManagedHookEvents,
|
|
1617
|
+
missingManagedHookTrustState
|
|
1605
1618
|
},
|
|
1606
1619
|
claude: {
|
|
1607
1620
|
command: "claude",
|
|
@@ -1649,8 +1662,9 @@ function renderDoctorStatus(status) {
|
|
|
1649
1662
|
: ["- Research Specification MCP tools: complete"]),
|
|
1650
1663
|
`- MCP elicitation approval: ${status.providers.codex.mcpElicitationsAllowed ? "allowed" : "not allowed"}`,
|
|
1651
1664
|
`- Codex hooks file: ${status.providers.codex.hooksExists ? "present" : "missing"} (${status.providers.codex.hooksPath})`,
|
|
1652
|
-
`-
|
|
1665
|
+
`- hooks feature: ${status.providers.codex.codexHooksEnabled ? "enabled" : "missing"}`,
|
|
1653
1666
|
`- managed hook coverage: ${status.providers.codex.missingManagedHookEvents.length === 0 ? "complete" : `missing ${status.providers.codex.missingManagedHookEvents.join(", ")}`}`,
|
|
1667
|
+
`- managed hook trust: ${status.providers.codex.missingManagedHookTrustState.length === 0 ? "current" : `missing/stale ${status.providers.codex.missingManagedHookTrustState.length}`}`,
|
|
1654
1668
|
"",
|
|
1655
1669
|
...renderProviderDoctorBlock("Claude", status.providers.claude),
|
|
1656
1670
|
"",
|
|
@@ -1700,12 +1714,15 @@ function renderDoctorStatus(status) {
|
|
|
1700
1714
|
status.providers.codex.missingMcpTools.length > 0 ||
|
|
1701
1715
|
!status.providers.codex.codexHooksEnabled ||
|
|
1702
1716
|
status.providers.codex.missingManagedHookEvents.length > 0 ||
|
|
1717
|
+
status.providers.codex.missingManagedHookTrustState.length > 0 ||
|
|
1703
1718
|
(status.setupExists &&
|
|
1704
1719
|
(!status.providers.codex.runtimeExists || !status.providers.claude.runtimeExists));
|
|
1705
1720
|
if (canFix) {
|
|
1706
1721
|
nextActions.push("longtable doctor --fix");
|
|
1707
1722
|
}
|
|
1708
|
-
if (!status.providers.codex.codexHooksEnabled ||
|
|
1723
|
+
if (!status.providers.codex.codexHooksEnabled ||
|
|
1724
|
+
status.providers.codex.missingManagedHookEvents.length > 0 ||
|
|
1725
|
+
status.providers.codex.missingManagedHookTrustState.length > 0) {
|
|
1709
1726
|
nextActions.push("longtable codex install-hooks");
|
|
1710
1727
|
}
|
|
1711
1728
|
if (!status.providers.codex.longtableMcpConfigured ||
|
|
@@ -1832,7 +1849,9 @@ async function repairDoctorStatus(args, status) {
|
|
|
1832
1849
|
if (status.providers.codex.legacyPromptFilesInstalled.length > 0) {
|
|
1833
1850
|
repair.removedLegacyPromptFiles = await removeCodexPromptAliases(codexPromptsDir);
|
|
1834
1851
|
}
|
|
1835
|
-
if (!status.providers.codex.codexHooksEnabled ||
|
|
1852
|
+
if (!status.providers.codex.codexHooksEnabled ||
|
|
1853
|
+
status.providers.codex.missingManagedHookEvents.length > 0 ||
|
|
1854
|
+
status.providers.codex.missingManagedHookTrustState.length > 0) {
|
|
1836
1855
|
await installCodexNativeHooks(args);
|
|
1837
1856
|
repair.installedCodexHooks = true;
|
|
1838
1857
|
}
|
|
@@ -3773,7 +3792,10 @@ async function runCodexSubcommand(subcommand, args) {
|
|
|
3773
3792
|
hooksExists: existsSync(hooksPath),
|
|
3774
3793
|
missingManagedHookEvents: hooksContent
|
|
3775
3794
|
? (getMissingManagedCodexHookEvents(hooksContent) ?? [...LONGTABLE_MANAGED_HOOK_EVENTS])
|
|
3776
|
-
: [...LONGTABLE_MANAGED_HOOK_EVENTS]
|
|
3795
|
+
: [...LONGTABLE_MANAGED_HOOK_EVENTS],
|
|
3796
|
+
missingManagedHookTrustState: hooksContent
|
|
3797
|
+
? getMissingManagedCodexHookTrustState(configContent, hooksPath, hooksContent)
|
|
3798
|
+
: []
|
|
3777
3799
|
};
|
|
3778
3800
|
if (args.json === true) {
|
|
3779
3801
|
console.log(JSON.stringify(status, null, 2));
|
|
@@ -3805,9 +3827,10 @@ async function runCodexSubcommand(subcommand, args) {
|
|
|
3805
3827
|
}
|
|
3806
3828
|
}
|
|
3807
3829
|
console.log(`- codex config: ${status.codexConfigPath}`);
|
|
3808
|
-
console.log(`-
|
|
3830
|
+
console.log(`- hooks feature: ${status.codexHooksEnabled ? "enabled" : "missing"}`);
|
|
3809
3831
|
console.log(`- hooks file: ${status.hooksExists ? "present" : "missing"} (${status.hooksPath})`);
|
|
3810
3832
|
console.log(`- managed hook coverage: ${status.missingManagedHookEvents.length === 0 ? "complete" : `missing ${status.missingManagedHookEvents.join(", ")}`}`);
|
|
3833
|
+
console.log(`- managed hook trust: ${status.missingManagedHookTrustState.length === 0 ? "current" : `missing/stale ${status.missingManagedHookTrustState.length}`}`);
|
|
3811
3834
|
return;
|
|
3812
3835
|
}
|
|
3813
3836
|
throw new Error("Unknown codex subcommand.");
|
package/dist/codex-hooks.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const LONGTABLE_MANAGED_HOOK_EVENTS: readonly ["SessionStart", "PreToolUse", "PostToolUse", "UserPromptSubmit", "Stop"];
|
|
1
|
+
export declare const LONGTABLE_MANAGED_HOOK_EVENTS: readonly ["SessionStart", "PreToolUse", "PostToolUse", "UserPromptSubmit", "PreCompact", "PostCompact", "Stop"];
|
|
2
2
|
type ManagedHookEventName = (typeof LONGTABLE_MANAGED_HOOK_EVENTS)[number];
|
|
3
3
|
type JsonObject = Record<string, unknown>;
|
|
4
4
|
export interface ManagedCodexHooksConfig {
|
|
@@ -12,11 +12,18 @@ export interface RemoveManagedCodexHooksResult {
|
|
|
12
12
|
nextContent: string | null;
|
|
13
13
|
removedCount: number;
|
|
14
14
|
}
|
|
15
|
+
export interface CodexHookTrustStateEntry {
|
|
16
|
+
trusted_hash: string;
|
|
17
|
+
}
|
|
15
18
|
export declare function buildManagedCodexHooksConfig(packageRoot: string): ManagedCodexHooksConfig;
|
|
16
19
|
export declare function parseCodexHooksConfig(content: string): ParsedCodexHooksConfig | null;
|
|
17
20
|
export declare function getMissingManagedCodexHookEvents(content: string): ManagedHookEventName[] | null;
|
|
21
|
+
export declare function buildManagedCodexHookTrustState(hooksPath: string, hooksContent: string): Record<string, CodexHookTrustStateEntry>;
|
|
22
|
+
export declare function mergeCodexHookTrustState(config: string, hooksPath: string, hooksContent: string): string;
|
|
23
|
+
export declare function removeCodexHookTrustState(config: string, hooksPath: string, hooksContent: string): string;
|
|
24
|
+
export declare function getMissingManagedCodexHookTrustState(config: string, hooksPath: string, hooksContent: string): string[];
|
|
18
25
|
export declare function mergeManagedCodexHooksConfig(existingContent: string | null | undefined, packageRoot: string): string;
|
|
19
26
|
export declare function removeManagedCodexHooks(existingContent: string): RemoveManagedCodexHooksResult;
|
|
20
|
-
export declare function enableCodexHooksFeature(existing: string): string;
|
|
27
|
+
export declare function enableCodexHooksFeature(existing: string, featureFlag?: string): string;
|
|
21
28
|
export declare function codexHooksEnabled(config: string): boolean;
|
|
22
29
|
export {};
|
package/dist/codex-hooks.js
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { join } from "node:path";
|
|
2
3
|
export const LONGTABLE_MANAGED_HOOK_EVENTS = [
|
|
3
4
|
"SessionStart",
|
|
4
5
|
"PreToolUse",
|
|
5
6
|
"PostToolUse",
|
|
6
7
|
"UserPromptSubmit",
|
|
8
|
+
"PreCompact",
|
|
9
|
+
"PostCompact",
|
|
7
10
|
"Stop"
|
|
8
11
|
];
|
|
12
|
+
const CODEX_HOOK_EVENT_LABELS = {
|
|
13
|
+
SessionStart: "session_start",
|
|
14
|
+
PreToolUse: "pre_tool_use",
|
|
15
|
+
PostToolUse: "post_tool_use",
|
|
16
|
+
UserPromptSubmit: "user_prompt_submit",
|
|
17
|
+
PreCompact: "pre_compact",
|
|
18
|
+
PostCompact: "post_compact",
|
|
19
|
+
Stop: "stop"
|
|
20
|
+
};
|
|
9
21
|
function isPlainObject(value) {
|
|
10
22
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
23
|
}
|
|
@@ -51,6 +63,12 @@ export function buildManagedCodexHooksConfig(packageRoot) {
|
|
|
51
63
|
statusMessage: "Applying LongTable research context"
|
|
52
64
|
})
|
|
53
65
|
],
|
|
66
|
+
PreCompact: [
|
|
67
|
+
buildCommandHook(command)
|
|
68
|
+
],
|
|
69
|
+
PostCompact: [
|
|
70
|
+
buildCommandHook(command)
|
|
71
|
+
],
|
|
54
72
|
Stop: [
|
|
55
73
|
buildCommandHook(command, {
|
|
56
74
|
timeout: 30
|
|
@@ -126,6 +144,152 @@ function stripManagedHooksFromEntry(entry) {
|
|
|
126
144
|
function serializeCodexHooksConfig(root) {
|
|
127
145
|
return JSON.stringify(root, null, 2) + "\n";
|
|
128
146
|
}
|
|
147
|
+
function canonicalJson(value) {
|
|
148
|
+
if (Array.isArray(value)) {
|
|
149
|
+
return value.map((item) => canonicalJson(item));
|
|
150
|
+
}
|
|
151
|
+
if (isPlainObject(value)) {
|
|
152
|
+
return Object.fromEntries(Object.keys(value)
|
|
153
|
+
.sort()
|
|
154
|
+
.map((key) => [key, canonicalJson(value[key])]));
|
|
155
|
+
}
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
function versionForCodexTomlIdentity(value) {
|
|
159
|
+
const serialized = JSON.stringify(canonicalJson(value));
|
|
160
|
+
return `sha256:${createHash("sha256").update(serialized).digest("hex")}`;
|
|
161
|
+
}
|
|
162
|
+
function normalizedCommandHookIdentity(eventName, entry, hook) {
|
|
163
|
+
return {
|
|
164
|
+
event_name: CODEX_HOOK_EVENT_LABELS[eventName],
|
|
165
|
+
...(typeof entry.matcher === "string" ? { matcher: entry.matcher } : {}),
|
|
166
|
+
hooks: [
|
|
167
|
+
{
|
|
168
|
+
type: "command",
|
|
169
|
+
command: hook.command,
|
|
170
|
+
timeout: Math.max(1, typeof hook.timeout === "number" ? hook.timeout : 600),
|
|
171
|
+
async: false,
|
|
172
|
+
...(typeof hook.statusMessage === "string" ? { statusMessage: hook.statusMessage } : {})
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function managedHookStateKey(hooksPath, eventName, groupIndex, handlerIndex) {
|
|
178
|
+
return `${hooksPath}:${CODEX_HOOK_EVENT_LABELS[eventName]}:${groupIndex}:${handlerIndex}`;
|
|
179
|
+
}
|
|
180
|
+
export function buildManagedCodexHookTrustState(hooksPath, hooksContent) {
|
|
181
|
+
const parsed = parseCodexHooksConfig(hooksContent);
|
|
182
|
+
if (!parsed) {
|
|
183
|
+
return {};
|
|
184
|
+
}
|
|
185
|
+
const state = {};
|
|
186
|
+
for (const eventName of LONGTABLE_MANAGED_HOOK_EVENTS) {
|
|
187
|
+
const entries = Array.isArray(parsed.hooks[eventName]) ? parsed.hooks[eventName] : [];
|
|
188
|
+
entries.forEach((entry, groupIndex) => {
|
|
189
|
+
if (!isPlainObject(entry) || !Array.isArray(entry.hooks)) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
entry.hooks.forEach((hook, handlerIndex) => {
|
|
193
|
+
if (!isPlainObject(hook) ||
|
|
194
|
+
hook.type !== "command" ||
|
|
195
|
+
typeof hook.command !== "string" ||
|
|
196
|
+
!isLongTableManagedHookCommand(hook.command)) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
state[managedHookStateKey(hooksPath, eventName, groupIndex, handlerIndex)] = {
|
|
200
|
+
trusted_hash: versionForCodexTomlIdentity(normalizedCommandHookIdentity(eventName, entry, hook))
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return state;
|
|
206
|
+
}
|
|
207
|
+
function escapeTomlBasicString(value) {
|
|
208
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
209
|
+
}
|
|
210
|
+
function unescapeTomlBasicString(value) {
|
|
211
|
+
return value.replace(/\\(["\\])/g, "$1");
|
|
212
|
+
}
|
|
213
|
+
function readHooksStateTableKey(line) {
|
|
214
|
+
const match = line.trim().match(/^\[hooks\.state\."((?:\\.|[^"\\])*)"\]$/);
|
|
215
|
+
return match ? unescapeTomlBasicString(match[1]) : null;
|
|
216
|
+
}
|
|
217
|
+
function removeHookStateTables(config, shouldRemove) {
|
|
218
|
+
const lines = config.split(/\r?\n/);
|
|
219
|
+
const kept = [];
|
|
220
|
+
for (let index = 0; index < lines.length;) {
|
|
221
|
+
const key = readHooksStateTableKey(lines[index]);
|
|
222
|
+
if (!key || !shouldRemove(key)) {
|
|
223
|
+
kept.push(lines[index]);
|
|
224
|
+
index += 1;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
index += 1;
|
|
228
|
+
while (index < lines.length && !/^\s*\[/.test(lines[index])) {
|
|
229
|
+
index += 1;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return `${kept.join("\n").trimEnd()}\n`;
|
|
233
|
+
}
|
|
234
|
+
function readHookStateTrustedHashes(config) {
|
|
235
|
+
const lines = config.split(/\r?\n/);
|
|
236
|
+
const hashes = new Map();
|
|
237
|
+
for (let index = 0; index < lines.length;) {
|
|
238
|
+
const key = readHooksStateTableKey(lines[index]);
|
|
239
|
+
if (!key) {
|
|
240
|
+
index += 1;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
index += 1;
|
|
244
|
+
while (index < lines.length && !/^\s*\[/.test(lines[index])) {
|
|
245
|
+
const match = lines[index].trim().match(/^trusted_hash\s*=\s*"([^"]+)"$/);
|
|
246
|
+
if (match) {
|
|
247
|
+
hashes.set(key, match[1]);
|
|
248
|
+
}
|
|
249
|
+
index += 1;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return hashes;
|
|
253
|
+
}
|
|
254
|
+
function renderCodexHookTrustToml(state) {
|
|
255
|
+
return Object.entries(state)
|
|
256
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
257
|
+
.flatMap(([key, entry]) => [
|
|
258
|
+
`[hooks.state."${escapeTomlBasicString(key)}"]`,
|
|
259
|
+
`trusted_hash = "${escapeTomlBasicString(entry.trusted_hash)}"`,
|
|
260
|
+
""
|
|
261
|
+
])
|
|
262
|
+
.join("\n")
|
|
263
|
+
.trimEnd();
|
|
264
|
+
}
|
|
265
|
+
export function mergeCodexHookTrustState(config, hooksPath, hooksContent) {
|
|
266
|
+
const state = buildManagedCodexHookTrustState(hooksPath, hooksContent);
|
|
267
|
+
const keys = new Set(Object.keys(state));
|
|
268
|
+
if (keys.size === 0) {
|
|
269
|
+
return config.trimEnd() ? `${config.trimEnd()}\n` : "";
|
|
270
|
+
}
|
|
271
|
+
const withoutCurrent = removeHookStateTables(config, (key) => keys.has(key)).trimEnd();
|
|
272
|
+
const trustToml = renderCodexHookTrustToml(state);
|
|
273
|
+
return withoutCurrent ? `${withoutCurrent}\n\n${trustToml}\n` : `${trustToml}\n`;
|
|
274
|
+
}
|
|
275
|
+
export function removeCodexHookTrustState(config, hooksPath, hooksContent) {
|
|
276
|
+
const keys = new Set(Object.keys(buildManagedCodexHookTrustState(hooksPath, hooksContent)));
|
|
277
|
+
if (keys.size === 0) {
|
|
278
|
+
return config.trimEnd() ? `${config.trimEnd()}\n` : "";
|
|
279
|
+
}
|
|
280
|
+
return removeHookStateTables(config, (key) => keys.has(key));
|
|
281
|
+
}
|
|
282
|
+
export function getMissingManagedCodexHookTrustState(config, hooksPath, hooksContent) {
|
|
283
|
+
const expected = buildManagedCodexHookTrustState(hooksPath, hooksContent);
|
|
284
|
+
const hashes = readHookStateTrustedHashes(config);
|
|
285
|
+
const missing = [];
|
|
286
|
+
for (const [key, entry] of Object.entries(expected)) {
|
|
287
|
+
if (hashes.get(key) !== entry.trusted_hash) {
|
|
288
|
+
missing.push(key);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return missing;
|
|
292
|
+
}
|
|
129
293
|
export function mergeManagedCodexHooksConfig(existingContent, packageRoot) {
|
|
130
294
|
const managedConfig = buildManagedCodexHooksConfig(packageRoot);
|
|
131
295
|
const parsed = typeof existingContent === "string"
|
|
@@ -204,22 +368,26 @@ function findSectionBounds(lines, sectionHeader) {
|
|
|
204
368
|
}
|
|
205
369
|
return { start, end };
|
|
206
370
|
}
|
|
207
|
-
|
|
371
|
+
function normalizeCodexHookFeatureFlag(value) {
|
|
372
|
+
return value === "codex_hooks" ? "codex_hooks" : "hooks";
|
|
373
|
+
}
|
|
374
|
+
export function enableCodexHooksFeature(existing, featureFlag) {
|
|
375
|
+
const flagName = normalizeCodexHookFeatureFlag(featureFlag);
|
|
208
376
|
const trimmed = existing.trimEnd();
|
|
209
377
|
const lines = trimmed ? trimmed.split(/\r?\n/) : [];
|
|
210
378
|
const section = findSectionBounds(lines, "[features]");
|
|
211
379
|
if (!section) {
|
|
212
380
|
return trimmed
|
|
213
|
-
? `${trimmed}\n\n[features]\
|
|
214
|
-
:
|
|
381
|
+
? `${trimmed}\n\n[features]\n${flagName} = true\n`
|
|
382
|
+
: `[features]\n${flagName} = true\n`;
|
|
215
383
|
}
|
|
216
384
|
const featureLines = lines.slice(section.start + 1, section.end);
|
|
217
|
-
const existingIndex = featureLines.findIndex((line) =>
|
|
385
|
+
const existingIndex = featureLines.findIndex((line) => new RegExp(`^\\s*${flagName}\\s*=`).test(line));
|
|
218
386
|
if (existingIndex !== -1) {
|
|
219
|
-
featureLines[existingIndex] =
|
|
387
|
+
featureLines[existingIndex] = `${flagName} = true`;
|
|
220
388
|
}
|
|
221
389
|
else {
|
|
222
|
-
featureLines.push(
|
|
390
|
+
featureLines.push(`${flagName} = true`);
|
|
223
391
|
}
|
|
224
392
|
const rebuilt = [
|
|
225
393
|
...lines.slice(0, section.start + 1),
|
|
@@ -236,5 +404,5 @@ export function codexHooksEnabled(config) {
|
|
|
236
404
|
}
|
|
237
405
|
return lines
|
|
238
406
|
.slice(section.start + 1, section.end)
|
|
239
|
-
.some((line) => /^\s*codex_hooks\s*=\s*true\s*$/.test(line));
|
|
407
|
+
.some((line) => /^\s*(?:hooks|codex_hooks)\s*=\s*true\s*$/.test(line));
|
|
240
408
|
}
|
|
@@ -21,6 +21,8 @@ function readHookEventName(payload) {
|
|
|
21
21
|
candidate === "PreToolUse" ||
|
|
22
22
|
candidate === "PostToolUse" ||
|
|
23
23
|
candidate === "UserPromptSubmit" ||
|
|
24
|
+
candidate === "PreCompact" ||
|
|
25
|
+
candidate === "PostCompact" ||
|
|
24
26
|
candidate === "Stop") {
|
|
25
27
|
return candidate;
|
|
26
28
|
}
|
|
@@ -329,6 +331,31 @@ function sessionStartContext(runtime) {
|
|
|
329
331
|
sections.push("Treat `.longtable/` state and `CURRENT.md` as the source of truth for this workspace.");
|
|
330
332
|
return sections.filter(Boolean).join("\n\n");
|
|
331
333
|
}
|
|
334
|
+
function postCompactContext(runtime) {
|
|
335
|
+
const blockingQuestion = pendingRequiredQuestions(runtime.state)[0];
|
|
336
|
+
const blockingObligation = pendingObligations(runtime.state)[0];
|
|
337
|
+
const interview = activeInterviewHook(runtime.state);
|
|
338
|
+
if (!blockingQuestion && !blockingObligation && !interview) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
const sections = [buildWorkspaceSummary(runtime, "compact").join("\n")];
|
|
342
|
+
if (interview) {
|
|
343
|
+
sections.push(buildActiveInterviewContext(interview));
|
|
344
|
+
if (blockingQuestion) {
|
|
345
|
+
sections.push(buildSeparatePendingQuestionNotice(blockingQuestion));
|
|
346
|
+
}
|
|
347
|
+
else if (blockingObligation) {
|
|
348
|
+
sections.push(buildSeparatePendingObligationNotice(blockingObligation));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else if (blockingQuestion) {
|
|
352
|
+
sections.push(buildPendingQuestionContext(blockingQuestion));
|
|
353
|
+
}
|
|
354
|
+
else if (blockingObligation) {
|
|
355
|
+
sections.push(buildPendingObligationContext(blockingObligation));
|
|
356
|
+
}
|
|
357
|
+
return sections.filter(Boolean).join("\n\n");
|
|
358
|
+
}
|
|
332
359
|
async function userPromptSubmitContext(runtime, prompt) {
|
|
333
360
|
const blockingQuestion = pendingRequiredQuestions(runtime.state)[0];
|
|
334
361
|
const blockingObligation = pendingObligations(runtime.state)[0];
|
|
@@ -451,6 +478,15 @@ export async function dispatchCodexHook(payload, cwdOverride) {
|
|
|
451
478
|
if (hookEventName === "PostToolUse") {
|
|
452
479
|
return postToolUseOutput(runtime, payload);
|
|
453
480
|
}
|
|
481
|
+
if (hookEventName === "PreCompact") {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
if (hookEventName === "PostCompact") {
|
|
485
|
+
const additionalContext = postCompactContext(runtime);
|
|
486
|
+
return additionalContext
|
|
487
|
+
? buildAdditionalContextOutput(hookEventName, additionalContext)
|
|
488
|
+
: null;
|
|
489
|
+
}
|
|
454
490
|
if (hookEventName === "Stop") {
|
|
455
491
|
return stopOutput(runtime);
|
|
456
492
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DecisionRecord, EvidenceRecord, InvocationRecord, LongTableQuestionObligation, ProviderKind, QuestionOption, QuestionCommitmentFamily, QuestionEpistemicBasis, QuestionGenerationResult, QuestionOpportunity, QuestionSurface, QuestionRecord, ResearchSpecificationChange, ResearchSpecificationPatch, ResearchSpecificationPatchSource, ResearchSpecificationRevision, ResearchState } from "@longtable/core";
|
|
1
|
+
import type { DecisionRecord, EvidenceRecord, InvocationRecord, LongTableQuestionObligation, ProviderKind, QuestionOption, QuestionCommitmentFamily, QuestionEpistemicBasis, QuestionGenerationResult, QuestionOpportunity, QuestionSurface, QuestionPromptType, QuestionRecord, ResearchSpecificationChange, ResearchSpecificationPatch, ResearchSpecificationPatchSource, ResearchSpecificationRevision, 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 type StartInterviewSignal = "phenomenon" | "audience" | "artifact" | "evidence" | "assumption" | "decision_risk" | "voice";
|
|
@@ -393,8 +393,11 @@ export declare function createWorkspaceQuestion(options: {
|
|
|
393
393
|
prompt: string;
|
|
394
394
|
title?: string;
|
|
395
395
|
question?: string;
|
|
396
|
+
type?: QuestionPromptType;
|
|
396
397
|
checkpointKey?: string;
|
|
397
398
|
questionOptions?: QuestionOption[];
|
|
399
|
+
allowOther?: boolean;
|
|
400
|
+
otherLabel?: string;
|
|
398
401
|
displayReason?: string;
|
|
399
402
|
provider?: ProviderKind;
|
|
400
403
|
required?: boolean;
|
|
@@ -404,10 +407,17 @@ export declare function createWorkspaceQuestion(options: {
|
|
|
404
407
|
question: QuestionRecord;
|
|
405
408
|
state: ResearchState;
|
|
406
409
|
}>;
|
|
410
|
+
type WorkspaceQuestionAnswerInput = string | string[] | {
|
|
411
|
+
answer?: string | string[];
|
|
412
|
+
selectedValue?: string;
|
|
413
|
+
selectedValues?: string[];
|
|
414
|
+
otherText?: string;
|
|
415
|
+
rationale?: string;
|
|
416
|
+
};
|
|
407
417
|
export declare function answerWorkspaceQuestion(options: {
|
|
408
418
|
context: LongTableProjectContext;
|
|
409
419
|
questionId?: string;
|
|
410
|
-
answer:
|
|
420
|
+
answer: WorkspaceQuestionAnswerInput;
|
|
411
421
|
rationale?: string;
|
|
412
422
|
provider?: "codex" | "claude";
|
|
413
423
|
surface?: QuestionSurface;
|
package/dist/project-session.js
CHANGED
|
@@ -2397,6 +2397,7 @@ export async function createWorkspaceQuestion(options) {
|
|
|
2397
2397
|
studyContract: state.studyContract
|
|
2398
2398
|
});
|
|
2399
2399
|
const checkpointKey = options.checkpointKey ?? trigger.signal.checkpointKey;
|
|
2400
|
+
const promptType = options.type ?? "single_choice";
|
|
2400
2401
|
const createdAt = nowIso();
|
|
2401
2402
|
const title = options.title ?? questionTitleForCheckpoint(trigger.family, checkpointKey);
|
|
2402
2403
|
const questionText = options.question ?? questionTextForCheckpoint(trigger.family, options.prompt, checkpointKey);
|
|
@@ -2427,10 +2428,10 @@ export async function createWorkspaceQuestion(options) {
|
|
|
2427
2428
|
checkpointKey,
|
|
2428
2429
|
title,
|
|
2429
2430
|
question: questionText,
|
|
2430
|
-
type:
|
|
2431
|
+
type: promptType,
|
|
2431
2432
|
options: options.questionOptions ?? optionsForCheckpointTrigger(trigger.family, checkpointKey),
|
|
2432
|
-
allowOther:
|
|
2433
|
-
otherLabel: "Other decision",
|
|
2433
|
+
allowOther: options.allowOther ?? promptType !== "free_text",
|
|
2434
|
+
otherLabel: options.otherLabel ?? "Other decision",
|
|
2434
2435
|
required: options.required ?? trigger.requiresQuestionBeforeClosure,
|
|
2435
2436
|
source: "checkpoint",
|
|
2436
2437
|
displayReason: options.displayReason ?? trigger.rationale[0],
|
|
@@ -2486,53 +2487,159 @@ function splitAnswerAndRationale(rawAnswer) {
|
|
|
2486
2487
|
...(rationale ? { rationale } : {})
|
|
2487
2488
|
};
|
|
2488
2489
|
}
|
|
2489
|
-
function
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
if (
|
|
2490
|
+
function uniqueStrings(values) {
|
|
2491
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
2492
|
+
}
|
|
2493
|
+
function answerInputParts(input) {
|
|
2494
|
+
if (typeof input === "string") {
|
|
2495
|
+
return { values: [input], rawText: input };
|
|
2496
|
+
}
|
|
2497
|
+
if (Array.isArray(input)) {
|
|
2498
|
+
return { values: input };
|
|
2499
|
+
}
|
|
2500
|
+
let values = input.selectedValues ?? [];
|
|
2501
|
+
if (values.length === 0 && input.selectedValue) {
|
|
2502
|
+
values = [input.selectedValue];
|
|
2503
|
+
}
|
|
2504
|
+
if (values.length === 0 && Array.isArray(input.answer)) {
|
|
2505
|
+
values = input.answer;
|
|
2506
|
+
}
|
|
2507
|
+
if (values.length === 0 && typeof input.answer === "string") {
|
|
2508
|
+
values = [input.answer];
|
|
2509
|
+
}
|
|
2510
|
+
const otherText = input.otherText?.trim();
|
|
2511
|
+
const rationale = input.rationale?.trim();
|
|
2512
|
+
return {
|
|
2513
|
+
values,
|
|
2514
|
+
...(otherText ? { otherText } : {}),
|
|
2515
|
+
...(rationale ? { inlineRationale: rationale } : {})
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
function splitSelectionTokens(selection, type) {
|
|
2519
|
+
if (type !== "multi_choice") {
|
|
2520
|
+
return [selection];
|
|
2521
|
+
}
|
|
2522
|
+
return selection.split(/[;,]/).map((token) => token.trim()).filter(Boolean);
|
|
2523
|
+
}
|
|
2524
|
+
function normalizeFreeTextAnswer(input) {
|
|
2525
|
+
const parts = answerInputParts(input);
|
|
2526
|
+
const selectedText = parts.values.join("\n").trim();
|
|
2527
|
+
const text = parts.rawText ?? (selectedText || parts.otherText || "");
|
|
2528
|
+
const trimmed = text.trim();
|
|
2529
|
+
if (!trimmed) {
|
|
2530
|
+
throw new Error("Free-text LongTable question answers cannot be empty.");
|
|
2531
|
+
}
|
|
2532
|
+
return {
|
|
2533
|
+
selectedValues: [trimmed],
|
|
2534
|
+
selectedLabels: [trimmed],
|
|
2535
|
+
otherText: trimmed,
|
|
2536
|
+
...(parts.inlineRationale ? { inlineRationale: parts.inlineRationale } : {})
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
function resolveQuestionAnswerToken(question, token) {
|
|
2540
|
+
const trimmed = token.trim();
|
|
2541
|
+
if (!trimmed) {
|
|
2542
|
+
throw new Error("LongTable question answers cannot contain empty selections.");
|
|
2543
|
+
}
|
|
2544
|
+
const numeric = Number(trimmed);
|
|
2545
|
+
if (/^\d+$/.test(trimmed) && Number.isInteger(numeric)) {
|
|
2494
2546
|
const option = question.prompt.options[numeric - 1];
|
|
2495
2547
|
if (option) {
|
|
2496
2548
|
return {
|
|
2497
2549
|
selectedValue: option.value,
|
|
2498
|
-
selectedLabel: option.label
|
|
2499
|
-
...(rationale ? { inlineRationale: rationale } : {})
|
|
2550
|
+
selectedLabel: option.label
|
|
2500
2551
|
};
|
|
2501
2552
|
}
|
|
2502
2553
|
if (question.prompt.allowOther && numeric === question.prompt.options.length + 1) {
|
|
2503
2554
|
return {
|
|
2504
2555
|
selectedValue: "other",
|
|
2505
|
-
selectedLabel: question.prompt.otherLabel ?? "Other"
|
|
2506
|
-
...(rationale ? { inlineRationale: rationale } : {})
|
|
2556
|
+
selectedLabel: question.prompt.otherLabel ?? "Other"
|
|
2507
2557
|
};
|
|
2508
2558
|
}
|
|
2509
|
-
throw new Error(`Answer ${
|
|
2559
|
+
throw new Error(`Answer ${trimmed} is outside the available LongTable question options.`);
|
|
2510
2560
|
}
|
|
2511
|
-
const normalizedSelection = normalizeAnswerToken(
|
|
2561
|
+
const normalizedSelection = normalizeAnswerToken(trimmed);
|
|
2512
2562
|
const option = question.prompt.options.find((candidate) => optionAnswerCandidates(candidate).includes(normalizedSelection));
|
|
2513
2563
|
if (option) {
|
|
2514
2564
|
return {
|
|
2515
2565
|
selectedValue: option.value,
|
|
2516
|
-
selectedLabel: option.label
|
|
2517
|
-
...(rationale ? { inlineRationale: rationale } : {})
|
|
2566
|
+
selectedLabel: option.label
|
|
2518
2567
|
};
|
|
2519
2568
|
}
|
|
2520
2569
|
if (normalizedSelection === "other" && question.prompt.allowOther) {
|
|
2521
2570
|
return {
|
|
2522
2571
|
selectedValue: "other",
|
|
2523
|
-
selectedLabel: question.prompt.otherLabel ?? "Other"
|
|
2524
|
-
...(rationale ? { inlineRationale: rationale } : {})
|
|
2572
|
+
selectedLabel: question.prompt.otherLabel ?? "Other"
|
|
2525
2573
|
};
|
|
2526
2574
|
}
|
|
2527
2575
|
if (question.prompt.allowOther) {
|
|
2528
2576
|
return {
|
|
2529
|
-
selectedValue:
|
|
2530
|
-
selectedLabel:
|
|
2531
|
-
otherText: trimmed
|
|
2532
|
-
...(rationale ? { inlineRationale: rationale } : {})
|
|
2577
|
+
selectedValue: trimmed,
|
|
2578
|
+
selectedLabel: question.prompt.otherLabel ?? "Other",
|
|
2579
|
+
otherText: trimmed
|
|
2533
2580
|
};
|
|
2534
2581
|
}
|
|
2535
|
-
throw new Error(`Answer "${
|
|
2582
|
+
throw new Error(`Answer "${trimmed}" does not match a LongTable question option.`);
|
|
2583
|
+
}
|
|
2584
|
+
function isOtherAnswerToken(question, token) {
|
|
2585
|
+
if (!question.prompt.allowOther) {
|
|
2586
|
+
return false;
|
|
2587
|
+
}
|
|
2588
|
+
const trimmed = token.trim();
|
|
2589
|
+
const normalized = normalizeAnswerToken(trimmed);
|
|
2590
|
+
const otherLabel = normalizeAnswerToken(question.prompt.otherLabel ?? "Other");
|
|
2591
|
+
if (normalized === "other" || normalized === otherLabel) {
|
|
2592
|
+
return true;
|
|
2593
|
+
}
|
|
2594
|
+
const numeric = Number(trimmed);
|
|
2595
|
+
return /^\d+$/.test(trimmed) && Number.isInteger(numeric) && numeric === question.prompt.options.length + 1;
|
|
2596
|
+
}
|
|
2597
|
+
function normalizeQuestionAnswerSelection(question, rawAnswer) {
|
|
2598
|
+
if (question.prompt.type === "free_text") {
|
|
2599
|
+
return normalizeFreeTextAnswer(rawAnswer);
|
|
2600
|
+
}
|
|
2601
|
+
const parts = answerInputParts(rawAnswer);
|
|
2602
|
+
const parsedValues = parts.values.flatMap((value) => {
|
|
2603
|
+
if (typeof rawAnswer === "string") {
|
|
2604
|
+
const { selection } = splitAnswerAndRationale(value.trim());
|
|
2605
|
+
return splitSelectionTokens(selection, question.prompt.type);
|
|
2606
|
+
}
|
|
2607
|
+
return splitSelectionTokens(value, question.prompt.type);
|
|
2608
|
+
});
|
|
2609
|
+
const inlineRationale = typeof rawAnswer === "string"
|
|
2610
|
+
? splitAnswerAndRationale(rawAnswer.trim()).rationale
|
|
2611
|
+
: parts.inlineRationale;
|
|
2612
|
+
const hasOtherText = Boolean(parts.otherText);
|
|
2613
|
+
const hasOtherToken = parsedValues.some((value) => isOtherAnswerToken(question, value));
|
|
2614
|
+
if (hasOtherText && !hasOtherToken) {
|
|
2615
|
+
throw new Error("Other text requires selecting the LongTable question Other option.");
|
|
2616
|
+
}
|
|
2617
|
+
const selectionTokens = parts.otherText
|
|
2618
|
+
? parsedValues.filter((value) => !isOtherAnswerToken(question, value))
|
|
2619
|
+
: parsedValues;
|
|
2620
|
+
const tokens = uniqueStrings([
|
|
2621
|
+
...selectionTokens,
|
|
2622
|
+
...(parts.otherText ? [parts.otherText] : [])
|
|
2623
|
+
]);
|
|
2624
|
+
if (tokens.length === 0) {
|
|
2625
|
+
throw new Error("LongTable question answers cannot be empty.");
|
|
2626
|
+
}
|
|
2627
|
+
if (question.prompt.type !== "multi_choice" && tokens.length > 1) {
|
|
2628
|
+
throw new Error("Single-choice LongTable questions accept exactly one answer.");
|
|
2629
|
+
}
|
|
2630
|
+
const resolved = tokens.map((token) => resolveQuestionAnswerToken(question, token));
|
|
2631
|
+
const selectedValues = uniqueStrings(resolved.map((entry) => entry.selectedValue));
|
|
2632
|
+
const selectedLabels = selectedValues.map((value) => {
|
|
2633
|
+
const entry = resolved.find((candidate) => candidate.selectedValue === value);
|
|
2634
|
+
return entry?.selectedLabel ?? value;
|
|
2635
|
+
});
|
|
2636
|
+
const otherText = parts.otherText ?? resolved.map((entry) => entry.otherText).find(Boolean);
|
|
2637
|
+
return {
|
|
2638
|
+
selectedValues,
|
|
2639
|
+
selectedLabels,
|
|
2640
|
+
...(otherText ? { otherText } : {}),
|
|
2641
|
+
...(inlineRationale ? { inlineRationale } : {})
|
|
2642
|
+
};
|
|
2536
2643
|
}
|
|
2537
2644
|
export async function answerWorkspaceQuestion(options) {
|
|
2538
2645
|
const state = await loadResearchState(options.context.stateFilePath);
|
|
@@ -2546,8 +2653,8 @@ export async function answerWorkspaceQuestion(options) {
|
|
|
2546
2653
|
.join("\n");
|
|
2547
2654
|
const answer = {
|
|
2548
2655
|
promptId: question.prompt.id,
|
|
2549
|
-
selectedValues:
|
|
2550
|
-
selectedLabels:
|
|
2656
|
+
selectedValues: normalized.selectedValues,
|
|
2657
|
+
selectedLabels: normalized.selectedLabels,
|
|
2551
2658
|
...(normalized.otherText ? { otherText: normalized.otherText } : {}),
|
|
2552
2659
|
...(rationale ? { rationale } : {}),
|
|
2553
2660
|
...(options.provider ? { provider: options.provider } : {}),
|
|
@@ -2564,6 +2671,7 @@ export async function answerWorkspaceQuestion(options) {
|
|
|
2564
2671
|
...(question.commitmentFamily ? { commitmentFamily: question.commitmentFamily } : {}),
|
|
2565
2672
|
...(question.epistemicBasis ? { epistemicBasis: question.epistemicBasis } : {}),
|
|
2566
2673
|
selectedOption: answer.selectedValues[0],
|
|
2674
|
+
selectedOptions: answer.selectedValues,
|
|
2567
2675
|
...(rationale ? { rationale } : {})
|
|
2568
2676
|
};
|
|
2569
2677
|
const answeredQuestion = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.50",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Researcher-facing LongTable CLI",
|
|
6
6
|
"type": "module",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@clack/prompts": "^1.2.0",
|
|
32
|
-
"@longtable/checkpoints": "0.1.
|
|
33
|
-
"@longtable/core": "0.1.
|
|
34
|
-
"@longtable/memory": "0.1.
|
|
35
|
-
"@longtable/provider-claude": "0.1.
|
|
36
|
-
"@longtable/provider-codex": "0.1.
|
|
37
|
-
"@longtable/setup": "0.1.
|
|
32
|
+
"@longtable/checkpoints": "0.1.50",
|
|
33
|
+
"@longtable/core": "0.1.50",
|
|
34
|
+
"@longtable/memory": "0.1.50",
|
|
35
|
+
"@longtable/provider-claude": "0.1.50",
|
|
36
|
+
"@longtable/provider-codex": "0.1.50",
|
|
37
|
+
"@longtable/setup": "0.1.50"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.10.1",
|