@deepseek-ai/dsh-agent-presets 0.0.1-rc.1
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 +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +140 -0
- package/README.zh.md +140 -0
- package/lib/index.js +1130 -0
- package/lib/invariant.js +1123 -0
- package/lib/types/authoring.d.ts +86 -0
- package/lib/types/discovery.d.ts +41 -0
- package/lib/types/index.d.ts +273 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/metadata.d.ts +52 -0
- package/lib/types/mount.d.ts +112 -0
- package/lib/types/session.d.ts +47 -0
- package/lib/types/types.d.ts +83 -0
- package/package.json +66 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copying, reading, and deleting locally authored presets.
|
|
3
|
+
*
|
|
4
|
+
* Authoring is confined to a `user` root: the shipped `.system` set is part of
|
|
5
|
+
* the deployment, and letting a browser rewrite it would turn "reset to a known
|
|
6
|
+
* preset" into something the same caller could have broken first.
|
|
7
|
+
*
|
|
8
|
+
* The only authoring write is a whole-directory copy of an existing preset.
|
|
9
|
+
* No caller supplies composition text: the inputs are ids the host resolves
|
|
10
|
+
* against its own roots plus an optional display name, so authoring grants no
|
|
11
|
+
* capability the copied preset did not already carry.
|
|
12
|
+
* @module @deepseek-ai/dsh-agent-presets/authoring
|
|
13
|
+
*/
|
|
14
|
+
import { type AgentPreset, type PresetRoot } from './types.ts';
|
|
15
|
+
/** A preset id that cannot be used as a directory name under a root. */
|
|
16
|
+
export declare class InvalidPresetIdError extends Error {
|
|
17
|
+
/** The rejected id. */
|
|
18
|
+
readonly presetId: string;
|
|
19
|
+
constructor(
|
|
20
|
+
/** The rejected id. */
|
|
21
|
+
presetId: string);
|
|
22
|
+
}
|
|
23
|
+
/** A copy target that is already occupied — a copy never overwrites. */
|
|
24
|
+
export declare class PresetExistsError extends Error {
|
|
25
|
+
/** The id that is already taken. */
|
|
26
|
+
readonly presetId: string;
|
|
27
|
+
constructor(
|
|
28
|
+
/** The id that is already taken. */
|
|
29
|
+
presetId: string);
|
|
30
|
+
}
|
|
31
|
+
/** Authoring was attempted where the deployment allows none. */
|
|
32
|
+
export declare class PresetNotWritableError extends Error {
|
|
33
|
+
/** What the caller tried to change, for the diagnostic. */
|
|
34
|
+
readonly presetId: string;
|
|
35
|
+
constructor(
|
|
36
|
+
/** What the caller tried to change, for the diagnostic. */
|
|
37
|
+
presetId: string, reason: string);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The root locally authored presets are written to.
|
|
41
|
+
* @param roots - the configured roots in precedence order.
|
|
42
|
+
* @returns the absolute path of the first `user` root.
|
|
43
|
+
* @throws when the deployment configured no writable root.
|
|
44
|
+
*/
|
|
45
|
+
export declare function writableRoot(roots: readonly PresetRoot[]): string;
|
|
46
|
+
/**
|
|
47
|
+
* Read one preset's composition text.
|
|
48
|
+
* @param preset - the resolved preset.
|
|
49
|
+
* @returns the file's contents.
|
|
50
|
+
*/
|
|
51
|
+
export declare function readComposition(preset: AgentPreset): Promise<string>;
|
|
52
|
+
/**
|
|
53
|
+
* Create a preset by copying an existing one's whole directory.
|
|
54
|
+
*
|
|
55
|
+
* The copy carries everything the source directory holds — composition,
|
|
56
|
+
* metadata, skill directories, assets — because a preset is its directory,
|
|
57
|
+
* not one file. Symlinks are dereferenced so the copy is self-contained
|
|
58
|
+
* rather than a set of links back into the install it was copied from.
|
|
59
|
+
*
|
|
60
|
+
* The copied metadata is then rewritten: the source's description is kept
|
|
61
|
+
* (the file is the author's to edit afterwards), but its name and roster
|
|
62
|
+
* `order` are not — a copy presenting itself identically to its source, or
|
|
63
|
+
* sorted into the shipped set's declared order, would make the roster stop
|
|
64
|
+
* distinguishing them. With no name given and no description to keep, the
|
|
65
|
+
* file is removed so the copy publishes nothing rather than a blank.
|
|
66
|
+
* @param roots - the configured roots; the first `user` one receives the copy.
|
|
67
|
+
* @param source - the resolved preset the copy starts from.
|
|
68
|
+
* @param id - the new preset's id, which becomes its directory name.
|
|
69
|
+
* @param name - display name for the copy; omitted falls back to the id.
|
|
70
|
+
* @returns the absolute path of the new preset directory.
|
|
71
|
+
* @throws when the id is unusable or already occupied on disk, or the
|
|
72
|
+
* deployment configures no writable root.
|
|
73
|
+
*/
|
|
74
|
+
export declare function copyComposition(roots: readonly PresetRoot[], source: AgentPreset, id: string, name?: string): Promise<string>;
|
|
75
|
+
/**
|
|
76
|
+
* Delete a locally authored preset.
|
|
77
|
+
*
|
|
78
|
+
* A shipped preset is refused: it belongs to the deployment. A preset a live
|
|
79
|
+
* session mounted is NOT refused — the composition was read at creation and is
|
|
80
|
+
* never re-read, so that session keeps running exactly as it was.
|
|
81
|
+
* @param roots - the configured roots.
|
|
82
|
+
* @param preset - the resolved preset to remove.
|
|
83
|
+
* @throws when the preset ships with the deployment or lies outside the writable root.
|
|
84
|
+
*/
|
|
85
|
+
export declare function deleteComposition(roots: readonly PresetRoot[], preset: AgentPreset): Promise<void>;
|
|
86
|
+
//# sourceMappingURL=authoring.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem discovery of agent presets. A preset is a directory holding
|
|
3
|
+
* {@link COMPOSITION_FILE}, optionally beside a {@link METADATA_FILE} carrying
|
|
4
|
+
* its display text; the directory name is the preset id. Discovery
|
|
5
|
+
* re-reads the roots on every call so a preset authored while the process is
|
|
6
|
+
* running is visible without a restart.
|
|
7
|
+
*
|
|
8
|
+
* Discovery also owns preset HEALTH: a directory whose composition is
|
|
9
|
+
* missing or unloadable is reported as a broken roster row rather than
|
|
10
|
+
* skipped. A skipped directory would still occupy its id on disk — the copy
|
|
11
|
+
* path refuses the name while no surface shows anything to delete — and a
|
|
12
|
+
* malformed composition would otherwise read as an ordinary preset until the
|
|
13
|
+
* first session fails to mount it.
|
|
14
|
+
* @module @deepseek-ai/dsh-agent-presets/discovery
|
|
15
|
+
*/
|
|
16
|
+
import { type AgentPreset, type PresetRoot } from './types.ts';
|
|
17
|
+
/** The composition file that makes a directory a preset. */
|
|
18
|
+
export declare const COMPOSITION_FILE = "agent.cordis.yml";
|
|
19
|
+
/**
|
|
20
|
+
* Scan one root for preset directories.
|
|
21
|
+
*
|
|
22
|
+
* An absent root yields no presets rather than throwing: the user root does
|
|
23
|
+
* not exist until the first locally authored preset, and naming a default
|
|
24
|
+
* that no root supplies already fails loud at resolution.
|
|
25
|
+
*
|
|
26
|
+
* Every directory whose name is a usable preset id is a roster row — broken
|
|
27
|
+
* when its composition is missing or unloadable. A directory named outside
|
|
28
|
+
* {@link PRESET_ID} is skipped instead: no copy could ever claim that name,
|
|
29
|
+
* so it blocks nothing, and reporting `.DS_Store`-grade residue as broken
|
|
30
|
+
* presets would teach users to ignore the marker.
|
|
31
|
+
* @param root - the directory and the trust its presets inherit.
|
|
32
|
+
* @returns the root's presets ordered by id.
|
|
33
|
+
*/
|
|
34
|
+
export declare function scanRoot(root: PresetRoot): Promise<AgentPreset[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Scan every root in precedence order.
|
|
37
|
+
* @param roots - roots in precedence order; an earlier root wins a duplicate id.
|
|
38
|
+
* @returns every discovered preset, first-root-wins per id.
|
|
39
|
+
*/
|
|
40
|
+
export declare function discoverPresets(roots: readonly PresetRoot[]): Promise<AgentPreset[]>;
|
|
41
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent presets: each session composes its model-facing plugin set from one
|
|
3
|
+
* preset `cordis.yml`, mounted ONCE per preset under a standing scope and
|
|
4
|
+
* joined by every agent that names it.
|
|
5
|
+
*
|
|
6
|
+
* The standing mount is what makes a preset one composition rather than one
|
|
7
|
+
* per session: its plugin instances, tool registrations, prompt sections, and
|
|
8
|
+
* projection units exist exactly once, keyed per session inside the plugins
|
|
9
|
+
* themselves (they predate presets and were written for a shared world). An
|
|
10
|
+
* agent joins by having its scope key parented to the mount's
|
|
11
|
+
* ({@link bindScopeParent}), which makes the mount's registrations visible to
|
|
12
|
+
* that agent's views and the mount's listeners receive that agent's events —
|
|
13
|
+
* and a host reader with no agent at all (a cold transcript read) resolves
|
|
14
|
+
* the same standing registrations by preset id.
|
|
15
|
+
*
|
|
16
|
+
* This package owns the preset vocabulary, filesystem discovery, and the
|
|
17
|
+
* guarded standing mount. It does not decide when an agent is created — the
|
|
18
|
+
* agent factory's `setup(agentCtx)` hook is the one supported call site,
|
|
19
|
+
* because only there is the join installed while the agent is still
|
|
20
|
+
* unpublished, so a rejected composition rolls the whole creation back.
|
|
21
|
+
* @module @deepseek-ai/dsh-agent-presets
|
|
22
|
+
*/
|
|
23
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
24
|
+
import z from '@deepseek-ai/schemastery';
|
|
25
|
+
import { type ScopeKey } from '@deepseek-ai/dsh-scope';
|
|
26
|
+
import { type AgentPreset, type Config } from './types.ts';
|
|
27
|
+
/** Settings namespace carrying the user's chosen default preset. */
|
|
28
|
+
export declare const SETTINGS_NAMESPACE = "agent-presets";
|
|
29
|
+
/** The user-writable slice of this plugin's config. */
|
|
30
|
+
export interface AgentPresetSettings {
|
|
31
|
+
/** Preset mounted when a session names none. */
|
|
32
|
+
default?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Runtime schema for the user-writable slice. */
|
|
35
|
+
export declare const AgentPresetSettingsSchema: z<AgentPresetSettings>;
|
|
36
|
+
export { COMPOSITION_FILE, discoverPresets, scanRoot } from './discovery.ts';
|
|
37
|
+
export { METADATA_FILE, readPresetMetadata, renderPresetMetadata, type PresetMetadata, } from './metadata.ts';
|
|
38
|
+
export { inactiveRows, leakedServices, livePresetMounts, mountPreset, serviceForAgent, standingMountFor, type JoinedPresetMount, type PresetMount, } from './mount.ts';
|
|
39
|
+
export { copyComposition, deleteComposition, InvalidPresetIdError, PresetExistsError, PresetNotWritableError, readComposition, writableRoot, } from './authoring.ts';
|
|
40
|
+
export { resolveSessionPreset, type PresetBearingSession } from './session.ts';
|
|
41
|
+
export { PresetMountError, UnknownPresetError } from './types.ts';
|
|
42
|
+
export type { AgentPreset, Config, PresetRoot, PresetTrust } from './types.ts';
|
|
43
|
+
declare module '@deepseek-ai/cordis' {
|
|
44
|
+
interface Context {
|
|
45
|
+
agentPresets: AgentPresets;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Registry over the deployment's agent presets.
|
|
50
|
+
*
|
|
51
|
+
* Discovery is unmemoized: `list()` and `resolve()` re-read the roots on every
|
|
52
|
+
* call so a preset authored while the process runs is visible immediately,
|
|
53
|
+
* and a preset deleted underneath a picker disappears from the next read.
|
|
54
|
+
*/
|
|
55
|
+
export declare class AgentPresets extends Service {
|
|
56
|
+
config: Config;
|
|
57
|
+
static inject: string[];
|
|
58
|
+
/** Runtime schema for the preset roster. */
|
|
59
|
+
static Config: z<Config>;
|
|
60
|
+
/**
|
|
61
|
+
* The user layer over `config.default`, present only while a settings
|
|
62
|
+
* provider is composed. Held rather than snapshotted so a hot-reloaded
|
|
63
|
+
* document takes effect without a restart.
|
|
64
|
+
*/
|
|
65
|
+
private settings;
|
|
66
|
+
/**
|
|
67
|
+
* The settings service behind {@link settings}, held for the one write this
|
|
68
|
+
* service makes: clearing a user default it has just deleted.
|
|
69
|
+
*/
|
|
70
|
+
private settingsService;
|
|
71
|
+
/**
|
|
72
|
+
* The service's own untraced context. Methods invoked through the traceable
|
|
73
|
+
* proxy see `this.ctx` rebound to the CALLER's context, which carries a
|
|
74
|
+
* shadow; a subtree minted from it resolves every service through that
|
|
75
|
+
* shadow's fiber instead of each entry's own inject store, so preset rows
|
|
76
|
+
* would fail on the very services they declare. Standing mounts must hang
|
|
77
|
+
* off the untraced original (the `tasks-local` selfCtx precedent).
|
|
78
|
+
*/
|
|
79
|
+
private readonly selfCtx;
|
|
80
|
+
constructor(ctx: Context, config: Config);
|
|
81
|
+
/**
|
|
82
|
+
* The preset id mounted when a caller names none.
|
|
83
|
+
*
|
|
84
|
+
* Read per call rather than cached: the settings document is hot-reloaded, so
|
|
85
|
+
* changing the default takes effect on the next session created and leaves
|
|
86
|
+
* every running session on the preset it was composed from.
|
|
87
|
+
*/
|
|
88
|
+
get defaultId(): string;
|
|
89
|
+
/**
|
|
90
|
+
* Every preset the configured roots currently supply.
|
|
91
|
+
* @returns the presets, first-root-wins per id.
|
|
92
|
+
*/
|
|
93
|
+
list(): Promise<AgentPreset[]>;
|
|
94
|
+
/**
|
|
95
|
+
* Resolve one preset by id.
|
|
96
|
+
*
|
|
97
|
+
* A broken preset resolves — deleting one, reading one, and reporting one
|
|
98
|
+
* all need the row — and the mounting paths refuse it AFTER resolution
|
|
99
|
+
* through {@link resolveMountable}.
|
|
100
|
+
* @param id - the preset id, or `undefined` for {@link defaultId}.
|
|
101
|
+
* @returns the resolved preset.
|
|
102
|
+
* @throws when no configured root supplies that id.
|
|
103
|
+
*/
|
|
104
|
+
resolve(id?: string): Promise<AgentPreset>;
|
|
105
|
+
/**
|
|
106
|
+
* Resolve one preset that is about to compose an agent, refusing a broken
|
|
107
|
+
* one with its discovery-reported reason. Failing here rather than inside
|
|
108
|
+
* the loader keeps the answer the same for every unloadable shape — ghost
|
|
109
|
+
* directory, unparsable YAML, rowless list — and spends no mount attempt
|
|
110
|
+
* on a composition discovery already read as unusable.
|
|
111
|
+
* @param id - the preset id, or `undefined` for {@link defaultId}.
|
|
112
|
+
* @returns the resolved, mountable preset.
|
|
113
|
+
* @throws when the preset is unknown or discovery reports it broken.
|
|
114
|
+
*/
|
|
115
|
+
private resolveMountable;
|
|
116
|
+
/**
|
|
117
|
+
* Standing mounts by preset id, single-flight so two agents racing the
|
|
118
|
+
* first use of one preset share one composition. A settled failure is
|
|
119
|
+
* removed so a later session retries a preset whose file has been fixed; a
|
|
120
|
+
* settled success serves until the composition FILE visibly changes — each
|
|
121
|
+
* generation records its file stamp, and a stale stamp starts the next
|
|
122
|
+
* generation for sessions created afterwards. Sessions already joined keep
|
|
123
|
+
* the generation they run on; a superseded one is never disposed while the
|
|
124
|
+
* process lives (reclaimed only by whole-tree teardown), so editing files
|
|
125
|
+
* is bounded by how often compositions change, not by session count.
|
|
126
|
+
*/
|
|
127
|
+
private readonly standing;
|
|
128
|
+
/**
|
|
129
|
+
* Parent bindings of the agents this roster composed, keyed by the agent's
|
|
130
|
+
* scope key. The binding is dsh-scope's only re-link capability; holding it
|
|
131
|
+
* here makes this service the sole authority that can move an agent between
|
|
132
|
+
* standing compositions. WeakMap: entries die with their agents.
|
|
133
|
+
*/
|
|
134
|
+
private readonly bindings;
|
|
135
|
+
/**
|
|
136
|
+
* Compose one agent from a preset: ensure the preset's standing mount, then
|
|
137
|
+
* parent the agent's scope key to it so the mount's registrations and
|
|
138
|
+
* listeners cover this agent.
|
|
139
|
+
*
|
|
140
|
+
* Call from the agent factory's `setup(agentCtx)`; a rejection there rolls
|
|
141
|
+
* the agent creation back, so a broken preset never yields a half-composed
|
|
142
|
+
* session.
|
|
143
|
+
* @param agentCtx - the agent's scope context.
|
|
144
|
+
* @param id - the preset id, or `undefined` for {@link defaultId}.
|
|
145
|
+
* @returns the preset that was composed, for the caller to record.
|
|
146
|
+
* @throws when the preset is unknown or its composition is unusable.
|
|
147
|
+
*/
|
|
148
|
+
mount(agentCtx: Context, id?: string): Promise<AgentPreset>;
|
|
149
|
+
/**
|
|
150
|
+
* Join one agent to the SAME standing composition another already runs on.
|
|
151
|
+
*
|
|
152
|
+
* This is how a child agent inherits its parent's capabilities. It is a bind,
|
|
153
|
+
* not a mount: the parent's generation is already composed, so the child gets
|
|
154
|
+
* that exact instance — the same plugin objects, the same tool registrations,
|
|
155
|
+
* the same prompt sections. Re-resolving the parent's preset by id instead
|
|
156
|
+
* would re-read the roster, and a composition file edited since the parent
|
|
157
|
+
* started would hand the child a DIFFERENT generation than the one its
|
|
158
|
+
* parent's history was produced under (and a preset deleted since would fail
|
|
159
|
+
* the child outright while its parent keeps running).
|
|
160
|
+
*
|
|
161
|
+
* Synchronous, and with no composition failure mode of its own — it reads no
|
|
162
|
+
* roster, mounts nothing, and touches no file — which is what lets a child
|
|
163
|
+
* creation window use it: the two in-process subagent drivers compose their
|
|
164
|
+
* children inside a synchronous `setup`. It still rejects a caller error, as
|
|
165
|
+
* the `@throws` below record.
|
|
166
|
+
*
|
|
167
|
+
* A parent that joined no preset — a rosterless deployment — yields no join
|
|
168
|
+
* and no error: there, the model-facing rows sit in the host composition and
|
|
169
|
+
* the child already sees them through the global layer.
|
|
170
|
+
* @param agentCtx - the joining agent's scope context.
|
|
171
|
+
* @param parentCtx - the scope context of the agent whose composition to join.
|
|
172
|
+
* @returns the preset id joined, or undefined when the parent joined none.
|
|
173
|
+
* @throws when `agentCtx` carries no scope, or has already joined a preset.
|
|
174
|
+
*/
|
|
175
|
+
composeFrom(agentCtx: Context, parentCtx: Context): string | undefined;
|
|
176
|
+
/**
|
|
177
|
+
* The preset one live agent runs on.
|
|
178
|
+
*
|
|
179
|
+
* Read from the live scope chain rather than from the session, so it answers
|
|
180
|
+
* for an agent whose session has not recorded a preset yet — a child agent
|
|
181
|
+
* whose durable header is being built from its parent's composition.
|
|
182
|
+
* @param agentCtx - the agent's scope context.
|
|
183
|
+
* @returns the preset id, or undefined when the agent joined none.
|
|
184
|
+
*/
|
|
185
|
+
composedPreset(agentCtx: Context): string | undefined;
|
|
186
|
+
/** Whether this deployment configures a root locally authored presets go to. */
|
|
187
|
+
get authorable(): boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Read one preset's composition text.
|
|
190
|
+
* @param id - the preset id.
|
|
191
|
+
* @returns the composition exactly as stored.
|
|
192
|
+
* @throws when no configured root supplies that id.
|
|
193
|
+
*/
|
|
194
|
+
read(id: string): Promise<string>;
|
|
195
|
+
/**
|
|
196
|
+
* Create a locally authored preset by copying an existing one whole.
|
|
197
|
+
*
|
|
198
|
+
* Copy is the only authoring write. Composition text never crosses this
|
|
199
|
+
* seam: the source is named by id and its directory is copied as it stands,
|
|
200
|
+
* so the copy is exactly as loadable as its source and authoring grants no
|
|
201
|
+
* capability the roster did not already carry. The copy is NOT mounted to
|
|
202
|
+
* validate — a source that mounts today yields a copy that mounts today.
|
|
203
|
+
* @param from - the preset the copy starts from; shipped presets are the
|
|
204
|
+
* primary source, so any trust is accepted.
|
|
205
|
+
* @param id - the new preset's id, which becomes its directory name.
|
|
206
|
+
* @param name - display name for the copy; absent falls back to the id.
|
|
207
|
+
* @throws when the source is unknown, the id is unusable or already taken,
|
|
208
|
+
* or the deployment configures no writable root.
|
|
209
|
+
*/
|
|
210
|
+
copy(from: string, id: string, name?: string): Promise<void>;
|
|
211
|
+
/**
|
|
212
|
+
* Delete a locally authored preset.
|
|
213
|
+
* @param id - the preset id.
|
|
214
|
+
* @throws when the preset is unknown or ships with the deployment.
|
|
215
|
+
*/
|
|
216
|
+
remove(id: string): Promise<void>;
|
|
217
|
+
/**
|
|
218
|
+
* One agent's instance of a service its preset mounted.
|
|
219
|
+
*
|
|
220
|
+
* A preset publishes services behind `isolate` realms, which are invisible
|
|
221
|
+
* outside the group that declares them — including to the host. This is how a
|
|
222
|
+
* caller holding the agent reads one anyway: a request that is ABOUT a
|
|
223
|
+
* session but arrives from outside it, which is every browser RPC.
|
|
224
|
+
*
|
|
225
|
+
* Read addressing only. A host row that `inject`s a service cannot use this,
|
|
226
|
+
* because injection resolves before any session exists and has no agent to
|
|
227
|
+
* key by; such a service belongs on the host plane instead.
|
|
228
|
+
* @param agent - the agent whose composition to look inside.
|
|
229
|
+
* @param name - the service name as the preset's rows resolve it.
|
|
230
|
+
* @returns the agent's instance, or undefined when its preset mounts none.
|
|
231
|
+
*/
|
|
232
|
+
serviceFor<K extends string & keyof Context>(agent: {
|
|
233
|
+
ctx: Context;
|
|
234
|
+
}, name: K): Context[K] | undefined;
|
|
235
|
+
/**
|
|
236
|
+
* Re-link one agent to a different preset's standing composition.
|
|
237
|
+
*
|
|
238
|
+
* Only valid while the agent has produced nothing: swapping tools mid
|
|
239
|
+
* conversation would leave logged tool calls the new composition cannot
|
|
240
|
+
* make. The CALLER owns that check — this method does not read session
|
|
241
|
+
* history.
|
|
242
|
+
*
|
|
243
|
+
* The swap is a parent re-link, not an unmount: standing mounts are shared
|
|
244
|
+
* and permanent, so the old composition stays for its other agents and the
|
|
245
|
+
* new one is ensured BEFORE the link moves. An unknown or unusable preset
|
|
246
|
+
* therefore throws with the agent exactly as it was — there is no torn-down
|
|
247
|
+
* state to restore. The re-link runs through the binding this roster kept
|
|
248
|
+
* from the agent's mount — dsh-scope's only re-link authority. An agent
|
|
249
|
+
* that never composed one has nothing to re-link: the switch is then the
|
|
250
|
+
* agent's first bind, exactly a mount.
|
|
251
|
+
* @param agentCtx - the agent's scope context.
|
|
252
|
+
* @param id - the preset to compose the agent from instead.
|
|
253
|
+
* @returns the preset now installed.
|
|
254
|
+
* @throws when the preset is unknown or its composition is unusable.
|
|
255
|
+
*/
|
|
256
|
+
recompose(agentCtx: Context, id: string): Promise<AgentPreset>;
|
|
257
|
+
/**
|
|
258
|
+
* The standing scope key of one preset, for a host reader with no agent.
|
|
259
|
+
*
|
|
260
|
+
* A cold transcript read resolves tool presenters against the composition
|
|
261
|
+
* the session recorded, and the standing mount makes that possible without
|
|
262
|
+
* resuming anything: ensuring the mount composes plugins but starts no
|
|
263
|
+
* agent, no session, and no turn.
|
|
264
|
+
* @param id - the preset id, or `undefined` for {@link defaultId}.
|
|
265
|
+
* @returns the standing scope key readers pass as a registry view scope.
|
|
266
|
+
* @throws when the preset is unknown or its composition is unusable.
|
|
267
|
+
*/
|
|
268
|
+
standingKeyFor(id?: string): Promise<ScopeKey>;
|
|
269
|
+
/** Resolve (or create, single-flight) the standing mount of one preset. */
|
|
270
|
+
private ensureStanding;
|
|
271
|
+
}
|
|
272
|
+
export default AgentPresets;
|
|
273
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-agent-presets`.
|
|
3
|
+
* @module @deepseek-ai/dsh-agent-presets/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "agent-presets-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
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,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A preset's display metadata: the name and description a picker shows.
|
|
3
|
+
*
|
|
4
|
+
* It lives in its own file because the composition is a top-level list of
|
|
5
|
+
* plugin rows — YAML cannot carry sibling keys beside it, and faking a
|
|
6
|
+
* metadata row would hand the Loader something to load. Keeping it separate
|
|
7
|
+
* also keeps the composition exactly what its name says: a Cordis file the
|
|
8
|
+
* loader owns and the cordis preset can author.
|
|
9
|
+
*
|
|
10
|
+
* The file carries display text ONLY. `id` is the directory name and `trust`
|
|
11
|
+
* comes from the root a preset was discovered under, so neither is writable
|
|
12
|
+
* here — otherwise a locally authored preset could claim to be a shipped one.
|
|
13
|
+
*
|
|
14
|
+
* Every read failure degrades to no metadata. A preset whose display text is
|
|
15
|
+
* missing, malformed, or unreadable still mounts: presentation is not a
|
|
16
|
+
* capability, and a broken name must never become an agent that cannot start.
|
|
17
|
+
* @module @deepseek-ai/dsh-agent-presets/metadata
|
|
18
|
+
*/
|
|
19
|
+
/** The optional display-metadata file beside a preset's composition. */
|
|
20
|
+
export declare const METADATA_FILE = "preset.yml";
|
|
21
|
+
/** Display text a preset may publish about itself. */
|
|
22
|
+
export interface PresetMetadata {
|
|
23
|
+
/** Human-facing name; falls back to the preset id when absent. */
|
|
24
|
+
readonly name?: string;
|
|
25
|
+
/** One sentence on what this preset is for. */
|
|
26
|
+
readonly description?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Position within its group; lower comes first. A preset that declares
|
|
29
|
+
* none sorts after every preset that does, then by id — so the shipped set
|
|
30
|
+
* can read in capability order while authored ones stay alphabetical.
|
|
31
|
+
*/
|
|
32
|
+
readonly order?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read one preset directory's display metadata.
|
|
36
|
+
*
|
|
37
|
+
* Absent, unparsable, and wrongly-shaped files are all the same answer —
|
|
38
|
+
* empty metadata — because the caller renders a picker, not a diagnostic.
|
|
39
|
+
* @param directory - the preset directory.
|
|
40
|
+
* @returns the display text the preset published, possibly empty.
|
|
41
|
+
*/
|
|
42
|
+
export declare function readPresetMetadata(directory: string): Promise<PresetMetadata>;
|
|
43
|
+
/**
|
|
44
|
+
* Render display metadata as the file's contents.
|
|
45
|
+
*
|
|
46
|
+
* Absent fields are omitted rather than written empty, so a preset with no
|
|
47
|
+
* description does not ship a key that reads as an intentional blank.
|
|
48
|
+
* @param metadata - the display text to store.
|
|
49
|
+
* @returns the YAML document, or undefined when there is nothing to store.
|
|
50
|
+
*/
|
|
51
|
+
export declare function renderPresetMetadata(metadata: PresetMetadata): string | undefined;
|
|
52
|
+
//# sourceMappingURL=metadata.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mount one preset composition under an agent's scope context, then prove the
|
|
3
|
+
* result is usable before the agent is published.
|
|
4
|
+
*
|
|
5
|
+
* The scope context is what makes the composition per-session: entry contexts
|
|
6
|
+
* chain to the context the subtree was plugged into, so every `ctx.tools`
|
|
7
|
+
* and `ctx.systemPrompt` registration inside the preset files into that
|
|
8
|
+
* agent's layer and unwinds with it. Two guards make that safe. A row that
|
|
9
|
+
* never reached a usable state is rejected, because a directly-plugged subtree
|
|
10
|
+
* is absent from `ctx.loader.entries()` and no boot audit covers it. A row that
|
|
11
|
+
* published a service into the ROOT realm is rejected, because such a service
|
|
12
|
+
* is process-global rather than per-session and the second session mounting the
|
|
13
|
+
* same preset collides with the first.
|
|
14
|
+
* @module @deepseek-ai/dsh-agent-presets/mount
|
|
15
|
+
*/
|
|
16
|
+
import { Context, type Fiber } from '@deepseek-ai/cordis';
|
|
17
|
+
import type { EntryTree } from '@deepseek-ai/cordis-plugin-loader';
|
|
18
|
+
import { type ScopeKey } from '@deepseek-ai/dsh-scope';
|
|
19
|
+
import { type AgentPreset } from './types.ts';
|
|
20
|
+
/** One preset composition currently installed under some agent. */
|
|
21
|
+
export interface PresetMount {
|
|
22
|
+
/** The preset the subtree was composed from. */
|
|
23
|
+
readonly presetId: string;
|
|
24
|
+
/** The mounted subtree's fiber. */
|
|
25
|
+
readonly fiber: Fiber;
|
|
26
|
+
/** The standing scope key agents are parented to (undefined only in torn-down records). */
|
|
27
|
+
readonly key: ScopeKey | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Every preset composition still installed, pruning fibers disposed since the
|
|
31
|
+
* last read.
|
|
32
|
+
* @returns the live mounts.
|
|
33
|
+
*/
|
|
34
|
+
export declare function livePresetMounts(): PresetMount[];
|
|
35
|
+
/**
|
|
36
|
+
* Service names the mounted subtree published into the root realm.
|
|
37
|
+
*
|
|
38
|
+
* A provider without an `isolate` realm stores its implementation under the
|
|
39
|
+
* root's symbol for that name, which is exactly the comparison below; a
|
|
40
|
+
* provider inside an `isolate` realm stores under a realm-private symbol and
|
|
41
|
+
* is correctly absent here.
|
|
42
|
+
* @param ctx - any context of the runtime whose service store is inspected.
|
|
43
|
+
* @param mount - the mounted subtree's fiber.
|
|
44
|
+
* @returns the leaked service names in lexical order.
|
|
45
|
+
*/
|
|
46
|
+
export declare function leakedServices(ctx: Context, mount: Fiber): string[];
|
|
47
|
+
/** A live standing mount located through one agent already joined to it. */
|
|
48
|
+
export type JoinedPresetMount = PresetMount & {
|
|
49
|
+
/** The standing key, definite because it is what the lookup matched on. */
|
|
50
|
+
readonly key: ScopeKey;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The standing composition one agent is joined to.
|
|
54
|
+
*
|
|
55
|
+
* The agent's own key is parented to its preset's standing key, so the mount
|
|
56
|
+
* is found by matching that parent rather than by walking up from the agent —
|
|
57
|
+
* the mount is not under the agent's fiber. An agent that joined no preset —
|
|
58
|
+
* a deployment composing no roster, or a child agent before its join — has no
|
|
59
|
+
* parent link and resolves to undefined.
|
|
60
|
+
* @param agentCtx - the agent's scope context.
|
|
61
|
+
* @returns the mount the agent joined, or undefined when it joined none.
|
|
62
|
+
*/
|
|
63
|
+
export declare function standingMountFor(agentCtx: Context): JoinedPresetMount | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* One agent's instance of a service its preset mounted.
|
|
66
|
+
*
|
|
67
|
+
* A preset publishes a service behind an `isolate` realm so two sessions
|
|
68
|
+
* cannot collide, and an entry-local realm is invisible to everything outside
|
|
69
|
+
* the group — including the agent's own scope context and the host. That is
|
|
70
|
+
* right for the rows inside the group and wrong for one caller: a request that
|
|
71
|
+
* is ABOUT a session but arrives from outside it, which is every browser RPC
|
|
72
|
+
* the api-proxy serves.
|
|
73
|
+
*
|
|
74
|
+
* Ownership is the same relation {@link leakedServices} reads, inverted: there
|
|
75
|
+
* it names implementations a subtree published into the ROOT realm, here it
|
|
76
|
+
* names the one this subtree published anywhere. Fiber membership is object
|
|
77
|
+
* identity for the reason stated on {@link withinFiber}.
|
|
78
|
+
*
|
|
79
|
+
* This is READ addressing for a caller that already holds the agent. It is not
|
|
80
|
+
* a general host handle on a session's internals: a host row that `inject`s a
|
|
81
|
+
* service cannot use it, because injection resolves before any session exists
|
|
82
|
+
* and has no agent to key by — such a service belongs on the host plane.
|
|
83
|
+
* @param ctx - any context of the runtime whose service store is inspected.
|
|
84
|
+
* @param agent - the agent whose mounted composition to look inside.
|
|
85
|
+
* @param name - the service name as the preset's rows resolve it.
|
|
86
|
+
* @returns the agent's instance, or undefined when its preset mounts none.
|
|
87
|
+
*/
|
|
88
|
+
export declare function serviceForAgent<K extends string & keyof Context>(ctx: Context, agent: {
|
|
89
|
+
ctx: Context;
|
|
90
|
+
}, name: K): Context[K] | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Rows that did not reach a usable state, each rendered as one diagnostic line.
|
|
93
|
+
*
|
|
94
|
+
* A row whose module failed to import or whose plugin threw already rejects the
|
|
95
|
+
* mount through the loader; what remains observable here is a row still waiting
|
|
96
|
+
* for a service the composition never supplies.
|
|
97
|
+
* @param tree - the mounted subtree.
|
|
98
|
+
* @returns one line per unusable row, empty when every enabled row is usable.
|
|
99
|
+
*/
|
|
100
|
+
export declare function inactiveRows(tree: EntryTree): string[];
|
|
101
|
+
/**
|
|
102
|
+
* Mount `preset` under `agentCtx` and return only once every row is usable.
|
|
103
|
+
*
|
|
104
|
+
* The subtree is owned by `agentCtx`'s fiber, so it unwinds with the agent and
|
|
105
|
+
* the caller receives no disposer. A rejection leaves nothing mounted.
|
|
106
|
+
* @param agentCtx - the agent's scope context, from the agent factory's `setup`.
|
|
107
|
+
* @param preset - the resolved preset to compose the agent from.
|
|
108
|
+
* @throws when `agentCtx` carries no scope, a row is unusable, or a row
|
|
109
|
+
* published a service into the root realm.
|
|
110
|
+
*/
|
|
111
|
+
export declare function mountPreset(agentCtx: Context, preset: AgentPreset): Promise<void>;
|
|
112
|
+
//# sourceMappingURL=mount.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session-log record of which preset a session actually runs.
|
|
3
|
+
*
|
|
4
|
+
* The creation header names the preset a session STARTED with, and it is
|
|
5
|
+
* deep-frozen because that is a creation fact. A session may still change
|
|
6
|
+
* preset while it is blank, and the effect of that change outlives the blank
|
|
7
|
+
* window: the first turn — and every turn after it — runs under the newly
|
|
8
|
+
* mounted composition. Recording the change is what keeps the log honest, and
|
|
9
|
+
* it is required outright by the repo's model-visible ⟺ logged rule, since the
|
|
10
|
+
* preset decides the tool schemas and prompt sections the model sees.
|
|
11
|
+
*
|
|
12
|
+
* Reconstruction reads {@link resolveSessionPreset}, never the header alone.
|
|
13
|
+
* @module @deepseek-ai/dsh-agent-presets/session
|
|
14
|
+
*/
|
|
15
|
+
import type { SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session';
|
|
16
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
17
|
+
interface SessionEventMap {
|
|
18
|
+
/**
|
|
19
|
+
* The session's agent preset was chosen after creation, while the session
|
|
20
|
+
* was still blank. Log-only: it records the composition later turns ran
|
|
21
|
+
* under, so a resumed or forked session rebuilds the same one instead of
|
|
22
|
+
* the header's creation-time value.
|
|
23
|
+
*/
|
|
24
|
+
'agent-preset/selected': {
|
|
25
|
+
agentPreset: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** The minimum a caller must supply to resolve a session's preset. */
|
|
30
|
+
export interface PresetBearingSession {
|
|
31
|
+
/** The session's creation header. */
|
|
32
|
+
readonly header: SessionHeader;
|
|
33
|
+
/** The session's event log, oldest first. */
|
|
34
|
+
readonly events: readonly SessionEvent[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The preset a session actually runs, newest selection winning.
|
|
38
|
+
*
|
|
39
|
+
* The header supplies the creation-time value; every later selection is a
|
|
40
|
+
* logged event, so the last one is the answer. Reading the header alone
|
|
41
|
+
* rebuilds a switched session under the composition it was created with, not
|
|
42
|
+
* the one its history was produced under.
|
|
43
|
+
* @param session - the session's header and event log.
|
|
44
|
+
* @returns the preset id, or `undefined` when the deployment composes none.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveSessionPreset(session: PresetBearingSession): string | undefined;
|
|
47
|
+
//# sourceMappingURL=session.d.ts.map
|