zames_pro 2.48.0 → 2.49.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/dist/browser.js +26 -11
- package/dist/extraTools.js +20 -13
- package/dist/i18n.js +18 -0
- package/dist/index.js +86 -6
- package/dist/input.js +20 -1
- package/dist/sessions.js +6 -1
- package/dist/tools.js +2 -2
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -87,6 +87,13 @@ export function askPassword(question) {
|
|
|
87
87
|
}
|
|
88
88
|
const USER_DATA_DIR = path.join(os.homedir(), '.zames', 'profile');
|
|
89
89
|
const CHAT_URL = 'https://chat.deepseek.com/';
|
|
90
|
+
// The "New chat" control is NOT a <button>/<a> on the current DeepSeek build
|
|
91
|
+
// (it is a <div>), so matching only button/a missed it, the click timed out
|
|
92
|
+
// and newChat() silently no-opped. Match any clickable-ish element whose text
|
|
93
|
+
// is EXACTLY the new-chat label (an anchored regex keeps a big outer container
|
|
94
|
+
// from matching its long descendant text).
|
|
95
|
+
const NEW_CHAT_SELECTOR = 'button, a, [role="button"], div, span';
|
|
96
|
+
const NEW_CHAT_TEXT_RE = /^\s*(new chat|новый чат|новый диалог)\s*$/i;
|
|
90
97
|
// Cache of the sanitized (non-Headless) User-Agent, keyed by the browser
|
|
91
98
|
// engine path. On the FIRST headless run we detect the real UA, strip the
|
|
92
99
|
// "Headless" marker and store it here; later runs pass it straight to
|
|
@@ -1175,8 +1182,8 @@ export class DeepSeekBrowser {
|
|
|
1175
1182
|
async newChat() {
|
|
1176
1183
|
const before = await this.getCurrentChatId();
|
|
1177
1184
|
const newChatBtn = this.page
|
|
1178
|
-
.locator(
|
|
1179
|
-
.filter({ hasText:
|
|
1185
|
+
.locator(NEW_CHAT_SELECTOR)
|
|
1186
|
+
.filter({ hasText: NEW_CHAT_TEXT_RE })
|
|
1180
1187
|
.first();
|
|
1181
1188
|
let clicked = false;
|
|
1182
1189
|
try {
|
|
@@ -1184,26 +1191,34 @@ export class DeepSeekBrowser {
|
|
|
1184
1191
|
clicked = true;
|
|
1185
1192
|
}
|
|
1186
1193
|
catch {
|
|
1187
|
-
// fall through to the goto fallback below
|
|
1194
|
+
// No usable control — fall through to the goto fallback below.
|
|
1188
1195
|
}
|
|
1189
|
-
//
|
|
1190
|
-
//
|
|
1191
|
-
//
|
|
1196
|
+
// Poll for the POSITIVE signal: the chat id must actually change. The old
|
|
1197
|
+
// code accepted `!clicked` as success, so a failed click returned as if a
|
|
1198
|
+
// fresh chat had opened and the next prompt went into the OLD chat (this
|
|
1199
|
+
// is what made auto-compact loop on the same chat). The old chat also has
|
|
1200
|
+
// a ready input, so an input alone is not proof.
|
|
1192
1201
|
let ok = false;
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
if (
|
|
1202
|
+
if (clicked) {
|
|
1203
|
+
const deadline = Date.now() + 5000;
|
|
1204
|
+
while (Date.now() < deadline) {
|
|
1205
|
+
if (await this._chatIdChangedFrom(before)) {
|
|
1197
1206
|
ok = true;
|
|
1198
1207
|
break;
|
|
1199
1208
|
}
|
|
1209
|
+
await this.page.waitForTimeout(100);
|
|
1200
1210
|
}
|
|
1201
|
-
await this.page.waitForTimeout(100);
|
|
1202
1211
|
}
|
|
1203
1212
|
if (!ok) {
|
|
1213
|
+
// Navigating to the base URL always yields a fresh chat.
|
|
1204
1214
|
await this.page.goto(CHAT_URL, { waitUntil: 'domcontentloaded' });
|
|
1205
1215
|
await this._awaitInput(5000);
|
|
1206
1216
|
}
|
|
1217
|
+
// The chat id sniffed off the network belongs to the PREVIOUS chat; the
|
|
1218
|
+
// base URL carries no id, so getCurrentChatId() would otherwise return the
|
|
1219
|
+
// STALE old id for the fresh chat. Clear it — a fresh chat has no id until
|
|
1220
|
+
// the first message creates one.
|
|
1221
|
+
this._netChatId = null;
|
|
1207
1222
|
}
|
|
1208
1223
|
async _findVisible(selectors, timeout = 1000) {
|
|
1209
1224
|
for (const sel of selectors) {
|
package/dist/extraTools.js
CHANGED
|
@@ -15,15 +15,23 @@ function decodeContent(content, contentBase64) {
|
|
|
15
15
|
}
|
|
16
16
|
return String(content ?? '');
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const todoList = [];
|
|
22
|
-
export function getTodos() {
|
|
23
|
-
return todoList.map((t) => ({ ...t }));
|
|
18
|
+
const defaultTodoStore = { items: [] };
|
|
19
|
+
export function createTodoStore(onChange) {
|
|
20
|
+
return { items: [], onChange };
|
|
24
21
|
}
|
|
25
|
-
export function
|
|
26
|
-
|
|
22
|
+
export function getTodos(store = defaultTodoStore) {
|
|
23
|
+
return store.items.map((t) => ({ ...t }));
|
|
24
|
+
}
|
|
25
|
+
export function resetTodos(store = defaultTodoStore) {
|
|
26
|
+
store.items.length = 0;
|
|
27
|
+
}
|
|
28
|
+
// Replace the whole list. Used by TodoWrite and by the session restore (which
|
|
29
|
+
// re-injects the persisted checklist).
|
|
30
|
+
export function setTodos(items, store = defaultTodoStore) {
|
|
31
|
+
const clean = normalizeTodos(items);
|
|
32
|
+
store.items.length = 0;
|
|
33
|
+
store.items.push(...clean);
|
|
34
|
+
store.onChange?.(store.items.map((t) => ({ ...t })));
|
|
27
35
|
}
|
|
28
36
|
export function renderTodos(items) {
|
|
29
37
|
if (!items.length)
|
|
@@ -160,7 +168,8 @@ export function applyUpdateHunk(content, hunkLines) {
|
|
|
160
168
|
};
|
|
161
169
|
}
|
|
162
170
|
// ---------- factory ----------
|
|
163
|
-
export function createExtraTools(workdir, { undo } = {}) {
|
|
171
|
+
export function createExtraTools(workdir, { undo, todos } = {}) {
|
|
172
|
+
const todoStore = todos || defaultTodoStore;
|
|
164
173
|
const root = path.resolve(workdir);
|
|
165
174
|
const safe = (p) => {
|
|
166
175
|
const resolved = path.resolve(root, p);
|
|
@@ -289,10 +298,8 @@ export function createExtraTools(workdir, { undo } = {}) {
|
|
|
289
298
|
else {
|
|
290
299
|
throw new Error('todos must be an array.');
|
|
291
300
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
todoList.push(...items);
|
|
295
|
-
return renderTodos(todoList);
|
|
301
|
+
setTodos(normalizeTodos(list), todoStore);
|
|
302
|
+
return renderTodos(todoStore.items);
|
|
296
303
|
},
|
|
297
304
|
},
|
|
298
305
|
{
|
package/dist/i18n.js
CHANGED
|
@@ -205,6 +205,10 @@ const CATALOG = {
|
|
|
205
205
|
ru: '/queue [clear] показать/очистить очередь сообщений',
|
|
206
206
|
en: '/queue [clear] list/clear the pending message queue',
|
|
207
207
|
},
|
|
208
|
+
'help.cmd.tasks': {
|
|
209
|
+
ru: '/tasks список задач агента (TodoWrite)',
|
|
210
|
+
en: '/tasks the agent task list (TodoWrite)',
|
|
211
|
+
},
|
|
208
212
|
'help.cmd.thinking': {
|
|
209
213
|
ru: '/thinking [on|off] включить/выключить режим размышления (можно во время работы)',
|
|
210
214
|
en: '/thinking [on|off] toggle Deep thinking (works while the agent is busy)',
|
|
@@ -496,6 +500,20 @@ const CATALOG = {
|
|
|
496
500
|
'status.transcript': { ru: 'Транскрипт: {v}', en: 'Transcript: {v}' },
|
|
497
501
|
'status.locale': { ru: 'Язык: {v}', en: 'Language: {v}' },
|
|
498
502
|
'status.goal': { ru: 'Цель сессии: {v}', en: 'Session goal: {v}' },
|
|
503
|
+
'status.tasks_summary': {
|
|
504
|
+
ru: 'задачи: {done}/{total}',
|
|
505
|
+
en: 'tasks: {done}/{total}',
|
|
506
|
+
},
|
|
507
|
+
// ---------- tasks (/tasks) ----------
|
|
508
|
+
'tasks.title': { ru: 'Задачи агента:', en: 'Agent tasks:' },
|
|
509
|
+
'tasks.empty': {
|
|
510
|
+
ru: 'Список задач пуст (агент ещё не вызывал TodoWrite).',
|
|
511
|
+
en: 'The task list is empty (the agent has not called TodoWrite yet).',
|
|
512
|
+
},
|
|
513
|
+
'tasks.restored': {
|
|
514
|
+
ru: 'Восстановлен список задач: {done}/{total}',
|
|
515
|
+
en: 'Task list restored: {done}/{total}',
|
|
516
|
+
},
|
|
499
517
|
// ---------- messages ----------
|
|
500
518
|
'msg.working_dir': {
|
|
501
519
|
ru: 'Рабочая директория: {v}',
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import { theme } from './theme.js';
|
|
7
7
|
import { DeepSeekBrowser, headlessUACacheReady } from './browser.js';
|
|
8
8
|
import { createTools } from './tools.js';
|
|
9
|
+
import { createTodoStore, getTodos, setTodos, resetTodos, renderTodos, } from './extraTools.js';
|
|
9
10
|
import { runAgentLoop } from './agent-loop.js';
|
|
10
11
|
import { createSpinner } from './spinner.js';
|
|
11
12
|
import { LineEditor, expandPastes, pasteReplacement, formatCompactTokens, } from './input.js';
|
|
@@ -21,7 +22,7 @@ import { performCompact } from './compact.js';
|
|
|
21
22
|
import { Scheduler, parseInterval, formatInterval, formatJobLine, parseCron, } from './scheduler.js';
|
|
22
23
|
import { renderMarkdown } from './markdown.js';
|
|
23
24
|
import { closeWeb } from './web.js';
|
|
24
|
-
import { saveSession, loadLastSession, listSessions, sessionsDir, } from './sessions.js';
|
|
25
|
+
import { saveSession, loadLastSession, readSession, listSessions, sessionsDir, } from './sessions.js';
|
|
25
26
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
27
|
const NL = String.fromCharCode(10);
|
|
27
28
|
// Floor for a /loop interval. A loop faster than the send throttle (15s)
|
|
@@ -330,6 +331,7 @@ ${theme.bold(t('help.commands'))}
|
|
|
330
331
|
${t('help.cmd.web')}
|
|
331
332
|
${t('help.cmd.compact')}
|
|
332
333
|
${t('help.cmd.queue')}
|
|
334
|
+
${t('help.cmd.tasks')}
|
|
333
335
|
${t('help.cmd.config')}
|
|
334
336
|
${t('help.cmd.lang')}
|
|
335
337
|
${t('help.cmd.debug_dom')}
|
|
@@ -393,6 +395,7 @@ const SLASH_COMMANDS = [
|
|
|
393
395
|
{ name: '/web', key: 'help.cmd.web' },
|
|
394
396
|
{ name: '/compact', key: 'help.cmd.compact' },
|
|
395
397
|
{ name: '/queue', key: 'help.cmd.queue' },
|
|
398
|
+
{ name: '/tasks', key: 'help.cmd.tasks' },
|
|
396
399
|
{ name: '/config', key: 'help.cmd.config' },
|
|
397
400
|
{ name: '/skills', key: 'help.cmd.skills' },
|
|
398
401
|
{ name: '/memory', key: 'help.cmd.memory' },
|
|
@@ -1334,6 +1337,51 @@ async function main() {
|
|
|
1334
1337
|
let lastChats = [];
|
|
1335
1338
|
let currentChatId = null;
|
|
1336
1339
|
let running = true;
|
|
1340
|
+
// The agent's TodoWrite checklist, shared with the tool factory and
|
|
1341
|
+
// persisted in the session JSON. A store (not module state) so /reload does
|
|
1342
|
+
// not silently drop the list, and so the writer in onTodosChange sees the
|
|
1343
|
+
// same instance the tools mutate.
|
|
1344
|
+
const todoStore = createTodoStore();
|
|
1345
|
+
const persistTodos = () => {
|
|
1346
|
+
if (!currentChatId)
|
|
1347
|
+
return;
|
|
1348
|
+
saveSession({
|
|
1349
|
+
id: currentChatId,
|
|
1350
|
+
workdir: currentWorkdir,
|
|
1351
|
+
todos: getTodos(todoStore),
|
|
1352
|
+
});
|
|
1353
|
+
};
|
|
1354
|
+
// Write the checklist whenever the agent changes it, so a restart/resume
|
|
1355
|
+
// brings the tasks back (they used to be in-memory only).
|
|
1356
|
+
todoStore.onChange = () => persistTodos();
|
|
1357
|
+
// A compact "tasks: 2/5" summary for the status line. Empty when the list
|
|
1358
|
+
// is empty, so an idle status is not cluttered.
|
|
1359
|
+
const todosSummary = () => {
|
|
1360
|
+
const items = getTodos(todoStore);
|
|
1361
|
+
if (!items.length)
|
|
1362
|
+
return '';
|
|
1363
|
+
const done = items.filter((t) => t.status === 'completed').length;
|
|
1364
|
+
return t('status.tasks_summary', {
|
|
1365
|
+
done: String(done),
|
|
1366
|
+
total: String(items.length),
|
|
1367
|
+
});
|
|
1368
|
+
};
|
|
1369
|
+
// Re-inject a persisted checklist after /resume or the startup restore.
|
|
1370
|
+
// The list is REPLACED (not merged): a resumed chat with no saved checklist
|
|
1371
|
+
// must clear the previous chat's tasks, otherwise the status line keeps
|
|
1372
|
+
// showing them for a chat that never had them.
|
|
1373
|
+
const loadSessionTodos = (id) => {
|
|
1374
|
+
const s = readSession(id);
|
|
1375
|
+
const items = s?.todos || [];
|
|
1376
|
+
setTodos(items, todoStore);
|
|
1377
|
+
if (!items.length)
|
|
1378
|
+
return;
|
|
1379
|
+
const done = items.filter((x) => x.status === 'completed').length;
|
|
1380
|
+
console.log(theme.system(t('tasks.restored', {
|
|
1381
|
+
done: String(done),
|
|
1382
|
+
total: String(items.length),
|
|
1383
|
+
})));
|
|
1384
|
+
};
|
|
1337
1385
|
// Long-lived session goal (set via /goal). Prepended to every task message so
|
|
1338
1386
|
// the model keeps the big picture across many turns. Persisted per working
|
|
1339
1387
|
// directory in <project>/.zames-goal (best-effort); restored right away.
|
|
@@ -1441,6 +1489,7 @@ async function main() {
|
|
|
1441
1489
|
sendSystemPromptNext = promptOnResume();
|
|
1442
1490
|
saveLastChat(resumeId, currentWorkdir);
|
|
1443
1491
|
console.log(theme.system(t('msg.chat_opened') + ' ' + resumeId + String.fromCharCode(10)));
|
|
1492
|
+
loadSessionTodos(resumeId);
|
|
1444
1493
|
await printRestoredHistory(browser, null, resumeId);
|
|
1445
1494
|
}
|
|
1446
1495
|
catch (e) {
|
|
@@ -1490,6 +1539,9 @@ async function main() {
|
|
|
1490
1539
|
// Queue badge: show "⧗N" while messages are waiting to be sent after the
|
|
1491
1540
|
// current task. Pulled on every render (like the context counter).
|
|
1492
1541
|
ed.onQueueQuery = () => pendingQueue.length;
|
|
1542
|
+
// Task-list summary ("tasks: 2/5") before the context counter. Pulled on
|
|
1543
|
+
// every render, like the queue badge.
|
|
1544
|
+
ed.onTasksQuery = () => todosSummary();
|
|
1493
1545
|
// Use the configured context window for the fill percentage/color.
|
|
1494
1546
|
ed.setContextLimit(config.ui.contextLimit);
|
|
1495
1547
|
// Toggle icons (🧠 deep thinking, 🌐 web search) before the context
|
|
@@ -2061,6 +2113,9 @@ async function main() {
|
|
|
2061
2113
|
await browser.newChat();
|
|
2062
2114
|
freshChatNext = false;
|
|
2063
2115
|
sendSystemPromptNext = true;
|
|
2116
|
+
// A new chat starts a fresh checklist: keeping the old tasks would
|
|
2117
|
+
// label them as "restored" in a chat that never saw them.
|
|
2118
|
+
resetTodos(todoStore);
|
|
2064
2119
|
currentChatId = await browser.getCurrentChatId();
|
|
2065
2120
|
saveLastChat(currentChatId, currentWorkdir);
|
|
2066
2121
|
transcript.log('new_chat');
|
|
@@ -2310,6 +2365,7 @@ async function main() {
|
|
|
2310
2365
|
theme.system(sendSystemPromptNext
|
|
2311
2366
|
? String.fromCharCode(10) + t('chats.prompt_will_resend')
|
|
2312
2367
|
: String.fromCharCode(10) + t('chats.prompt_no_resend')));
|
|
2368
|
+
loadSessionTodos(pick.id);
|
|
2313
2369
|
await printRestoredHistory(browser, editor, pick.id);
|
|
2314
2370
|
}
|
|
2315
2371
|
catch (e) {
|
|
@@ -2370,6 +2426,7 @@ async function main() {
|
|
|
2370
2426
|
transcript.log('resume_chat', { id });
|
|
2371
2427
|
console.log(theme.assistant(t('msg.chat_opened')) +
|
|
2372
2428
|
theme.system(String.fromCharCode(10)));
|
|
2429
|
+
loadSessionTodos(id);
|
|
2373
2430
|
await printRestoredHistory(browser, editor, id);
|
|
2374
2431
|
}
|
|
2375
2432
|
catch (e) {
|
|
@@ -2505,6 +2562,20 @@ async function main() {
|
|
|
2505
2562
|
console.log(theme.dim(t('msg.queue_cleared_hint')));
|
|
2506
2563
|
continue;
|
|
2507
2564
|
}
|
|
2565
|
+
if (lower === '/tasks') {
|
|
2566
|
+
const items = getTodos(todoStore);
|
|
2567
|
+
if (!items.length) {
|
|
2568
|
+
console.log(theme.dim(t('tasks.empty')));
|
|
2569
|
+
continue;
|
|
2570
|
+
}
|
|
2571
|
+
console.log(theme.system(t('tasks.title')));
|
|
2572
|
+
// Skip the "Todo list (N/M done):" header line from renderTodos — the
|
|
2573
|
+
// /tasks title already says it.
|
|
2574
|
+
for (const line of renderTodos(items).split(NL).slice(1)) {
|
|
2575
|
+
console.log(theme.assistant(line));
|
|
2576
|
+
}
|
|
2577
|
+
continue;
|
|
2578
|
+
}
|
|
2508
2579
|
if (lower.startsWith('/thinking') || lower.startsWith('/web')) {
|
|
2509
2580
|
const tg = parseLiveToggle(trimmed);
|
|
2510
2581
|
if (tg) {
|
|
@@ -2592,7 +2663,10 @@ async function main() {
|
|
|
2592
2663
|
'layout; and any gotchas or rules an agent must follow. Keep it concise ' +
|
|
2593
2664
|
'and factual, based only on what you find. Write the file with the Write ' +
|
|
2594
2665
|
'tool, then reply via respond with a one-line summary.';
|
|
2595
|
-
const initTools = mod.createTools(currentWorkdir, {
|
|
2666
|
+
const initTools = mod.createTools(currentWorkdir, {
|
|
2667
|
+
undo,
|
|
2668
|
+
todos: todoStore,
|
|
2669
|
+
});
|
|
2596
2670
|
if (editor)
|
|
2597
2671
|
editor.busy = true;
|
|
2598
2672
|
console.log(theme.system(t('init.analyzing')));
|
|
@@ -2874,7 +2948,7 @@ async function main() {
|
|
|
2874
2948
|
transcript,
|
|
2875
2949
|
ui: editor,
|
|
2876
2950
|
buildSystemPrompt: mod.buildSystemPrompt,
|
|
2877
|
-
tools: mod.createTools(currentWorkdir, { undo }),
|
|
2951
|
+
tools: mod.createTools(currentWorkdir, { undo, todos: todoStore }),
|
|
2878
2952
|
answerTimeoutMs: config.browser.answerTimeoutMs,
|
|
2879
2953
|
fallbackLimit: COMPACT_FALLBACK_LIMIT,
|
|
2880
2954
|
});
|
|
@@ -2892,7 +2966,10 @@ async function main() {
|
|
|
2892
2966
|
const staged = rest.indexOf('--staged') !== -1;
|
|
2893
2967
|
const focus = rest.replace(/--staged/g, '').trim();
|
|
2894
2968
|
const reviewTask = buildReviewPrompt(focus, staged);
|
|
2895
|
-
const reviewTools = mod.createTools(currentWorkdir, {
|
|
2969
|
+
const reviewTools = mod.createTools(currentWorkdir, {
|
|
2970
|
+
undo,
|
|
2971
|
+
todos: todoStore,
|
|
2972
|
+
});
|
|
2896
2973
|
if (editor)
|
|
2897
2974
|
editor.busy = true;
|
|
2898
2975
|
try {
|
|
@@ -3010,7 +3087,7 @@ async function main() {
|
|
|
3010
3087
|
// Dev mode: pick up fresh logic modules before the task.
|
|
3011
3088
|
await autoReload();
|
|
3012
3089
|
transcript.log('user_task', { task: taskText, workdir: currentWorkdir });
|
|
3013
|
-
const tools = mod.createTools(currentWorkdir, { undo });
|
|
3090
|
+
const tools = mod.createTools(currentWorkdir, { undo, todos: todoStore });
|
|
3014
3091
|
if (mcpPool)
|
|
3015
3092
|
tools.push(...mcpPool.tools);
|
|
3016
3093
|
if (editor)
|
|
@@ -3046,7 +3123,10 @@ async function main() {
|
|
|
3046
3123
|
transcript,
|
|
3047
3124
|
ui: editor,
|
|
3048
3125
|
buildSystemPrompt: mod.buildSystemPrompt,
|
|
3049
|
-
tools: mod.createTools(currentWorkdir, {
|
|
3126
|
+
tools: mod.createTools(currentWorkdir, {
|
|
3127
|
+
undo,
|
|
3128
|
+
todos: todoStore,
|
|
3129
|
+
}),
|
|
3050
3130
|
answerTimeoutMs: config.browser.answerTimeoutMs,
|
|
3051
3131
|
quiet: true,
|
|
3052
3132
|
skipUiLock: true,
|
package/dist/input.js
CHANGED
|
@@ -315,6 +315,9 @@ export class LineEditor {
|
|
|
315
315
|
// A callback the editor calls before every render to read the CURRENT queue
|
|
316
316
|
// length, so the badge stays live without the caller polling.
|
|
317
317
|
onQueueQuery;
|
|
318
|
+
// A callback the editor calls before every render to read the agent's
|
|
319
|
+
// current task-list summary ("tasks: 2/5"), empty when there is no list.
|
|
320
|
+
onTasksQuery;
|
|
318
321
|
// Whether the DeepSeek "Deep thinking" / "Smart search" toggles are ON.
|
|
319
322
|
// Shown as two colored icons before the context counter: a dim gray icon
|
|
320
323
|
// means off, a teal-green icon means on.
|
|
@@ -395,6 +398,7 @@ export class LineEditor {
|
|
|
395
398
|
this.onContextQuery = null;
|
|
396
399
|
this.queueLength = 0;
|
|
397
400
|
this.onQueueQuery = null;
|
|
401
|
+
this.onTasksQuery = null;
|
|
398
402
|
}
|
|
399
403
|
// Refresh the toggle icon states from onToggleQuery (if wired) before a
|
|
400
404
|
// render. A broken callback must never break the render.
|
|
@@ -460,6 +464,21 @@ export class LineEditor {
|
|
|
460
464
|
return '';
|
|
461
465
|
return theme.warn('⧗' + this.queueLength) + ' ';
|
|
462
466
|
}
|
|
467
|
+
// The agent's task-list summary ("tasks: 2/5") shown before the context
|
|
468
|
+
// counter. Empty when there is no list, so an idle status is not cluttered.
|
|
469
|
+
// Refreshed from onTasksQuery before every render when wired.
|
|
470
|
+
_tasksBadge() {
|
|
471
|
+
if (!this.onTasksQuery)
|
|
472
|
+
return '';
|
|
473
|
+
try {
|
|
474
|
+
const s = this.onTasksQuery();
|
|
475
|
+
return s ? theme.system(s) + ' ' : '';
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
// A broken callback must never break the render.
|
|
479
|
+
return '';
|
|
480
|
+
}
|
|
481
|
+
}
|
|
463
482
|
// Icons for the DeepSeek toggles: 🧠 "Deep thinking" and 🌐 "Smart search",
|
|
464
483
|
// shown before the context counter. Only the ENABLED toggles are shown, so
|
|
465
484
|
// the state is unambiguous WITHOUT relying on the terminal coloring the
|
|
@@ -695,7 +714,7 @@ export class LineEditor {
|
|
|
695
714
|
// but the icons are still meaningful.
|
|
696
715
|
const ctxRaw = this._contextText();
|
|
697
716
|
this._refreshToggles();
|
|
698
|
-
const ctxText = this._queueBadge() + this._toggleIcons() + ctxRaw;
|
|
717
|
+
const ctxText = this._queueBadge() + this._tasksBadge() + this._toggleIcons() + ctxRaw;
|
|
699
718
|
let statusOut = '';
|
|
700
719
|
let top = 0;
|
|
701
720
|
// Never fill a row to the FULL terminal width: on terminals with
|
package/dist/sessions.js
CHANGED
|
@@ -15,7 +15,7 @@ function safeName(id) {
|
|
|
15
15
|
}
|
|
16
16
|
// Save/update a session. workdir is needed so that when launched from the same
|
|
17
17
|
// project we restore exactly its last chat.
|
|
18
|
-
export function saveSession({ id, title = '', workdir = '', }) {
|
|
18
|
+
export function saveSession({ id, title = '', workdir = '', todos, }) {
|
|
19
19
|
if (!id)
|
|
20
20
|
return null;
|
|
21
21
|
try {
|
|
@@ -34,6 +34,11 @@ export function saveSession({ id, title = '', workdir = '', }) {
|
|
|
34
34
|
createdAt: prev.createdAt || now,
|
|
35
35
|
updatedAt: now,
|
|
36
36
|
};
|
|
37
|
+
// Keep the previous checklist unless a new one is explicitly passed —
|
|
38
|
+
// saveLastChat() calls this for title/workdir only and must not wipe it.
|
|
39
|
+
const nextTodos = todos !== undefined ? todos : prev.todos;
|
|
40
|
+
if (nextTodos !== undefined)
|
|
41
|
+
data.todos = nextTodos;
|
|
37
42
|
fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf-8');
|
|
38
43
|
writeIndex({ id, workdir: data.workdir });
|
|
39
44
|
return data;
|
package/dist/tools.js
CHANGED
|
@@ -8,7 +8,7 @@ import { createExtraTools } from './extraTools.js';
|
|
|
8
8
|
// user-facing. They must be in ENGLISH (like Claude Code / Codex). Only the
|
|
9
9
|
// strings the OPERATOR sees (help, spinner, service messages) are localized —
|
|
10
10
|
// those live in src/i18n.ts (CATALOG) and are picked by the locale setting.
|
|
11
|
-
export function createTools(workdir, { undo } = {}) {
|
|
11
|
+
export function createTools(workdir, { undo, todos } = {}) {
|
|
12
12
|
const root = path.resolve(workdir);
|
|
13
13
|
const safe = (p) => {
|
|
14
14
|
const resolved = path.resolve(root, p);
|
|
@@ -256,7 +256,7 @@ export function createTools(workdir, { undo } = {}) {
|
|
|
256
256
|
];
|
|
257
257
|
const gitTools = createGitTools(workdir);
|
|
258
258
|
const webTools = createWebTools();
|
|
259
|
-
const extraTools = createExtraTools(workdir, { undo });
|
|
259
|
+
const extraTools = createExtraTools(workdir, { undo, todos });
|
|
260
260
|
const respondTool = {
|
|
261
261
|
name: 'respond',
|
|
262
262
|
description: 'Give the final answer to the user and finish the task.',
|