zames_pro 2.80.8 → 2.81.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/CHANGELOG.md +50 -0
- package/README.md +71 -21
- package/dist/browser-login.js +179 -0
- package/dist/browser-pure.js +112 -8
- package/dist/browser.js +274 -16
- package/dist/commands.js +24 -8
- package/dist/config-command.js +5 -1
- package/dist/config.js +110 -2
- package/dist/glm-browser.js +1770 -0
- package/dist/glm-chats.js +170 -0
- package/dist/glm-flags.js +75 -0
- package/dist/glm-net-capture.js +338 -0
- package/dist/glm-ui.js +171 -0
- package/dist/i18n/common.js +18 -2
- package/dist/i18n/config.js +41 -1
- package/dist/i18n/doctor.js +11 -6
- package/dist/i18n/glm.js +86 -0
- package/dist/i18n/help.js +14 -10
- package/dist/i18n/qwen.js +47 -15
- package/dist/i18n/self-review.js +2 -2
- package/dist/i18n.js +2 -0
- package/dist/index.js +332 -114
- package/dist/provider-handoff.js +11 -0
- package/dist/providers.js +65 -0
- package/dist/qwen-browser.js +274 -30
- package/dist/send-runtime.js +112 -0
- package/dist/send-telemetry.js +99 -0
- package/dist/send-throttle.js +108 -0
- package/dist/system-prompt.js +23 -2
- package/package.json +3 -2
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Send TELEMETRY — one JSONL line per agent send, per provider.
|
|
2
|
+
//
|
|
3
|
+
// WHY: the send interval is currently a hand-tuned constant (DeepSeek 15 s,
|
|
4
|
+
// Qwen 4 s, GLM 0) and the real limits are only visible in the OUTCOME of a
|
|
5
|
+
// send: a rate limit, a busy server, a truncated turn. Recording every send
|
|
6
|
+
// with its pause and its outcome turns "what is the limit?" into data instead
|
|
7
|
+
// of a guess, and feeds the adaptive throttle (src/send-throttle.ts).
|
|
8
|
+
//
|
|
9
|
+
// NOTHING about the prompt or the answer is recorded: only timing, the mode
|
|
10
|
+
// flags, and the error code/headers a server returned. The file is rotated so
|
|
11
|
+
// an unattended run cannot grow it without bound.
|
|
12
|
+
//
|
|
13
|
+
// The module is PURE apart from the append/rotate helpers, which take the
|
|
14
|
+
// directory as an argument (unit-tested with a temp dir).
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
/** One line of the telemetry file (PURE). */
|
|
18
|
+
export function formatSendEvent(e) {
|
|
19
|
+
return JSON.stringify(e);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* PURE: parse the raw JSONL text into events, skipping a broken line (a
|
|
23
|
+
* half-written last line is expected after a crash and must not poison the
|
|
24
|
+
* statistics).
|
|
25
|
+
*/
|
|
26
|
+
export function parseSendEvents(raw) {
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const line of String(raw || '').split(String.fromCharCode(10))) {
|
|
29
|
+
const t = line.trim();
|
|
30
|
+
if (!t)
|
|
31
|
+
continue;
|
|
32
|
+
try {
|
|
33
|
+
const obj = JSON.parse(t);
|
|
34
|
+
if (obj && typeof obj === 'object' && typeof obj.outcome === 'string') {
|
|
35
|
+
out.push(obj);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch { }
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
/** PURE: aggregate the last events for the /limits view. */
|
|
43
|
+
export function summarizeSends(events, now = Date.now()) {
|
|
44
|
+
const within = (ms) => events.filter((e) => {
|
|
45
|
+
const t = Date.parse(e.at);
|
|
46
|
+
return Number.isFinite(t) && now - t <= ms;
|
|
47
|
+
}).length;
|
|
48
|
+
const blocks = events
|
|
49
|
+
.filter((e) => e.outcome === 'rate_limit' && typeof e.blockMs === 'number')
|
|
50
|
+
.map((e) => e.blockMs);
|
|
51
|
+
const avgBlockMs = blocks.length
|
|
52
|
+
? Math.round(blocks.reduce((a, b) => a + b, 0) / blocks.length)
|
|
53
|
+
: 0;
|
|
54
|
+
return {
|
|
55
|
+
last10m: within(10 * 60_000),
|
|
56
|
+
last30m: within(30 * 60_000),
|
|
57
|
+
last60m: within(60 * 60_000),
|
|
58
|
+
rateLimits: events.filter((e) => e.outcome === 'rate_limit').length,
|
|
59
|
+
avgBlockMs,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** `~/.zames/logs/sends-<provider>.jsonl`. */
|
|
63
|
+
export function sendLogPath(logsDir, provider) {
|
|
64
|
+
return path.join(logsDir, 'sends-' + provider + '.jsonl');
|
|
65
|
+
}
|
|
66
|
+
/** Keep at most `keep` sends-*.jsonl files (rotate the oldest away). */
|
|
67
|
+
export function rotateSendLogs(logsDir, keep = 5) {
|
|
68
|
+
try {
|
|
69
|
+
const files = fs
|
|
70
|
+
.readdirSync(logsDir)
|
|
71
|
+
.filter((f) => /^sends-.*\.jsonl$/.test(f))
|
|
72
|
+
.map((f) => ({ f, t: fs.statSync(path.join(logsDir, f)).mtimeMs }))
|
|
73
|
+
.sort((a, b) => b.t - a.t);
|
|
74
|
+
for (const old of files.slice(keep)) {
|
|
75
|
+
try {
|
|
76
|
+
fs.unlinkSync(path.join(logsDir, old.f));
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch { }
|
|
82
|
+
}
|
|
83
|
+
/** Append one event. Best-effort: a logging failure never breaks a send. */
|
|
84
|
+
export function appendSendEvent(logsDir, e) {
|
|
85
|
+
try {
|
|
86
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
87
|
+
fs.appendFileSync(sendLogPath(logsDir, e.provider), formatSendEvent(e) + String.fromCharCode(10), 'utf-8');
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
}
|
|
91
|
+
/** Read the recent events for one provider (best-effort, [] on failure). */
|
|
92
|
+
export function readSendEvents(logsDir, provider) {
|
|
93
|
+
try {
|
|
94
|
+
return parseSendEvents(fs.readFileSync(sendLogPath(logsDir, provider), 'utf-8'));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Adaptive SEND THROTTLE — how long to wait before the next agent send.
|
|
2
|
+
//
|
|
3
|
+
// WHY: a fixed interval is wrong in both directions. Too small and the site
|
|
4
|
+
// answers with a rate limit whose PENALTY grows with the overrun (DeepSeek: a
|
|
5
|
+
// zero-gap burst ran ~30 min, then a ~30 min block). Too large and every turn
|
|
6
|
+
// pays a pause that is usually unnecessary. The throttle learns from the
|
|
7
|
+
// telemetry: it stays below a soft budget, backs off on a real limit and
|
|
8
|
+
// recovers gradually after a run of successes.
|
|
9
|
+
//
|
|
10
|
+
// The module is PURE: it takes the current state and one event and returns the
|
|
11
|
+
// next state + the wait. The browser owns the state, so switching providers or
|
|
12
|
+
// finishing a task does not reset what was learned.
|
|
13
|
+
export const DEFAULT_THROTTLE = {
|
|
14
|
+
windowMs: 10 * 60_000,
|
|
15
|
+
// Off until the operator has data: an infinite budget never fires.
|
|
16
|
+
budget: Number.POSITIVE_INFINITY,
|
|
17
|
+
recoverAfter: 3,
|
|
18
|
+
maxMs: 60_000,
|
|
19
|
+
limitFloorMs: 2000,
|
|
20
|
+
};
|
|
21
|
+
export function initialThrottleState(cfg) {
|
|
22
|
+
return {
|
|
23
|
+
intervalMs: Math.max(0, cfg.baseMs),
|
|
24
|
+
okStreak: 0,
|
|
25
|
+
blocked: false,
|
|
26
|
+
blockedAt: 0,
|
|
27
|
+
forcedWaitMs: 0,
|
|
28
|
+
recent: [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Drop sends older than the window (keeps the state small). */
|
|
32
|
+
function prune(recent, now, windowMs) {
|
|
33
|
+
return recent.filter((t) => now - t <= windowMs);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* PURE: the wait before the next send, given the state and `now`. A blocked
|
|
37
|
+
* throttle waits out the forced pause; otherwise the learned interval applies
|
|
38
|
+
* since the last send.
|
|
39
|
+
*/
|
|
40
|
+
export function nextWaitMs(state, lastSentAt, now) {
|
|
41
|
+
if (state.forcedWaitMs > 0)
|
|
42
|
+
return state.forcedWaitMs;
|
|
43
|
+
if (!lastSentAt)
|
|
44
|
+
return 0;
|
|
45
|
+
const gap = state.intervalMs - (now - lastSentAt);
|
|
46
|
+
return gap > 0 ? gap : 0;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* PURE: fold one telemetry event into the state.
|
|
50
|
+
*
|
|
51
|
+
* Rules (the operator's strategy):
|
|
52
|
+
* - a soft budget: too many sends in the window GROW the interval smoothly,
|
|
53
|
+
* ahead of a ban;
|
|
54
|
+
* - a rate limit: wait the server's own Retry-After when present, else the
|
|
55
|
+
* configured pause; after it lifts the interval doubles (from a floor);
|
|
56
|
+
* - after `recoverAfter` successes in a row the interval halves, down to the
|
|
57
|
+
* base.
|
|
58
|
+
*/
|
|
59
|
+
export function applySendEvent(state, event, cfg, now = Date.now()) {
|
|
60
|
+
const next = { ...state, recent: [...state.recent] };
|
|
61
|
+
const at = Date.parse(event.at);
|
|
62
|
+
const stamp = Number.isFinite(at) ? at : now;
|
|
63
|
+
if (event.outcome === 'ok') {
|
|
64
|
+
next.recent.push(stamp);
|
|
65
|
+
next.recent = prune(next.recent, now, cfg.windowMs);
|
|
66
|
+
next.okStreak = state.okStreak + 1;
|
|
67
|
+
if (next.okStreak >= cfg.recoverAfter) {
|
|
68
|
+
next.intervalMs = Math.max(cfg.baseMs, Math.floor(next.intervalMs / 2));
|
|
69
|
+
next.okStreak = 0;
|
|
70
|
+
}
|
|
71
|
+
next.blocked = false;
|
|
72
|
+
next.blockedAt = 0;
|
|
73
|
+
next.forcedWaitMs = 0;
|
|
74
|
+
}
|
|
75
|
+
else if (event.outcome === 'rate_limit') {
|
|
76
|
+
next.blocked = true;
|
|
77
|
+
next.blockedAt = state.blocked ? state.blockedAt || stamp : stamp;
|
|
78
|
+
next.okStreak = 0;
|
|
79
|
+
next.forcedWaitMs =
|
|
80
|
+
typeof event.retryAfterMs === 'number' && event.retryAfterMs > 0
|
|
81
|
+
? event.retryAfterMs
|
|
82
|
+
: cfg.rateLimitWaitMs;
|
|
83
|
+
const floor = Math.max(cfg.baseMs, cfg.limitFloorMs);
|
|
84
|
+
next.intervalMs = Math.min(cfg.maxMs, Math.max(next.intervalMs, floor) * 2);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// busy / incomplete / guest_limit: not a rate signal, keep the interval.
|
|
88
|
+
next.recent.push(stamp);
|
|
89
|
+
next.recent = prune(next.recent, now, cfg.windowMs);
|
|
90
|
+
}
|
|
91
|
+
// Soft budget: a smooth growth once the window is over budget.
|
|
92
|
+
if (Number.isFinite(cfg.budget) && next.recent.length > cfg.budget) {
|
|
93
|
+
const over = next.recent.length - cfg.budget;
|
|
94
|
+
next.intervalMs = Math.min(cfg.maxMs, Math.max(next.intervalMs, cfg.baseMs) + over * 1000);
|
|
95
|
+
}
|
|
96
|
+
return next;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* PURE: a rate limit was observed and the block ended — record its duration on
|
|
100
|
+
* the event so the telemetry can average it.
|
|
101
|
+
*/
|
|
102
|
+
export function withBlockDuration(event, blockedAt, liftedAt) {
|
|
103
|
+
if (event.outcome !== 'rate_limit')
|
|
104
|
+
return event;
|
|
105
|
+
if (!blockedAt)
|
|
106
|
+
return event;
|
|
107
|
+
return { ...event, blockMs: Math.max(0, liftedAt - blockedAt) };
|
|
108
|
+
}
|
package/dist/system-prompt.js
CHANGED
|
@@ -195,7 +195,7 @@ export function renderToolSection(tools) {
|
|
|
195
195
|
.map((t2) => `### ${t2.name}\n${t2.description}\nParameters: ${JSON.stringify(t2.parameters)}`)
|
|
196
196
|
.join('\n\n');
|
|
197
197
|
}
|
|
198
|
-
export function buildSystemPrompt({ workdir, tools, gitContext = null, locale = 'ru', attachments = [], context = null, selfImprovement = false, }) {
|
|
198
|
+
export function buildSystemPrompt({ workdir, tools, gitContext = null, locale = 'ru', attachments = [], context = null, selfImprovement = false, provider, }) {
|
|
199
199
|
const t = translate(locale);
|
|
200
200
|
const toolDescriptions = renderToolSection(tools);
|
|
201
201
|
// Emit the subagent guidance ONLY when the Task tool is actually in the set
|
|
@@ -208,6 +208,27 @@ export function buildSystemPrompt({ workdir, tools, gitContext = null, locale =
|
|
|
208
208
|
const askUserSection = tools.some((x) => x.name === 'AskUser')
|
|
209
209
|
? ASKUSER_SECTION
|
|
210
210
|
: '';
|
|
211
|
+
// GLM runs on chat.z.ai, whose OWN agent mode used to execute tool calls in
|
|
212
|
+
// a cloud sandbox instead of the operator's machine. The provider neutralises
|
|
213
|
+
// that mode (see glm-browser.ts), but the model must also be TOLD where it
|
|
214
|
+
// runs: otherwise it asks for a sandbox ("Limit Sandbox Concurrency") or
|
|
215
|
+
// claims it ran something it did not.
|
|
216
|
+
const runtimeSection = provider === 'glm'
|
|
217
|
+
? "\n## You run on the OPERATOR'S MACHINE (GLM)\n\n" +
|
|
218
|
+
"The tools above execute on the operator's computer, in the working " +
|
|
219
|
+
'directory shown at the top. They are the ONLY way to touch files or ' +
|
|
220
|
+
'run commands.\n\n' +
|
|
221
|
+
'- There is NO cloud sandbox for you. Do NOT request or use a z.ai ' +
|
|
222
|
+
'sandbox/VM; a "Limit Sandbox Concurrency" page is a leftover of the ' +
|
|
223
|
+
"site's own agent mode and is NOT part of this task.\n" +
|
|
224
|
+
'- Never claim a command ran unless you emitted its tool call and ' +
|
|
225
|
+
'received a tool result. If you have not called the tool, say so.\n' +
|
|
226
|
+
"- Paths like `/home/z/...` or `/mnt/data` are NOT the operator's " +
|
|
227
|
+
'machine.\n' +
|
|
228
|
+
'- Do NOT use web search / WebSearch unless the task explicitly asks ' +
|
|
229
|
+
'for current web information: it is slow, it burns the account quota ' +
|
|
230
|
+
'and it is not needed to read or edit files.\n'
|
|
231
|
+
: '';
|
|
211
232
|
const gitSection = gitContext
|
|
212
233
|
? `\n## Git context\n\n${gitContext}\n`
|
|
213
234
|
: '\n## Git context\n\nNot a git repository (or git is not installed).\n';
|
|
@@ -269,7 +290,7 @@ task is done (or when you must ask the operator), not between tool calls.
|
|
|
269
290
|
You have access to the following tools:
|
|
270
291
|
|
|
271
292
|
${toolDescriptions}
|
|
272
|
-
${gitSection}${attachSection}${contextSection}${selfSection}
|
|
293
|
+
${runtimeSection}${gitSection}${attachSection}${contextSection}${selfSection}
|
|
273
294
|
## Choosing the right tool
|
|
274
295
|
|
|
275
296
|
Prefer the most specific tool; a wrong choice wastes a turn:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.81.1",
|
|
4
4
|
"description": "Terminal coding agent that drives a real browser to chat with DeepSeek, Qwen and other web LLMs — no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"render-how": "tsx scripts/render-how-it-works.mts",
|
|
42
42
|
"build:readme": "tsx scripts/build-readme.mts",
|
|
43
43
|
"postinstall": "node scripts/postinstall.mjs",
|
|
44
|
-
"prepare": "husky"
|
|
44
|
+
"prepare": "husky",
|
|
45
|
+
"glm-smoke": "tsx scripts/glm-smoke.mts"
|
|
45
46
|
},
|
|
46
47
|
"keywords": [
|
|
47
48
|
"agent",
|