zames_pro 2.75.5 → 2.76.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +10 -4
  3. package/dist/agent-loop.js +61 -1
  4. package/dist/browser.js +40 -8
  5. package/dist/commands.js +0 -1
  6. package/dist/config.js +8 -3
  7. package/dist/context.js +99 -0
  8. package/dist/deepseek-ui.js +1 -3
  9. package/dist/i18n/common.js +13 -16
  10. package/dist/i18n/config.js +2 -2
  11. package/dist/i18n/help.js +48 -6
  12. package/dist/i18n/spinner.js +7 -1
  13. package/dist/index.js +10 -50
  14. package/dist/net-capture.js +43 -11
  15. package/dist/provider-handoff.js +0 -4
  16. package/dist/qwen-ui.js +0 -8
  17. package/dist/run-task.js +24 -1
  18. package/dist/shell.js +0 -6
  19. package/dist/system-prompt.js +52 -2
  20. package/dist/tools.js +33 -0
  21. package/dist/tui/AskQuestion.js +108 -0
  22. package/dist/tui/FullScreenApp.js +2 -1
  23. package/dist/tui/InputLine.js +0 -3
  24. package/dist/tui/StatusBar.js +0 -9
  25. package/dist/tui/TuiEditor.js +4 -43
  26. package/dist/tui/edit.js +13 -22
  27. package/dist/tui/keys.js +12 -14
  28. package/dist/tui/run-ask.js +22 -0
  29. package/dist/tui/store.js +0 -2
  30. package/dist/tui/widgets.js +3 -9
  31. package/package.json +2 -2
  32. package/scripts/build-readme.mts +0 -101
  33. package/scripts/changelog-draft.mts +0 -93
  34. package/scripts/clean-dist.mts +0 -17
  35. package/scripts/coverage-gate.mts +0 -112
  36. package/scripts/ctd-live-driver.mts +0 -51
  37. package/scripts/ctd-live.tsx +0 -33
  38. package/scripts/escd-live-driver.mts +0 -37
  39. package/scripts/eslint-ts6.mts +0 -58
  40. package/scripts/inline-status-driver.mts +0 -31
  41. package/scripts/inline-status.tsx +0 -33
  42. package/scripts/mouse-churn.mts +0 -34
  43. package/scripts/qwen-models-smoke.mts +0 -117
  44. package/scripts/qwen-smoke.mts +0 -221
  45. package/scripts/release.mts +0 -199
  46. package/scripts/render-demo.mts +0 -154
  47. package/scripts/render-how-it-works.mts +0 -229
  48. package/scripts/ru-shot.mts +0 -20
  49. package/scripts/sched-smoke.mts +0 -150
  50. package/scripts/self-smoke.mts +0 -309
  51. package/scripts/tui-comp-shot.mts +0 -63
  52. package/scripts/tui-components.tsx +0 -62
  53. package/scripts/tui-config-driver.mts +0 -82
  54. package/scripts/tui-config-probe.tsx +0 -74
  55. package/scripts/tui-edit-driver.mts +0 -100
  56. package/scripts/tui-edit-probe.tsx +0 -38
  57. package/scripts/tui-editor-probe.tsx +0 -24
  58. package/scripts/tui-input-driver.mts +0 -54
  59. package/scripts/tui-input-shot.tsx +0 -59
  60. package/scripts/tui-log-driver.mts +0 -35
  61. package/scripts/tui-log-shot.tsx +0 -48
  62. package/scripts/tui-longpaste-driver.mts +0 -56
  63. package/scripts/tui-longpaste.tsx +0 -35
  64. package/scripts/tui-manyselect-shot.mts +0 -34
  65. package/scripts/tui-manyselect.tsx +0 -17
  66. package/scripts/tui-mouse-churn-probe.tsx +0 -28
  67. package/scripts/tui-paste-driver.mts +0 -88
  68. package/scripts/tui-paste-kinds.tsx +0 -76
  69. package/scripts/tui-pastetest.mts +0 -38
  70. package/scripts/tui-probe.tsx +0 -62
  71. package/scripts/tui-repro.tsx +0 -64
  72. package/scripts/tui-resize.mts +0 -70
  73. package/scripts/tui-scroll-driver.mts +0 -53
  74. package/scripts/tui-scroll-probe.tsx +0 -46
  75. package/scripts/tui-shot.mts +0 -106
  76. package/scripts/tui-status-driver.mts +0 -24
  77. package/scripts/tui-status-shot.tsx +0 -27
  78. package/scripts/tui-submit.tsx +0 -57
  79. package/scripts/tui-wheel-driver.mts +0 -61
  80. package/scripts/tui-wheel-probe.tsx +0 -48
  81. package/scripts/tui-widgets-driver.mts +0 -20
  82. package/scripts/tui-widgets.tsx +0 -44
package/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.76.0] - 2026-10-16
11
+
12
+ ### Added
13
+
14
+ - bake methodology skills in, localize skill hints, fix Ctrl+Del and scroll-to-bottom
15
+
16
+ ## [2.75.6] - 2026-10-16
17
+
18
+ ### Added
19
+
20
+ - **tools:** AskUser — a modal question with options (and a free-text escape)
21
+ - **skills:** tell the model to check and read a matching skill before a non-trivial task
22
+
23
+ ### Fixed
24
+
25
+ - **deepseek:** retry a FINISHED turn with only a truncated RESPONSE head
26
+ - **tui:** default ui.mouseScroll back to true (wheel scrolls the log)
27
+ - **tui:** default ui.mouseScroll back to false so native selection works
28
+
29
+ ### Changed
30
+
31
+ - prune dead code, untrack one-off TUI probes, ship only postinstall
32
+ - **tui:** drop the unused onCopy/hint affordance from CodeBlock
33
+ - **tui:** drop the code-block copy feature (Ctrl+Y / /copy-code)
34
+
10
35
  ## [2.75.5] - 2026-10-15
