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/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
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "Änderungsansicht, Eingabeverlauf, Shift+Tab /plan, Fortsetzen mit einem Klick, Warteschlange per Drag-and-drop, Links, Benachrichtigungen, 11 Sprachen und automatische Updates."
5
+ }
6
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "Changes panel, input history, Shift+Tab /plan, one-click Continue, queue drag-and-drop, deeplinks, notifications, 11 UI languages and automatic updates."
5
+ }
6
+ }
package/locale/es.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "Panel de cambios, historial de entrada, Shift+Tab /plan, Continuar con un clic, reordenar la cola arrastrando, enlaces, notificaciones, 11 idiomas y actualizaciones automáticas."
5
+ }
6
+ }
package/locale/fr.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "Panneau des modifications, historique de saisie, Shift+Tab /plan, Continuer en un clic, file réorganisable par glisser-déposer, liens, notifications, 11 langues et mises à jour automatiques."
5
+ }
6
+ }
package/locale/hi.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "बदलाव पैनल, इनपुट इतिहास, Shift+Tab /plan, एक क्लिक में जारी रखें, कतार ड्रैग-एंड-ड्रॉप, लिंक, सूचनाएं, 11 भाषाएं और अपने-आप अपडेट।"
5
+ }
6
+ }
package/locale/id.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "Panel perubahan, riwayat input, Shift+Tab /plan, Lanjutkan sekali klik, urutkan antrean dengan seret, tautan, notifikasi, 11 bahasa, dan pembaruan otomatis."
5
+ }
6
+ }
package/locale/ja.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "変更パネル、入力履歴、Shift+Tab で /plan、ワンクリック続行、キューのドラッグ並べ替え、リンク、通知、11 言語、自動アップデート。"
5
+ }
6
+ }
package/locale/ko.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "변경 사항 패널, 입력 기록, Shift+Tab /plan, 원클릭 계속, 대기열 끌어서 정렬, 링크, 알림, 11개 언어와 자동 업데이트."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "Painel de alterações, histórico de entrada, Shift+Tab /plan, Continuar com um clique, reordenar a fila arrastando, links, notificações, 11 idiomas e atualizações automáticas."
5
+ }
6
+ }
package/locale/ru.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "Панель изменений, история ввода, Shift+Tab /plan, продолжение в один клик, перетаскивание очереди, ссылки, уведомления, 11 языков и автообновления."
5
+ }
6
+ }
package/locale/tr.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "Değişiklikler paneli, giriş geçmişi, Shift+Tab /plan, tek tıkla devam, sıradaki mesajları sürükle-bırak, bağlantılar, bildirimler, 11 arayüz dili ve otomatik güncellemeler."
5
+ }
6
+ }
package/locale/vi.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "Bảng thay đổi, lịch sử nhập, Shift+Tab /plan, Tiếp tục một chạm, kéo thả hàng đợi, liên kết, thông báo, 11 ngôn ngữ và tự động cập nhật."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Composer Plus",
4
+ "description": "改动面板、输入历史、Shift+Tab /plan、一键继续、队列拖拽排序、会话链接、通知、11 种界面语言和自动更新。"
5
+ }
6
+ }
@@ -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.0.2",
4
- "description": "DeepSeek Harness helpers: Codex-style Changes panel, input history, Shift+Tab /plan, one-click Continue, queue drag-and-drop, Copy deeplink, Auto Review allow-for-chat, background notifications",
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
- "plugin"
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
  },