dsh-client-auto-continue 0.2.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/lib/index.js ADDED
@@ -0,0 +1,38 @@
1
+ // src/index.ts
2
+ import z from "@deepseek-ai/schemastery";
3
+ import { settingsNamespace } from "@deepseek-ai/dsh-settings";
4
+ var AUTO_CONTINUE_NS = "auto-continue";
5
+ var AutoContinueSchema = z.object({
6
+ /** Text automatically sent after an interruption. */
7
+ continueText: z.string().default("继续"),
8
+ /** Grace period after an interruption before auto-sending (ms). */
9
+ graceMs: z.natural().default(3e3),
10
+ /** Minimum interval between two auto-continues per session (ms). */
11
+ cooldownMs: z.natural().default(2e4),
12
+ /** Max consecutive auto-continues per session before stopping. */
13
+ maxConsecutive: z.natural().min(1).default(3),
14
+ /** Scan recently interrupted sessions on page load / reconnect. */
15
+ scanOnBoot: z.boolean().default(true),
16
+ /** Max sessions the scan checks (most recently updated). */
17
+ scanLimit: z.natural().min(1).default(8),
18
+ /** Scan only considers interruptions inside this window (ms). */
19
+ freshMs: z.natural().default(15 * 60 * 1e3),
20
+ /** Delay before scanning after a reconnect (ms). */
21
+ reconnectScanDelayMs: z.natural().default(5e3),
22
+ /** SSE reconnect backoff (ms). */
23
+ reconnectBackoffMs: z.natural().default(3e3),
24
+ /** Log `[auto-continue]` lines to the browser console. */
25
+ verbose: z.boolean().default(true)
26
+ });
27
+ function apply(ctx) {
28
+ ctx.inject(["settings"], (settingsCtx) => {
29
+ settingsCtx.settings.register(settingsNamespace(AUTO_CONTINUE_NS), AutoContinueSchema, {
30
+ applies: "live"
31
+ });
32
+ });
33
+ }
34
+ export {
35
+ AUTO_CONTINUE_NS,
36
+ AutoContinueSchema,
37
+ apply
38
+ };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Auto-continue engine — browser half core.
3
+ *
4
+ * Watches the two live event streams of the dsh web GUI (mux + host):
5
+ * - turns ended for a non-human reason (`turn/end` reason ∈ error / interrupted / max-tokens)
6
+ * - host-reported agent failures with no turn position (`host/agent-error`)
7
+ * After a grace period it sends a queued prompt (default 「继续」) to that
8
+ * session — exactly equivalent to the user typing it manually.
9
+ *
10
+ * All behavior is driven by the `auto-continue` settings namespace (see the
11
+ * plugin's settings card); every knob below is user-configurable there.
12
+ */
13
+ import type { IApiClient } from '@deepseek-ai/dsh-client-connection/client';
14
+ /** The `auto-continue` settings section (all fields optional on the wire; the host schema carries defaults). */
15
+ export interface AutoContinueSettings {
16
+ /** Text automatically sent after an interruption. */
17
+ continueText?: string;
18
+ /** Grace period after an interruption before auto-sending (ms). */
19
+ graceMs?: number;
20
+ /** Minimum interval between two auto-continues per session (ms). */
21
+ cooldownMs?: number;
22
+ /** Max consecutive auto-continues per session before stopping. */
23
+ maxConsecutive?: number;
24
+ /** Scan recently interrupted sessions on page load / reconnect. */
25
+ scanOnBoot?: boolean;
26
+ /** Max sessions the scan checks (most recently updated). */
27
+ scanLimit?: number;
28
+ /** Scan only considers interruptions inside this window (ms). */
29
+ freshMs?: number;
30
+ /** Delay before scanning after a reconnect (ms). */
31
+ reconnectScanDelayMs?: number;
32
+ /** SSE reconnect backoff (ms). */
33
+ reconnectBackoffMs?: number;
34
+ /** Log `[auto-continue]` lines to the browser console. */
35
+ verbose?: boolean;
36
+ }
37
+ /** Fully resolved configuration (built-in defaults + user overrides). */
38
+ export type AutoContinueConfig = Required<AutoContinueSettings>;
39
+ /** Built-in defaults — must match the host schema defaults in src/index.ts. */
40
+ export declare const DEFAULT_CONFIG: AutoContinueConfig;
41
+ /** Resolve a (possibly partial / not-yet-loaded) settings section to a full config. */
42
+ export declare function resolveConfig(section: AutoContinueSettings | undefined): AutoContinueConfig;
43
+ /** 插件主体: 一条 mux 流 + 一条 host 流 + 启动/重连扫描。 */
44
+ export declare class AutoContinueRunner {
45
+ private readonly api;
46
+ private readonly getConfig;
47
+ private readonly states;
48
+ private readonly muxAbort;
49
+ private readonly hostAbort;
50
+ private disposed;
51
+ private reconnectScans;
52
+ /**
53
+ * @param api - shared wire client (ctx.connection.api).
54
+ * @param getConfig - read the current resolved configuration (settings scope).
55
+ */
56
+ constructor(api: IApiClient, getConfig: () => AutoContinueConfig);
57
+ private log;
58
+ dispose(): void;
59
+ private state;
60
+ private runMux;
61
+ private runHost;
62
+ private onMuxFrame;
63
+ private onSessionEvent;
64
+ private onHostFrame;
65
+ private schedule;
66
+ private cancelPending;
67
+ private fire;
68
+ private runningViaList;
69
+ private scheduleReconnectScan;
70
+ private bootScanLoop;
71
+ /** 反复尝试扫描, 直到成功(宿主就绪)或达到次数上限。 */
72
+ private scanLoop;
73
+ /**
74
+ * 扫描最近中断过的会话: 最后回合以非人为原因结束, 且其后没有新回合或用户消息。
75
+ * @returns 是否成功完成一次扫描(宿主就绪)。
76
+ */
77
+ private scanInterrupted;
78
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Auto-continue plugin, browser half.
3
+ *
4
+ * - Runs the auto-continue engine over the live mux + host event streams.
5
+ * - Registers the `auto-continue` settings card into the plugin-configuration
6
+ * section (`settings.plugin.item`), editing the same namespace the engine
7
+ * reads — every behavior knob is configurable from the GUI.
8
+ */
9
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
10
+ import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client';
11
+ import { type SettingsCardKey } from './locales.ts';
12
+ /** 客户端根上下文的 connection 服务(由 dsh-client-connection 挂载)。 */
13
+ declare module '@deepseek-ai/cordis' {
14
+ interface Context {
15
+ connection: ConnectionHandle;
16
+ }
17
+ }
18
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
19
+ interface LocaleNamespaceMap {
20
+ /** auto-continue settings-card copy. */
21
+ 'auto-continue': SettingsCardKey;
22
+ }
23
+ }
24
+ /** Services required by this plugin. */
25
+ export declare const inject: string[];
26
+ /**
27
+ * Plugin body: mount the engine and the settings card.
28
+ * @param ctx - client root context.
29
+ */
30
+ export declare function apply(ctx: ClientContext): void;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `auto-continue` namespace dictionaries: copy for the plugin settings card
3
+ * registered into the `settings.plugin.item` seat of the plugin-configuration
4
+ * section. Includes the card-chrome keys the card component reads.
5
+ */
6
+ /** 简体中文词典(键集的事实来源)。 */
7
+ export declare const zh: {
8
+ 'card.title': string;
9
+ 'card.description': string;
10
+ 'field.continueText': string;
11
+ 'field.continueTextHint': string;
12
+ 'field.graceMs': string;
13
+ 'field.graceMsHint': string;
14
+ 'field.cooldownMs': string;
15
+ 'field.cooldownMsHint': string;
16
+ 'field.maxConsecutive': string;
17
+ 'field.maxConsecutiveHint': string;
18
+ 'field.scanOnBoot': string;
19
+ 'field.scanOnBootHint': string;
20
+ 'field.scanLimit': string;
21
+ 'field.scanLimitHint': string;
22
+ 'field.freshMs': string;
23
+ 'field.freshMsHint': string;
24
+ 'field.reconnectScanDelayMs': string;
25
+ 'field.reconnectScanDelayMsHint': string;
26
+ 'field.reconnectBackoffMs': string;
27
+ 'field.reconnectBackoffMsHint': string;
28
+ 'field.verbose': string;
29
+ 'field.verboseHint': string;
30
+ 'chrome.collapse': string;
31
+ 'chrome.expand': string;
32
+ 'chrome.unsaved': string;
33
+ 'chrome.readOnly': string;
34
+ 'chrome.saveFailed': string;
35
+ 'chrome.discard': string;
36
+ 'chrome.saving': string;
37
+ 'chrome.save': string;
38
+ 'chrome.overridden': string;
39
+ 'chrome.reset': string;
40
+ 'chrome.invalidNumber': string;
41
+ 'chrome.inherit': string;
42
+ 'chrome.on': string;
43
+ 'chrome.off': string;
44
+ };
45
+ /** 本插件的键联合。 */
46
+ export type SettingsCardKey = keyof typeof zh;
47
+ /** English dictionary, checked complete against the zh key set. */
48
+ export declare const en: Record<SettingsCardKey, string>;
@@ -0,0 +1,47 @@
1
+ import { type SettingsScope, type SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
2
+ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
3
+ import type { AutoContinueSettings } from './engine.ts';
4
+ import { type CardActions, type CardFieldState, type CardShell } from './settings-form.ts';
5
+ /** What the auto-continue card renders. */
6
+ export interface AutoContinueSettingsCardState extends CardShell {
7
+ continueText: CardFieldState;
8
+ graceMs: CardFieldState;
9
+ cooldownMs: CardFieldState;
10
+ maxConsecutive: CardFieldState;
11
+ scanOnBoot: CardFieldState;
12
+ scanLimit: CardFieldState;
13
+ freshMs: CardFieldState;
14
+ reconnectScanDelayMs: CardFieldState;
15
+ reconnectBackoffMs: CardFieldState;
16
+ verbose: CardFieldState;
17
+ }
18
+ /** The registration-side face the card's slot entry injects. */
19
+ export interface AutoContinueSettingsCardFace extends CardActions {
20
+ hooks: {
21
+ /** Card snapshot bound by the renderer as useAutoContinueSettingsCard. */
22
+ autoContinueSettingsCard: SnapshotStore<AutoContinueSettingsCardState>;
23
+ };
24
+ }
25
+ /** Bridges the `auto-continue` scope onto the card's staged form. */
26
+ export declare class AutoContinueSettingsCardController {
27
+ private readonly form;
28
+ private readonly store;
29
+ /**
30
+ * @param scope - the bound settings scope for the `auto-continue` namespace.
31
+ */
32
+ constructor(scope: SettingsScope<AutoContinueSettings>);
33
+ private projection;
34
+ /**
35
+ * Build the face the card's slot registration injects.
36
+ * @returns the card's snapshot and its form actions.
37
+ */
38
+ inject(): AutoContinueSettingsCardFace;
39
+ }
40
+ /** Props the renderer binds for the auto-continue card. */
41
+ export type AutoContinueSettingsCardProps = PropsRuntime<'settings.plugin.item'> & PropsLocale<'auto-continue'> & InjectFace<AutoContinueSettingsCardFace>;
42
+ /**
43
+ * Render the auto-continue card.
44
+ * @param props - locale copy, the card snapshot, and its form actions.
45
+ * @returns the card.
46
+ */
47
+ export declare function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps): import("react").JSX.Element;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Staged form model behind the plugin settings card — a self-contained
3
+ * implementation of the plugin-card store pattern used by the DSH plugin
4
+ * configuration section.
5
+ *
6
+ * A card stages what the user types and writes it only when they save. Each
7
+ * settings write is a durable, revision-fenced document mutation, so staging
8
+ * keeps what is on screen exactly what a save would store. A field shows its
9
+ * effective value — the user layer over the composition layer over the schema
10
+ * default — and whether the user layer carries it (presence, not value
11
+ * equality, marks an override).
12
+ */
13
+ import type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
14
+ /** The write one field's staged text performs when the card is saved. */
15
+ export type FieldWrite = {
16
+ kind: 'set';
17
+ value: unknown;
18
+ } | {
19
+ kind: 'clear';
20
+ };
21
+ /** How one field converts between its stored value and its draft text. */
22
+ export interface CardFieldSpec {
23
+ /** Field name inside the namespace section. */
24
+ field: string;
25
+ /** Render a stored value as draft text; the empty string when the section carries none. */
26
+ format: (value: unknown) => string;
27
+ /**
28
+ * The write this draft text stages, or undefined when the text is not a
29
+ * value this field accepts — which blocks the save rather than discarding it.
30
+ */
31
+ parse: (text: string) => FieldWrite | undefined;
32
+ }
33
+ /** One field as the card's control renders it. */
34
+ export interface CardFieldState {
35
+ /** Draft text the control renders. */
36
+ text: string;
37
+ /** Whether saving would leave a user-layer entry for this field. */
38
+ overridden: boolean;
39
+ /** Whether the draft is not a value this field accepts, which blocks saving. */
40
+ invalid: boolean;
41
+ }
42
+ /** Form state every plugin card shares. */
43
+ export interface CardShell {
44
+ /** False while the namespace is not served to this client; the card renders nothing. */
45
+ available: boolean;
46
+ /** Whether the Host document accepts writes. */
47
+ writable: boolean;
48
+ /** Whether the form holds edits that a save would write. */
49
+ dirty: boolean;
50
+ /** Whether any staged draft is invalid, which blocks the save. */
51
+ invalid: boolean;
52
+ /** Whether a save is crossing the wire. */
53
+ saving: boolean;
54
+ /** Whether the last save did not land as staged; cleared by the next edit or save. */
55
+ failed: boolean;
56
+ }
57
+ /** The write actions the card's slot entry injects. */
58
+ export interface CardActions {
59
+ /** Stage draft text for one field. */
60
+ edit: (field: string, text: string) => void;
61
+ /** Stage a clear, so saving lets the field re-inherit the composition layer. */
62
+ resetField: (field: string) => void;
63
+ /** Write every staged edit, then re-seed from what the Host accepted. */
64
+ save: () => void;
65
+ /** Drop every staged edit. */
66
+ discard: () => void;
67
+ }
68
+ /** A whole-number field. An empty draft clears the field; a non-number or out-of-range draft blocks the save. */
69
+ export declare function numberField(field: string, min?: number): CardFieldSpec;
70
+ /** A free-text field. An empty draft clears the field, so emptying the control and saving is the same gesture as resetting it. */
71
+ export declare function textField(field: string): CardFieldSpec;
72
+ /** A boolean field, edited through true/false draft text; an empty draft inherits. */
73
+ export declare function booleanField(field: string): CardFieldSpec;
74
+ /**
75
+ * Stages one card's edits over one settings namespace and writes them on save.
76
+ *
77
+ * The Host is the only authority on whether a value was accepted, so the
78
+ * outcome is read back from the section rather than predicted here. A save
79
+ * that did not land keeps its drafts, so the user can correct them instead of
80
+ * retyping.
81
+ */
82
+ export declare class CardForm<T> {
83
+ private readonly scope;
84
+ private readonly specs;
85
+ private readonly staged;
86
+ private readonly listeners;
87
+ private saving;
88
+ private failed;
89
+ /**
90
+ * @param scope - the bound settings scope for this card's namespace.
91
+ * @param specs - the section fields this card edits.
92
+ */
93
+ constructor(scope: SettingsScope<T>, specs: CardFieldSpec[]);
94
+ /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
95
+ bind<S>(project: () => S, createStore: (init: S) => SnapshotStore<S>): SnapshotStore<S>;
96
+ /** Read the card-level state: what the Host serves, and what a save would do. */
97
+ shell(): CardShell;
98
+ /** Read one field's state from the effective section and its staged draft. */
99
+ field(field: string): CardFieldState;
100
+ /** The actions the card's slot registration injects. */
101
+ actions(): CardActions;
102
+ /**
103
+ * Write every staged edit, then re-seed from what the Host accepted.
104
+ * @returns settlement after every write and the read-back.
105
+ */
106
+ save(): Promise<void>;
107
+ /**
108
+ * Every staged edit a save would write. An entry whose draft is not a value
109
+ * its field accepts carries no write: the form is still dirty, and the save
110
+ * refuses rather than dropping the edit. A staged edit that matches the
111
+ * effective section is not a write at all.
112
+ */
113
+ private plan;
114
+ private clear;
115
+ private store;
116
+ private stage;
117
+ private specOf;
118
+ private sectionValue;
119
+ private baseValue;
120
+ private userLayer;
121
+ private stored;
122
+ private publish;
123
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Styles for the auto-continue settings card, injected at factory
3
+ * materialization so the client module system's style bookkeeping (HMR) owns
4
+ * them. Uses the DSH design tokens (`--dsw-alias-*`) so the card follows the
5
+ * active theme.
6
+ */
7
+ /** Inject the stylesheet once; a no-op outside a browser environment. */
8
+ export declare function injectStyles(): void;
@@ -0,0 +1,59 @@
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.
5
+ */
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ import z from '@deepseek-ai/schemastery';
8
+ /** Settings namespace of the auto-continue plugin (lowercase kebab-case). */
9
+ export declare const AUTO_CONTINUE_NS = "auto-continue";
10
+ /** Wire schema of the auto-continue section; defaults are the plugin's built-in values. */
11
+ export declare const AutoContinueSchema: z<Schemastery.ObjectS<{
12
+ /** Text automatically sent after an interruption. */
13
+ continueText: z<string, string>;
14
+ /** Grace period after an interruption before auto-sending (ms). */
15
+ graceMs: z<number, number>;
16
+ /** Minimum interval between two auto-continues per session (ms). */
17
+ cooldownMs: z<number, number>;
18
+ /** Max consecutive auto-continues per session before stopping. */
19
+ maxConsecutive: z<number, number>;
20
+ /** Scan recently interrupted sessions on page load / reconnect. */
21
+ scanOnBoot: z<boolean, boolean>;
22
+ /** Max sessions the scan checks (most recently updated). */
23
+ scanLimit: z<number, number>;
24
+ /** Scan only considers interruptions inside this window (ms). */
25
+ freshMs: z<number, number>;
26
+ /** Delay before scanning after a reconnect (ms). */
27
+ reconnectScanDelayMs: z<number, number>;
28
+ /** SSE reconnect backoff (ms). */
29
+ reconnectBackoffMs: z<number, number>;
30
+ /** Log `[auto-continue]` lines to the browser console. */
31
+ verbose: z<boolean, boolean>;
32
+ }>, Schemastery.ObjectT<{
33
+ /** Text automatically sent after an interruption. */
34
+ continueText: z<string, string>;
35
+ /** Grace period after an interruption before auto-sending (ms). */
36
+ graceMs: z<number, number>;
37
+ /** Minimum interval between two auto-continues per session (ms). */
38
+ cooldownMs: z<number, number>;
39
+ /** Max consecutive auto-continues per session before stopping. */
40
+ maxConsecutive: z<number, number>;
41
+ /** Scan recently interrupted sessions on page load / reconnect. */
42
+ scanOnBoot: z<boolean, boolean>;
43
+ /** Max sessions the scan checks (most recently updated). */
44
+ scanLimit: z<number, number>;
45
+ /** Scan only considers interruptions inside this window (ms). */
46
+ freshMs: z<number, number>;
47
+ /** Delay before scanning after a reconnect (ms). */
48
+ reconnectScanDelayMs: z<number, number>;
49
+ /** SSE reconnect backoff (ms). */
50
+ reconnectBackoffMs: z<number, number>;
51
+ /** Log `[auto-continue]` lines to the browser console. */
52
+ verbose: z<boolean, boolean>;
53
+ }>>;
54
+ /**
55
+ * Plugin body: register the settings namespace when a settings provider is
56
+ * composed. Changes apply live — the browser half observes the scope.
57
+ * @param ctx - host plugin context.
58
+ */
59
+ export declare function apply(ctx: Context): void;
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "dsh-client-auto-continue",
3
+ "description": "DSH Web UI plugin: automatically sends \"继续\" (continue) when a request is interrupted by network errors or other non-human causes",
4
+ "version": "0.2.0",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./client": {
14
+ "types": "./lib/types/client/index.d.ts",
15
+ "default": "./lib/client.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "lib",
21
+ "cordis.patch.yml",
22
+ "README.md",
23
+ "README.zh.md",
24
+ "LICENSE"
25
+ ],
26
+ "dsh": {
27
+ "bundle": {
28
+ "patch": "./cordis.patch.yml"
29
+ },
30
+ "client": {
31
+ "platform": "web",
32
+ "inject": [
33
+ "@deepseek-ai/dsh-client-connection",
34
+ "@deepseek-ai/dsh-client-runtime",
35
+ "@deepseek-ai/dsh-client-locale",
36
+ "@deepseek-ai/dsh-client-ui-settings",
37
+ "@deepseek-ai/dsh-client-ui-settings-plugins"
38
+ ]
39
+ }
40
+ },
41
+ "scripts": {
42
+ "build": "node build.mjs && tsc -p tsconfig.build.json",
43
+ "watch": "node build.mjs --watch",
44
+ "typecheck": "tsc --noEmit",
45
+ "test": "node tests/simulate.mjs",
46
+ "prepublishOnly": "npm run build"
47
+ },
48
+ "keywords": [
49
+ "dsh",
50
+ "dsh-plugin",
51
+ "deepseek-harness",
52
+ "plugin",
53
+ "web-ui",
54
+ "auto-continue"
55
+ ],
56
+ "repository": {
57
+ "type": "git",
58
+ "url": "git+https://github.com/HsiangNianian/dsh-auto-continue.git"
59
+ },
60
+ "homepage": "https://github.com/HsiangNianian/dsh-auto-continue",
61
+ "devDependencies": {
62
+ "esbuild": "^0.25.0",
63
+ "typescript": "~5.8.0",
64
+ "@types/react": "~18.3.1",
65
+ "@deepseek-ai/cordis": "^4.0.1",
66
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
67
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
68
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
69
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
70
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.6",
71
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
72
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
73
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
74
+ "@deepseek-ai/schemastery": "^3.18.1"
75
+ },
76
+ "license": "MIT",
77
+ "peerDependencies": {
78
+ "react": "^18.2.0",
79
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
80
+ "@deepseek-ai/schemastery": "^3.18.1"
81
+ }
82
+ }