11
36
 
12
37
  ### Fixed
package/README.md CHANGED
@@ -258,8 +258,11 @@ The prompt is a small line editor with persistent history:
258
258
  Full-screen mode adds dialog navigation, so a long answer can be read and
259
259
  selected without leaving the terminal:
260
260
 
261
- - mouse wheel — scroll the dialog (never the input history; history is arrow
262
- keys only). Selecting text needs `Shift`+click, the standard TUI convention.
261
+ - `ui.mouseScroll=false` (default) — the terminal owns the mouse: normal text
262
+ selection works and the wheel is inert (it never scrolls the input history —
263
+ history is arrow keys only).
264
+ - `ui.mouseScroll=true` — the app consumes the wheel and scrolls the dialog;
265
+ selecting text then needs `Shift`+click (the TUI convention).
263
266
  - `PgUp` / `PgDn` — move up/down the dialog half a screen.
264
267
  - `Home` / `End` — jump to the very top / bottom of the dialog (only while the
265
268
  input line is empty; with text typed they move the caret as usual).
@@ -838,8 +841,11 @@ zames
838
841
  В полноэкранном режиме добавляется навигация по диалогу, чтобы длинный ответ
839
842
  можно было прочитать и выделить не покидая терминал:
840
843
 
841
- - колесо мыши — скролл диалога (историю ввода колесо НЕ листает; история —
842
- только стрелки). Выделение текста — `Shift`+клик, стандарт TUI.
844
+ - `ui.mouseScroll=false` (по умолчанию) — терминал владеет мышью: обычное
845
+ выделение текста работает, колесо инертно (и никогда не листает историю
846
+ ввода — история только стрелками).
847
+ - `ui.mouseScroll=true` — приложение перехватывает колесо и скроллит диалог;
848
+ выделение текста тогда — `Shift`+клик (стандарт TUI).
843
849
  - `PgUp` / `PgDn` — вверх/вниз по диалогу на пол-экрана.
844
850
  - `Home` / `End` — прыжок в самый верх / низ диалога (только когда строка ввода
845
851
  пуста; при набранном тексте они двигают курсор, как обычно).
@@ -7,7 +7,7 @@ import { substituteAttachmentMarkers } from './commands.js';
7
7
  import { loadHooks, runPreToolUse, runPostToolUse, runLifecycleHooks, } from './hooks.js';
8
8
  import { loadPermissions, decidePermission, } from './permissions.js';
9
9
  import { shouldRunDiagnostics, runDiagnostics, } from './diagnostics.js';
10
- export async function runAgentLoop({ browser, tools, task, workdir, maxIterations = 0, freshChat = false, sendSystemPrompt = false, transcript = null, attachments = [], onThinking = () => { }, onSendPause = () => { }, onSendState = () => { }, onNotice = () => { }, onAssistantThought = () => { }, onToolCall = () => { }, onToolResult = () => { }, onAssistantMessage = () => { }, onChatReady = () => { }, onWarning = () => { }, debugLog = false, locale = 'ru', askDeadlineMs = 240_000, maxAfterToolRetries = 6, onAutoCompact = null, autoCompactPct = 95, contextLimit = 1_000_000, getTokenUsage = null, hooks = undefined, permissions = undefined, diagnostics = null, onAskPermission = undefined, selfImprovement = false, onSubagent = null, }) {
10
+ export async function runAgentLoop({ browser, tools, task, workdir, maxIterations = 0, freshChat = false, sendSystemPrompt = false, transcript = null, attachments = [], onThinking = () => { }, onSendPause = () => { }, onSendState = () => { }, onNotice = () => { }, onAssistantThought = () => { }, onToolCall = () => { }, onToolResult = () => { }, onAssistantMessage = () => { }, onChatReady = () => { }, onWarning = () => { }, debugLog = false, locale = 'ru', askDeadlineMs = 240_000, maxAfterToolRetries = 6, onAutoCompact = null, autoCompactPct = 95, contextLimit = 1_000_000, getTokenUsage = null, hooks = undefined, permissions = undefined, diagnostics = null, onAskPermission = undefined, selfImprovement = false, onSubagent = null, onAskUser = null, }) {
11
11
  // Resolve the hook config ONCE per task: a read per tool call would be
12
12
  // wasteful, and a mid-task edit of hooks.json is not something to chase.
13
13
  // `undefined` means "read .zames/hooks.json"; an explicit null disables
@@ -884,6 +884,66 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
884
884
  }
885
885
  continue;
886
886
  }
