dsh-composer-plus 1.0.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -20
- package/assets/features/allowForChat.webp +0 -0
- package/assets/features/autoUpdate.webp +0 -0
- package/assets/features/changesPanel.webp +0 -0
- package/assets/features/continueButton.webp +0 -0
- package/assets/features/copyDeeplink.webp +0 -0
- package/assets/features/inputHistory.webp +0 -0
- package/assets/features/language.webp +0 -0
- package/assets/features/notifications.webp +0 -0
- package/assets/features/planToggle.webp +0 -0
- package/assets/features/projectStatus.webp +0 -0
- package/assets/features/queueReorder.webp +0 -0
- package/assets/icon.svg +13 -0
- package/client.js +402 -51
- package/index.js +92 -1
- package/locale/de.json +6 -0
- package/locale/en.json +6 -0
- package/locale/es.json +6 -0
- package/locale/fr.json +6 -0
- package/locale/hi.json +6 -0
- package/locale/id.json +6 -0
- package/locale/ja.json +6 -0
- package/locale/ko.json +6 -0
- package/locale/pt-BR.json +6 -0
- package/locale/ru.json +6 -0
- package/locale/tr.json +6 -0
- package/locale/vi.json +6 -0
- package/locale/zh.json +6 -0
- package/locales/LICENSE +21 -0
- package/package.json +36 -7
package/index.js
CHANGED
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host half of dsh-composer-plus.
|
|
3
|
+
* - Message-free Continue: authenticated resume through public agent lifecycle hooks.
|
|
3
4
|
* - Changes panel: read-only git status/diff for a session's working directory, served on the authenticated `/api` channel.
|
|
4
5
|
* - Persistent Auto Review grants: per-session tool names saved under the dsh home, so "Allow for this chat" survives a restart.
|
|
6
|
+
* - Update check: the latest GitHub release, offered only once npm serves that exact version (the client installs it
|
|
7
|
+
* through dsh's own plugin manager).
|
|
5
8
|
*/
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
6
10
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
7
12
|
import { homedir } from 'node:os';
|
|
8
13
|
import { dirname, join } from 'node:path';
|
|
9
14
|
|
|
10
15
|
export const name = 'dsh-composer-plus';
|
|
11
|
-
export const inject = ['connection', 'subprocess', 'sessionQuery'];
|
|
16
|
+
export const inject = ['connection', 'subprocess', 'sessionQuery', 'sessionController'];
|
|
12
17
|
|
|
13
18
|
export const CHANGES_PATH = '/api/composer-plus.changes';
|
|
14
19
|
export const DIFF_PATH = '/api/composer-plus.diff';
|
|
15
20
|
export const GRANTS_PATH = '/api/composer-plus.grants';
|
|
21
|
+
export const CONTINUE_PATH = '/api/composer-plus.continue';
|
|
22
|
+
export const UPDATE_PATH = '/api/composer-plus.update';
|
|
23
|
+
export const SHOT_PATH = '/api/composer-plus.shot';
|
|
24
|
+
/** Feature screenshots shipped in assets/features/<id>.webp; only these ids are served (no path input). */
|
|
25
|
+
export const SHOTS = ['changesPanel', 'projectStatus', 'notifications', 'allowForChat', 'continueButton', 'inputHistory', 'planToggle',
|
|
26
|
+
'queueReorder', 'copyDeeplink', 'language', 'autoUpdate'];
|
|
27
|
+
const PACKAGE = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'));
|
|
28
|
+
const NPM_NAME = 'dsh-composer-plus';
|
|
29
|
+
const RELEASES_URL = 'https://api.github.com/repos/furkancak1r/dsh-composer-plus/releases/latest';
|
|
30
|
+
const CONTINUE_SOURCE = 'composer-plus-continue';
|
|
31
|
+
const CONTINUE_TEXT = 'The previous turn stopped before it finished (it was interrupted or failed). Continue the task from where it stopped. Do not repeat completed work.';
|
|
32
|
+
const continueMessage = () => Object.freeze({ id: randomUUID(), role: 'user', content: Object.freeze([Object.freeze({ type: 'text', text: CONTINUE_TEXT })]),
|
|
33
|
+
source: Object.freeze({ kind: 'composer-plus-resume', form: 'snapshot' }) });
|
|
16
34
|
export const MODES = ['uncommitted', 'unstaged', 'staged', 'branch'];
|
|
17
35
|
const MAX_OUTPUT = 4 * 1024 * 1024;
|
|
18
36
|
const MAX_FILES = 500;
|
|
@@ -151,10 +169,66 @@ export function grantsStore(file) {
|
|
|
151
169
|
};
|
|
152
170
|
}
|
|
153
171
|
|
|
172
|
+
/** `1.2.3` / `v1.2.3` -> [1, 2, 3]; anything else (pre-releases, ranges) is not an update target. */
|
|
173
|
+
export const parseVersion = (text) => {
|
|
174
|
+
const m = /^v?(\d{1,6})\.(\d{1,6})\.(\d{1,6})$/.exec(String(text ?? '').trim());
|
|
175
|
+
return m === null ? undefined : m.slice(1).map(Number);
|
|
176
|
+
};
|
|
177
|
+
export const newer = (a, b) => { for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i]; return false; };
|
|
178
|
+
/** Latest GitHub release vs this copy. `managed` is false for a renamed or linked local copy, which pnpm can't upgrade. */
|
|
179
|
+
export async function checkUpdate(fetchImpl = fetch, pkg = PACKAGE, signal) {
|
|
180
|
+
const current = parseVersion(pkg.version);
|
|
181
|
+
const managed = pkg.name === NPM_NAME && current !== undefined;
|
|
182
|
+
const timeout = AbortSignal.timeout(10000);
|
|
183
|
+
const opts = { signal: signal === undefined ? timeout : AbortSignal.any([signal, timeout]) };
|
|
184
|
+
const response = await fetchImpl(RELEASES_URL, { ...opts, headers: { accept: 'application/vnd.github+json', 'user-agent': NPM_NAME } });
|
|
185
|
+
if (!response.ok) throw new Error(`GitHub answered ${response.status}`);
|
|
186
|
+
const release = await response.json();
|
|
187
|
+
const latest = parseVersion(release?.tag_name);
|
|
188
|
+
if (latest === undefined) throw new Error('latest release has no x.y.z tag');
|
|
189
|
+
const info = { name: NPM_NAME, current: pkg.version, latest: latest.join('.'), managed, newer: false,
|
|
190
|
+
url: typeof release.html_url === 'string' && release.html_url.startsWith('https://github.com/') ? release.html_url : undefined };
|
|
191
|
+
if (current === undefined || !newer(latest, current)) return info;
|
|
192
|
+
// A release can go out before `npm publish`; offer only what pnpm can install.
|
|
193
|
+
const npm = await fetchImpl(`https://registry.npmjs.org/${NPM_NAME}/${info.latest}`, opts);
|
|
194
|
+
return { ...info, newer: npm.ok };
|
|
195
|
+
}
|
|
196
|
+
|
|
154
197
|
const json = (value, status = 200) => Response.json(value, { status, headers: { 'cache-control': 'no-store' } });
|
|
155
198
|
const isText = (v, max = 512) => typeof v === 'string' && v.length > 0 && v.length <= max;
|
|
156
199
|
|
|
157
200
|
export function apply(ctx) {
|
|
201
|
+
// The empty wake marker opens the step; it is then replaced by a fresh context message. Only messages
|
|
202
|
+
// claimed from the inbox render as a chat bubble, so the replacement stays out of Chat, but it gives the
|
|
203
|
+
// model a final user turn: providers such as Claude reject a request that ends on an assistant message.
|
|
204
|
+
ctx.on('agent/pre-step', async (payload, next) => {
|
|
205
|
+
const decision = await next();
|
|
206
|
+
if (decision.kind !== 'enter' || !decision.messages.some((m) => m.source?.kind === CONTINUE_SOURCE)) return decision;
|
|
207
|
+
return { ...decision, messages: decision.messages.map((m) => (m.source?.kind === CONTINUE_SOURCE ? continueMessage() : m)) };
|
|
208
|
+
}, true);
|
|
209
|
+
ctx.connection.fetch.register({
|
|
210
|
+
path: CONTINUE_PATH, methods: ['POST'], requestBody: 'buffered',
|
|
211
|
+
fetch: async (request) => {
|
|
212
|
+
let body;
|
|
213
|
+
try { body = await request.json(); } catch { return json({ error: 'bad request' }, 400); }
|
|
214
|
+
if (!isText(body?.sessionId) || !Number.isSafeInteger(body?.afterSeq) || body.afterSeq < 0) return json({ error: 'bad request' }, 400);
|
|
215
|
+
try {
|
|
216
|
+
const found = await ctx.sessionController.resolveAgent(body.sessionId);
|
|
217
|
+
if ('error' in found) return json({ error: 'session unavailable' }, 409);
|
|
218
|
+
const { agent } = found;
|
|
219
|
+
const observation = await ctx.sessionQuery.observeSession(body.sessionId, { signal: request.signal, projectionMode: 'none' });
|
|
220
|
+
try {
|
|
221
|
+
const last = observation.events.findLast((e) => e.type === 'turn/start' || e.type === 'turn/end');
|
|
222
|
+
if (agent.status !== 'idle' || last?.seq !== body.afterSeq || (last.type === 'turn/end' && last.data?.reason?.kind === 'completed')) {
|
|
223
|
+
return json({ error: 'session no longer needs continuation' }, 409);
|
|
224
|
+
}
|
|
225
|
+
request.signal.throwIfAborted();
|
|
226
|
+
agent.steer({ id: randomUUID(), role: 'user', content: [], source: { kind: CONTINUE_SOURCE } });
|
|
227
|
+
return json({ accepted: true });
|
|
228
|
+
} finally { observation[Symbol.dispose]?.(); }
|
|
229
|
+
} catch { return json({ error: 'continuation failed' }, 500); }
|
|
230
|
+
},
|
|
231
|
+
});
|
|
158
232
|
/** The session's own working directory (the endpoint never takes a path from the browser) and its newest
|
|
159
233
|
* `workspace/changes` event, which dsh's own review tab opens as "Last turn". */
|
|
160
234
|
const sessionFacts = async (sessionId, signal) => {
|
|
@@ -201,6 +275,23 @@ export function apply(ctx) {
|
|
|
201
275
|
} catch (error) { return json({ error: String(error?.message ?? error) }, 500); }
|
|
202
276
|
},
|
|
203
277
|
});
|
|
278
|
+
ctx.connection.fetch.register({
|
|
279
|
+
path: SHOT_PATH, methods: ['GET'], requestBody: 'buffered',
|
|
280
|
+
fetch: async (request) => {
|
|
281
|
+
const id = new URL(request.url).searchParams.get('id');
|
|
282
|
+
if (!SHOTS.includes(id)) return json({ error: 'not found' }, 404);
|
|
283
|
+
try {
|
|
284
|
+
const bytes = await readFile(new URL(`./assets/features/${id}.webp`, import.meta.url));
|
|
285
|
+
return new Response(bytes, { headers: { 'content-type': 'image/webp', 'cache-control': 'private, max-age=86400' } });
|
|
286
|
+
} catch { return json({ error: 'not found' }, 404); }
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
ctx.connection.fetch.register({
|
|
290
|
+
path: UPDATE_PATH, methods: ['GET'], requestBody: 'buffered',
|
|
291
|
+
fetch: async (request) => {
|
|
292
|
+
try { return json(await checkUpdate(fetch, PACKAGE, request.signal)); } catch (error) { return json({ error: String(error?.message ?? error) }, 502); }
|
|
293
|
+
},
|
|
294
|
+
});
|
|
204
295
|
const store = grantsStore(join(process.env.DSH_HOME || join(homedir(), '.dsh'), 'storages', 'dsh-composer-plus', 'grants.json'));
|
|
205
296
|
ctx.connection.fetch.register({
|
|
206
297
|
path: GRANTS_PATH, methods: ['GET', 'POST'], requestBody: 'buffered',
|
package/locale/de.json
ADDED
package/locale/en.json
ADDED
package/locale/es.json
ADDED
package/locale/fr.json
ADDED
package/locale/hi.json
ADDED
package/locale/id.json
ADDED
package/locale/ja.json
ADDED
package/locale/ko.json
ADDED
package/locale/ru.json
ADDED
package/locale/tr.json
ADDED
package/locale/vi.json
ADDED
package/locale/zh.json
ADDED
package/locales/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,31 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-composer-plus",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "DeepSeek Harness helpers:
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "DeepSeek Harness helpers: 11 extra UI languages (Türkçe, Español, Français, Deutsch, 日本語…), Changes panel, input history, Shift+Tab /plan, one-click Continue, queue drag-and-drop, deeplinks, Auto Review allow-for-chat, notifications, automatic updates",
|
|
5
|
+
"icon": "./assets/icon.svg",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"keywords": [
|
|
9
|
+
"auto-review",
|
|
10
|
+
"auto-update",
|
|
8
11
|
"changes",
|
|
12
|
+
"claude-code",
|
|
13
|
+
"codex",
|
|
14
|
+
"composer-plus",
|
|
15
|
+
"continue",
|
|
16
|
+
"deeplink",
|
|
17
|
+
"deepseek",
|
|
9
18
|
"deepseek-harness",
|
|
19
|
+
"deepseek-harness-plugin",
|
|
20
|
+
"drag-and-drop",
|
|
10
21
|
"dsh",
|
|
22
|
+
"dsh-composer-plus",
|
|
11
23
|
"dsh-plugin",
|
|
24
|
+
"dsh-plugins",
|
|
12
25
|
"git",
|
|
26
|
+
"git-diff",
|
|
27
|
+
"i18n",
|
|
28
|
+
"input-history",
|
|
29
|
+
"localization",
|
|
13
30
|
"notifications",
|
|
14
|
-
"
|
|
31
|
+
"plan-mode",
|
|
32
|
+
"plugin",
|
|
33
|
+
"queue",
|
|
34
|
+
"translation"
|
|
15
35
|
],
|
|
16
36
|
"exports": {
|
|
17
37
|
".": "./index.js",
|
|
18
|
-
"./client": "./client.js"
|
|
38
|
+
"./client": "./client.js",
|
|
39
|
+
"./locale/*.json": "./locale/*.json",
|
|
40
|
+
"./package.json": "./package.json"
|
|
19
41
|
},
|
|
20
42
|
"files": [
|
|
21
43
|
"index.js",
|
|
22
44
|
"client.js",
|
|
45
|
+
"locale/*.json",
|
|
46
|
+
"assets/icon.svg",
|
|
23
47
|
"cordis.patch.yml",
|
|
24
48
|
"README.md",
|
|
25
|
-
"LICENSE"
|
|
49
|
+
"LICENSE",
|
|
50
|
+
"locales/LICENSE",
|
|
51
|
+
"assets/features/*.webp"
|
|
26
52
|
],
|
|
27
53
|
"scripts": {
|
|
28
|
-
"test": "node test/continue.test.mjs && node test/queue-drag.test.mjs && node test/plan-toggle.test.mjs && node test/deeplink.test.mjs && node test/chat-grant.test.mjs && node test/notify.test.mjs && node test/changes.test.mjs && node test/project-status.test.mjs && node test/toggles.test.mjs"
|
|
54
|
+
"test": "node scripts/build-locales.mjs --check && node test/locale.test.mjs && node test/continue.test.mjs && node test/continue-host.test.mjs && node test/queue-drag.test.mjs && node test/plan-toggle.test.mjs && node test/deeplink.test.mjs && node test/chat-grant.test.mjs && node test/notify.test.mjs && node test/changes.test.mjs && node test/project-status.test.mjs && node test/toggles.test.mjs && node test/update.test.mjs && node test/meta.test.mjs && node test/shots.test.mjs",
|
|
55
|
+
"build:locales": "node scripts/build-locales.mjs",
|
|
56
|
+
"prepublishOnly": "npm test"
|
|
29
57
|
},
|
|
30
58
|
"dsh": {
|
|
31
59
|
"bundle": {
|
|
@@ -43,7 +71,8 @@
|
|
|
43
71
|
"@deepseek-ai/dsh-client-ui-sidebar-right",
|
|
44
72
|
"@deepseek-ai/dsh-client-ui-session",
|
|
45
73
|
"@deepseek-ai/dsh-api-remotes",
|
|
46
|
-
"@deepseek-ai/dsh-api-session-controller"
|
|
74
|
+
"@deepseek-ai/dsh-api-session-controller",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
47
76
|
]
|
|
48
77
|
}
|
|
49
78
|
},
|