dsh-loop-engine 0.1.5-rc1

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.
Files changed (65) hide show
  1. package/README.md +125 -0
  2. package/README.zh.md +53 -0
  3. package/cordis.patch.yml +3 -0
  4. package/lib/client.js +675 -0
  5. package/lib/index.js +6821 -0
  6. package/lib/invariant.js +108 -0
  7. package/lib/types/client/LoopEngineBadge.d.ts +34 -0
  8. package/lib/types/client/LoopEngineComposerSelect.d.ts +40 -0
  9. package/lib/types/client/LoopEngineSection.d.ts +34 -0
  10. package/lib/types/client/index.d.ts +29 -0
  11. package/lib/types/client/locales.d.ts +46 -0
  12. package/lib/types/client/store.d.ts +58 -0
  13. package/lib/types/commands.d.ts +69 -0
  14. package/lib/types/driver-core/assistant-stream.d.ts +68 -0
  15. package/lib/types/driver-core/context-files.d.ts +62 -0
  16. package/lib/types/driver-core/inbox.d.ts +104 -0
  17. package/lib/types/driver-core/ownership.d.ts +40 -0
  18. package/lib/types/driver-core/permission-knobs.d.ts +26 -0
  19. package/lib/types/driver-core/prompt.d.ts +23 -0
  20. package/lib/types/driver-core/skill-inject.d.ts +59 -0
  21. package/lib/types/engine-claude/agent.d.ts +106 -0
  22. package/lib/types/engine-claude/loop.d.ts +111 -0
  23. package/lib/types/engine-claude/mapping.d.ts +83 -0
  24. package/lib/types/engine-claude/permission.d.ts +41 -0
  25. package/lib/types/engine-claude/process.d.ts +59 -0
  26. package/lib/types/engine-claude/sdk.d.ts +57 -0
  27. package/lib/types/engine-claude/types.d.ts +18 -0
  28. package/lib/types/engine-codex/agent.d.ts +113 -0
  29. package/lib/types/engine-codex/appserver/client.d.ts +49 -0
  30. package/lib/types/engine-codex/appserver/mapping.d.ts +67 -0
  31. package/lib/types/engine-codex/appserver/thread.d.ts +66 -0
  32. package/lib/types/engine-codex/appserver/types.d.ts +215 -0
  33. package/lib/types/engine-codex/loop.d.ts +114 -0
  34. package/lib/types/engine-codex/permission.d.ts +32 -0
  35. package/lib/types/engine-codex/skills.d.ts +29 -0
  36. package/lib/types/engine-codex/types.d.ts +19 -0
  37. package/lib/types/engine-kimi/acp/client.d.ts +76 -0
  38. package/lib/types/engine-kimi/acp/mapping.d.ts +44 -0
  39. package/lib/types/engine-kimi/acp/types.d.ts +95 -0
  40. package/lib/types/engine-kimi/agent.d.ts +130 -0
  41. package/lib/types/engine-kimi/commands.d.ts +40 -0
  42. package/lib/types/engine-kimi/loop.d.ts +108 -0
  43. package/lib/types/engine-kimi/mapping.d.ts +71 -0
  44. package/lib/types/engine-kimi/permission.d.ts +28 -0
  45. package/lib/types/engine-kimi/process.d.ts +61 -0
  46. package/lib/types/engine-kimi/skills.d.ts +57 -0
  47. package/lib/types/engine-kimi/types.d.ts +23 -0
  48. package/lib/types/engine-pi/agent.d.ts +150 -0
  49. package/lib/types/engine-pi/loop.d.ts +125 -0
  50. package/lib/types/engine-pi/permission.d.ts +43 -0
  51. package/lib/types/engine-pi/probe.d.ts +23 -0
  52. package/lib/types/engine-pi/rpc/client.d.ts +105 -0
  53. package/lib/types/engine-pi/rpc/mapping.d.ts +37 -0
  54. package/lib/types/engine-pi/rpc/types.d.ts +235 -0
  55. package/lib/types/engine-pi/skills.d.ts +55 -0
  56. package/lib/types/engine-pi/types.d.ts +27 -0
  57. package/lib/types/index.d.ts +114 -0
  58. package/lib/types/invariant.d.ts +23 -0
  59. package/lib/types/namespace.d.ts +9 -0
  60. package/lib/types/patch-manager.d.ts +59 -0
  61. package/lib/types/preset.d.ts +73 -0
  62. package/lib/types/provider-route.d.ts +49 -0
  63. package/lib/types/settings.d.ts +31 -0
  64. package/lib/types/skills.d.ts +93 -0
  65. package/package.json +104 -0
