@youdie006/prodex 0.40.2 → 0.40.4
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/blocker-report.js +7 -1
- package/dist/browser-send-lock.js +21 -1
- package/dist/chatgpt-browser.js +351 -51
- package/dist/cli-args.js +5 -0
- package/dist/cli-help.js +9 -4
- package/dist/cli-pro.js +165 -33
- package/dist/config.js +45 -6
- package/dist/continue-thread.js +133 -0
- package/dist/mcp.js +12 -2
- package/package.json +1 -1
package/dist/cli-help.js
CHANGED
|
@@ -26,7 +26,7 @@ Ask / consult commands:
|
|
|
26
26
|
prodex pro browser models [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--timeout-ms 15000] # read-only list of model menu options
|
|
27
27
|
prodex pro browser projects [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--timeout-ms 15000] # read-only list of sidebar project names (for --project)
|
|
28
28
|
prodex pro browser recover [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] --target-url <thread-url> [--timeout-ms 60000] # recover a finished answer from a thread whose send timed out
|
|
29
|
-
prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] [--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"|Max|Ultra|Pro] [--project "name" | --project-new "name"] "prompt" # explicit visible-browser send
|
|
29
|
+
prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--continue | --continue-task task_id] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] [--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"|Max|Ultra|Pro] [--project "name" | --project-new "name"] "prompt" # explicit visible-browser send
|
|
30
30
|
prodex pro latest [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--json]
|
|
31
31
|
prodex pro blockers [--cwd /absolute/path/to/repo] [--since 7d] [--limit 10] [--json] # what actually blocks consults, ranked, across every bridge root on this machine
|
|
32
32
|
prodex pro list [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--json]
|
|
@@ -170,7 +170,7 @@ Commands:
|
|
|
170
170
|
prodex pro browser check [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo]
|
|
171
171
|
prodex pro browser smoke [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo]
|
|
172
172
|
prodex pro browser models [--source-cli /absolute/path/to/dist/cli.js]
|
|
173
|
-
prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--target-url url --confirm-target] [--new-chat] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] [--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"|Max|Ultra|Pro] [--project "name" | --project-new "name"] "prompt"
|
|
173
|
+
prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--target-url url --confirm-target] [--new-chat] [--continue | --continue-task task_id] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] [--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"|Max|Ultra|Pro] [--project "name" | --project-new "name"] "prompt"
|
|
174
174
|
prodex pro latest [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--json]
|
|
175
175
|
prodex pro blockers [--cwd /absolute/path/to/repo] [--since 7d] [--limit 10] [--json] # what actually blocks consults, ranked, across every bridge root on this machine
|
|
176
176
|
prodex pro list [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--json]
|
|
@@ -258,8 +258,8 @@ export function printProBrowserHelp(stdout, sourceCli) {
|
|
|
258
258
|
: "prodex pro browser smoke [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 90000]";
|
|
259
259
|
const selectionUsage = '[--model Pro] [--pro-mode 기본|확장] [--effort 즉시|중간|높음|"매우 높음"|Max|Ultra|Pro] [--project "name" | --project-new "name"]';
|
|
260
260
|
const askUsage = sourceCli
|
|
261
|
-
? `${cli} pro browser ask${sourceCliOption} [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] ${selectionUsage} "prompt"`
|
|
262
|
-
: `prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] ${selectionUsage} "prompt"`;
|
|
261
|
+
? `${cli} pro browser ask${sourceCliOption} [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--continue | --continue-task task_id] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] ${selectionUsage} "prompt"`
|
|
262
|
+
: `prodex pro browser ask [--source-cli /absolute/path/to/dist/cli.js] [--cwd /absolute/path/to/repo] [--port 9333] [--timeout-ms 300000] [--busy-wait-ms 600000] [--target-url url --confirm-target] [--new-chat] [--continue | --continue-task task_id] [--temporary] [--allow-model-fallback] [--stdin] [--json] [--auto-login|--no-auto-login] [--file path] [--attach path] [--tool deep-research|web-search|create-image] ${selectionUsage} "prompt"`;
|
|
263
263
|
const modelsUsage = sourceCli
|
|
264
264
|
? `${cli} pro browser models${sourceCliOption} [--port 9333] [--timeout-ms 15000]`
|
|
265
265
|
: "prodex pro browser models [--source-cli /absolute/path/to/dist/cli.js] [--port 9333] [--timeout-ms 15000]";
|
|
@@ -306,6 +306,11 @@ Model/project selection (ask):
|
|
|
306
306
|
--pro-mode Pro sub-mode: 기본 (standard) or 확장 (extended), used when the model is Pro. A Pro selection raises the default --timeout-ms to 1200000.
|
|
307
307
|
--effort Reasoning effort: 즉시 / 중간 / 높음 / 매우 높음 / Max / Ultra / Pro (aliases: instant/light, medium, high, extrahigh/max, ultra). One power slider sets the model and the effort together, so picking an effort moves off Pro. Max and Ultra are rungs of the Work surface and only apply when the browser is already on Work; everything else is sent on Chat.
|
|
308
308
|
--project Enter an existing sidebar project before sending. Cannot be combined with --target-url.
|
|
309
|
+
|
|
310
|
+
Continuing a conversation (ask):
|
|
311
|
+
--continue Send into the conversation a previous consult is already in: the newest FINISHED consult of the same project, read from this repo's own .bridge records. The browser tab is shared and a pinned project starts a new chat on every send, so the tab is not what decides this. A --project here scopes the search rather than navigating. Refuses instead of guessing when this project has no finished consult yet.
|
|
312
|
+
--continue-task Continue one named past consult by its task_id, when the newest is not the conversation meant. List them with \`${cli} pro list\`.
|
|
313
|
+
Cannot be combined with --new-chat, --target-url, --project-new or --temporary.
|
|
309
314
|
--pro-mode and --effort cannot be combined. Labels are matched in both the Korean and English (US) ChatGPT UI (e.g. 높음/High, Pro 확장/Pro Extended).
|
|
310
315
|
Run \`${cli} pro browser models${sourceCliOption}\` to list the labels your account currently shows.
|
|
311
316
|
Persist defaults with \`${cli} setup${sourceCliOption}\`; per-ask flags override them.
|
package/dist/cli-pro.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, statSync } from "node:fs";
|
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { buildDryRunBundle } from "./bundle.js";
|
|
5
|
-
import { DEFAULT_CDP_PORT, resolveCdpPort, resolveConversationToDelete, resolveProjectToDelete, chatGptVisibilityBlocker, defaultChatGptProfileDir, formatDurationMs, getChatGptBrowserStatus, formatModelMenuOption, listChatGptModelOptions, deleteChatGptConversation, endWedgedBrowser, browserRecoveryPlan, findWedgedBrowser, wedgedBrowserBlocker, deleteChatGptProject, listChatGptProjectsWithIds, listRecentChatGptConversations, listChatGptSidebarProjects, normalizeChatGptTargetUrl, openChatGptBrowser, openChatGptTab, parseProMode, parseReasoningEffort, defaultTimeoutForTools, ensureVirtualDisplay, minimizeChatGptWindow, readLastBrowserLoginLaunch, resolveVirtualDisplayPreference, resolveBrowserWindowMode, resolveHeadlessPreference, recordBrowserLoginLaunch, recoverChatGptAnswerFromThread, sendChatGptPrompt, statusMeansBrowserDead, namesPro } from "./chatgpt-browser.js";
|
|
5
|
+
import { DEFAULT_CDP_PORT, resolveCdpPort, resolveConversationToDelete, resolveProjectToDelete, chatGptVisibilityBlocker, defaultChatGptProfileDir, formatDurationMs, getChatGptBrowserStatus, formatModelMenuOption, listChatGptModelOptions, deleteChatGptConversation, endWedgedBrowser, browserRecoveryPlan, findWedgedBrowser, wedgedBrowserBlocker, deleteChatGptProject, listChatGptProjectsWithIds, listRecentChatGptConversations, listChatGptSidebarProjects, normalizeChatGptTargetUrl, openChatGptBrowser, openChatGptTab, parseProMode, parseReasoningEffort, defaultTimeoutForTools, ensureVirtualDisplay, minimizeChatGptWindow, readLastBrowserLoginLaunch, resolveVirtualDisplayPreference, resolveBrowserWindowMode, resolveHeadlessPreference, recordBrowserLoginLaunch, recoverChatGptAnswerFromThread, sendChatGptPrompt, statusMeansBrowserDead, namesPro, destinationVerification, chatGptProjectIdFromUrl } from "./chatgpt-browser.js";
|
|
6
6
|
import { ASK_PRO_BOOLEAN_FLAGS, ASK_PRO_PREVIEW_VALUE_FLAGS, ASK_PRO_VALUE_FLAGS, assertHelpRequestArgs, assertNoExtraArgs, assertOnlyOptions, findHelpFlagIndexBeforePromptDelimiter, formatCliCommand, hasAskProDryRunMode, hasAskProMode, hasAskProSendMode, isHelpSubcommand, parseAskProArgs, printHelpIfRequested, readFlag, readPortFlag, readPositionalsWithOptions, readNonNegativeIntegerFlag, readPositiveIntegerFlag, readRepeatedFlag, resolveCwdFlag, resolveOptionalFileFlag, unknownSubcommandError } from "./cli-args.js";
|
|
7
7
|
import { printProBrowserHelp, printProHelp } from "./cli-help.js";
|
|
8
8
|
import { listRawResultsForInspection, listTasksForInspection } from "./cli-ledger.js";
|
|
@@ -11,6 +11,7 @@ import { errorMessage, firstLine, formatBlockedConsultRecordedMessage, formatPro
|
|
|
11
11
|
import { getTokenExpiryStatus, loadBrowserDefaults, loadLocalConfig } from "./config.js";
|
|
12
12
|
import { withBrowserSendLock } from "./browser-send-lock.js";
|
|
13
13
|
import { blockerCause, buildBlockerReport } from "./blocker-report.js";
|
|
14
|
+
import { resolveContinuationThread } from "./continue-thread.js";
|
|
14
15
|
import { readBridgeRoots } from "./registry.js";
|
|
15
16
|
import { BridgeStore, MAX_FETCHABLE_RESULT_ARTIFACT_BYTES } from "./store.js";
|
|
16
17
|
import { CLI_VERSION } from "./cli-help.js";
|
|
@@ -162,6 +163,8 @@ export async function runProCommand(rest, io, runCliFn) {
|
|
|
162
163
|
"--effort",
|
|
163
164
|
"--new-chat",
|
|
164
165
|
"--temporary",
|
|
166
|
+
"--continue",
|
|
167
|
+
"--continue-task",
|
|
165
168
|
"--auto-login",
|
|
166
169
|
"--no-auto-login"
|
|
167
170
|
].find((flag) => proArgs.includes(flag));
|
|
@@ -1058,7 +1061,8 @@ export async function runAskProCommand(rest, io) {
|
|
|
1058
1061
|
throw new Error("--tool only applies when sending (`prodex pro browser ask`); the dry-run preview cannot open ChatGPT's tools menu.");
|
|
1059
1062
|
}
|
|
1060
1063
|
const targetUrl = readFlag(parsedAskPro.optionArgs, "--target-url");
|
|
1061
|
-
|
|
1064
|
+
let normalizedTargetUrl = targetUrl ? normalizeChatGptTargetUrl(targetUrl) : undefined;
|
|
1065
|
+
let continuedFromTaskId;
|
|
1062
1066
|
if (!normalizedTargetUrl && parsedAskPro.optionArgs.includes("--confirm-target")) {
|
|
1063
1067
|
throw new Error("--confirm-target requires --target-url so the visible browser target is explicit.");
|
|
1064
1068
|
}
|
|
@@ -1104,6 +1108,46 @@ export async function runAskProCommand(rest, io) {
|
|
|
1104
1108
|
if (normalizedTargetUrl && (explicitProject !== undefined || explicitProjectNew !== undefined)) {
|
|
1105
1109
|
throw new Error("ask-pro cannot combine --target-url with --project/--project-new: --target-url pins the confirmed tab while the project step navigates the sidebar away from it. Open the project thread in the browser and pass its URL as --target-url instead.");
|
|
1106
1110
|
}
|
|
1111
|
+
// Continue the conversation a previous consult left off in. Resolved from
|
|
1112
|
+
// this repo's records rather than from the shared tab: the tab is whatever
|
|
1113
|
+
// the last person or session left on screen, and a pinned project
|
|
1114
|
+
// navigates away from it before every send anyway - which is why a
|
|
1115
|
+
// "continuing" consult on a machine with a default project measurably
|
|
1116
|
+
// started a new thread every time.
|
|
1117
|
+
const continueRequested = parsedAskPro.optionArgs.includes("--continue");
|
|
1118
|
+
const continueTaskId = readFlag(parsedAskPro.optionArgs, "--continue-task");
|
|
1119
|
+
if (continueRequested || continueTaskId !== undefined) {
|
|
1120
|
+
const conflict = [
|
|
1121
|
+
parsedAskPro.optionArgs.includes("--new-chat") ? "--new-chat" : undefined,
|
|
1122
|
+
targetUrl !== undefined ? "--target-url" : undefined,
|
|
1123
|
+
explicitProjectNew !== undefined ? "--project-new" : undefined,
|
|
1124
|
+
parsedAskPro.optionArgs.includes("--temporary") ? "--temporary" : undefined
|
|
1125
|
+
].find(Boolean);
|
|
1126
|
+
if (conflict) {
|
|
1127
|
+
throw new Error(`ask-pro cannot combine --continue with ${conflict}: continuing means sending into the conversation a previous consult is already in.`);
|
|
1128
|
+
}
|
|
1129
|
+
// Scope by the project this send would have used, so a follow-up cannot
|
|
1130
|
+
// land in another project's conversation.
|
|
1131
|
+
const continuationProject = explicitProject ?? (suppressProject ? undefined : browserDefaults?.project);
|
|
1132
|
+
const resolved = resolveContinuationThread({
|
|
1133
|
+
consults: (await targetStore.listSessionsReadOnly()).map((session) => ({
|
|
1134
|
+
taskId: session.task_id ?? "",
|
|
1135
|
+
...(session.thread ? { thread: session.thread } : {}),
|
|
1136
|
+
status: session.status,
|
|
1137
|
+
...(session.created_at ? { createdAt: session.created_at } : {})
|
|
1138
|
+
})),
|
|
1139
|
+
...(continuationProject ? { project: continuationProject } : {}),
|
|
1140
|
+
...(continueTaskId !== undefined ? { taskId: continueTaskId } : {})
|
|
1141
|
+
});
|
|
1142
|
+
if ("error" in resolved)
|
|
1143
|
+
throw new Error(resolved.error);
|
|
1144
|
+
continuedFromTaskId = resolved.target.taskId;
|
|
1145
|
+
// The thread pins the tab exactly as --target-url does, and carries its
|
|
1146
|
+
// own project with it - so the project step is suppressed below for the
|
|
1147
|
+
// same reason a pinned target suppresses it.
|
|
1148
|
+
normalizedTargetUrl = normalizeChatGptTargetUrl(resolved.target.thread);
|
|
1149
|
+
io.stderr(`progress: continuing ${resolved.target.taskId}`);
|
|
1150
|
+
}
|
|
1107
1151
|
const newChat = parsedAskPro.optionArgs.includes("--new-chat");
|
|
1108
1152
|
// A temporary chat is not saved, so there is nothing to come back to: the
|
|
1109
1153
|
// recovery path every timeout message points at cannot fetch it later.
|
|
@@ -1130,18 +1174,19 @@ export async function runAskProCommand(rest, io) {
|
|
|
1130
1174
|
}
|
|
1131
1175
|
const explicitProMode = explicitProModeRaw === undefined ? undefined : parseProMode(explicitProModeRaw);
|
|
1132
1176
|
const explicitEffort = explicitEffortRaw === undefined ? undefined : parseReasoningEffort(explicitEffortRaw);
|
|
1133
|
-
// Explicit per-ask flags override persisted defaults
|
|
1134
|
-
//
|
|
1135
|
-
// pinning --target-url suppresses a default project
|
|
1136
|
-
// from the confirmed tab
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1177
|
+
// Explicit per-ask flags override persisted defaults; resolveSelectionAxes
|
|
1178
|
+
// holds which default a given flag suppresses and why. The project axis is
|
|
1179
|
+
// separate: pinning --target-url suppresses a default project, because
|
|
1180
|
+
// entering one would navigate away from the confirmed tab.
|
|
1181
|
+
const selectionAxes = resolveSelectionAxes({
|
|
1182
|
+
explicit: {
|
|
1183
|
+
...(explicitModel !== undefined ? { model: explicitModel } : {}),
|
|
1184
|
+
...(explicitProMode !== undefined ? { proMode: explicitProMode } : {}),
|
|
1185
|
+
...(explicitEffort !== undefined ? { effort: explicitEffort } : {})
|
|
1186
|
+
},
|
|
1187
|
+
...(browserDefaults ? { defaults: browserDefaults } : {})
|
|
1188
|
+
});
|
|
1189
|
+
const selectionModel = selectionAxes.model;
|
|
1145
1190
|
const selectionProjectNew = explicitProjectNew;
|
|
1146
1191
|
// A persisted default project APPLIES under --new-chat: since 0.16.11 a
|
|
1147
1192
|
// fresh chat inside the project is exactly what "--new-chat + project"
|
|
@@ -1154,15 +1199,24 @@ export async function runAskProCommand(rest, io) {
|
|
|
1154
1199
|
// produced "composer did not rebind after entering project" - a temporary
|
|
1155
1200
|
// chat is never saved, a project chat is, and entering a project leaves
|
|
1156
1201
|
// temporary mode.
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1202
|
+
// A continuation sends into a thread that already lives in its project, so
|
|
1203
|
+
// the project step must not run - entering a project navigates AWAY from
|
|
1204
|
+
// the pinned thread and starts a new chat, which is the very failure
|
|
1205
|
+
// --continue exists to fix. An explicit --project on a continuation is the
|
|
1206
|
+
// SCOPE of the search, not an instruction to navigate.
|
|
1207
|
+
const selectionProject = continuedFromTaskId
|
|
1208
|
+
? undefined
|
|
1209
|
+
: explicitProject ??
|
|
1210
|
+
(normalizedTargetUrl || selectionProjectNew !== undefined || suppressProject || temporary
|
|
1211
|
+
? undefined
|
|
1212
|
+
: browserDefaults?.project);
|
|
1213
|
+
const selectionProMode = selectionAxes.proMode;
|
|
1214
|
+
const selectionEffort = selectionAxes.effort;
|
|
1215
|
+
const continuationScopeProject = continuedFromTaskId
|
|
1216
|
+
? explicitProject ?? (suppressProject ? undefined : browserDefaults?.project)
|
|
1217
|
+
: undefined;
|
|
1164
1218
|
const selectionMetadata = {
|
|
1165
|
-
...(selectionProject ? { project: selectionProject } : {}),
|
|
1219
|
+
...(selectionProject ?? continuationScopeProject ? { project: (selectionProject ?? continuationScopeProject) } : {}),
|
|
1166
1220
|
...(selectionProjectNew ? { project_new: selectionProjectNew } : {}),
|
|
1167
1221
|
...(selectionModel ? { model: selectionModel } : {}),
|
|
1168
1222
|
...(selectionProMode ? { pro_mode: selectionProMode } : {}),
|
|
@@ -1259,6 +1313,9 @@ export async function runAskProCommand(rest, io) {
|
|
|
1259
1313
|
port: browserPort,
|
|
1260
1314
|
prompt: bundle.sendText,
|
|
1261
1315
|
targetUrl: normalizedTargetUrl,
|
|
1316
|
+
// A thread prodex resolved from its own records is reached by
|
|
1317
|
+
// navigating; a --target-url the person confirmed is not moved.
|
|
1318
|
+
...(continuedFromTaskId ? { navigateToTargetUrl: true } : {}),
|
|
1262
1319
|
timeoutMs: browserTimeoutMs,
|
|
1263
1320
|
...(attachments.length > 0 ? { attachments } : {}),
|
|
1264
1321
|
...(tools.length > 0 ? { tools } : {}),
|
|
@@ -1307,10 +1364,7 @@ export async function runAskProCommand(rest, io) {
|
|
|
1307
1364
|
// "project not found" error). Local stdout/stderr keep it (useful to the
|
|
1308
1365
|
// operator), but the persisted task/session cross the MCP boundary, so
|
|
1309
1366
|
// scrub the project name there the same way provenance.project is redacted.
|
|
1310
|
-
const redactProject = (text) =>
|
|
1311
|
-
const name = selectionMetadata.project;
|
|
1312
|
-
return name ? text.split(name).join("<project>") : text;
|
|
1313
|
-
};
|
|
1367
|
+
const redactProject = (text) => redactProjectNames(text, [selectionMetadata.project, selectionMetadata.project_new]);
|
|
1314
1368
|
// Where the prompt actually landed beats where the caller aimed: with
|
|
1315
1369
|
// --new-chat there is no target url, and a blocker that started a run
|
|
1316
1370
|
// still has a thread worth handing back.
|
|
@@ -1359,13 +1413,21 @@ export async function runAskProCommand(rest, io) {
|
|
|
1359
1413
|
(consult.modelSlug ? ` - it answered as "${consult.modelSlug}"` : "") +
|
|
1360
1414
|
". Pin one with `prodex setup --model Pro` or pass --model/--effort.");
|
|
1361
1415
|
}
|
|
1362
|
-
//
|
|
1363
|
-
//
|
|
1364
|
-
//
|
|
1365
|
-
//
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1416
|
+
// Where the answer actually IS, checked rather than assumed: the project
|
|
1417
|
+
// id the composer bound to before typing, against the project the
|
|
1418
|
+
// answered thread belongs to. The requested name is intent - a send that
|
|
1419
|
+
// ended up elsewhere was recorded under the name of the place it never
|
|
1420
|
+
// reached.
|
|
1421
|
+
const continuationProjectId = continuedFromTaskId ? chatGptProjectIdFromUrl(normalizedTargetUrl) : undefined;
|
|
1422
|
+
const destination = destinationVerification({
|
|
1423
|
+
requestedProject: Boolean(selectionMetadata.project || selectionMetadata.project_new),
|
|
1424
|
+
...(consult.boundProjectId ?? continuationProjectId
|
|
1425
|
+
? { boundProjectId: (consult.boundProjectId ?? continuationProjectId) }
|
|
1426
|
+
: {}),
|
|
1427
|
+
...(consult.url ? { answeredUrl: consult.url } : {})
|
|
1428
|
+
});
|
|
1429
|
+
if (destination.warning)
|
|
1430
|
+
persistenceWarnings.push(destination.warning);
|
|
1369
1431
|
// Truncation and other send warnings must be visible at runtime, not
|
|
1370
1432
|
// only inside the persisted receipt: a caller who never opens .bridge
|
|
1371
1433
|
// would otherwise treat a cut-off answer as complete.
|
|
@@ -1373,6 +1435,13 @@ export async function runAskProCommand(rest, io) {
|
|
|
1373
1435
|
io.stderr(warning);
|
|
1374
1436
|
if (consult.modelSlug)
|
|
1375
1437
|
io.stderr(`model_used: ${consult.modelSlug}`);
|
|
1438
|
+
// Which conversation this followed, and where the answer actually landed.
|
|
1439
|
+
// The progress line that says it is filtered out of MCP notes, so an
|
|
1440
|
+
// agent asking for a follow-up had no way to know which thread it got -
|
|
1441
|
+
// or whether the answer is in the project it asked for.
|
|
1442
|
+
if (continuedFromTaskId)
|
|
1443
|
+
io.stderr(`continued_from: ${continuedFromTaskId}`);
|
|
1444
|
+
io.stderr(`destination: ${destination.destination}${destination.verified === undefined ? "" : ` verified=${destination.verified}`}`);
|
|
1376
1445
|
// "Can I count this as a Pro review?" - answered here rather than left
|
|
1377
1446
|
// for whoever reads the receipt to work out from two other fields.
|
|
1378
1447
|
const proVerified = proSelectionVerified({
|
|
@@ -1414,6 +1483,14 @@ export async function runAskProCommand(rest, io) {
|
|
|
1414
1483
|
// receipt used to record only what prodex asked for.
|
|
1415
1484
|
...(consult.modelSlug ? { model_used: consult.modelSlug } : {}),
|
|
1416
1485
|
...(proVerified !== undefined ? { pro_verified: proVerified } : {}),
|
|
1486
|
+
// Intent and evidence, kept apart. `selection` is what was asked
|
|
1487
|
+
// for; this is where the answer turned out to be, and whether
|
|
1488
|
+
// anything actually confirmed it.
|
|
1489
|
+
...(continuedFromTaskId ? { continued_from: continuedFromTaskId } : {}),
|
|
1490
|
+
destination: {
|
|
1491
|
+
observed: destination.destination,
|
|
1492
|
+
...(destination.verified !== undefined ? { verified: destination.verified } : {})
|
|
1493
|
+
},
|
|
1417
1494
|
warnings: persistenceWarnings
|
|
1418
1495
|
}
|
|
1419
1496
|
});
|
|
@@ -1458,6 +1535,11 @@ export async function runAskProCommand(rest, io) {
|
|
|
1458
1535
|
status: result.status,
|
|
1459
1536
|
thread: consult.url,
|
|
1460
1537
|
answer: result.summary,
|
|
1538
|
+
...(continuedFromTaskId ? { continued_from: continuedFromTaskId } : {}),
|
|
1539
|
+
destination: {
|
|
1540
|
+
observed: destination.destination,
|
|
1541
|
+
...(destination.verified !== undefined ? { verified: destination.verified } : {})
|
|
1542
|
+
},
|
|
1461
1543
|
warnings: persistenceWarnings
|
|
1462
1544
|
}, null, 2));
|
|
1463
1545
|
}
|
|
@@ -1579,6 +1661,8 @@ export async function performBrowserConsultForMcp(cwd, input, onProgress) {
|
|
|
1579
1661
|
...(input.attach ?? []).flatMap((file) => ["--attach", file]),
|
|
1580
1662
|
...(input.tools ?? []).flatMap((tool) => ["--tool", tool]),
|
|
1581
1663
|
...(input.new_chat ? ["--new-chat"] : []),
|
|
1664
|
+
...(input.continue_thread ? ["--continue"] : []),
|
|
1665
|
+
...(input.continue_task !== undefined ? ["--continue-task", input.continue_task] : []),
|
|
1582
1666
|
...(input.allow_model_fallback ? ["--allow-model-fallback"] : []),
|
|
1583
1667
|
"--",
|
|
1584
1668
|
input.prompt
|
|
@@ -1823,6 +1907,54 @@ export function temporaryProjectConflict(input) {
|
|
|
1823
1907
|
return (`--temporary and --project cannot be combined: a temporary chat is never saved, and a chat inside a project is. ` +
|
|
1824
1908
|
`Drop --temporary to send into "${input.explicitProject}", or drop --project to send a throwaway chat.`);
|
|
1825
1909
|
}
|
|
1910
|
+
/**
|
|
1911
|
+
* What a send actually selects, given the per-ask flags and the persisted
|
|
1912
|
+
* defaults.
|
|
1913
|
+
*
|
|
1914
|
+
* Explicit flags beat persisted defaults - except that a saved effort used to
|
|
1915
|
+
* beat an explicit `--model Pro`, because Pro IS the top step of the effort
|
|
1916
|
+
* slider: setting any other step deselects it. So the send ran at the saved
|
|
1917
|
+
* effort, the answer came from a lesser model, and the warning about it told
|
|
1918
|
+
* the caller to clear a saved MODEL default, which was not what overrode
|
|
1919
|
+
* anything. Asking for Pro on the model axis now suppresses a saved effort
|
|
1920
|
+
* exactly as `--effort` suppresses a saved model. A saved pro_mode survives:
|
|
1921
|
+
* it only refines Pro.
|
|
1922
|
+
*
|
|
1923
|
+
* Combining an explicit model with an explicit effort is left alone - that is
|
|
1924
|
+
* the caller saying both out loud, and the picker warns about it.
|
|
1925
|
+
*/
|
|
1926
|
+
export function resolveSelectionAxes(input) {
|
|
1927
|
+
const { explicit } = input;
|
|
1928
|
+
const defaults = input.defaults;
|
|
1929
|
+
const reasoningAxisChosen = explicit.proMode !== undefined || explicit.effort !== undefined;
|
|
1930
|
+
const explicitlyPro = explicit.model !== undefined && namesPro(explicit.model);
|
|
1931
|
+
const model = explicit.model ?? (explicit.effort !== undefined ? undefined : defaults?.model);
|
|
1932
|
+
const proMode = explicit.proMode ?? (reasoningAxisChosen ? undefined : defaults?.pro_mode);
|
|
1933
|
+
const effort = explicit.effort ?? (reasoningAxisChosen || explicitlyPro ? undefined : defaults?.effort);
|
|
1934
|
+
return {
|
|
1935
|
+
...(model !== undefined ? { model } : {}),
|
|
1936
|
+
...(proMode !== undefined ? { proMode } : {}),
|
|
1937
|
+
...(effort !== undefined ? { effort } : {})
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Scrub project names out of text that gets persisted.
|
|
1942
|
+
*
|
|
1943
|
+
* Both names count. A send that CREATES its project names it in exactly the
|
|
1944
|
+
* same failures - the binding refusal, the composer that never appeared, the
|
|
1945
|
+
* sidebar click that missed - and only the requested name was ever scrubbed,
|
|
1946
|
+
* so those records kept a real project name while the equivalent record for an
|
|
1947
|
+
* existing project did not.
|
|
1948
|
+
*/
|
|
1949
|
+
export function redactProjectNames(text, names) {
|
|
1950
|
+
let redacted = text;
|
|
1951
|
+
// Longest first: a project named "Notes" inside "Notes Archive" would
|
|
1952
|
+
// otherwise leave "<project> Archive" behind.
|
|
1953
|
+
for (const name of [...names].filter((name) => Boolean(name)).sort((a, b) => b.length - a.length)) {
|
|
1954
|
+
redacted = redacted.split(name).join("<project>");
|
|
1955
|
+
}
|
|
1956
|
+
return redacted;
|
|
1957
|
+
}
|
|
1826
1958
|
export function browserSendBlockerFromError(error) {
|
|
1827
1959
|
const blocker = typeof error === "object" && error !== null && "blocker" in error ? error.blocker : undefined;
|
|
1828
1960
|
if (typeof blocker === "object" &&
|
package/dist/config.js
CHANGED
|
@@ -208,22 +208,61 @@ export function envBrowserDefaults() {
|
|
|
208
208
|
});
|
|
209
209
|
return (model || project) && freeText.success ? freeText.data : undefined;
|
|
210
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Combine the global env defaults with this repo's.
|
|
213
|
+
*
|
|
214
|
+
* The project is independent of the rest, so the repo's wins that field and
|
|
215
|
+
* the env fills it in. The model and the two reasoning fields are NOT
|
|
216
|
+
* independent: an effort is a step ChatGPT deselects the model to reach, and a
|
|
217
|
+
* pro_mode only refines Pro. Merging those field-by-field built a request
|
|
218
|
+
* neither side asked for - a repo pinning `model: Pro` next to
|
|
219
|
+
* PRODEX_DEFAULT_EFFORT ran at the effort and dropped Pro - so whichever side
|
|
220
|
+
* names the reasoning selection provides all of it.
|
|
221
|
+
*/
|
|
222
|
+
export function mergeBrowserDefaultSources(env, repo) {
|
|
223
|
+
if (!repo && !env)
|
|
224
|
+
return undefined;
|
|
225
|
+
const namesSelection = (defaults) => Boolean(defaults && (defaults.model !== undefined || defaults.pro_mode !== undefined || defaults.effort !== undefined));
|
|
226
|
+
const selection = namesSelection(repo) ? repo : env;
|
|
227
|
+
const merged = {
|
|
228
|
+
...(selection?.model !== undefined ? { model: selection.model } : {}),
|
|
229
|
+
...(selection?.pro_mode !== undefined ? { pro_mode: selection.pro_mode } : {}),
|
|
230
|
+
...(selection?.effort !== undefined ? { effort: selection.effort } : {})
|
|
231
|
+
};
|
|
232
|
+
const project = repo?.project ?? env?.project;
|
|
233
|
+
if (project !== undefined)
|
|
234
|
+
merged.project = project;
|
|
235
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
236
|
+
}
|
|
211
237
|
// Read persisted browser-selection defaults without failing when the local
|
|
212
238
|
// config is absent or unrelated to this cwd (defaults are optional convenience).
|
|
213
|
-
//
|
|
214
|
-
//
|
|
239
|
+
// The repo's config provides the project and, if it names any of them, the
|
|
240
|
+
// reasoning selection; PRODEX_DEFAULT_* env vars are the global fallback so a
|
|
241
|
+
// pinned default project/model applies from any cwd.
|
|
215
242
|
export async function loadBrowserDefaults(cwd) {
|
|
216
243
|
const env = envBrowserDefaults();
|
|
217
244
|
let repo;
|
|
218
245
|
try {
|
|
219
246
|
repo = (await loadLocalConfig(cwd)).browser_defaults;
|
|
220
247
|
}
|
|
221
|
-
catch {
|
|
248
|
+
catch (error) {
|
|
249
|
+
// Having no config is the ordinary case - browser sends work without one -
|
|
250
|
+
// and stays silent. A config that EXISTS and cannot be read is a different
|
|
251
|
+
// thing, and swallowing it was the quiet failure: the defaults it pins
|
|
252
|
+
// stop applying with nothing said, so a consult that should have landed in
|
|
253
|
+
// a project lands in the general chat and looks like it worked.
|
|
254
|
+
if (!isMissingFileError(error)) {
|
|
255
|
+
// Say only what is true. Passing the flags explicitly does NOT get past
|
|
256
|
+
// this - the config is read before any of them are looked at - and
|
|
257
|
+
// advice that does not work is worse than none. Moving the file aside
|
|
258
|
+
// does work: a repo with no config has no defaults and sends fine.
|
|
259
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)} Until then prodex will not apply the browser defaults ` +
|
|
260
|
+
`pinned there (project, model), and every send from this repo stops here. Move .bridge/config.local.json aside ` +
|
|
261
|
+
`if this repo does not need the HTTP MCP surface.`, { cause: error });
|
|
262
|
+
}
|
|
222
263
|
repo = undefined;
|
|
223
264
|
}
|
|
224
|
-
|
|
225
|
-
return undefined;
|
|
226
|
-
return { ...(env ?? {}), ...(repo ?? {}) };
|
|
265
|
+
return mergeBrowserDefaultSources(env, repo);
|
|
227
266
|
}
|
|
228
267
|
export function getTokenExpiryStatus(config, now = new Date()) {
|
|
229
268
|
if (!config.token_expires_at) {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which conversation a follow-up consult belongs to.
|
|
3
|
+
*
|
|
4
|
+
* "Continue" used to mean "whatever thread the shared browser tab is showing",
|
|
5
|
+
* which is not a conversation anyone named: another session, or a person
|
|
6
|
+
* clicking around, moves it. Worse, a pinned project navigates AWAY from that
|
|
7
|
+
* tab before every send, so on a machine with a default project every consult
|
|
8
|
+
* started a fresh thread while the tool description promised the opposite
|
|
9
|
+
* (measured: two consecutive sends into one project landed in two different
|
|
10
|
+
* /c/ threads).
|
|
11
|
+
*
|
|
12
|
+
* prodex already writes down where each consult landed. That record - not the
|
|
13
|
+
* tab - is what a follow-up should resolve against. Pure on purpose: the
|
|
14
|
+
* reading lives in the command.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The project name as it appears inside a thread URL.
|
|
18
|
+
*
|
|
19
|
+
* Measured: a project named "prodex-smoke-project" answers on
|
|
20
|
+
* `/g/g-p-<id>-prodex-smoke-project/c/<id>`, and "Codex" on
|
|
21
|
+
* `/g/g-p-<id>-codex/c/<id>` - the name lowercased, with runs of anything else
|
|
22
|
+
* collapsed to a single dash.
|
|
23
|
+
*/
|
|
24
|
+
export function chatGptProjectSlug(name) {
|
|
25
|
+
return name
|
|
26
|
+
.trim()
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
29
|
+
.replace(/^-+|-+$/g, "");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether a recorded URL is a conversation someone can go back to.
|
|
33
|
+
*
|
|
34
|
+
* Not everything prodex records as a "thread" is one. Measured across this
|
|
35
|
+
* machine's records: six of them are `https://chatgpt.com/?temporary-chat=true`
|
|
36
|
+
* - a temporary chat is never saved, so returning to that URL opens a fresh
|
|
37
|
+
* empty one. Continuing into it would look like a follow-up and be a new
|
|
38
|
+
* conversation with none of the context, which is the failure --continue
|
|
39
|
+
* exists to remove rather than reproduce.
|
|
40
|
+
*/
|
|
41
|
+
export function isChatGptConversationUrl(threadUrl) {
|
|
42
|
+
return /\/c\/[^/?#]+/.test(threadUrl);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The project ids these recorded threads have been seen using, by project name.
|
|
46
|
+
*
|
|
47
|
+
* Some thread URLs carry the project name after its id and some carry only the
|
|
48
|
+
* id (measured: three of this machine's hundred-odd records). The named ones
|
|
49
|
+
* teach the mapping, so the bare ones can still be recognised instead of being
|
|
50
|
+
* invisible to every follow-up - which would quietly continue an OLDER
|
|
51
|
+
* conversation while the newest one sat unmatched.
|
|
52
|
+
*/
|
|
53
|
+
export function projectIdsByName(threadUrls) {
|
|
54
|
+
const byName = new Map();
|
|
55
|
+
for (const url of threadUrls) {
|
|
56
|
+
const match = /\/g\/g-p-([0-9a-f]+)-([^/?#]+)/i.exec(url);
|
|
57
|
+
if (!match)
|
|
58
|
+
continue;
|
|
59
|
+
const name = match[2].toLowerCase();
|
|
60
|
+
const ids = byName.get(name) ?? new Set();
|
|
61
|
+
ids.add(match[1].toLowerCase());
|
|
62
|
+
byName.set(name, ids);
|
|
63
|
+
}
|
|
64
|
+
return byName;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Whether a recorded thread belongs to the project this send is for.
|
|
68
|
+
*
|
|
69
|
+
* A send with no project continues only a thread that belongs to no project,
|
|
70
|
+
* so a follow-up meant for the general chat cannot walk into a project - and a
|
|
71
|
+
* project's follow-up cannot land in another project's conversation.
|
|
72
|
+
*/
|
|
73
|
+
export function threadMatchesProject(threadUrl, project, knownProjectIds) {
|
|
74
|
+
const projectSegment = /\/g\/(g-p-[^/?#]+)/.exec(threadUrl)?.[1];
|
|
75
|
+
if (!project)
|
|
76
|
+
return projectSegment === undefined;
|
|
77
|
+
if (!projectSegment)
|
|
78
|
+
return false;
|
|
79
|
+
const slug = chatGptProjectSlug(project);
|
|
80
|
+
// The id comes first and the name follows it, so an exact suffix match keeps
|
|
81
|
+
// "notes" from answering for "notes-archive".
|
|
82
|
+
if (slug && projectSegment.toLowerCase().endsWith(`-${slug}`))
|
|
83
|
+
return true;
|
|
84
|
+
// No name in the URL: fall back to the id this project has been seen using.
|
|
85
|
+
// Nothing is guessed - the id came from another record of the same project -
|
|
86
|
+
// and it survives a rename, which the name comparison cannot.
|
|
87
|
+
const id = /^g-p-([0-9a-f]+)/i.exec(projectSegment)?.[1]?.toLowerCase();
|
|
88
|
+
return Boolean(id && knownProjectIds?.has(id));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The thread a follow-up should continue, or why it cannot be resolved.
|
|
92
|
+
*
|
|
93
|
+
* Naming a task wins over the search, because the caller who names one knows
|
|
94
|
+
* which conversation they mean. Otherwise it is the most recent consult that
|
|
95
|
+
* finished, in this project - fail-closed when there is none, since guessing
|
|
96
|
+
* the conversation is the failure this exists to prevent.
|
|
97
|
+
*/
|
|
98
|
+
export function resolveContinuationThread(input) {
|
|
99
|
+
const withThread = input.consults.filter((consult) => consult.thread && isChatGptConversationUrl(consult.thread));
|
|
100
|
+
if (input.taskId) {
|
|
101
|
+
const named = withThread.find((consult) => consult.taskId === input.taskId);
|
|
102
|
+
const recordedButUnusable = !named && input.consults.some((consult) => consult.taskId === input.taskId);
|
|
103
|
+
if (recordedButUnusable) {
|
|
104
|
+
return {
|
|
105
|
+
error: `Consult "${input.taskId}" has no conversation to continue - it was a temporary chat, or it never reached one. ` +
|
|
106
|
+
`A temporary chat is never saved, so there is nothing to go back to.`
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
if (!named) {
|
|
110
|
+
return {
|
|
111
|
+
error: `No recorded consult thread for "${input.taskId}". List what is here with \`prodex pro list\`, ` +
|
|
112
|
+
`or pass the thread itself with --target-url --confirm-target.`
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return { target: { taskId: named.taskId, thread: named.thread } };
|
|
116
|
+
}
|
|
117
|
+
const knownProjectIds = input.project
|
|
118
|
+
? projectIdsByName(withThread.map((consult) => consult.thread)).get(chatGptProjectSlug(input.project))
|
|
119
|
+
: undefined;
|
|
120
|
+
const candidates = withThread
|
|
121
|
+
.filter((consult) => consult.status === "done")
|
|
122
|
+
.filter((consult) => threadMatchesProject(consult.thread, input.project, knownProjectIds))
|
|
123
|
+
.sort((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? ""));
|
|
124
|
+
const latest = candidates[0];
|
|
125
|
+
if (!latest) {
|
|
126
|
+
const where = input.project ? `project "${input.project}"` : "a chat outside any project";
|
|
127
|
+
return {
|
|
128
|
+
error: `No finished consult of this repo has a thread in ${where} to continue. ` +
|
|
129
|
+
`Send once without --continue, or name a consult with --continue-task <task_id>.`
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { target: { taskId: latest.taskId, thread: latest.thread } };
|
|
133
|
+
}
|
package/dist/mcp.js
CHANGED
|
@@ -162,7 +162,7 @@ export function createServer(cwd = process.cwd(), options = {}) {
|
|
|
162
162
|
const browserConsult = options.browserConsult;
|
|
163
163
|
if (browserConsult) {
|
|
164
164
|
server.registerTool("pro_consult", {
|
|
165
|
-
description: "Ask the user's logged-in ChatGPT (Pro) in the visible browser and wait for the full answer. This drives a real browser send: it can take minutes (Pro extended reasoning), is human-paced, and records a durable receipt under .bridge/. Requires a running `prodex pro browser login` session. By
|
|
165
|
+
description: "Ask the user's logged-in ChatGPT (Pro) in the visible browser and wait for the full answer. This drives a real browser send: it can take minutes (Pro extended reasoning), is human-paced, and records a durable receipt under .bridge/. Requires a running `prodex pro browser login` session. By default the consult continues in whatever thread the browser tab is showing - EXCEPT when a project applies (passed here, or pinned as a saved default), because entering a project starts a new chat in it. To follow up on a previous consult, pass continue_thread:true: it resolves the thread from prodex's own records - the newest finished consult of the same project - instead of trusting the shared tab, and continue_task with a task_id names one exactly. Pass new_chat:true to start a fresh thread for a genuinely new topic. If the thread is still generating a previous answer, the send automatically queues behind it (up to the timeout budget) - long 'tab busy' progress is normal, not stuck. `project` and `model` come from saved defaults (per-repo config, or PRODEX_DEFAULT_PROJECT / PRODEX_DEFAULT_MODEL env vars) when omitted - do NOT pass them per-call unless deliberately overriding. Returns task_id, thread URL, and the answer text.",
|
|
166
166
|
inputSchema: {
|
|
167
167
|
prompt: McpBridgeTextSchema.min(1),
|
|
168
168
|
model: McpShortTextSchema.optional(),
|
|
@@ -187,7 +187,17 @@ export function createServer(cwd = process.cwd(), options = {}) {
|
|
|
187
187
|
new_chat: z
|
|
188
188
|
.boolean()
|
|
189
189
|
.optional()
|
|
190
|
-
.describe("Start a fresh thread
|
|
190
|
+
.describe("Start a fresh thread for a new topic."),
|
|
191
|
+
continue_thread: z
|
|
192
|
+
.boolean()
|
|
193
|
+
.optional()
|
|
194
|
+
.describe("Follow up inside the conversation a previous consult is already in, resolved from prodex's records: the newest finished consult of the same project. This is the reliable way to keep a follow-up in one conversation - the tab is shared, and a project default starts a new chat on every send. Fails rather than guessing when this project has no finished consult yet."),
|
|
195
|
+
continue_task: z
|
|
196
|
+
.string()
|
|
197
|
+
.min(1)
|
|
198
|
+
.max(200)
|
|
199
|
+
.optional()
|
|
200
|
+
.describe("Continue one NAMED past consult by its task_id, when the newest one is not the conversation meant."),
|
|
191
201
|
allow_model_fallback: z
|
|
192
202
|
.boolean()
|
|
193
203
|
.optional()
|