dsh-composer-plus 1.14.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +6 -6
- package/README.es.md +6 -6
- package/README.fr.md +6 -6
- package/README.hi.md +6 -6
- package/README.id.md +6 -6
- package/README.ja.md +6 -6
- package/README.ko.md +6 -6
- package/README.md +6 -6
- package/README.pt-BR.md +6 -6
- package/README.ru.md +6 -6
- package/README.tr.md +6 -6
- package/README.vi.md +6 -6
- package/README.zh.md +6 -6
- package/client.js +74 -21
- package/index.js +35 -7
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -3672,8 +3672,10 @@ async function excludeWorktrees(subprocess, { root, common }) {
|
|
|
3672
3672
|
const worktreeStarts = new Set(); // main roots with a "New branch" start running now
|
|
3673
3673
|
/** "New branch": a new branch and worktree `<main root>/.worktrees/<name>` from the chat's HEAD, the included ignored files
|
|
3674
3674
|
* copied, the worktree registered as a dsh workspace, and a new chat there (the source chat's preset and model) that gets
|
|
3675
|
-
* `text
|
|
3676
|
-
|
|
3675
|
+
* `text` and `attachments` (dsh's prompt parts: `{type:'image', mediaType, data}` as is; `{type:'file', receiptId}` is
|
|
3676
|
+
* an upload of the source chat, which is uploaded again to the new chat). One start per repository at a time. A failure
|
|
3677
|
+
* after the worktree exists keeps it on disk and names its path. */
|
|
3678
|
+
export async function startOnBranch({ subprocess, sessionController, sessionQuery, registry, llm, route, agents, fileUploads, store }, sessionId, text, signal, attachments = []) {
|
|
3677
3679
|
const observation = await sessionQuery.observeSession(sessionId, { signal, projectionMode: 'all' });
|
|
3678
3680
|
let cwd, origin, preset, model;
|
|
3679
3681
|
try {
|
|
@@ -3689,11 +3691,20 @@ export async function startOnBranch({ subprocess, sessionController, sessionQuer
|
|
|
3689
3691
|
if (repo === undefined) throw refused('this chat\'s folder is not in a git repository');
|
|
3690
3692
|
if (repo.root === undefined) throw refused('a bare repository or a submodule has no main folder for worktrees');
|
|
3691
3693
|
if (!repo.hasCommit) throw refused('this repository has no commit yet: make the first commit, then start a branch');
|
|
3694
|
+
// The files are found before git starts: a file that is gone makes no worktree.
|
|
3695
|
+
const source = attachments.some((a) => a.type === 'file') ? agents?.get?.(sessionId) : undefined;
|
|
3696
|
+
const files = attachments.filter((a) => a.type === 'file').map((a) => {
|
|
3697
|
+
if (fileUploads?.uploadStream === undefined || store?.readFileStream === undefined) throw refused('this dsh cannot give files to a new chat');
|
|
3698
|
+
const ref = source === undefined ? undefined : fileUploads.resolve?.(source, a.receiptId);
|
|
3699
|
+
if (ref === undefined) throw refused('a file of this message is not uploaded any more: add it again');
|
|
3700
|
+
return ref;
|
|
3701
|
+
});
|
|
3692
3702
|
if (worktreeStarts.has(repo.root)) throw refused('a new branch of this repository is already starting');
|
|
3693
3703
|
worktreeStarts.add(repo.root);
|
|
3694
3704
|
try {
|
|
3695
3705
|
// Only the naming stops with the request: once git starts, the steps run to the end (no half-made worktree or chat).
|
|
3696
|
-
|
|
3706
|
+
// A message of files only gets the dated name.
|
|
3707
|
+
const { name: base, fromModel } = text.trim() === '' ? { name: fallbackBranch(''), fromModel: false } : await branchName({ llm, route }, text, signal);
|
|
3697
3708
|
signal?.throwIfAborted();
|
|
3698
3709
|
// `.worktrees` must be a real folder (or absent): a committed symlink would put the checkout and the copied secrets
|
|
3699
3710
|
// outside the repository (lstat does not follow it).
|
|
@@ -3713,9 +3724,19 @@ export async function startOnBranch({ subprocess, sessionController, sessionQuer
|
|
|
3713
3724
|
const { sessionId: created } = await sessionController.create({ workspaceId: workspace.id, ...(preset === undefined ? {} : { agentPreset: preset }) });
|
|
3714
3725
|
// ponytail: a model that the new chat cannot take leaves dsh's default; the chat still starts.
|
|
3715
3726
|
if (model !== undefined) await sessionController.selectModel({ sessionId: created, ...model }).catch(() => {});
|
|
3727
|
+
// dsh's upload receipt belongs to one chat: each file is stored again for the new chat (the same content-addressed
|
|
3728
|
+
// file). The parts keep dsh's order: attachments, then the text.
|
|
3729
|
+
const content = [];
|
|
3730
|
+
let next = 0;
|
|
3731
|
+
for (const a of attachments) {
|
|
3732
|
+
if (a.type !== 'file') { content.push(a); continue; }
|
|
3733
|
+
const ref = files[next++];
|
|
3734
|
+
content.push({ type: 'file', receiptId: (await fileUploads.uploadStream({ sessionId: created, data: store.readFileStream(ref), name: ref.name })).receiptId });
|
|
3735
|
+
}
|
|
3736
|
+
if (text !== '') content.push({ type: 'text', text });
|
|
3716
3737
|
// dsh's `prompt(request, signal)` calls `signal.throwIfAborted()`. Not the request's signal: a closed request must not
|
|
3717
3738
|
// leave the new chat without its message; the timeout only ends a stuck admission.
|
|
3718
|
-
await sessionController.prompt({ requestId: randomUUID(), sessionId: created, mode: 'queue', content
|
|
3739
|
+
await sessionController.prompt({ requestId: randomUUID(), sessionId: created, mode: 'queue', content }, AbortSignal.timeout(STOP_WAIT_MS));
|
|
3719
3740
|
return { sessionId: created, branch: name, path, workspaceId: workspace.id, copied, skipped, named: fromModel };
|
|
3720
3741
|
} catch (error) {
|
|
3721
3742
|
throw Object.assign(new Error(`${error?.message ?? error}; the new worktree stays at ${path}`), Number.isInteger(error?.status) ? { status: error.status } : {});
|
|
@@ -3733,7 +3754,7 @@ export async function openWorktree({ subprocess, sessionQuery, registry }, sessi
|
|
|
3733
3754
|
}
|
|
3734
3755
|
|
|
3735
3756
|
/** Worktree chats (Host half): `GET ?sessionId[&list=1]` answers the chat's branch status (and the repository's
|
|
3736
|
-
* worktrees); `POST {action:'start', sessionId, text}` starts a chat on a new branch; `POST {action:'open', sessionId,
|
|
3757
|
+
* worktrees); `POST {action:'start', sessionId, text, attachments?}` starts a chat on a new branch; `POST {action:'open', sessionId,
|
|
3737
3758
|
* path}` registers one listed worktree as a workspace. The browser never gives a path that git did not list. */
|
|
3738
3759
|
function installWorktrees(ctx) {
|
|
3739
3760
|
const get = (name) => { try { return ctx.get?.(name); } catch { return undefined; } };
|
|
@@ -3759,12 +3780,19 @@ function installWorktrees(ctx) {
|
|
|
3759
3780
|
if (registry?.create === undefined) return json({ error: 'this dsh cannot add a workspace' }, 409);
|
|
3760
3781
|
return json(await openWorktree({ subprocess: ctx.subprocess, sessionQuery: ctx.sessionQuery, registry }, body.sessionId, body.path, request.signal));
|
|
3761
3782
|
}
|
|
3762
|
-
|
|
3783
|
+
// dsh's own prompt parts (dsh checks the image bytes and limits when the new chat takes the message).
|
|
3784
|
+
const attachments = body.attachments ?? [];
|
|
3785
|
+
const part = (a) => (a?.type === 'image' && Object.keys(a).every((k) => ['type', 'mediaType', 'data', 'name'].includes(k)) && isText(a.mediaType, 64)
|
|
3786
|
+
&& isText(a.data, 400e6) && (a.name === undefined || isText(a.name, 1024)))
|
|
3787
|
+
|| (a?.type === 'file' && Object.keys(a).length === 2 && isText(a.receiptId, 128));
|
|
3788
|
+
if (body.action !== 'start' || typeof body.text !== 'string' || body.text.length > 100000 || !Array.isArray(attachments) || attachments.length > 50
|
|
3789
|
+
|| !attachments.every(part) || (body.text.trim() === '' && attachments.length === 0)) return json({ error: 'bad request' }, 400);
|
|
3763
3790
|
if (registry?.create === undefined) return json({ error: 'this dsh cannot add a workspace' }, 409);
|
|
3764
3791
|
// No model (or no catalog): the branch gets the message's own slug.
|
|
3765
3792
|
const llm = get('llm');
|
|
3766
3793
|
const route = llm?.prepareCall === undefined ? undefined : await routeOf(ctx, get).catch(() => undefined);
|
|
3767
|
-
return json(await startOnBranch({ subprocess: ctx.subprocess, sessionController: ctx.sessionController, sessionQuery: ctx.sessionQuery, registry, llm, route
|
|
3794
|
+
return json(await startOnBranch({ subprocess: ctx.subprocess, sessionController: ctx.sessionController, sessionQuery: ctx.sessionQuery, registry, llm, route,
|
|
3795
|
+
agents: get('agents'), fileUploads: get('fileUploads'), store: get('attachments') }, body.sessionId, body.text, request.signal, attachments));
|
|
3768
3796
|
} catch (error) { return json({ error: String(error?.message ?? error) }, Number.isInteger(error?.status) ? error.status : 500); }
|
|
3769
3797
|
},
|
|
3770
3798
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-composer-plus",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.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",
|