887
+ // `AskUser` is a SEAM like `Task`: the CALLER owns the modal UI, the
888
+ // loop only relays the question and folds the answer back as the tool
889
+ // result. Intercepted BEFORE the unknown-tool branch so the tool can be
890
+ // handled even though its `fn` is a stub.
891
+ if (call.tool === 'AskUser') {
892
+ const rawQ = typeof call.args.question === 'string'
893
+ ? call.args.question.trim()
894
+ : '';
895
+ const opts = Array.isArray(call.args.options)
896
+ ? call.args.options
897
+ .map((o) => String(o ?? '').trim())
898
+ .filter(Boolean)
899
+ .slice(0, 6)
900
+ : [];
901
+ const recRaw = Number(call.args.recommended);
902
+ const recommended = Number.isInteger(recRaw) && recRaw >= 0 && recRaw < opts.length
903
+ ? recRaw
904
+ : null;
905
+ const allowText = call.args.allow_text !== false;
906
+ if (!onAskUser || !rawQ || opts.length < 2) {
907
+ const err = !onAskUser
908
+ ? 'AskUser is not available in this run. Choose a sensible default, proceed, and state the assumption in your final answer.'
909
+ : 'AskUser requires a non-empty `question` and at least two `options`. Proceed with your best judgement instead.';
910
+ safeToolResult(err);
911
+ transcript?.log('tool_error', { tool: 'AskUser', error: err });
912
+ results.push({ tool: 'AskUser', result: err });
913
+ continue;
914
+ }
915
+ syncToolAbort();
916
+ safeToolCall('AskUser', call.args);
917
+ transcript?.log('ask_user', {
918
+ question: rawQ,
919
+ options: opts,
920
+ recommended,
921
+ allowText,
922
+ });
923
+ let answer;
924
+ try {
925
+ answer = await onAskUser({
926
+ question: rawQ,
927
+ options: opts,
928
+ recommended,
929
+ allowText,
930
+ });
931
+ }
932
+ catch (e) {
933
+ answer = `AskUser error: ${e.message}`;
934
+ }
935
+ const out = answer === null || answer === ''
936
+ ? '(the operator dismissed the question — choose a sensible default, proceed, and state the assumption)'
937
+ : 'The operator answered: ' + answer;
938
+ safeToolResult(out);
939
+ transcript?.log('ask_user_result', { answer: out });
940
+ results.push({ tool: 'AskUser', result: out });
941
+ if (browser._abort || browser._stopped) {
942
+ transcript?.log('user_aborted');
943
+ return '(прервано пользователем)';
944
+ }
945
+ continue;
946
+ }
887
947
  const tool = tools.find((t) => t.name === call.tool);