@@ -0,0 +1,108 @@
1
+ // src/settings.ts
2
+ import z from "@deepseek-ai/schemastery";
3
+ var LOOP_ENGINE_IDS = ["in-process", "claude-code", "codex", "pi", "kimi"];
4
+ var LOOP_ENGINE_SETTINGS_SCHEMA = z.object({
5
+ engine: z.union([z.const("in-process"), z.const("claude-code"), z.const("codex"), z.const("pi"), z.const("kimi")]).default("in-process"),
6
+ showInComposer: z.boolean().default(true)
7
+ });
8
+
9
+ // src/patch-manager.ts
10
+ var MANAGED_BLOCK_BEGIN = "# -- dsh-loop-engine managed block: ";
11
+ var MANAGED_BLOCK_END = "# -- /dsh-loop-engine managed block --";
12
+ var END_MARKER_LINE = `${MANAGED_BLOCK_END}
13
+ `;
14
+ function renderManagedBlock(engine) {
15
+ if (engine === "in-process") return "";
16
+ return [
17
+ `${MANAGED_BLOCK_BEGIN}${engine} --`,
18
+ "- id: agent-loop",
19
+ " disabled: true",
20
+ "- id: command-goal",
21
+ " disabled: true",
22
+ END_MARKER_LINE
23
+ ].join("\n");
24
+ }
25
+ var BEGIN_MARKER_RE = /^# -- dsh-loop-engine managed block: (\S+) --$/m;
26
+ function currentEngineOf(text) {
27
+ const engine = BEGIN_MARKER_RE.exec(text)?.[1];
28
+ return LOOP_ENGINE_IDS.includes(engine ?? "") ? engine : "in-process";
29
+ }
30
+ function managedSpan(text) {
31
+ const begin = text.indexOf(MANAGED_BLOCK_BEGIN);
32
+ if (begin === -1) return { head: text, tail: "", present: false, blankBefore: false };
33
+ const afterBegin = begin + MANAGED_BLOCK_BEGIN.length;
34
+ const endAt = text.indexOf(MANAGED_BLOCK_END, afterBegin);
35
+ const spanEnd = endAt === -1 ? text.length : endAt + END_MARKER_LINE.length;
36
+ const before = text.slice(0, begin);
37
+ const blankBefore = before.endsWith("\n\n");
38
+ return {
39
+ head: blankBefore ? before.slice(0, -1) : before,
40
+ tail: text.slice(spanEnd),
41
+ present: true,
42
+ blankBefore
43
+ };
44
+ }
45
+ function ensureTrailingNewline(text) {
46
+ return text.endsWith("\n") ? text : `${text}
47
+ `;
48
+ }
49
+ function hasRootEntry(text) {
50
+ return /^(?:- |\[)/m.test(text);
51
+ }
52
+ function dropSeedPlaceholder(text) {
53
+ return text.replace(/^\[\]\n/m, "");
54
+ }
55
+ function seedEmptyArray(text) {
56
+ const head = text.replace(/\n+$/, "");
57
+ return head === "" ? "[]\n" : `${head}
58
+ []
59
+ `;
60
+ }
61
+ function applyManagedBlock(text, engine) {
62
+ const block = renderManagedBlock(engine);
63
+ const span = managedSpan(text);
64
+ let result;
65
+ if (!span.present) {
66
+ if (block === "") {
67
+ result = text;
68
+ } else {
69
+ const base = ensureTrailingNewline(text);
70
+ result = `${base}
71
+ ${block}`;
72
+ }
73
+ } else if (block === "") {
74
+ result = span.tail.startsWith("\n") ? `${span.head}${span.tail.slice(1)}` : `${span.head}${span.tail}`;
75
+ } else {
76
+ result = `${span.head}${span.blankBefore ? "\n" : ""}${block}${span.tail}`;
77
+ }
78
+ if (block !== "") return dropSeedPlaceholder(result);
79
+ if (text.trim() === "") return result;
80
+ return hasRootEntry(result) ? result : seedEmptyArray(result);
81
+ }
82
+
83
+ // src/invariant.ts
84
+ var PACKAGE_NAME = "dsh-loop-engine";
85
+ var name = "loop-engine-invariant";
86
+ var inject = ["invariants"];
87
+ var install = (ctx, fail) => {
88
+ void ctx;
89
+ const seed = "";
90
+ const commentOnly = "# dsh profile patch layer\n";
91
+ for (const engine of LOOP_ENGINE_IDS) {
92
+ const applied = applyManagedBlock(seed, engine);
93
+ const reborn = applyManagedBlock(applied, currentEngineOf(applied));
94
+ if (reborn !== applied) fail(`bare round trip for ${engine} is not a fixed point`);
95
+ if (engine === "in-process" && applied !== seed) fail("in-process engine must leave a bare layer unchanged");
96
+ if (engine !== "in-process" && currentEngineOf(renderManagedBlock(engine)) !== engine) fail(`${engine} block must read back as the ${engine} engine`);
97
+ if (engine === "in-process" && applyManagedBlock(commentOnly, engine) === commentOnly) {
98
+ fail("in-process engine must re-seed a comment-only file to a loadable top-level array");
99
+ }
100
+ }
101
+ };
102
+ var apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
103
+ export {
104
+ apply,
105
+ inject,
106
+ name
107
+ };
108
+ //# sourceMappingURL=invariant.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Session header engine badge: a read-only chip naming the loop engine that
3
+ * drives this session. The engine is a deployment-level choice, so the chip
4
+ * reports the same value for every session — naming what sessions run is the
5
+ * honest affordance; the switch itself lives in the settings section.
6
+ *
7
+ * Styling is token-driven inline styles like the settings section (the
8
+ * client-module bundle is esbuild-built without a CSS loader).
9
+ * @module dsh-loop-engine/client/badge
10
+ */
11
+ import type { JSX } from 'react';
12
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
13
+ import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
14
+ import type { LoopEngineState } from './store.ts';
15
+ import type { en } from './locales.ts';
16
+ /** Registration-side business face for the header badge. */
17
+ export interface LoopEngineBadgeInjected {
18
+ hooks: {
19
+ /** Engine snapshot bound by the renderer as useSnapshot. */
20
+ snapshot: SnapshotStore<LoopEngineState>;
21
+ };
22
+ /** Section copy bound to the engine dictionaries. */
23
+ t: (key: keyof typeof en) => string;
24
+ }
25
+ /** Props delivered by the slot outlet (the renderer erases the share boundary). */
26
+ export type LoopEngineBadgeProps = Partial<InjectFace<LoopEngineBadgeInjected>>;
27
+ /**
28
+ * Render the session header's loop-engine chip. Hides until the settings
29
+ * scope settles, so the header never flashes a provisional engine.
30
+ * @param props - composed slot props.
31
+ * @returns the chip, or null while the engine is unknown.
32
+ */
33
+ export declare function LoopEngineBadge(props: LoopEngineBadgeProps): JSX.Element | null;
34
+ //# sourceMappingURL=LoopEngineBadge.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Composer loop-engine picker: a compact dropdown registered at the
3
+ * `conversation.input.right` seat, so it sits immediately left of the model
4
+ * select in the composer's tool row. The engine is a deployment-level choice,
5
+ * so this surface shares the same settings-backed {@link LoopEngineStore} as
6
+ * the settings section and the header badge — a change in any one is what the
7
+ * others show next. Switching still asks for confirmation first (it interrupts
8
+ * sessions still running on the previous engine) and reloads the page once the
9
+ * commit lands, matching the settings section's semantics.
10
+ *
11
+ * Styling is token-driven inline styles like the badge and section (the
12
+ * client-module bundle is esbuild-built without a CSS loader).
13
+ * @module dsh-loop-engine/client/composer
14
+ */
15
+ import { type JSX } from 'react';
16
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
17
+ import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
18
+ import type { LoopEngineStore, LoopEngineState } from './store.ts';
19
+ import type { en } from './locales.ts';
20
+ /** Injected dependencies of {@link LoopEngineComposerSelect} (slot `inject`). */
21
+ export interface LoopEngineComposerSelectInjected {
22
+ /** The selection store (loaded on mount, refreshed by scope pushes). */
23
+ controller: LoopEngineStore;
24
+ hooks: {
25
+ /** Engine snapshot bound by the UI renderer as useSnapshot. */
26
+ snapshot: SnapshotStore<LoopEngineState>;
27
+ };
28
+ /** Composer copy bound to the loop engine dictionaries. */
29
+ t: (key: keyof typeof en) => string;
30
+ }
31
+ /** Props delivered by the slot outlet (the renderer erases the share boundary). */
32
+ export type LoopEngineComposerSelectProps = Partial<InjectFace<LoopEngineComposerSelectInjected>>;
33
+ /**
34
+ * Render the composer's loop-engine dropdown. Hides until the settings scope
35
+ * settles, so the composer never flashes a provisional engine.
36
+ * @param props - composed slot props.
37
+ * @returns the picker, or null while the engine is unknown.
38
+ */
39
+ export declare function LoopEngineComposerSelect(props: LoopEngineComposerSelectProps): JSX.Element | null;
40
+ //# sourceMappingURL=LoopEngineComposerSelect.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Loop engine settings section component: one dropdown choosing the agent
3
+ * loop engine, backed by the duplicated settings scope through the inject face.
4
+ * Changing the engine asks for confirmation first, because the switch
5
+ * interrupts sessions still running on the previous engine.
6
+ *
7
+ * Styling is token-driven like the rest of the settings shell (`--dsw-*`
8
+ * aliases), with the picker rendered through the shared `Menu` primitive and
9
+ * the confirmation through `Modal`. The client-module bundle is esbuild-built
10
+ * without a CSS loader, so the section shell uses token-based inline styles
11
+ * instead of a CSS module.
12
+ * @module dsh-loop-engine/client
13
+ */
14
+ import { type JSX } from 'react';
15
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
16
+ import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
17
+ import type { LoopEngineStore, LoopEngineState } from './store.ts';
18
+ import type { en } from './locales.ts';
19
+ /** Injected dependencies of {@link LoopEngineSection} (slot `inject`). */
20
+ export interface LoopEngineSectionInjected {
21
+ /** The selection store (loaded on mount, refreshed by scope pushes). */
22
+ controller: LoopEngineStore;
23
+ hooks: {
24
+ /** Section snapshot bound by the UI renderer as useSnapshot. */
25
+ snapshot: SnapshotStore<LoopEngineState>;
26
+ };
27
+ /** Section copy. */
28
+ t: (key: keyof typeof en) => string;
29
+ }
30
+ /** Props delivered by the slot outlet (the renderer erases the share boundary). */
31
+ export type LoopEngineSectionProps = Partial<InjectFace<LoopEngineSectionInjected>>;
32
+ /** Render the engine dropdown plus the interrupt notice and the switch confirmation. */
33
+ export declare function LoopEngineSection(props: LoopEngineSectionProps): JSX.Element;
34
+ //# sourceMappingURL=LoopEngineSection.d.ts.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Loop engine settings plugin, browser half. Registers the "Loop engine"
3
+ * page under the settings section slot once the settings shell declares it,
4
+ * binding one store to the duplicated `agent-loop-engine` settings scope.
5
+ * Export discipline: packages/client/AGENTS.md.
6
+ * @module dsh-loop-engine/client
7
+ */
8
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
9
+ import { type LoopEngineKey } from './locales.ts';
10
+ export type { LoopEngineSectionInjected, LoopEngineSectionProps } from './LoopEngineSection.tsx';
11
+ export type { LoopEngineBadgeInjected, LoopEngineBadgeProps } from './LoopEngineBadge.tsx';
12
+ export type { LoopEngineComposerSelectInjected, LoopEngineComposerSelectProps } from './LoopEngineComposerSelect.tsx';
13
+ export type { LoopEngineState } from './store.ts';
14
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
15
+ interface LocaleNamespaceMap {
16
+ /** The Loop engine settings page copy. */
17
+ 'settings.loop-engine': LoopEngineKey;
18
+ }
19
+ }
20
+ /** Required services (cordis fiber inject). The target slot is declared by
21
+ * ui-settings' apply; registration depends on it through `slots.inject()`. */
22
+ export declare const inject: string[];
23
+ /**
24
+ * Register the Loop engine section once the `settings.section` declaration is
25
+ * on the ledger and bind its store to the duplicated settings scope.
26
+ * @param ctx - client root context.
27
+ */
28
+ export declare function apply(ctx: ClientContext): void;
29
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Loop engine settings page copy (Chinese product copy; comments in English).
3
+ * @module dsh-loop-engine/client/locales
4
+ */
5
+ /** Copy keys of the loop engine settings page. */
6
+ export interface LoopEngineKey {
7
+ /** Settings navigation label. */
8
+ nav: string;
9
+ /** Panel description under the title. */
10
+ description: string;
11
+ /** Option label: the default in-process loop driver. */
12
+ engineInProcess: string;
13
+ /** Option label: the Claude Code CLI driver. */
14
+ engineClaudeCode: string;
15
+ /** Option label: the Codex CLI driver. */
16
+ engineCodex: string;
17
+ /** Option label: the Pi CLI driver. */
18
+ enginePi: string;
19
+ /** Option label: the Kimi Code CLI driver. */
20
+ engineKimi: string;
21
+ /** Settings toggle: show the engine picker in the chat page composer. */
22
+ showInComposerLabel: string;
23
+ /** Unavailable-state message. */
24
+ unavailable: string;
25
+ /** Notice shown when the selection would interrupt running agents. */
26
+ switchNotice: string;
27
+ /** Saving state label. */
28
+ saving: string;
29
+ /** Confirmation dialog title. */
30
+ confirmTitle: string;
31
+ /** Confirmation dialog body. */
32
+ confirmBody: string;
33
+ /** Confirmation action label. */
34
+ confirmAction: string;
35
+ /** Cancel action label. */
36
+ cancelAction: string;
37
+ /** Accessible close-button label of the confirmation dialog. */
38
+ closeLabel: string;
39
+ /** Notice shown while the Claude Code engine owns the slot: model selection is native. */
40
+ claudeModelNotice: string;
41
+ }
42
+ /** Simplified Chinese copy. */
43
+ export declare const zh: Record<keyof LoopEngineKey, string>;
44
+ /** English copy. */
45
+ export declare const en: Record<keyof LoopEngineKey, string>;
46
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Loop engine selection store: the durable settings scope is the transport,
3
+ * and the store publishes a render-safe snapshot plus the write path.
4
+ * @module dsh-loop-engine/client/store
5
+ */
6
+ import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
7
+ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
8
+ import type { LoopEngineId } from '../settings.ts';
9
+ /** State rendered by the loop engine section. */
10
+ export interface LoopEngineState {
11
+ status: 'loading' | 'ready' | 'unavailable' | 'saving';
12
+ engine: LoopEngineId;
13
+ showInComposer: boolean;
14
+ writable: boolean;
15
+ error: string | null;
16
+ }
17
+ /** Narrow a wire section to the stored engine id and display toggle; an invalid one reads default. */
18
+ export declare function decodeLoopEngine(section: unknown): {
19
+ engine: LoopEngineId;
20
+ showInComposer: boolean;
21
+ } | undefined;
22
+ /** Coordinates the settings-backed loop engine selection. */
23
+ export declare class LoopEngineStore {
24
+ private readonly scope;
25
+ /** uSES-safe state source shared by the registered settings section. */
26
+ readonly store: SnapshotStore<LoopEngineState>;
27
+ private following;
28
+ private saving;
29
+ /**
30
+ * @param scope - the loop engine settings namespace scope.
31
+ */
32
+ constructor(scope: SettingsScope<{
33
+ engine: LoopEngineId;
34
+ showInComposer: boolean;
35
+ }>);
36
+ /** Begin following the bound scope and publish its current answer. */
37
+ load(): void;
38
+ /**
39
+ * Persist the selected engine. Success is judged against the snapshot the
40
+ * write left behind, so a refused write reports error after its recovery.
41
+ * @param engine - the engine to select for future Agent turns.
42
+ * @returns whether the write landed.
43
+ */
44
+ setEngine(engine: LoopEngineId): Promise<boolean>;
45
+ /**
46
+ * Persist whether the composer shows the engine picker. Success is judged
47
+ * against the snapshot the write left behind, so a refused write reports
48
+ * error after its recovery. Unlike {@link setEngine}, landing does not reload
49
+ * the page — the toggle only changes composer visibility.
50
+ * @param show - whether the chat page composer reveals the engine picker.
51
+ * @returns whether the write landed.
52
+ */
53
+ setShowInComposer(show: boolean): Promise<boolean>;
54
+ /** Stop following the scope. */
55
+ dispose(): void;
56
+ private derive;
57
+ }
58
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Claude Code slash-command bridge.
3
+ *
4
+ * The dsh `commands` runtime executes a registered command locally — the line
5
+ * is consumed and never reaches the model — so a command whose real processing
6
+ * lives inside the Claude Code CLI must forward the raw line back to the
7
+ * engine. The definitions here do exactly that: the handler delivers
8
+ * `/<name> [args]` to the receiving agent as a plain user message, and the CLI
9
+ * then expands it natively (built-ins and custom `.claude/commands/*.md`).
10
+ * Registering the built-ins keeps them visible in the dsh web slash menu;
11
+ * unregistered `/lines` pass through as user text, but the menu would hide the
12
+ * engine's command surface.
13
+ *
14
+ * User-level custom slash commands (`~/.claude/commands/*.md`) are discovered
15
+ * and registered the same way, so they appear in the menu AND reach the CLI.
16
+ * Project-level `.claude/commands/` files are left to the CLI entirely: they
17
+ * are cwd-dependent, and a global dsh registration would collide across
18
+ * projects.
19
+ *
20
+ * @module dsh-loop-engine/commands
21
+ */
22
+ import type { UserMessage } from '@deepseek-ai/dsh-session';
23
+ /** Minimal shape of a DSH command definition (avoiding a direct peer dep on @deepseek-ai/dsh-commands). */
24
+ export interface CommandDefinition {
25
+ readonly name: string;
26
+ readonly description: string;
27
+ readonly input?: {
28
+ readonly hint: string;
29
+ readonly images?: boolean;
30
+ };
31
+ readonly handler: (invocation: CommandInvocation) => CommandResult | Promise<CommandResult>;
32
+ }
33
+ /** Invocation delivered to one registered command handler. */
34
+ export interface CommandInvocation {
35
+ readonly commandId: string;
36
+ /** The receiving agent; forwarding handlers deliver the raw line back to it. */
37
+ readonly agent: {
38
+ readonly followup: (input: UserMessage) => void;
39
+ };
40
+ /** Exact text following the command name, including separator whitespace. */
41
+ readonly rawInput: string;
42
+ readonly signal: AbortSignal;
43
+ }
44
+ /** Settled result of one command handler. */
45
+ export interface CommandResult {
46
+ readonly kind: 'success' | 'error';
47
+ readonly text?: string;
48
+ }
49
+ /**
50
+ * Build the forwarding handler for one Claude Code slash command: it
51
+ * re-delivers the full `/<name> [args]` line to the receiving agent as a
52
+ * plain user message, where the CLI expands it. `rawInput` already carries the
53
+ * separator whitespace and any arguments.
54
+ * @param name - the command name without the leading slash.
55
+ * @returns the command handler.
56
+ */
57
+ export declare function forwardClaudeCodeCommand(name: string): (invocation: CommandInvocation) => CommandResult;
58
+ /** Claude Code's built-in slash commands. */
59
+ export declare const CLAUDE_CODE_COMMANDS: readonly CommandDefinition[];
60
+ /**
61
+ * Discover the user-level custom slash commands from `~/.claude/commands/*.md`
62
+ * and build forwarding definitions for them. The scan is synchronous so the
63
+ * mount path can register the commands before the engine-selection commit
64
+ * returns; files without a usable name or description, and names already taken
65
+ * by the built-ins, are skipped.
66
+ * @returns forwarding definitions, sorted by file name.
67
+ */
68
+ export declare function discoverUserSlashCommands(): CommandDefinition[];
69
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Live assistant-stream framing for the hosted engines.
3
+ *
4
+ * Harness 0.1.5 made the model stream the durable record of an assistant
5
+ * attempt: `assistant/message` embeds its exact timed stream and may no longer
6
+ * cite `sourceEventSeqs`, and the transient per-chunk log events the plugin
7
+ * used to append (`assistant/chunk`) no longer exist. Live partials now travel
8
+ * through the process-local `agent/assistant-stream` notification instead, and
9
+ * the compact records are handed to the durable message.
10
+ *
11
+ * This is the driver-side equivalent of the loop's `AssistantStreamAttempt`,
12
+ * minus the block assembly: a hosted engine keeps assembling its own
13
+ * authoritative transcript from its own protocol, and this class only frames
14
+ * the attempt and compacts its stream for replay fidelity.
15
+ *
16
+ * @module dsh-loop-engine/driver-core/assistant-stream
17
+ */
18
+ import type { AssistantStreamFrame } from '@deepseek-ai/dsh-agent';
19
+ import { LlmAttemptId, type AssistantStreamRecord, type StreamChunk } from '@deepseek-ai/dsh-llm';
20
+ import type { SessionId, SessionSeq } from '@deepseek-ai/dsh-session';
21
+ /**
22
+ * One model attempt on a hosted engine: ordered live frames plus the compact
23
+ * stream the durable `assistant/message` carries.
24
+ */
25
+ export declare class DriverAssistantStream {
26
+ private readonly turn;
27
+ private readonly step;
28
+ private readonly emit;
29
+ private accumulator;
30
+ private index;
31
+ private revision;
32
+ private terminal;
33
+ /** Attempt identity unique within this agent lifecycle. */
34
+ readonly attemptId: LlmAttemptId;
35
+ /**
36
+ * @param sessionId - identity embedded only in the agent-lifecycle-local attempt id.
37
+ * @param attempt - agent-local attempt counter.
38
+ * @param turn - durable turn owning the request.
39
+ * @param step - durable step owning the request.
40
+ * @param emit - agent-scoped notification publisher.
41
+ */
42
+ constructor(sessionId: SessionId, attempt: number, turn: number, step: number, emit: (frame: AssistantStreamFrame) => void);
43
+ /** Whether this attempt has already published its terminal frame. */
44
+ get ended(): boolean;
45
+ /** Publish the opening marker before the first delivered chunk. */
46
+ start(): void;
47
+ /** Snapshot one chunk once, then feed durable compaction and live publication. */
48
+ push(chunk: StreamChunk): void;
49
+ /** Exact compact stream for the durable assistant message. */
50
+ get stream(): AssistantStreamRecord[];
51
+ /**
52
+ * Close the current record run and return it, leaving later chunks in a
53
+ * fresh run. An engine whose protocol reports content segments (rather than
54
+ * one stream per message) cuts at each segment boundary, so every durable
55
+ * message embeds exactly the chunks its own content produced even though the
56
+ * live frames keep flowing through one attempt.
57
+ * @returns the compact records accumulated since the previous cut.
58
+ */
59
+ takeStream(): AssistantStreamRecord[];
60
+ /**
61
+ * Publish terminal settlement after the matching durable event commits.
62
+ * @param append - synchronous durable append returning its committed seq.
63
+ */
64
+ settle(append: () => SessionSeq): void;
65
+ /** Publish abandonment when no durable settlement can be committed. */
66
+ abandon(): void;
67
+ }
68
+ //# sourceMappingURL=assistant-stream.d.ts.map
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Context-file collection and body loading shared by the hosted engine
3
+ * drivers.
4
+ *
5
+ * Codex and Pi read per-directory instruction files (`AGENTS.md`; pi also
6
+ * accepts `CLAUDE.md` and prefers `AGENTS.override.md` inside any directory
7
+ * that has one) while walking from the session cwd up to the git root. The
8
+ * skill providers surface each collected set as one merged skill candidate so
9
+ * the dsh skill-injection seam (`/name` gestures) can carry it into the
10
+ * prompt; the body-loading helpers below feed both providers' list/get paths.
11
+ *
12
+ * @module dsh-loop-engine/driver-core/context-files
13
+ */
14
+ /** Per-directory context-file resolution policy for one engine. */
15
+ export interface ContextFilePolicy {
16
+ /** Per-directory override file that replaces the primary files when present. */
17
+ readonly override?: string;
18
+ /** Per-directory primary files, tried in order until one exists. */
19
+ readonly primary: readonly string[];
20
+ }
21
+ /**
22
+ * The directory chain from `cwd` up to the git root, nearest first. Without a
23
+ * repository the chain is just the resolved `cwd` itself, matching
24
+ * {@link findProjectRoot}'s fallback so the walk stays bounded.
25
+ * @param cwd - the session working directory.
26
+ * @returns the chain of directories to inspect.
27
+ */
28
+ export declare function projectAncestors(cwd: string): Promise<string[]>;
29
+ /**
30
+ * Collect every directory's context file per the policy, from the session cwd
31
+ * up to the git root.
32
+ * @param cwd - the session working directory.
33
+ * @param policy - per-directory resolution policy.
34
+ * @returns existing context files, nearest directory first.
35
+ */
36
+ export declare function collectProjectContextFiles(cwd: string, policy: ContextFilePolicy): Promise<string[]>;
37
+ /**
38
+ * Read one file, or `undefined` when it is unreadable.
39
+ * @param path - the file to read.
40
+ * @returns the file body, or `undefined` on any failure.
41
+ */
42
+ export declare function readOptionalFile(path: string): Promise<string | undefined>;
43
+ /**
44
+ * Whether any of the given sources carries non-whitespace content.
45
+ * @param paths - candidate file paths.
46
+ * @returns whether at least one readable source is non-empty.
47
+ */
48
+ export declare function anySourceNonEmpty(paths: readonly string[]): Promise<boolean>;
49
+ /**
50
+ * Whether one file exists and carries non-whitespace content.
51
+ * @param path - the file to inspect.
52
+ * @returns whether the file is readable and non-empty.
53
+ */
54
+ export declare function fileNonEmpty(path: string): Promise<boolean>;
55
+ /**
56
+ * Concatenate every non-empty readable source body in order, or `undefined`
57
+ * when none are readable.
58
+ * @param paths - candidate file paths, nearest directory first.
59
+ * @returns the joined bodies, or `undefined` when nothing could be read.
60
+ */
61
+ export declare function readSources(paths: readonly string[]): Promise<string | undefined>;
62
+ //# sourceMappingURL=context-files.d.ts.map
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Driver-owned durable inbox for the hosted engines.
3
+ *
4
+ * Harness 0.1.5 turned `Inbox` from a concrete class into an interface the
5
+ * driver owns, so this module carries the projection the plugin used to
6
+ * construct from `dsh-agent`: pending input is folded from the session's own
7
+ * `agent/inbox/spliced` events and every mutation commits a normalized splice
8
+ * back into that log before the live lists change. The durable event stream is
9
+ * what the web client's conversation reducer and session resume both read, so
10
+ * the semantics match the removed class exactly.
11
+ *
12
+ * @module dsh-loop-engine/driver-core/inbox
13
+ */
14
+ import type { Inbox, InboxTarget } from '@deepseek-ai/dsh-agent';
15
+ import type { MessageId } from '@deepseek-ai/dsh-llm';
16
+ import type { Session, UserMessage } from '@deepseek-ai/dsh-session';
17
+ /** Live notifications committed by inbox mutations. */
18
+ export interface InboxNotifications {
19
+ /** Publish one inserted message. */
20
+ inserted(message: UserMessage): void;
21
+ /** Publish one discarded message. */
22
+ discarded(message: UserMessage): void;
23
+ /** Publish one claimed message inside its owning turn. */
24
+ claimed(message: UserMessage, turn: number): void;
25
+ }
26
+ /**
27
+ * A replay-once projection of the session's durable inbox splices. Unlike the
28
+ * harness's driver inbox, this one keeps its own fold instead of registering a
29
+ * session projection, because the hosted engines drive sessions whose preset
30
+ * context is not the agent-loop's.
31
+ */
32
+ export declare class DriverInbox implements Inbox {
33
+ private readonly session;
34
+ private readonly notifications;
35
+ private readonly state;
36
+ constructor(session: Session, notifications: InboxNotifications);
37
+ /** Prompts awaiting individual turns. */
38
+ get nextTurn(): readonly UserMessage[];
39
+ /** Input awaiting the next step boundary. */
40
+ get nextStep(): readonly UserMessage[];
41
+ /** Whether either pending-message list contains work. */
42
+ get hasPending(): boolean;
43
+ /** Durably cancel all pending input, clearing next-step before next-turn. */
44
+ clear(): void;
45
+ /**
46
+ * Remove and return the complete batch proposed for one step, publishing
47
+ * each claimed message. The durable splices are pure deletions.
48
+ * @param target - whether this boundary also consumes one queued turn.
49
+ * @param turn - turn that will own the claimed batch.
50
+ * @returns next-step input followed by the queued turn, when requested.
51
+ */
52
+ claim(target: InboxTarget, turn: number): UserMessage[];
53
+ /**
54
+ * Append one message to a pending list and durably record the insertion.
55
+ * @param target - pending list to extend.
56
+ * @param message - message to append.
57
+ * @throws if the message identity is already pending.
58
+ */
59
+ append(target: InboxTarget, message: UserMessage): void;
60
+ /**
61
+ * Prepend one message to a pending list and durably record the insertion.
62
+ * @param target - pending list to extend.
63
+ * @param message - message to prepend.
64
+ * @throws if the message identity is already pending.
65
+ */
66
+ prepend(target: InboxTarget, message: UserMessage): void;
67
+ /**
68
+ * Replace one pending message in place, possibly changing its identity. A
69
+ * successful replacement publishes the old message as discarded and the new
70
+ * message as inserted.
71
+ * @param messageId - identity of the pending message to replace.
72
+ * @param newMessage - replacement message.
73
+ * @returns whether the message was still pending.
74
+ * @throws if the replacement duplicates another pending message identity.
75
+ */
76
+ replace(messageId: MessageId, newMessage: UserMessage): boolean;
77
+ /**
78
+ * Remove one pending message and durably record its cancellation.
79
+ * @param messageId - identity of the pending message to remove.
80
+ * @returns whether the message was still pending.
81
+ */
82
+ remove(messageId: MessageId): boolean;
83
+ /**
84
+ * Apply standard splice semantics and durably record the normalized result.
85
+ * The durable event commits before the live projection mutates, so
86
+ * synchronous `session/event` observers see the pre-splice lists and can
87
+ * reconstruct the removed messages from the normalized coordinates.
88
+ * @param target - pending list to mutate.
89
+ * @param start - splice position.
90
+ * @param deleteCount - maximum number of messages to remove.
91
+ * @param inserted - messages to insert at the resolved position.
92
+ * @returns messages removed by the splice.
93
+ */
94
+ splice(target: InboxTarget, start: number, deleteCount: number, inserted: UserMessage[]): UserMessage[];
95
+ /** Locate one pending identity across both owned lists. */
96
+ private locate;
97
+ /** Commit one normalized mutation and publish its live notifications. */
98
+ private mutate;
99
+ /** Apply one normalized durable splice to the projection. */
100
+ private apply;
101
+ /** Validate one normalized splice against the current projection. */
102
+ private validate;
103
+ }
104
+ //# sourceMappingURL=inbox.d.ts.map