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/README.de.md +8 -8
- package/README.es.md +8 -8
- package/README.fr.md +8 -8
- package/README.hi.md +8 -8
- package/README.id.md +8 -8
- package/README.ja.md +8 -8
- package/README.ko.md +8 -8
- package/README.md +8 -8
- package/README.pt-BR.md +8 -8
- package/README.ru.md +8 -8
- package/README.tr.md +8 -8
- package/README.vi.md +8 -8
- package/README.zh.md +8 -8
- package/assets/features/taskBoard.webp +0 -0
- package/client.js +351 -131
- package/index.js +165 -58
- package/package.json +1 -1
- package/release-notes.json +14 -14
- package/tether/gateway/server.mjs +1 -1
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
|
-
*
|
|
6440
|
-
*
|
|
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
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
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`
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
const timeout = AbortSignal.timeout(
|
|
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(
|
|
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
|
|
6536
|
+
let out = '';
|
|
6533
6537
|
let finish;
|
|
6534
|
-
for await (const chunk of call.stream({ ...call.config, system
|
|
6535
|
-
|
|
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
|
|
6541
|
-
}
|
|
6542
|
-
|
|
6543
|
-
/**
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
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
|
-
|
|
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
|
|
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 `{
|
|
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) ||
|
|
6630
|
-
const { sessionId
|
|
6631
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|
package/release-notes.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.20.0",
|
|
3
3
|
"notes": {
|
|
4
|
-
"en": "## New\n\n- **Task board
|
|
5
|
-
"zh": "## 新功能\n\n-
|
|
6
|
-
"tr": "## Yeni\n\n- **Görev panosu
|
|
7
|
-
"es": "## Novedades\n\n-
|
|
8
|
-
"pt-BR": "## Novidades\n\n-
|
|
9
|
-
"fr": "## Nouveautés\n\n-
|
|
10
|
-
"de": "## Neu\n\n-
|
|
11
|
-
"ru": "## Новое\n\n- **Доска
|
|
12
|
-
"ja": "## 新機能\n\n-
|
|
13
|
-
"ko": "## 새 기능\n\n- **작업
|
|
14
|
-
"id": "## Baru\n\n- **Papan tugas
|
|
15
|
-
"vi": "## Mới\n\n- **Bảng tác v
|
|
16
|
-
"hi": "## नया\n\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 '
|