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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.en.md +150 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +151 -0
  4. package/cordis.patch.yml +24 -0
  5. package/lib/client.js +1115 -0
  6. package/lib/dsh-cli-provider-UhWvivte.js +1701 -0
  7. package/lib/dsh-cli-provider.js +3 -0
  8. package/lib/index.js +624 -0
  9. package/lib/invariant.js +33 -0
  10. package/lib/provision.js +602 -0
  11. package/lib/records.js +109 -0
  12. package/lib/session-log-DOsSz56-.js +88 -0
  13. package/lib/types/client/SettingsCard.d.ts +78 -0
  14. package/lib/types/client/SettingsCard.js +200 -0
  15. package/lib/types/client/icons.d.ts +18 -0
  16. package/lib/types/client/icons.js +2 -0
  17. package/lib/types/client/index.d.ts +45 -0
  18. package/lib/types/client/index.js +88 -0
  19. package/lib/types/client/locales.d.ts +76 -0
  20. package/lib/types/client/locales.js +67 -0
  21. package/lib/types/dsh-cli-provider.d.ts +213 -0
  22. package/lib/types/dsh-cli-provider.js +733 -0
  23. package/lib/types/index.d.ts +124 -0
  24. package/lib/types/index.js +347 -0
  25. package/lib/types/invariant.d.ts +16 -0
  26. package/lib/types/invariant.js +37 -0
  27. package/lib/types/live-driver.d.ts +180 -0
  28. package/lib/types/live-driver.js +887 -0
  29. package/lib/types/live-switch.d.ts +76 -0
  30. package/lib/types/live-switch.js +100 -0
  31. package/lib/types/model-broker.d.ts +86 -0
  32. package/lib/types/model-broker.js +149 -0
  33. package/lib/types/model-catalog.d.ts +27 -0
  34. package/lib/types/model-catalog.js +71 -0
  35. package/lib/types/provision.d.ts +382 -0
  36. package/lib/types/provision.js +656 -0
  37. package/lib/types/records.d.ts +41 -0
  38. package/lib/types/records.js +125 -0
  39. package/lib/types/session-log.d.ts +58 -0
  40. package/lib/types/session-log.js +83 -0
  41. package/lib/types/session-mirror.d.ts +155 -0
  42. package/lib/types/session-mirror.js +451 -0
  43. package/locale/en.json +6 -0
  44. package/locale/zh.json +6 -0
  45. package/package.json +121 -0
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Provision the sub-dsh's headless profile inside the harness scoped home.
3
+ * The sub-dsh runs `dsh --profile <name>` with `$DSH_HOME` pointed at the
4
+ * scoped home, so its profile must live under
5
+ * `<scoped home>/profiles/<name>` — a manifest listing the official
6
+ * `@deepseek-ai/dsh-base` layer, the family headless composition as the
7
+ * profile's own patch layer (copied verbatim from the headless bundle's
8
+ * `cordis.patch.yml`), and a single symlink resolving
9
+ * `@khorsheed/dsh-local-agent-dsh-headless` from the sub-profile's
10
+ * node_modules (the loader resolves the patch's insert rows through it).
11
+ * Everything else the boot needs (dsh-base and its whole dependency graph,
12
+ * cordis, the healed `profiles/node_modules` fallback) is resolved from the
13
+ * dsh installation anchor automatically, so provisioning costs no pnpm
14
+ * install.
15
+ *
16
+ * A sub-profile may additionally carry a PRESET ROSTER: one appended patch
17
+ * layer inserting `@deepseek-ai/dsh-agent-presets` with a `default` preset
18
+ * id. Without it the sub-dsh's model-facing rows come from the host plane and
19
+ * the agent reads them off the global layer — which is why "the same harness
20
+ * under two presets" was not a thing a condition could ask for. With it, the
21
+ * scope directory IS the capability face: two scopes whose rosters name two
22
+ * presets are two subjects, and the difference is a file a person can read.
23
+ *
24
+ * Where the preset DIRECTORY lives is the scope's business too. The roster
25
+ * derives a user root of `<$DSH_HOME>/.agent-presets` and the sub-dsh runs
26
+ * with `DSH_HOME` pointed at the scoped home, so a scope that keeps its own
27
+ * copy there is resolved identically on the host and inside an evaluation
28
+ * unit (which bind-mounts the scope, and nothing else). `provisionDshScope`
29
+ * below is the entry that puts the copy there, records what the scope
30
+ * composes in a file of its own, and rosters it with no `roots` at all.
31
+ *
32
+ * It may also carry a PERMISSION BOUNDARY: one appended patch layer pinning
33
+ * `sandbox-policy`'s mode and `user-approval`'s policy to one of dsh's own
34
+ * three permission presets. Without it the sub-dsh runs whatever `dsh-base`
35
+ * composes — `workspace-write` plus `ask` — which is the right default for a
36
+ * sub-dsh on a developer's machine and the wrong one inside an evaluation
37
+ * unit, where the container IS the boundary and nobody is there to answer an
38
+ * approval prompt. The layer is a FILE in the scope directory for the same
39
+ * reason the roster is: a scoped home bind-mounted into a unit carries its
40
+ * own boundary with it, and the value never has to ride the spawn env (where
41
+ * every name enters the unit's composite fingerprint) or be baked into an
42
+ * image.
43
+ *
44
+ * The headless bundle declares no `dsh.bundle` on purpose (the T6/G3
45
+ * incident): a declaration would let the host's `dsh plugin` reconcile mount
46
+ * its sub-dsh-only composition into any profile where the package is a
47
+ * direct dependency — duplicate `ptc-runtime` at best. The patch therefore
48
+ * reaches the sub-profile through this copy, not through the manifest
49
+ * declaration; being the profile's own layer, it lands after every bundle
50
+ * layer, the same position the headless bundle layer used to occupy.
51
+ * @module @khorsheed/dsh-local-agent-dsh/provision
52
+ */
53
+ /** Default name of the sub-dsh profile under the scoped home. */
54
+ export declare const DEFAULT_SUB_PROFILE_NAME = "headless-local-agent-dsh";
55
+ /** One preset root the sub-profile's roster scans. */
56
+ export interface DshSubProfilePresetRoot {
57
+ /** Directory holding preset sub-directories; a leading `~` is expanded by the roster. */
58
+ path: string;
59
+ /** Trust level; the roster defaults it to `user`. */
60
+ trust?: 'system' | 'user';
61
+ }
62
+ /**
63
+ * The preset roster a sub-profile composes.
64
+ *
65
+ * `id` is the preset every sub-dsh session of this scope runs on. With no
66
+ * `roots`, the roster's own derived roots apply: the presets shipped inside
67
+ * `@deepseek-ai/dsh-agent-presets`, then `<scoped home>/.agent-presets` —
68
+ * the sub-dsh runs with `DSH_HOME` pointed at the scoped home, so dropping a
69
+ * preset directory THERE is how a scope gets a preset of its own.
70
+ */
71
+ export interface DshSubProfilePreset {
72
+ /** The preset id the sub-dsh composes (`[a-z0-9][a-z0-9-]*`, a directory name). */
73
+ id: string;
74
+ /** Extra roots to scan, in precedence order. */
75
+ roots?: readonly DshSubProfilePresetRoot[];
76
+ /** Keep the roster's bundled presets (default true, the roster's own default). */
77
+ includeShippedRoot?: boolean;
78
+ /** Keep `<scoped home>/.agent-presets` (default true, the roster's own default). */
79
+ includeUserRoot?: boolean;
80
+ }
81
+ /**
82
+ * A sub-dsh permission preset: dsh's own three-word vocabulary, spelled the
83
+ * way `@deepseek-ai/dsh-base` spells it in its `permission-presets` table.
84
+ * Each word names BOTH halves of the boundary — the sandbox mode and the
85
+ * approval policy — because that is what the table pairs them as.
86
+ */
87
+ export type DshSubProfilePermissions = 'read-only' | 'workspace-write' | 'danger-full-access';
88
+ /** Config the provisioning step reads. */
89
+ export interface DshSubProfileConfig {
90
+ /** Sub-dsh profile name under the scoped home. */
91
+ profileName?: string;
92
+ /**
93
+ * Override the headless bundle directory the sub-profile symlinks to.
94
+ * Absent means resolve `@khorsheed/dsh-local-agent-dsh-headless` from this
95
+ * package's own installation.
96
+ */
97
+ headlessBundleDir?: string;
98
+ /**
99
+ * Compose a preset roster into the sub-profile. Absent leaves the
100
+ * sub-profile exactly as every scope had it before this field existed: no
101
+ * roster row, and the agent reading the global layer.
102
+ */
103
+ preset?: DshSubProfilePreset;
104
+ /**
105
+ * Pin the sub-dsh's permission boundary — the sandbox mode every confined
106
+ * call runs under and the approval policy a denied call escalates through.
107
+ * Absent leaves `dsh-base`'s own rows untouched, which is byte for byte
108
+ * the behavior of every scope before this field existed.
109
+ */
110
+ permissions?: DshSubProfilePermissions;
111
+ }
112
+ /**
113
+ * The roster package the roster row names.
114
+ *
115
+ * Deliberately NOT symlinked into the sub-profile the way the headless bundle
116
+ * is. The headless bundle is a `@khorsheed` package the dsh installation does
117
+ * not carry, so it must be linked in from the plugin installation; the roster
118
+ * is an OFFICIAL package, already in the installation anchor's closure beside
119
+ * `@deepseek-ai/dsh-base`. Linking a second copy would give it a second
120
+ * `@deepseek-ai/cordis` — and cordis does service lookup and type checks by
121
+ * instance identity, so the symptom would be silently missing services rather
122
+ * than an error (the same failure the dual-filesystem contract in the README
123
+ * describes for the bundle). A deployment whose anchor genuinely lacks the
124
+ * roster gets the loader's own "cannot resolve" message, which names the
125
+ * module better than provisioning could.
126
+ */
127
+ export declare const PRESET_ROSTER_MODULE = "@deepseek-ai/dsh-agent-presets";
128
+ /** A preset id doubles as a directory name — the roster's own rule. */
129
+ export declare const SUB_PROFILE_PRESET_ID_RE: RegExp;
130
+ /**
131
+ * Resolve the family headless bundle's directory. The bundle is a declared
132
+ * dependency of this package, so it resolves from the same installation; the
133
+ * explicit config override exists for deployments that keep it elsewhere.
134
+ * @param config - provisioning config carrying the optional override.
135
+ * @returns the absolute bundle directory (holding `cordis.patch.yml`).
136
+ */
137
+ export declare function resolveHeadlessBundleDir(config?: DshSubProfileConfig): string;
138
+ /**
139
+ * The roster layer appended to the sub-profile's patch, or `''` when the
140
+ * sub-profile composes no preset.
141
+ *
142
+ * It is a SEPARATE patch operation appended after the headless bundle's own
143
+ * list, which is what keeps the two independent: an upgraded headless bundle
144
+ * rewrites its half and the roster survives, and dropping the preset drops
145
+ * exactly these lines.
146
+ * @param preset - the roster to compose, or undefined for none.
147
+ * @returns the YAML text to append (already newline-terminated), or `''`.
148
+ */
149
+ export declare function presetRosterLayer(preset: DshSubProfilePreset | undefined): string;
150
+ /**
151
+ * The preset id a provisioned sub-profile's patch composes, read back from
152
+ * the file. The roster layer is generated, so it is also parseable: a lock
153
+ * check or a status surface can answer "which preset does this scope run?"
154
+ * without booting the sub-dsh.
155
+ * @param homeDir - the harness scoped home.
156
+ * @param profileName - the sub-profile name (default {@link DEFAULT_SUB_PROFILE_NAME}).
157
+ * @returns the preset id, or undefined when the sub-profile composes no roster.
158
+ */
159
+ export declare function readSubProfilePreset(homeDir: string, profileName?: string): string | undefined;
160
+ /**
161
+ * The permission layer appended to the sub-profile's patch, or `''` when the
162
+ * sub-profile pins no boundary.
163
+ *
164
+ * Two PATCH rows, not inserts: `sandbox-policy` and `approval` already exist
165
+ * in the `@deepseek-ai/dsh-base` layer this profile stacks on, and the layer
166
+ * lands after it, so these override what the base composed. A base that
167
+ * mounts neither row (a future line, a different bundle) makes the loader
168
+ * warn "entry not found" and skip — a sub-dsh that boots with the composition
169
+ * it has, never one that fails to boot over a knob.
170
+ *
171
+ * Both rows RE-STATE every key they own. A patch `config` is a whole-value
172
+ * REPLACE, not a deep merge (`applyEntryPatches`: `target[key] = value`), so
173
+ * a row that wrote `mode` alone would silently drop `workspaceRoot` — and
174
+ * both rows carry `name` so a foreign plugin squatting the id makes the
175
+ * loader report a name mismatch and skip, rather than take this config.
176
+ *
177
+ * Pinning the literal also takes the boundary OFF `DSH_PERMISSION_MODE`,
178
+ * which is what `dsh-base` otherwise reads it from: the value belongs to the
179
+ * scope directory, where `home.sha` already hashes it, rather than to
180
+ * whatever environment happened to spawn the process.
181
+ * @param permissions - the boundary to pin, or undefined for none.
182
+ * @returns the YAML text to append (already newline-terminated), or `''`.
183
+ */
184
+ export declare function permissionBoundaryLayer(permissions: DshSubProfilePermissions | undefined): string;
185
+ /**
186
+ * The permission boundary a provisioned sub-profile's patch pins, read back
187
+ * from the file — the counterpart of {@link readSubProfilePreset}, and
188
+ * readable for the same reason: the layer is generated, so a lock check or a
189
+ * status surface can answer "what boundary does this scope run under?"
190
+ * without booting the sub-dsh.
191
+ * @param homeDir - the harness scoped home.
192
+ * @param profileName - the sub-profile name (default {@link DEFAULT_SUB_PROFILE_NAME}).
193
+ * @returns the pinned mode, or undefined when the sub-profile pins none.
194
+ */
195
+ export declare function readSubProfilePermissions(homeDir: string, profileName?: string): DshSubProfilePermissions | undefined;
196
+ /**
197
+ * Provision (idempotently) the sub-dsh headless profile in the harness
198
+ * scoped home: manifest, the headless patch as the profile's own patch
199
+ * layer, and the bundle symlink. A file whose content already matches is
200
+ * left untouched, so re-running after a restart is a no-op; a drifted file
201
+ * (an upgraded headless bundle, or a profile provisioned by the pre-T6
202
+ * scheme whose manifest listed the headless bundle as a layer) is rewritten,
203
+ * which heals it before the sub-dsh boots.
204
+ * @param homeDir - the `dsh` harness's scoped home (its `$DSH_HOME` root).
205
+ * @param config - profile naming and bundle-dir overrides.
206
+ * @returns the absolute sub-profile directory.
207
+ */
208
+ export declare function provisionDshSubProfile(homeDir: string, config?: DshSubProfileConfig): string;
209
+ /**
210
+ * The preset root a harness home derives — the roster's own rule, spelled
211
+ * here because this module has to write into it.
212
+ */
213
+ export declare const USER_PRESET_DIR = ".agent-presets";
214
+ /**
215
+ * The scope's own declaration of what its sub-profile composes.
216
+ *
217
+ * A FILE in the scope directory, for the third time and the same reason as
218
+ * the roster layer and the permission boundary: a scoped home bind-mounted
219
+ * into an evaluation unit carries its own composition with it, and nothing
220
+ * has to ride the spawn env or be baked into an image.
221
+ *
222
+ * It exists because the plugin's own `config` is instance-global — one
223
+ * deployment, one answer — while a preset is a property of the SUBJECT. Two
224
+ * scopes rostering two presets was therefore unexpressible, and the only way
225
+ * to build it was to hand-append the roster layer to each scope's patch. That
226
+ * hand edit does not survive: this module regenerates the patch WHOLE, and
227
+ * the registry re-provisions a scope the first time anything names it in a
228
+ * fresh host process — so the roster silently disappears on the next restart,
229
+ * and the next read-back reports a scope that rosters nothing.
230
+ */
231
+ export declare const SCOPE_SUB_PROFILE_FILENAME = "sub-profile.json";
232
+ /** What {@link SCOPE_SUB_PROFILE_FILENAME} may say. */
233
+ export interface DshScopeSubProfile {
234
+ /** The preset id this scope's sub-dsh composes. */
235
+ preset?: string;
236
+ }
237
+ /**
238
+ * Read one scope's own sub-profile declaration.
239
+ *
240
+ * Degrades to `undefined` for every unreadable shape — no file, unparsable
241
+ * JSON, a `preset` that is not a usable id — because the caller's next step
242
+ * is the plugin-level config, and a scope whose declaration is junk must fall
243
+ * back to the deployment's answer rather than fail to provision at all.
244
+ * @param homeDir - the harness scoped home.
245
+ * @returns the declaration, or undefined when the scope declares nothing usable.
246
+ */
247
+ export declare function readScopeSubProfile(homeDir: string): DshScopeSubProfile | undefined;
248
+ /**
249
+ * Record one scope's sub-profile declaration, so the next provisioning of
250
+ * that scope reproduces it without being told. Idempotent — a file that
251
+ * already says this is left untouched.
252
+ * @param homeDir - the harness scoped home.
253
+ * @param declaration - what the scope composes.
254
+ */
255
+ export declare function writeScopeSubProfile(homeDir: string, declaration: DshScopeSubProfile): void;
256
+ /**
257
+ * Every absolute filesystem path a preset composition names.
258
+ *
259
+ * An absolute path is the one thing in a composition that cannot be true in
260
+ * two places at once, and a preset used as an evaluation factor is copied:
261
+ * it is read on the host from the deployment's preset root, again from the
262
+ * scope's own copy, and a third time inside a unit where the scope is bound
263
+ * at a different mount point. `skill-filesystem` resolves each
264
+ * `customSkillDirs` entry with `resolve()`, so an absolute entry is taken as
265
+ * written and a bare relative one resolves against the process's cwd —
266
+ * neither travels.
267
+ *
268
+ * What does travel is the loader's own expression form, which the shipped
269
+ * `cordis` preset already uses:
270
+ *
271
+ * ```yaml
272
+ * customSkillDirs:
273
+ * - !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('skills/', baseUrl))"
274
+ * ```
275
+ *
276
+ * `baseUrl` is the composition's own directory (the include rewrites it), so
277
+ * the root resolves wherever the preset is installed.
278
+ *
279
+ * Unparsable YAML reports NO findings: the loader is the authority on whether
280
+ * a composition loads, and refusing a preset because this reader could not
281
+ * parse it would be this module overruling it.
282
+ * @param text - the `agent.cordis.yml` content.
283
+ * @returns the offending literals, in document order, deduplicated.
284
+ */
285
+ export declare function compositionAbsolutePaths(text: string): string[];
286
+ /**
287
+ * Whether two preset directories are the same directory, byte for byte.
288
+ *
289
+ * This is the whole claim the evaluation rests on: a capability face carries
290
+ * no filesystem path, so the fingerprint taken against the deployment's copy
291
+ * describes the scope's copy exactly when the two hold the same bytes.
292
+ * @param a - one preset directory.
293
+ * @param b - the other.
294
+ * @returns true when both hold the same files with the same contents.
295
+ */
296
+ export declare function presetTreesEqual(a: string, b: string): boolean;
297
+ /** What {@link snapshotScopePreset} did. */
298
+ export interface DshScopePresetSnapshot {
299
+ /** The scope's copy of the preset directory. */
300
+ dir: string;
301
+ /** Whether that copy is byte-for-byte the deployment's own. */
302
+ matchesSource: boolean;
303
+ /** Whether this call wrote it. */
304
+ copied: boolean;
305
+ }
306
+ /**
307
+ * Give one scope its own copy of a preset, from the deployment's preset root.
308
+ *
309
+ * The copy is what makes a preset reachable on BOTH paths. The roster derives
310
+ * a user root of `<$DSH_HOME>/.agent-presets`, and the sub-dsh runs with
311
+ * `DSH_HOME` pointed at the scoped home — which is `<scope>/.agent-presets`
312
+ * on the host and `/creds/dsh/.agent-presets` inside an evaluation unit,
313
+ * one directory, because the unit binds the scope. A roster pointed at the
314
+ * deployment's root instead resolves on the host and names a path the unit
315
+ * does not have.
316
+ *
317
+ * `refresh` is the difference between provisioning a condition and healing a
318
+ * scope. A deliberate provision re-syncs from the deployment's copy, because
319
+ * that is the moment a new lock is minted and an edited preset should be
320
+ * picked up. Every other provisioning leaves an existing copy alone: nothing
321
+ * may move the subject under a run.
322
+ * @param homeDir - the harness scoped home.
323
+ * @param presetRoot - the deployment's preset root.
324
+ * @param id - the preset id (a directory name in both roots).
325
+ * @param options - `refresh` re-syncs an existing copy.
326
+ * @returns where the copy is and whether it matches the source.
327
+ * @throws when the source is missing, is not a preset, or names an absolute path.
328
+ */
329
+ export declare function snapshotScopePreset(homeDir: string, presetRoot: string, id: string, options?: {
330
+ refresh?: boolean;
331
+ }): DshScopePresetSnapshot;
332
+ /**
333
+ * The deployment's own preset root, when the caller names no other.
334
+ *
335
+ * The roster's own derivation, repeated here because the provisioning paths
336
+ * that heal a scope (a host round, a live runtime) have no settings service
337
+ * in reach: the harness home from the environment, else the installation
338
+ * default.
339
+ */
340
+ export declare function defaultPresetRoot(): string;
341
+ /** Per-scope inputs {@link provisionDshScope} takes. */
342
+ export interface DshScopeProvisionOptions {
343
+ /**
344
+ * The preset this scope composes. Present, it is authoritative: it is
345
+ * persisted into the scope's own declaration and its copy is re-synced from
346
+ * the deployment's preset root. Absent, the scope's declaration decides.
347
+ */
348
+ preset?: string;
349
+ /** The deployment's preset root; defaults to `<$DSH_HOME>/.agent-presets`. */
350
+ presetRoot?: string;
351
+ }
352
+ /** What {@link provisionDshScope} left in the scope. */
353
+ export interface DshScopeProvisioned {
354
+ /** The sub-profile directory. */
355
+ profileDir: string;
356
+ /** The preset the sub-profile now rosters, when it rosters one. */
357
+ preset?: string;
358
+ /** The scope's own copy of that preset, when it keeps one. */
359
+ presetSnapshot?: {
360
+ matchesSource: boolean;
361
+ };
362
+ }
363
+ /**
364
+ * Provision one scope: its own preset copy, its declaration, and the
365
+ * sub-profile that rosters it.
366
+ *
367
+ * The preset resolves in one order, and the order is the decision: the
368
+ * CALLER's explicit request, then the SCOPE's own declaration, then the
369
+ * deployment's plugin config. The first is how a condition says which subject
370
+ * this scope is; the second is how that survives a restart; the third is the
371
+ * deployment-wide answer every scope had before either existed.
372
+ *
373
+ * A scope-owned preset rosters with NO `roots`. The roster's derived user
374
+ * root is exactly the scope's copy on both paths, so leaving the roots alone
375
+ * is what gives the host and the unit one resolution rule instead of two.
376
+ * @param homeDir - the harness scoped home.
377
+ * @param config - the deployment's sub-profile config.
378
+ * @param options - per-scope inputs.
379
+ * @returns what the scope now composes.
380
+ */
381
+ export declare function provisionDshScope(homeDir: string, config?: DshSubProfileConfig, options?: DshScopeProvisionOptions): DshScopeProvisioned;
382
+ //# sourceMappingURL=provision.d.ts.map