zames_pro 2.13.0 → 2.15.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/README.md +39 -0
- package/dist/agent-loop.js +43 -43
- package/dist/commands.js +201 -0
- package/dist/extraTools.js +352 -0
- package/dist/gitTools.js +13 -13
- package/dist/i18n.js +12 -0
- package/dist/index.js +161 -1
- package/dist/system-prompt.js +1 -1
- package/dist/tools.js +100 -46
- package/dist/web.js +15 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,45 @@ zames --version
|
|
|
76
76
|
zames --help
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
## Tools
|
|
80
|
+
|
|
81
|
+
The agent has the same style of tools as Claude Code / Codex CLI:
|
|
82
|
+
|
|
83
|
+
- **File tools** — `Read`, `Write`, `Edit`, `Bash`, `Glob`, `Grep`. `Read`
|
|
84
|
+
returns raw content by default; pass `numbered=true` to get `cat -n`-style
|
|
85
|
+
line numbers (for reference only — do not paste them into `Edit`).
|
|
86
|
+
- **Extra tools** — `LS` (list a directory), `MultiEdit` (several edits to one
|
|
87
|
+
file applied atomically), `TodoWrite` (session task checklist), `ApplyPatch`
|
|
88
|
+
(multi-file patch in Codex's V4A format: `*** Begin Patch` … `*** End Patch`).
|
|
89
|
+
- **Git** — `GitStatus`, `GitDiff`, `GitLog`, `GitAdd`, `GitCommit`, `GitPush`.
|
|
90
|
+
- **Web** — `WebFetch`, `WebSearch`.
|
|
91
|
+
- **Service** — `respond` (final answer to the operator, ends the task).
|
|
92
|
+
|
|
93
|
+
All file tools stay inside the working directory (sandbox). `Write`/`Edit` and
|
|
94
|
+
`MultiEdit`/`ApplyPatch` make a backup (undo) before touching a file.
|
|
95
|
+
|
|
96
|
+
## Slash commands
|
|
97
|
+
|
|
98
|
+
Type / in the prompt for hints (Tab completes). Besides the session and
|
|
99
|
+
config commands (/new, /chats, /resume, /cd, /status, /config,
|
|
100
|
+
/undo, /transcript, /mcp, /skills, /memory, /init, /reload,
|
|
101
|
+
/debug-dom, /help, /exit) there are a few that mirror Claude Code /
|
|
102
|
+
Codex CLI:
|
|
103
|
+
|
|
104
|
+
- /diff [--staged] — show the working-tree git diff (--staged for the index).
|
|
105
|
+
- /cost (alias /usage) — session stats: tasks, tool calls, duration
|
|
106
|
+
(DeepSeek web does not expose token counts).
|
|
107
|
+
- /export [file] — write the session transcript to a Markdown file
|
|
108
|
+
(zames-export-<stamp>.md by default).
|
|
109
|
+
- /doctor — diagnose node, git, config, browser, clipboard and MCP.
|
|
110
|
+
- /permissions — show the confirmation settings (Write/Edit/Bash + the
|
|
111
|
+
alwaysConfirm regex list).
|
|
112
|
+
- /add-dir <path> — validate an extra directory (the sandbox is fixed at
|
|
113
|
+
startup; relaunch with --dir to write there).
|
|
114
|
+
- /review [focus] [--staged] — ask the agent to review uncommitted changes
|
|
115
|
+
and report findings (no code changes).
|
|
116
|
+
|
|
117
|
+
|
|
79
118
|
## Project context, skills and memory
|
|
80
119
|
|
|
81
120
|
Like Codex / Claude Code, zames reads project instructions and reusable
|
package/dist/agent-loop.js
CHANGED
|
@@ -194,20 +194,20 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
194
194
|
attempt: afterToolRetries,
|
|
195
195
|
error: e.message,
|
|
196
196
|
});
|
|
197
|
-
safeWarning('browser.ask()
|
|
197
|
+
safeWarning('browser.ask() did not return an answer within ' +
|
|
198
198
|
Math.round(askDeadlineMs / 1000) +
|
|
199
|
-
'
|
|
199
|
+
's — retrying the request.');
|
|
200
200
|
if (afterToolRetries < MAX_AFTER_TOOL_RETRIES) {
|
|
201
201
|
afterToolRetries++;
|
|
202
202
|
await new Promise((r) => setTimeout(r, 1500));
|
|
203
203
|
continue;
|
|
204
204
|
}
|
|
205
205
|
transcript?.log('ask_timeout_exhausted', {
|
|
206
|
-
message: 'ask()
|
|
206
|
+
message: 'ask() did not return an answer and the retry limit is exhausted',
|
|
207
207
|
});
|
|
208
|
-
safeWarning('browser.ask()
|
|
209
|
-
'
|
|
210
|
-
return 'ask() watchdog:
|
|
208
|
+
safeWarning('browser.ask() stopped responding; retry limit exhausted, ' +
|
|
209
|
+
'stopping. No model answer — check the DeepSeek chat manually.');
|
|
210
|
+
return 'ask() watchdog: no model answer received';
|
|
211
211
|
}
|
|
212
212
|
await reportChat();
|
|
213
213
|
transcript?.log('assistant_raw', { response: rawResponse });
|
|
@@ -262,10 +262,10 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
262
262
|
response: String(rawResponse || '').slice(0, 200),
|
|
263
263
|
});
|
|
264
264
|
message =
|
|
265
|
-
'
|
|
266
|
-
'
|
|
267
|
-
'
|
|
268
|
-
'
|
|
265
|
+
'You stopped after a tool result. Continue the work: ' +
|
|
266
|
+
'reply with EXACTLY one JSON tool-call object, no text before or after, ' +
|
|
267
|
+
'for example: {"tool": "Bash", "args": {"command": "..."}}. ' +
|
|
268
|
+
'If the task is really done — call respond with the final message.';
|
|
269
269
|
await new Promise((r) => setTimeout(r, 1500));
|
|
270
270
|
continue;
|
|
271
271
|
}
|
|
@@ -287,7 +287,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
287
287
|
if (parsedCalls.some((p) => p && p._permissive)) {
|
|
288
288
|
transcript?.log('permissive_parse', { response: rawResponse });
|
|
289
289
|
if (debugLog) {
|
|
290
|
-
console.error('
|
|
290
|
+
console.error('warning: tool-call recognized by the permissive parser');
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
if (!parsed) {
|
|
@@ -304,17 +304,17 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
304
304
|
response: rawResponse,
|
|
305
305
|
});
|
|
306
306
|
if (debugLog) {
|
|
307
|
-
console.error('
|
|
307
|
+
console.error('warning: the answer looks like a tool-call but was not recognized (attempt ' +
|
|
308
308
|
malformedRetries +
|
|
309
309
|
'/' +
|
|
310
310
|
MAX_MALFORMED_RETRIES +
|
|
311
311
|
')');
|
|
312
312
|
}
|
|
313
313
|
message =
|
|
314
|
-
'
|
|
315
|
-
'
|
|
316
|
-
'
|
|
317
|
-
'
|
|
314
|
+
'Your previous answer was not recognized as a tool call. ' +
|
|
315
|
+
'Reply with EXACTLY one JSON tool-call object, no text before or after. ' +
|
|
316
|
+
'Do NOT use XML/DSML tags — plain JSON only. ' +
|
|
317
|
+
'For example: {"tool": "Read", "args": {"path": "src/index.js"}}';
|
|
318
318
|
continue;
|
|
319
319
|
}
|
|
320
320
|
const trimmed = (rawResponse || '').trim();
|
|
@@ -336,16 +336,16 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
336
336
|
response: rawResponse,
|
|
337
337
|
});
|
|
338
338
|
if (debugLog) {
|
|
339
|
-
console.error('
|
|
339
|
+
console.error('warning: empty/service answer, asking to continue (attempt ' +
|
|
340
340
|
stallRetries +
|
|
341
341
|
'/' +
|
|
342
342
|
MAX_STALL_RETRIES +
|
|
343
343
|
')');
|
|
344
344
|
}
|
|
345
345
|
message =
|
|
346
|
-
'
|
|
347
|
-
'
|
|
348
|
-
'
|
|
346
|
+
'Continue the task. If you need a tool — reply with EXACTLY ' +
|
|
347
|
+
'one JSON tool-call object: {"tool": "...", "args": {...}}. ' +
|
|
348
|
+
'If the task is done — call the respond tool with the final message.';
|
|
349
349
|
continue;
|
|
350
350
|
}
|
|
351
351
|
// The answer looks like "I'll call a tool now", but contains no call.
|
|
@@ -361,18 +361,18 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
361
361
|
response: rawResponse,
|
|
362
362
|
});
|
|
363
363
|
if (debugLog) {
|
|
364
|
-
console.error('
|
|
364
|
+
console.error('warning: the answer looks like unfinished work, asking to continue (attempt ' +
|
|
365
365
|
looksDoneRetries +
|
|
366
366
|
'/' +
|
|
367
367
|
MAX_LOOKSDONE_RETRIES +
|
|
368
368
|
')');
|
|
369
369
|
}
|
|
370
370
|
message =
|
|
371
|
-
'
|
|
372
|
-
'
|
|
373
|
-
'
|
|
374
|
-
'
|
|
375
|
-
'
|
|
371
|
+
'It looks like you meant to call a tool but did not. ' +
|
|
372
|
+
'If the task is not finished — reply with EXACTLY one JSON ' +
|
|
373
|
+
'tool-call object, no text before or after. ' +
|
|
374
|
+
'If the task is really done — call respond with the final ' +
|
|
375
|
+
'message to the operator.';
|
|
376
376
|
continue;
|
|
377
377
|
}
|
|
378
378
|
// STRICT: only tool calls and respond reach the operator. Plain text is
|
|
@@ -387,12 +387,12 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
387
387
|
});
|
|
388
388
|
message =
|
|
389
389
|
(justRanTool
|
|
390
|
-
? '
|
|
391
|
-
: '
|
|
392
|
-
'
|
|
393
|
-
'
|
|
390
|
+
? 'You stopped after a tool call and wrote plain text. '
|
|
391
|
+
: 'You wrote plain text without a tool call. ') +
|
|
392
|
+
'The task is not finished. Reply with EXACTLY one JSON tool-call ' +
|
|
393
|
+
'object, no text before or after, for example: ' +
|
|
394
394
|
'{\"tool\": \"Bash\", \"args\": {\"command\": \"...\"}}. ' +
|
|
395
|
-
'
|
|
395
|
+
'If the task is really done — call respond with the final message.';
|
|
396
396
|
continue;
|
|
397
397
|
}
|
|
398
398
|
// Budget exhausted. Ask for respond EXACTLY once more; if the model
|
|
@@ -404,8 +404,8 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
404
404
|
response: rawResponse.slice(0, 500),
|
|
405
405
|
});
|
|
406
406
|
message =
|
|
407
|
-
'
|
|
408
|
-
'
|
|
407
|
+
'Last step: call the respond tool with the final message ' +
|
|
408
|
+
'to the operator. Do not write plain text — only a respond call, for example: ' +
|
|
409
409
|
'{\"tool\": \"respond\", \"args\": {\"message\": \"...\"}}';
|
|
410
410
|
continue;
|
|
411
411
|
}
|
|
@@ -450,16 +450,16 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
450
450
|
stallRetries++;
|
|
451
451
|
transcript?.log('empty_respond', { attempt: stallRetries });
|
|
452
452
|
if (debugLog) {
|
|
453
|
-
console.error('
|
|
453
|
+
console.error('warning: empty respond, asking to continue (attempt ' +
|
|
454
454
|
stallRetries +
|
|
455
455
|
'/' +
|
|
456
456
|
MAX_STALL_RETRIES +
|
|
457
457
|
')');
|
|
458
458
|
}
|
|
459
459
|
message =
|
|
460
|
-
'
|
|
461
|
-
'
|
|
462
|
-
'
|
|
460
|
+
'You called respond with an empty message. If the task is done — ' +
|
|
461
|
+
'call respond with the final message to the operator. If not — ' +
|
|
462
|
+
'continue the work with a tool call.';
|
|
463
463
|
continue;
|
|
464
464
|
}
|
|
465
465
|
// An EMPTY respond must NEVER be a silent final: the operator would see
|
|
@@ -469,9 +469,9 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
469
469
|
// the task normally.
|
|
470
470
|
if (!isMeaningfulRespond(msg)) {
|
|
471
471
|
transcript?.log('empty_respond_exhausted', { response: rawResponse });
|
|
472
|
-
safeWarning('
|
|
473
|
-
'
|
|
474
|
-
return '
|
|
472
|
+
safeWarning('The model called respond without text, and the retry limit is exhausted. ' +
|
|
473
|
+
'Check the DeepSeek chat manually.');
|
|
474
|
+
return 'The model produced no final message (empty respond).';
|
|
475
475
|
}
|
|
476
476
|
safeAssistantMessage(msg);
|
|
477
477
|
transcript?.log('assistant_final', { message: msg });
|
|
@@ -485,7 +485,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
485
485
|
for (const call of callsToRun) {
|
|
486
486
|
const tool = tools.find((t) => t.name === call.tool);
|
|
487
487
|
if (!tool) {
|
|
488
|
-
const err =
|
|
488
|
+
const err = `Unknown tool: ${call.tool}`;
|
|
489
489
|
safeToolResult(err);
|
|
490
490
|
transcript?.log('tool_error', { tool: call.tool, error: err });
|
|
491
491
|
results.push({ tool: call.tool, result: err });
|
|
@@ -498,7 +498,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
498
498
|
result = await tool.fn(call.args);
|
|
499
499
|
}
|
|
500
500
|
catch (e) {
|
|
501
|
-
result =
|
|
501
|
+
result = `Error: ${e.message}`;
|
|
502
502
|
}
|
|
503
503
|
safeToolResult(result);
|
|
504
504
|
transcript?.log('tool_result', {
|
|
@@ -536,7 +536,7 @@ export async function runAgentLoop({ browser, tools, task, workdir, maxIteration
|
|
|
536
536
|
.join('\n\n');
|
|
537
537
|
}
|
|
538
538
|
}
|
|
539
|
-
return '
|
|
539
|
+
return 'Iteration limit reached.';
|
|
540
540
|
}
|
|
541
541
|
// The answer looks like a tool call, but parseToolCall() did not recognize it.
|
|
542
542
|
// Used as a safeguard against "the agent called a tool and stopped": in that
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
// Helpers for the extra slash commands (/diff, /cost, /export, /doctor,
|
|
3
|
+
// /permissions, /review, /add-dir). Pure functions, unit-tested without a
|
|
4
|
+
// live agent/browser. index.ts only renders their output.
|
|
5
|
+
const NL = String.fromCharCode(10);
|
|
6
|
+
const CR = String.fromCharCode(13);
|
|
7
|
+
// ---------- /diff ----------
|
|
8
|
+
export function formatDiff(diffText, opts = {}) {
|
|
9
|
+
const maxLines = opts.maxLines ?? 200;
|
|
10
|
+
const trimmed = String(diffText ?? '').split(CR + NL).join(NL).trimEnd();
|
|
11
|
+
if (!trimmed)
|
|
12
|
+
return '(no changes)';
|
|
13
|
+
const lines = trimmed.split(NL);
|
|
14
|
+
if (lines.length <= maxLines)
|
|
15
|
+
return trimmed;
|
|
16
|
+
const head = lines.slice(0, maxLines).join(NL);
|
|
17
|
+
const rest = lines.length - maxLines;
|
|
18
|
+
return (head +
|
|
19
|
+
NL +
|
|
20
|
+
'... [' + rest + ' more lines, use Bash git diff for the full output]');
|
|
21
|
+
}
|
|
22
|
+
export function diffGitArgs(staged = false) {
|
|
23
|
+
return staged ? 'git diff --staged' : 'git diff';
|
|
24
|
+
}
|
|
25
|
+
export function summarizeTranscript(entries) {
|
|
26
|
+
const stats = {
|
|
27
|
+
turns: 0,
|
|
28
|
+
toolCalls: 0,
|
|
29
|
+
toolCounts: {},
|
|
30
|
+
durationMs: 0,
|
|
31
|
+
startedAt: null,
|
|
32
|
+
};
|
|
33
|
+
for (const e of entries) {
|
|
34
|
+
if (!e || typeof e !== 'object')
|
|
35
|
+
continue;
|
|
36
|
+
if (e.type === 'user_task')
|
|
37
|
+
stats.turns++;
|
|
38
|
+
if (e.type === 'tool_call') {
|
|
39
|
+
stats.toolCalls++;
|
|
40
|
+
const tool = String(e.tool ?? '?');
|
|
41
|
+
stats.toolCounts[tool] = (stats.toolCounts[tool] || 0) + 1;
|
|
42
|
+
}
|
|
43
|
+
if (typeof e.elapsed === 'number' && e.elapsed > stats.durationMs) {
|
|
44
|
+
stats.durationMs = e.elapsed;
|
|
45
|
+
}
|
|
46
|
+
if (!stats.startedAt && typeof e.ts === 'string')
|
|
47
|
+
stats.startedAt = e.ts;
|
|
48
|
+
}
|
|
49
|
+
return stats;
|
|
50
|
+
}
|
|
51
|
+
export function parseTranscript(body) {
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const line of String(body ?? '').split(NL)) {
|
|
54
|
+
const t = line.trim();
|
|
55
|
+
if (!t)
|
|
56
|
+
continue;
|
|
57
|
+
try {
|
|
58
|
+
const v = JSON.parse(t);
|
|
59
|
+
if (v && typeof v === 'object')
|
|
60
|
+
out.push(v);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// skip malformed lines
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
export function formatDuration(ms) {
|
|
69
|
+
const total = Math.max(0, Math.round(ms / 1000));
|
|
70
|
+
const h = Math.floor(total / 3600);
|
|
71
|
+
const m = Math.floor((total % 3600) / 60);
|
|
72
|
+
const s = total % 60;
|
|
73
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
74
|
+
if (h > 0)
|
|
75
|
+
return h + 'h ' + pad(m) + 'm ' + pad(s) + 's';
|
|
76
|
+
if (m > 0)
|
|
77
|
+
return m + 'm ' + pad(s) + 's';
|
|
78
|
+
return s + 's';
|
|
79
|
+
}
|
|
80
|
+
export function renderCost(stats, transcriptFile) {
|
|
81
|
+
const lines = [];
|
|
82
|
+
lines.push('Session stats (DeepSeek web does not expose token counts):');
|
|
83
|
+
lines.push(' tasks: ' + stats.turns);
|
|
84
|
+
lines.push(' tool calls: ' + stats.toolCalls);
|
|
85
|
+
const top = Object.entries(stats.toolCounts).sort((a, b) => b[1] - a[1]);
|
|
86
|
+
if (top.length) {
|
|
87
|
+
lines.push(' by tool:');
|
|
88
|
+
for (const [name, n] of top)
|
|
89
|
+
lines.push(' ' + name + ': ' + n);
|
|
90
|
+
}
|
|
91
|
+
lines.push(' duration: ' + formatDuration(stats.durationMs));
|
|
92
|
+
if (stats.startedAt)
|
|
93
|
+
lines.push(' started: ' + stats.startedAt);
|
|
94
|
+
lines.push(' transcript: ' + (transcriptFile || '(off)'));
|
|
95
|
+
return lines.join(NL);
|
|
96
|
+
}
|
|
97
|
+
// ---------- /export ----------
|
|
98
|
+
export function formatExport(entries, meta = {}) {
|
|
99
|
+
const out = [];
|
|
100
|
+
out.push('# zames session export');
|
|
101
|
+
out.push('');
|
|
102
|
+
if (meta.workdir)
|
|
103
|
+
out.push('- workdir: ' + meta.workdir);
|
|
104
|
+
if (meta.chatId)
|
|
105
|
+
out.push('- chat: ' + meta.chatId);
|
|
106
|
+
out.push('- exported: ' + new Date().toISOString());
|
|
107
|
+
out.push('');
|
|
108
|
+
for (const e of entries) {
|
|
109
|
+
const type = String(e.type ?? '?');
|
|
110
|
+
if (type === 'user_task') {
|
|
111
|
+
out.push('## Task');
|
|
112
|
+
out.push('');
|
|
113
|
+
out.push(String(e.task ?? ''));
|
|
114
|
+
out.push('');
|
|
115
|
+
}
|
|
116
|
+
else if (type === 'tool_call') {
|
|
117
|
+
out.push('- tool ' +
|
|
118
|
+
String(e.tool ?? '?') +
|
|
119
|
+
' ' +
|
|
120
|
+
JSON.stringify(e.args ?? {}));
|
|
121
|
+
}
|
|
122
|
+
else if (type === 'tool_result') {
|
|
123
|
+
out.push(' -> ' +
|
|
124
|
+
String(e.tool ?? '?') +
|
|
125
|
+
': ' +
|
|
126
|
+
String(e.result ?? '').slice(0, 500));
|
|
127
|
+
}
|
|
128
|
+
else if (type === 'assistant_final') {
|
|
129
|
+
out.push('## Answer');
|
|
130
|
+
out.push('');
|
|
131
|
+
out.push(String(e.message ?? ''));
|
|
132
|
+
out.push('');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return out.join(NL);
|
|
136
|
+
}
|
|
137
|
+
export function defaultExportPath(workdir, now = new Date()) {
|
|
138
|
+
const stamp = now
|
|
139
|
+
.toISOString()
|
|
140
|
+
.replace(/[:.]/g, '-')
|
|
141
|
+
.replace('T', '_')
|
|
142
|
+
.replace('Z', '');
|
|
143
|
+
return path.join(workdir, 'zames-export-' + stamp + '.md');
|
|
144
|
+
}
|
|
145
|
+
export function renderDoctor(d) {
|
|
146
|
+
const rows = [];
|
|
147
|
+
const row = (ok, label, value) => {
|
|
148
|
+
rows.push(' ' + (ok ? '[OK] ' : '[WARN]') + ' ' + label.padEnd(16) + ' ' + value);
|
|
149
|
+
};
|
|
150
|
+
row(true, 'node', d.nodeVersion);
|
|
151
|
+
row(true, 'platform', d.platform);
|
|
152
|
+
row(true, 'workdir', d.workdir);
|
|
153
|
+
row(d.gitOk, 'git', d.gitOk ? 'repo (' + (d.gitBranch || 'detached') + ')' : 'not a repository');
|
|
154
|
+
row(d.configOk, 'config', d.configOk ? 'loaded' : 'error: ' + (d.configError || 'unknown'));
|
|
155
|
+
row(true, 'browser', d.browserChannel ? d.browserChannel : 'bundled chromium');
|
|
156
|
+
row(!!d.clipboardTool, 'clipboard', d.clipboardTool || 'no tool found (image paste disabled)');
|
|
157
|
+
row(true, 'mcp', d.mcpServers + ' server(s), ' + d.mcpTools + ' tool(s)');
|
|
158
|
+
row(d.transcriptOk, 'transcript', d.transcriptOk ? 'on' : 'off');
|
|
159
|
+
return 'Doctor:' + NL + rows.join(NL);
|
|
160
|
+
}
|
|
161
|
+
export function renderPermissions(p) {
|
|
162
|
+
const onoff = (b) => (b ? 'ask' : 'allow');
|
|
163
|
+
const lines = [];
|
|
164
|
+
lines.push('Tool permissions (confirmation settings):');
|
|
165
|
+
lines.push(' Write: ' + onoff(p.write));
|
|
166
|
+
lines.push(' Edit: ' + onoff(p.edit));
|
|
167
|
+
lines.push(' Bash: ' + onoff(p.bash));
|
|
168
|
+
if (p.alwaysConfirm.length) {
|
|
169
|
+
lines.push(' Always confirm (regex):');
|
|
170
|
+
for (const re of p.alwaysConfirm)
|
|
171
|
+
lines.push(' ' + re);
|
|
172
|
+
}
|
|
173
|
+
lines.push('');
|
|
174
|
+
lines.push('Change with: /config set confirmation.write false (and .edit / .bash)');
|
|
175
|
+
return lines.join(NL);
|
|
176
|
+
}
|
|
177
|
+
// ---------- /add-dir ----------
|
|
178
|
+
export function resolveExtraDir(input, workdir) {
|
|
179
|
+
const raw = String(input ?? '').trim();
|
|
180
|
+
if (!raw)
|
|
181
|
+
return { error: 'Usage: /add-dir <path>' };
|
|
182
|
+
const abs = path.resolve(workdir, raw);
|
|
183
|
+
if (abs === path.resolve(workdir)) {
|
|
184
|
+
return { error: 'This is already the working directory.' };
|
|
185
|
+
}
|
|
186
|
+
return { path: abs };
|
|
187
|
+
}
|
|
188
|
+
// ---------- /review ----------
|
|
189
|
+
export function buildReviewPrompt(focus, hasStaged = false) {
|
|
190
|
+
const scope = hasStaged ? 'staged' : 'uncommitted';
|
|
191
|
+
const f = String(focus ?? '').trim();
|
|
192
|
+
let s = 'Review the ' +
|
|
193
|
+
scope +
|
|
194
|
+
' changes in this repository (use GitDiff). ' +
|
|
195
|
+
'Focus on real bugs: correctness, race conditions, error handling, ' +
|
|
196
|
+
'security, and missing tests. Do NOT invent problems and do NOT change ' +
|
|
197
|
+
'code: only report findings with file:line and a short rationale.';
|
|
198
|
+
if (f)
|
|
199
|
+
s += NL + 'Extra focus: ' + f;
|
|
200
|
+
return s;
|
|
201
|
+
}
|