dsh-composer-plus 1.0.2 → 1.1.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,25 @@
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.
5
6
  */
6
7
  import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
8
+ import { randomUUID } from 'node:crypto';
7
9
  import { homedir } from 'node:os';
8
10
  import { dirname, join } from 'node:path';
9
11
 
10
12
  export const name = 'dsh-composer-plus';
11
- export const inject = ['connection', 'subprocess', 'sessionQuery'];
13
+ export const inject = ['connection', 'subprocess', 'sessionQuery', 'sessionController'];
12
14
 
13
15
  export const CHANGES_PATH = '/api/composer-plus.changes';
14
16
  export const DIFF_PATH = '/api/composer-plus.diff';
15
17
  export const GRANTS_PATH = '/api/composer-plus.grants';
18
+ export const CONTINUE_PATH = '/api/composer-plus.continue';
19
+ const CONTINUE_SOURCE = 'composer-plus-continue';
20
+ 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.';
21
+ const continueMessage = () => Object.freeze({ id: randomUUID(), role: 'user', content: Object.freeze([Object.freeze({ type: 'text', text: CONTINUE_TEXT })]),
22
+ source: Object.freeze({ kind: 'composer-plus-resume', form: 'snapshot' }) });
16
23
  export const MODES = ['uncommitted', 'unstaged', 'staged', 'branch'];
17
24
  const MAX_OUTPUT = 4 * 1024 * 1024;
18
25
  const MAX_FILES = 500;
@@ -155,6 +162,37 @@ const json = (value, status = 200) => Response.json(value, { status, headers: {
155
162
  const isText = (v, max = 512) => typeof v === 'string' && v.length > 0 && v.length <= max;
156
163
 
157
164
  export function apply(ctx) {
165
+ // The empty wake marker opens the step; it is then replaced by a fresh context message. Only messages
166
+ // claimed from the inbox render as a chat bubble, so the replacement stays out of Chat, but it gives the
167
+ // model a final user turn: providers such as Claude reject a request that ends on an assistant message.
168
+ ctx.on('agent/pre-step', async (payload, next) => {
169
+ const decision = await next();
170
+ if (decision.kind !== 'enter' || !decision.messages.some((m) => m.source?.kind === CONTINUE_SOURCE)) return decision;
171
+ return { ...decision, messages: decision.messages.map((m) => (m.source?.kind === CONTINUE_SOURCE ? continueMessage() : m)) };
172
+ }, true);
173
+ ctx.connection.fetch.register({
174
+ path: CONTINUE_PATH, methods: ['POST'], requestBody: 'buffered',
175
+ fetch: async (request) => {
176
+ let body;
177
+ try { body = await request.json(); } catch { return json({ error: 'bad request' }, 400); }
178
+ if (!isText(body?.sessionId) || !Number.isSafeInteger(body?.afterSeq) || body.afterSeq < 0) return json({ error: 'bad request' }, 400);
179
+ try {
180
+ const found = await ctx.sessionController.resolveAgent(body.sessionId);
181
+ if ('error' in found) return json({ error: 'session unavailable' }, 409);
182
+ const { agent } = found;
183
+ const observation = await ctx.sessionQuery.observeSession(body.sessionId, { signal: request.signal, projectionMode: 'none' });
184
+ try {
185
+ const last = observation.events.findLast((e) => e.type === 'turn/start' || e.type === 'turn/end');
186
+ if (agent.status !== 'idle' || last?.seq !== body.afterSeq || (last.type === 'turn/end' && last.data?.reason?.kind === 'completed')) {
187
+ return json({ error: 'session no longer needs continuation' }, 409);
188
+ }
189
+ request.signal.throwIfAborted();
190
+ agent.steer({ id: randomUUID(), role: 'user', content: [], source: { kind: CONTINUE_SOURCE } });
191
+ return json({ accepted: true });
192
+ } finally { observation[Symbol.dispose]?.(); }
193
+ } catch { return json({ error: 'continuation failed' }, 500); }
194
+ },
195
+ });
158
196
  /** The session's own working directory (the endpoint never takes a path from the browser) and its newest
159
197
  * `workspace/changes` event, which dsh's own review tab opens as "Last turn". */
160
198
  const sessionFacts = async (sessionId, signal) => {
@@ -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,7 +1,7 @@
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.1.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",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "keywords": [
@@ -10,8 +10,11 @@
10
10
  "dsh",
11
11
  "dsh-plugin",
12
12
  "git",
13
+ "i18n",
14
+ "localization",
13
15
  "notifications",
14
- "plugin"
16
+ "plugin",
17
+ "translation"
15
18
  ],
16
19
  "exports": {
17
20
  ".": "./index.js",
@@ -22,10 +25,13 @@
22
25
  "client.js",
23
26
  "cordis.patch.yml",
24
27
  "README.md",
25
- "LICENSE"
28
+ "LICENSE",
29
+ "locales/LICENSE"
26
30
  ],
27
31
  "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"
32
+ "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",
33
+ "build:locales": "node scripts/build-locales.mjs",
34
+ "prepublishOnly": "npm test"
29
35
  },
30
36
  "dsh": {
31
37
  "bundle": {
@@ -43,7 +49,8 @@
43
49
  "@deepseek-ai/dsh-client-ui-sidebar-right",
44
50
  "@deepseek-ai/dsh-client-ui-session",
45
51
  "@deepseek-ai/dsh-api-remotes",
46
- "@deepseek-ai/dsh-api-session-controller"
52
+ "@deepseek-ai/dsh-api-session-controller",
53
+ "@deepseek-ai/dsh-client-ui-settings"
47
54
  ]
48
55
  }
49
56
  },