zames_pro 2.89.0 → 2.90.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.
- package/CHANGELOG.md +47 -0
- package/README.md +32 -585
- package/dist/agent-loop.js +47 -4
- package/dist/ask-retry.js +49 -0
- package/dist/ask-state.js +80 -0
- package/dist/browser-base.js +69 -0
- package/dist/browser.js +170 -41
- package/dist/command-registry.js +58 -0
- package/dist/commands.js +25 -2
- package/dist/completion-check.js +77 -0
- package/dist/config-schema.js +8 -0
- package/dist/config.js +14 -0
- package/dist/dev-commands.js +27 -0
- package/dist/dev-watch.js +96 -0
- package/dist/first-run.js +80 -0
- package/dist/glm-browser.js +133 -32
- package/dist/i18n/common.js +6 -3
- package/dist/i18n/config.js +16 -0
- package/dist/i18n/wizard.js +25 -0
- package/dist/i18n.js +6 -1
- package/dist/index.js +1289 -919
- package/dist/info-commands.js +61 -0
- package/dist/input/layout.js +38 -1
- package/dist/input.js +2 -2
- package/dist/password-store.js +48 -0
- package/dist/prompt-budget.js +23 -0
- package/dist/provider-errors.js +53 -0
- package/dist/provider-fallback.js +46 -0
- package/dist/qwen-browser.js +155 -39
- package/dist/run-task.js +2 -1
- package/dist/scheduler-commands.js +41 -0
- package/dist/send-slot.js +36 -0
- package/dist/session-commands.js +42 -0
- package/dist/spinner.js +3 -3
- package/dist/swallow.js +45 -0
- package/dist/system-prompt.js +2 -2
- package/dist/tree-commands.js +36 -0
- package/dist/tui/FullScreenApp.js +13 -4
- package/dist/tui/InputLine.js +20 -3
- package/dist/tui/LogView.js +11 -5
- package/dist/tui/StatusBar.js +4 -1
- package/dist/tui/TuiApp.js +11 -2
- package/dist/tui/layout.js +14 -1
- package/dist/tui/store.js +2 -1
- package/dist/tui/ui-colors.js +2 -0
- package/dist/width.js +10 -5
- package/llms-full.txt +16 -292
- package/package.json +2 -2
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// N2 group (a): the INFORMATIONAL slash commands — /status, /chat, /pwd,
|
|
2
|
+
// /transcript, /cost, /limits, /memory, /skills. Their logic drives live
|
|
3
|
+
// state, so everything is INJECTED (the same DI shape as config-command.ts):
|
|
4
|
+
// the registry calls the handler, the handler asks the deps for data and
|
|
5
|
+
// prints through `ctx.print`. The routing is unit-tested without a browser.
|
|
6
|
+
import { theme } from './theme.js';
|
|
7
|
+
/**
|
|
8
|
+
* Build the informational group. The handlers only gather + print; nothing
|
|
9
|
+
* here mutates session state.
|
|
10
|
+
*/
|
|
11
|
+
export function createInfoCommands(deps) {
|
|
12
|
+
const { t } = deps;
|
|
13
|
+
const printLines = (lines, print) => {
|
|
14
|
+
for (const line of lines)
|
|
15
|
+
print(theme.system(line));
|
|
16
|
+
};
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
name: 'status',
|
|
20
|
+
handler: (ctx) => printLines(deps.statusLines(), ctx.print),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'chat',
|
|
24
|
+
handler: async (ctx) => {
|
|
25
|
+
const id = deps.currentChatId();
|
|
26
|
+
if (id) {
|
|
27
|
+
ctx.print(theme.system(t('chats.current_id', { v: id })));
|
|
28
|
+
ctx.print(theme.system(`URL: ${deps.chatUrl(id)}`));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const live = await deps.resolveChatId();
|
|
32
|
+
ctx.print(theme.system(live ? t('chats.current_id', { v: live }) : t('chats.not_created')));
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'pwd',
|
|
37
|
+
handler: (ctx) => ctx.print(theme.system(deps.workdir())),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'transcript',
|
|
41
|
+
handler: (ctx) => ctx.print(theme.system(deps.transcriptFile() || t('common.off'))),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'cost',
|
|
45
|
+
aliases: ['usage'],
|
|
46
|
+
handler: async (ctx) => printLines(await deps.costReport(), ctx.print),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'limits',
|
|
50
|
+
handler: async (ctx) => printLines(await deps.limitsReport(), ctx.print),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'memory',
|
|
54
|
+
handler: async (ctx) => printLines(await deps.memoryReport(), ctx.print),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'skills',
|
|
58
|
+
handler: async (ctx) => printLines(await deps.skillsReport(), ctx.print),
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
package/dist/input/layout.js
CHANGED
|
@@ -362,6 +362,32 @@ export function truncateToWidth(s, maxCols) {
|
|
|
362
362
|
export function inputInnerCols(cols, borderCols = 2) {
|
|
363
363
|
return Math.max(4, (Number(cols) || 80) - borderCols - 2);
|
|
364
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Horizontal gutter (columns on EACH side) that the full-screen layout keeps
|
|
367
|
+
* free so the UI does not hug the terminal edges. The operator asked for it: on
|
|
368
|
+
* a 170-column terminal the header, the answers and the input box were pinned
|
|
369
|
+
* to column 0 with nothing breathing around them.
|
|
370
|
+
*
|
|
371
|
+
* A gutter is dropped entirely on a narrow terminal (< GUTTER_MIN_COLS): the
|
|
372
|
+
* panes would otherwise lose more width than they gain in looks, and a deep
|
|
373
|
+
* workdir in the header already competes for the same row.
|
|
374
|
+
*/
|
|
375
|
+
export const GUTTER_MIN_COLS = 60;
|
|
376
|
+
export function screenGutter(cols) {
|
|
377
|
+
const c = Math.max(1, Math.floor(Number(cols) || 80));
|
|
378
|
+
return c >= GUTTER_MIN_COLS ? 2 : 0;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Inner width of a full-screen pane: the terminal minus the gutter on BOTH
|
|
382
|
+
* sides. One helper so the header, the log, the status bar and the input box
|
|
383
|
+
* wrap and truncate to the SAME width — a mismatch is exactly the class of bug
|
|
384
|
+
* that clipped an input row or wrapped a header title onto a second line.
|
|
385
|
+
*/
|
|
386
|
+
export function screenInnerCols(cols) {
|
|
387
|
+
const c = Math.max(1, Math.floor(Number(cols) || 80));
|
|
388
|
+
const g = screenGutter(c);
|
|
389
|
+
return Math.max(4, c - g * 2);
|
|
390
|
+
}
|
|
365
391
|
// Real height (rows) of the framed input box for the CURRENT buffer: 2 border
|
|
366
392
|
// rows + the number of VISUAL rows the buffer wraps to. Using the LOGICAL line
|
|
367
393
|
// count (buf.split('\n').length) was wrong: a long line wraps and renders MORE
|
|
@@ -421,10 +447,21 @@ export function layoutInput(promptStr, buf, cursor, cols) {
|
|
|
421
447
|
const atLineEnd = i >= chars.length || chars[i] === NL;
|
|
422
448
|
if (!atLineEnd && widthUsed >= avail && lastSpace > 0) {
|
|
423
449
|
// Move the "tail" of the line to the next visual line.
|
|
450
|
+
//
|
|
451
|
+
// The break happens AT a space, and that space belongs to neither row:
|
|
452
|
+
// it is the wrap point. Leaving it in `text` printed a trailing space at
|
|
453
|
+
// the end of the first row, and leaving it in the tail printed a LEADING
|
|
454
|
+
// space on the continuation row (`❯ ф вйцв …` / ` йца …` — the
|
|
455
|
+
// operator saw the extra indent on every wrapped line). Consume it.
|
|
424
456
|
const textChars = Array.from(text);
|
|
425
|
-
const tailLen = textChars.length - lastSpace;
|
|
457
|
+
const tailLen = textChars.length - lastSpace - 1;
|
|
426
458
|
i -= tailLen;
|
|
427
459
|
text = textChars.slice(0, lastSpace).join('');
|
|
460
|
+
// Skip the space itself so the next row starts at the word.
|
|
461
|
+
i += 0;
|
|
462
|
+
// `text` may still contain the space as its LAST char when the break
|
|
463
|
+
// lands right after it; trim it (the row must not end with blank space).
|
|
464
|
+
text = text.replace(/ +$/, '');
|
|
428
465
|
}
|
|
429
466
|
if (i < chars.length && chars[i] === NL) {
|
|
430
467
|
i++;
|
package/dist/input.js
CHANGED
|
@@ -3,7 +3,7 @@ import { summarizeToolCall } from './tool-summary.js';
|
|
|
3
3
|
import { renderMarkdown } from './markdown.js';
|
|
4
4
|
import { contentWidth } from './width.js';
|
|
5
5
|
import { randomThinkingPhrase, stripEllipsis, DOTS, renderDots, } from './spinner.js';
|
|
6
|
-
import { translate } from './i18n.js';
|
|
6
|
+
import { translate, DEFAULT_LOCALE } from './i18n.js';
|
|
7
7
|
import { AttachmentStore, parseImagePaste, looksLikeFilePath, } from './attachments.js';
|
|
8
8
|
import { NL, CR, ESC, PASTE_START, PASTE_END, SUGGEST_PAGE, pasteReplacement, expandPastes, safeJson, visRows, visLen, charWidth, layoutInput, formatTokenStatus, tokenStatusLevel, truncateToWidth, wrapToWidth, splitPasteTail, CONTEXT_LIMIT, } from './input/layout.js';
|
|
9
9
|
// The pure layout/formatting helpers (visual rows, cursor position, visible
|
|
@@ -197,7 +197,7 @@ export class LineEditor {
|
|
|
197
197
|
// as a mutable field (set from config.ui.contextLimit) so the status bar
|
|
198
198
|
// reflects the real limit instead of a hardcoded constant.
|
|
199
199
|
contextLimit;
|
|
200
|
-
constructor({ prompt = '> ', commands = [], locale =
|
|
200
|
+
constructor({ prompt = '> ', commands = [], locale = DEFAULT_LOCALE, } = {}) {
|
|
201
201
|
this.locale = locale;
|
|
202
202
|
this.promptStr = prompt;
|
|
203
203
|
this.buf = '';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// N21: WHERE a provider password is stored.
|
|
2
|
+
//
|
|
3
|
+
// Today `browser.auth.password` sits in `~/.zames/config.json` as plain text
|
|
4
|
+
// (the file is 0600, which is not encryption). This module owns the DECISION —
|
|
5
|
+
// a pure function, so the choice is testable without touching a keychain:
|
|
6
|
+
//
|
|
7
|
+
// - `none` — do not store the password at all; the session lives in the
|
|
8
|
+
// browser profile and the password is asked for only when that
|
|
9
|
+
// session is gone.
|
|
10
|
+
// - `keychain` — an external OS tool (`secret-tool` on Linux Secret Service,
|
|
11
|
+
// `security` on macOS, PowerShell Credential Manager on
|
|
12
|
+
// Windows). No native npm dependency.
|
|
13
|
+
// - `config` — the historical plain-text field (the fallback).
|
|
14
|
+
//
|
|
15
|
+
// The MIGRATION is explicit: an existing plain password is moved to the chosen
|
|
16
|
+
// store and the config field is cleared, never silently duplicated.
|
|
17
|
+
/**
|
|
18
|
+
* The store that can actually be used right now. `preferred` comes from the
|
|
19
|
+
* operator (`browser.passwordStore`); when it is `keychain` but no tool exists,
|
|
20
|
+
* the caller falls back to `config` WITH a warning — the password must never
|
|
21
|
+
* be silently dropped.
|
|
22
|
+
*/
|
|
23
|
+
export function resolvePasswordStore(preferred, probe) {
|
|
24
|
+
if (preferred !== 'keychain')
|
|
25
|
+
return { store: preferred, degraded: false };
|
|
26
|
+
const available = (probe.platform === 'linux' && probe.secretTool) ||
|
|
27
|
+
(probe.platform === 'darwin' && probe.security) ||
|
|
28
|
+
(probe.platform === 'win32' && probe.powershell);
|
|
29
|
+
if (available)
|
|
30
|
+
return { store: 'keychain', degraded: false };
|
|
31
|
+
return { store: 'config', degraded: true };
|
|
32
|
+
}
|
|
33
|
+
export function planPasswordMigration(existing, store, degraded) {
|
|
34
|
+
const has = !!(existing && existing.trim());
|
|
35
|
+
if (!has)
|
|
36
|
+
return { keychainWrite: null, clearConfig: false, warn: degraded };
|
|
37
|
+
if (store === 'none') {
|
|
38
|
+
return { keychainWrite: null, clearConfig: true, warn: degraded };
|
|
39
|
+
}
|
|
40
|
+
if (store === 'keychain') {
|
|
41
|
+
return { keychainWrite: existing, clearConfig: true, warn: degraded };
|
|
42
|
+
}
|
|
43
|
+
return { keychainWrite: null, clearConfig: false, warn: degraded };
|
|
44
|
+
}
|
|
45
|
+
/** The account name used inside the OS store (one entry per provider). */
|
|
46
|
+
export function keychainAccount(provider) {
|
|
47
|
+
return 'zames:' + provider;
|
|
48
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// N6/N12: the SYSTEM PROMPT BUDGET.
|
|
2
|
+
//
|
|
3
|
+
// The system prompt is sent into EVERY new chat, so it silently eats the web
|
|
4
|
+
// chat's context window before the first task line. `test/system-prompt-size`
|
|
5
|
+
// pins the default size; `/doctor` and `/context` report it in KB, with a
|
|
6
|
+
// warning above the same threshold. One constant for all three, so a raised
|
|
7
|
+
// budget is a deliberate edit here with a reason, not an accident.
|
|
8
|
+
/** Measured ~21.9 KB for the full default tool set (subagents on). */
|
|
9
|
+
export const SYSTEM_PROMPT_BUDGET_BYTES = 24000;
|
|
10
|
+
/**
|
|
11
|
+
* The project-context files (AGENTS.md / MEMORY.md) load into the same
|
|
12
|
+
* prompt. A single huge file is the most common reason for a blown budget, so
|
|
13
|
+
* the CONTEXT budget is reported next to the prompt one.
|
|
14
|
+
*/
|
|
15
|
+
export const CONTEXT_BUDGET_BYTES = 120000;
|
|
16
|
+
/** kB with one decimal, for a human-facing report. */
|
|
17
|
+
export function kb(bytes) {
|
|
18
|
+
return (bytes / 1024).toFixed(1) + ' KB';
|
|
19
|
+
}
|
|
20
|
+
/** True when the size is above the budget (a WARN in /doctor). */
|
|
21
|
+
export function overBudget(bytes, budget) {
|
|
22
|
+
return bytes > budget;
|
|
23
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// N1 stage 2: the error families shared by the chat providers.
|
|
2
|
+
//
|
|
3
|
+
// Each provider used to declare its own copy (`QwenRateLimitError`,
|
|
4
|
+
// `GlmRateLimitError`, ...), so the retry policy in every `ask()` re-listed the
|
|
5
|
+
// same six families and a new one had to be added three times. The classes
|
|
6
|
+
// here are the SHARED vocabulary; the provider keeps its own pre-check
|
|
7
|
+
// (`instanceof`) only where the semantics genuinely differ (guest limits and
|
|
8
|
+
// the age gate exist on one backend only).
|
|
9
|
+
//
|
|
10
|
+
// `provider` is carried for the operator-facing message only — the retry
|
|
11
|
+
// decision is per CLASS, never per string.
|
|
12
|
+
export class ProviderRateLimitError extends Error {
|
|
13
|
+
constructor(detail) {
|
|
14
|
+
super('rate limit: ' + detail);
|
|
15
|
+
this.name = 'ProviderRateLimitError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class ProviderServerBusyError extends Error {
|
|
19
|
+
constructor(detail) {
|
|
20
|
+
super('server busy: ' + detail);
|
|
21
|
+
this.name = 'ProviderServerBusyError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class ProviderContextLimitError extends Error {
|
|
25
|
+
constructor(detail) {
|
|
26
|
+
super('context limit: ' + detail);
|
|
27
|
+
this.name = 'ProviderContextLimitError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class ProviderIncompleteError extends Error {
|
|
31
|
+
constructor(detail) {
|
|
32
|
+
super('generation incomplete: ' + detail);
|
|
33
|
+
this.name = 'ProviderIncompleteError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class ProviderGuestLimitError extends Error {
|
|
37
|
+
constructor(detail) {
|
|
38
|
+
super('guest limit: ' + detail);
|
|
39
|
+
this.name = 'ProviderGuestLimitError';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export class ProviderAgeGateError extends Error {
|
|
43
|
+
constructor(detail) {
|
|
44
|
+
super('age gate: ' + detail);
|
|
45
|
+
this.name = 'ProviderAgeGateError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export class ProviderCaptchaError extends Error {
|
|
49
|
+
constructor(detail) {
|
|
50
|
+
super('captcha: ' + detail);
|
|
51
|
+
this.name = 'ProviderCaptchaError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// N8: the FALLBACK-PROVIDER decision as a PURE function.
|
|
2
|
+
//
|
|
3
|
+
// When the active backend exhausted its retries (rate limit / server busy /
|
|
4
|
+
// an unrecoverable send), the task used to stop even though `/provider` and
|
|
5
|
+
// the existing handoff machinery could continue it on another chat. A
|
|
6
|
+
// candidate is usable ONLY when it is a different, AUTHORIZED provider: a
|
|
7
|
+
// marker in `~/.zames/auth.json` AND a confirmed login (`probeLogin()`). Guest
|
|
8
|
+
// mode is never a fallback — a guest gets a handful of turns a day (Qwen) and
|
|
9
|
+
// GLM gates the first send with a captcha, so the task would die a few steps
|
|
10
|
+
// later, in a worse place.
|
|
11
|
+
//
|
|
12
|
+
// Everything here is data-in, decision-out: the caller supplies the auth
|
|
13
|
+
// marker text, the config flag and the async login probe.
|
|
14
|
+
/**
|
|
15
|
+
* Pick the first usable fallback provider.
|
|
16
|
+
*
|
|
17
|
+
* `hasMarker(id, raw)` is injected (authMarkerHas) so this module stays free
|
|
18
|
+
* of the marker format; `probeLogin(id)` confirms the stored session really
|
|
19
|
+
* works — a marker without a live session is useless (the switch would land on
|
|
20
|
+
* a login page mid-task).
|
|
21
|
+
*/
|
|
22
|
+
export async function pickFallbackProvider(active, candidates, probeLogin, hasMarker) {
|
|
23
|
+
const authorizable = [];
|
|
24
|
+
for (const c of candidates) {
|
|
25
|
+
if (c.id === active)
|
|
26
|
+
continue;
|
|
27
|
+
// A live GUEST session is not a fallback (it dies in a few turns), but the
|
|
28
|
+
// provider is still authorizable — signing in makes it a candidate.
|
|
29
|
+
if (c.guestAllowed && c.guestSession !== false) {
|
|
30
|
+
authorizable.push(c.id);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const marker = hasMarker(c.authMarker, c.id);
|
|
34
|
+
if (!marker) {
|
|
35
|
+
authorizable.push(c.id);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const ok = await probeLogin(c.id).catch(() => false);
|
|
39
|
+
if (ok)
|
|
40
|
+
return { provider: c.id, authorizable };
|
|
41
|
+
// A stale marker is NOT authorizable in place: the session must be renewed
|
|
42
|
+
// interactively, which this path must not start mid-task.
|
|
43
|
+
authorizable.push(c.id);
|
|
44
|
+
}
|
|
45
|
+
return { provider: null, authorizable };
|
|
46
|
+
}
|