@vidge/dsh-agent-hub 0.1.0-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.
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/README.zh.md +115 -0
- package/cordis.patch.yml +22 -0
- package/lib/client.js +1494 -0
- package/lib/index.js +5045 -0
- package/lib/invariant.js +96 -0
- package/lib/types/client/LoopEngineComposerSelect.d.ts +73 -0
- package/lib/types/client/LoopEngineSection.d.ts +43 -0
- package/lib/types/client/engine-rpc.d.ts +74 -0
- package/lib/types/client/index.d.ts +28 -0
- package/lib/types/client/locales.d.ts +44 -0
- package/lib/types/client/session-location.d.ts +63 -0
- package/lib/types/client/store.d.ts +58 -0
- package/lib/types/commands.d.ts +69 -0
- package/lib/types/driver-core/context-files.d.ts +62 -0
- package/lib/types/driver-core/ownership.d.ts +40 -0
- package/lib/types/driver-core/permission-knobs.d.ts +26 -0
- package/lib/types/driver-core/prompt.d.ts +23 -0
- package/lib/types/driver-core/skill-inject.d.ts +59 -0
- package/lib/types/engine-claude/agent.d.ts +116 -0
- package/lib/types/engine-claude/loop.d.ts +99 -0
- package/lib/types/engine-claude/mapping.d.ts +84 -0
- package/lib/types/engine-claude/permission.d.ts +41 -0
- package/lib/types/engine-claude/process.d.ts +59 -0
- package/lib/types/engine-claude/provider-env.d.ts +50 -0
- package/lib/types/engine-claude/sdk.d.ts +101 -0
- package/lib/types/engine-claude/types.d.ts +28 -0
- package/lib/types/engine-codex/agent.d.ts +109 -0
- package/lib/types/engine-codex/appserver/client.d.ts +49 -0
- package/lib/types/engine-codex/appserver/mapping.d.ts +67 -0
- package/lib/types/engine-codex/appserver/thread.d.ts +66 -0
- package/lib/types/engine-codex/appserver/types.d.ts +215 -0
- package/lib/types/engine-codex/loop.d.ts +92 -0
- package/lib/types/engine-codex/permission.d.ts +32 -0
- package/lib/types/engine-codex/skills.d.ts +29 -0
- package/lib/types/engine-codex/types.d.ts +19 -0
- package/lib/types/engine-pi/agent.d.ts +125 -0
- package/lib/types/engine-pi/loop.d.ts +96 -0
- package/lib/types/engine-pi/permission.d.ts +43 -0
- package/lib/types/engine-pi/rpc/client.d.ts +105 -0
- package/lib/types/engine-pi/rpc/mapping.d.ts +37 -0
- package/lib/types/engine-pi/rpc/types.d.ts +235 -0
- package/lib/types/engine-pi/skills.d.ts +55 -0
- package/lib/types/engine-pi/types.d.ts +27 -0
- package/lib/types/engine-record.d.ts +124 -0
- package/lib/types/index.d.ts +138 -0
- package/lib/types/invariant.d.ts +23 -0
- package/lib/types/llm-compat.d.ts +32 -0
- package/lib/types/namespace.d.ts +19 -0
- package/lib/types/patch-manager.d.ts +78 -0
- package/lib/types/router.d.ts +189 -0
- package/lib/types/rpc.d.ts +113 -0
- package/lib/types/settings.d.ts +29 -0
- package/lib/types/skills.d.ts +93 -0
- package/package.json +107 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed-block editing for a profile's `cordis.patch.yml`.
|
|
3
|
+
*
|
|
4
|
+
* The plugin owns one contiguous block inside the user's patch file, delimited
|
|
5
|
+
* by a begin/end marker pair, and rewrites only that span — everything else the
|
|
6
|
+
* user wrote (other patches, their comments) survives byte for byte. The
|
|
7
|
+
* block's content is the loader patch that takes the loop engine over: it
|
|
8
|
+
* disables the base bundle's `agent-loop` row so this plugin's factory can
|
|
9
|
+
* register without colliding, because the harness admits exactly one
|
|
10
|
+
* AgentFactory:
|
|
11
|
+
*
|
|
12
|
+
* # -- dsh-loop-engine managed block --
|
|
13
|
+
* - id: agent-loop
|
|
14
|
+
* disabled: true
|
|
15
|
+
* # -- /dsh-loop-engine managed block --
|
|
16
|
+
*
|
|
17
|
+
* The block is now PERMANENT and engine-independent. Under per-session routing
|
|
18
|
+
* this plugin always owns the slot — it registers a router that dispatches to
|
|
19
|
+
* whichever engine a session belongs to, and it hosts the base in-process loop
|
|
20
|
+
* itself as one of those engines. The block therefore no longer carries an
|
|
21
|
+
* engine name and no longer changes when the user picks a different engine;
|
|
22
|
+
* that choice is now runtime state, not boot state.
|
|
23
|
+
*
|
|
24
|
+
* All functions here are pure string transforms — file I/O and durability live
|
|
25
|
+
* in the plugin's apply.
|
|
26
|
+
*
|
|
27
|
+
* @module dsh-agent-hub/patch-manager
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Begin marker of the plugin-managed span inside a profile patch file.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately still spelled `dsh-loop-engine` after the package was renamed to
|
|
33
|
+
* `dsh-agent-hub`: this string is not a brand, it is the key by which the
|
|
34
|
+
* plugin recognizes a block it already wrote into the *user's* file. Renaming
|
|
35
|
+
* it would make every existing install fail to find its own span and append a
|
|
36
|
+
* second one, leaving two `agent-loop: disabled` rows in the patch list.
|
|
37
|
+
*/
|
|
38
|
+
export declare const MANAGED_BLOCK_BEGIN = "# -- dsh-loop-engine managed block";
|
|
39
|
+
/** End marker of the plugin-managed span inside a profile patch file. */
|
|
40
|
+
export declare const MANAGED_BLOCK_END = "# -- /dsh-loop-engine managed block --";
|
|
41
|
+
/**
|
|
42
|
+
* Render the permanent managed block. The plugin always owns the AgentFactory
|
|
43
|
+
* slot, so the base `agent-loop` row is always disabled.
|
|
44
|
+
*/
|
|
45
|
+
export declare function renderManagedBlock(): string;
|
|
46
|
+
/** Whether a patch-file text contains the managed block span. */
|
|
47
|
+
export declare function hasManagedBlock(text: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Ensure the permanent managed block is present, preserving every byte outside
|
|
50
|
+
* the managed span. Appends the span when absent; rewrites it in place when
|
|
51
|
+
* present, which also upgrades a legacy engine-tagged marker from the era when
|
|
52
|
+
* the block encoded the selected engine.
|
|
53
|
+
*
|
|
54
|
+
* This is a belt-and-braces layer, not the mechanism that disables the base
|
|
55
|
+
* loop. Disabling `agent-loop` is a boot-time fact and only the bundle patch can
|
|
56
|
+
* state it: the loader applies bundle layers while expanding entries, whereas
|
|
57
|
+
* this profile layer is applied afterwards and this very write happens inside
|
|
58
|
+
* apply() — by which point `AgentLoop`'s constructor has already claimed the
|
|
59
|
+
* sole AgentFactory slot. The row therefore lives in this package's own
|
|
60
|
+
* `cordis.patch.yml`, and what lands here merely re-disables an already-disabled
|
|
61
|
+
* row, which is idempotent. It is kept so a profile that pins an older bundle,
|
|
62
|
+
* or that lists `agent-loop` itself, still boots.
|
|
63
|
+
*
|
|
64
|
+
* The file must always parse as a top-level YAML *array*: app-boot's
|
|
65
|
+
* `parsePatchList` throws `must be a top-level YAML array of loader patch
|
|
66
|
+
* entries` on anything else, which fails the whole plugin tree — including this
|
|
67
|
+
* plugin's own `insert` row, so no agent factory registers at all.
|
|
68
|
+
*
|
|
69
|
+
* A fresh profile's file is `[]`, a complete flow-style document. Block
|
|
70
|
+
* sequence items cannot follow it, so the `[]` is dropped when the block goes
|
|
71
|
+
* in. The reverse direction no longer exists: the block is permanent, so it is
|
|
72
|
+
* never removed and can never leave a comments-only file behind.
|
|
73
|
+
*
|
|
74
|
+
* @param text - current patch-file text.
|
|
75
|
+
* @returns the rewritten patch-file text.
|
|
76
|
+
*/
|
|
77
|
+
export declare function applyManagedBlock(text: string): string;
|
|
78
|
+
//# sourceMappingURL=patch-manager.d.ts.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session engine routing: the single AgentFactory slot owner, plus the
|
|
3
|
+
* service shadows that let every engine keep its unmodified registration calls
|
|
4
|
+
* while its three siblings are mounted alongside it.
|
|
5
|
+
*
|
|
6
|
+
* The harness admits exactly one AgentFactory (`ctx.agents.setFactory` throws
|
|
7
|
+
* on a second registration), which historically forced the engine to be a
|
|
8
|
+
* process-wide choice. This module inverts that: one {@link LoopEngineRouter}
|
|
9
|
+
* occupies the slot for the life of the process and dispatches each call to
|
|
10
|
+
* the engine that owns the session, so four engines coexist and a session's
|
|
11
|
+
* engine is fixed at creation.
|
|
12
|
+
*
|
|
13
|
+
* The engines themselves are untouched. Each is a `Service` whose constructor
|
|
14
|
+
* runs `ctx.effect(() => ctx.agents.setFactory(this))`; mounting one through a
|
|
15
|
+
* context whose `agents` member is {@link shadowAgents} redirects that call
|
|
16
|
+
* into router registration instead. `ctx.isolate('agents')` cannot do this —
|
|
17
|
+
* a second `AgentRegistry` calls `ctx.accessor('agent', …)`, which is not
|
|
18
|
+
* isolate-scoped, and the duplicate declaration fails the fiber — so the shadow
|
|
19
|
+
* wraps the *same* registry and overrides exactly one method.
|
|
20
|
+
*
|
|
21
|
+
* {@link shadowSystemPrompt} resolves the second collision the same way, for
|
|
22
|
+
* the prompt variables every engine constructor registers under the same names.
|
|
23
|
+
*
|
|
24
|
+
* @module dsh-agent-hub/router
|
|
25
|
+
*/
|
|
26
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
27
|
+
import type { AgentFactory, AgentHandle, CreateAgentOptions, ResumeAgentOptions } from '@deepseek-ai/dsh-agent';
|
|
28
|
+
import type { LoopEngineId } from './settings.ts';
|
|
29
|
+
/**
|
|
30
|
+
* The subset of the agent registry the shadow must reproduce. Only
|
|
31
|
+
* `setFactory` is intercepted; everything else forwards to the real service.
|
|
32
|
+
*/
|
|
33
|
+
interface AgentsLike {
|
|
34
|
+
setFactory(factory: AgentFactory): () => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Wrap the real `agents` service so `setFactory` registers with the router
|
|
38
|
+
* instead of the harness slot.
|
|
39
|
+
*
|
|
40
|
+
* Every other member forwards to the real registry. Methods are bound to the
|
|
41
|
+
* target rather than the proxy: `AgentRegistry` keeps its state in private
|
|
42
|
+
* fields, and an unbound method invoked with the proxy as receiver would trip
|
|
43
|
+
* the brand check on those fields.
|
|
44
|
+
*
|
|
45
|
+
* @param agents - the real `ctx.agents` service.
|
|
46
|
+
* @param capture - receives the factory an engine tries to register; its return is handed back as the disposer.
|
|
47
|
+
* @returns a stand-in service to install via `ctx.extend({ agents })`.
|
|
48
|
+
*/
|
|
49
|
+
export declare function shadowAgents<T extends AgentsLike>(agents: T, capture: (factory: AgentFactory) => () => void): T;
|
|
50
|
+
/** Resolve the engine that owns a session being resumed. */
|
|
51
|
+
export type EngineResolver = (sessionId: string) => Promise<LoopEngineId> | LoopEngineId;
|
|
52
|
+
/**
|
|
53
|
+
* The subset of the system-prompt service the shadow must reproduce. Only
|
|
54
|
+
* `variable` is intercepted; everything else forwards to the real service.
|
|
55
|
+
*/
|
|
56
|
+
interface SystemPromptLike {
|
|
57
|
+
variable(name: string, provider: (context: any) => string | undefined): () => void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Wrap the real `systemPrompt` service so a prompt-variable name that a sibling
|
|
61
|
+
* engine already claimed is ignored instead of throwing.
|
|
62
|
+
*
|
|
63
|
+
* Every engine constructor registers `provider`, `model`, and `cwd` on its
|
|
64
|
+
* mount context. That context carries no scope tag, so all three registrations
|
|
65
|
+
* target the one global prompt layer and the second engine to mount dies with
|
|
66
|
+
* `prompt variable "provider" is already registered`. Under the old design only
|
|
67
|
+
* one engine was ever mounted, so the collision could not arise; hosting all
|
|
68
|
+
* four makes it certain.
|
|
69
|
+
*
|
|
70
|
+
* Scoping the mount contexts would not help: an agent mints its own scope with
|
|
71
|
+
* `createScope(loopCtx, this)` and no `parent`, so `scopeChainOf(agent)` is just
|
|
72
|
+
* `[agent]` — a variable parked on an engine-level scope would be invisible to
|
|
73
|
+
* that engine's own agents. Deduplicating on the global layer is therefore the
|
|
74
|
+
* correct level.
|
|
75
|
+
*
|
|
76
|
+
* Collapsing the three registrations to one is sound because they are
|
|
77
|
+
* byte-identical across the engines and engine-independent by construction:
|
|
78
|
+
* each reads the *assembling* agent's own options (`context.agent?.options`) or
|
|
79
|
+
* its session header, so whichever engine registered first serves every
|
|
80
|
+
* engine's agents with that agent's own values.
|
|
81
|
+
*
|
|
82
|
+
* @param systemPrompt - the real `ctx.systemPrompt` service.
|
|
83
|
+
* @param claim - decides whether this context may own `name`; false means a sibling already did.
|
|
84
|
+
* @returns a stand-in service to install via `ctx.extend({ systemPrompt })`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function shadowSystemPrompt<T extends SystemPromptLike>(systemPrompt: T, claim: (name: string) => boolean): T;
|
|
87
|
+
/** Compose extra per-agent registrations into the caller's setup callback. */
|
|
88
|
+
export type SetupDecorator = (engine: LoopEngineId, agentCtx: Context) => void;
|
|
89
|
+
/** Wiring the router needs from the plugin that owns it. */
|
|
90
|
+
export interface RouterOptions {
|
|
91
|
+
/**
|
|
92
|
+
* The engine a new session gets when it carries no reservation: the profile
|
|
93
|
+
* default from settings.
|
|
94
|
+
*/
|
|
95
|
+
engineForNewSession(): LoopEngineId;
|
|
96
|
+
/**
|
|
97
|
+
* Record a new session's engine durably, before the engine creates it.
|
|
98
|
+
*
|
|
99
|
+
* Takes the session's `meta` rather than its id alone: the persistence
|
|
100
|
+
* backend keys a session's artifact directory by `cwd`, so a record written
|
|
101
|
+
* without it lands in a directory no resume will look in.
|
|
102
|
+
*/
|
|
103
|
+
remember(meta: {
|
|
104
|
+
readonly id: string;
|
|
105
|
+
readonly cwd?: string;
|
|
106
|
+
}, engine: LoopEngineId): Promise<void>;
|
|
107
|
+
/** Recover a persisted session's engine. */
|
|
108
|
+
resolve: EngineResolver;
|
|
109
|
+
/**
|
|
110
|
+
* Registers engine-specific per-agent contributions (skill providers) into
|
|
111
|
+
* the agent's own scope layer. Invoked with the scope-tagged `agentCtx` the
|
|
112
|
+
* engine passes to `setup`, so the harness's layered skill registry filters
|
|
113
|
+
* them to that session automatically.
|
|
114
|
+
*/
|
|
115
|
+
decorateSetup?: SetupDecorator;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The process's sole AgentFactory. Holds one candidate per engine and forwards
|
|
119
|
+
* `createAgent` / `resume` to the right one.
|
|
120
|
+
*
|
|
121
|
+
* Forwarding is contract-compliant by construction: both factory methods are
|
|
122
|
+
* per-call and take the caller's `ownerCtx`, which the contract requires the
|
|
123
|
+
* implementation to attach lifecycle to ("it must not infer ownership from the
|
|
124
|
+
* factory object's registration context"). The router passes it through
|
|
125
|
+
* verbatim, so the engine owns the transaction exactly as if it had been
|
|
126
|
+
* registered directly.
|
|
127
|
+
*/
|
|
128
|
+
export declare class LoopEngineRouter implements AgentFactory {
|
|
129
|
+
private readonly options;
|
|
130
|
+
private readonly engines;
|
|
131
|
+
/**
|
|
132
|
+
* Engines claimed for session ids that do not exist yet.
|
|
133
|
+
*
|
|
134
|
+
* Agent creation is eager — the harness calls `createAgent` when a session is
|
|
135
|
+
* opened, not when its first prompt is sent — so a client that wants a
|
|
136
|
+
* specific engine cannot ask for it after the fact. Instead it mints the
|
|
137
|
+
* session id itself, reserves the engine here, and only then asks the host to
|
|
138
|
+
* create that id. Insertion order is the eviction order (Map preserves it).
|
|
139
|
+
*/
|
|
140
|
+
private readonly reservations;
|
|
141
|
+
constructor(options: RouterOptions);
|
|
142
|
+
/**
|
|
143
|
+
* Claim an engine for a session id the caller is about to create.
|
|
144
|
+
*
|
|
145
|
+
* The reservation is consumed by the first `createAgent` for that id, so it
|
|
146
|
+
* cannot leak into a later session that happens to reuse the id.
|
|
147
|
+
*
|
|
148
|
+
* @param sessionId - id the caller will pass to session creation.
|
|
149
|
+
* @param engine - engine that session must run on.
|
|
150
|
+
*/
|
|
151
|
+
reserve(sessionId: string, engine: LoopEngineId): void;
|
|
152
|
+
/**
|
|
153
|
+
* Take the engine a new session should run on: its reservation when it has
|
|
154
|
+
* one, the profile default otherwise.
|
|
155
|
+
*/
|
|
156
|
+
private claim;
|
|
157
|
+
/**
|
|
158
|
+
* Register one engine's factory as a routing candidate.
|
|
159
|
+
* @param engine - the engine id this factory implements.
|
|
160
|
+
* @param factory - the engine's factory, captured from its `setFactory` call.
|
|
161
|
+
* @returns a disposer that withdraws the candidate.
|
|
162
|
+
*/
|
|
163
|
+
register(engine: LoopEngineId, factory: AgentFactory): () => void;
|
|
164
|
+
/** The shadow to install on the context that mounts `engine`. */
|
|
165
|
+
shadowFor<T extends AgentsLike>(engine: LoopEngineId, agents: T): T;
|
|
166
|
+
/**
|
|
167
|
+
* Look up a mounted engine, failing loud when it is selected but absent.
|
|
168
|
+
* A silent fallback would run the session on an engine that cannot read its
|
|
169
|
+
* history, which is the exact defect per-session routing exists to remove.
|
|
170
|
+
*/
|
|
171
|
+
private factoryFor;
|
|
172
|
+
/** Wrap the caller's setup so the engine's per-agent registrations land in the agent's scope. */
|
|
173
|
+
private withSetup;
|
|
174
|
+
/**
|
|
175
|
+
* Create a session on the engine it reserved, or on the profile default when
|
|
176
|
+
* it made no reservation. The engine is recorded durably *before* delegation:
|
|
177
|
+
* a crash between the two leaves an orphan record (harmless — no session ever
|
|
178
|
+
* reads it), whereas the reverse order would leave a session whose engine
|
|
179
|
+
* cannot be recovered.
|
|
180
|
+
*
|
|
181
|
+
* `options.meta.cwd` is forwarded to the record so the sidecar lands in the
|
|
182
|
+
* same per-project directory the backend will write the session into.
|
|
183
|
+
*/
|
|
184
|
+
createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise<AgentHandle>;
|
|
185
|
+
/** Resume a session on the engine that created it. */
|
|
186
|
+
resume(ownerCtx: Context, options: ResumeAgentOptions): Promise<AgentHandle>;
|
|
187
|
+
}
|
|
188
|
+
export {};
|
|
189
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/loop-engine` Connection RPC channel: the browser's only route to the
|
|
3
|
+
* node half's per-session engine state.
|
|
4
|
+
*
|
|
5
|
+
* Two facts about the harness make this channel necessary rather than merely
|
|
6
|
+
* convenient:
|
|
7
|
+
*
|
|
8
|
+
* - **Agent creation is eager.** `createAgent` fires when a session is
|
|
9
|
+
* *opened*, not when its first prompt is sent, so by the time a user can
|
|
10
|
+
* click anything in the composer the engine is already chosen. A client that
|
|
11
|
+
* wants a specific engine must therefore say so *before* creating the
|
|
12
|
+
* session — hence `bind`, which reserves an engine for a session id the
|
|
13
|
+
* client mints itself.
|
|
14
|
+
* - **The engine lives in a node-side sidecar.** Nothing in the session log,
|
|
15
|
+
* header, or settings carries it (see {@link ../engine-record.ts} for why
|
|
16
|
+
* each of those is closed), so the browser cannot read it from any existing
|
|
17
|
+
* projection — hence `resolve`.
|
|
18
|
+
*
|
|
19
|
+
* A custom Connection channel is the smallest seam that provides both. The
|
|
20
|
+
* alternative, a `@Remote` service on the typert gateway, would work on the
|
|
21
|
+
* host but requires hand-written strict zod descriptors on the client
|
|
22
|
+
* (`requireStrictDescriptor` rejects reflection-derived ones), which is a large
|
|
23
|
+
* contract surface for two internal methods. This plugin owns both ends of this
|
|
24
|
+
* channel, so it validates its own payloads and needs no codec at all.
|
|
25
|
+
*
|
|
26
|
+
* @module dsh-agent-hub/rpc
|
|
27
|
+
*/
|
|
28
|
+
import { type LoopEngineId } from './settings.ts';
|
|
29
|
+
/** Channel path this plugin owns. Reserved names (`/api`) are refused by the host. */
|
|
30
|
+
export declare const LOOP_ENGINE_RPC_CHANNEL = "/loop-engine";
|
|
31
|
+
/** Carrier-neutral result shape the Connection RPC transport expects back. */
|
|
32
|
+
export type RpcResult<T> = {
|
|
33
|
+
readonly ok: true;
|
|
34
|
+
readonly value: T;
|
|
35
|
+
} | {
|
|
36
|
+
readonly ok: false;
|
|
37
|
+
readonly error: {
|
|
38
|
+
readonly code: string;
|
|
39
|
+
readonly message: string;
|
|
40
|
+
readonly details: object;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/** One logical endpoint handler, as `connection.rpc.handle` supplies it. */
|
|
44
|
+
export type RpcHandler = (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<RpcResult<unknown>>;
|
|
45
|
+
/**
|
|
46
|
+
* Trust fence the host applies before a request reaches this channel's handler.
|
|
47
|
+
*
|
|
48
|
+
* `trusted-host` accepts any authority in the deployment's configured
|
|
49
|
+
* `trustedHosts`; `loopback` narrows to loopback only. This channel takes
|
|
50
|
+
* `trusted-host` because it must work wherever the composer works — a dsh
|
|
51
|
+
* reachable over a LAN authority serves the same UI, and a stricter fence would
|
|
52
|
+
* 403 the seat's own requests on exactly those deployments.
|
|
53
|
+
*/
|
|
54
|
+
export type ConnectionRpcAuthority = 'trusted-host' | 'loopback';
|
|
55
|
+
/** Registration policy `rpc.handle` requires for a channel. */
|
|
56
|
+
export interface ConnectionRpcHandlerOptions {
|
|
57
|
+
/** Browser authority accepted by every endpoint in this channel. */
|
|
58
|
+
readonly authority: ConnectionRpcAuthority;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The host Connection surface this module borrows. Declared structurally so the
|
|
62
|
+
* plugin needs no peer dependency on the connection package, and so a profile
|
|
63
|
+
* without a Connection (headless, SDK) degrades instead of failing to mount.
|
|
64
|
+
*
|
|
65
|
+
* `options` is **required**, and getting that wrong fails in a way worth
|
|
66
|
+
* spelling out: the host reads `options.authority` on the first line of
|
|
67
|
+
* `register`, so omitting the argument throws `Cannot read properties of
|
|
68
|
+
* undefined (reading 'authority')` *inside the effect*. Cordis swallows that
|
|
69
|
+
* into the fiber, the plugin keeps running, every engine still works — and the
|
|
70
|
+
* route is simply never added, so every request to the channel falls through to
|
|
71
|
+
* the plugin-bundle server and comes back 405. Nothing logs at the default
|
|
72
|
+
* level. Structural typing is what let this compile: the declaration below is
|
|
73
|
+
* the plugin's own, so it cannot disagree with the installed package unless it
|
|
74
|
+
* is kept in step with it by hand.
|
|
75
|
+
*/
|
|
76
|
+
export interface ConnectionLike {
|
|
77
|
+
readonly rpc: {
|
|
78
|
+
handle(channel: string, handler: RpcHandler, options: ConnectionRpcHandlerOptions): () => Promise<void>;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** The router operations this channel drives. */
|
|
82
|
+
export interface EngineRpcTarget {
|
|
83
|
+
/** Claim an engine for a session id the client is about to create. */
|
|
84
|
+
reserve(sessionId: string, engine: LoopEngineId): void;
|
|
85
|
+
/** Read back the engine a session is actually bound to. */
|
|
86
|
+
recall(sessionId: string): Promise<LoopEngineId>;
|
|
87
|
+
/** The profile default, for a session that has no record yet. */
|
|
88
|
+
fallback(): LoopEngineId;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Build the `/loop-engine` handler.
|
|
92
|
+
*
|
|
93
|
+
* Exported separately from {@link installEngineRpc} so the endpoints are
|
|
94
|
+
* testable without a Connection.
|
|
95
|
+
*
|
|
96
|
+
* @param target - the router and record store operations to drive.
|
|
97
|
+
* @returns the handler to register on the channel.
|
|
98
|
+
*/
|
|
99
|
+
export declare function createEngineRpcHandler(target: EngineRpcTarget): RpcHandler;
|
|
100
|
+
/**
|
|
101
|
+
* Register the channel when the profile has a Connection.
|
|
102
|
+
*
|
|
103
|
+
* The Connection is injected conditionally by the gateway, so a headless or SDK
|
|
104
|
+
* profile legitimately has none. There the plugin keeps working — per-session
|
|
105
|
+
* routing is a node-side property — and only the browser controls degrade.
|
|
106
|
+
*
|
|
107
|
+
* @param connection - the host connection, or undefined when absent.
|
|
108
|
+
* @param register - installs the handler under the plugin's effect lifecycle.
|
|
109
|
+
* @param target - the router and record store operations to drive.
|
|
110
|
+
* @returns whether the channel was registered.
|
|
111
|
+
*/
|
|
112
|
+
export declare function installEngineRpc(connection: ConnectionLike | undefined, register: (install: () => () => Promise<void>, label: string) => void, target: EngineRpcTarget): boolean;
|
|
113
|
+
//# sourceMappingURL=rpc.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared loop-engine identity, namespace, and schema.
|
|
3
|
+
*
|
|
4
|
+
* The namespace literal lives in the zero-import `./namespace.ts` so both
|
|
5
|
+
* halves agree on the section name: the node half brands it through
|
|
6
|
+
* `settingsNamespace()` (a runtime value), while the browser half imports the
|
|
7
|
+
* same literal without pulling the host-side `dsh-settings` service into the
|
|
8
|
+
* client bundle (cross-plugin value imports go through cordis services, and
|
|
9
|
+
* `settings-scope.ts` follows the same discipline).
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-agent-hub/settings
|
|
12
|
+
*/
|
|
13
|
+
import z from '@deepseek-ai/schemastery';
|
|
14
|
+
import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
15
|
+
import { type LoopEngineId } from './namespace.ts';
|
|
16
|
+
export { LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL } from './namespace.ts';
|
|
17
|
+
export { LOOP_ENGINE_IDS, type LoopEngineId } from './namespace.ts';
|
|
18
|
+
/** Stored and composed loop engine selection. */
|
|
19
|
+
export interface LoopEngineSettings {
|
|
20
|
+
/** The engine future Agents are created on. */
|
|
21
|
+
engine: LoopEngineId;
|
|
22
|
+
/** Whether the composer's loop engine picker is shown on the chat page. */
|
|
23
|
+
showInComposer: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Schema of the loop engine settings section. */
|
|
26
|
+
export declare const LOOP_ENGINE_SETTINGS_SCHEMA: z<LoopEngineSettings>;
|
|
27
|
+
/** Brand the shared literal through the settings API on the node side. */
|
|
28
|
+
export declare function loopEngineSettingsNamespace(): SettingsNamespace;
|
|
29
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code skill provider: discovers skills from the project's `.claude/`
|
|
3
|
+
* directory, the user-level `~/.claude/skills/` directory, and the project's
|
|
4
|
+
* `CLAUDE.md` file, using the same YAML-frontmatter + markdown format as DSH
|
|
5
|
+
* skills.
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-agent-hub/skills
|
|
8
|
+
*/
|
|
9
|
+
export interface SkillInvocationPolicy {
|
|
10
|
+
readonly modelInvocable: boolean;
|
|
11
|
+
readonly userInvocable: boolean;
|
|
12
|
+
}
|
|
13
|
+
export type SkillSource = string;
|
|
14
|
+
export interface SkillCandidate {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly description: string;
|
|
17
|
+
readonly whenToUse?: string;
|
|
18
|
+
readonly invocation: SkillInvocationPolicy;
|
|
19
|
+
readonly source: SkillSource;
|
|
20
|
+
readonly provider: string;
|
|
21
|
+
readonly rank: number;
|
|
22
|
+
readonly locator: unknown;
|
|
23
|
+
readonly path?: string;
|
|
24
|
+
readonly resourceBase?: {
|
|
25
|
+
readonly kind: string;
|
|
26
|
+
readonly path: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface SkillDefinition {
|
|
30
|
+
readonly name: string;
|
|
31
|
+
readonly description: string;
|
|
32
|
+
readonly whenToUse?: string;
|
|
33
|
+
readonly invocation: SkillInvocationPolicy;
|
|
34
|
+
readonly source: SkillSource;
|
|
35
|
+
readonly provider: string;
|
|
36
|
+
readonly content: string;
|
|
37
|
+
readonly path?: string;
|
|
38
|
+
readonly resourceBase?: {
|
|
39
|
+
readonly kind: string;
|
|
40
|
+
readonly path: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface SkillLookupOptions {
|
|
44
|
+
readonly cwd?: string;
|
|
45
|
+
readonly signal?: AbortSignal;
|
|
46
|
+
}
|
|
47
|
+
export interface SkillProvider {
|
|
48
|
+
readonly name: string;
|
|
49
|
+
list(options: SkillLookupOptions): Promise<readonly SkillCandidate[] | {
|
|
50
|
+
candidates: readonly SkillCandidate[];
|
|
51
|
+
complete: boolean;
|
|
52
|
+
}>;
|
|
53
|
+
get(candidate: SkillCandidate, options: SkillLookupOptions): Promise<SkillDefinition | undefined>;
|
|
54
|
+
}
|
|
55
|
+
export interface SkillProviderControl {
|
|
56
|
+
readonly signal: AbortSignal;
|
|
57
|
+
invalidate(): void;
|
|
58
|
+
}
|
|
59
|
+
/** One parsed agents-skill standard skill: frontmatter metadata plus the body. */
|
|
60
|
+
export interface ParsedSkill {
|
|
61
|
+
name: string;
|
|
62
|
+
description: string;
|
|
63
|
+
whenToUse?: string;
|
|
64
|
+
invocation: SkillInvocationPolicy;
|
|
65
|
+
content: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parse one agents-skill standard file: YAML frontmatter (`name`,
|
|
69
|
+
* `description`, optional `whenToUse`/`disable-model-invocation`/
|
|
70
|
+
* `user-invocable`) plus the markdown body.
|
|
71
|
+
* @param raw - the raw file content.
|
|
72
|
+
* @returns the parsed skill, or `undefined` when the file is not a skill.
|
|
73
|
+
*/
|
|
74
|
+
export declare function parseSkillFile(raw: string): ParsedSkill | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Skill provider that discovers skills from Claude Code's standard locations:
|
|
77
|
+
* - `<project>/.claude/skills/` — project skills
|
|
78
|
+
* - `~/.claude/skills/` — personal skills
|
|
79
|
+
* Each location accepts both Claude Code layouts: a `<name>/SKILL.md`
|
|
80
|
+
* directory (its directory becomes the resource base) and a flat `<name>.md`
|
|
81
|
+
* file. `CLAUDE.md` in the project root is also read when it carries skill
|
|
82
|
+
* frontmatter.
|
|
83
|
+
*/
|
|
84
|
+
export declare class ClaudeCodeSkillProvider implements SkillProvider {
|
|
85
|
+
private readonly control;
|
|
86
|
+
readonly name = "claude-code";
|
|
87
|
+
constructor(control: SkillProviderControl);
|
|
88
|
+
list(options: SkillLookupOptions): Promise<readonly SkillCandidate[]>;
|
|
89
|
+
get(candidate: SkillCandidate, _options: SkillLookupOptions): Promise<SkillDefinition | undefined>;
|
|
90
|
+
}
|
|
91
|
+
export declare function findProjectRoot(cwd: string): Promise<string>;
|
|
92
|
+
export default ClaudeCodeSkillProvider;
|
|
93
|
+
//# sourceMappingURL=skills.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vidge/dsh-agent-hub",
|
|
3
|
+
"description": "Per-session agent loop engine router for DSH — run Claude Code, Codex, pi, or the in-process loop per session, all on DSH's unified session, LLM, and tracing substrate",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.1.0-rc1",
|
|
8
|
+
"repository": "github:vidgewong/dsh-agent-hub",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "lib/index.js",
|
|
11
|
+
"types": "lib/types/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./lib/types/index.d.ts",
|
|
15
|
+
"default": "./lib/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./invariant": {
|
|
18
|
+
"types": "./lib/types/invariant.d.ts",
|
|
19
|
+
"default": "./lib/invariant.js"
|
|
20
|
+
},
|
|
21
|
+
"./client": {
|
|
22
|
+
"types": "./lib/types/client/index.d.ts",
|
|
23
|
+
"default": "./lib/client.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"README.md",
|
|
31
|
+
"lib/index.js",
|
|
32
|
+
"lib/invariant.js",
|
|
33
|
+
"lib/client.js",
|
|
34
|
+
"lib/types/**/*.d.ts",
|
|
35
|
+
"cordis.patch.yml"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "node build.mjs",
|
|
40
|
+
"typecheck": "tsc -p tsconfig.build.json --noEmit",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:coverage": "vitest run --coverage"
|
|
43
|
+
},
|
|
44
|
+
"dsh": {
|
|
45
|
+
"bundle": {
|
|
46
|
+
"patch": "./cordis.patch.yml"
|
|
47
|
+
},
|
|
48
|
+
"client": {
|
|
49
|
+
"inject": [
|
|
50
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
51
|
+
"@deepseek-ai/dsh-client-locale",
|
|
52
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
53
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
54
|
+
"@deepseek-ai/dsh-client-connection",
|
|
55
|
+
"@deepseek-ai/dsh-api-remotes"
|
|
56
|
+
],
|
|
57
|
+
"platform": "web"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@anthropic-ai/sdk": "0.93.0",
|
|
62
|
+
"@deepseek-ai/schemastery": "3.18.1"
|
|
63
|
+
},
|
|
64
|
+
"optionalDependencies": {
|
|
65
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.220",
|
|
66
|
+
"@earendil-works/pi-coding-agent": "0.84.3",
|
|
67
|
+
"@openai/codex": "0.149.1"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
71
|
+
"@deepseek-ai/dsh-agent": "0.1.1-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-agent-loop": "0.1.1-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-llm": "0.1.1-rc.2 || 0.1.2-alpha.2",
|
|
75
|
+
"@deepseek-ai/dsh-scope": "0.1.1-rc.2",
|
|
76
|
+
"@deepseek-ai/dsh-session": "0.1.1-rc.2",
|
|
77
|
+
"@deepseek-ai/dsh-session-persistence": "0.1.1-rc.2",
|
|
78
|
+
"@deepseek-ai/dsh-settings": "0.1.1-rc.2",
|
|
79
|
+
"@deepseek-ai/dsh-subprocess": "0.1.1-rc.2",
|
|
80
|
+
"@deepseek-ai/dsh-timeout": "0.1.1-rc.2"
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@deepseek-ai/dsh-attachment": "0.1.1-rc.2",
|
|
84
|
+
"@deepseek-ai/dsh-agent-loop": "0.1.1-rc.2",
|
|
85
|
+
"@deepseek-ai/dsh-client-locale": "0.1.1-rc.2",
|
|
86
|
+
"@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
|
|
87
|
+
"@deepseek-ai/dsh-client-store": "0.1.2-alpha.2",
|
|
88
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.1-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.1-rc.2",
|
|
91
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.1-rc.2",
|
|
94
|
+
"@deepseek-ai/dsh-subprocess-local": "0.1.1-rc.2",
|
|
95
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.1-rc.2",
|
|
96
|
+
"@types/node": "^22.20.0",
|
|
97
|
+
"@types/react": "^19.0.0",
|
|
98
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
99
|
+
"esbuild": "^0.28.2",
|
|
100
|
+
"tsx": "^4.22.4",
|
|
101
|
+
"typescript": "^6.0.3",
|
|
102
|
+
"vite": "^7.0.0",
|
|
103
|
+
"vite-tsconfig-paths": "^6.1.1",
|
|
104
|
+
"vitest": "^4.1.8",
|
|
105
|
+
"yaml": "^2.9.0"
|
|
106
|
+
}
|
|
107
|
+
}
|