@khorsheed/dsh-local-agent-dsh-headless 0.1.0-rc.6

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.
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Serve mode: the resident sub-dsh loop behind the family-internal live-driver
3
+ * wire (see `./wire.ts`). Where the one-shot runner drives a single task and
4
+ * exits, serve mode keeps the tree alive on stdin: each `turn/start` request
5
+ * feeds one followup into the caller-named session's Agent, every session
6
+ * event streams back as a `session/event` notification while it happens, and
7
+ * `session/idle` closes the turn after the log flush. `turn/interrupt`
8
+ * delivers the runtime-level graceful cancel (`Agent.cancel`) that the
9
+ * process boundary cannot express — the process survives and the session
10
+ * stays continuable, which is the live driver's reason to exist.
11
+ * @module @khorsheed/dsh-local-agent-dsh-headless/serve
12
+ */
13
+ import { StringDecoder } from 'node:string_decoder';
14
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
15
+ import { resolveSubDshSelection, loadSubDshAgent, summarizeTurn } from './agent-loader.js';
16
+ import { LIVE_SERVER_NAME, LIVE_WIRE_ERROR_INTERNAL, LIVE_WIRE_ERROR_METHOD_NOT_FOUND, LIVE_WIRE_PROTOCOL_VERSION, } from './wire.js';
17
+ /** Flatten an unknown thrown value for a wire error or stderr line. */
18
+ function messageOf(error) {
19
+ return error instanceof Error ? error.message : String(error);
20
+ }
21
+ /**
22
+ * Run the resident loop until stdin closes or a `shutdown` request arrives.
23
+ * @param ctx - plugin context carrying the loader settle, core services, and
24
+ * the session/event feed.
25
+ * @param io - process-facing effects.
26
+ */
27
+ export async function runServe(ctx, io, model, effort) {
28
+ // Loader siblings mount concurrently; await the complete application before
29
+ // driving Agents so scoped tools and adapters are not half-composed.
30
+ await ctx.get('loader')?.await();
31
+ const agents = ctx.get('agents');
32
+ const sessions = ctx.get('sessions');
33
+ if (agents === undefined || sessions === undefined || ctx.get('agentDefaultModel') === undefined) {
34
+ io.stderr.write('dsh: serve mode requires the agents, sessions, and agentDefaultModel services\n');
35
+ io.exit(1);
36
+ return;
37
+ }
38
+ /** Loaded agents by caller session id; one member per process in practice. */
39
+ const managed = new Map();
40
+ /** Per-session turn serialization: one in-flight turn per session, FIFO. */
41
+ const queues = new Map();
42
+ /**
43
+ * The in-flight round's parent-supplied turn number per session, set while
44
+ * a turn body runs and echoed on its notifications — the parent drops a
45
+ * cancelled round's late unwind instead of mis-settling the next round.
46
+ */
47
+ const activeTurns = new Map();
48
+ let shuttingDown = false;
49
+ let buffer = '';
50
+ const decoder = new StringDecoder('utf8');
51
+ const send = (message) => {
52
+ io.stdout.write(JSON.stringify(message) + '\n');
53
+ };
54
+ const respond = (id, result) => {
55
+ send({ jsonrpc: '2.0', id, result });
56
+ };
57
+ const respondError = (id, message, code = LIVE_WIRE_ERROR_INTERNAL) => {
58
+ send({ jsonrpc: '2.0', id, error: { code, message } });
59
+ };
60
+ const notify = (method, params) => {
61
+ send({ jsonrpc: '2.0', method, params });
62
+ };
63
+ // Push every session event of a managed session the moment it lands; the
64
+ // parent mirrors event-by-event instead of polling the log file. Events
65
+ // outside a round (agent maintenance, load-time scaffolding) carry a null
66
+ // turn tag and the parent ignores them.
67
+ ctx.on('session/event', (session, event) => {
68
+ if (shuttingDown || !managed.has(String(session.id)))
69
+ return;
70
+ const id = String(session.id);
71
+ notify('session/event', {
72
+ sessionId: id,
73
+ turn: activeTurns.get(id) ?? null,
74
+ event: event,
75
+ });
76
+ });
77
+ // This process owns real Agents, so the public scoped stream bus is valid
78
+ // here. The parent presents these frames through the plugin's own channel.
79
+ ctx.on('agent/assistant-stream', ({ agent, frame }) => {
80
+ const sessionId = String(agent.session.id);
81
+ const turn = activeTurns.get(sessionId);
82
+ if (shuttingDown || !managed.has(sessionId) || turn === undefined)
83
+ return;
84
+ notify('session/assistant-stream', { sessionId, turn, frame });
85
+ }, { global: true });
86
+ const debug = process.env['DSH_SERVE_DEBUG'] === '1'
87
+ ? (message) => { io.stderr.write(`dsh-serve: ${message}\n`); }
88
+ : () => { };
89
+ const shutdown = async () => {
90
+ if (shuttingDown)
91
+ return;
92
+ shuttingDown = true;
93
+ debug('shutdown requested');
94
+ for (const handle of managed.values()) {
95
+ await handle.dispose().catch(() => { });
96
+ }
97
+ debug('managed agents disposed; requesting exit');
98
+ io.exit(0);
99
+ // The launcher's bounded shutdown completes with `process.exitCode`, which
100
+ // only takes effect once the event loop drains — our flowing stdin pipe
101
+ // would hold it open forever (a one-shot never listens on stdin, so only
102
+ // serve mode hits this). Detach our stdin handle and arm a bounded
103
+ // self-exit fallback for anything else still holding the loop.
104
+ const stdin = io.stdin;
105
+ stdin.removeAllListeners?.('data');
106
+ stdin.removeAllListeners?.('end');
107
+ stdin.unref?.();
108
+ setTimeout(() => {
109
+ debug('exit fallback fired');
110
+ process.exit(0);
111
+ }, 2_000).unref();
112
+ };
113
+ /** Accept one turn: followup now, outcome later as `session/idle`. */
114
+ const startTurn = (sessionId, text, turn, agent) => {
115
+ const previous = queues.get(sessionId) ?? Promise.resolve();
116
+ const run = previous.then(async () => {
117
+ activeTurns.set(sessionId, turn);
118
+ let reason;
119
+ try {
120
+ const firstSeq = agent.session.seq;
121
+ agent.followup(createUserMessage({
122
+ content: [{ type: 'text', text }],
123
+ source: { kind: 'user' },
124
+ }));
125
+ await agent.whenIdle();
126
+ // Flush BEFORE the idle notification so the parent's file-based
127
+ // reconciliation pass sees the complete round on disk.
128
+ await sessions.flush(agent.session);
129
+ reason = summarizeTurn(agent.session.snapshotEvents(), firstSeq).reason ?? null;
130
+ }
131
+ catch (error) {
132
+ // A turn-level failure must still close the parent's wait.
133
+ reason = { kind: 'error', error: { message: messageOf(error), code: 'UNKNOWN' } };
134
+ }
135
+ const params = { sessionId, turn, reason };
136
+ try {
137
+ notify('session/idle', params);
138
+ }
139
+ finally {
140
+ activeTurns.delete(sessionId);
141
+ }
142
+ });
143
+ queues.set(sessionId, run);
144
+ };
145
+ const handleRequest = async (id, method, params) => {
146
+ switch (method) {
147
+ case 'initialize':
148
+ respond(id, { serverInfo: { name: LIVE_SERVER_NAME }, protocolVersion: LIVE_WIRE_PROTOCOL_VERSION });
149
+ return;
150
+ case 'session/prepare': {
151
+ try {
152
+ await resolveSubDshSelection(ctx, model, effort, true);
153
+ respond(id, { prepared: true });
154
+ }
155
+ catch (error) {
156
+ respondError(id, messageOf(error));
157
+ }
158
+ return;
159
+ }
160
+ case 'turn/start': {
161
+ const sessionId = params['sessionId'];
162
+ const text = params['text'];
163
+ const turn = params['turn'];
164
+ const resume = params['resume'] === true;
165
+ if (typeof sessionId !== 'string' || sessionId === '' || typeof text !== 'string'
166
+ || typeof turn !== 'number' || !Number.isSafeInteger(turn) || turn < 1) {
167
+ respondError(id, 'turn/start requires a sessionId, a text, and a positive integer turn');
168
+ return;
169
+ }
170
+ let handle = managed.get(sessionId);
171
+ if (handle === undefined) {
172
+ try {
173
+ // The launch's `--model` binds every session this resident
174
+ // process hosts: a runtime's model is a process fact, which is
175
+ // exactly why the parent refuses a per-delegation model on the
176
+ // live path.
177
+ handle = await loadSubDshAgent(ctx, {
178
+ ...resume ? { resumeSessionId: sessionId } : { sessionId },
179
+ ...model === undefined ? {} : { model },
180
+ ...effort === undefined ? {} : { effort },
181
+ });
182
+ }
183
+ catch (error) {
184
+ respondError(id, messageOf(error));
185
+ return;
186
+ }
187
+ managed.set(sessionId, handle);
188
+ }
189
+ startTurn(sessionId, text, turn, handle.agent);
190
+ respond(id, { accepted: true });
191
+ return;
192
+ }
193
+ case 'turn/interrupt': {
194
+ const sessionId = params['sessionId'];
195
+ if (typeof sessionId !== 'string') {
196
+ respondError(id, 'turn/interrupt requires a sessionId');
197
+ return;
198
+ }
199
+ const handle = managed.get(sessionId);
200
+ // The official Agent cancel: the active turn aborts, un-started inbox
201
+ // work survives, and the process/session stay continuable.
202
+ handle?.agent.cancel({ kind: 'parent' });
203
+ respond(id, { interrupted: handle !== undefined });
204
+ return;
205
+ }
206
+ case 'shutdown':
207
+ respond(id, {});
208
+ void shutdown();
209
+ return;
210
+ default:
211
+ respondError(id, `unknown method: ${method}`, LIVE_WIRE_ERROR_METHOD_NOT_FOUND);
212
+ }
213
+ };
214
+ io.stdin.on('data', (chunk) => {
215
+ if (shuttingDown)
216
+ return;
217
+ // A multi-byte UTF-8 sequence can straddle two chunks; the StringDecoder
218
+ // holds the partial tail instead of corrupting it into U+FFFD.
219
+ const text = typeof chunk === 'string'
220
+ ? chunk
221
+ : decoder.write(chunk);
222
+ buffer += text;
223
+ let index = buffer.indexOf('\n');
224
+ while (index >= 0) {
225
+ const line = buffer.slice(0, index);
226
+ buffer = buffer.slice(index + 1);
227
+ index = buffer.indexOf('\n');
228
+ if (line.trim() === '')
229
+ continue;
230
+ let message;
231
+ try {
232
+ message = JSON.parse(line);
233
+ }
234
+ catch {
235
+ io.stderr.write('dsh: serve ignored a malformed wire line\n');
236
+ continue;
237
+ }
238
+ // Only requests carry an id; the parent sends no notifications.
239
+ if ((typeof message.id === 'number' || typeof message.id === 'string') && typeof message.method === 'string') {
240
+ const id = Number(message.id);
241
+ void handleRequest(id, message.method, (message.params ?? {})).catch((error) => {
242
+ respondError(id, messageOf(error));
243
+ });
244
+ }
245
+ }
246
+ });
247
+ // The parent closing the pipe IS the shutdown request (reclaim path).
248
+ io.stdin.on('end', () => { void shutdown(); });
249
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The sub-dsh one-shot app's command-line provider: it parses the task
3
+ * positional, the caller-supplied `--session-id` (fresh delegation) and
4
+ * `--resume` (continuation) flags, and `--help`, then publishes
5
+ * {@link LOCAL_AGENT_DSH_HEADLESS_STARTUP_SERVICE}. The runner is an ordinary
6
+ * consumer whose lazy config waits for that service. The session id is
7
+ * caller-supplied — the parent provider generates one uuid and passes the same
8
+ * value to `agents.create`/`agents.resume` on the sub-dsh side, so stdout
9
+ * never carries a parseable session marker.
10
+ * @module @khorsheed/dsh-local-agent-dsh-headless/startup
11
+ */
12
+ import type { Context } from '@deepseek-ai/cordis';
13
+ /** Stable Cordis plugin name. */
14
+ export declare const name = "local-agent-dsh-headless-startup";
15
+ /** Services required before the invocation can be resolved. */
16
+ export declare const inject: string[];
17
+ /** Service provided by this plugin and injected by the one-shot runner. */
18
+ export declare const LOCAL_AGENT_DSH_HEADLESS_STARTUP_SERVICE = "localAgentDshHeadlessStartup";
19
+ /** What the runner row reads from {@link LOCAL_AGENT_DSH_HEADLESS_STARTUP_SERVICE}. */
20
+ export interface LocalAgentDshHeadlessStartupValues {
21
+ /** The task text this invocation asked for; empty in serve mode. */
22
+ task: string;
23
+ /** Fresh delegation: create a session with this exact id. */
24
+ sessionId?: string;
25
+ /** Continuation: resume the existing session with this id. */
26
+ resumeSessionId?: string;
27
+ /** Resident mode: drive turns over the stdio wire, never exiting on idle. */
28
+ serve?: boolean;
29
+ /**
30
+ * The model this launch runs, spelled `provider/model` (a bare id names the
31
+ * model and keeps the instance's provider). Absent means the instance's own
32
+ * default model selection, unchanged. Under `--serve` it applies to every
33
+ * session the resident process hosts — a resident runtime binds its model
34
+ * at spawn, which is why the parent refuses a per-delegation model there.
35
+ */
36
+ model?: string;
37
+ effort?: string;
38
+ }
39
+ /**
40
+ * Parse and provide the one-shot invocation as an ordinary Cordis service.
41
+ * The command's action publishes the invocation; a missing or whitespace-only
42
+ * task, or both session flags together, is a usage error, so on rejection (and
43
+ * on `--help`) nothing is provided.
44
+ * @param ctx - plugin context carrying the command line.
45
+ */
46
+ export declare function apply(ctx: Context): void;
47
+ //# sourceMappingURL=startup.d.ts.map
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The sub-dsh one-shot app's command-line provider: it parses the task
3
+ * positional, the caller-supplied `--session-id` (fresh delegation) and
4
+ * `--resume` (continuation) flags, and `--help`, then publishes
5
+ * {@link LOCAL_AGENT_DSH_HEADLESS_STARTUP_SERVICE}. The runner is an ordinary
6
+ * consumer whose lazy config waits for that service. The session id is
7
+ * caller-supplied — the parent provider generates one uuid and passes the same
8
+ * value to `agents.create`/`agents.resume` on the sub-dsh side, so stdout
9
+ * never carries a parseable session marker.
10
+ * @module @khorsheed/dsh-local-agent-dsh-headless/startup
11
+ */
12
+ import { Command } from 'commander';
13
+ import { parseCmdline } from '@deepseek-ai/dsh-cmdline';
14
+ /** Stable Cordis plugin name. */
15
+ export const name = 'local-agent-dsh-headless-startup';
16
+ /** Services required before the invocation can be resolved. */
17
+ export const inject = ['cmdlineArgs'];
18
+ /** Service provided by this plugin and injected by the one-shot runner. */
19
+ export const LOCAL_AGENT_DSH_HEADLESS_STARTUP_SERVICE = 'localAgentDshHeadlessStartup';
20
+ /**
21
+ * This app's command: the session flags, the task positional, and help text.
22
+ * @returns a fresh program, so one process can parse more than once (tests).
23
+ */
24
+ function headlessCommand() {
25
+ return new Command()
26
+ .name('dsh --profile headless-local-agent-dsh')
27
+ .description('Answer one task in a caller-named session, print the final assistant message, and exit.')
28
+ .helpOption('-h, --help', 'show this help')
29
+ .option('--session-id <id>', 'create a fresh session with exactly this id')
30
+ .option('--resume <id>', 'continue the existing session with this id')
31
+ .option('--serve', 'stay resident and drive turns over the stdio wire (no task)')
32
+ .option('--model <provider/model>', 'run this model instead of the instance default (a bare id keeps the provider)')
33
+ .option('--effort <value>', 'native reasoning effort for this launch')
34
+ .argument('[task...]', 'the task text; multiple words are joined by spaces')
35
+ .addHelpText('after', `
36
+ Examples:
37
+ dsh --profile headless-local-agent-dsh --session-id 6ba7... "run the tests" create one session and answer
38
+ dsh --profile headless-local-agent-dsh --resume 6ba7... "run the rest" continue the same session
39
+ dsh --profile headless-local-agent-dsh --serve resident live-driver mode (stdio wire)
40
+ dsh --profile headless-local-agent-dsh --model deepseek-official/deepseek-v4-pro --session-id 6ba7... "run the tests"
41
+ `);
42
+ }
43
+ /**
44
+ * Parse and provide the one-shot invocation as an ordinary Cordis service.
45
+ * The command's action publishes the invocation; a missing or whitespace-only
46
+ * task, or both session flags together, is a usage error, so on rejection (and
47
+ * on `--help`) nothing is provided.
48
+ * @param ctx - plugin context carrying the command line.
49
+ */
50
+ export function apply(ctx) {
51
+ const program = headlessCommand();
52
+ program.action(() => {
53
+ const task = program.args.join(' ');
54
+ // Commander camelizes `--session-id` to `sessionId` but keeps `--resume`
55
+ // as `resume`; map both onto the service's explicit field names.
56
+ const options = program.opts();
57
+ // `--model` is orthogonal to the session flags: it names WHICH model runs,
58
+ // not which session, so it rides both modes.
59
+ const effort = options.effort?.trim();
60
+ if (effort === '')
61
+ program.error('error: --effort needs a native reasoning value');
62
+ const model = options.model?.trim();
63
+ if (model !== undefined && model === '') {
64
+ program.error('error: --model needs a model identifier, for example deepseek-official/deepseek-v4-pro');
65
+ }
66
+ if (options.serve === true) {
67
+ if (options.sessionId !== undefined || options.resume !== undefined) {
68
+ program.error('error: --serve drives sessions over the wire; --session-id/--resume do not apply');
69
+ }
70
+ if (task.trim() !== '') {
71
+ program.error('error: --serve takes no task; turns arrive over the wire');
72
+ }
73
+ ctx.provide(LOCAL_AGENT_DSH_HEADLESS_STARTUP_SERVICE, {
74
+ task: '',
75
+ serve: true,
76
+ ...(model === undefined ? {} : { model }),
77
+ ...(effort === undefined ? {} : { effort }),
78
+ });
79
+ return;
80
+ }
81
+ if (task.trim() === '') {
82
+ program.error('error: a task is required, for example: dsh --profile headless-local-agent-dsh --session-id <id> "run the tests"');
83
+ }
84
+ if (options.sessionId !== undefined && options.resume !== undefined) {
85
+ program.error('error: --session-id and --resume are mutually exclusive');
86
+ }
87
+ ctx.provide(LOCAL_AGENT_DSH_HEADLESS_STARTUP_SERVICE, {
88
+ task,
89
+ ...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
90
+ ...(options.resume !== undefined ? { resumeSessionId: options.resume } : {}),
91
+ ...(model === undefined ? {} : { model }),
92
+ ...(effort === undefined ? {} : { effort }),
93
+ });
94
+ });
95
+ parseCmdline(ctx, program);
96
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The family-internal live-driver wire between the `local-agent-dsh` provider
3
+ * (parent side) and this bundle's serve mode (the resident sub-dsh). Newline-
4
+ * delimited JSON-RPC 2.0 over the sub-dsh's stdio, framed exactly like the
5
+ * official `JsonRpcLineTransport` (`@deepseek-ai/dsh-sdk-protocol`) so a
6
+ * future swap to the official SDK server stays mechanical. The official
7
+ * server is NOT usable today: its wire has no turn-level interrupt
8
+ * (`initialize` / `session/prompt` / `shutdown` only), and graceful interrupt
9
+ * is the live driver's core win — serve mode drives the in-process
10
+ * `Agent.cancel` instead (upstream seam registry, SDK-wire-interrupt entry).
11
+ *
12
+ * One resident process serves one member (one session). All requests are
13
+ * quick acknowledgements; the turn's events and terminal outcome flow back as
14
+ * notifications, never as the request response, so a long turn never holds a
15
+ * request open.
16
+ * @module @khorsheed/dsh-local-agent-dsh-headless/wire
17
+ */
18
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
19
+ import type { AssistantStreamFrame } from '@deepseek-ai/dsh-agent';
20
+ /** Additive transient notification; old clients safely ignore unknown methods. */
21
+ export interface LiveAssistantStreamParams {
22
+ readonly sessionId: string;
23
+ readonly turn: number;
24
+ readonly frame: AssistantStreamFrame;
25
+ }
26
+ /** Wire contract version; bumped on any incompatible shape change. */
27
+ export declare const LIVE_WIRE_PROTOCOL_VERSION = 1;
28
+ /** The serve mode's identity, reported by the `initialize` handshake. */
29
+ export declare const LIVE_SERVER_NAME = "@khorsheed/dsh-local-agent-dsh-headless/serve";
30
+ /** A wire request (parent → sub-dsh). */
31
+ export interface LiveWireRequest {
32
+ readonly jsonrpc: '2.0';
33
+ readonly id: number;
34
+ readonly method: string;
35
+ readonly params?: Record<string, unknown>;
36
+ }
37
+ /** A wire response (sub-dsh → parent): exactly one of result/error. */
38
+ export interface LiveWireResponse {
39
+ readonly jsonrpc: '2.0';
40
+ readonly id: number;
41
+ readonly result?: unknown;
42
+ readonly error?: {
43
+ readonly code: number;
44
+ readonly message: string;
45
+ };
46
+ }
47
+ /** A wire notification (sub-dsh → parent). */
48
+ export interface LiveWireNotification {
49
+ readonly jsonrpc: '2.0';
50
+ readonly method: string;
51
+ readonly params?: Record<string, unknown>;
52
+ }
53
+ /** `initialize` result: the handshake proving the channel speaks this wire. */
54
+ export interface LiveInitializeResult {
55
+ readonly serverInfo: {
56
+ readonly name: string;
57
+ };
58
+ readonly protocolVersion: number;
59
+ }
60
+ /** `turn/start` params: drive one turn of the named session. */
61
+ export interface LiveTurnStartParams {
62
+ /** Caller-supplied session id (the parent-side child session id). */
63
+ readonly sessionId: string;
64
+ /** The turn's prompt text (the family's text-only task contract). */
65
+ readonly text: string;
66
+ /** True on a resume round: load the existing session, never create it. */
67
+ readonly resume: boolean;
68
+ /**
69
+ * The parent's delegation-round number (fresh = 1, each resume +1). The
70
+ * serve side echoes it on every `session/event` / `session/idle` of this
71
+ * turn, so the parent can drop a cancelled round's late unwind
72
+ * notifications instead of letting them mis-settle the next round.
73
+ */
74
+ readonly turn: number;
75
+ }
76
+ /** `turn/start` result: the turn was accepted; its outcome arrives as `session/idle`. */
77
+ export interface LiveTurnStartResult {
78
+ readonly accepted: true;
79
+ }
80
+ /** `turn/interrupt` params: gracefully cancel the named session's active turn. */
81
+ export interface LiveTurnInterruptParams {
82
+ readonly sessionId: string;
83
+ }
84
+ /** `turn/interrupt` result: whether the session is loaded in this runtime. */
85
+ export interface LiveTurnInterruptResult {
86
+ readonly interrupted: boolean;
87
+ }
88
+ /** The sub-dsh turn's terminal reason, mirrored from its `turn/end` event. */
89
+ export type LiveTurnReason = SessionEvent<'turn/end'>['data']['reason'];
90
+ /** `session/event` notification params: one live session event, in order. */
91
+ export interface LiveSessionEventParams {
92
+ readonly sessionId: string;
93
+ /**
94
+ * The round this event belongs to (the echo of `turn/start`'s `turn`), or
95
+ * null when the event landed outside any round — the parent mirrors only
96
+ * its own round's events.
97
+ */
98
+ readonly turn: number | null;
99
+ readonly event: SessionEvent;
100
+ }
101
+ /**
102
+ * `session/idle` notification params: the session's active turn settled and
103
+ * its session log was flushed to disk, so a file-based reconciliation pass
104
+ * right after this notification sees the complete round.
105
+ */
106
+ export interface LiveSessionIdleParams {
107
+ readonly sessionId: string;
108
+ /** The round this idle closes (the echo of `turn/start`'s `turn`). */
109
+ readonly turn: number;
110
+ /** Null when the owned interval recorded no turn (a broken turn). */
111
+ readonly reason: LiveTurnReason | null;
112
+ }
113
+ /** JSON-RPC error codes this wire uses. */
114
+ export declare const LIVE_WIRE_ERROR_METHOD_NOT_FOUND = -32601;
115
+ export declare const LIVE_WIRE_ERROR_INTERNAL = -32603;
116
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The family-internal live-driver wire between the `local-agent-dsh` provider
3
+ * (parent side) and this bundle's serve mode (the resident sub-dsh). Newline-
4
+ * delimited JSON-RPC 2.0 over the sub-dsh's stdio, framed exactly like the
5
+ * official `JsonRpcLineTransport` (`@deepseek-ai/dsh-sdk-protocol`) so a
6
+ * future swap to the official SDK server stays mechanical. The official
7
+ * server is NOT usable today: its wire has no turn-level interrupt
8
+ * (`initialize` / `session/prompt` / `shutdown` only), and graceful interrupt
9
+ * is the live driver's core win — serve mode drives the in-process
10
+ * `Agent.cancel` instead (upstream seam registry, SDK-wire-interrupt entry).
11
+ *
12
+ * One resident process serves one member (one session). All requests are
13
+ * quick acknowledgements; the turn's events and terminal outcome flow back as
14
+ * notifications, never as the request response, so a long turn never holds a
15
+ * request open.
16
+ * @module @khorsheed/dsh-local-agent-dsh-headless/wire
17
+ */
18
+ /** Wire contract version; bumped on any incompatible shape change. */
19
+ export const LIVE_WIRE_PROTOCOL_VERSION = 1;
20
+ /** The serve mode's identity, reported by the `initialize` handshake. */
21
+ export const LIVE_SERVER_NAME = '@khorsheed/dsh-local-agent-dsh-headless/serve';
22
+ /** JSON-RPC error codes this wire uses. */
23
+ export const LIVE_WIRE_ERROR_METHOD_NOT_FOUND = -32601;
24
+ export const LIVE_WIRE_ERROR_INTERNAL = -32603;
package/lib/wire.js ADDED
@@ -0,0 +1,27 @@
1
+ //#region lib/types/wire.js
2
+ /**
3
+ * The family-internal live-driver wire between the `local-agent-dsh` provider
4
+ * (parent side) and this bundle's serve mode (the resident sub-dsh). Newline-
5
+ * delimited JSON-RPC 2.0 over the sub-dsh's stdio, framed exactly like the
6
+ * official `JsonRpcLineTransport` (`@deepseek-ai/dsh-sdk-protocol`) so a
7
+ * future swap to the official SDK server stays mechanical. The official
8
+ * server is NOT usable today: its wire has no turn-level interrupt
9
+ * (`initialize` / `session/prompt` / `shutdown` only), and graceful interrupt
10
+ * is the live driver's core win — serve mode drives the in-process
11
+ * `Agent.cancel` instead (upstream seam registry, SDK-wire-interrupt entry).
12
+ *
13
+ * One resident process serves one member (one session). All requests are
14
+ * quick acknowledgements; the turn's events and terminal outcome flow back as
15
+ * notifications, never as the request response, so a long turn never holds a
16
+ * request open.
17
+ * @module @khorsheed/dsh-local-agent-dsh-headless/wire
18
+ */
19
+ /** Wire contract version; bumped on any incompatible shape change. */
20
+ const LIVE_WIRE_PROTOCOL_VERSION = 1;
21
+ /** The serve mode's identity, reported by the `initialize` handshake. */
22
+ const LIVE_SERVER_NAME = "@khorsheed/dsh-local-agent-dsh-headless/serve";
23
+ /** JSON-RPC error codes this wire uses. */
24
+ const LIVE_WIRE_ERROR_METHOD_NOT_FOUND = -32601;
25
+ const LIVE_WIRE_ERROR_INTERNAL = -32603;
26
+ //#endregion
27
+ export { LIVE_SERVER_NAME, LIVE_WIRE_ERROR_INTERNAL, LIVE_WIRE_ERROR_METHOD_NOT_FOUND, LIVE_WIRE_PROTOCOL_VERSION };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@khorsheed/dsh-local-agent-dsh-headless",
3
+ "description": "Sub-dsh headless bundle for the local-agent-dsh harness: a direct Agent/Session runner over dsh-base — one-shot (caller-supplied --session-id/--resume, prints the final assistant text, exits) or resident (--serve) driving turns over the family stdio wire",
4
+ "keywords": [
5
+ "dsh",
6
+ "dsh-plugin",
7
+ "cordis",
8
+ "local-agent",
9
+ "headless"
10
+ ],
11
+ "version": "0.1.0-rc.6",
12
+ "type": "module",
13
+ "main": "lib/index.js",
14
+ "types": "lib/types/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./lib/types/index.d.ts",
18
+ "default": "./lib/index.js"
19
+ },
20
+ "./startup": {
21
+ "types": "./lib/types/startup.d.ts",
22
+ "default": "./lib/startup.js"
23
+ },
24
+ "./wire": {
25
+ "types": "./lib/types/wire.d.ts",
26
+ "default": "./lib/wire.js"
27
+ },
28
+ "./invariant": {
29
+ "types": "./lib/types/invariant.d.ts",
30
+ "default": "./lib/invariant.js"
31
+ },
32
+ "./src/*": "./src/*",
33
+ "./cordis.patch.yml": "./cordis.patch.yml",
34
+ "./package.json": "./package.json"
35
+ },
36
+ "dsh": {
37
+ "composition": {
38
+ "component": "sub-profile-patch"
39
+ },
40
+ "compat": {
41
+ "minHost": "0.1.5-rc.1",
42
+ "verifiedHost": "0.1.5-rc.1"
43
+ }
44
+ },
45
+ "files": [
46
+ "lib",
47
+ "cordis.patch.yml"
48
+ ],
49
+ "license": "MIT",
50
+ "peerDependencies": {
51
+ "@deepseek-ai/cordis": "^4.0.1",
52
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
53
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
54
+ "@deepseek-ai/dsh-agent-default-model": "^0.1.0-rc.6",
55
+ "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
56
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
57
+ "@deepseek-ai/dsh-mcp-client": "^0.1.0-rc.8",
58
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "@deepseek-ai/dsh-mcp-client": {
62
+ "optional": true
63
+ }
64
+ }
65
+ }