dsh-composer-plus 1.19.0 → 1.20.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/index.js CHANGED
@@ -6436,8 +6436,9 @@ export function installBackup(ctx, home, { graceMs = AUTO_BACKUP_GRACE_MS, retry
6436
6436
  /** Task board (Host half): a kanban board of dsh's real chats. The client draws the cards from dsh's own stores (the chat
6437
6437
  * list, each chat's live state and the projects); the Host keeps the switch and the record of the chats that the board
6438
6438
  * started: `storages/dsh-composer-plus/board-chats.json`, `{ chats: { [sessionId]: { prompt, tags, at } } }`, owner-only.
6439
- * A board start makes a new chat in a dsh project with the user's prompt (dsh's own title service names the chat); one
6440
- * short call to the new chat's model writes its tags. The old notebook board (`board.json`) is not read or changed. */
6439
+ * New task opens dsh's own new chat screen; the client sends `{sessionId}` of that chat. When its first message of its own
6440
+ * arrives (the user's, from dsh's composer: attachments, commands and the model menu as usual), the Host keeps its text
6441
+ * as the card's prompt and one short call to the chat's model writes its tags. The old `board.json` is not read. */
6441
6442
  export const BOARD_PATH = '/api/composer-plus.board';
6442
6443
  export const BOARD_CHATS_PATH = '/api/composer-plus.board-chats';
6443
6444
  export const BOARD_LIMITS = { prompt: 20000, tags: 3, tag: 24, chats: 2000 };
@@ -6445,8 +6446,6 @@ const BOARD_TAG_INPUT = 4000;
6445
6446
  const BOARD_TAG_TOKENS = 200;
6446
6447
  const BOARD_TAG_TIMEOUT_MS = 60000;
6447
6448
  export const BOARD_TAG_SYSTEM = `You label a coding task for a kanban board. Read the user's request and answer with only a JSON array of 1 to ${BOARD_LIMITS.tags} short tags, for example ["frontend","bug"]. Each tag is one or two lower-case words, at most ${BOARD_LIMITS.tag} characters, in the language of the request. No other text.`;
6448
- const BRANCH_DIR = /^(.+?)[\\/]\.worktrees[\\/][^\\/]+(?:[\\/].*)?$/;
6449
- const trimSlash = (p) => (p.length > 1 ? p.replace(/[\\/]+$/, '') : p);
6450
6449
  const boardLine = (raw) => String(raw ?? '').replace(/\s+/g, ' ').trim();
6451
6450
 
6452
6451
  /** The model's tags: the first JSON array of strings, each one line, lower case, at most BOARD_LIMITS.tag characters, no
@@ -6510,63 +6509,94 @@ export function boardChatsStore(file) {
6510
6509
  };
6511
6510
  }
6512
6511
 
6513
- /** The dsh project of a board start, by its workspace id: a registered workspace that is not a Branch per chat folder. */
6514
- export function boardProject(registry, id) {
6515
- if (!isText(id)) return undefined;
6516
- let list;
6517
- try { list = registry?.list?.() ?? []; } catch { list = []; }
6518
- return list.find((w) => w?.id === id && isText(w.path, 4096) && !BRANCH_DIR.test(trimSlash(w.path)));
6512
+ /** The text of a chat's first message of its own: the first `user/message` from the user at or after `from` (a fork's
6513
+ * inherited events come first, rule 10) that no rollback withdrew (Composer Plus's and dsh-rewind-plugin's markers, rule 9),
6514
+ * at most BOARD_LIMITS.prompt characters; undefined when there is none. */
6515
+ export function boardPromptOf(events, from = 0) {
6516
+ const spans = rollbackSpans(events, [ROLLBACK_SOURCE, REWIND_SOURCE]);
6517
+ for (const e of events) {
6518
+ if (e?.type !== 'user/message' || e.data?.source?.kind !== 'user' || !Number.isSafeInteger(e.seq) || e.seq < from) continue;
6519
+ if (spans.some(([a, b]) => e.seq >= a && e.seq <= b)) continue;
6520
+ const text = textBlocks(e.data?.content).trim();
6521
+ if (text !== '') return text.slice(0, BOARD_LIMITS.prompt);
6522
+ }
6523
+ return undefined;
6519
6524
  }
6520
6525
 
6521
- /** One short call to `route` for the tags of a prompt; [] when the call fails or answers nothing usable. */
6522
- export async function boardTagsFor({ llm, route }, prompt, signal) {
6523
- if (llm?.prepareCall === undefined || route === undefined) return [];
6524
- const timeout = AbortSignal.timeout(BOARD_TAG_TIMEOUT_MS);
6526
+ /** One short background call to `route` with `system` and one user message: the answer's text. The lowest reasoning effort
6527
+ * keeps it fast; the output cap never goes above the model's own default. Throws when the call ends with an error. */
6528
+ async function boardCall({ llm, route }, system, text, { tokens, timeoutMs }, signal) {
6529
+ const timeout = AbortSignal.timeout(timeoutMs);
6525
6530
  const callSignal = signal === undefined ? timeout : AbortSignal.any([signal, timeout]);
6526
6531
  const call = await llm.prepareCall(route, callSignal, (controls, model) => {
6527
- // The lowest reasoning effort keeps it fast; the output cap never goes above the model's own default.
6528
6532
  const first = model?.reasoning?.efforts?.[0];
6529
- const cap = Number.isSafeInteger(model?.defaultMaxTokens) && model.defaultMaxTokens > 0 ? Math.min(BOARD_TAG_TOKENS, model.defaultMaxTokens) : BOARD_TAG_TOKENS;
6533
+ const cap = Number.isSafeInteger(model?.defaultMaxTokens) && model.defaultMaxTokens > 0 ? Math.min(tokens, model.defaultMaxTokens) : tokens;
6530
6534
  return { ...controls, maxTokens: cap, ...(first === undefined ? {} : { reasoningEffort: first.id }) };
6531
6535
  });
6532
- let text = '';
6536
+ let out = '';
6533
6537
  let finish;
6534
- for await (const chunk of call.stream({ ...call.config, system: BOARD_TAG_SYSTEM, signal: callSignal,
6535
- messages: [{ role: 'user', content: [{ type: 'text', text: String(prompt).slice(0, BOARD_TAG_INPUT) }] }] })) {
6536
- if (chunk.type === 'text-delta') text += chunk.text;
6538
+ for await (const chunk of call.stream({ ...call.config, system, signal: callSignal, messages: [{ role: 'user', content: [{ type: 'text', text }] }] })) {
6539
+ if (chunk.type === 'text-delta') out += chunk.text;
6537
6540
  else if (chunk.type === 'finish') finish = chunk.reason;
6538
6541
  }
6539
6542
  if (finish?.kind === 'error' || finish?.kind === 'aborted') throw new Error(finish.failure?.message || `the model call ended: ${finish.kind}`);
6540
- return boardTags(text);
6541
- }
6542
-
6543
- /** Start a board task: a new chat in the project's dsh workspace with `prompt` as its first message (a `/command` that dsh
6544
- * knows runs as that command), then the record. All or none: when the prompt cannot be sent, the new chat is archived and
6545
- * the error says so. The prompt is sent with its own timeout, not the request's signal, so a closed request does not leave
6546
- * an empty chat. Returns `{ sessionId, agent }`. */
6547
- export async function startBoardChat({ sessionController, commands, registry, store }, project, prompt) {
6548
- const text = String(prompt ?? '').trim();
6549
- if (text === '' || text.length > BOARD_LIMITS.prompt) throw refused(`a task needs a prompt of at most ${BOARD_LIMITS.prompt} characters`, 400);
6550
- const workspace = boardProject(registry, project);
6551
- if (workspace === undefined) throw refused('this is not a dsh project', 404);
6552
- const { sessionId } = await sessionController.create({ workspaceId: workspace.id });
6553
- let agent;
6554
- try {
6555
- const found = await sessionController.resolveAgent(sessionId);
6556
- if ('error' in found) throw new Error('the new chat could not start');
6557
- agent = found.agent;
6558
- const name = /^\/([\w:.-]+)(?:\s|$)/.exec(text)?.[1];
6559
- let done;
6560
- if (name !== undefined && commands?.find?.(agent, name) !== undefined) {
6561
- done = await commands.execute(agent, text, [], AbortSignal.timeout(STOP_WAIT_MS));
6562
- if (done?.result?.kind !== 'success') throw new Error(`/${name} failed${done?.result?.text ? `: ${done.result.text}` : ''}`);
6563
- } else await sessionController.prompt({ requestId: randomUUID(), sessionId, mode: 'queue', content: [{ type: 'text', text }] }, AbortSignal.timeout(STOP_WAIT_MS));
6564
- await store.change((chats) => { chats[sessionId] = { prompt: text, tags: [], at: Date.now() }; });
6565
- } catch (error) {
6566
- const undone = await Promise.resolve(registry?.archiveSession?.(sessionId, { stopActivity: true })).then(() => registry?.archiveSession !== undefined, () => false);
6567
- throw Object.assign(new Error(`${error?.message ?? error}; ${undone ? 'the new chat was archived' : `the new chat ${sessionId} stays empty`}`), { status: Number.isInteger(error?.status) ? error.status : 502 });
6543
+ return out;
6544
+ }
6545
+
6546
+ /** One short call to `route` for the tags of a prompt; [] when it answers nothing usable. */
6547
+ export async function boardTagsFor({ llm, route }, prompt, signal) {
6548
+ if (llm?.prepareCall === undefined || route === undefined) return [];
6549
+ return boardTags(await boardCall({ llm, route }, BOARD_TAG_SYSTEM, String(prompt).slice(0, BOARD_TAG_INPUT), { tokens: BOARD_TAG_TOKENS, timeoutMs: BOARD_TAG_TIMEOUT_MS }, signal));
6550
+ }
6551
+
6552
+ /* Progress on hover: what a running chat does now. "fast" (the default) reads the chat's log only; "ai" also asks the
6553
+ * chat's model for a one- or two-sentence summary (Settings → Task board → Progress on hover). */
6554
+ export const BOARD_PROGRESS_PATH = '/api/composer-plus.board-progress';
6555
+ export const BOARD_PROGRESS_MODES = ['fast', 'ai'];
6556
+ export const BOARD_PROGRESS = { detail: 120, said: 200, summary: 240, input: 6000, tokens: 200, timeoutMs: 30000, gapMs: 20000, kept: 200 };
6557
+ export const BOARD_PROGRESS_SYSTEM = `You write the progress note of a running coding task on a kanban card. Read the newest part of the task's chat. Answer with one or two short sentences, at most ${BOARD_PROGRESS.summary} characters: what the agent does now and how far the task is. Use the language of the user's messages. Plain text only: no Markdown, no other text.`;
6558
+ /** Tool calls that the progress does not show as "the step": the todo list has its own line, a plan is not work. */
6559
+ const BOARD_PROGRESS_QUIET = new Set(['todo_write', 'plan_next_steps']);
6560
+ /** A tool call in a few words: its description (bash, a subagent), else its command's first line, a path or a pattern. */
6561
+ const toolDetail = (args) => {
6562
+ if (!plainObject(args)) return '';
6563
+ const pick = [args.description, args.command, args.file_path, args.path, args.pattern, args.url, args.query].find((v) => typeof v === 'string' && v.trim() !== '');
6564
+ return pick === undefined ? '' : cutEnd(boardLine(pick.trim().split('\n')[0]), BOARD_PROGRESS.detail);
6565
+ };
6566
+
6567
+ /** What a running chat does now, from its own events (from `from`: a fork's inherited events come first, rule 10; steps that
6568
+ * a rollback withdrew are left out, rule 9): `since` (the start of the newest turn, epoch ms), `todos` (`{done, total,
6569
+ * current?}` of the newest todo list), `tool` (`{name, detail, running}`: the newest tool call of that turn, running while it
6570
+ * has no result) and `said` (the last line of the agent's newest text in that turn). */
6571
+ export function boardProgressOf(events, from = 0) {
6572
+ const hidden = withdrawn(events);
6573
+ const out = {};
6574
+ let call;
6575
+ for (const [i, e] of events.entries()) {
6576
+ if (i < from || hidden(e.seq)) continue;
6577
+ if (e.type === 'turn/start') { out.since = Number.isFinite(e.time) ? e.time : undefined; call = undefined; delete out.said; }
6578
+ else if (e.type === 'todo/write' && Array.isArray(e.data?.todos)) {
6579
+ const todos = e.data.todos.filter((x) => typeof x?.content === 'string' && x.content.trim() !== '');
6580
+ const current = todos.find((x) => x.status === 'in_progress');
6581
+ out.todos = todos.length === 0 ? undefined
6582
+ : { done: todos.filter((x) => x.status === 'completed').length, total: todos.length, ...(current === undefined ? {} : { current: cutEnd(boardLine(current.content), BOARD_PROGRESS.detail) }) };
6583
+ } else if (e.type === 'tool/call' && isText(e.data?.name, 200) && !BOARD_PROGRESS_QUIET.has(e.data.name)) {
6584
+ call = { id: e.data.callId, name: e.data.name, detail: toolDetail(argsOf(e.data.arguments)), running: true };
6585
+ } else if (e.type === 'tool/result' && call !== undefined && e.data?.message?.source?.callId === call.id) call.running = false;
6586
+ else if (e.type === 'assistant/message') {
6587
+ const line = textBlocks(e.data?.message?.content).split('\n').map(boardLine).filter(Boolean).at(-1);
6588
+ if (line !== undefined) out.said = cutEnd(line, BOARD_PROGRESS.said);
6589
+ }
6568
6590
  }
6569
- return { sessionId, agent };
6591
+ if (call !== undefined) { const { id: _, ...tool } = call; out.tool = tool; }
6592
+ return JSON.parse(JSON.stringify(out));
6593
+ }
6594
+
6595
+ /** One short call for the progress summary of a chat's newest part; '' when it answers nothing. */
6596
+ export async function boardSummaryFor({ llm, route }, transcript, signal) {
6597
+ if (llm?.prepareCall === undefined || route === undefined) return '';
6598
+ const text = await boardCall({ llm, route }, BOARD_PROGRESS_SYSTEM, `The task's chat (its newest part):\n\n${transcript}\n\nThe progress note:`, { tokens: BOARD_PROGRESS.tokens, timeoutMs: BOARD_PROGRESS.timeoutMs }, signal);
6599
+ return cutEnd(boardLine(text.replace(/<think>[\s\S]*?<\/think>/g, '')), BOARD_PROGRESS.summary);
6570
6600
  }
6571
6601
 
6572
6602
  /** Task board (Host half): the switch route and, while it is on, the board's chat route. */
@@ -6577,8 +6607,13 @@ export function installBoard(ctx, home, dir = join(home, 'storages', 'dsh-compos
6577
6607
  let on = true;
6578
6608
  const ready = sw.read().then((v) => { on = v; }, () => {});
6579
6609
  let closed = false;
6580
- const tagging = new Set(); // background tag calls; unload stops them
6610
+ const tagging = new Set(); // background tag and summary calls; unload stops them
6581
6611
  ctx.effect?.(() => () => { closed = true; for (const c of tagging) c.abort(); }, 'dsh-composer-plus: task board');
6612
+ /** The chat's own model (its selection), else dsh's default. */
6613
+ const chatRoute = async (agent) => {
6614
+ const current = agent === undefined ? undefined : ctx.sessionController.agents?.selectionFor?.(agent)?.current;
6615
+ return isText(current?.provider) && isText(current?.model) ? { provider: current.provider, model: current.model } : routeOf(ctx, get);
6616
+ };
6582
6617
  /** The tags of a new chat, in the background: the chat's own model (its selection), else dsh's default. */
6583
6618
  const tag = async (sessionId, agent, prompt) => {
6584
6619
  const llm = get('llm');
@@ -6586,13 +6621,27 @@ export function installBoard(ctx, home, dir = join(home, 'storages', 'dsh-compos
6586
6621
  const mine = new AbortController();
6587
6622
  tagging.add(mine);
6588
6623
  try {
6589
- const current = ctx.sessionController.agents?.selectionFor?.(agent)?.current;
6590
- const route = isText(current?.provider) && isText(current?.model) ? { provider: current.provider, model: current.model } : await routeOf(ctx, get);
6591
- const tags = await boardTagsFor({ llm, route }, prompt, mine.signal);
6624
+ const tags = await boardTagsFor({ llm, route: await chatRoute(agent) }, prompt, mine.signal);
6592
6625
  if (tags.length > 0 && !closed) await store.change((chats) => { if (chats[sessionId] !== undefined) chats[sessionId] = { ...chats[sessionId], tags }; });
6593
6626
  } catch (error) { if (!closed) ctx.logger?.warn?.(`dsh-composer-plus: task board tags failed: ${String(error?.message ?? error).slice(0, 300)}`); }
6594
6627
  finally { tagging.delete(mine); }
6595
6628
  };
6629
+ /** A board chat that has no prompt yet gets its first message of its own as its prompt, and its tags. Once per chat: the
6630
+ * record changes one at a time and sets the prompt only while it is empty, so a second message, or the route and the
6631
+ * event at the same time, do not write it again or call the model again. */
6632
+ const name = async (sessionId, agent, prompt) => {
6633
+ if (prompt === undefined || closed || store.list()[sessionId]?.prompt !== '') return;
6634
+ try {
6635
+ let set = false;
6636
+ await store.change((chats) => { if (chats[sessionId]?.prompt === '') { chats[sessionId] = { ...chats[sessionId], prompt }; set = true; } });
6637
+ if (set) await tag(sessionId, agent, prompt);
6638
+ } catch (error) { ctx.logger?.warn?.(`dsh-composer-plus: task board record failed: ${String(error?.message ?? error).slice(0, 300)}`); }
6639
+ };
6640
+ ctx.on?.('session/event', (session, event) => {
6641
+ if (!on || closed || event?.type !== 'user/message') return;
6642
+ const from = Number.isSafeInteger(session.inheritedEventCount) && session.inheritedEventCount > 0 ? session.inheritedEventCount : 0;
6643
+ name(session.id, get('agents')?.get?.(session.id), boardPromptOf([event], from));
6644
+ });
6596
6645
  let switching = ready;
6597
6646
  ctx.connection.fetch.register({
6598
6647
  path: BOARD_PATH, methods: ['GET', 'POST'], requestBody: 'buffered',
@@ -6611,7 +6660,9 @@ export function installBoard(ctx, home, dir = join(home, 'storages', 'dsh-compos
6611
6660
  return json({ on });
6612
6661
  },
6613
6662
  });
6614
- /** GET `?rev=N`: `{rev, chats}` (`{rev, same}` when nothing changed since rev). POST `{project, prompt}`: start a chat. */
6663
+ /** GET `?rev=N`: `{rev, chats}` (`{rev, same}` when nothing changed since rev). POST `{sessionId}`: New task opened this
6664
+ * dsh chat; it goes on the board (Todo until its first message). A message that came before the POST is read from the
6665
+ * chat's log, so a fast first message still names the card. */
6615
6666
  ctx.connection.fetch.register({
6616
6667
  path: BOARD_CHATS_PATH, methods: ['GET', 'POST'], requestBody: 'buffered',
6617
6668
  fetch: async (request) => {
@@ -6626,14 +6677,70 @@ export function installBoard(ctx, home, dir = join(home, 'storages', 'dsh-compos
6626
6677
  }
6627
6678
  let body;
6628
6679
  try { body = await request.json(); } catch { return json({ error: 'bad request' }, 400); }
6629
- if (!plainObject(body) || !isText(body.project) || typeof body.prompt !== 'string' || body.prompt.length > BOARD_LIMITS.prompt + 1000) return json({ error: 'bad request' }, 400);
6630
- const { sessionId, agent } = await startBoardChat({ sessionController: ctx.sessionController, commands: get('commands'), registry: get('workspaceRegistry'), store }, body.project, body.prompt);
6631
- tag(sessionId, agent, body.prompt.trim());
6680
+ if (!plainObject(body) || Object.keys(body).join() !== 'sessionId' || !isText(body.sessionId, 128) || body.sessionId === '__proto__') return json({ error: 'bad request' }, 400);
6681
+ const { sessionId } = body;
6682
+ const found = await ctx.sessionController.resolveAgent(sessionId);
6683
+ if (found === undefined || 'error' in found) return json({ error: 'this is not a dsh chat' }, 404);
6684
+ if (store.list()[sessionId] === undefined) await store.change((chats) => { chats[sessionId] ??= { prompt: '', tags: [], at: Date.now() }; });
6685
+ const session = found.agent?.session;
6686
+ const from = Number.isSafeInteger(session?.inheritedEventCount) && session.inheritedEventCount > 0 ? session.inheritedEventCount : 0;
6687
+ await name(sessionId, found.agent, boardPromptOf(session?.snapshotEvents?.() ?? [], from));
6632
6688
  return json({ rev: store.rev(), sessionId });
6633
6689
  } catch (error) { return json({ error: String(error?.message ?? error) }, Number.isInteger(error?.status) ? error.status : 500); }
6634
6690
  },
6635
6691
  });
6636
- return { store, ready, tag, isOn: () => on };
6692
+ /** Progress on hover. The mode is a setting (`board-progress.json`, owner-only). In "ai" mode one summary call per chat
6693
+ * runs at a time, in the background: a new one only when the chat moved on and BOARD_PROGRESS.gapMs after the last one
6694
+ * started; the answer gives the newest summary that is ready (`pending` while a call runs). */
6695
+ const progress = settingsStore(join(dir, 'board-progress.json'), (v) => ({ mode: v?.mode === 'ai' ? 'ai' : 'fast' }));
6696
+ const summaries = new Map(); // sessionId → { key, at, text?, busy }
6697
+ const summaryOf = (sessionId, agent, events, from) => {
6698
+ const key = events.at(-1)?.seq;
6699
+ const kept = summaries.get(sessionId);
6700
+ const llm = get('llm');
6701
+ if (!closed && llm?.prepareCall !== undefined && kept?.busy !== true && kept?.key !== key && Date.now() - (kept?.at ?? 0) >= BOARD_PROGRESS.gapMs) {
6702
+ const entry = { key, at: Date.now(), text: kept?.text, busy: true };
6703
+ summaries.delete(sessionId); summaries.set(sessionId, entry);
6704
+ if (summaries.size > BOARD_PROGRESS.kept) summaries.delete(summaries.keys().next().value);
6705
+ const mine = new AbortController();
6706
+ tagging.add(mine);
6707
+ (async () => {
6708
+ try { const text = await boardSummaryFor({ llm, route: await chatRoute(agent) }, goalTranscript(events, from, BOARD_PROGRESS.input), mine.signal); if (text !== '') entry.text = text; }
6709
+ catch (error) { if (!closed) ctx.logger?.warn?.(`dsh-composer-plus: task board summary failed: ${String(error?.message ?? error).slice(0, 300)}`); }
6710
+ finally { entry.busy = false; tagging.delete(mine); }
6711
+ })();
6712
+ }
6713
+ const now = summaries.get(sessionId);
6714
+ return { ...(now?.text ? { summary: now.text } : {}), ...(now?.busy ? { pending: true } : {}) };
6715
+ };
6716
+ /** GET `?sessionId=`: `{mode, since?, todos?, tool?, said?, summary?, pending?}` of a chat that dsh has open (404
6717
+ * otherwise; 409 while the board is off). GET: `{mode}`. POST `{mode}`: "fast" or "ai" (Settings only). */
6718
+ ctx.connection.fetch.register({
6719
+ path: BOARD_PROGRESS_PATH, methods: ['GET', 'POST'], requestBody: 'buffered',
6720
+ fetch: async (request) => {
6721
+ await ready;
6722
+ try {
6723
+ if (request.method === 'POST') {
6724
+ let body;
6725
+ try { body = await request.json(); } catch { return json({ error: 'bad request' }, 400); }
6726
+ if (!plainObject(body) || Object.keys(body).join() !== 'mode' || !BOARD_PROGRESS_MODES.includes(body.mode)) return json({ error: 'bad request' }, 400);
6727
+ return json(await progress.change(() => ({ mode: body.mode })));
6728
+ }
6729
+ const { mode } = await progress.read();
6730
+ const sessionId = new URL(request.url).searchParams.get('sessionId');
6731
+ if (sessionId === null) return json({ mode });
6732
+ if (!on) return json({ error: 'Task board is off' }, 409);
6733
+ if (!isText(sessionId, 128)) return json({ error: 'bad request' }, 400);
6734
+ const agent = get('agents')?.get?.(sessionId);
6735
+ const session = agent?.session;
6736
+ if (typeof session?.snapshotEvents !== 'function') return json({ error: 'dsh has no open chat with this id' }, 404);
6737
+ const events = session.snapshotEvents();
6738
+ const from = Number.isSafeInteger(session.inheritedEventCount) && session.inheritedEventCount > 0 ? session.inheritedEventCount : 0;
6739
+ return json({ mode, ...boardProgressOf(events, from), ...(mode === 'ai' ? summaryOf(sessionId, agent, events, from) : {}) });
6740
+ } catch (error) { return json({ error: String(error?.message ?? error) }, 500); }
6741
+ },
6742
+ });
6743
+ return { store, ready, tag, name, isOn: () => on, progress };
6637
6744
  }
6638
6745
 
6639
6746
  /* Planned steps, after HarnessMM (arXiv 2610.32813, "guarded decision reuse"): the model plans its next commands with the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-composer-plus",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "description": "DeepSeek Harness helpers: 11 extra UI languages (Türkçe, Español, Français, Deutsch, 日本語…), Changes panel, swipe-card feature ideas, sidebar search in message text, turn rollback with git file restore, delete chats to the Trash, input history, Shift+Tab /plan, one-click Continue, resume running chats after a restart, queue drag-and-drop, deeplinks, Auto Review allow-for-chat, notifications, iPhone access with the Tether app, automatic updates",
5
5
  "icon": "./assets/icon.svg",
6
6
  "license": "MIT",
@@ -1,18 +1,18 @@
1
1
  {
2
- "version": "1.19.0",
2
+ "version": "1.20.0",
3
3
  "notes": {
4
- "en": "## New\n\n- **Task board** shows your dsh chats with their live state, as the sidebar does. A chat that waits for your approval, answer or plan review is in **Review** with an orange dot. A running chat is in **Doing**, and a finished chat is in **Done** (green until you open it). **New task** takes only a prompt: `Enter` starts a new chat in the project. dsh names the chat, and one short call to the chat's model writes up to 3 tags. Click a card to open its chat. On an orange card, click **Answer** to answer on the board: **Reject** or **Allow once**, **Approve** or **Request changes** for a plan, or the options and your own text for a question. **Hide finished** hides **Done** on this device. Tether has the same board in the chat title menu.\n- The What's new window shows the release notes in the language of **Application language**. The notes come with the package in 13 languages. A language without notes shows the English notes.\n\n## Changes\n\n- **Task board** no longer keeps its own tasks: the notebook cards, the Backlog column, drag and drop and the agent board tools are gone. The old `board.json` stays on disk, unread.\n- **Git branch on chat rows** shows the branch pill only in the **In one list** view. Under a project row, the chat is already below its folder, so the row shows no `main` or branch name. Tether does the same.\n- Typing in **Open memory** no longer lags when many projects are open. The **Collapse all projects** button used a page-wide style rule that made the browser compute the whole page again at each key press.",
5
- "zh": "## 新功能\n\n- **任务看板** 像侧边栏一样显示你的 dsh 对话及其实时状态。等待你审批、回答或审阅计划的对话位于 **审核**,并带有橙色圆点。正在运行的对话位于 **进行中**,已结束的对话位于 **已完成**(在你打开之前显示为绿色)。**新建任务** 只需要一段提示:按 `Enter` 会在该项目中开始一个新对话。dsh 为对话命名,再向该对话的模型发起一次简短调用,写入最多 3 个标签。点击卡片即可打开它的对话。在橙色卡片上,点击 **回答** 即可直接在看板上作答:**拒绝** 或 **允许一次**;计划可选 **同意执行** 或 **要求修改**;问题可选选项或输入你自己的文字。**隐藏已完成** 会在此设备上隐藏 **已完成**。Tether 在对话标题菜单中提供同样的看板。\n- 新功能窗口会以 **应用语言** 中的语言显示发布说明。这些说明随软件包提供 13 种语言。没有说明的语言会显示英文说明。\n\n## 变更\n\n- **任务看板** 不再保存自己的任务:笔记本式卡片、积压列、拖放和智能体看板工具都已移除。旧的 `board.json` 仍留在磁盘上,但不再读取。\n- **对话行显示 Git 分支** 只在 **单列表** 视图中显示分支标签。在项目行下,对话已经位于它的文件夹下方,因此该行不显示 `main` 或分支名称。Tether 也一样。\n- 打开很多项目时,在 **打开记忆** 中输入不再卡顿。**折叠全部项目** 按钮使用了一条作用于整个页面的样式规则,使浏览器在每次按键时都重新计算整个页面。",
6
- "tr": "## Yeni\n\n- **Görev panosu**, kenar çubuğu gibi dsh sohbetlerinizi canlı durumlarıyla gösterir. Onayınızı, yanıtınızı ya da plan incelemenizi bekleyen bir sohbet, turuncu bir noktayla **İncelemede** sütunundadır. Çalışan bir sohbet **Yapılıyor** sütununda, biten bir sohbet ise **Bitti** sütunundadır (siz açana kadar yeşildir). **Yeni görev** yalnızca bir istem alır: `Enter` projede yeni bir sohbet başlatır. Sohbetin adını dsh koyar; sohbetin modeline yapılan tek bir kısa çağrı en çok 3 etiket yazar. Bir kartın sohbetini açmak için karta tıklayın. Turuncu bir kartta panodan yanıt vermek için **Yanıtla** düğmesine tıklayın: **Reddet** ya da **Bir kez izin ver**; bir plan için **Onayla** ya da **Değişiklik iste**; bir soru için seçenekler ve kendi metniniz. **Bitenleri gizle**, bu cihazda **Bitti** sütununu gizler. Tether'da aynı pano sohbet başlığı menüsündedir.\n- Yenilikler penceresi, sürüm notlarını **Uygulama dili** ayarındaki dilde gösterir. Notlar pakette 13 dilde gelir. Notu olmayan bir dilde İngilizce notlar gösterilir.\n\n## Değişiklikler\n\n- **Görev panosu** artık kendi görevlerini tutmaz: defter kartları, Bekleyenler sütunu, sürükle bırak ve ajanların pano araçları kaldırıldı. Eski `board.json` diskte kalır ama okunmaz.\n- **Sohbet satırlarında Git dalı**, dal etiketini yalnızca **Tek listede** görünümünde gösterir. Bir proje satırının altında sohbet zaten kendi klasörünün altındadır; bu yüzden satır `main` ya da bir dal adı göstermez. Tether da aynısını yapar.\n- Çok sayıda proje açıkken **Hafızayı aç** içinde yazmak artık gecikmez. **Tüm projeleri daralt** düğmesi sayfa genelinde bir stil kuralı kullanıyordu. Bu kural, tarayıcının her tuşa basışta tüm sayfayı yeniden hesaplamasına yol açıyordu.",
7
- "es": "## Novedades\n\n- El **Tablero de tareas** muestra tus chats de dsh con su estado en vivo, como la barra lateral. Un chat que espera tu aprobación, tu respuesta o la revisión de un plan está en **En revisión** con un punto naranja. Un chat en ejecución está en **En curso**, y un chat terminado está en **Hecho** (en verde hasta que lo abras). **Nueva tarea** solo pide un prompt: `Enter` inicia un chat nuevo en el proyecto. dsh le pone nombre al chat, y una breve llamada al modelo del chat escribe hasta 3 etiquetas. Haz clic en una tarjeta para abrir su chat. En una tarjeta naranja, haz clic en **Responder** para responder en el tablero: **Rechazar** o **Permitir una vez**, **Aprobar** o **Solicitar cambios** para un plan, o las opciones y tu propio texto para una pregunta. **Ocultar terminados** oculta **Hecho** en este dispositivo. Tether tiene el mismo tablero en el menú del título del chat.\n- La ventana de novedades muestra las notas de la versión en el idioma de **Idioma de la aplicación**. Las notas vienen con el paquete en 13 idiomas. Un idioma sin notas muestra las notas en inglés.\n\n## Cambios\n\n- El **Tablero de tareas** ya no guarda sus propias tareas: se quitaron las tarjetas de tipo cuaderno, la columna En espera, arrastrar y soltar, y las herramientas del tablero para los agentes. El antiguo `board.json` se queda en el disco, sin leerse.\n- **Rama de Git en las filas de chat** muestra la etiqueta de la rama solo en la vista **En una sola lista**. Bajo una fila de proyecto, el chat ya está debajo de su carpeta, así que la fila no muestra `main` ni ningún nombre de rama. Tether hace lo mismo.\n- Escribir en **Abrir memoria** ya no va con retraso cuando hay muchos proyectos abiertos. El botón **Contraer todos los proyectos** usaba una regla de estilo para toda la página que hacía que el navegador volviera a calcular toda la página con cada pulsación de tecla.",
8
- "pt-BR": "## Novidades\n\n- O **Quadro de tarefas** mostra seus chats do dsh com o estado ao vivo, como a barra lateral. Um chat que espera sua aprovação, sua resposta ou a revisão de um plano fica em **Em revisão**, com um ponto laranja. Um chat em execução fica em **Fazendo**, e um chat terminado fica em **Concluído** (verde até você abri-lo). **Nova tarefa** pede só um prompt: `Enter` inicia um novo chat no projeto. O dsh dá nome ao chat, e uma chamada curta ao modelo do chat escreve até 3 etiquetas. Clique em um cartão para abrir o chat dele. Em um cartão laranja, clique em **Responder** para responder no quadro: **Rejeitar** ou **Permitir uma vez**, **Aprovar** ou **Solicitar alterações** para um plano, ou as opções e seu próprio texto para uma pergunta. **Ocultar concluídos** oculta **Concluído** neste dispositivo. O Tether tem o mesmo quadro no menu do título do chat.\n- A janela de novidades mostra as notas da versão no idioma de **Idioma do aplicativo**. As notas vêm com o pacote em 13 idiomas. Um idioma sem notas mostra as notas em inglês.\n\n## Alterações\n\n- O **Quadro de tarefas** não guarda mais as próprias tarefas: os cartões no estilo caderno, a coluna Em espera, arrastar e soltar e as ferramentas do quadro para os agentes foram removidos. O antigo `board.json` continua no disco, sem ser lido.\n- **Branch do Git nas linhas de chat** mostra a etiqueta do branch só na visualização **Em uma única lista**. Sob uma linha de projeto, o chat já fica abaixo da pasta dele, então a linha não mostra `main` nem nome de branch. O Tether faz o mesmo.\n- Digitar em **Abrir memória** não fica mais lento quando muitos projetos estão abertos. O botão **Recolher todos os projetos** usava uma regra de estilo para a página inteira, que fazia o navegador recalcular a página toda a cada tecla pressionada.",
9
- "fr": "## Nouveautés\n\n- Le **Tableau des tâches** affiche vos discussions dsh avec leur état en direct, comme la barre latérale. Une discussion qui attend votre approbation, votre réponse ou la revue d’un plan se trouve dans la colonne **En revue**, avec un point orange. Une discussion qui s’exécute se trouve dans **En cours**, et une discussion terminée dans **Terminé** (en vert jusqu’à ce que vous l’ouvriez). **Nouvelle tâche** ne demande qu’un prompt : `Enter` lance une nouvelle discussion dans le projet. dsh nomme la discussion, et un court appel au modèle de la discussion écrit jusqu’à 3 étiquettes. Cliquez sur une carte pour ouvrir sa discussion. Sur une carte orange, cliquez sur **Répondre** pour répondre depuis le tableau : **Refuser** ou **Autoriser une fois**, **Approuver** ou **Demander des modifications** pour un plan, ou les options et votre propre texte pour une question. **Masquer les terminées** masque la colonne **Terminé** sur cet appareil. Tether a le même tableau dans le menu du titre de la discussion.\n- La fenêtre des nouveautés affiche les notes de version dans la langue choisie dans **Langue de l’application**. Les notes sont fournies avec le paquet en 13 langues. Pour une langue sans notes, les notes en anglais s’affichent.\n\n## Modifications\n\n- Le **Tableau des tâches** ne conserve plus ses propres tâches : les cartes de type bloc-notes, la colonne « En attente », le glisser-déposer et les outils de tableau des agents ont disparu. L’ancien `board.json` reste sur le disque, mais n’est plus lu.\n- **Branche Git sur les lignes de discussion** affiche la pastille de branche seulement dans la vue **Dans une seule liste**. Sous une ligne de projet, la discussion est déjà sous son dossier : la ligne n’affiche donc ni `main` ni nom de branche. Tether fait de même.\n- La saisie dans **Ouvrir la mémoire** n’est plus lente quand de nombreux projets sont ouverts. Le bouton **Réduire tous les projets** utilisait une règle de style sur toute la page, qui obligeait le navigateur à recalculer toute la page à chaque frappe.",
10
- "de": "## Neu\n\n- Die **Aufgabentafel** zeigt deine dsh-Chats mit ihrem Live-Status, wie die Seitenleiste. Ein Chat, der auf deine Genehmigung, deine Antwort oder die Prüfung eines Plans wartet, steht mit einem orangen Punkt in der Spalte **In Prüfung**. Ein laufender Chat steht in **In Arbeit**, ein beendeter Chat in **Fertig** (grün, bis du ihn öffnest). **Neue Aufgabe** braucht nur einen Prompt: `Enter` startet einen neuen Chat im Projekt. dsh benennt den Chat, und ein kurzer Aufruf an das Modell des Chats schreibt bis zu 3 Schlagwörter. Klicke auf eine Karte, um ihren Chat zu öffnen. Klicke auf einer orangen Karte auf **Antworten**, um auf der Tafel zu antworten: **Ablehnen** oder **Einmal zulassen**, **Genehmigen** oder **Änderungen anfordern** für einen Plan, oder die Optionen und dein eigener Text für eine Frage. **Beendete ausblenden** blendet die Spalte **Fertig** auf diesem Gerät aus. Tether hat dieselbe Tafel im Menü des Chat-Titels.\n- Das Neuigkeiten-Fenster zeigt die Versionshinweise in der Sprache, die unter **App-Sprache** eingestellt ist. Die Hinweise sind in 13 Sprachen im Paket enthalten. Für eine Sprache ohne Hinweise erscheinen die englischen Hinweise.\n\n## Änderungen\n\n- Die **Aufgabentafel** speichert keine eigenen Aufgaben mehr: Die Notizkarten, die Spalte „Warteliste“, Drag-and-drop und die Tafel-Tools der Agenten fallen weg. Die alte `board.json` bleibt auf der Festplatte, wird aber nicht mehr gelesen.\n- **Git-Branch in Chatzeilen** zeigt das Branch-Badge nur in der Ansicht **In einer Liste**. Unter einer Projektzeile steht der Chat schon unter seinem Ordner, daher zeigt die Zeile weder `main` noch einen Branch-Namen. Tether macht es genauso.\n- Die Eingabe in **Gedächtnis öffnen** reagiert nicht mehr verzögert, wenn viele Projekte offen sind. Die Schaltfläche **Alle Projekte einklappen** nutzte eine Stilregel für die ganze Seite, durch die der Browser bei jedem Tastendruck die ganze Seite neu berechnete.",
11
- "ru": "## Новое\n\n- **Доска задач** показывает ваши чаты dsh с их состоянием в реальном времени, как боковая панель. Чат, который ждёт вашего подтверждения, ответа или проверки плана, находится в колонке **На проверке** с оранжевой точкой. Работающий чат находится в колонке **В работе**, а завершённый — в колонке **Готово** (зелёный, пока вы его не откроете). **Новая задача** принимает только промпт: `Enter` запускает новый чат в проекте. dsh задаёт название чата, а один короткий вызов модели чата пишет до 3 тегов. Нажмите на карточку, чтобы открыть её чат. На оранжевой карточке нажмите **Ответить**, чтобы ответить прямо на доске: **Отклонить** или **Разрешить один раз**, **Одобрить** или **Запросить изменения** для плана, или варианты ответа и ваш собственный текст для вопроса. **Скрыть завершённые** скрывает колонку **Готово** на этом устройстве. В Tether та же доска есть в меню заголовка чата.\n- Окно новинок показывает примечания к выпуску на языке, выбранном в **Язык приложения**. Примечания входят в пакет на 13 языках. Для языка без примечаний показываются примечания на английском.\n\n## Изменения\n\n- **Доска задач** больше не хранит собственные задачи: карточки-заметки, колонка «Бэклог», перетаскивание и инструменты доски для агентов удалены. Старый файл `board.json` остаётся на диске, но больше не читается.\n- **Ветка Git в строках чатов** показывает метку ветки только в представлении **Одним списком**. Под строкой проекта чат и так находится под своей папкой, поэтому строка не показывает ни `main`, ни имя ветки. Tether делает так же.\n- Ввод текста в **Открыть память** больше не тормозит, когда открыто много проектов. Кнопка **Свернуть все проекты** использовала правило стиля для всей страницы, из-за которого браузер пересчитывал всю страницу при каждом нажатии клавиши.",
12
- "ja": "## 新機能\n\n- **タスクボード** は、サイドバーと同じように、dsh のチャットをリアルタイムの状態とともに表示します。承認、回答、またはプランのレビューを待っているチャットは、オレンジ色の点付きで **レビュー** 列に入ります。実行中のチャットは **作業中** 列に、終了したチャットは **完了** 列に入ります(開くまでは緑色です)。**新しいタスク** に入力するのはプロンプトだけです。`Enter` でプロジェクトに新しいチャットを開始します。dsh がチャットに名前を付け、チャットのモデルへの短い呼び出し 1 回で最大 3 つのタグを付けます。カードをクリックすると、そのチャットが開きます。オレンジ色のカードで **回答** をクリックすると、ボード上で回答できます:**拒否** または **1回だけ許可**、プランには **承認** または **変更をリクエスト**、質問には選択肢と自分のテキスト。**完了を非表示** は、このデバイスで **完了** 列を非表示にします。Tether では、チャットのタイトルメニューに同じボードがあります。\n- 新機能ウィンドウには、**アプリケーションの言語** で選んだ言語でリリースノートが表示されます。リリースノートは 13 言語でパッケージに含まれています。リリースノートがない言語では、英語のリリースノートが表示されます。\n\n## 変更点\n\n- **タスクボード** は独自のタスクを保持しなくなりました。メモ形式のカード、「バックログ」列、ドラッグ&ドロップ、エージェント用のボードツールは削除されました。古い `board.json` はディスクに残りますが、読み込まれません。\n- **チャット行の Git ブランチ** は、**1つのリスト** 表示のときだけブランチのバッジを表示します。プロジェクト行の下では、チャットはすでにそのフォルダーの下にあるため、行に `main` やブランチ名は表示されません。Tether も同じです。\n- 多くのプロジェクトを開いているときでも、**メモリーを開く** での入力が遅れなくなりました。**すべてのプロジェクトを折りたたむ** ボタンがページ全体に効くスタイルルールを使っていたため、キーを押すたびにブラウザーがページ全体を再計算していました。",
13
- "ko": "## 새 기능\n\n- **작업 보드**는 사이드바처럼 dsh 채팅을 실시간 상태와 함께 보여 줍니다. 승인, 답변 또는 계획 검토를 기다리는 채팅은 주황색 점과 함께 **검토**에 있습니다. 실행 중인 채팅은 **진행 중**에, 끝난 채팅은 **완료**에 있습니다(열기 전까지 초록색). **새 작업**에는 프롬프트만 입력합니다. `Enter`를 누르면 프로젝트에서 새 채팅이 시작됩니다. dsh가 채팅 이름을 짓고, 채팅의 모델을 한 번 짧게 호출해 태그를 최대 3개 씁니다. 카드를 클릭하면 채팅이 열립니다. 주황색 카드에서 **답변**을 클릭하면 보드에서 바로 답할 수 있습니다. 승인 요청에는 **거부** 또는 **한 번 허용**, 계획에는 **승인** 또는 **변경 요청**, 질문에는 선택지와 직접 쓴 텍스트로 답합니다. **끝난 채팅 숨기기**는 이 기기에서 **완료**를 숨깁니다. Tether에서는 채팅 제목 메뉴에 같은 보드가 있습니다.\n- 새로운 기능 창은 **애플리케이션 언어**에서 고른 언어로 릴리스 노트를 보여 줍니다. 노트는 13개 언어로 패키지에 함께 들어 있습니다. 노트가 없는 언어에서는 영어 노트를 보여 줍니다.\n\n## 변경 사항\n\n- **작업 보드**는 더 이상 자체 작업을 보관하지 않습니다. 노트 카드, 백로그 열, 끌어서 놓기, 에이전트용 보드 도구가 없어졌습니다. 이전 `board.json`은 디스크에 그대로 남지만 읽지 않습니다.\n- **채팅 행의 Git 브랜치**는 **하나의 목록** 보기에서만 브랜치 배지를 표시합니다. 프로젝트 행 아래에서는 채팅이 이미 해당 폴더 아래에 있으므로, 행에 `main`이나 브랜치 이름이 표시되지 않습니다. Tether도 똑같이 동작합니다.\n- 열린 프로젝트가 많을 때 **메모리 열기**에서 입력이 더 이상 느려지지 않습니다. **모든 프로젝트 접기** 버튼이 페이지 전체에 적용되는 스타일 규칙을 사용해서, 키를 누를 때마다 브라우저가 페이지 전체를 다시 계산했습니다.",
14
- "id": "## Baru\n\n- **Papan tugas** menampilkan obrolan dsh Anda dengan status langsungnya, seperti bilah samping. Obrolan yang menunggu persetujuan, jawaban, atau tinjauan rencana Anda ada di **Ditinjau** dengan titik oranye. Obrolan yang berjalan ada di **Sedang dikerjakan**, dan obrolan yang sudah selesai ada di **Selesai** (hijau sampai Anda membukanya). **Tugas baru** hanya memerlukan prompt: `Enter` memulai obrolan baru di proyek. dsh memberi nama obrolan, dan satu panggilan singkat ke model obrolan menulis hingga 3 label. Klik kartu untuk membuka obrolannya. Di kartu oranye, klik **Jawab** untuk menjawab di papan: **Tolak** atau **Izinkan sekali**, **Setujui** atau **Minta perubahan** untuk rencana, atau pilihan dan teks Anda sendiri untuk pertanyaan. **Sembunyikan yang selesai** menyembunyikan **Selesai** di perangkat ini. Tether memiliki papan yang sama di menu judul obrolan.\n- Jendela Yang baru menampilkan catatan rilis dalam bahasa **Bahasa aplikasi**. Catatan ikut bersama paket dalam 13 bahasa. Bahasa tanpa catatan menampilkan catatan berbahasa Inggris.\n\n## Perubahan\n\n- **Papan tugas** tidak lagi menyimpan tugasnya sendiri: kartu catatan, kolom Tertunda, seret dan lepas, serta alat papan untuk agen sudah dihapus. `board.json` lama tetap ada di disk, tetapi tidak dibaca.\n- **Branch Git di baris obrolan** hanya menampilkan label branch di tampilan **Dalam satu daftar**. Di bawah baris proyek, obrolan sudah berada di bawah foldernya, sehingga baris itu tidak menampilkan `main` atau nama branch. Tether melakukan hal yang sama.\n- Mengetik di **Buka memori** tidak lagi tersendat saat banyak proyek terbuka. Tombol **Ciutkan semua proyek** memakai aturan gaya untuk seluruh halaman, sehingga browser menghitung ulang seluruh halaman pada setiap penekanan tombol.",
15
- "vi": "## Mới\n\n- **Bảng tác vụ** hiện các cuộc trò chuyện dsh của bạn với trạng thái trực tiếp, giống như thanh bên. Cuộc trò chuyện đang chờ bạn phê duyệt, trả lời hoặc xem xét kế hoạch nằm ở **Đang duyệt** với chấm cam. Cuộc trò chuyện đang chạy nằm ở **Đang làm**, và cuộc trò chuyện đã xong nằm ở **Xong** (màu xanh lá cho đến khi bạn mở). **Tác vụ mới** chỉ cần một prompt: `Enter` bắt đầu một cuộc trò chuyện mới trong dự án. dsh đặt tên cho cuộc trò chuyện, và một lần gọi ngắn đến mô hình của cuộc trò chuyện viết tối đa 3 nhãn. Nhấp vào thẻ để mở cuộc trò chuyện của nó. Trên thẻ màu cam, nhấp **Trả lời** để trả lời ngay trên bảng: **Từ chối** hoặc **Cho phép một lần**, **Phê duyệt** hoặc **Yêu cầu thay đổi** cho một kế hoạch, hoặc các lựa chọn và văn bản của riêng bạn cho một câu hỏi. **Ẩn mục đã xong** ẩn **Xong** trên thiết bị này. Tether có cùng bảng này trong menu tiêu đề cuộc trò chuyện.\n- Cửa sổ Có gì mới hiện ghi chú phát hành bằng ngôn ngữ trong **Ngôn ngữ ứng dụng**. Ghi chú đi kèm gói bằng 13 ngôn ngữ. Ngôn ngữ không có ghi chú sẽ hiện ghi chú tiếng Anh.\n\n## Thay đổi\n\n- **Bảng tác vụ** không còn lưu tác vụ riêng: thẻ ghi chép, cột Tồn đọng, tính năng kéo thả và các công cụ bảng cho tác tử đã bị gỡ bỏ. Tệp `board.json` cũ vẫn nằm trên ổ đĩa nhưng không được đọc.\n- **Branch Git trên dòng cuộc trò chuyện** chỉ hiện nhãn branch trong chế độ xem **Trong một danh sách**. Dưới một dòng dự án, cuộc trò chuyện đã nằm dưới thư mục của nó, nên dòng đó không hiện `main` hay tên branch. Tether cũng làm như vậy.\n- Gõ trong **Mở bộ nhớ** không còn bị chậm khi có nhiều dự án đang mở. Nút **Thu gọn tất cả dự án** dùng một quy tắc định kiểu cho cả trang, khiến trình duyệt tính toán lại toàn bộ trang mỗi lần nhấn phím.",
16
- "hi": "## नया\n\n- **टास्क बोर्ड** आपकी dsh चैट को उनकी लाइव स्थिति के साथ दिखाता है, जैसे साइडबार दिखाता है। जो चैट आपकी मंज़ूरी, जवाब या प्लान रिव्यू का इंतज़ार करती है, वह नारंगी बिंदु के साथ **समीक्षा** में है। चल रही चैट **प्रगति में** है, और खत्म हुई चैट **पूरा हुआ** में है (जब तक आप उसे न खोलें, तब तक हरी)। **नया टास्क** सिर्फ़ एक प्रॉम्प्ट लेता है: `Enter` प्रोजेक्ट में नई चैट शुरू करता है। dsh चैट को नाम देता है, और चैट के मॉडल को एक छोटी कॉल ज़्यादा से ज़्यादा 3 टैग लिखती है। चैट खोलने के लिए कार्ड पर क्लिक करें। नारंगी कार्ड पर, बोर्ड पर ही जवाब देने के लिए **जवाब दें** पर क्लिक करें: **अस्वीकार करें** या **एक बार अनुमति दें**, प्लान के लिए **मंज़ूर करें** या **बदलावों का अनुरोध करें**, या सवाल के लिए विकल्प और अपना टेक्स्ट। **खत्म हुई चैट छिपाएं** इस डिवाइस पर **पूरा हुआ** को छिपाता है। Tether में यही बोर्ड चैट के शीर्षक वाले मेन्यू में है।\n- नया क्या है विंडो रिलीज़ नोट्स को **ऐप्लिकेशन की भाषा** में चुनी गई भाषा में दिखाती है। नोट्स पैकेज के साथ 13 भाषाओं में आते हैं। जिस भाषा के नोट्स नहीं हैं, उसमें अंग्रेज़ी नोट्स दिखते हैं।\n\n## बदलाव\n\n- **टास्क बोर्ड** अब अपने अलग टास्क नहीं रखता: नोटबुक कार्ड, बैकलॉग कॉलम, ड्रैग-एंड-ड्रॉप और एजेंट के बोर्ड टूल हटा दिए गए हैं। पुरानी `board.json` फ़ाइल डिस्क पर बनी रहती है, पर उसे पढ़ा नहीं जाता।\n- **चैट पंक्तियों पर Git ब्रांच** ब्रांच लेबल सिर्फ़ **एक ही सूची में** वाले व्यू में दिखाता है। प्रोजेक्ट पंक्ति के नीचे चैट पहले से अपने फ़ोल्डर के नीचे होती है, इसलिए पंक्ति में `main` या कोई ब्रांच नाम नहीं दिखता। Tether भी ऐसा ही करता है।\n- बहुत सारे प्रोजेक्ट खुले होने पर **मेमोरी खोलें** में टाइप करना अब धीमा नहीं पड़ता। **सभी प्रोजेक्ट छोटे करें** बटन पूरे पेज पर लागू होने वाला एक स्टाइल नियम इस्तेमाल करता था, जिसकी वजह से हर बार कुंजी दबाने पर ब्राउज़र पूरे पेज की गणना फिर से करता था।"
4
+ "en": "## New\n\n- **Task board**: point at a card in **Doing** (or move the keyboard focus to it) to see its progress: how long it runs, the step of its todo list, the tool that runs now and the agent's last line. In Settings → Composer Plus → Task board, **Progress on hover** picks **Fast (no AI)** (the default: it reads the chat only) or **AI summary** (one short call to the chat's model also writes one or two sentences). Tether shows one progress line under each running card.\n- **New task** now opens dsh's own new chat in the project, so the first message has dsh's composer (files, commands, model menu). After you send it, the board opens again. Until then the chat waits in **Todo**, and **Todo** shows only then.\n\n## Changes\n\n- **Task board** has no project colors: the only colors are the states (orange waits for you, blue runs, green is finished).\n- A project with a chat that waits for you has an orange dot in the board's project list. Tether opens the board on **Review** when a chat waits for you.\n- Only the project list scrolls: the title, **Search projects** and **Active in the last 2 hours** stay in place. **Search projects** has the height of a project row.\n- In a narrow board, the header stays on one line: **Hide finished** shows only its icon.",
5
+ "zh": "## 新功能\n\n- **任务看板**:将指针指向 **进行中** 里的卡片(或把键盘焦点移到卡片上),即可看到它的进度:运行了多久、待办列表中的当前步骤、正在运行的工具,以及智能体的最后一行。在设置 → Composer Plus → 任务看板中,**悬停显示进度** 可选择 **快速(不用 AI)**(默认:只读取对话)或 **AI 摘要**(再向对话的模型发送一次简短调用,额外写出一两句话)。Tether 会在每张运行中的卡片下方显示一行进度。\n- **新建任务** 现在会在项目中打开 dsh 自己的新对话,因此写第一条消息时可以使用 dsh 的输入框(文件、命令、模型菜单)。发送后,看板会再次打开。在此之前,该对话在 **待办** 中等待,**待办** 也只在这时显示。\n\n## 变更\n\n- **任务看板** 不使用项目颜色:颜色只表示状态(橙色表示在等你,蓝色表示正在运行,绿色表示已结束)。\n- 有对话在等你的项目,在看板的项目列表中带橙色圆点。有对话在等你时,Tether 会在 **审核** 列打开看板。\n- 只有项目列表会滚动:标题、**搜索项目** 和 **最近 2 小时内活跃** 固定不动。**搜索项目** 与项目行同高。\n- 看板较窄时,顶栏仍保持在一行内:**隐藏已完成** 只显示图标。",
6
+ "tr": "## Yeni\n\n- **Görev panosu**: ilerlemesini görmek için **Yapılıyor** sütunundaki bir kartın üzerine gelin (ya da klavye odağını karta taşıyın): sohbetin ne kadar süredir çalıştığı, yapılacaklar listesindeki adımı, şu an çalışan araç ve ajanın son satırı. Ayarlar → Composer Plus → Görev panosu bölümünde **Üzerine gelince ilerleme** ile **Hızlı (AI’sız)** (varsayılan: yalnız sohbeti okur) ya da **AI özeti** (sohbetin modeline yapılan kısa bir çağrı bir iki cümle de yazar) seçilir. Tether, çalışan her kartın altında bir ilerleme satırı gösterir.\n- **Yeni görev** artık projede dsh’nin kendi yeni sohbetini açar; böylece ilk mesajı dsh’nin mesaj alanında (dosyalar, komutlar, model menüsü) yazarsınız. Mesajı gönderdikten sonra pano yeniden açılır. O zamana kadar sohbet **Yapılacak** sütununda bekler ve **Yapılacak** yalnız bu sırada görünür.\n\n## Değişiklikler\n\n- **Görev panosu** proje renklerini kullanmaz: renkler yalnız durumları gösterir (turuncu: sizi bekliyor, mavi: çalışıyor, yeşil: bitti).\n- Sizi bekleyen bir sohbeti olan projenin yanında, panonun proje listesinde turuncu bir nokta görünür. Bir sohbet sizi beklerken Tether panoyu **İncelemede** sütununda açar.\n- Yalnız proje listesi kayar: başlık, **Projelerde ara** ve **Son 2 saatte aktif olanlar** yerinde kalır. **Projelerde ara**, bir proje satırı yüksekliğindedir.\n- Pano darken üst çubuk tek satırda kalır: **Bitenleri gizle** yalnız simgesini gösterir.",
7
+ "es": "## Novedades\n\n- **Tablero de tareas**: apunta a una tarjeta de **En curso** (o mueve el foco del teclado a ella) para ver su progreso: cuánto tiempo lleva en ejecución, el paso de su lista de tareas pendientes, la herramienta que se ejecuta ahora y la última línea del agente. En Configuración → Composer Plus → Tablero de tareas, **Progreso al pasar el cursor** elige **Rápido (sin IA)** (el predeterminado: solo lee el chat) o **Resumen con IA** (una llamada corta al modelo del chat también escribe una o dos frases). Tether muestra una línea de progreso debajo de cada tarjeta en ejecución.\n- **Nueva tarea** ahora abre el propio chat nuevo de dsh en el proyecto, así que el primer mensaje tiene el editor de dsh (archivos, comandos, menú de modelos). Después de enviarlo, el tablero se abre de nuevo. Hasta entonces, el chat espera en **Por hacer**, y **Por hacer** solo se muestra entonces.\n\n## Cambios\n\n- El **Tablero de tareas** no tiene colores de proyecto: los únicos colores son los de los estados (naranja: te espera; azul: en ejecución; verde: terminado).\n- Un proyecto con un chat que te espera tiene un punto naranja en la lista de proyectos del tablero. Tether abre el tablero en **En revisión** cuando un chat te espera.\n- Solo se desplaza la lista de proyectos: el título, **Buscar proyectos** y **Activos en las últimas 2 horas** se quedan en su sitio. **Buscar proyectos** tiene la altura de una fila de proyecto.\n- En un tablero estrecho, la cabecera se queda en una sola línea: **Ocultar terminados** solo muestra su icono.",
8
+ "pt-BR": "## Novidades\n\n- **Quadro de tarefas**: aponte para um cartão em **Fazendo** (ou mova o foco do teclado até ele) para ver o progresso: há quanto tempo ele está em execução, a etapa da sua lista de tarefas pendentes, a ferramenta que está em execução agora e a última linha do agente. Em Configurações → Composer Plus → Quadro de tarefas, **Progresso ao passar o cursor** escolhe **Rápido (sem IA)** (o padrão: só lê o chat) ou **Resumo com IA** (uma chamada curta ao modelo do chat também escreve uma ou duas frases). O Tether mostra uma linha de progresso abaixo de cada cartão em execução.\n- **Nova tarefa** agora abre o próprio novo chat do dsh no projeto, então a primeira mensagem tem o editor de mensagens do dsh (arquivos, comandos, menu de modelos). Depois que você a envia, o quadro abre de novo. Até lá, o chat espera em **A fazer**, e **A fazer** só aparece nesse momento.\n\n## Alterações\n\n- O **Quadro de tarefas** não tem cores de projeto: as únicas cores são as dos estados (laranja: espera por você; azul: em execução; verde: terminado).\n- Um projeto com um chat que espera por você tem um ponto laranja na lista de projetos do quadro. O Tether abre o quadro em **Em revisão** quando um chat espera por você.\n- Só a lista de projetos rola: o título, **Buscar projetos** e **Ativos nas últimas 2 horas** ficam no lugar. **Buscar projetos** tem a altura de uma linha de projeto.\n- Em um quadro estreito, o cabeçalho fica em uma só linha: **Ocultar concluídos** mostra só o ícone.",
9
+ "fr": "## Nouveautés\n\n- **Tableau des tâches** : pointez une carte dans **En cours** (ou placez-y le focus du clavier) pour voir sa progression : depuis combien de temps la discussion s’exécute, l’étape de sa liste de tâches, l’outil qui s’exécute en ce moment et la dernière ligne de l’agent. Dans Réglages → Composer Plus → Tableau des tâches, **Progression au survol** choisit **Rapide (sans IA)** (par défaut : lit seulement la discussion) ou **Résumé par IA** (un court appel au modèle de la discussion écrit aussi une ou deux phrases). Tether affiche une ligne de progression sous chaque carte en cours d’exécution.\n- **Nouvelle tâche** ouvre maintenant la nouvelle discussion de dsh lui-même dans le projet : le premier message dispose donc de la zone de saisie de dsh (fichiers, commandes, menu des modèles). Après l’envoi, le tableau s’ouvre de nouveau. D’ici là, la discussion attend dans **À faire**, et **À faire** ne s’affiche qu’à ce moment-là.\n\n## Modifications\n\n- Le **Tableau des tâches** n’a pas de couleurs de projet : les seules couleurs sont celles des états (orange : vous attend, bleu : s’exécute, vert : terminé).\n- Un projet dont une discussion vous attend a un point orange dans la liste des projets du tableau. Tether ouvre le tableau sur **En revue** quand une discussion vous attend.\n- Seule la liste des projets défile : le titre, **Rechercher des projets** et **Actifs au cours des 2 dernières heures** restent en place. **Rechercher des projets** a la hauteur d’une ligne de projet.\n- Dans un tableau étroit, l’en-tête reste sur une seule ligne : **Masquer les terminées** n’affiche que son icône.",
10
+ "de": "## Neu\n\n- **Aufgabentafel**: Zeige auf eine Karte in **In Arbeit** (oder setze den Tastaturfokus darauf), um ihren Fortschritt zu sehen: wie lange der Chat läuft, den Schritt seiner To-do-Liste, das Tool, das gerade läuft, und die letzte Zeile des Agenten. Unter Einstellungen → Composer Plus → Aufgabentafel wählt **Fortschritt beim Darüberfahren** zwischen **Schnell (ohne KI)** (Standard: liest nur den Chat) und **KI-Zusammenfassung** (ein kurzer Aufruf an das Modell des Chats schreibt zusätzlich ein oder zwei Sätze). Tether zeigt unter jeder laufenden Karte eine Fortschrittszeile.\n- **Neue Aufgabe** öffnet jetzt den eigenen neuen Chat von dsh im Projekt, sodass die erste Nachricht das Eingabefeld von dsh hat (Dateien, Befehle, Modellmenü). Nachdem du sie gesendet hast, öffnet sich die Tafel wieder. Bis dahin wartet der Chat in **Zu erledigen**, und **Zu erledigen** erscheint nur dann.\n\n## Änderungen\n\n- Die **Aufgabentafel** hat keine Projektfarben: Farben gibt es nur für die Zustände (Orange: wartet auf dich, Blau: läuft, Grün: fertig).\n- Ein Projekt mit einem Chat, der auf dich wartet, hat in der Projektliste der Tafel einen orangen Punkt. Tether öffnet die Tafel auf **In Prüfung**, wenn ein Chat auf dich wartet.\n- Nur die Projektliste scrollt: Der Titel, **Projekte durchsuchen** und **In den letzten 2 Stunden aktiv** bleiben an ihrem Platz. **Projekte durchsuchen** hat die Höhe einer Projektzeile.\n- In einer schmalen Tafel bleibt die Kopfleiste einzeilig: **Beendete ausblenden** zeigt nur sein Symbol.",
11
+ "ru": "## Новое\n\n- **Доска задач**: наведите указатель на карточку в **В работе** (или переведите на неё фокус клавиатуры), чтобы увидеть ход работы: сколько времени работает чат, шаг его списка задач, инструмент, который работает сейчас, и последнюю строку агента. В разделе Настройки → Composer Plus → Доска задач параметр **Ход работы при наведении** выбирает **Быстро (без ИИ)** (по умолчанию: только читает чат) или **Сводка ИИ** (один короткий вызов модели чата дополнительно пишет одно-два предложения). Tether показывает одну строку хода работы под каждой работающей карточкой.\n- **Новая задача** теперь открывает собственный новый чат dsh в проекте, поэтому для первого сообщения доступно поле ввода dsh (файлы, команды, меню моделей). После отправки доска снова открывается. До этого чат ждёт в **К выполнению**, и **К выполнению** показывается только в это время.\n\n## Изменения\n\n- **Доска задач** не использует цвета проектов: цвета есть только у состояний (оранжевый — ждёт вас, синий — работает, зелёный — завершён).\n- У проекта с чатом, который ждёт вас, в списке проектов доски есть оранжевая точка. Когда чат ждёт вас, Tether открывает доску на столбце **На проверке**.\n- Прокручивается только список проектов: заголовок, **Поиск проектов** и **Активные за последние 2 часа** остаются на месте. Поле **Поиск проектов** по высоте равно строке проекта.\n- На узкой доске шапка остаётся в одну строку: кнопка **Скрыть завершённые** показывает только свой значок.",
12
+ "ja": "## 新機能\n\n- **タスクボード**:**作業中** のカードにポインターを合わせる(またはキーボードのフォーカスを移す)と、その進行状況が表示されます。実行時間、ToDo リストのステップ、いま実行中のツール、エージェントの最後の行です。設定 → Composer Plus → タスクボード の **ホバーで進行状況を表示** で、**高速(AI なし)**(既定:チャットを読むだけです)か **AI の要約**(チャットのモデルを短く 1 回呼び出し、1〜2 文も書きます)を選びます。Tether は、実行中の各カードの下に進行状況を 1 行表示します。\n- **新しいタスク** は、プロジェクトで dsh 自身の新しいチャットを開くようになりました。そのため、最初のメッセージで dsh の入力欄(ファイル、コマンド、モデルメニュー)を使えます。送信すると、ボードがまた開きます。それまで、そのチャットは **未着手** で待ち、**未着手** はこのときだけ表示されます。\n\n## 変更点\n\n- **タスクボード** ではプロジェクトの色を使いません。色は状態だけを示します(オレンジはあなたの対応待ち、青は実行中、緑は終了)。\n- あなたを待っているチャットがあるプロジェクトには、ボードのプロジェクト一覧でオレンジの点が付きます。あなたを待っているチャットがあると、Tether はボードを **レビュー** の列で開きます。\n- スクロールするのはプロジェクト一覧だけです。タイトル、**プロジェクトを検索**、**過去 2 時間以内にアクティブ** は動きません。**プロジェクトを検索** の高さはプロジェクトの行と同じです。\n- 幅の狭いボードでも、ヘッダーは 1 行のままです。**完了を非表示** はアイコンだけになります。",
13
+ "ko": "## 새 기능\n\n- **작업 보드**: **진행 중**의 카드를 가리키면(또는 키보드 포커스를 옮기면) 진행 상황을 볼 수 있습니다. 실행된 시간, 할 일 목록의 단계, 지금 실행 중인 도구, 에이전트의 마지막 줄이 표시됩니다. 설정 → Composer Plus → 작업 보드의 **마우스를 올리면 진행 상황 표시**에서 **빠름(AI 없음)**(기본값: 채팅만 읽음) 또는 **AI 요약**(채팅의 모델을 한 번 짧게 호출해 한두 문장도 씀)을 고릅니다. Tether는 실행 중인 각 카드 아래에 진행 상황을 한 줄 표시합니다.\n- 이제 **새 작업**은 프로젝트에서 dsh 자체의 새 채팅을 엽니다. 그래서 첫 메시지에 dsh의 입력란(파일, 명령, 모델 메뉴)을 쓸 수 있습니다. 메시지를 보내면 보드가 다시 열립니다. 그때까지 채팅은 **할 일**에서 기다리며, **할 일**은 이때만 표시됩니다.\n\n## 변경 사항\n\n- **작업 보드**에는 프로젝트 색이 없습니다. 색은 상태만 나타냅니다(주황색은 응답 대기, 파란색은 실행 중, 초록색은 끝남).\n- 응답을 기다리는 채팅이 있는 프로젝트에는 보드의 프로젝트 목록에서 주황색 점이 붙습니다. 응답을 기다리는 채팅이 있으면 Tether는 보드를 **검토** 열에서 엽니다.\n- 프로젝트 목록만 스크롤됩니다. 제목, **프로젝트 검색**, **최근 2시간 내 활성**은 제자리에 있습니다. **프로젝트 검색**의 높이는 프로젝트 행과 같습니다.\n- 보드가 좁아도 헤더는 한 줄로 유지됩니다. **끝난 채팅 숨기기**는 아이콘만 표시합니다.",
14
+ "id": "## Baru\n\n- **Papan tugas**: arahkan penunjuk ke kartu di **Sedang dikerjakan** (atau pindahkan fokus keyboard ke kartu itu) untuk melihat progresnya: berapa lama obrolan berjalan, langkah dari daftar tugasnya, alat yang sedang berjalan, dan baris terakhir agen. Di Pengaturan → Composer Plus → Papan tugas, **Progres saat kursor di atas** memilih **Cepat (tanpa AI)** (default: hanya membaca obrolan) atau **Ringkasan AI** (satu panggilan singkat ke model obrolan juga menulis satu atau dua kalimat). Tether menampilkan satu baris progres di bawah setiap kartu yang berjalan.\n- **Tugas baru** kini membuka obrolan baru milik dsh sendiri di proyek, jadi pesan pertama memakai kotak pesan dsh (file, perintah, menu model). Setelah Anda mengirimnya, papan terbuka lagi. Sampai saat itu, obrolan menunggu di **Akan dikerjakan**, dan **Akan dikerjakan** hanya muncul saat itu.\n\n## Perubahan\n\n- **Papan tugas** tidak memakai warna proyek: satu-satunya warna adalah warna status (oranye menunggu Anda, biru berjalan, hijau selesai).\n- Proyek yang punya obrolan yang menunggu Anda memiliki titik oranye di daftar proyek papan. Tether membuka papan di **Ditinjau** saat ada obrolan yang menunggu Anda.\n- Hanya daftar proyek yang bergulir: judul, **Cari proyek**, dan **Aktif dalam 2 jam terakhir** tetap di tempatnya. **Cari proyek** setinggi satu baris proyek.\n- Di papan yang sempit, header tetap dalam satu baris: **Sembunyikan yang selesai** hanya menampilkan ikonnya.",
15
+ "vi": "## Mới\n\n- **Bảng tác vụ**: trỏ vào một thẻ ở **Đang làm** (hoặc chuyển tiêu điểm bàn phím đến thẻ đó) để xem tiến độ: cuộc trò chuyện đã chạy bao lâu, bước trong danh sách việc cần làm, công cụ đang chạy và dòng cuối của tác tử. Trong Cài đặt → Composer Plus → Bảng tác vụ, **Tiến độ khi di chuột** chọn **Nhanh (không dùng AI)** (mặc định: chỉ đọc cuộc trò chuyện) hoặc **Tóm tắt bằng AI** (một lệnh gọi ngắn tới mô hình của cuộc trò chuyện viết thêm một hoặc hai câu). Tether hiện một dòng tiến độ dưới mỗi thẻ đang chạy.\n- **Tác vụ mới** giờ mở cuộc trò chuyện mới của chính dsh trong dự án, nên tin nhắn đầu tiên có khung soạn thảo của dsh (tệp, lệnh, menu mô hình). Sau khi bạn gửi, bảng mở lại. Trước đó, cuộc trò chuyện chờ ở **Cần làm**, và **Cần làm** chỉ hiện vào lúc đó.\n\n## Thay đổi\n\n- **Bảng tác vụ** không dùng màu theo dự án: màu chỉ dành cho trạng thái (cam là đang chờ bạn, xanh dương là đang chạy, xanh lá là đã xong).\n- Dự án có cuộc trò chuyện đang chờ bạn có một chấm cam trong danh sách dự án của bảng. Tether mở bảng ở **Đang duyệt** khi có cuộc trò chuyện đang chờ bạn.\n- Chỉ danh sách dự án cuộn: tiêu đề, **Tìm dự án** và **Hoạt động trong 2 giờ qua** giữ nguyên vị trí. **Tìm dự án** cao bằng một hàng dự án.\n- Khi bảng hẹp, phần đầu bảng vẫn nằm trên một dòng: **Ẩn mục đã xong** chỉ hiện biểu tượng.",
16
+ "hi": "## नया\n\n- **टास्क बोर्ड**: **प्रगति में** के किसी कार्ड पर पॉइंटर रखें (या कीबोर्ड फ़ोकस उस पर ले जाएं), तो उसकी प्रगति दिखती है: चैट कितनी देर से चल रही है, उसकी करने वाले कामों की सूची का चरण, अभी चल रहा टूल और एजेंट की आखिरी लाइन। सेटिंग → Composer Plus → टास्क बोर्ड में **होवर पर प्रगति** से **तेज़ (AI के बिना)** (डिफ़ॉल्ट: सिर्फ़ चैट पढ़ता है) या **AI सारांश** (चैट के मॉडल को एक छोटी कॉल एक या दो वाक्य भी लिखती है) चुनें। Tether हर चल रहे कार्ड के नीचे प्रगति की एक लाइन दिखाता है।\n- **नया टास्क** अब प्रोजेक्ट में dsh की अपनी नई चैट खोलता है, इसलिए पहले संदेश के लिए dsh का कंपोज़र (फ़ाइलें, कमांड, मॉडल मेन्यू) मिलता है। संदेश भेजने के बाद बोर्ड फिर से खुल जाता है। तब तक चैट **करना है** में इंतज़ार करती है, और **करना है** सिर्फ़ तभी दिखता है।\n\n## बदलाव\n\n- **टास्क बोर्ड** में प्रोजेक्ट के रंग नहीं हैं: रंग सिर्फ़ स्थितियों के हैं (नारंगी: आपका इंतज़ार, नीला: चल रहा है, हरा: खत्म)।\n- जिस प्रोजेक्ट में कोई चैट आपका इंतज़ार कर रही है, बोर्ड की प्रोजेक्ट सूची में उसके आगे नारंगी बिंदु दिखता है। जब कोई चैट आपका इंतज़ार कर रही हो, तो Tether बोर्ड को **समीक्षा** पर खोलता है।\n- सिर्फ़ प्रोजेक्ट सूची स्क्रॉल होती है: शीर्षक, **प्रोजेक्ट खोजें** और **पिछले 2 घंटों में सक्रिय** अपनी जगह पर रहते हैं। **प्रोजेक्ट खोजें** की ऊंचाई एक प्रोजेक्ट पंक्ति जितनी है।\n- संकरे बोर्ड में हेडर एक ही लाइन में रहता है: **खत्म हुई चैट छिपाएं** सिर्फ़ अपना आइकन दिखाता है।"
17
17
  }
18
18
  }
@@ -48,7 +48,7 @@ export const PLUS = new Map([
48
48
  ['rollback', ['GET', 'POST']], ['delete-chat', ['POST']], ['ideas', ['POST']], ['later', ['GET', 'POST']], ['idea-history', ['GET', 'POST']],
49
49
  ['queue-move', ['POST']], ['queue-edit', ['POST']], ['branches', ['GET']], ['predict', ['POST']], ['complete', ['POST']], ['improve', ['POST']], ['unread', ['GET', 'POST']],
50
50
  ['worktrees', ['GET', 'POST']], ['new-session-workspace', ['POST']], ['memory-edit', ['GET', 'POST']], ['handoff', ['POST']], ['context', ['GET']],
51
- ['board-chats', ['GET', 'POST']],
51
+ ['board-chats', ['GET', 'POST']], ['board-progress', ['GET']],
52
52
  ])
53
53
  /** Allow for this chat: an Auto Review ask (`approval/request` with this reason) for a tool that the chat granted. */
54
54
  export const AUTO_REVIEW_ASK = 'Auto review denied tool '