@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,85 @@
1
+ /**
2
+ * Agent loading and turn aggregation shared by the one-shot runner and the
3
+ * serve loop: both drive a direct Agent over the core registry with the same
4
+ * model selection, the same scoped installModelSelection setup, the same
5
+ * preset join, and the same caller-supplied session identity, differing only
6
+ * in process lifetime.
7
+ * @module @khorsheed/dsh-local-agent-dsh-headless/agent-loader
8
+ */
9
+ import type { Context } from '@deepseek-ai/cordis';
10
+ import type { AgentHandle, ModelSelection } from '@deepseek-ai/dsh-agent';
11
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
12
+ /** Outcome of one owned turn interval. */
13
+ export interface TurnOutcome {
14
+ text: string;
15
+ reason: SessionEvent<'turn/end'>['data']['reason'] | undefined;
16
+ }
17
+ /**
18
+ * Aggregate the last assistant text and turn outcome in one owned interval.
19
+ * @param events - the agent session's full event log.
20
+ * @param firstSeq - the session seq at which this driver's interval opened.
21
+ */
22
+ export declare function summarizeTurn(events: readonly SessionEvent[], firstSeq: number): TurnOutcome;
23
+ /**
24
+ * Apply a `--model` request onto the instance's default selection.
25
+ *
26
+ * The request is spelled `provider/model` — the same shape
27
+ * `effectiveSettings.model` reports the configured model in — and splits at
28
+ * the FIRST slash, so a provider-qualified id survives a model id that
29
+ * contains none. A request with no slash names the MODEL only and keeps the
30
+ * default selection's provider, which is what a caller switching between two
31
+ * models of the same route means.
32
+ *
33
+ * Everything else on the selection is carried through untouched (the
34
+ * reasoning effort in particular): `--model` changes the model, not the rest
35
+ * of the instance's configuration. An absent or blank request returns the
36
+ * default selection unchanged — byte for byte the behavior before the flag
37
+ * existed.
38
+ * @param selection - the instance's default model selection.
39
+ * @param request - the `--model` value, when the launch carried one.
40
+ * @returns the selection this launch runs.
41
+ */
42
+ export declare function applyModelRequest(selection: ModelSelection, request: string | undefined): ModelSelection;
43
+ /**
44
+ * Join this agent to the sub-profile's preset, when the sub-profile composes
45
+ * a roster.
46
+ *
47
+ * Without a roster this is a no-op and the agent reads the model-facing rows
48
+ * off the global layer — byte for byte the behavior of every sub-dsh before
49
+ * the roster layer existed, and the right answer for a rosterless
50
+ * composition (`@deepseek-ai/dsh-agent-presets` README, "Composing a child
51
+ * agent"). With one, the preset decides the tools, prompt sections and
52
+ * skills of every session this process hosts, which is what makes the
53
+ * scope's capability face a per-condition factor instead of an instance-wide
54
+ * constant.
55
+ *
56
+ * A roster that REFUSES (unknown preset id, broken composition) is not
57
+ * degraded past: mounting rejects, the agent creation rolls back, and the
58
+ * launch fails naming the preset. A sub-dsh that silently ran the global
59
+ * layer after being told to run preset X would attribute the round to a
60
+ * capability face it never had.
61
+ * @param ctx - plugin context (where the roster service is resolved).
62
+ * @param agentCtx - the agent's scope context, which joins the standing mount.
63
+ * @returns the preset id joined, or undefined when the composition has no roster.
64
+ */
65
+ export declare function joinSubDshPreset(ctx: Context, agentCtx: Context): Promise<string | undefined>;
66
+ /**
67
+ * Create or resume the direct Agent for one caller-named session. The caller
68
+ * resolved the core services first (both call sites bail out on a disposed
69
+ * tree). The caller-supplied id is authoritative for both branches: the
70
+ * parent provider generated one uuid and passes the same value on every round
71
+ * of the same delegation, so resume finds exactly the sub-dsh session the
72
+ * fresh round created.
73
+ * @param ctx - plugin context carrying the Agent registry and default model.
74
+ * @param identity - fresh (`sessionId`) or continuation (`resumeSessionId`).
75
+ * @returns the owned agent handle (its disposer unwinds the agent's world).
76
+ */
77
+ export declare function loadSubDshAgent(ctx: Context, identity: {
78
+ sessionId?: string;
79
+ resumeSessionId?: string;
80
+ model?: string;
81
+ effort?: string;
82
+ }): Promise<AgentHandle>;
83
+ /** Validate the actual sub-instance's configuration without creating a session or generating. */
84
+ export declare function resolveSubDshSelection(ctx: Context, model?: string, effort?: string, validateModel?: boolean): Promise<ModelSelection>;
85
+ //# sourceMappingURL=agent-loader.d.ts.map
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Agent loading and turn aggregation shared by the one-shot runner and the
3
+ * serve loop: both drive a direct Agent over the core registry with the same
4
+ * model selection, the same scoped installModelSelection setup, the same
5
+ * preset join, and the same caller-supplied session identity, differing only
6
+ * in process lifetime.
7
+ * @module @khorsheed/dsh-local-agent-dsh-headless/agent-loader
8
+ */
9
+ import { randomUUID } from 'node:crypto';
10
+ import { installModelSelection } from '@deepseek-ai/dsh-agent';
11
+ import { SessionId } from '@deepseek-ai/dsh-session';
12
+ /**
13
+ * Aggregate the last assistant text and turn outcome in one owned interval.
14
+ * @param events - the agent session's full event log.
15
+ * @param firstSeq - the session seq at which this driver's interval opened.
16
+ */
17
+ export function summarizeTurn(events, firstSeq) {
18
+ let started = false;
19
+ let text = '';
20
+ let reason;
21
+ for (const event of events) {
22
+ if (event.seq < firstSeq)
23
+ continue;
24
+ if (event.type === 'turn/start') {
25
+ started = true;
26
+ continue;
27
+ }
28
+ if (!started)
29
+ continue;
30
+ if (event.type === 'assistant/message') {
31
+ const joined = event.data.message.content
32
+ .filter(block => block.type === 'text')
33
+ .map(block => block.text)
34
+ .join('');
35
+ if (joined !== '')
36
+ text = joined;
37
+ }
38
+ if (event.type === 'turn/end')
39
+ reason = event.data.reason;
40
+ }
41
+ return { text, reason };
42
+ }
43
+ /**
44
+ * Apply a `--model` request onto the instance's default selection.
45
+ *
46
+ * The request is spelled `provider/model` — the same shape
47
+ * `effectiveSettings.model` reports the configured model in — and splits at
48
+ * the FIRST slash, so a provider-qualified id survives a model id that
49
+ * contains none. A request with no slash names the MODEL only and keeps the
50
+ * default selection's provider, which is what a caller switching between two
51
+ * models of the same route means.
52
+ *
53
+ * Everything else on the selection is carried through untouched (the
54
+ * reasoning effort in particular): `--model` changes the model, not the rest
55
+ * of the instance's configuration. An absent or blank request returns the
56
+ * default selection unchanged — byte for byte the behavior before the flag
57
+ * existed.
58
+ * @param selection - the instance's default model selection.
59
+ * @param request - the `--model` value, when the launch carried one.
60
+ * @returns the selection this launch runs.
61
+ */
62
+ export function applyModelRequest(selection, request) {
63
+ const wanted = request?.trim();
64
+ if (wanted === undefined || wanted === '')
65
+ return selection;
66
+ const slash = wanted.indexOf('/');
67
+ if (slash <= 0 || slash === wanted.length - 1)
68
+ return { ...selection, model: wanted };
69
+ return { ...selection, provider: wanted.slice(0, slash), model: wanted.slice(slash + 1) };
70
+ }
71
+ /**
72
+ * Join this agent to the sub-profile's preset, when the sub-profile composes
73
+ * a roster.
74
+ *
75
+ * Without a roster this is a no-op and the agent reads the model-facing rows
76
+ * off the global layer — byte for byte the behavior of every sub-dsh before
77
+ * the roster layer existed, and the right answer for a rosterless
78
+ * composition (`@deepseek-ai/dsh-agent-presets` README, "Composing a child
79
+ * agent"). With one, the preset decides the tools, prompt sections and
80
+ * skills of every session this process hosts, which is what makes the
81
+ * scope's capability face a per-condition factor instead of an instance-wide
82
+ * constant.
83
+ *
84
+ * A roster that REFUSES (unknown preset id, broken composition) is not
85
+ * degraded past: mounting rejects, the agent creation rolls back, and the
86
+ * launch fails naming the preset. A sub-dsh that silently ran the global
87
+ * layer after being told to run preset X would attribute the round to a
88
+ * capability face it never had.
89
+ * @param ctx - plugin context (where the roster service is resolved).
90
+ * @param agentCtx - the agent's scope context, which joins the standing mount.
91
+ * @returns the preset id joined, or undefined when the composition has no roster.
92
+ */
93
+ export async function joinSubDshPreset(ctx, agentCtx) {
94
+ const roster = ctx.get('agentPresets');
95
+ if (roster?.mount === undefined)
96
+ return undefined;
97
+ return (await roster.mount(agentCtx)).id;
98
+ }
99
+ /**
100
+ * Create or resume the direct Agent for one caller-named session. The caller
101
+ * resolved the core services first (both call sites bail out on a disposed
102
+ * tree). The caller-supplied id is authoritative for both branches: the
103
+ * parent provider generated one uuid and passes the same value on every round
104
+ * of the same delegation, so resume finds exactly the sub-dsh session the
105
+ * fresh round created.
106
+ * @param ctx - plugin context carrying the Agent registry and default model.
107
+ * @param identity - fresh (`sessionId`) or continuation (`resumeSessionId`).
108
+ * @returns the owned agent handle (its disposer unwinds the agent's world).
109
+ */
110
+ export async function loadSubDshAgent(ctx, identity) {
111
+ const agents = ctx.get('agents');
112
+ if (agents === undefined)
113
+ throw new Error('local-agent-dsh-headless: the agents service is not mounted');
114
+ const selection = await resolveSubDshSelection(ctx, identity.model, identity.effort);
115
+ // This bundle composes no preset roster, so the model-facing rows sit in the
116
+ // host plane and the agent reads them from the global layer. A deployment
117
+ // that DOES configure one has to join it here first
118
+ // (@deepseek-ai/dsh-agent-presets README, "Composing a child agent").
119
+ const agentOptions = { provider: selection.provider, model: selection.model };
120
+ const setup = async (agentCtx) => {
121
+ const selected = { current: selection, assembled: undefined };
122
+ // The released installModelSelection registers the two scoped waterfall
123
+ // listeners and returns their disposer; the run owns the whole process
124
+ // lifetime, so the disposer is deliberately dropped.
125
+ installModelSelection(agentCtx, selected);
126
+ // The preset join happens INSIDE setup, before the agent is published:
127
+ // the roster parents this agent's scope key to the preset's standing
128
+ // mount, so the preset's tools and prompt sections exist before the first
129
+ // prompt assembly. A rejection here rolls the creation back.
130
+ await joinSubDshPreset(ctx, agentCtx);
131
+ };
132
+ if (identity.resumeSessionId !== undefined) {
133
+ return agents.resume({
134
+ resumeSessionId: SessionId(identity.resumeSessionId),
135
+ agentOptions,
136
+ setup,
137
+ });
138
+ }
139
+ return agents.create({
140
+ sessionId: SessionId(identity.sessionId ?? `session-${randomUUID()}`),
141
+ meta: { cwd: process.cwd() },
142
+ agentOptions,
143
+ setup,
144
+ });
145
+ }
146
+ /** Validate the actual sub-instance's configuration without creating a session or generating. */
147
+ export async function resolveSubDshSelection(ctx, model, effort, validateModel = false) {
148
+ const defaults = ctx.get('agentDefaultModel');
149
+ if (defaults === undefined)
150
+ throw new Error('local-agent-dsh-headless: agentDefaultModel is not mounted');
151
+ const selection = applyModelRequest(defaults.currentSelection(), model);
152
+ if (effort === undefined && !validateModel)
153
+ return selection;
154
+ let timer;
155
+ try {
156
+ const info = await Promise.race([
157
+ ctx.get('llm')?.resolveModelInfo(selection.provider, selection.model, AbortSignal.timeout(5_000)),
158
+ new Promise((_resolve, reject) => { timer = setTimeout(() => reject(new Error('Sub-DSH model validation timed out')), 5_000); timer.unref(); }),
159
+ ]);
160
+ if (info === undefined)
161
+ throw new Error('The sub-DSH adapter cannot resolve the requested model');
162
+ if (effort === undefined)
163
+ return selection;
164
+ const native = info.reasoning?.efforts.find(option => String(option.id) === effort);
165
+ if (native === undefined)
166
+ throw new Error('The sub-DSH model does not advertise the requested reasoning effort');
167
+ return { ...selection, reasoningEffort: native.id };
168
+ }
169
+ finally {
170
+ if (timer !== undefined)
171
+ clearTimeout(timer);
172
+ }
173
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @khorsheed/dsh-local-agent-dsh-headless — sub-dsh direct Agent driver with
3
+ * two modes. One-shot (default): create one Agent through the core registry
4
+ * on a caller-supplied session id (or resume one), drive the task to
5
+ * quiescence, flush its Session, print the final assistant text, and exit.
6
+ * Serve (`--serve`): stay resident and drive turns over the family-internal
7
+ * live-driver wire (see `./serve.ts` / `./wire.ts`). The session id comes
8
+ * from the invocation (`--session-id` / `--resume`) or the wire, never from
9
+ * stdout — the parent provider generates one uuid and both sides use it, so
10
+ * stdout carries no parseable session marker.
11
+ *
12
+ * @module @khorsheed/dsh-local-agent-dsh-headless
13
+ */
14
+ import type { Context } from '@deepseek-ai/cordis';
15
+ import z from '@deepseek-ai/schemastery';
16
+ /** Stable Cordis plugin name. */
17
+ export declare const name = "local-agent-dsh-headless-runner";
18
+ /** Core services required before the one-shot turn can start. */
19
+ export declare const inject: string[];
20
+ /** Plugin config: the one-shot task and caller session identity, or serve mode. */
21
+ export interface Config {
22
+ /** The prompt text for the single run; unused in serve mode. */
23
+ task?: string;
24
+ /** Fresh delegation: create a session with exactly this id. */
25
+ sessionId?: string;
26
+ /** Continuation: resume the existing session with this id. */
27
+ resumeSessionId?: string;
28
+ /** Resident mode: drive turns over the stdio wire instead of one task. */
29
+ serve?: boolean;
30
+ /**
31
+ * The model this launch runs (`--model`), spelled `provider/model`; a bare
32
+ * id names the model and keeps the instance's provider. Absent means the
33
+ * instance's default selection, unchanged. Under `serve` it binds every
34
+ * session the resident process hosts.
35
+ */
36
+ model?: string;
37
+ effort?: string;
38
+ }
39
+ export declare const Config: z<Config>;
40
+ /** Process-facing effects of one run: output streams plus the launcher's bounded exit request. */
41
+ interface HeadlessIo {
42
+ stdin: {
43
+ on(event: 'data', listener: (chunk: unknown) => void): unknown;
44
+ on(event: 'end', listener: () => void): unknown;
45
+ };
46
+ stdout: {
47
+ write(chunk: string): unknown;
48
+ };
49
+ stderr: {
50
+ write(chunk: string): unknown;
51
+ };
52
+ /** Request process exit with `code` after the tree disposes. */
53
+ exit(code: number): void;
54
+ }
55
+ /** The process streams the runner writes to; tests substitute captures. */
56
+ export declare const internals: {
57
+ stdin: HeadlessIo['stdin'];
58
+ stdout: HeadlessIo['stdout'];
59
+ stderr: HeadlessIo['stderr'];
60
+ };
61
+ /**
62
+ * Mount the direct driver: one-shot by default, the resident serve loop when
63
+ * the invocation passed `--serve`.
64
+ * @param ctx - plugin context carrying core services and the launcher-provided exit request.
65
+ * @param config - validated task/serve config.
66
+ */
67
+ export declare function apply(ctx: Context, config: Config): void;
68
+ export {};
69
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @khorsheed/dsh-local-agent-dsh-headless — sub-dsh direct Agent driver with
3
+ * two modes. One-shot (default): create one Agent through the core registry
4
+ * on a caller-supplied session id (or resume one), drive the task to
5
+ * quiescence, flush its Session, print the final assistant text, and exit.
6
+ * Serve (`--serve`): stay resident and drive turns over the family-internal
7
+ * live-driver wire (see `./serve.ts` / `./wire.ts`). The session id comes
8
+ * from the invocation (`--session-id` / `--resume`) or the wire, never from
9
+ * stdout — the parent provider generates one uuid and both sides use it, so
10
+ * stdout carries no parseable session marker.
11
+ *
12
+ * @module @khorsheed/dsh-local-agent-dsh-headless
13
+ */
14
+ import z from '@deepseek-ai/schemastery';
15
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
16
+ import { loadSubDshAgent, summarizeTurn } from './agent-loader.js';
17
+ import { runServe } from './serve.js';
18
+ /** Stable Cordis plugin name. */
19
+ export const name = 'local-agent-dsh-headless-runner';
20
+ /** Core services required before the one-shot turn can start. */
21
+ export const inject = ['agentDefaultModel', 'agents', 'sessions'];
22
+ export const Config = z.object({
23
+ task: z.string(),
24
+ sessionId: z.string(),
25
+ resumeSessionId: z.string(),
26
+ serve: z.boolean().default(false),
27
+ model: z.string(),
28
+ effort: z.string(),
29
+ });
30
+ /** The process streams the runner writes to; tests substitute captures. */
31
+ export const internals = {
32
+ stdin: process.stdin,
33
+ stdout: process.stdout,
34
+ stderr: process.stderr,
35
+ };
36
+ /** Report an unexpected direct-driver failure and request a failing exit. */
37
+ function fail(io, error) {
38
+ io.stderr.write(`dsh: ${error instanceof Error ? error.message : String(error)}\n`);
39
+ io.exit(1);
40
+ }
41
+ /**
42
+ * Run one task through a fresh or resumed Agent and request process exit.
43
+ * @param ctx - plugin context carrying the Agent, default model, Session, and launcher IO services.
44
+ * @param config - validated one-shot config (task plus the caller session id).
45
+ * @param io - process-facing effects.
46
+ */
47
+ async function run(ctx, config, io) {
48
+ // Loader siblings mount concurrently. Await the complete application before
49
+ // creating an Agent so its scoped tools and adapters are not half-composed.
50
+ await ctx.get('loader')?.await();
51
+ const sessions = ctx.get('sessions');
52
+ // Early process shutdown can dispose the tree while settlement is pending.
53
+ if (ctx.get('agents') === undefined || ctx.get('agentDefaultModel') === undefined || sessions === undefined)
54
+ return;
55
+ const handle = await loadSubDshAgent(ctx, {
56
+ ...config.sessionId === undefined ? {} : { sessionId: config.sessionId },
57
+ ...config.resumeSessionId === undefined ? {} : { resumeSessionId: config.resumeSessionId },
58
+ ...config.model === undefined ? {} : { model: config.model },
59
+ ...config.effort === undefined ? {} : { effort: config.effort },
60
+ });
61
+ const agent = handle.agent;
62
+ await agent.whenIdle();
63
+ const firstSeq = agent.session.seq;
64
+ agent.followup(createUserMessage({
65
+ content: [{ type: 'text', text: config.task ?? '' }],
66
+ source: { kind: 'user' },
67
+ }));
68
+ await agent.whenIdle();
69
+ await sessions.flush(agent.session);
70
+ const outcome = summarizeTurn(agent.session.snapshotEvents(), firstSeq);
71
+ io.stdout.write(outcome.text + '\n');
72
+ if (outcome.reason?.kind === 'error') {
73
+ io.stderr.write(`dsh: ${outcome.reason.error.code}: ${outcome.reason.error.message}\n`);
74
+ }
75
+ io.exit(outcome.reason?.kind === 'completed' ? 0 : 1);
76
+ }
77
+ /**
78
+ * Mount the direct driver: one-shot by default, the resident serve loop when
79
+ * the invocation passed `--serve`.
80
+ * @param ctx - plugin context carrying core services and the launcher-provided exit request.
81
+ * @param config - validated task/serve config.
82
+ */
83
+ export function apply(ctx, config) {
84
+ // Read through the global service store, not the property proxy: appExit is
85
+ // an optional host value, never an injected dependency.
86
+ const exit = ctx.get('appExit');
87
+ if (exit === undefined) {
88
+ throw new Error('local-agent-dsh-headless-runner: the launcher must provide ctx.appExit before the tree mounts');
89
+ }
90
+ const io = { stdin: internals.stdin, stdout: internals.stdout, stderr: internals.stderr, exit };
91
+ if (config.serve === true) {
92
+ void runServe(ctx, io, config.model, config.effort).catch((error) => { fail(io, error); });
93
+ return;
94
+ }
95
+ if (config.task === undefined || config.task.trim() === '') {
96
+ throw new Error('local-agent-dsh-headless-runner: a task is required unless serve mode is on');
97
+ }
98
+ void run(ctx, config, io).catch((error) => { fail(io, error); });
99
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@khorsheed/dsh-local-agent-dsh-headless`.
3
+ * @module @khorsheed/dsh-local-agent-dsh-headless/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "local-agent-dsh-headless-invariant";
8
+ /** Service required before the companion can register. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Package-owned invariant companion for `@khorsheed/dsh-local-agent-dsh-headless`.
3
+ * @module @khorsheed/dsh-local-agent-dsh-headless/invariant
4
+ */
5
+ const PACKAGE_NAME = '@khorsheed/dsh-local-agent-dsh-headless';
6
+ /** Cordis companion plugin name. */
7
+ export const name = 'local-agent-dsh-headless-invariant';
8
+ /** Service required before the companion can register. */
9
+ export const inject = ['invariants'];
10
+ /**
11
+ * Composition guard: this bundle is sub-profile-only. Its patch rows
12
+ * (persona override, `hmr` off, `tools` mode, the `ptc-runtime` insert, the
13
+ * member-bridge MCP row) collide with or leak into any Host/web composition —
14
+ * 2026-08-23 P0: reconciled into the prod web profile as a direct dependency
15
+ * (reconcilePlugins auto-mounts every `dsh.bundle`-declaring direct dep), the
16
+ * `code-runtime` insert (today's `ptc-runtime` row) hit the web-app row's duplicate entry id and the
17
+ * whole instance failed to boot. The loader's duplicate-id failure is loud
18
+ * but cryptic, and only fires when the ids collide; this invariant is the
19
+ * CLEAR failure for the non-colliding case (e.g. an upstream row rename would
20
+ * otherwise leak the persona override into real user sessions). The web
21
+ * layer's `webStartup` service is the marker: in-box bundles mount before
22
+ * dependency-managed ones, so it is already provided when this companion
23
+ * applies.
24
+ */
25
+ const install = (ctx, fail) => {
26
+ // Duck-typed global-store read: the web marker is not a dependency of this
27
+ // package, so no typed Context merge exists for it.
28
+ const probe = ctx;
29
+ if (probe.get('webStartup') !== undefined) {
30
+ fail('mounted into a web composition — this bundle is sub-profile-only. '
31
+ + 'Remove it from the profile’s direct dependencies: the parent '
32
+ + 'local-agent-dsh bundle installs it transitively, and the '
33
+ + 'sub-profile symlink resolves from that closure');
34
+ }
35
+ };
36
+ /**
37
+ * Register this package's invariant companion.
38
+ * @param ctx - Cordis context carrying the invariant service.
39
+ * @returns the installed registration's disposer after setup succeeds.
40
+ */
41
+ export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
@@ -0,0 +1,39 @@
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 type { Context } from '@deepseek-ai/cordis';
14
+ /**
15
+ * The process streams serve mode reads and writes; tests substitute captures.
16
+ * `stdin` is the minimal readable surface (`data`/`end`), `exit` is the
17
+ * launcher-provided bounded exit request.
18
+ */
19
+ export interface ServeIo {
20
+ readonly stdin: {
21
+ on(event: 'data', listener: (chunk: unknown) => void): unknown;
22
+ on(event: 'end', listener: () => void): unknown;
23
+ };
24
+ readonly stdout: {
25
+ write(chunk: string): unknown;
26
+ };
27
+ readonly stderr: {
28
+ write(chunk: string): unknown;
29
+ };
30
+ readonly exit: (code: number) => void;
31
+ }
32
+ /**
33
+ * Run the resident loop until stdin closes or a `shutdown` request arrives.
34
+ * @param ctx - plugin context carrying the loader settle, core services, and
35
+ * the session/event feed.
36
+ * @param io - process-facing effects.
37
+ */
38
+ export declare function runServe(ctx: Context, io: ServeIo, model?: string, effort?: string): Promise<void>;
39
+ //# sourceMappingURL=serve.d.ts.map