wtagent 0.1.0-alpha.3 → 0.1.0-alpha.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/README.md +8 -4
- package/package.json +1 -1
- package/src/browser/chatgpt-web-adapter.js +81 -6
- package/src/browser/mode-selection.js +30 -8
- package/src/cli/main.js +37 -8
- package/src/cli/mode-choice.js +25 -0
- package/src/cli/prompt-input.js +18 -4
- package/src/runtime/agent-runtime.js +2 -9
- package/src/shared/limits.js +0 -1
package/README.md
CHANGED
|
@@ -35,14 +35,14 @@ WTAgent 把 GPT 网页聊天连接到本地工具,提供类似 Codex 的 CLI A
|
|
|
35
35
|
需要 Node.js 20.17+ 和 Chrome/Chromium。
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
npm install -g wtagent
|
|
38
|
+
npm install -g wtagent --registry=https://registry.npmjs.org/
|
|
39
39
|
wtagent login
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Windows PowerShell:
|
|
43
43
|
|
|
44
44
|
```powershell
|
|
45
|
-
npm install -g wtagent
|
|
45
|
+
npm install -g wtagent --registry=https://registry.npmjs.org/
|
|
46
46
|
wtagent doctor
|
|
47
47
|
wtagent login
|
|
48
48
|
```
|
|
@@ -54,6 +54,8 @@ cd my-project
|
|
|
54
54
|
wtagent
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
启动后先选择 ChatGPT 模式:`Pro` 会为新对话选择 Pro,`Current` 会保留网页当前设置且不执行模型切换。非交互调用默认使用 `Current`;需要 Pro 时可显式传入 `--mode Pro`。
|
|
58
|
+
|
|
57
59
|
也可以直接附带任务:
|
|
58
60
|
|
|
59
61
|
```bash
|
|
@@ -100,14 +102,14 @@ Boundaries:
|
|
|
100
102
|
Requires Node.js 20.17+ and Chrome/Chromium.
|
|
101
103
|
|
|
102
104
|
```bash
|
|
103
|
-
npm install -g wtagent
|
|
105
|
+
npm install -g wtagent --registry=https://registry.npmjs.org/
|
|
104
106
|
wtagent login
|
|
105
107
|
```
|
|
106
108
|
|
|
107
109
|
Windows PowerShell:
|
|
108
110
|
|
|
109
111
|
```powershell
|
|
110
|
-
npm install -g wtagent
|
|
112
|
+
npm install -g wtagent --registry=https://registry.npmjs.org/
|
|
111
113
|
wtagent doctor
|
|
112
114
|
wtagent login
|
|
113
115
|
```
|
|
@@ -119,6 +121,8 @@ cd my-project
|
|
|
119
121
|
wtagent
|
|
120
122
|
```
|
|
121
123
|
|
|
124
|
+
At startup, choose a ChatGPT mode: `Pro` selects Pro for the new conversation, while `Current` keeps the current web setting without changing it. Non-interactive runs default to `Current`; pass `--mode Pro` to opt in explicitly.
|
|
125
|
+
|
|
122
126
|
Or provide the first task directly:
|
|
123
127
|
|
|
124
128
|
```bash
|
package/package.json
CHANGED
|
@@ -5,6 +5,8 @@ import { discoverChromeExecutable } from "../platform/chrome-discovery.js";
|
|
|
5
5
|
import { ensureDirectory } from "../platform/paths.js";
|
|
6
6
|
import { BrowserAdapterError } from "../shared/errors.js";
|
|
7
7
|
import {
|
|
8
|
+
chooseModeOption,
|
|
9
|
+
labelMatchesToken,
|
|
8
10
|
runModeSelection,
|
|
9
11
|
slugMatchesToken,
|
|
10
12
|
normalizeToken,
|
|
@@ -236,6 +238,15 @@ export class ChatGPTWebAdapter {
|
|
|
236
238
|
const page = this.page;
|
|
237
239
|
return {
|
|
238
240
|
alreadyOnMode: async (requested) => {
|
|
241
|
+
const token = normalizeToken(requested);
|
|
242
|
+
const switcher = await this.#findModelSwitcher();
|
|
243
|
+
const switcherLabel = switcher
|
|
244
|
+
? await switcher.innerText().catch(() => "")
|
|
245
|
+
: "";
|
|
246
|
+
if (labelMatchesToken(switcherLabel, token)) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
239
250
|
const messages = this.#assistantMessages();
|
|
240
251
|
if (await messages.count() === 0) {
|
|
241
252
|
return false;
|
|
@@ -243,17 +254,24 @@ export class ChatGPTWebAdapter {
|
|
|
243
254
|
const slug = await messages.last()
|
|
244
255
|
.getAttribute("data-message-model-slug")
|
|
245
256
|
.catch(() => null);
|
|
246
|
-
return Boolean(slug) && slugMatchesToken(slug,
|
|
257
|
+
return Boolean(slug) && slugMatchesToken(slug, token);
|
|
247
258
|
},
|
|
248
259
|
hasSwitcher: async () => Boolean(await this.#findModelSwitcher()),
|
|
249
|
-
openMenu: async () => {
|
|
260
|
+
openMenu: async (requested) => {
|
|
250
261
|
const switcher = await this.#findModelSwitcher();
|
|
251
262
|
if (switcher) {
|
|
252
|
-
await switcher.click(
|
|
263
|
+
const opened = await switcher.click({ timeout: 5_000 })
|
|
264
|
+
.then(() => true)
|
|
265
|
+
.catch(() => false);
|
|
266
|
+
if (!opened) {
|
|
267
|
+
await switcher.click({ force: true, timeout: 5_000 })
|
|
268
|
+
.catch(() => null);
|
|
269
|
+
}
|
|
253
270
|
// Wait for the Radix menu portal to attach before reading options.
|
|
254
|
-
await page.locator('[role="menu"] [role="menuitem"], [role="menu"] [role="menuitemradio"]')
|
|
271
|
+
await page.locator('[role="menu"]:visible [role="menuitem"]:visible, [role="menu"]:visible [role="menuitemradio"]:visible')
|
|
255
272
|
.first().waitFor({ state: "visible", timeout: 5_000 })
|
|
256
273
|
.catch(() => null);
|
|
274
|
+
await this.#revealNestedModeOption(requested);
|
|
257
275
|
}
|
|
258
276
|
},
|
|
259
277
|
readOptions: async () => this.#readModeOptions(),
|
|
@@ -273,6 +291,32 @@ export class ChatGPTWebAdapter {
|
|
|
273
291
|
.catch(() => false);
|
|
274
292
|
return ok;
|
|
275
293
|
},
|
|
294
|
+
waitSelected: async (requested) => {
|
|
295
|
+
const token = normalizeToken(requested);
|
|
296
|
+
const deadline = Date.now() + 5_000;
|
|
297
|
+
while (Date.now() < deadline) {
|
|
298
|
+
const switcher = await this.#findModelSwitcher(500);
|
|
299
|
+
const label = switcher
|
|
300
|
+
? await switcher.innerText().catch(() => "")
|
|
301
|
+
: "";
|
|
302
|
+
if (labelMatchesToken(label, token)) {
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const selectedOption = (await this.#readModeOptions()).find(
|
|
307
|
+
(option) => option.selected
|
|
308
|
+
&& (
|
|
309
|
+
slugMatchesToken(option.slug, token)
|
|
310
|
+
|| labelMatchesToken(option.label, token)
|
|
311
|
+
),
|
|
312
|
+
);
|
|
313
|
+
if (selectedOption) {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
await page.waitForTimeout(100);
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
},
|
|
276
320
|
closeMenu: async () => {
|
|
277
321
|
await page.keyboard.press("Escape").catch(() => null);
|
|
278
322
|
await page.waitForTimeout(150);
|
|
@@ -283,8 +327,36 @@ export class ChatGPTWebAdapter {
|
|
|
283
327
|
|
|
284
328
|
#modeOptionLocators() {
|
|
285
329
|
return this.page.locator(
|
|
286
|
-
'[role="menu"] [role="menuitemradio"],
|
|
330
|
+
'[role="menu"]:visible [role="menuitemradio"]:visible, '
|
|
331
|
+
+ '[role="menu"]:visible [role="menuitem"]:visible',
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async #revealNestedModeOption(requested) {
|
|
336
|
+
const present = (options) => {
|
|
337
|
+
const choice = chooseModeOption(options, requested);
|
|
338
|
+
return choice.status !== "unavailable";
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
let options = await this.#readModeOptions();
|
|
342
|
+
if (present(options)) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const submenuTriggers = this.page.locator(
|
|
347
|
+
'[role="menu"]:visible [role="menuitem"][aria-haspopup="menu"]:visible',
|
|
287
348
|
);
|
|
349
|
+
const count = await submenuTriggers.count().catch(() => 0);
|
|
350
|
+
for (let index = 0; index < count; index += 1) {
|
|
351
|
+
const trigger = submenuTriggers.nth(index);
|
|
352
|
+
await trigger.dispatchEvent("pointermove", { pointerType: "mouse" })
|
|
353
|
+
.catch(() => null);
|
|
354
|
+
await this.page.waitForTimeout(250);
|
|
355
|
+
options = await this.#readModeOptions();
|
|
356
|
+
if (present(options)) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
288
360
|
}
|
|
289
361
|
|
|
290
362
|
// Enumerates the open menu's options into { index, slug, label, disabled }.
|
|
@@ -296,13 +368,14 @@ export class ChatGPTWebAdapter {
|
|
|
296
368
|
const options = [];
|
|
297
369
|
for (let index = 0; index < count; index += 1) {
|
|
298
370
|
const item = locator.nth(index);
|
|
299
|
-
const [testId, dataValue, dataTestValue, id, ariaDisabled, dataDisabled, dataState, label] =
|
|
371
|
+
const [testId, dataValue, dataTestValue, id, ariaDisabled, ariaChecked, dataDisabled, dataState, label] =
|
|
300
372
|
await Promise.all([
|
|
301
373
|
item.getAttribute("data-testid").catch(() => null),
|
|
302
374
|
item.getAttribute("data-value").catch(() => null),
|
|
303
375
|
item.getAttribute("data-test-value").catch(() => null),
|
|
304
376
|
item.getAttribute("id").catch(() => null),
|
|
305
377
|
item.getAttribute("aria-disabled").catch(() => null),
|
|
378
|
+
item.getAttribute("aria-checked").catch(() => null),
|
|
306
379
|
item.getAttribute("data-disabled").catch(() => null),
|
|
307
380
|
item.getAttribute("data-state").catch(() => null),
|
|
308
381
|
item.innerText().catch(() => ""),
|
|
@@ -313,11 +386,13 @@ export class ChatGPTWebAdapter {
|
|
|
313
386
|
|| dataDisabled === ""
|
|
314
387
|
|| dataState === "disabled"
|
|
315
388
|
|| !await item.isEnabled().catch(() => true);
|
|
389
|
+
const selected = ariaChecked === "true" || dataState === "checked";
|
|
316
390
|
options.push({
|
|
317
391
|
index,
|
|
318
392
|
slug,
|
|
319
393
|
label: (label ?? "").trim(),
|
|
320
394
|
disabled,
|
|
395
|
+
selected,
|
|
321
396
|
});
|
|
322
397
|
}
|
|
323
398
|
return options;
|
|
@@ -38,16 +38,17 @@ export function slugMatchesToken(slug, token) {
|
|
|
38
38
|
return false;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function labelMatchesToken(label, token) {
|
|
41
|
+
export function labelMatchesToken(label, token) {
|
|
42
42
|
return String(label ?? "")
|
|
43
43
|
.split(/\r?\n/)
|
|
44
44
|
.some((line) => normalizeToken(line) === token);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// Given the enumerated menu options and the requested mode, decide what to do.
|
|
48
|
-
// options: [{ index, slug, label, disabled }] in DOM order.
|
|
48
|
+
// options: [{ index, slug, label, disabled, selected }] in DOM order.
|
|
49
49
|
// Returns { status, targetIndex, selectedLabel, reason }.
|
|
50
50
|
// status "select" -> requested mode is available; click it.
|
|
51
|
+
// status "already" -> requested mode is already selected.
|
|
51
52
|
// status "fallback" -> requested mode is limited/disabled; click
|
|
52
53
|
// the option immediately before it.
|
|
53
54
|
// status "unavailable" -> requested mode is not present in the menu.
|
|
@@ -74,6 +75,14 @@ export function chooseModeOption(options, requested) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
const pro = options[proIndex];
|
|
78
|
+
if (pro.selected) {
|
|
79
|
+
return {
|
|
80
|
+
status: "already",
|
|
81
|
+
targetIndex: null,
|
|
82
|
+
selectedLabel: pro.label || requested,
|
|
83
|
+
reason: `Already using ${requested}.`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
77
86
|
if (!pro.disabled) {
|
|
78
87
|
return {
|
|
79
88
|
status: "select",
|
|
@@ -112,9 +121,10 @@ export function chooseModeOption(options, requested) {
|
|
|
112
121
|
// port: {
|
|
113
122
|
// alreadyOnMode(requested) -> bool,
|
|
114
123
|
// hasSwitcher() -> bool,
|
|
115
|
-
// openMenu()
|
|
116
|
-
// readOptions() -> [{index, slug, label, disabled}],
|
|
124
|
+
// openMenu(requested) -> void,
|
|
125
|
+
// readOptions() -> [{index, slug, label, disabled, selected}],
|
|
117
126
|
// clickOption(index) -> bool (click landed),
|
|
127
|
+
// waitSelected(requested) -> bool (picker reflects the requested mode),
|
|
118
128
|
// waitClosed() -> bool (menu closed after click),
|
|
119
129
|
// closeMenu() -> void,
|
|
120
130
|
// writeDiagnostics(label) -> void,
|
|
@@ -146,7 +156,7 @@ export async function runModeSelection(port, requested, { maxAttempts = 2 } = {}
|
|
|
146
156
|
|
|
147
157
|
let lastReason = "";
|
|
148
158
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
149
|
-
await port.openMenu();
|
|
159
|
+
await port.openMenu(requested);
|
|
150
160
|
const options = await port.readOptions();
|
|
151
161
|
|
|
152
162
|
// The Radix menu populates asynchronously; an empty read is a race, retry.
|
|
@@ -158,13 +168,25 @@ export async function runModeSelection(port, requested, { maxAttempts = 2 } = {}
|
|
|
158
168
|
|
|
159
169
|
const choice = chooseModeOption(options, requested);
|
|
160
170
|
|
|
171
|
+
if (choice.status === "already") {
|
|
172
|
+
await port.closeMenu();
|
|
173
|
+
return { ...choice, requested, attempts: attempt };
|
|
174
|
+
}
|
|
175
|
+
|
|
161
176
|
if (choice.status === "select" || choice.status === "fallback") {
|
|
162
177
|
const clicked = await port.clickOption(choice.targetIndex);
|
|
163
|
-
const
|
|
164
|
-
|
|
178
|
+
const expectedMode = choice.status === "fallback"
|
|
179
|
+
? choice.selectedLabel
|
|
180
|
+
: requested;
|
|
181
|
+
const selected = clicked && (
|
|
182
|
+
typeof port.waitSelected === "function"
|
|
183
|
+
? await port.waitSelected(expectedMode)
|
|
184
|
+
: await port.waitClosed()
|
|
185
|
+
);
|
|
186
|
+
if (selected) {
|
|
165
187
|
return { ...choice, requested, attempts: attempt };
|
|
166
188
|
}
|
|
167
|
-
lastReason = "The option click did not
|
|
189
|
+
lastReason = "The option click did not change the selected mode.";
|
|
168
190
|
await port.closeMenu();
|
|
169
191
|
continue;
|
|
170
192
|
}
|
package/src/cli/main.js
CHANGED
|
@@ -28,10 +28,16 @@ import { extractAtMentions } from "./at-files.js";
|
|
|
28
28
|
import {
|
|
29
29
|
classifyChatInput,
|
|
30
30
|
promptForText,
|
|
31
|
+
promptForSelect,
|
|
31
32
|
readChatMessage,
|
|
32
33
|
ShellChatInput,
|
|
33
34
|
} from "./prompt-input.js";
|
|
34
35
|
import { createRenderer } from "./render-events.js";
|
|
36
|
+
import {
|
|
37
|
+
CHATGPT_MODE_CHOICES,
|
|
38
|
+
modeFromPromptChoice,
|
|
39
|
+
normalizeConfiguredMode,
|
|
40
|
+
} from "./mode-choice.js";
|
|
35
41
|
|
|
36
42
|
function resolveRuntimePaths(options) {
|
|
37
43
|
const appDataDir = path.resolve(options.home ?? getAppDataDir());
|
|
@@ -401,13 +407,34 @@ async function runAgent(taskParts, options) {
|
|
|
401
407
|
const interactive = !options.once && process.stdin.isTTY && process.stdout.isTTY;
|
|
402
408
|
const chatInput = interactive ? new ShellChatInput() : null;
|
|
403
409
|
|
|
410
|
+
if (interactive) {
|
|
411
|
+
printChatBanner(projectRoot);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let requestedMode;
|
|
415
|
+
if (options.mode != null) {
|
|
416
|
+
requestedMode = normalizeConfiguredMode(options.mode);
|
|
417
|
+
} else if (interactive) {
|
|
418
|
+
const modeChoice = await promptForSelect({
|
|
419
|
+
message: "ChatGPT mode",
|
|
420
|
+
choices: CHATGPT_MODE_CHOICES,
|
|
421
|
+
});
|
|
422
|
+
if (modeChoice == null) {
|
|
423
|
+
chatInput?.close();
|
|
424
|
+
console.log("");
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
requestedMode = modeFromPromptChoice(modeChoice);
|
|
428
|
+
} else {
|
|
429
|
+
// Non-interactive callers cannot answer a picker. Preserve the current web
|
|
430
|
+
// setting unless they explicitly opt into `--mode Pro`.
|
|
431
|
+
requestedMode = null;
|
|
432
|
+
}
|
|
433
|
+
|
|
404
434
|
// In interactive mode an initial task is optional: the user can just start
|
|
405
435
|
// typing at the prompt. In one-shot mode a task is required.
|
|
406
436
|
let task = taskParts.join(" ").trim();
|
|
407
437
|
if (!task) {
|
|
408
|
-
if (interactive) {
|
|
409
|
-
printChatBanner(projectRoot, options.mode);
|
|
410
|
-
}
|
|
411
438
|
const initialMessage = interactive
|
|
412
439
|
? await readChatMessage(() => chatInput.read())
|
|
413
440
|
: await promptForText({
|
|
@@ -421,7 +448,6 @@ async function runAgent(taskParts, options) {
|
|
|
421
448
|
}
|
|
422
449
|
task = initialMessage.trim();
|
|
423
450
|
} else if (interactive) {
|
|
424
|
-
printChatBanner(projectRoot, options.mode);
|
|
425
451
|
chatInput.remember(task);
|
|
426
452
|
}
|
|
427
453
|
|
|
@@ -431,7 +457,7 @@ async function runAgent(taskParts, options) {
|
|
|
431
457
|
sessionsDir: paths.sessionsDir,
|
|
432
458
|
task,
|
|
433
459
|
projectRoot,
|
|
434
|
-
mode:
|
|
460
|
+
mode: requestedMode,
|
|
435
461
|
});
|
|
436
462
|
|
|
437
463
|
// Resolve @file attachments in the opening task, if any. The task itself is
|
|
@@ -454,7 +480,7 @@ async function runAgent(taskParts, options) {
|
|
|
454
480
|
return await executeSession({ session, options, files, chatInput });
|
|
455
481
|
}
|
|
456
482
|
|
|
457
|
-
function printChatBanner(projectRoot
|
|
483
|
+
function printChatBanner(projectRoot) {
|
|
458
484
|
const CYAN = "\x1b[36m";
|
|
459
485
|
const DIM = "\x1b[2m";
|
|
460
486
|
const RESET = "\x1b[0m";
|
|
@@ -585,12 +611,15 @@ async function runExport(sessionId, options) {
|
|
|
585
611
|
const program = new Command()
|
|
586
612
|
.name("wtagent")
|
|
587
613
|
.description("Turn your web AI session into a local tool-using agent.")
|
|
588
|
-
.version("0.1.0-alpha.
|
|
614
|
+
.version("0.1.0-alpha.4")
|
|
589
615
|
.option("--home <path>", "Application data directory")
|
|
590
616
|
.option("--profile-dir <path>", "Dedicated Chrome profile directory")
|
|
591
617
|
.option("--chrome-path <path>", "Chrome/Chromium executable")
|
|
592
618
|
.option("-C, --project <path>", "Project directory", process.cwd())
|
|
593
|
-
.option(
|
|
619
|
+
.option(
|
|
620
|
+
"--mode <name>",
|
|
621
|
+
'ChatGPT mode: "Pro" selects Pro; "Current" keeps the web setting',
|
|
622
|
+
)
|
|
594
623
|
.option(
|
|
595
624
|
"--once",
|
|
596
625
|
"Run a single request and exit instead of a conversation",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const CHATGPT_MODE_CHOICES = Object.freeze([
|
|
2
|
+
Object.freeze({
|
|
3
|
+
name: "Pro — select Pro for this conversation",
|
|
4
|
+
value: "pro",
|
|
5
|
+
}),
|
|
6
|
+
Object.freeze({
|
|
7
|
+
name: "Current — keep the current ChatGPT setting",
|
|
8
|
+
value: "current",
|
|
9
|
+
}),
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export function normalizeConfiguredMode(value) {
|
|
13
|
+
const configured = String(value ?? "").trim();
|
|
14
|
+
if (/^current$/i.test(configured)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
if (/^pro$/i.test(configured)) {
|
|
18
|
+
return "Pro";
|
|
19
|
+
}
|
|
20
|
+
throw new Error('ChatGPT mode must be either "Pro" or "Current".');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function modeFromPromptChoice(choice) {
|
|
24
|
+
return choice === "pro" ? "Pro" : null;
|
|
25
|
+
}
|
package/src/cli/prompt-input.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { input } from "@inquirer/prompts";
|
|
1
|
+
import { input, select } from "@inquirer/prompts";
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
3
|
|
|
4
4
|
const CLEAN_EXIT_ERRORS = new Set([
|
|
@@ -112,11 +112,11 @@ export class ShellChatInput {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
prompt
|
|
115
|
+
async function promptWithCleanExit(config, {
|
|
116
|
+
prompt,
|
|
117
117
|
inputStream = process.stdin,
|
|
118
118
|
outputStream = process.stdout,
|
|
119
|
-
}
|
|
119
|
+
}) {
|
|
120
120
|
if (inputStream.readableEnded) {
|
|
121
121
|
return null;
|
|
122
122
|
}
|
|
@@ -150,3 +150,17 @@ export async function promptForText(config, {
|
|
|
150
150
|
inputStream.removeListener("close", onEof);
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
|
+
|
|
154
|
+
export async function promptForText(config, options = {}) {
|
|
155
|
+
return await promptWithCleanExit(config, {
|
|
156
|
+
...options,
|
|
157
|
+
prompt: options.prompt ?? input,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function promptForSelect(config, options = {}) {
|
|
162
|
+
return await promptWithCleanExit(config, {
|
|
163
|
+
...options,
|
|
164
|
+
prompt: options.prompt ?? select,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
@@ -239,7 +239,7 @@ export class AgentRuntime {
|
|
|
239
239
|
let activeMode = resume
|
|
240
240
|
? (this.session.state.activeMode ?? null)
|
|
241
241
|
: null;
|
|
242
|
-
if (!resume) {
|
|
242
|
+
if (!resume && mode) {
|
|
243
243
|
const modeResult = await this.adapter.selectMode(mode);
|
|
244
244
|
if (modeResult) {
|
|
245
245
|
await this.emit("conversation.mode_selected", {
|
|
@@ -339,7 +339,7 @@ export class AgentRuntime {
|
|
|
339
339
|
let protocolErrors = 0;
|
|
340
340
|
const baseTurn = resume ? Number(this.session.state.turn || 0) : 0;
|
|
341
341
|
|
|
342
|
-
for (let step = 1;
|
|
342
|
+
for (let step = 1; ; step += 1) {
|
|
343
343
|
const turnNumber = baseTurn + step;
|
|
344
344
|
await this.session.update({ turn: turnNumber, phase: "waiting_model" });
|
|
345
345
|
const raw = await this.adapter.waitForTurnComplete({
|
|
@@ -699,12 +699,5 @@ export class AgentRuntime {
|
|
|
699
699
|
});
|
|
700
700
|
}
|
|
701
701
|
|
|
702
|
-
const message = `Maximum turn limit reached (${this.limits.maxTurns}).`;
|
|
703
|
-
await this.session.update({
|
|
704
|
-
phase: "interrupted",
|
|
705
|
-
lastError: message,
|
|
706
|
-
});
|
|
707
|
-
await this.emit("run.interrupted", { message });
|
|
708
|
-
throw new Error(message);
|
|
709
702
|
}
|
|
710
703
|
}
|