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.
@@ -8,6 +8,8 @@
8
8
  // A single Renderer instance is shared across every turn of a conversation so
9
9
  // spinner state and per-tool timing survive between the model's replies.
10
10
 
11
+ import { t } from "./i18n.js";
12
+
11
13
  const DIM = "\x1b[2m";
12
14
  const BOLD = "\x1b[1m";
13
15
  const CYAN = "\x1b[36m";
@@ -109,7 +111,7 @@ export class Renderer {
109
111
  }
110
112
  this.spinner = {
111
113
  kind: "processing",
112
- label: "processing request",
114
+ label: t("renderer.processing"),
113
115
  start: Date.now(),
114
116
  bytes: 0,
115
117
  };
@@ -119,7 +121,7 @@ export class Renderer {
119
121
  beginRunning(name) {
120
122
  this.spinner = {
121
123
  kind: "running",
122
- label: `running ${name}`,
124
+ label: t("renderer.running", { tool: name }),
123
125
  start: Date.now(),
124
126
  bytes: 0,
125
127
  };
@@ -235,7 +237,7 @@ export class Renderer {
235
237
  answer(message) {
236
238
  this.stopSpinner();
237
239
  this.println("");
238
- this.println(`${GREEN}${BOLD}⏺ assistant${RESET}`);
240
+ this.println(`${GREEN}${BOLD}⏺ ${t("renderer.assistant")}${RESET}`);
239
241
  for (const line of String(message ?? "").split(/\r?\n/)) {
240
242
  this.println(line);
241
243
  }
@@ -260,36 +262,25 @@ export class Renderer {
260
262
  break;
261
263
  }
262
264
  this.lifecycleShown.add(type);
263
- this.status("Chrome started.");
265
+ this.status(t("renderer.chromeStarted"));
264
266
  break;
265
267
  case "browser.auth_required":
266
268
  this.stopSpinner();
267
- this.println(`${YELLOW}Log in to ${this.providerLabel} in the opened Chrome window…${RESET}`);
269
+ this.println(
270
+ `${YELLOW}${t("renderer.loginRequired", { provider: this.providerLabel })}${RESET}`,
271
+ );
268
272
  break;
269
273
  case "browser.authenticated":
270
- this.println(`${GREEN}${this.providerLabel} login detected.${RESET}`);
271
- break;
272
- case "conversation.mode_selected": {
273
- const { requested, status, selectedLabel } = payload;
274
- if (status === "select" || status === "already") {
275
- this.status(`Mode: ${selectedLabel ?? requested}.`);
276
- } else if (status === "fallback") {
277
- this.note(`Mode: ${requested} unavailable; using ${selectedLabel}.`);
278
- } else {
279
- this.note(`Mode: could not select ${requested}; continuing on current mode.`);
280
- }
274
+ this.println(
275
+ `${GREEN}${t("renderer.loginDetected", { provider: this.providerLabel })}${RESET}`,
276
+ );
281
277
  break;
282
- }
283
278
  case "conversation.started":
284
279
  if (this.lifecycleShown.has(type)) {
285
280
  break;
286
281
  }
287
282
  this.lifecycleShown.add(type);
288
- if (payload.mode) {
289
- this.status(`Conversation ready (${payload.mode}).`);
290
- } else {
291
- this.status("Conversation ready (current mode).");
292
- }
283
+ this.status(t("renderer.conversationReady"));
293
284
  break;
294
285
  case "model.message_sent":
295
286
  this.beginProcessing();
@@ -305,18 +296,29 @@ export class Renderer {
305
296
  this.stopSpinner();
306
297
  this.note(
307
298
  payload.deadRequest
308
- ? `no reply from ${this.providerLabel}; asking it to continue (${payload.retry}/${payload.maxRetries})`
299
+ ? t("renderer.noReply", {
300
+ provider: this.providerLabel,
301
+ retry: payload.retry,
302
+ maxRetries: payload.maxRetries,
303
+ })
309
304
  : payload.generationFailed
310
- ? `${this.providerLabel} generation failed (server error); asking it to retry (${payload.retry}/${payload.maxRetries})`
311
- : `empty ${this.providerLabel} response; asking it to continue (${payload.retry}/${payload.maxRetries})`,
305
+ ? t("renderer.generationFailed", {
306
+ provider: this.providerLabel,
307
+ retry: payload.retry,
308
+ maxRetries: payload.maxRetries,
309
+ })
310
+ : t("renderer.emptyResponse", {
311
+ provider: this.providerLabel,
312
+ retry: payload.retry,
313
+ maxRetries: payload.maxRetries,
314
+ }),
312
315
  );
313
316
  break;
314
317
  case "model.limit_reached":
315
318
  this.stopSpinner();
316
- this.note(
317
- `${this.providerLabel} usage limit reached. Wait for the limit to reset, `
318
- + "try a different mode on resume, or change plans.",
319
- );
319
+ this.note(t("renderer.limitReached", {
320
+ provider: this.providerLabel,
321
+ }));
320
322
  break;
321
323
  case "model.progress":
322
324
  this.stopSpinner();
@@ -329,7 +331,7 @@ export class Renderer {
329
331
  break;
330
332
  case "protocol.plain_answer":
331
333
  this.stopSpinner();
332
- this.note("The model answered without the XML protocol; showing the reply as the final answer.");
334
+ this.note(t("renderer.plainAnswer"));
333
335
  break;
334
336
  case "tool.proposed":
335
337
  this.stopSpinner();
@@ -361,21 +363,25 @@ export class Renderer {
361
363
  this.stopSpinner();
362
364
  if (!payload.id || !this.handled.has(payload.id)) {
363
365
  if (payload.id) this.handled.add(payload.id);
364
- this.println(`${this.#hdr()} ${DIM}↺ reused prior result for ${payload.name}${RESET}`);
366
+ this.println(
367
+ `${this.#hdr()} ${DIM}↺ ${t("renderer.reused", { tool: payload.name })}${RESET}`,
368
+ );
365
369
  }
366
370
  break;
367
371
  case "tool.reused_unknown":
368
372
  this.stopSpinner();
369
373
  if (!payload.id || !this.handled.has(payload.id)) {
370
374
  if (payload.id) this.handled.add(payload.id);
371
- this.println(`${this.#hdr()} ${YELLOW}↺ reused (completion unknown) for ${payload.name}${RESET}`);
375
+ this.println(
376
+ `${this.#hdr()} ${YELLOW}↺ ${t("renderer.reusedUnknown", { tool: payload.name })}${RESET}`,
377
+ );
372
378
  }
373
379
  break;
374
380
  case "tool.conflict":
375
381
  this.stopSpinner();
376
382
  if (!payload.id || !this.handled.has(payload.id)) {
377
383
  if (payload.id) this.handled.add(payload.id);
378
- this.note(`conflicting reuse for ${payload.name}`);
384
+ this.note(t("renderer.conflictingReuse", { tool: payload.name }));
379
385
  }
380
386
  break;
381
387
  case "run.completed":
@@ -383,12 +389,16 @@ export class Renderer {
383
389
  break;
384
390
  case "run.interrupted":
385
391
  this.stopSpinner();
386
- this.println(`${YELLOW}Run interrupted: ${truncate(payload.message, 160)}${RESET}`);
392
+ this.println(`${YELLOW}${t("renderer.interrupted", {
393
+ message: truncate(payload.message, 160),
394
+ })}${RESET}`);
387
395
  break;
388
396
  case "run.recovery_required":
389
397
  this.stopSpinner();
390
398
  this.println(
391
- `${YELLOW}${truncate(payload.message, 180)} The session and Chrome window remain open.${RESET}`,
399
+ `${YELLOW}${t("renderer.recoveryOpen", {
400
+ message: truncate(payload.message, 180),
401
+ })}${RESET}`,
392
402
  );
393
403
  break;
394
404
  case "tool.result_sent":
@@ -6,12 +6,14 @@ import { getPackageName, getPackageVersion } from "../shared/package-info.js";
6
6
  export const UPDATE_CHECK_TIMEOUT_MS = NETWORK_TIMEOUT_MS;
7
7
  export const UPDATE_COMMAND_TIMEOUT_MS = 10_000;
8
8
  export const INSTALL_TIMEOUT_MS = 120_000;
9
- export const MANUAL_INSTALL_COMMAND = "npm install -g wtagent@latest";
9
+ export const OFFICIAL_NPM_REGISTRY = "https://registry.npmjs.org/";
10
+ export const MANUAL_INSTALL_COMMAND =
11
+ `npm install -g wtagent@latest --registry=${OFFICIAL_NPM_REGISTRY}`;
10
12
 
11
13
  const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
12
14
 
13
15
  export function npmRegistryLatestUrl(name = getPackageName()) {
14
- return `https://registry.npmjs.org/${encodeURIComponent(name)}/latest`;
16
+ return `${OFFICIAL_NPM_REGISTRY}${encodeURIComponent(name)}/latest`;
15
17
  }
16
18
 
17
19
  export function parseVersion(value) {
@@ -76,7 +78,12 @@ export async function installLatest({
76
78
  timeoutMs = INSTALL_TIMEOUT_MS,
77
79
  stdio = "inherit",
78
80
  } = {}) {
79
- const plan = planCommandImpl("npm", ["install", "-g", "wtagent@latest"]);
81
+ const plan = planCommandImpl("npm", [
82
+ "install",
83
+ "-g",
84
+ "wtagent@latest",
85
+ `--registry=${OFFICIAL_NPM_REGISTRY}`,
86
+ ]);
80
87
  return await new Promise((resolve) => {
81
88
  let settled = false;
82
89
  let timer;
@@ -178,7 +178,7 @@ function buildBatchCommand(scriptPath, argv, {
178
178
  export function resolveLaunchPlan({
179
179
  program,
180
180
  argv = [],
181
- cwd,
181
+ cwd = process.cwd(),
182
182
  env = process.env,
183
183
  platform = process.platform,
184
184
  existsSync = fs.existsSync,
@@ -7,6 +7,25 @@ import { getEnvCaseInsensitive } from "./command-launcher.js";
7
7
  const APP_NAME = "wtagent";
8
8
  const LEGACY_APP_NAME = "webagent";
9
9
 
10
+ export function formatHomePath(filePath, {
11
+ platform = process.platform,
12
+ homeDir = os.homedir(),
13
+ } = {}) {
14
+ const pathApi = platform === "win32" ? path.win32 : path.posix;
15
+ if (!pathApi.isAbsolute(filePath)) {
16
+ return filePath;
17
+ }
18
+ const relative = pathApi.relative(homeDir, filePath);
19
+ if (relative === "") {
20
+ return "~";
21
+ }
22
+ if (relative === ".." || relative.startsWith(`..${pathApi.sep}`)
23
+ || pathApi.isAbsolute(relative)) {
24
+ return filePath;
25
+ }
26
+ return `~${pathApi.sep}${relative}`;
27
+ }
28
+
10
29
  function platformAppDataDir(appName, {
11
30
  env,
12
31
  platform,
@@ -8,11 +8,12 @@ import { promisify } from "node:util";
8
8
  import { discoverChromeExecutable } from "./chrome-discovery.js";
9
9
  import { resolveLaunchPlan } from "./command-launcher.js";
10
10
  import { inspectCdpProfileState } from "../browser/cdp-state.js";
11
+ import { getPackageInfo } from "../shared/package-info.js";
11
12
 
12
13
  const execFileAsync = promisify(execFile);
13
14
 
14
15
  function parseMajorMinorPatch(version) {
15
- const match = String(version ?? "").replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)/);
16
+ const match = String(version ?? "").replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)$/);
16
17
  if (!match) {
17
18
  return null;
18
19
  }
@@ -24,20 +25,12 @@ export function isSupportedNodeVersion(version = process.version) {
24
25
  if (!parsed) {
25
26
  return false;
26
27
  }
27
- const [major, minor, patch] = parsed;
28
- if (major > 20) {
29
- return true;
30
- }
31
- if (major < 20) {
32
- return false;
33
- }
34
- if (minor > 17) {
35
- return true;
36
- }
37
- if (minor < 17) {
38
- return false;
39
- }
40
- return patch >= 0;
28
+ const [major, minor] = parsed;
29
+ // Keep aligned with package.json and Inquirer's supported Node releases.
30
+ return (major === 20 && minor >= 17)
31
+ || (major === 22 && minor >= 13)
32
+ || (major === 23 && minor >= 5)
33
+ || major >= 24;
41
34
  }
42
35
 
43
36
  export function detectWsl({
@@ -126,7 +119,7 @@ export function assertNativeRuntimeSupported(context = {}) {
126
119
  const version = context.version ?? process.version;
127
120
  if (!isSupportedNodeVersion(version)) {
128
121
  throw new Error(
129
- `WTAgent requires Node.js 20.17.0 or newer; current runtime is ${version}.`,
122
+ `WTAgent requires Node.js ${getPackageInfo().engines.node}; current runtime is ${version}.`,
130
123
  );
131
124
  }
132
125
 
@@ -73,7 +73,11 @@ export function isPathInside(rootPath, candidatePath, options = {}) {
73
73
  };
74
74
  const root = normalize(rootPath);
75
75
  const candidate = normalize(candidatePath);
76
- return candidate === root || candidate.startsWith(`${root}\\`);
76
+ const relative = path.win32.relative(root, candidate);
77
+ return relative === ""
78
+ || (relative !== ".."
79
+ && !relative.startsWith("..\\")
80
+ && !path.win32.isAbsolute(relative));
77
81
  }
78
82
  const relative = path.relative(rootPath, candidatePath);
79
83
  return relative === ""