dsh-client-auto-continue 0.7.4 → 0.8.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/src/index.ts CHANGED
@@ -1,13 +1,24 @@
1
1
  /**
2
- * Host half of the auto-continue plugin: registers the `auto-continue`
3
- * settings namespace so the browser half's settings card can edit it and the
4
- * engine can read it. No other host-side behavior.
2
+ * Host half of the auto-continue plugin.
3
+ *
4
+ * - Registers the `auto-continue` settings namespace (the browser half's
5
+ * settings card edits it; the host engine reads it).
6
+ * - Runs the single-instance auto-continue engine: listens to the session
7
+ * event firehose, sends via `agent.followup`, cancels via `agent.cancel`.
8
+ * - Serves a status bridge the browser half subscribes to: notifications and
9
+ * runtime state (stats / pauses), plus an action endpoint for notification
10
+ * buttons.
5
11
  */
6
12
  import type { Context } from '@deepseek-ai/cordis';
7
13
  import z from '@deepseek-ai/schemastery';
8
14
  import { settingsNamespace } from '@deepseek-ai/dsh-settings';
15
+ import { AutoContinueRunner, resolveConfig, type AutoContinueSettings } from './host/engine.ts';
9
16
  // Type-only: pulls the `ctx.settings` Context augmentation from dsh-settings.
10
17
  import type {} from '@deepseek-ai/dsh-settings';
18
+ // Type-only: pulls the `ctx.webServer` Context augmentation.
19
+ import type {} from '@deepseek-ai/dsh-host-webserver';
20
+ import type {} from '@deepseek-ai/dsh-agent';
21
+ import type {} from '@deepseek-ai/dsh-session';
11
22
 
12
23
  /** Settings namespace of the auto-continue plugin (lowercase kebab-case). */
13
24
  export const AUTO_CONTINUE_NS = 'auto-continue';
@@ -75,8 +86,8 @@ export const AutoContinueSchema = z.object({
75
86
  });
76
87
 
77
88
  /**
78
- * Plugin body: register the settings namespace when a settings provider is
79
- * composed. Changes apply live — the browser half observes the scope.
89
+ * Plugin body: register the settings namespace, start the single-instance
90
+ * engine, and serve the status bridge.
80
91
  * @param ctx - host plugin context.
81
92
  */
82
93
  export function apply(ctx: Context): void {
@@ -85,4 +96,85 @@ export function apply(ctx: Context): void {
85
96
  applies: 'live',
86
97
  });
87
98
  });
99
+
100
+ // 单实例引擎: host 进程内监听会话事件, 所有标签页共享同一个引擎。
101
+ ctx.inject(['settings', 'agents', 'webServer'], (engineCtx) => {
102
+ const runner = new AutoContinueRunner(engineCtx, () =>
103
+ resolveConfig(engineCtx.settings.get(settingsNamespace(AUTO_CONTINUE_NS)) as AutoContinueSettings | undefined),
104
+ );
105
+
106
+ // 状态桥: browser 侧订阅通知与运行时状态(SSE)。
107
+ const sseClients = new Set<(data: string) => void>();
108
+ const pushToAll = (data: string): void => {
109
+ for (const send of sseClients) {
110
+ try {
111
+ send(data);
112
+ } catch {
113
+ sseClients.delete(send);
114
+ }
115
+ }
116
+ };
117
+ const statePayload = (): string =>
118
+ JSON.stringify({
119
+ type: 'state',
120
+ stats: runner.todayStats(),
121
+ paused: runner.activePauses(),
122
+ });
123
+
124
+ runner.subscribeNotices(() => {
125
+ for (const notice of runner.drainNotices()) {
126
+ pushToAll(`data: ${JSON.stringify({ type: 'notice', notice })}\n\n`);
127
+ }
128
+ });
129
+ runner.subscribeState(() => {
130
+ pushToAll(`data: ${statePayload()}\n\n`);
131
+ });
132
+
133
+ engineCtx.webServer.register({
134
+ kind: 'exact',
135
+ path: '/api/auto-continue-bridge',
136
+ handler: (req, res) => {
137
+ res.writeHead(200, {
138
+ 'content-type': 'text/event-stream',
139
+ 'cache-control': 'no-cache',
140
+ connection: 'keep-alive',
141
+ });
142
+ res.write(`data: ${statePayload()}\n\n`);
143
+ const send = (data: string): void => {
144
+ res.write(data);
145
+ };
146
+ sseClients.add(send);
147
+ req.on('close', () => sseClients.delete(send));
148
+ },
149
+ });
150
+
151
+ // 通知按钮动作: browser 点击「立即续跑 / 暂停该会话」时 POST 到这里。
152
+ engineCtx.webServer.register({
153
+ kind: 'exact',
154
+ path: '/api/auto-continue-action',
155
+ handler: (req, res) => {
156
+ let body = '';
157
+ req.on('data', (chunk: Buffer) => {
158
+ body += chunk.toString('utf8');
159
+ if (body.length > 4096) req.destroy();
160
+ });
161
+ req.on('end', () => {
162
+ try {
163
+ const parsed = JSON.parse(body) as { sessionId?: string; action?: string };
164
+ if (typeof parsed.action === 'string') {
165
+ runner.handleNoticeAction((parsed.sessionId as never) ?? undefined, parsed.action);
166
+ res.writeHead(200, { 'content-type': 'application/json' });
167
+ res.end(JSON.stringify({ ok: true }));
168
+ return;
169
+ }
170
+ res.writeHead(400, { 'content-type': 'application/json' });
171
+ res.end(JSON.stringify({ ok: false }));
172
+ } catch {
173
+ res.writeHead(400, { 'content-type': 'application/json' });
174
+ res.end(JSON.stringify({ ok: false }));
175
+ }
176
+ });
177
+ },
178
+ });
179
+ });
88
180
  }
package/tsconfig.json CHANGED
@@ -17,7 +17,8 @@
17
17
  "forceConsistentCasingInFileNames": true,
18
18
  "noUncheckedIndexedAccess": true,
19
19
  "types": [
20
- "react"
20
+ "react",
21
+ "node"
21
22
  ],
22
23
  "allowImportingTsExtensions": true,
23
24
  "rewriteRelativeImportExtensions": true