wtagent 0.2.2 → 0.4.0

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.
@@ -19,15 +19,8 @@ export const DEFAULT_LIMITS = Object.freeze({
19
19
  maxSearchResults: 200,
20
20
  });
21
21
 
22
- // ChatGPT Pro plans can legitimately think longer than the default before the
23
- // first token (see the runtime's mode-aware timeout selection).
24
- export const PRO_MODEL_TURN_TIMEOUT_MS = 16 * 60_000;
25
-
26
- // The CLI's --model-turn-timeout-ms always wins over the mode-aware default.
27
- export function isProMode(mode) {
28
- return String(mode ?? "") === "Pro";
29
- }
30
-
22
+ // Model response timing is provider/model agnostic. Users who need a longer
23
+ // window can override it explicitly with --model-turn-timeout-ms.
31
24
  export function resolveLimits({ modelTurnTimeoutMs } = {}) {
32
25
  if (modelTurnTimeoutMs == null || modelTurnTimeoutMs === "") {
33
26
  return DEFAULT_LIMITS;
@@ -402,7 +402,7 @@ export function createDefaultToolRegistry({
402
402
  }
403
403
  next = edit.replace_all
404
404
  ? next.split(matchedOld).join(edit.new_text)
405
- : next.replace(matchedOld, edit.new_text);
405
+ : next.replace(matchedOld, () => edit.new_text);
406
406
  }
407
407
 
408
408
  await writeTextAtomic(target, next, context);
@@ -1,214 +0,0 @@
1
- // Language-independent model/mode selection logic for the ChatGPT web picker.
2
- //
3
- // Prefer language-independent option slugs derived from data-testid / data-* /
4
- // id. Current ChatGPT menus sometimes omit every such attribute, including on
5
- // the Pro item, so an exact normalized label match is kept as a conservative
6
- // fallback. We never use fuzzy label matching.
7
- //
8
- // This module is pure and DOM-free so it can be unit-tested without a browser.
9
- // The adapter supplies a small "port" of async DOM operations to
10
- // `runModeSelection`; all the retry / fallback policy lives here.
11
-
12
- export function slugTokens(slug) {
13
- return String(slug ?? "").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
14
- }
15
-
16
- export function normalizeToken(value) {
17
- return String(value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "");
18
- }
19
-
20
- // Does the option's stable slug encode the requested mode token?
21
- // `token` is a normalized (separator-free) string, e.g. "pro" or "extrahigh".
22
- // Short tokens (< 4 chars, e.g. "pro") require an exact whole-segment match so
23
- // "pro" does not match "provider". Longer tokens also allow a substring match
24
- // against the separator-free slug, so a multi-word mode like "Extra high"
25
- // (-> "extrahigh") matches a hyphenated slug like "o3-extra-high".
26
- export function slugMatchesToken(slug, token) {
27
- if (!token) {
28
- return false;
29
- }
30
- const tokens = slugTokens(slug);
31
- if (tokens.includes(token)) {
32
- return true;
33
- }
34
- if (token.length >= 4) {
35
- const joined = tokens.join("");
36
- return joined.includes(token) || tokens.some((candidate) => candidate.includes(token));
37
- }
38
- return false;
39
- }
40
-
41
- export function labelMatchesToken(label, token) {
42
- return String(label ?? "")
43
- .split(/\r?\n/)
44
- .some((line) => normalizeToken(line) === token);
45
- }
46
-
47
- // Given the enumerated menu options and the requested mode, decide what to do.
48
- // options: [{ index, slug, label, disabled, selected }] in DOM order.
49
- // Returns { status, targetIndex, selectedLabel, reason }.
50
- // status "select" -> requested mode is available; click it.
51
- // status "already" -> requested mode is already selected.
52
- // status "fallback" -> requested mode is limited/disabled; click
53
- // the option immediately before it.
54
- // status "unavailable" -> requested mode is not present in the menu.
55
- // status "unavailable_disabled"-> requested mode is disabled and the previous
56
- // option is not selectable either.
57
- export function chooseModeOption(options, requested) {
58
- const token = normalizeToken(requested);
59
- let proIndex = options.findIndex(
60
- (option) => slugMatchesToken(option.slug, token),
61
- );
62
- if (proIndex === -1) {
63
- proIndex = options.findIndex(
64
- (option) => labelMatchesToken(option.label, token),
65
- );
66
- }
67
-
68
- if (proIndex === -1) {
69
- return {
70
- status: "unavailable",
71
- targetIndex: null,
72
- selectedLabel: null,
73
- reason: `"${requested}" is not in the model menu.`,
74
- };
75
- }
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
- }
86
- if (!pro.disabled) {
87
- return {
88
- status: "select",
89
- targetIndex: proIndex,
90
- selectedLabel: pro.label || requested,
91
- reason: `Selecting ${requested}.`,
92
- };
93
- }
94
-
95
- // Pro is present but limited. Fall back to the option immediately before it.
96
- const previous = options[proIndex - 1];
97
- if (previous && !previous.disabled) {
98
- return {
99
- status: "fallback",
100
- targetIndex: proIndex - 1,
101
- selectedLabel: previous.label || `option ${proIndex}`,
102
- reason: `${requested} is limited; selecting the previous option `
103
- + `"${previous.label || proIndex - 1}" instead.`,
104
- };
105
- }
106
-
107
- return {
108
- status: "unavailable_disabled",
109
- targetIndex: null,
110
- selectedLabel: null,
111
- reason: previous
112
- ? `${requested} is limited and the previous option is also unavailable.`
113
- : `${requested} is limited and has no previous option to fall back to.`,
114
- };
115
- }
116
-
117
- // Orchestrates selection against a DOM "port". Never throws for mode issues:
118
- // it always resolves to a structured result the runtime can surface to the CLI,
119
- // so a limited/absent Pro degrades gracefully instead of aborting the run.
120
- //
121
- // port: {
122
- // alreadyOnMode(requested) -> bool,
123
- // hasSwitcher() -> bool,
124
- // openMenu(requested) -> void,
125
- // readOptions() -> [{index, slug, label, disabled, selected}],
126
- // clickOption(index) -> bool (click landed),
127
- // waitSelected(requested) -> bool (picker reflects the requested mode),
128
- // waitClosed() -> bool (menu closed after click),
129
- // closeMenu() -> void,
130
- // writeDiagnostics(label) -> void,
131
- // }
132
- export async function runModeSelection(port, requested, { maxAttempts = 2 } = {}) {
133
- if (!requested) {
134
- return { status: "skipped", requested, attempts: 0, reason: "No mode requested." };
135
- }
136
-
137
- if (await port.alreadyOnMode(requested)) {
138
- return {
139
- status: "already",
140
- requested,
141
- selectedLabel: requested,
142
- attempts: 0,
143
- reason: `Already using ${requested}.`,
144
- };
145
- }
146
-
147
- if (!await port.hasSwitcher()) {
148
- await port.writeDiagnostics("mode-switcher-not-found");
149
- return {
150
- status: "switcher_not_found",
151
- requested,
152
- attempts: 0,
153
- reason: "Model switcher was not found.",
154
- };
155
- }
156
-
157
- let lastReason = "";
158
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
159
- await port.openMenu(requested);
160
- const options = await port.readOptions();
161
-
162
- // The Radix menu populates asynchronously; an empty read is a race, retry.
163
- if (!options || options.length === 0) {
164
- lastReason = "Model menu did not populate in time.";
165
- await port.closeMenu();
166
- continue;
167
- }
168
-
169
- const choice = chooseModeOption(options, requested);
170
-
171
- if (choice.status === "already") {
172
- await port.closeMenu();
173
- return { ...choice, requested, attempts: attempt };
174
- }
175
-
176
- if (choice.status === "select" || choice.status === "fallback") {
177
- const clicked = await port.clickOption(choice.targetIndex);
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) {
187
- return { ...choice, requested, attempts: attempt };
188
- }
189
- lastReason = "The option click did not change the selected mode.";
190
- await port.closeMenu();
191
- continue;
192
- }
193
-
194
- await port.closeMenu();
195
-
196
- // A genuinely absent Pro might still be populating on the first pass, so
197
- // retry once; a disabled Pro will not change, so report immediately.
198
- if (choice.status === "unavailable" && attempt < maxAttempts) {
199
- lastReason = choice.reason;
200
- continue;
201
- }
202
-
203
- await port.writeDiagnostics(`mode-${requested}-${choice.status}`);
204
- return { ...choice, requested, attempts: attempt };
205
- }
206
-
207
- await port.writeDiagnostics(`mode-${requested}-unresolved`);
208
- return {
209
- status: "unresolved",
210
- requested,
211
- attempts: maxAttempts,
212
- reason: lastReason || `Could not select ${requested}.`,
213
- };
214
- }
@@ -1,25 +0,0 @@
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
- }