888
948
  if (!tool) {
889
949
  const err = `Unknown tool: ${call.tool}`;
package/dist/browser.js CHANGED
@@ -459,15 +459,20 @@ export class DeepSeekBrowser {
459
459
  const cid = url.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/);
460
460
  if (cid)
461
461
  this._netChatId = cid[0];
462
+ // A live hang: the turn ENDED (`quasi_status: FINISHED`) with no real
463
+ // answer — only reasoning, or a RESPONSE fragment truncated to a lone
464
+ // `{`. The extractor yields '' or the fragment, so the OLD
465
+ // `!extracted` guard skipped the check and the finish loop waited out
466
+ // the whole askDeadlineMs. Run the check on the RAW body regardless of
467
+ // whether a (bogus) fragment was extracted.
468
+ if (isFinishedWithoutAnswer(body)) {
469
+ // The UI shows Stopped + Continue. Record it with a fresh timestamp
470
+ // so the ask() loops do not wait out the timeout — they click
471
+ // Continue / resend.
472
+ this._netNoAnswer = true;
473
+ this._netCapture = '';
474
+ }
462
475
  if (!extracted) {
463
- if (isFinishedWithoutAnswer(body)) {
464
- // A FINISHED turn with NO RESPONSE fragment (only reasoning): the
465
- // UI shows Stopped + Continue. Record it with a fresh timestamp
466
- // so the ask() loops do not wait out the timeout — they click
467
- // Continue / resend.
468
- this._netNoAnswer = true;
469
- this._netCapture = '';
470
- }
471
476
  // A truncated turn (generation_err / INCOMPLETE) also has no
472
477
  // RESPONSE; make the body visible to the incomplete check by
473
478
  // refreshing the timestamp.
@@ -2044,6 +2049,12 @@ export class DeepSeekBrowser {
2044
2049
  // Ticks since the SSE body reported FINISHED-with-answer while the DOM
2045
2050
  // heuristics kept the loop waiting (see the safety net below).
2046
2051
  let netFinishedTicks = 0;
2052
+ // Ticks since the SSE body reported FINISHED but produced NO real answer
2053
+ // (only reasoning / a truncated head) while no Continue button was found.
2054
+ // Waiting out the full answerTimeoutMs (up to 15 min) on a turn the server
2055
+ // already ENDED with nothing is pointless — after a short grace period the
2056
+ // loop gives up so ask() resends the prompt instead of a 4-minute spinner.
2057
+ let deadTicks = 0;
2047
2058
  while (Date.now() < deadline) {
2048
2059
  if (this._abort || this._askCancelled) {
2049
2060
  return last || '(прервано пользователем)';
@@ -2160,6 +2171,27 @@ export class DeepSeekBrowser {
2160
2171
  else {
2161
2172
  netFinishedTicks = 0;
2162
2173
  }
2174
+ // DEAD-TURN SAFETY NET. The server ENDED the turn (`quasi_status:
2175
+ // FINISHED`) but produced no real answer (only reasoning, or a truncated
2176
+ // RESPONSE head) AND there is no Continue button to resume it. The
2177
+ // `truncated`/`noAnswer` branches above throw at once when the raw body
2178
+ // says so; this is the belt-and-braces variant that also covers a body
2179
+ // whose flags were cleared by an earlier Continue click. Waiting out the
2180
+ // full answerTimeoutMs (up to 15 min) is pointless — after a short grace
2181
+ // period give up so ask() resends the prompt instead of a long spinner.
2182
+ if (this._netCaptureAt >= this._lastSentAt &&
2183
+ isFinishedWithAnswer(this._netBody) === false &&
2184
+ isGenerationIncompleteText(this._netBody) === false &&
2185
+ (this._netNoAnswer || isFinishedWithoutAnswer(this._netBody))) {
2186
+ deadTicks++;
2187
+ if (deadTicks >= 6 && !(await this._continueButtonVisible())) {
2188
+ this._askDebug('THROW dead-turn (finished without answer)');
2189
+ throw new GenerationIncompleteError('finished without answer');
2190
+ }
2191
+ }
2192
+ else {
2193
+ deadTicks = 0;
2194
+ }
2163
2195
  if (isNew && cur === last) {
2164
2196
  stable++;
2165
2197
  // `stabilityChecks` / `stabilityDelayMs` are the real knobs here.
package/dist/commands.js CHANGED
@@ -987,7 +987,6 @@ export const SLASH_COMMANDS = [
987
987
  { name: '/rename', key: 'help.cmd.rename' },
988
988
  { name: '/context', key: 'help.cmd.context' },
989
989
  { name: '/copy', key: 'help.cmd.copy' },
990
- { name: '/copy-code', key: 'help.cmd.copy_code' },
991
990
  { name: '/chat', key: 'help.cmd.chat' },
992
991
  { name: '/cd', key: 'help.cmd.cd' },
993
992
  { name: '/pwd', key: 'help.cmd.pwd' },
package/dist/config.js CHANGED
@@ -71,7 +71,11 @@ export const DEFAULTS = {
71
71
  incompleteWaitMs: 2000,
72
72
  autoContinue: true,
73
73
  continueMinGapMs: 1500,
74
- askDeadlineMs: 240000,
74
+ // The agent-loop watchdog for ONE ask(). It only bounds a SILENT send:
75
+ // a turn the server already ENDED with no answer (FINISHED + truncated
76
+ // RESPONSE) is detected by the network detectors and retried in seconds,
77
+ // not waited out. 240s used to mean a 4-minute spinner per dead turn.
78
+ askDeadlineMs: 120000,
75
79
  maxAfterToolRetries: 6,
76
80
  autoCompact: false,
77
81
  autoCompactPct: 95,
@@ -113,8 +117,9 @@ export const DEFAULTS = {
113
117
  // REPORTING (1000/1006) plus alternate-scroll OFF (1007l), so the wheel
114
118
  // arrives as SGR events we consume and never as Up/Down arrows. The
115
119
  // trade-off is that the terminal no longer does its own drag-select, so
116
- // selecting text needs SHIFT+DRAG (the standard TUI convention). Set to
117
- // false to give the terminal the mouse back (native select, wheel inert).
120
+ // selecting text needs SHIFT+DRAG (the standard TUI convention: Claude
121
+ // Code / Codex / opencode do the same). Set to false to give the terminal
122
+ // the mouse back (native select, wheel inert).
118
123
  mouseScroll: true,
119
124
  },
120
125
  // Off by default: a full tsc/eslint run after every edit is slow; opt in.
package/dist/context.js CHANGED
@@ -57,6 +57,105 @@ const BUILTIN_SKILLS = [
57
57
  'couplings, and any WHY-comments that matter.\n' +
58
58
  '4. Do NOT change code. Answer in the operator language.',
59
59
  },
60
+ // Methodology skills, ported from superpowers (github.com/obra/superpowers)
61
+ // as plain prompt bodies — the zames equivalent of its SessionStart
62
+ // bootstrap, without a hook. The model is nudged to read the matching one
63
+ // before a non-trivial task by SKILLS_DISCIPLINE (src/system-prompt.ts).
64
+ // Baked in (not a file on disk) so a user who installed the npm package has
65
+ // them out of the box; a project skill of the same name overrides them.
66
+ {
67
+ name: 'brainstorming',
68
+ description: 'Use when the request is a GOAL, not a spec — "add X", "make it better", "integrate Y" — and it is unclear what "done" means.',
69
+ body: 'When a request is a goal ("make scrolling feel right"), building ' +
70
+ 'immediately produces the wrong thing fast. First make the intent concrete.\n' +
71
+ 'Pin down: (a) the REAL outcome — what should be TRUE after that is not ' +
72
+ 'true now; (b) the acceptance check — how the operator will know it works; ' +
73
+ '(c) scope edges — what is explicitly NOT part of this; (d) the ' +
74
+ 'alternatives considered and why you chose one.\n' +
75
+ 'zames has NO conversational turn: the operator sees only your final ' +
76
+ 'respond. So do the brainstorming YOURSELF and return a SHORT, concrete ' +
77
+ 'proposal in ONE respond — the goal as you understood it, your ' +
78
+ 'assumptions and defaults, what you will NOT do — then either proceed if ' +
79
+ 'the request was clearly actionable, or stop and ask the ONE decision you ' +
80
+ 'genuinely cannot make alone. For an already-precise request ("read X", ' +
81
+ '"rename Y") skip this entirely.',
82
+ },
83
+ {
84
+ name: 'writing-plans',
85
+ description: 'Use when a task needs more than one or two tool calls — a multi-file change, a new feature, a refactor. Breaks it into small ordered steps with exact paths and a verification for each.',
86
+ body: 'A plan is a sequence of steps a careful engineer with NO project ' +
87
+ 'context could follow. Each step (2-5 minutes) names: the EXACT file ' +
88
+ 'path(s); the concrete edit; how to verify it (command, test name, ' +
89
+ 'expected output). Order matters: put the step that DE-RISKS the task ' +
90
+ 'first (a spike, a failing test, a proof the API works) — do not build ' +
91
+ 'three layers on an unverified assumption.\n' +
92
+ 'Rules: one concern per step ("add the flag AND refactor the caller" is ' +
93
+ 'two); prefer a step that FAILS LOUDLY over one that silently ' +
94
+ 'half-works; every behavioural change gets a test step (see ' +
95
+ 'test-driven-development); if a step turns out wrong, STOP and revise the ' +
96
+ 'plan instead of improvising; a 30-step plan means the task should be ' +
97
+ 'split. In zames the plan lives in your reasoning and, for a long task, ' +
98
+ 'in a TodoWrite list — one entry per step. Do not narrate it between tool ' +
99
+ 'calls; the todo list IS the visible plan.',
100
+ },
101
+ {
102
+ name: 'test-driven-development',
103
+ description: 'Use when writing or changing testable behaviour — a bug fix, a new function, a parser, a formatter. Write the failing test first, watch it fail, then make it pass.',
104
+ body: 'RED -> GREEN -> REFACTOR. The order is the point, not a formality.\n' +
105
+ '1. RED — write the smallest test that expresses the new behaviour and ' +
106
+ 'RUN it. It must FAIL for the RIGHT reason ("not implemented", not a ' +
107
+ 'typo). A test you never saw fail proves nothing.\n' +
108
+ '2. GREEN — write the MINIMUM code that makes it pass; do not add the ' +
109
+ 'next feature "while you are there".\n' +
110
+ '3. REFACTOR — clean up with the test green, then re-run.\n' +
111
+ 'A good test asserts BEHAVIOUR (input -> output), is deterministic (no ' +
112
+ 'sleeps, no network, no wall-clock or test-order dependence) and fails ' +
113
+ 'for ONE reason with a message that says what broke. In this project a ' +
114
+ 'bug fix ALWAYS gets a regression test reproducing the original bug. ' +
115
+ 'When a test needs fixing, ask whether the TEST or the CODE was wrong; ' +
116
+ 'if the code was written before its test, rewrite it driven by the test.',
117
+ },
118
+ {
119
+ name: 'systematic-debugging',
120
+ description: 'Use when something is broken and the cause is not obvious — a failing test, a wrong result, a hang, a crash. Four phases: reproduce, isolate, hypothesize, fix.',
121
+ body: 'Do NOT start by editing code. Work the four phases in order; each ' +
122
+ 'produces an artifact you can point at.\n' +
123
+ '1. REPRODUCE — get a command that FAILS the way the operator describes; ' +
124
+ 'copy the exact error. If it passes you do not have the bug yet: narrow ' +
125
+ 'the input until it fails.\n' +
126
+ '2. ISOLATE — shrink to the smallest failing unit (bisect, stub, ' +
127
+ 'binary-search). Read the code the failing path ACTUALLY executes. Check ' +
128
+ 'the obvious first: wrong file, stale build, cached state, different ' +
129
+ 'config, an error swallowed by a catch.\n' +
130
+ '3. HYPOTHESIZE — state it in one sentence and predict what you should ' +
131
+ 'see if it is right; test exactly that. One change at a time; prefer an ' +
132
+ 'observation (log, test assertion) over an edit. If the prediction is ' +
133
+ 'wrong, DISCARD the hypothesis.\n' +
134
+ '4. FIX AND VERIFY — fix the CAUSE, not the symptom; add a regression ' +
135
+ 'test that fails WITHOUT the fix; re-run the phase-1 reproduction ' +
136
+ 'verbatim; look for the same bug elsewhere.\n' +
137
+ 'Anti-patterns: editing before reproducing; "it works now" without ' +
138
+ 'knowing what changed; silencing an error you do not understand with a ' +
139
+ 'try/catch; trusting a green test never seen red.',
140
+ },
141
+ {
142
+ name: 'verification-before-completion',
143
+ description: 'Use BEFORE declaring a task done — a feature, a bug fix, a generated file. Forces you to run the verification you promised instead of trusting the code looks right.',
144
+ body: '"It should work" is not done. Before reporting success, produce ' +
145
+ 'EVIDENCE from the real artifact, not from your intent.\n' +
146
+ '1. Re-read the original request; tick every requirement (including the ' +
147
+ 'ones in a parenthetical) against something you can show.\n' +
148
+ '2. Run the thing: build/typecheck/tests for code; run a script; for a ' +
149
+ 'generated file, open/parse it and confirm it is COMPLETE, not ' +
150
+ 'truncated; for a UI, the real screen, not the diff.\n' +
151
+ '3. Re-read your own diff as a reviewer: leftover debug output, dead ' +
152
+ 'code, a wrong path, a test that asserts nothing.\n' +
153
+ '4. State the evidence in the final report — the exact command and its ' +
154
+ 'result — so the operator can reproduce it. "Tests pass" without the ' +
155
+ 'command is not evidence.\n' +
156
+ 'If you cannot run the verification, SAY SO explicitly and describe what ' +
157
+ 'you did instead; never report "done" implying a check you did not run.',
158
+ },
60
159
  ];
61
160
  // Per-file budgets. AGENTS.md/MEMORY.md are hand-maintained and grow over
62
161
  // time, so the caps are generous: an AGENTS.md of a real project (this repo's
@@ -18,7 +18,7 @@ export const CHAT_URL = 'https://chat.deepseek.com/';
18
18
  export const CHAT_ID_RE = /\/chat\/s\/([a-zA-Z0-9_-]+)/;
19
19
  // DeepSeek's chat URL shape. A direct navigation always yields the requested
20
20
  // chat (the sidebar link click is tried first, this is the fallback).
21
- export const CHAT_PATH_PREFIX = 'a/chat/s/';
21
+ const CHAT_PATH_PREFIX = 'a/chat/s/';
22
22
  export function chatUrl(id) {
23
23
  return CHAT_URL + CHAT_PATH_PREFIX + id;
24
24
  }
@@ -177,7 +177,6 @@ export const MFA_CODE_SELECTORS = [
177
177
  '.ds-mfa-verification-modal input[type="tel"]',
178
178
  'input[inputmode="numeric"][maxlength="6"]',
179
179
  ];
180
- export const MFA_MODAL_SELECTOR = '.ds-mfa-verification-modal, [role="dialog"]';
181
180
  export const MFA_SUBMIT_SELECTORS = [
182
181
  'div[role="button"].ds-mfa-verification-submit',
183
182
  '.ds-mfa-verification-modal div[role="button"].ds-button--primary',
@@ -264,7 +263,6 @@ export const INCOMPLETE_STATUS_RE = /"quasi_status"(?::|,"v":)"INCOMPLETE"/i;
264
263
  // fragment. The UI shows Stopped + Continue. Matches both the JSON-property
265
264
  // and the BATCH shape (see INCOMPLETE_STATUS_RE).
266
265
  export const FINISHED_STATUS_RE = /"quasi_status"(?::|,"v":)"FINISHED"/i;
267
- export const RESPONSE_CONTENT_RE = /"type":"RESPONSE","content":"[^"]/;
268
266
  // Only a completion/continue turn carries a real answer. Gating the capture
269
267
  // assignment to these URLs keeps a future endpoint (which happens to return a
270
268
  // `content`/`text` field) from clobbering the answer and looking fresh.
@@ -117,6 +117,19 @@ export const commonMessages = {
117
117
  en: 'Allow this call?',
118
118
  },
119
119
  'perm.title': { ru: 'Подтверждение', en: 'Confirmation' },
120
+ // The AskUser modal (the model asks the operator a question with options).
121
+ 'askuser.title': { ru: 'Вопрос от агента', en: 'Question from the agent' },
122
+ 'askuser.other': {
123
+ ru: 'Свой вариант — введу текстом',
124
+ en: 'Other — I will type my own answer',
125
+ },
126
+ 'askuser.recommended': { ru: 'рекомендую', en: 'recommended' },
127
+ 'askuser.your_answer': { ru: 'Ваш ответ:', en: 'Your answer:' },
128
+ 'askuser.hint_select': { ru: 'выбор', en: 'select' },
129
+ 'askuser.hint_ok': { ru: 'ок', en: 'ok' },
130
+ 'askuser.hint_dismiss': { ru: 'закрыть', en: 'dismiss' },
131
+ 'askuser.hint_send': { ru: 'отправить', en: 'send' },
132
+ 'askuser.hint_cancel': { ru: 'отмена', en: 'cancel' },
120
133
  'confirm.yes': { ru: 'Да', en: 'Yes' },
121
134
  'confirm.no': { ru: 'Нет', en: 'No' },
122
135
  'perm.ask_sub': {
@@ -246,22 +259,6 @@ export const commonMessages = {
246
259
  ru: 'Не удалось скопировать (нет {tool} или он недоступен).',
247
260
  en: 'Could not copy ({tool} missing or unavailable).',
248
261
  },
249
- 'copy.no_code': {
250
- ru: 'В последнем ответе нет блоков кода.',
251
- en: 'The last answer has no code blocks.',
252
- },
253
- 'copy.bad_index': {
254
- ru: 'Нет блока кода #{n}.',
255
- en: 'No code block #{n}.',
256
- },
257
- 'copy_code.list': {
258
- ru: 'Блоки кода последнего ответа:',
259
- en: 'Code blocks in the last answer:',
260
- },
261
- 'copy_code.item': {
262
- ru: ' #{n} {lang} — {lines} стр.',
263
- en: ' #{n} {lang} — {lines} lines',
264
- },
265
262
  'retry.running': { ru: 'Повторяю задачу…', en: 'Retrying the task...' },
266
263
  'rename.usage': {
267
264
  ru: 'Использование: /rename <title>',
@@ -44,8 +44,8 @@ export const configMessages = {
44
44
  en: 'Full-screen TUI mode (alternate screen)',
45
45
  },
46
46
  'cfg.f.ui_mouseScroll': {
47
- ru: 'Прокрутка лога колесом мыши (выделение текста — Shift+клик)',
48
- en: 'Mouse-wheel log scrolling (select text with Shift+click)',
47
+ ru: 'Колесо: лог или терминал (выкл = выделение без Shift)',
48
+ en: 'Wheel: log or terminal (off = select without Shift)',
49
49
  },
50
50
  'cfg.f.maxIterations': {
51
51
  ru: 'Лимит итераций на задачу',
package/dist/i18n/help.js CHANGED
@@ -102,8 +102,8 @@ export const helpMessages = {
102
102
  en: 'PgUp / PgDn up/down the dialog (Home/End — top/bottom) [full-screen]',
103
103
  },
104
104
  'help.key.wheel': {
105
- ru: 'колесо мыши скролл диалога; выделение — Shift+клик',
106
- en: 'mouse wheel scroll the dialog; select text with Shift+click',
105
+ ru: 'ui.mouseScroll on (умолч.) — колесо скроллит диалог (выделение Shift); off — выделение мышью',
106
+ en: 'ui.mouseScroll on (default) — wheel scrolls the dialog (select: Shift); off — select with the mouse',
107
107
  },
108
108
  'help.key.bang': {
109
109
  ru: '!команда выполнить shell-команду напрямую (мимо модели)',
@@ -201,10 +201,6 @@ export const helpMessages = {
201
201
  ru: '/copy скопировать последний ответ в буфер обмена',
202
202
  en: '/copy copy the last answer to the clipboard',
203
203
  },
204
- 'help.cmd.copy_code': {
205
- ru: '/copy-code [номер] копировать блок кода из последнего ответа (Ctrl+Y)',
206
- en: '/copy-code [n] copy a code block from the last answer (Ctrl+Y)',
207
- },
208
204
  'help.cmd.cost': { ru: '/cost статистика сессии', en: '/cost session stats' },
209
205
  'help.cmd.export': {
210
206
  ru: '/export [file] выгрузить сессию в Markdown',
@@ -578,4 +574,50 @@ export const helpMessages = {
578
574
  ru: 'Индикаторы в статус-строке: 🧠 — глубокое размышление, 🌐 — поиск в интернете (видны, когда включены).',
579
575
  en: 'Status-line icons: 🧠 — Deep thinking, 🌐 — web search (shown when enabled).',
580
576
  },
577
+ // Localized descriptions for the WELL-KNOWN skills (built-in + bundled
578
+ // methodology). A skill's SKILL.md frontmatter is agent-facing English, so
579
+ // showing it verbatim in the «/» hints would leave an English line in a
580
+ // Russian UI. Keyed by skill NAME, applied regardless of source, so a local
581
+ // `.zames/skills/` copy of a bundled skill still shows the translated text.
582
+ // An unknown (user-authored) skill has no key and keeps its own description.
583
+ 'skill.desc.commit': {
584
+ ru: 'подготовить аккуратный git-коммит из текущих изменений',
585
+ en: 'prepare a clean git commit from the current changes',
586
+ },
587
+ 'skill.desc.review': {
588
+ ru: 'ревью текущего диффа на баги и риски (без правок)',
589
+ en: 'review the current diff for bugs and risks (no changes)',
590
+ },
591
+ 'skill.desc.test': {
592
+ ru: 'прогнать тесты проекта и чинить падения до зелёного',
593
+ en: 'run the project tests and fix failures until green',
594
+ },
595
+ 'skill.desc.debug': {
596
+ ru: 'системно диагностировать и починить баг',
597
+ en: 'systematically diagnose and fix a bug',
598
+ },
599
+ 'skill.desc.explain': {
600
+ ru: 'объяснить, как работает файл/модуль/фича',
601
+ en: 'explain how a file/module/feature works',
602
+ },
603
+ 'skill.desc.brainstorming': {
604
+ ru: 'когда запрос — это цель, а не спека: сначала прояснить замысел',
605
+ en: 'when the request is a goal, not a spec: pin down the intent first',
606
+ },
607
+ 'skill.desc.writing-plans': {
608
+ ru: 'разбить многошаговую задачу на маленькие шаги с проверкой каждого',
609
+ en: 'break a multi-step task into small steps, each with a check',
610
+ },
611
+ 'skill.desc.test-driven-development': {
612
+ ru: 'сначала падающий тест, потом код (RED → GREEN → REFACTOR)',
613
+ en: 'write the failing test first, then the code (RED → GREEN → REFACTOR)',
614
+ },
615
+ 'skill.desc.systematic-debugging': {
616
+ ru: 'четыре фазы отладки: воспроизвести, изолировать, гипотеза, фикс',
617
+ en: 'four debugging phases: reproduce, isolate, hypothesize, fix',
618
+ },
619
+ 'skill.desc.verification-before-completion': {
620
+ ru: 'прогнать проверку ПЕРЕД тем, как объявить задачу готовой',
621
+ en: 'run the verification BEFORE declaring the task done',
622
+ },
581
623
  };
@@ -35,11 +35,17 @@ export const spinnerMessages = {
35
35
  ru: 'Напишите задачу и нажмите Enter. /help — команды, Ctrl+J — новая строка.',
36
36
  en: 'Type a task and press Enter. /help for commands, Ctrl+J for a new line.',
37
37
  },
38
- // One-line key hint UNDER the input box (full-screen mode).
38
+ // One-line key hint UNDER the input box (full-screen mode). Two variants:
39
+ // with in-app wheel scroll the mouse belongs to the app, so selection needs
40
+ // Shift; without it the terminal keeps the mouse and the wheel is inert.
39
41
  'editor.keys_hint': {
40
42
  ru: 'выделение — Shift+клик · колесо — скролл · PgUp/PgDn — вверх/вниз · Home/End — в начало/конец',
41
43
  en: 'select — Shift+click · wheel — scroll · PgUp/PgDn — up/down · Home/End — top/bottom',
42
44
  },
45
+ 'editor.keys_hint_select': {
46
+ ru: 'выделение — мышью · PgUp/PgDn — вверх/вниз · Home/End — в начало/конец',
47
+ en: 'select — drag · PgUp/PgDn — up/down · Home/End — top/bottom',
48
+ },
43
49
  // Reverse search (Ctrl+R) status line. The query may be empty.
44
50
  'editor.search_prompt': {
45
51
  ru: '(обратный поиск) `{q}` — Ctrl+R дальше, Enter принять, Esc отмена',
package/dist/index.js CHANGED
@@ -35,7 +35,6 @@ import { Scheduler, parseInterval, formatInterval, formatJobLine, parseCron, } f
35
35
  import { createCliArgs } from './cli-args.js';
36
36
  import { qwenContextWindow } from './qwen-ui.js';
37
37
  import { renderMarkdown, setAnswerWidth } from './markdown.js';
38
- import { extractCodeBlocks } from './code-blocks.js';
39
38
  import { contentWidth } from './width.js';
40
39
  import { renderStartupBanner } from './banner.js';
41
40
  import { runStatusLineCommand } from './statusline.js';
@@ -531,9 +530,18 @@ async function refreshDynamicCommands(workdir) {
531
530
  for (const s of skills) {
532
531
  if (!s.userInvokable)
533
532
  continue;
533
+ // A well-known skill (built-in or bundled methodology) has a localized
534
+ // description keyed by NAME — its SKILL.md frontmatter is agent-facing
535
+ // English, which must not leak into a Russian UI. `t` returns the key
536
+ // itself when it is missing, so an unknown (user-authored) skill falls
537
+ // back to its own description.
538
+ const descKey = 'skill.desc.' + s.name;
539
+ const localized = t(descKey);
534
540
  out.push({
535
541
  name: '/' + s.name,
536
- description: s.description || t('help.cmd.skill'),
542
+ description: localized !== descKey
543
+ ? localized
544
+ : s.description || t('help.cmd.skill'),
537
545
  });
538
546
  }
539
547
  const cmds = await loadCommands(workdir);
@@ -1528,7 +1536,6 @@ async function main() {
1528
1536
  }) + theme.dim(' (' + res.via + ')')));
1529
1537
  return att;
1530
1538
  };
1531
- ed.onCopyText = (text) => writeClipboardText(text);
1532
1539
  ed.onSubmit = (text, items) => {
1533
1540
  // While the agent is busy the main command loop is blocked on
1534
1541
  // `await runTask()`, so ONLY commands that make sense without the main
@@ -3239,53 +3246,6 @@ async function main() {
3239
3246
  }
3240
3247
  continue;
3241
3248
  }
3242
- if (lower === '/copy-code' || lower.startsWith('/copy-code ')) {
3243
- const arg = trimmed.slice('/copy-code'.length).trim();
3244
- const blocks = extractCodeBlocks(lastAssistantMessage ?? '');
3245
- if (!blocks.length) {
3246
- console.log(theme.dim(t('copy.no_code')));
3247
- continue;
3248
- }
3249
- if (arg === 'list') {
3250
- console.log(theme.system(t('copy_code.list')));
3251
- for (const b of blocks) {
3252
- console.log(theme.assistant(t('copy_code.item', {
3253
- n: String(b.index),
3254
- lang: b.lang || 'text',
3255
- lines: String(b.text.split(String.fromCharCode(10)).length),
3256
- })));
3257
- }
3258
- continue;
3259
- }
3260
- // Prefer the TUI copy path (it reports into the log); fall back to a
3261
- // direct clipboard write for the legacy editor / non-TTY.
3262
- const n = arg ? Number(arg) : undefined;
3263
- if (n !== undefined && (!Number.isInteger(n) || n < 1)) {
3264
- console.log(theme.warn(t('copy.bad_index', { n: arg })));
3265
- continue;
3266
- }
3267
- if (editor &&
3268
- 'copyCode' in editor &&
3269
- typeof editor.copyCode === 'function') {
3270
- editor.copyCode(n);
3271
- continue;
3272
- }
3273
- const pick = n !== undefined
3274
- ? blocks.find((b) => b.index === n)
3275
- : blocks[blocks.length - 1];
3276
- if (!pick) {
3277
- console.log(theme.warn(t('copy.bad_index', { n: String(n) })));
3278
- continue;
3279
- }
3280
- const res2 = writeClipboardText(pick.text);
3281
- if (res2.ok) {
3282
- console.log(theme.assistant(t('copy.done', { tool: res2.via })));
3283
- }
3284
- else {
3285
- console.error(theme.warn(t('copy.failed', { tool: res2.via })));
3286
- }
3287
- continue;
3288
- }
3289
3249
  if (lower === '/retry') {
3290
3250
  if (!lastTaskText) {
3291
3251
  console.log(theme.dim(t('retry.none')));