zames_pro 2.89.0 → 2.90.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 +53 -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/TuiEditor.js +21 -4
- 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,77 @@
|
|
|
1
|
+
// Post-edit "is it really complete?" nudge.
|
|
2
|
+
//
|
|
3
|
+
// Why: the model can generate an INCOMPLETE artifact and declare the task
|
|
4
|
+
// done. A live case: Qwen wrote `snake.html` with a scene, a camera and a
|
|
5
|
+
// board but WITHOUT the game loop, movement/food logic or key handlers, then
|
|
6
|
+
// said DONE — the page rendered black. That is model behaviour, but the loop
|
|
7
|
+
// can help: if code files were WRITTEN/EDITED in this task and the model is
|
|
8
|
+
// about to finish WITHOUT having run a single command (Bash) since the last
|
|
9
|
+
// edit, it never verified that the artifact is complete and runs. Nudge it
|
|
10
|
+
// ONCE to check (not to block), mirroring the structural guards: a plain
|
|
11
|
+
// "please verify" line the model can answer with a tool call or a confirmed
|
|
12
|
+
// respond.
|
|
13
|
+
//
|
|
14
|
+
// Kept PURE and narrow on purpose: no filesystem access, no word matching of
|
|
15
|
+
// the answer — only the STRUCTURE of the run (which tools ran, in what order).
|
|
16
|
+
// Extensions whose result is an executable artifact: a page, a script, a
|
|
17
|
+
// module. Markdown/json/config edits are NOT here — "does it run" makes no
|
|
18
|
+
// sense for them, and nudging on every README edit would be noise.
|
|
19
|
+
const RUNNABLE_EXT = new Set([
|
|
20
|
+
'.html',
|
|
21
|
+
'.htm',
|
|
22
|
+
'.js',
|
|
23
|
+
'.mjs',
|
|
24
|
+
'.cjs',
|
|
25
|
+
'.ts',
|
|
26
|
+
'.tsx',
|
|
27
|
+
'.jsx',
|
|
28
|
+
'.py',
|
|
29
|
+
'.sh',
|
|
30
|
+
'.rb',
|
|
31
|
+
'.go',
|
|
32
|
+
'.rs',
|
|
33
|
+
'.java',
|
|
34
|
+
'.php',
|
|
35
|
+
'.css',
|
|
36
|
+
]);
|
|
37
|
+
export function isRunnablePath(p) {
|
|
38
|
+
if (!p)
|
|
39
|
+
return false;
|
|
40
|
+
const lower = p.toLowerCase();
|
|
41
|
+
const dot = lower.lastIndexOf('.');
|
|
42
|
+
if (dot < 0)
|
|
43
|
+
return false;
|
|
44
|
+
return RUNNABLE_EXT.has(lower.slice(dot));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* True when the model is about to finish after WRITING/EDITING a runnable
|
|
48
|
+
* code file and has run NO Bash since that last edit — i.e. nothing verified
|
|
49
|
+
* that the artifact is complete and actually runs.
|
|
50
|
+
*
|
|
51
|
+
* `steps` is the ordered history of tool calls in this task.
|
|
52
|
+
*/
|
|
53
|
+
export function needsCompletionNudge(steps) {
|
|
54
|
+
let lastEditIndex = -1;
|
|
55
|
+
let lastEditRunnable = false;
|
|
56
|
+
for (let i = 0; i < steps.length; i++) {
|
|
57
|
+
const s = steps[i];
|
|
58
|
+
if (s.mutating && isRunnablePath(s.path)) {
|
|
59
|
+
lastEditIndex = i;
|
|
60
|
+
lastEditRunnable = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (lastEditIndex < 0 || !lastEditRunnable)
|
|
64
|
+
return false;
|
|
65
|
+
// A Bash/verification run AFTER the last code edit counts as a check.
|
|
66
|
+
for (let i = lastEditIndex + 1; i < steps.length; i++) {
|
|
67
|
+
if (steps[i].tool === 'Bash')
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
export const COMPLETION_NUDGE = 'Before finishing: you wrote/edited a runnable code file in this task, but ' +
|
|
73
|
+
'you have not run any command since that edit. Check that the file is ' +
|
|
74
|
+
'COMPLETE and actually runs (e.g. syntax-check it, open/parse it, run its ' +
|
|
75
|
+
'tests or start it briefly) with one Bash call. If it is already verified ' +
|
|
76
|
+
'another way, or the task is genuinely done, call respond with the final ' +
|
|
77
|
+
'message.';
|
package/dist/config-schema.js
CHANGED
|
@@ -263,6 +263,14 @@ export const CONFIG_SCHEMA = [
|
|
|
263
263
|
topKey: 'cfg.top.browser',
|
|
264
264
|
groupKey: 'cfg.group.login',
|
|
265
265
|
},
|
|
266
|
+
{
|
|
267
|
+
path: 'browser.passwordStore',
|
|
268
|
+
type: 'enum',
|
|
269
|
+
values: ['none', 'keychain', 'config'],
|
|
270
|
+
labelKey: 'cfg.f.browser_passwordStore',
|
|
271
|
+
topKey: 'cfg.top.browser',
|
|
272
|
+
groupKey: 'cfg.group.login',
|
|
273
|
+
},
|
|
266
274
|
{
|
|
267
275
|
path: 'browser.auth.saveSession',
|
|
268
276
|
type: 'boolean',
|
package/dist/config.js
CHANGED
|
@@ -14,6 +14,10 @@ const HOME_CONFIG = path.join(ZAMES_HOME, 'config.json');
|
|
|
14
14
|
// The PROJECT config path is resolved LAZILY: `/cd` changes process.cwd() at
|
|
15
15
|
// runtime, so freezing it at import time meant a `.zamesrc.json` in the new
|
|
16
16
|
// working directory was never read (and writes went to the OLD directory).
|
|
17
|
+
/** The HOME config file (`~/.zames/config.json`). */
|
|
18
|
+
export function homeConfigPath() {
|
|
19
|
+
return HOME_CONFIG;
|
|
20
|
+
}
|
|
17
21
|
export function projectConfigPath() {
|
|
18
22
|
return path.join(process.cwd(), '.zamesrc.json');
|
|
19
23
|
}
|
|
@@ -121,6 +125,13 @@ export const DEFAULTS = {
|
|
|
121
125
|
// out of the main context, and the main chat is restored afterwards.
|
|
122
126
|
subagents: true,
|
|
123
127
|
maxSubagents: 3,
|
|
128
|
+
// N8: providers a task may CONTINUE on after the active one exhausted its
|
|
129
|
+
// retries. Empty by default (off): a fallback is only used for an
|
|
130
|
+
// AUTHORIZED provider (auth marker + a confirmed login), never a guest.
|
|
131
|
+
fallbackProviders: [],
|
|
132
|
+
// `ask` (the default) asks the operator before switching; `auto` switches
|
|
133
|
+
// without a prompt.
|
|
134
|
+
fallbackMode: 'ask',
|
|
124
135
|
deepThinking: false,
|
|
125
136
|
webSearch: true,
|
|
126
137
|
// OFF by default: the telemetry file only serves diagnosis and manual
|
|
@@ -133,6 +144,9 @@ export const DEFAULTS = {
|
|
|
133
144
|
password: '',
|
|
134
145
|
saveSession: true,
|
|
135
146
|
},
|
|
147
|
+
// N21: where the password lives. `config` keeps the historical field,
|
|
148
|
+
// `none` never stores it, `keychain` uses the OS store (with a fallback).
|
|
149
|
+
passwordStore: 'config',
|
|
136
150
|
},
|
|
137
151
|
ui: {
|
|
138
152
|
locale: DEFAULT_LOCALE,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// N2 group (e): the DEV-ONLY self-development commands — /improve, /backlog,
|
|
2
|
+
// /self-review, /self-fix, /self-done, /self-list, /self-diff, /self-apply.
|
|
3
|
+
//
|
|
4
|
+
// They are registry entries with `devOnly: true`, so the guard that used to be
|
|
5
|
+
// a separate pre-check in the main loop is the SAME data the dispatch reads:
|
|
6
|
+
// a regular user gets `msg.dev_only`, dev mode runs the handler. The list here
|
|
7
|
+
// and `DEV_ONLY_COMMANDS` in src/commands.ts are kept in sync by a unit test.
|
|
8
|
+
export function createDevCommands(deps) {
|
|
9
|
+
return [
|
|
10
|
+
{ name: 'improve', devOnly: true, handler: (c) => deps.improve(c.rest) },
|
|
11
|
+
{ name: 'backlog', devOnly: true, handler: (c) => deps.backlog(c.rest) },
|
|
12
|
+
{
|
|
13
|
+
name: 'self-review',
|
|
14
|
+
devOnly: true,
|
|
15
|
+
handler: (c) => deps.selfReview(c.rest),
|
|
16
|
+
},
|
|
17
|
+
{ name: 'self-fix', devOnly: true, handler: (c) => deps.selfFix(c.rest) },
|
|
18
|
+
{ name: 'self-done', devOnly: true, handler: () => deps.selfDone() },
|
|
19
|
+
{ name: 'self-list', devOnly: true, handler: () => deps.selfList() },
|
|
20
|
+
{ name: 'self-diff', devOnly: true, handler: (c) => deps.selfDiff(c.rest) },
|
|
21
|
+
{
|
|
22
|
+
name: 'self-apply',
|
|
23
|
+
devOnly: true,
|
|
24
|
+
handler: (c) => deps.selfApply(c.rest),
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
// N4: in dev mode a source file that is NOT hot-reloadable silently keeps
|
|
4
|
+
// running its OLD copy after an edit — the fix "does not work" until zames is
|
|
5
|
+
// restarted. Only `browser.ts` used to be watched. This module computes the
|
|
6
|
+
// FULL set of such files from the source tree, so an edit to index.ts,
|
|
7
|
+
// run-task.ts, a provider browser, browser-login.ts, send-*.ts or the Ink TUI
|
|
8
|
+
// is reported too.
|
|
9
|
+
/**
|
|
10
|
+
* Names (without extension) of the modules that /reload can hot-swap. Kept
|
|
11
|
+
* here so the watcher and `index.ts`'s RELOADABLE list cannot drift: the
|
|
12
|
+
* test asserts every entry is a real module.
|
|
13
|
+
*/
|
|
14
|
+
export const HOT_RELOADABLE = new Set([
|
|
15
|
+
'mcp',
|
|
16
|
+
'tools',
|
|
17
|
+
'extraTools',
|
|
18
|
+
'agent-loop',
|
|
19
|
+
'system-prompt',
|
|
20
|
+
'gitTools',
|
|
21
|
+
'web',
|
|
22
|
+
'self-review',
|
|
23
|
+
'diff',
|
|
24
|
+
'undo',
|
|
25
|
+
'checkpoint',
|
|
26
|
+
'transcript',
|
|
27
|
+
'spinner',
|
|
28
|
+
'config',
|
|
29
|
+
'fsutil',
|
|
30
|
+
'backlog',
|
|
31
|
+
'commands',
|
|
32
|
+
'run-task',
|
|
33
|
+
'attach-refs',
|
|
34
|
+
]);
|
|
35
|
+
/** A source file in `src/` (recursively) whose basename — without `.ts`/`.tsx` — is not hot-reloadable. */
|
|
36
|
+
export function nonReloadableSources(srcDir, hot = HOT_RELOADABLE) {
|
|
37
|
+
const out = [];
|
|
38
|
+
const walk = (dir) => {
|
|
39
|
+
let entries;
|
|
40
|
+
try {
|
|
41
|
+
entries = readdirSync(dir);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
for (const name of entries) {
|
|
47
|
+
const full = path.join(dir, name);
|
|
48
|
+
let isDir;
|
|
49
|
+
try {
|
|
50
|
+
isDir = statSync(full).isDirectory();
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (isDir) {
|
|
56
|
+
walk(full);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const m = /\.(ts|tsx)$/.exec(name);
|
|
60
|
+
if (!m)
|
|
61
|
+
continue;
|
|
62
|
+
const base = name.slice(0, -m[0].length);
|
|
63
|
+
// `index.ts` IS the entry point (it is not reloaded either), so it is
|
|
64
|
+
// included like any other non-reloadable module.
|
|
65
|
+
if (!hot.has(base))
|
|
66
|
+
out.push(full);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
walk(srcDir);
|
|
70
|
+
return out.sort();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Compare the mtimes of the watched files with the previous snapshot and
|
|
74
|
+
* return the CHANGED ones (first call initializes the snapshot and returns
|
|
75
|
+
* an empty list).
|
|
76
|
+
*/
|
|
77
|
+
export function diffChanged(files, previous) {
|
|
78
|
+
const changed = [];
|
|
79
|
+
for (const f of files) {
|
|
80
|
+
let mtime;
|
|
81
|
+
try {
|
|
82
|
+
mtime = statSync(f).mtimeMs;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const prev = previous.get(f);
|
|
88
|
+
if (prev === undefined)
|
|
89
|
+
previous.set(f, mtime);
|
|
90
|
+
else if (mtime !== prev) {
|
|
91
|
+
previous.set(f, mtime);
|
|
92
|
+
changed.push(f);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return changed;
|
|
96
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// N19: the FIRST-RUN WIZARD decision, as a pure state machine.
|
|
2
|
+
//
|
|
3
|
+
// A new user is dropped straight into DeepSeek with no explanation: which
|
|
4
|
+
// language, which provider, why DeepSeek needs a real sign-in while Qwen/GLM
|
|
5
|
+
// have guest limits. The wizard is shown ONLY on a truly fresh install (no
|
|
6
|
+
// `~/.zames/config.json`), never in a non-TTY / one-shot / non-interactive
|
|
7
|
+
// login run, and Esc accepts the defaults.
|
|
8
|
+
//
|
|
9
|
+
// Only the STEP LOGIC lives here (unit-tested); the Ink widgets (SelectList,
|
|
10
|
+
// LoginForm) already exist and are rendered by the caller.
|
|
11
|
+
export function initialWizardState() {
|
|
12
|
+
return { step: 0, locale: null, provider: null, needsLogin: false };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Should the wizard run at all? A fresh HOME only: an existing config means
|
|
16
|
+
* the operator already made these choices, and a non-interactive run (a pipe,
|
|
17
|
+
* `--task`, `ZAMES_NONINTERACTIVE_LOGIN=1`) must never block on a modal.
|
|
18
|
+
*/
|
|
19
|
+
export function shouldRunWizard(opts) {
|
|
20
|
+
if (opts.configExists)
|
|
21
|
+
return false;
|
|
22
|
+
if (!opts.isTTY)
|
|
23
|
+
return false;
|
|
24
|
+
if (opts.nonInteractiveLogin)
|
|
25
|
+
return false;
|
|
26
|
+
if (opts.oneShot)
|
|
27
|
+
return false;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
/** The step currently shown. */
|
|
31
|
+
export function currentStep(s) {
|
|
32
|
+
if (s.step === 0)
|
|
33
|
+
return { kind: 'language' };
|
|
34
|
+
if (s.step === 1)
|
|
35
|
+
return { kind: 'provider' };
|
|
36
|
+
if (s.step === 2 && s.provider && s.needsLogin) {
|
|
37
|
+
return { kind: 'login', provider: s.provider };
|
|
38
|
+
}
|
|
39
|
+
return { kind: 'done' };
|
|
40
|
+
}
|
|
41
|
+
/** Pick the interface language, then move on. */
|
|
42
|
+
export function chooseLanguage(s, locale) {
|
|
43
|
+
return { ...s, locale, step: 1 };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Pick a provider. DeepSeek ALWAYS needs a real sign-in; Qwen and GLM can be
|
|
47
|
+
* tried as a guest, but the wizard says honestly that the guest tier is a few
|
|
48
|
+
* turns a day (Qwen) and that GLM gates the first send with a captcha.
|
|
49
|
+
*/
|
|
50
|
+
export function chooseProvider(s, provider) {
|
|
51
|
+
return {
|
|
52
|
+
...s,
|
|
53
|
+
provider,
|
|
54
|
+
needsLogin: provider === 'deepseek' || provider === 'glm',
|
|
55
|
+
step: 2,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** The login step finished (or was skipped): the wizard is over. */
|
|
59
|
+
export function finishWizard(s) {
|
|
60
|
+
return { ...s, step: 3 };
|
|
61
|
+
}
|
|
62
|
+
/** Esc at any step accepts the current defaults (never blocks the first run). */
|
|
63
|
+
export function acceptDefaults(s) {
|
|
64
|
+
return {
|
|
65
|
+
locale: s.locale ?? 'en',
|
|
66
|
+
provider: s.provider ?? 'deepseek',
|
|
67
|
+
needsLogin: false,
|
|
68
|
+
step: 3,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** One-line honest note per provider, shown under the choice. */
|
|
72
|
+
export function providerNote(provider) {
|
|
73
|
+
if (provider === 'deepseek') {
|
|
74
|
+
return 'DeepSeek needs a real sign-in (a fresh headless login may ask for a captcha in a visible window).';
|
|
75
|
+
}
|
|
76
|
+
if (provider === 'qwen') {
|
|
77
|
+
return 'Qwen works as a guest for a few messages a day; sign in for long tasks.';
|
|
78
|
+
}
|
|
79
|
+
return 'GLM gates the first send with a slider captcha: a visible window (--headed) is the practical mode.';
|
|
80
|
+
}
|