wtagent 0.1.0 → 0.2.1
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 +53 -35
- package/package.json +2 -2
- package/src/browser/base-web-adapter.js +1178 -0
- package/src/browser/cdp-browser.js +62 -0
- package/src/browser/cdp-state.js +52 -4
- package/src/browser/chatgpt-web-adapter.js +301 -992
- package/src/browser/claude-web-adapter.js +205 -0
- package/src/browser/deepseek-web-adapter.js +337 -0
- package/src/browser/gemini-web-adapter.js +465 -0
- package/src/browser/glm-web-adapter.js +246 -0
- package/src/browser/kimi-web-adapter.js +285 -0
- package/src/browser/provider-registry.js +199 -0
- package/src/cli/main.js +284 -60
- package/src/cli/notice-store.js +125 -0
- package/src/cli/prompt-input.js +504 -170
- package/src/cli/render-events.js +14 -8
- package/src/cli/self-update.js +145 -0
- package/src/cli/startup-notices.js +161 -0
- package/src/platform/paths.js +7 -0
- package/src/platform/windows-diagnostics.js +37 -6
- package/src/protocol/markers.js +3 -2
- package/src/protocol/prompt-builder.js +9 -5
- package/src/protocol/xml-protocol.js +193 -11
- package/src/runtime/agent-runtime.js +131 -18
- package/src/session/agent-session.js +4 -1
- package/src/session/canonical-transcript.js +2 -2
- package/src/session/session-export.js +2 -2
- package/src/shared/fetch-json.js +24 -0
- package/src/shared/limits.js +11 -1
- package/src/shared/package-info.js +23 -0
- package/src/shared/usage-limit.js +2 -2
package/src/cli/render-events.js
CHANGED
|
@@ -54,8 +54,9 @@ function summarizeArgs(name, args = {}) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export class Renderer {
|
|
57
|
-
constructor({ stream = process.stdout } = {}) {
|
|
57
|
+
constructor({ stream = process.stdout, providerLabel = "model" } = {}) {
|
|
58
58
|
this.stream = stream;
|
|
59
|
+
this.providerLabel = providerLabel;
|
|
59
60
|
this.isTTY = Boolean(stream.isTTY);
|
|
60
61
|
this.spinner = null;
|
|
61
62
|
this.timer = null;
|
|
@@ -263,10 +264,10 @@ export class Renderer {
|
|
|
263
264
|
break;
|
|
264
265
|
case "browser.auth_required":
|
|
265
266
|
this.stopSpinner();
|
|
266
|
-
this.println(`${YELLOW}Log in to
|
|
267
|
+
this.println(`${YELLOW}Log in to ${this.providerLabel} in the opened Chrome window…${RESET}`);
|
|
267
268
|
break;
|
|
268
269
|
case "browser.authenticated":
|
|
269
|
-
this.println(`${GREEN}
|
|
270
|
+
this.println(`${GREEN}${this.providerLabel} login detected.${RESET}`);
|
|
270
271
|
break;
|
|
271
272
|
case "conversation.mode_selected": {
|
|
272
273
|
const { requested, status, selectedLabel } = payload;
|
|
@@ -304,16 +305,17 @@ export class Renderer {
|
|
|
304
305
|
this.stopSpinner();
|
|
305
306
|
this.note(
|
|
306
307
|
payload.deadRequest
|
|
307
|
-
? `no reply from
|
|
308
|
-
:
|
|
308
|
+
? `no reply from ${this.providerLabel}; asking it to continue (${payload.retry}/${payload.maxRetries})`
|
|
309
|
+
: 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})`,
|
|
309
312
|
);
|
|
310
313
|
break;
|
|
311
314
|
case "model.limit_reached":
|
|
312
315
|
this.stopSpinner();
|
|
313
316
|
this.note(
|
|
314
|
-
|
|
315
|
-
+ "
|
|
316
|
-
+ "for the limit to reset, or change plans.",
|
|
317
|
+
`${this.providerLabel} usage limit reached. Wait for the limit to reset, `
|
|
318
|
+
+ "try a different mode on resume, or change plans.",
|
|
317
319
|
);
|
|
318
320
|
break;
|
|
319
321
|
case "model.progress":
|
|
@@ -325,6 +327,10 @@ export class Renderer {
|
|
|
325
327
|
case "protocol.invalid":
|
|
326
328
|
this.note(`format retry${payload.count ? ` (${payload.count})` : ""}: ${truncate(payload.message, 100)}`);
|
|
327
329
|
break;
|
|
330
|
+
case "protocol.plain_answer":
|
|
331
|
+
this.stopSpinner();
|
|
332
|
+
this.note("The model answered without the XML protocol; showing the reply as the final answer.");
|
|
333
|
+
break;
|
|
328
334
|
case "tool.proposed":
|
|
329
335
|
this.stopSpinner();
|
|
330
336
|
this.#toolCall(payload.name, payload.args);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { resolveLaunchPlan } from "../platform/command-launcher.js";
|
|
3
|
+
import { fetchJson, NETWORK_TIMEOUT_MS } from "../shared/fetch-json.js";
|
|
4
|
+
import { getPackageName, getPackageVersion } from "../shared/package-info.js";
|
|
5
|
+
|
|
6
|
+
export const UPDATE_CHECK_TIMEOUT_MS = NETWORK_TIMEOUT_MS;
|
|
7
|
+
export const UPDATE_COMMAND_TIMEOUT_MS = 10_000;
|
|
8
|
+
export const INSTALL_TIMEOUT_MS = 120_000;
|
|
9
|
+
export const MANUAL_INSTALL_COMMAND = "npm install -g wtagent@latest";
|
|
10
|
+
|
|
11
|
+
const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
12
|
+
|
|
13
|
+
export function npmRegistryLatestUrl(name = getPackageName()) {
|
|
14
|
+
return `https://registry.npmjs.org/${encodeURIComponent(name)}/latest`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function parseVersion(value) {
|
|
18
|
+
const match = String(value ?? "").trim().match(VERSION_RE);
|
|
19
|
+
if (!match) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
core: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
24
|
+
pre: match[4] ?? null,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function compareVersions(left, right) {
|
|
29
|
+
const parsedLeft = parseVersion(left);
|
|
30
|
+
const parsedRight = parseVersion(right);
|
|
31
|
+
if (!parsedLeft || !parsedRight) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
for (let index = 0; index < 3; index += 1) {
|
|
35
|
+
if (parsedLeft.core[index] !== parsedRight.core[index]) {
|
|
36
|
+
return parsedLeft.core[index] < parsedRight.core[index] ? -1 : 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!parsedLeft.pre && !parsedRight.pre) {
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
if (!parsedLeft.pre) {
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
if (!parsedRight.pre) {
|
|
46
|
+
return -1;
|
|
47
|
+
}
|
|
48
|
+
if (parsedLeft.pre === parsedRight.pre) {
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
return parsedLeft.pre < parsedRight.pre ? -1 : 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function isRemoteNewer(remote, local = getPackageVersion()) {
|
|
55
|
+
const comparison = compareVersions(local, remote);
|
|
56
|
+
return comparison != null && comparison < 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function fetchLatestVersion({
|
|
60
|
+
fetchImpl,
|
|
61
|
+
timeoutMs = UPDATE_CHECK_TIMEOUT_MS,
|
|
62
|
+
packageName = getPackageName(),
|
|
63
|
+
} = {}) {
|
|
64
|
+
const document = await fetchJson(npmRegistryLatestUrl(packageName), {
|
|
65
|
+
fetchImpl,
|
|
66
|
+
timeoutMs,
|
|
67
|
+
});
|
|
68
|
+
return typeof document?.version === "string" && document.version.trim()
|
|
69
|
+
? document.version.trim()
|
|
70
|
+
: null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function installLatest({
|
|
74
|
+
spawnImpl = spawn,
|
|
75
|
+
planCommandImpl = (program, argv) => resolveLaunchPlan({ program, argv }),
|
|
76
|
+
timeoutMs = INSTALL_TIMEOUT_MS,
|
|
77
|
+
stdio = "inherit",
|
|
78
|
+
} = {}) {
|
|
79
|
+
const plan = planCommandImpl("npm", ["install", "-g", "wtagent@latest"]);
|
|
80
|
+
return await new Promise((resolve) => {
|
|
81
|
+
let settled = false;
|
|
82
|
+
let timer;
|
|
83
|
+
const finish = (result) => {
|
|
84
|
+
if (settled) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
settled = true;
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
resolve(result);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
let child;
|
|
93
|
+
try {
|
|
94
|
+
child = spawnImpl(plan.command, plan.args, {
|
|
95
|
+
stdio,
|
|
96
|
+
shell: plan.shell,
|
|
97
|
+
windowsVerbatimArguments: plan.windowsVerbatimArguments,
|
|
98
|
+
});
|
|
99
|
+
} catch (error) {
|
|
100
|
+
finish({ ok: false, error });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
timer = setTimeout(() => {
|
|
105
|
+
child.kill();
|
|
106
|
+
finish({ ok: false, error: new Error("npm install timed out.") });
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
|
|
109
|
+
child.once("error", (error) => {
|
|
110
|
+
finish({ ok: false, error });
|
|
111
|
+
});
|
|
112
|
+
child.once("exit", (code) => {
|
|
113
|
+
finish({ ok: code === 0, code });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function runSelfUpdate({
|
|
119
|
+
fetchLatest = fetchLatestVersion,
|
|
120
|
+
install = installLatest,
|
|
121
|
+
write = console.log,
|
|
122
|
+
writeError = console.error,
|
|
123
|
+
currentVersion = getPackageVersion(),
|
|
124
|
+
} = {}) {
|
|
125
|
+
const latest = await fetchLatest({ timeoutMs: UPDATE_COMMAND_TIMEOUT_MS });
|
|
126
|
+
if (!latest) {
|
|
127
|
+
writeError("Could not check npm for the latest WTAgent version.");
|
|
128
|
+
writeError(`Install it manually: ${MANUAL_INSTALL_COMMAND}`);
|
|
129
|
+
return { status: "error" };
|
|
130
|
+
}
|
|
131
|
+
if (!isRemoteNewer(latest, currentVersion)) {
|
|
132
|
+
write(`Already up to date (${currentVersion}).`);
|
|
133
|
+
return { status: "current", currentVersion, latest };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
write(`Updating WTAgent ${currentVersion} → ${latest} ...`);
|
|
137
|
+
const result = await install();
|
|
138
|
+
if (!result.ok) {
|
|
139
|
+
writeError("Update failed.");
|
|
140
|
+
writeError(`Install it manually: ${MANUAL_INSTALL_COMMAND}`);
|
|
141
|
+
return { status: "error", currentVersion, latest };
|
|
142
|
+
}
|
|
143
|
+
write(`Updated to ${latest}. Run wtagent again to use the new version.`);
|
|
144
|
+
return { status: "updated", currentVersion, latest };
|
|
145
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { fetchJson, NETWORK_TIMEOUT_MS } from "../shared/fetch-json.js";
|
|
2
|
+
import { getPackageVersion } from "../shared/package-info.js";
|
|
3
|
+
import { NoticeStore, getNoticeStorePath } from "./notice-store.js";
|
|
4
|
+
import {
|
|
5
|
+
MANUAL_INSTALL_COMMAND,
|
|
6
|
+
fetchLatestVersion,
|
|
7
|
+
installLatest,
|
|
8
|
+
isRemoteNewer,
|
|
9
|
+
} from "./self-update.js";
|
|
10
|
+
|
|
11
|
+
export const NOTICES_URL =
|
|
12
|
+
"https://raw.githubusercontent.com/XiXian42/wtagent/main/notices.json";
|
|
13
|
+
export const NOTICE_TIMEOUT_MS = NETWORK_TIMEOUT_MS;
|
|
14
|
+
const MAX_NOTICE_TEXT = 2_000;
|
|
15
|
+
const MAX_NOTICE_ID = 128;
|
|
16
|
+
|
|
17
|
+
const CYAN = "\x1b[36m";
|
|
18
|
+
const RESET = "\x1b[0m";
|
|
19
|
+
|
|
20
|
+
export function sanitizeNoticeText(value) {
|
|
21
|
+
return String(value ?? "")
|
|
22
|
+
.replace(/\r\n|\r/g, "\n")
|
|
23
|
+
.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, "")
|
|
24
|
+
.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g, "")
|
|
25
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, "")
|
|
26
|
+
.trim()
|
|
27
|
+
.slice(0, MAX_NOTICE_TEXT);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeTimes(value) {
|
|
31
|
+
if (value == null) {
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
const times = typeof value === "number" ? value : Number(value);
|
|
35
|
+
if (!Number.isFinite(times)) {
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
return Math.max(0, Math.floor(times));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parseNoticeDocument(document) {
|
|
42
|
+
const raw = Array.isArray(document?.notices) ? document.notices : [];
|
|
43
|
+
const notices = [];
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
for (const entry of raw) {
|
|
46
|
+
if (!entry || typeof entry !== "object") {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
50
|
+
const text = sanitizeNoticeText(entry.text);
|
|
51
|
+
if (!id || id.length > MAX_NOTICE_ID || !text || seen.has(id)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
seen.add(id);
|
|
55
|
+
notices.push({
|
|
56
|
+
id,
|
|
57
|
+
text,
|
|
58
|
+
times: normalizeTimes(entry.times),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return notices;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function selectNotice(notices, store) {
|
|
65
|
+
for (const notice of notices) {
|
|
66
|
+
if (notice.times <= 0) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (store.shownCount(notice.id) >= notice.times) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
return notice;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function formatNotice(text) {
|
|
78
|
+
return `\n${CYAN}Notice${RESET}\n${text}\n`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function fetchNoticeDocument({
|
|
82
|
+
fetchImpl,
|
|
83
|
+
timeoutMs = NOTICE_TIMEOUT_MS,
|
|
84
|
+
url = NOTICES_URL,
|
|
85
|
+
} = {}) {
|
|
86
|
+
const document = await fetchJson(url, { fetchImpl, timeoutMs });
|
|
87
|
+
return document && typeof document === "object" ? document : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function runStartupChecks({
|
|
91
|
+
appDataDir,
|
|
92
|
+
interactive = true,
|
|
93
|
+
fetchLatest = fetchLatestVersion,
|
|
94
|
+
fetchNotices = fetchNoticeDocument,
|
|
95
|
+
install = installLatest,
|
|
96
|
+
promptUpdate,
|
|
97
|
+
write = (text) => {
|
|
98
|
+
console.log(text);
|
|
99
|
+
},
|
|
100
|
+
writeError = (text) => {
|
|
101
|
+
console.error(text);
|
|
102
|
+
},
|
|
103
|
+
now,
|
|
104
|
+
} = {}) {
|
|
105
|
+
if (!interactive) {
|
|
106
|
+
return "continue";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const store = new NoticeStore({
|
|
110
|
+
filePath: getNoticeStorePath(appDataDir),
|
|
111
|
+
now,
|
|
112
|
+
});
|
|
113
|
+
await store.ensureLoaded();
|
|
114
|
+
|
|
115
|
+
const needUpdateCheck = !store.wasUpdatePromptedToday();
|
|
116
|
+
const needNotice = !store.wasNoticeShownToday();
|
|
117
|
+
const latestPromise = needUpdateCheck
|
|
118
|
+
? fetchLatest({ timeoutMs: NOTICE_TIMEOUT_MS })
|
|
119
|
+
: Promise.resolve(null);
|
|
120
|
+
const noticesPromise = needNotice
|
|
121
|
+
? fetchNotices({ timeoutMs: NOTICE_TIMEOUT_MS })
|
|
122
|
+
: Promise.resolve(null);
|
|
123
|
+
|
|
124
|
+
const currentVersion = getPackageVersion();
|
|
125
|
+
const latest = await latestPromise;
|
|
126
|
+
if (needUpdateCheck && latest) {
|
|
127
|
+
if (isRemoteNewer(latest, currentVersion)) {
|
|
128
|
+
write(`A newer WTAgent is available: ${currentVersion} → ${latest}`);
|
|
129
|
+
const choice = await promptUpdate({ currentVersion, latest });
|
|
130
|
+
if (choice == null) {
|
|
131
|
+
return "aborted";
|
|
132
|
+
}
|
|
133
|
+
await store.markUpdatePrompted();
|
|
134
|
+
if (choice) {
|
|
135
|
+
write(`Updating WTAgent ${currentVersion} → ${latest} ...`);
|
|
136
|
+
const result = await install();
|
|
137
|
+
if (result.ok) {
|
|
138
|
+
write(`Updated to ${latest}. Restart wtagent to use the new version.`);
|
|
139
|
+
return "updated";
|
|
140
|
+
}
|
|
141
|
+
writeError("Update failed.");
|
|
142
|
+
writeError(`Install it manually: ${MANUAL_INSTALL_COMMAND}`);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
await store.markUpdatePrompted();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!needNotice) {
|
|
150
|
+
return "continue";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const notices = parseNoticeDocument(await noticesPromise);
|
|
154
|
+
const selected = selectNotice(notices, store);
|
|
155
|
+
if (!selected) {
|
|
156
|
+
return "continue";
|
|
157
|
+
}
|
|
158
|
+
write(formatNotice(selected.text).trimEnd());
|
|
159
|
+
await store.recordNoticeShown(selected.id);
|
|
160
|
+
return "continue";
|
|
161
|
+
}
|
package/src/platform/paths.js
CHANGED
|
@@ -63,6 +63,13 @@ export function getChromeProfileDir(appDataDir = getAppDataDir()) {
|
|
|
63
63
|
return path.join(appDataDir, "chrome-profile");
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Resolves a named Chrome profile directory under the app data dir. The
|
|
67
|
+
// provider→basename mapping lives in the provider registry; this helper only
|
|
68
|
+
// joins the path so paths.js stays free of provider dependencies.
|
|
69
|
+
export function getProfileDir(appDataDir, basename) {
|
|
70
|
+
return path.join(appDataDir, basename);
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
export function getTasksDir(appDataDir = getAppDataDir()) {
|
|
67
74
|
return path.join(appDataDir, "tasks");
|
|
68
75
|
}
|
|
@@ -55,6 +55,35 @@ export function detectWsl({
|
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function getWslSupport({
|
|
59
|
+
platform = process.platform,
|
|
60
|
+
env = process.env,
|
|
61
|
+
osRelease = os.release(),
|
|
62
|
+
} = {}) {
|
|
63
|
+
if (!detectWsl({ platform, env, osRelease })) {
|
|
64
|
+
return { supported: true, preview: false, reason: null };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const display = env.WAYLAND_DISPLAY || env.DISPLAY;
|
|
68
|
+
if (!display) {
|
|
69
|
+
return {
|
|
70
|
+
supported: false,
|
|
71
|
+
preview: false,
|
|
72
|
+
reason:
|
|
73
|
+
"WSL is detected, but no Linux graphical display is configured. Enable WSLg (or an X server), install Chrome/Chromium inside the WSL distribution, and retry.",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
supported: true,
|
|
79
|
+
preview: true,
|
|
80
|
+
reason:
|
|
81
|
+
`WSL is detected with a ${env.WAYLAND_DISPLAY ? "Wayland" : "X11"} display environment configured. `
|
|
82
|
+
+ "This preview check does not verify that the display is reachable; WTAgent confirms that only when Linux Chrome starts. "
|
|
83
|
+
+ "Windows-host Chrome is not used.",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
58
87
|
export function getNativeWindowsSupport({
|
|
59
88
|
platform = process.platform,
|
|
60
89
|
arch = process.arch,
|
|
@@ -100,11 +129,12 @@ export function assertNativeRuntimeSupported(context = {}) {
|
|
|
100
129
|
`WTAgent requires Node.js 20.17.0 or newer; current runtime is ${version}.`,
|
|
101
130
|
);
|
|
102
131
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
);
|
|
132
|
+
|
|
133
|
+
const wsl = getWslSupport(context);
|
|
134
|
+
if (!wsl.supported) {
|
|
135
|
+
throw new Error(wsl.reason);
|
|
107
136
|
}
|
|
137
|
+
|
|
108
138
|
const windows = getNativeWindowsSupport(context);
|
|
109
139
|
if (!windows.supported) {
|
|
110
140
|
throw new Error(windows.reason);
|
|
@@ -356,11 +386,12 @@ export async function collectDoctorReport({
|
|
|
356
386
|
|
|
357
387
|
const isWsl = detectWsl({ platform, env, osRelease });
|
|
358
388
|
if (isWsl) {
|
|
389
|
+
const wsl = getWslSupport({ platform, env, osRelease });
|
|
359
390
|
add(
|
|
360
391
|
"host",
|
|
361
392
|
"Runtime host",
|
|
362
|
-
"fail",
|
|
363
|
-
|
|
393
|
+
wsl.supported ? (wsl.preview ? "degraded" : "pass") : "fail",
|
|
394
|
+
wsl.reason,
|
|
364
395
|
{ required: true },
|
|
365
396
|
);
|
|
366
397
|
} else {
|
package/src/protocol/markers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Delimiters that mark WTAgent-specific control content inside the plain-text
|
|
2
|
-
// messages exchanged with
|
|
2
|
+
// messages exchanged with a web AI. A normal web chat has no real system/tool
|
|
3
3
|
// channel, so the protocol instructions, tool catalog, and per-turn reminders
|
|
4
4
|
// all travel as ordinary chat text. These markers let the session exporters
|
|
5
5
|
// deterministically strip that scaffolding when converting a transcript into a
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
export const SYSTEM_PROMPT_TAG = "agent_protocol";
|
|
13
13
|
export const SYSTEM_REMINDER_TAG = "system_reminder";
|
|
14
14
|
export const DEFAULT_SYSTEM_REMINDER = [
|
|
15
|
-
"This is a reminder of the user's requested response format for the WTAgent integration, not a
|
|
15
|
+
"This is a reminder of the user's requested response format for the WTAgent integration, not a provider-native system message.",
|
|
16
16
|
"You do not need native tool access: write the XML request as text and the user's local Runtime will process it after your reply.",
|
|
17
17
|
"Your next response must use the XML application protocol.",
|
|
18
18
|
"It must contain exactly one <agent_response>...</agent_response> envelope.",
|
|
19
19
|
"Inside the optional `xml` code fence, the first content must be <agent_response>",
|
|
20
20
|
"and the last content must be </agent_response>; do not put text outside the envelope.",
|
|
21
|
+
"Write large code or file payloads as CDATA (<![CDATA[...]]>) inside <content>, <new_text>, or <message> — never raw code directly inside the envelope — and never include the sequence ]]> inside CDATA content.",
|
|
21
22
|
].join(" ");
|
|
22
23
|
|
|
23
24
|
export function wrapSystemPrompt(text) {
|
|
@@ -29,14 +29,14 @@ const DONE_SEMANTICS = `Current-run completion semantics:
|
|
|
29
29
|
function buildBootstrapScaffold({ projectRoot, tools }) {
|
|
30
30
|
const toolDocs = tools.map(formatTool).join("\n\n");
|
|
31
31
|
|
|
32
|
-
return `The user is running WTAgent, a local application that uses this
|
|
32
|
+
return `The user is running WTAgent, a local application that uses this web AI conversation for reasoning. The following is the user's requested application-level response format and collaboration contract; it is not a claim that this web chat has native filesystem or function-call tools.
|
|
33
33
|
|
|
34
|
-
You do not need direct filesystem access or
|
|
34
|
+
You do not need direct filesystem access or provider-native tool buttons. Return local operation requests as XML text. After your reply is complete, the user's local Node.js Runtime will parse the XML, validate the arguments, apply local policy, and may execute the requested operation. Its result will arrive in the next user message as <tool_result>. XML by itself never guarantees execution.
|
|
35
35
|
|
|
36
36
|
You are not limited to coding tasks. You can answer questions, write text, brainstorm, analyze, summarize, and — when the task requires it — request that the user's Runtime read, create, or modify files or run commands.
|
|
37
37
|
|
|
38
38
|
## Filesystem boundary
|
|
39
|
-
The project filesystem described below is a logical, virtual filesystem namespace exposed by the local Runtime. It is not mounted in
|
|
39
|
+
The project filesystem described below is a logical, virtual filesystem namespace exposed by the local Runtime. It is not mounted in the web provider's own environment and cannot be inspected directly from this webpage.
|
|
40
40
|
|
|
41
41
|
Do not inspect /workspace, /mnt/data, or any ambient, cloud, or sandbox filesystem. Those locations are unrelated to the user's project. Request all project reads, listings, writes, edits, and commands only through the XML operations declared below.
|
|
42
42
|
|
|
@@ -98,11 +98,15 @@ The project directory may contain content unrelated to the current task: depende
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// Returns the pieces needed by both transports:
|
|
101
|
-
// web - the exact text to send to
|
|
101
|
+
// web - the exact text to send to the web AI (scaffold is wrapped in
|
|
102
102
|
// <agent_protocol> markers, the user task follows outside them)
|
|
103
103
|
// developer - the transport scaffold, exposed for diagnostics/tests only
|
|
104
104
|
// user - the user task, for the canonical user message
|
|
105
|
-
export function buildBootstrapPrompt({
|
|
105
|
+
export function buildBootstrapPrompt({
|
|
106
|
+
task,
|
|
107
|
+
projectRoot,
|
|
108
|
+
tools,
|
|
109
|
+
}) {
|
|
106
110
|
const developer = buildBootstrapScaffold({ projectRoot, tools });
|
|
107
111
|
const web = `${wrapSystemPrompt(developer)}\n\n## User task\n${task}`;
|
|
108
112
|
return { web, developer, user: task };
|