@khorsheed/dsh-local-agent 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 (99) hide show
  1. package/README.en.md +181 -0
  2. package/README.i18n.yaml +2 -0
  3. package/README.md +181 -0
  4. package/cordis.patch.yml +21 -0
  5. package/lib/client.js +8832 -0
  6. package/lib/index.js +5281 -0
  7. package/lib/invariant.js +85 -0
  8. package/lib/member-bridge.js +298 -0
  9. package/lib/typert.host.d.ts +3 -0
  10. package/lib/typert.host.js +3137 -0
  11. package/lib/typert.remote-client.d.ts +67 -0
  12. package/lib/typert.remote-client.js +1971 -0
  13. package/lib/types/cli-version.d.ts +82 -0
  14. package/lib/types/cli-version.js +165 -0
  15. package/lib/types/client/AuthStatusDot.d.ts +17 -0
  16. package/lib/types/client/AuthStatusDot.js +9 -0
  17. package/lib/types/client/HarnessModelPicker.d.ts +17 -0
  18. package/lib/types/client/HarnessModelPicker.js +48 -0
  19. package/lib/types/client/LocalAgentRecordsAction.d.ts +41 -0
  20. package/lib/types/client/LocalAgentRecordsAction.js +74 -0
  21. package/lib/types/client/MemberComposer.d.ts +79 -0
  22. package/lib/types/client/MemberComposer.js +308 -0
  23. package/lib/types/client/MemberConfiguration.d.ts +9 -0
  24. package/lib/types/client/MemberConfiguration.js +46 -0
  25. package/lib/types/client/MemberInboxView.d.ts +12 -0
  26. package/lib/types/client/MemberInboxView.js +38 -0
  27. package/lib/types/client/MemberLiveNode.d.ts +14 -0
  28. package/lib/types/client/MemberLiveNode.js +74 -0
  29. package/lib/types/client/MemberPaintDiagnostics.d.ts +7 -0
  30. package/lib/types/client/MemberPaintDiagnostics.js +20 -0
  31. package/lib/types/client/ModelConfigurationFields.d.ts +15 -0
  32. package/lib/types/client/ModelConfigurationFields.js +23 -0
  33. package/lib/types/client/ProviderAuthBlock.d.ts +59 -0
  34. package/lib/types/client/ProviderAuthBlock.js +164 -0
  35. package/lib/types/client/auth-status.d.ts +28 -0
  36. package/lib/types/client/auth-status.js +56 -0
  37. package/lib/types/client/icons.d.ts +22 -0
  38. package/lib/types/client/icons.js +6 -0
  39. package/lib/types/client/index.d.ts +62 -0
  40. package/lib/types/client/index.js +135 -0
  41. package/lib/types/client/live-node.d.ts +14 -0
  42. package/lib/types/client/live-node.js +34 -0
  43. package/lib/types/client/live-output.d.ts +34 -0
  44. package/lib/types/client/live-output.js +94 -0
  45. package/lib/types/client/live-paint.d.ts +40 -0
  46. package/lib/types/client/live-paint.js +74 -0
  47. package/lib/types/client/locales.d.ts +120 -0
  48. package/lib/types/client/locales.js +227 -0
  49. package/lib/types/client/member-configuration.d.ts +41 -0
  50. package/lib/types/client/member-configuration.js +114 -0
  51. package/lib/types/client/member-dock.d.ts +94 -0
  52. package/lib/types/client/member-dock.js +102 -0
  53. package/lib/types/client/member-feed.d.ts +24 -0
  54. package/lib/types/client/member-feed.js +134 -0
  55. package/lib/types/client/request-id.d.ts +3 -0
  56. package/lib/types/client/request-id.js +6 -0
  57. package/lib/types/client/settings-scope.d.ts +74 -0
  58. package/lib/types/client/settings-scope.js +98 -0
  59. package/lib/types/client/use-configuration-menu.d.ts +10 -0
  60. package/lib/types/client/use-configuration-menu.js +61 -0
  61. package/lib/types/container.d.ts +69 -0
  62. package/lib/types/container.js +129 -0
  63. package/lib/types/env.d.ts +17 -0
  64. package/lib/types/env.js +89 -0
  65. package/lib/types/gateway.d.ts +162 -0
  66. package/lib/types/gateway.js +470 -0
  67. package/lib/types/index.d.ts +1463 -0
  68. package/lib/types/index.js +2704 -0
  69. package/lib/types/invariant.d.ts +16 -0
  70. package/lib/types/invariant.js +105 -0
  71. package/lib/types/live-flush.d.ts +21 -0
  72. package/lib/types/live-flush.js +59 -0
  73. package/lib/types/live-stream.d.ts +41 -0
  74. package/lib/types/live-stream.js +178 -0
  75. package/lib/types/member-bridge.d.ts +50 -0
  76. package/lib/types/member-bridge.js +216 -0
  77. package/lib/types/member-channel.d.ts +86 -0
  78. package/lib/types/member-channel.js +232 -0
  79. package/lib/types/member-control-storage.d.ts +10 -0
  80. package/lib/types/member-control-storage.js +100 -0
  81. package/lib/types/member-control.d.ts +62 -0
  82. package/lib/types/member-control.js +359 -0
  83. package/lib/types/member-controls.d.ts +23 -0
  84. package/lib/types/member-controls.js +143 -0
  85. package/lib/types/member-feed.d.ts +4 -0
  86. package/lib/types/member-feed.js +45 -0
  87. package/lib/types/member-inbox.d.ts +30 -0
  88. package/lib/types/member-inbox.js +225 -0
  89. package/lib/types/model-directory.d.ts +43 -0
  90. package/lib/types/model-directory.js +192 -0
  91. package/lib/types/prepared-members.d.ts +15 -0
  92. package/lib/types/prepared-members.js +61 -0
  93. package/lib/types/settings-face.d.ts +61 -0
  94. package/lib/types/settings-face.js +61 -0
  95. package/lib/types/types.d.ts +959 -0
  96. package/lib/types/types.js +24 -0
  97. package/locale/en.json +6 -0
  98. package/locale/zh.json +6 -0
  99. package/package.json +112 -0
@@ -0,0 +1,1463 @@
1
+ /**
2
+ * @khorsheed/dsh-local-agent — the local code-agent harness family core.
3
+ * Each locally-installed coding agent CLI (Kimi Code, Codex, Claude Code, …)
4
+ * registers itself as one harness: a scoped home under the shared homes root,
5
+ * the device-code login command, and a session-records adapter. The glue
6
+ * registers the `/<harness> login|sessions` command family and provisions the
7
+ * scoped home; every harness's state stays under its own directory and never
8
+ * touches the user's native installation.
9
+ *
10
+ * Delegation deliberately stays OUT of this seam: each harness bundle mounts
11
+ * its own subagent-provider row into the existing `subagent` capability
12
+ * (subagent-acp for a harness that speaks ACP over stdio, subagent-codex's
13
+ * provider for Codex's app-server wire, …), reading the scoped home through
14
+ * {@link LocalAgentRegistry.homeDir}. This package owns only harness identity
15
+ * and lifecycle.
16
+ *
17
+ * @module @khorsheed/dsh-local-agent
18
+ */
19
+ import type { Context } from '@deepseek-ai/cordis';
20
+ import z from '@deepseek-ai/schemastery';
21
+ import type { CommandInvocation, CommandResult } from '@deepseek-ai/dsh-commands';
22
+ import type { ContentBlock, TokenUsage } from '@deepseek-ai/dsh-llm';
23
+ import type { Session, SessionHeader } from '@deepseek-ai/dsh-session';
24
+ import type { SubagentRun } from '@deepseek-ai/dsh-subagent';
25
+ import type { DelegationCallOptions, LocalAgentDelegationInfo, LocalAgentDelegationIntent, LocalAgentDelegationRecord, LocalAgentEffectiveSettings, LocalAgentMemberRun, LocalAgentModelBroker, LocalAgentMemberBinding, LocalAgentMemberControlState, LocalAgentMemberInbox, LocalAgentMemberConfiguration, LocalAgentControlReceipt, LocalAgentAppliedConfiguration, LocalAgentRosterRow, LocalAgentRunProgress, LocalAgentSessionRecord, LocalAgentStatus, LocalAgentToolCalls } from './types.ts';
26
+ import { MemberInbox } from './member-inbox.ts';
27
+ /** Stable Cordis plugin name. */
28
+ export declare const name = "local-agent";
29
+ /** Services required before the registry and commands can mount. */
30
+ export declare const inject: string[];
31
+ export type { ContainerExecLaunch, } from './container.ts';
32
+ export type { DelegationCallOptions, LocalAgentCredentialState, DelegationExecTarget, LocalAgentDelegationInfo, LocalAgentDelegationIntent, LocalAgentDelegationRecord, LocalAgentDelegationView, LocalAgentEffectiveSettings, LocalAgentMemberRun, LocalAgentPromptResult, LocalAgentRosterRow, LocalAgentRunProgress, LocalAgentSessionRecord, LocalAgentStatus, } from './types.ts';
33
+ export type { LocalAgentMemberMessage, MemberMessageOutcome, RoomMemberMessageGate, RoomMemberMessageReceipt, } from './types.ts';
34
+ export { delegationEnv } from './env.ts';
35
+ export { ModelDirectoryCache, extendModelDirectory, modelDirectoryContextKey } from './model-directory.ts';
36
+ export { MemberConfigurationController } from './member-control.ts';
37
+ export type { MemberConfigurationAdapter, MemberControlStorage, MemberControlRecord } from './member-control.ts';
38
+ export { FileMemberControlStorage } from './member-control-storage.ts';
39
+ export { LiveFlush, LIVE_FLUSH_INTERVAL_MS } from './live-flush.ts';
40
+ import { LocalAgentStreams } from './live-stream.ts';
41
+ export { LocalAgentStreams, LiveStreamPublisher, LIVE_CHECKPOINT_INTERVAL_MS } from './live-stream.ts';
42
+ export { containerExecSpawn, containerScopedHome } from './container.ts';
43
+ export { settingsFace, vol } from './settings-face.ts';
44
+ export type { SettingsFace } from './settings-face.ts';
45
+ export { CLI_VERSION_FAILURE_TTL_MS, CLI_VERSION_PROBE_TIMEOUT_MS, clearCliVersionCache, parseCliVersion, probeCliVersion, } from './cli-version.ts';
46
+ export type { CliVersionProbe } from './cli-version.ts';
47
+ /** Per-harness session listing: reads the harness's own records format. */
48
+ export interface LocalAgentRecordsAdapter {
49
+ /**
50
+ * List sessions from the harness's scoped home.
51
+ * @param homeDir - the harness's scoped home directory.
52
+ * @returns the sessions in the harness's own order.
53
+ */
54
+ listSessions(homeDir: string): Promise<readonly LocalAgentSessionRecord[]>;
55
+ }
56
+ /**
57
+ * Compose a session-backed subagent's durable label from the harness's
58
+ * display name and the delegation's own description, so the standard 子代理
59
+ * surface shows which local agent produced the conversation. Providers
60
+ * (kimi, future codex/claude-code) call this when appending the child's
61
+ * `subagent/descriptor` event; the prefix is the durable, model-visible
62
+ * source marker.
63
+ * @param displayName - the harness's human display name (e.g. `Kimi Code`).
64
+ * @param description - the delegation's short description, when the caller
65
+ * supplied one.
66
+ * @returns the composed label, or just the display name without a description.
67
+ */
68
+ export declare function subagentDelegationLabel(displayName: string, description: string | undefined): string;
69
+ /**
70
+ * Append the parent-side `subagent/catalog` discovery row for a freshly
71
+ * established session-backed child. The official SubagentRuntime writes this
72
+ * row only for IN-PROCESS children (`run.localAgent.session`, harness
73
+ * `SubagentRuntime.start`); a family provider's run is remote, so the
74
+ * provider appends the row itself, right where the child's
75
+ * `subagent/descriptor` lands — exactly once per child session, since resume
76
+ * rounds never create a session.
77
+ *
78
+ * The payload mirrors the upstream `establishCatalogChild` helper
79
+ * (harness packages/subagent catalog.ts) byte-for-byte. It is inlined
80
+ * because that helper is unreachable through the package's exports map on
81
+ * the npm release line: only `./src/*` would resolve it, and published
82
+ * artifacts ship no `src/`. The `subagent/catalog` event type itself rides
83
+ * the official package's SessionEventMap augmentation, so this append stays
84
+ * type-checked against the host's own schema. A throw is the caller's to
85
+ * degrade (warn and continue) — a missing catalog row never blocks the
86
+ * delegation itself.
87
+ * @param parent - the durable direct parent session receiving the row.
88
+ * @param child - the established child's immutable session header.
89
+ * @param label - the same composed label the child's descriptor carries.
90
+ */
91
+ export declare function establishSubagentCatalogChild(parent: Session, child: SessionHeader, label: string | undefined): void;
92
+ /**
93
+ * Persist a delegated child session's events, the family providers' one entry
94
+ * point after every mirror pass. Two paths:
95
+ *
96
+ * - **live session + a core with {@link LocalAgentRegistry.syncChildSession}**
97
+ * (the production wiring): delegate to the registry, which syncs through
98
+ * its cached per-child write handle — the same handle the reattach recipe
99
+ * holds, so live write routing and the explicit suffix sync share one
100
+ * owner and never collide on SessionAlreadyOwnedError. The registry
101
+ * downgrades a sync failure to a warn, so this path never throws.
102
+ * - **anything else** (standalone tests, ad-hoc mirrors, a core predating
103
+ * syncChildSession): a one-shot handle flow — claim the write handle
104
+ * (creating the stored session on first persist), append only the unstored
105
+ * suffix (re-appending the full snapshot violates the contiguous-seq
106
+ * contract), flush, close. There is deliberately NO live-session skip:
107
+ * reading the stored prefix first makes the suffix append idempotent, so
108
+ * on a host line whose write-behind does store live appends the suffix is
109
+ * simply empty — while on host 0.1.5, where a mirror session without an
110
+ * agent loop never checkpoints, this flow is what lands the events at all.
111
+ *
112
+ * @param ctx - host context carrying the sessions/localAgent/sessionPersistence services.
113
+ * @param childSession - the dsh child session the provider mirrored into.
114
+ */
115
+ export declare function persistChildSession(ctx: Context, childSession: Session): Promise<void>;
116
+ /**
117
+ * A harness's login flow. Two variants:
118
+ *
119
+ * - **device-code**: spawn the CLI's login command and capture its printed
120
+ * prompt (the URL/code) for the reply while the CLI polls in the background.
121
+ * - **manual handoff**: the CLI's auth is TTY-only, so nothing is spawnable —
122
+ * `/login` replies with instructions naming the command for the user's own
123
+ * terminal, and the registry watches the scoped home for the credential.
124
+ */
125
+ export type LocalAgentLogin = {
126
+ command: string;
127
+ args: readonly string[];
128
+ /**
129
+ * Which stream carries the device-code prompt. Defaults to `stderr`
130
+ * (kimi's login prints there); harnesses whose CLI prints the prompt to
131
+ * stdout (codex `login --device-auth`, claude `setup-token`) declare
132
+ * `stdout` so the reply surfaces the URL instead of timing out.
133
+ */
134
+ capture?: 'stderr' | 'stdout';
135
+ } | {
136
+ manual: {
137
+ /** The exact command line the user runs in their own terminal. */
138
+ commandDisplay: string;
139
+ };
140
+ /**
141
+ * Credential probe polled while the handoff is in flight; defaults to the
142
+ * harness's own `isAuthenticated`.
143
+ */
144
+ watch?: (homeDir: string) => Promise<boolean>;
145
+ } | {
146
+ /**
147
+ * The CLI's auth needs a TTY but drives the browser itself (claude ≥2.1):
148
+ * the command is spawned under a pseudo-terminal wrapper so it opens the
149
+ * user's browser, and its output is watched for an OAuth URL to show as a
150
+ * fallback. When the CLI prompts for a code, the user pastes it via
151
+ * `/<name> code <value>`, which the registry writes to the child's stdin.
152
+ */
153
+ pty: {
154
+ command: string;
155
+ /**
156
+ * The argv, or a factory that receives the scoped home being logged
157
+ * in. The factory form exists because one harness pins its scoped home
158
+ * ON the argv (claude's `env CLAUDE_CONFIG_DIR=… claude auth login`, an
159
+ * assignment that outranks the spawn env): with a fixed array a
160
+ * `--scope` login would authorize the DEFAULT directory while claiming
161
+ * to authorize the scope. The plain array stays the form every other
162
+ * harness uses.
163
+ */
164
+ args: readonly string[] | ((homeDir: string) => readonly string[]);
165
+ };
166
+ /**
167
+ * Credential probe polled while the login is in flight; defaults to the
168
+ * harness's own `isAuthenticated`.
169
+ */
170
+ watch?: (homeDir: string) => Promise<boolean>;
171
+ };
172
+ /**
173
+ * Per-scope inputs one scope's provisioning may take.
174
+ *
175
+ * Provisioning has always been a function of the scoped home alone, which is
176
+ * right for every knob a harness reads from its own plugin config — one
177
+ * instance, one answer. `preset` is the first knob that is a property of the
178
+ * SUBJECT rather than of the deployment: an evaluation whose two conditions
179
+ * differ in exactly one preset needs two scopes rostering two presets, and a
180
+ * plugin-level setting cannot say that. So the caller that knows (the
181
+ * evaluation's `conditions provision`, which reads the condition document)
182
+ * hands it in here, and the harness persists it into the scope directory so
183
+ * the next provisioning of that scope reproduces it without being told.
184
+ *
185
+ * Only `dsh` reads it. The other three harnesses take the parameter and
186
+ * ignore it, which is what keeps their provisioning byte-identical.
187
+ */
188
+ export interface LocalAgentScopeProvisionOptions {
189
+ /** The agent preset this scope's composition must compose. */
190
+ preset?: string;
191
+ }
192
+ /**
193
+ * What one scope's provisioning reports back.
194
+ *
195
+ * Every field is optional and every one is a READ-BACK: what the scope now
196
+ * holds, not what the caller asked for. A caller that wanted to know whether
197
+ * its request took effect compares the two.
198
+ */
199
+ export interface LocalAgentScopeProvisionResult {
200
+ /** The preset the scope's composition now rosters, when it rosters one. */
201
+ preset?: string;
202
+ /**
203
+ * The scope's own copy of that preset directory, when the harness keeps
204
+ * one. `matchesSource` is the whole content of the claim: the copy is
205
+ * byte-for-byte the deployment's own preset directory, which is what lets
206
+ * a fingerprint taken against the deployment's copy describe the scope's.
207
+ */
208
+ presetSnapshot?: {
209
+ matchesSource: boolean;
210
+ };
211
+ }
212
+ /**
213
+ * One registered local code-agent harness.
214
+ */
215
+ export interface LocalAgentPreparation {
216
+ binding: LocalAgentMemberBinding;
217
+ childSession: Session;
218
+ configuration: LocalAgentAppliedConfiguration;
219
+ signal: AbortSignal;
220
+ }
221
+ export interface LocalAgentHarness {
222
+ /** Native handshake/configuration only; no prompt, turn, or side-effecting task. */
223
+ prepareMember?: (request: LocalAgentPreparation) => Promise<void>;
224
+ /** Command prefix and scoped-home directory name (lowercase, dashed). */
225
+ name: string;
226
+ /** Human display name, used in command replies and UI labels. */
227
+ displayName: string;
228
+ /** Environment variable naming the scoped home (e.g. `KIMI_CODE_HOME`). */
229
+ homeEnvVar: string;
230
+ /**
231
+ * Subagent provider name this harness delegates through, when it has one.
232
+ * A harness without delegation is record-and-login only. The core invariant
233
+ * cross-checks this against the mounted subagent providers, so a record
234
+ * that names a provider the composition does not mount fails loud.
235
+ */
236
+ delegationProvider?: string;
237
+ /**
238
+ * Login flow declaration. The device-code variant spawns the CLI and
239
+ * captures its printed prompt. The manual variant spawns NOTHING: the CLI's
240
+ * auth is TTY-only (claude ≥2.1 prints no OAuth URL off a TTY and
241
+ * `setup-token` needs Ink raw mode — scraping broke twice), so `/login`
242
+ * replies with instructions naming `manual.commandDisplay` for the user's
243
+ * own terminal, then watches for the credential to land. Absent means the
244
+ * harness has no login flow — it authenticates through the host instance
245
+ * (e.g. by resolving a credential from the parent's store), so `/login`
246
+ * reports that instead of guessing a command.
247
+ */
248
+ login?: LocalAgentLogin;
249
+ /** Session records reader for this harness's format. */
250
+ records: LocalAgentRecordsAdapter;
251
+ /**
252
+ * Bring one scoped home of this harness to the state its CLI needs before a
253
+ * login or a round can use it (codex's `config.toml` storage pin, kimi's
254
+ * provider/model config and permission rules, claude's `settings.json`,
255
+ * dsh's sub-profile). Called by the registry when a NAMED scope's directory
256
+ * is materialized — the default scope is provisioned by the harness bundle's
257
+ * own `apply`, exactly as it always was, so nothing about it moves.
258
+ * Best-effort: a failure is logged and the directory still exists (the
259
+ * status surface then reports what is actually there).
260
+ *
261
+ * `options` carries the per-scope inputs a caller supplies when it
262
+ * provisions one scope deliberately (see {@link LocalAgentRegistry.provisionScope});
263
+ * the registry's own materialization passes none, so a harness that reads
264
+ * nothing from it behaves exactly as it did before the parameter existed.
265
+ * The return value is a READ-BACK of what the scope now holds, and `void`
266
+ * is the honest answer for a harness with nothing to report.
267
+ * @param homeDir - the scoped home to provision.
268
+ * @param options - per-scope inputs, absent on the registry's own materialization.
269
+ */
270
+ provision?: (homeDir: string, options?: LocalAgentScopeProvisionOptions) => LocalAgentScopeProvisionResult | void | Promise<LocalAgentScopeProvisionResult | void>;
271
+ /**
272
+ * Whether the scoped home currently holds usable credentials. Absent means
273
+ * the harness reports "not authenticated" and the status command still
274
+ * runs; the check is per-harness (a credentials directory, an auth file).
275
+ */
276
+ isAuthenticated?: (homeDir: string) => Promise<boolean>;
277
+ /**
278
+ * The credential marker's modification stamp (epoch ms), undefined when no
279
+ * credential exists. Two consumers: the manual-login watch requires a stamp
280
+ * NEWER than the watch start (a stale marker must not read as a fresh
281
+ * login), and `statusOf` downgrades a presence-true probe when a delegation
282
+ * reported an auth failure newer than the stamp (presence cannot see a
283
+ * server-side revocation; the next real login rewrites the marker and
284
+ * recovers the status).
285
+ */
286
+ credentialStamp?: (homeDir: string) => Promise<number | undefined>;
287
+ /**
288
+ * Sign out of the scoped account, clearing the stored credentials so a
289
+ * later login authorizes another account. Absent means the harness has no
290
+ * logout path and `/logout` reports it instead of guessing.
291
+ */
292
+ logout?: (homeDir: string) => Promise<void>;
293
+ /**
294
+ * Optional extra `/<name>` subcommands beyond the family's own. Returning
295
+ * undefined means the input is not this harness's; the family's unknown-
296
+ * subcommand error answers instead. A harness owns its extras (config
297
+ * commands, preset management), keeping the core seam generic.
298
+ * @param input - the trimmed subcommand input.
299
+ * @param invocation - the full command invocation.
300
+ * @returns the command result, or undefined when the subcommand is not this
301
+ * harness's.
302
+ */
303
+ subcommand?: (input: string, invocation: CommandInvocation) => Promise<CommandResult> | CommandResult | undefined;
304
+ /**
305
+ * The harness's fairness-relevant effective settings — the read side of an
306
+ * evaluation condition hash (the web-eval frozen baseline: every factor
307
+ * must be hashable). The registry's {@link LocalAgentRegistry.effectiveSettings}
308
+ * resolves it, and both status surfaces (the `/<name> status` reply and the
309
+ * `LocalAgentStatus` Remote) attach it. The snapshot is a LIVE read — it
310
+ * reflects what the harness would use right now, including scoped-config
311
+ * values a person edited. Pure JSON, never credentials; see
312
+ * {@link LocalAgentEffectiveSettings} for the field vocabulary and the
313
+ * absence-is-a-knob-absent rule.
314
+ *
315
+ * The snapshot is taken of ONE scoped home, named by the parameter: a
316
+ * harness reads its config out of the directory it is handed, so a named
317
+ * scope reports the settings that scope's rounds would run with rather than
318
+ * the default scope's.
319
+ * @param homeDir - the scoped home to read; the registry passes the
320
+ * directory of the scope being asked about.
321
+ */
322
+ effectiveSettings?: (homeDir: string) => LocalAgentEffectiveSettings | Promise<LocalAgentEffectiveSettings>;
323
+ /**
324
+ * The harness's model knob: the settings card reads the memberless surface
325
+ * ("what would a round run with"), the member composer reads and switches a
326
+ * member's session-level override. Absent means the harness exposes no
327
+ * model surface beyond its free-text settings field — the gateway answers
328
+ * null and both UIs keep their pre-broker behavior.
329
+ */
330
+ modelBroker?: LocalAgentModelBroker;
331
+ }
332
+ /** Plugin config: the shared scoped-homes root and the login prompt wait. */
333
+ export interface Config {
334
+ /** Root holding one directory per harness; subdirectories are `agent/<name>`. */
335
+ homesRoot: string;
336
+ /** How long `/login` waits for the device-code prompt before failing. */
337
+ loginPromptTimeoutMs?: number;
338
+ }
339
+ /** Grace between SIGTERM and SIGKILL when replacing an abandoned login child. */
340
+ export declare const REPLACE_LOGIN_GRACE_MS = 5000;
341
+ /** Remove terminal escape sequences from scraped CLI text. */
342
+ export declare function stripAnsi(text: string): string;
343
+ /**
344
+ * The characters a named scope may use. A scope is a NAME, never a path: the
345
+ * scoped home it selects always lands directly under the shared homes root,
346
+ * so `..`, a slash, or a leading dash can never walk out of it.
347
+ */
348
+ export declare const SCOPE_NAME_RE: RegExp;
349
+ /**
350
+ * The directory NAME of one harness's scoped home under the homes root: the
351
+ * harness name for the default scope, `<name>@<scope>` for a named one. The
352
+ * named directory is a SIBLING of the default one, never a child: nesting it
353
+ * inside would put a second CLI state tree under a directory each harness's
354
+ * own CLI owns and prunes.
355
+ * @param name - the harness name.
356
+ * @param scope - the named scope, or undefined for the default one.
357
+ * @returns the directory name under the homes root.
358
+ * @throws when the scope is present but not a `[a-z0-9-]` name.
359
+ */
360
+ export declare function scopedHomeName(name: string, scope?: string): string;
361
+ /**
362
+ * Enforce the resume-scope consistency rule, the scoped-home twin of
363
+ * {@link assertResumeCwdUnchanged}: a CLI session continues in the scoped home
364
+ * its earlier rounds ran in, so a round naming a different scope — including
365
+ * naming none when the first round named one, and the reverse — is rejected
366
+ * instead of resuming the conversation against another account's credentials
367
+ * and another directory's session records. Records written before the `scope`
368
+ * field existed carry no scope and are default-scope records; a resume of one
369
+ * with a scope is exactly the mismatch this refuses.
370
+ * @param record - the delegation's persisted record, when one exists.
371
+ * @param scope - the round's scope, or undefined for the default one.
372
+ * @param provider - the provider name, for the error message.
373
+ * @throws when the record's scope differs from the round's.
374
+ */
375
+ export declare function assertResumeScopeUnchanged(record: LocalAgentDelegationRecord | undefined, scope: string | undefined, provider: string): void;
376
+ /**
377
+ * Refuse a round that names its own model and would be served by a live
378
+ * driver — the model twin of {@link assertScopeExecOnly}, and refused for the
379
+ * same reason. A resident runtime (codex's `app-server`, claude's and kimi's
380
+ * ACP servers, the sub-dsh `serve`) binds its model when the PROCESS starts
381
+ * and then serves many rounds of one member, so a per-delegation model would
382
+ * either be ignored or would silently change what every other round of that
383
+ * runtime runs. Refused rather than quietly downgraded to exec: the caller
384
+ * named a model, and a fallback would answer a different question than the
385
+ * one asked. The evaluation pins `drive: exec` anyway (web-eval frozen
386
+ * decision 2).
387
+ * @param model - the round's requested model, or undefined for none.
388
+ * @param provider - the provider name, for the error message.
389
+ * @throws when a named model meets an active live driver.
390
+ */
391
+ export declare function assertModelExecOnly(model: string | undefined, provider: string): void;
392
+ /**
393
+ * The model one round starts its CLI with, resolved in the family's fixed
394
+ * order: the DELEGATION's own model (the `model` call option on the fresh
395
+ * round, re-requested by every resume round from the record) first, then the
396
+ * harness's `model` plugin-config key, then nothing — which leaves the scoped
397
+ * configuration file and, last, the CLI's own default to decide exactly as
398
+ * they did before either key existed.
399
+ *
400
+ * Blank is not a model: a whitespace-only value at either layer reads as
401
+ * unset, so an empty settings field cannot produce an empty flag value.
402
+ * @param requested - the delegation's own model, when it named one.
403
+ * @param configured - the plugin config's per-round resolver, when the plugin
404
+ * passed one.
405
+ * @returns `{ model }` when either layer names one, `{}` otherwise — the
406
+ * fragment a provider spreads into its run spec, so an absent model leaves
407
+ * the spawn argv byte-identical to the shape that shipped before.
408
+ */
409
+ export declare function resolveRoundModel(requested: string | undefined, configured?: () => string | undefined): {
410
+ model?: string;
411
+ };
412
+ /**
413
+ * Refuse a scoped round that would be served by a live driver. The resident
414
+ * runtimes (codex's `app-server`, claude's and kimi's ACP servers, the
415
+ * sub-dsh `serve`) are started per member against the DEFAULT scoped home and
416
+ * outlive a single round, so a scoped round taken by one would run under the
417
+ * wrong credentials and write its records into the wrong directory. Refused
418
+ * rather than silently downgraded to exec: the caller named a scope, and a
419
+ * quiet fallback would answer a different question than the one asked. The
420
+ * evaluation pins `drive: exec` anyway (web-eval frozen decision 2).
421
+ * @param scope - the round's scope, or undefined for the default one.
422
+ * @param provider - the provider name, for the error message.
423
+ * @throws when a named scope meets an active live driver.
424
+ */
425
+ export declare function assertScopeExecOnly(scope: string | undefined, provider: string): void;
426
+ /** Poll interval for a manual-handoff login watch. */
427
+ export declare const MANUAL_LOGIN_POLL_MS = 2000;
428
+ /** How long a manual-handoff login watches before reporting the timeout. */
429
+ export declare const MANUAL_LOGIN_LIMIT_MS: number;
430
+ /** Heartbeat interval for facade-tracked in-flight runs. */
431
+ export declare const RUN_PROGRESS_HEARTBEAT_MS = 5000;
432
+ /**
433
+ * How long after a run's result settles the facade still routes progress to
434
+ * that call's `onProgress`.
435
+ *
436
+ * A round's settle OBSERVATION (its observed model, CLI version, and token
437
+ * usage) is only knowable once the CLI process has exited and its whole
438
+ * output stream has been parsed — which, on every exec-drive provider, is
439
+ * strictly after `run.result` resolves (a cancelled run settles at the cancel
440
+ * moment, before the process is even reaped). Clearing the tracked run at
441
+ * result-settle therefore dropped exactly the one report the caller asked for:
442
+ * the `settled` payload reached the cordis event and nothing else. The entry's
443
+ * cancel lever and heartbeat still stop at result-settle — only the progress
444
+ * route is held open, and it closes the moment the `settled` report arrives.
445
+ */
446
+ export declare const RUN_PROGRESS_SETTLE_GRACE_MS = 60000;
447
+ /** Socket filename of the member-bridge listener, under the shared homes root. */
448
+ export declare const MEMBER_BRIDGE_SOCKET_FILENAME = "member-bridge.sock";
449
+ /**
450
+ * The per-harness append-only delegation-mapping log inside the harness's
451
+ * scoped home (`<homeDir>/<harness>/delegations.jsonl`): one JSON record per
452
+ * line, the last line per childSessionId wins — the same replace semantics as
453
+ * {@link LocalAgentRegistry.recordDelegation}. The file grows without
454
+ * rotation (accepted; same growth class as session_index/rollout files).
455
+ */
456
+ export declare const DELEGATIONS_FILENAME = "delegations.jsonl";
457
+ /**
458
+ * Resolve the working directory one CLI delegation round runs in: the caller's
459
+ * override when supplied (the {@link DelegationCallOptions.cwd} position —
460
+ * an orchestrator giving each evaluation cell its own directory), otherwise
461
+ * the parent session's cwd. Returns `undefined` when neither is present — the
462
+ * caller fails loud on that, exactly as it did before overrides existed.
463
+ * Named after the seam the official ACP provider uses for the same override;
464
+ * the future container-workdir layer (web-eval I3) lands here too.
465
+ * @param parentCwd - the parent session's cwd, when the session has one.
466
+ * @param override - the caller-supplied cwd, when one was passed.
467
+ * @returns the effective cwd, or undefined when neither source has one.
468
+ */
469
+ export declare function resolveChildCwd(parentCwd: string | undefined, override: string | undefined): string | undefined;
470
+ /**
471
+ * Enforce the resume-cwd consistency rule: a CLI session continues in the
472
+ * directory its earlier rounds ran in, so a round whose effective cwd differs
473
+ * from the recorded first-round cwd is rejected instead of resuming the
474
+ * conversation somewhere else. Records written before the `cwd` field existed
475
+ * carry no anchor and opt out — absence is the honest state, never a guess.
476
+ * @param record - the delegation's persisted record, when one exists.
477
+ * @param effectiveCwd - the round's resolved working directory.
478
+ * @param provider - the provider name, for the error message.
479
+ * @throws when the record anchors a different first-round cwd.
480
+ */
481
+ export declare function assertResumeCwdUnchanged(record: LocalAgentDelegationRecord | undefined, effectiveCwd: string, provider: string): void;
482
+ export declare const Config: z;
483
+ /** Service name provided by this plugin and injected by patch rows. */
484
+ export declare const LOCAL_AGENT_SERVICE = "localAgent";
485
+ declare module '@deepseek-ai/cordis' {
486
+ interface Context {
487
+ localAgent: LocalAgentRegistry;
488
+ }
489
+ interface Events {
490
+ /**
491
+ * A harness became resolvable: its scoped home exists and its command
492
+ * family is registered.
493
+ * @param harness - the registered harness.
494
+ * @mode emit
495
+ */
496
+ 'localAgent/harness-added'(harness: LocalAgentHarness): void;
497
+ /**
498
+ * A harness left the registry; its command family is unregistered.
499
+ * @param name - the harness name that no longer resolves.
500
+ * @mode emit
501
+ */
502
+ 'localAgent/harness-removed'(name: string): void;
503
+ /**
504
+ * Progress for one delegation run. The facade emits `heartbeat` payloads
505
+ * while a facade-tracked run is in flight; providers report the
506
+ * data-bearing kinds through `LocalAgentRegistry.reportRunProgress` and
507
+ * the registry re-emits them here (reports for sessions with no
508
+ * facade-tracked run still emit, so a provider-only run stays
509
+ * observable).
510
+ * @param childSessionId - the dsh child session id of the run.
511
+ * @param progress - the progress payload.
512
+ * @mode emit
513
+ */
514
+ 'localAgent/run-progress'(childSessionId: string, progress: LocalAgentRunProgress): void;
515
+ }
516
+ }
517
+ /**
518
+ * Split one `/<harness>` input into its optional `--scope <name>` flag and the
519
+ * remaining subcommand. Both spellings a person reaches for are accepted
520
+ * (`--scope eval-b` and `--scope=eval-b`); everything else is left in place,
521
+ * so an input without the flag comes back exactly as it went in.
522
+ * @param input - the trimmed subcommand input.
523
+ * @returns the remaining input and the scope, or the error naming the rule.
524
+ */
525
+ export declare function parseScopeFlag(input: string): {
526
+ input: string;
527
+ scope?: string;
528
+ error?: string;
529
+ };
530
+ /**
531
+ * Registry of local code-agent harnesses and the per-harness command family.
532
+ * @module @khorsheed/dsh-local-agent
533
+ */
534
+ export declare class LocalAgentRegistry {
535
+ private readonly ctx;
536
+ private readonly homesRoot;
537
+ private readonly loginPromptTimeoutMs;
538
+ private readonly preparedMembers;
539
+ private readonly preparations;
540
+ readonly memberInbox: MemberInbox;
541
+ private readonly memberControls;
542
+ /** All provider starts, including direct tool starts, acquire the same whole-turn lease. */
543
+ withMemberConfigurationRound(binding: LocalAgentMemberBinding, start: (configuration: LocalAgentAppliedConfiguration) => Promise<SubagentRun>, signal?: AbortSignal, onAdmitted?: () => void | Promise<void>): Promise<SubagentRun>;
544
+ private requireInboxMember;
545
+ enqueueMemberInput(childSessionId: string, text: string, requestId: string): string;
546
+ resumeMemberInbox(childSessionId: string): void;
547
+ readMemberInbox(childSessionId: string): LocalAgentMemberInbox;
548
+ queuedMemberRounds(childSessionId: string): number;
549
+ /** The admitted snapshot belongs to this exact run object, including after settle. */
550
+ runConfiguration(run: object): LocalAgentAppliedConfiguration | undefined;
551
+ supportsMemberConfiguration(provider: string): boolean;
552
+ private memberControl;
553
+ /** Execution identity is available before the first native transcript exists. */
554
+ memberBinding(childSessionId: string): LocalAgentMemberBinding | undefined;
555
+ memberConfiguration(childSessionId: string): LocalAgentMemberControlState;
556
+ selectMemberConfiguration(childSessionId: string, requestId: string, expectedRevision: number, selection: LocalAgentMemberConfiguration): Promise<LocalAgentControlReceipt>;
557
+ cancelMemberConfiguration(childSessionId: string, requestId: string, expectedRevision: number): LocalAgentControlReceipt;
558
+ retryMemberConfiguration(childSessionId: string, expectedRevision: number): Promise<void>;
559
+ followMemberConfiguration(childSessionId: string, signal: AbortSignal): AsyncIterable<LocalAgentMemberControlState>;
560
+ /** Compatibility write routes through the durable slot, including while busy. */
561
+ setMemberModel(childSessionId: string, model: string | undefined): Promise<void>;
562
+ /** Independent live output transport for mirrored sessions without native Agents. */
563
+ readonly liveStreams: LocalAgentStreams;
564
+ private readonly harnesses;
565
+ /**
566
+ * Named scopes materialized in this host process, keyed exactly like their
567
+ * directory (`<name>@<scope>`) — the guard that keeps the mkdir, the
568
+ * harness provisioning and the per-directory delegation load to once each.
569
+ */
570
+ private readonly scopedHomes;
571
+ /**
572
+ * The provisioning currently running for each materialized scope.
573
+ *
574
+ * Materialization fires the harness's hook and does not wait for it — the
575
+ * directory is what the caller asked for, and the hook is best-effort. A
576
+ * caller that then provisions the SAME scope deliberately would otherwise
577
+ * race it, and both writers regenerate the same files. Recording the
578
+ * promise lets {@link LocalAgentRegistry.provisionScope} let the first one
579
+ * finish before it starts.
580
+ */
581
+ private readonly scopeProvisioning;
582
+ private readonly commandDisposers;
583
+ private readonly logins;
584
+ /**
585
+ * One delegation per dsh child session id: the CLI session id a resumed
586
+ * round continues, plus the ownership proof (parent session) and the
587
+ * provider that owns the CLI session.
588
+ */
589
+ private readonly delegations;
590
+ /**
591
+ * Observation recency for {@link latestObservedModel}: a monotonically
592
+ * increasing stamp per child session, bumped every time a record carrying an
593
+ * `observedModel` lands in {@link delegations} (recordDelegation,
594
+ * recordRoundSettled's merge, and the boot-time `delegations.jsonl` load —
595
+ * file order stamps the load, so the log's own last-line-wins semantics carry
596
+ * over). In-memory only: the records themselves deliberately carry no
597
+ * timestamp, and the Map's insertion order cannot serve (a `set` on an
598
+ * existing key keeps its first position, so an old record re-observed by a
599
+ * fresh round would sort as old).
600
+ */
601
+ private readonly observedModelStamps;
602
+ /** The stamp counter for {@link observedModelStamps}. */
603
+ private observedModelClock;
604
+ /**
605
+ * A settled round's observation that arrived BEFORE the delegation record
606
+ * existed ({@link recordRoundSettled} has nothing to merge into yet), held
607
+ * until {@link recordDelegation} lands and consumed there. A settle and the
608
+ * first-round record are ordered on every provider path today, but the
609
+ * record point is the provider's (a live round records at session/new, an
610
+ * exec round at the settle-time output parse), so the channel tolerates the
611
+ * inversion instead of dropping the observation. In-memory only and never
612
+ * persisted on its own: an observation whose record never arrives stays
613
+ * unrecorded, exactly as before.
614
+ */
615
+ private readonly pendingRoundObservations;
616
+ /**
617
+ * Per-(parent session, provider) FIFO of delegation intents. The family
618
+ * resume tool stages exactly one intent per call before awaiting
619
+ * `ctx.subagents.start()`, and the owning provider consumes exactly one per
620
+ * start, so fresh and resume rounds stay paired even under parallel
621
+ * delegations from one parent.
622
+ */
623
+ private readonly delegationIntents;
624
+ /**
625
+ * Per-child-session resume locks: a dsh child session may have only one
626
+ * in-flight resume at a time. The provider takes the lock before spawning
627
+ * the resume CLI and releases it when the run settles, so a second resume
628
+ * of the same child fails loud instead of racing the first process.
629
+ */
630
+ private readonly resumeLocks;
631
+ /**
632
+ * Kimi transcript-line mirror offset per child session, tracked
633
+ * INDEPENDENTLY of the delegation record: the offset must survive even when
634
+ * the CLI session hint is not parsed from stderr (the record — and therefore
635
+ * a resume — needs the hint, but the mirror bookkeeping does not). Without
636
+ * this separation a resume round would fall back to offset 0 and duplicate
637
+ * the first round's messages.
638
+ */
639
+ private readonly kimiMirrorOffsets;
640
+ /**
641
+ * Delegation-reported auth failures per harness (epoch ms of the report).
642
+ * Presence probes cannot see a server-side revocation, so a provider
643
+ * observing a 401/403 reports it here and `statusOf` downgrades the harness
644
+ * until its credential marker is rewritten by a real re-login (stamp newer
645
+ * than the mark).
646
+ */
647
+ private readonly authFailures;
648
+ /**
649
+ * Delegation-reported auth SUCCESSES per harness (epoch ms of the report):
650
+ * a round that reached the CLI's endpoint and completed, which is the only
651
+ * evidence a host has that a present credential is actually live. Cleared
652
+ * when a login starts or a logout lands, because both change which account
653
+ * the scoped home holds — a verification of the previous credential says
654
+ * nothing about the next one. Per host process on purpose: after a restart
655
+ * a present credential is `present-unverified` until a round exercises it,
656
+ * which is exactly what is known.
657
+ */
658
+ private readonly authSuccesses;
659
+ /**
660
+ * In-flight runs registered with the member channel, by per-run token. The
661
+ * bridge MCP server presents its token (and parent pid) on every callback;
662
+ * entries are invalidated on the run's settle path.
663
+ */
664
+ private readonly memberRuns;
665
+ /**
666
+ * The ACTIVE DELEGATION REGISTRY — in-flight local-agent runs by dsh child
667
+ * session id, the key the `/local-agent stop <childSessionId>` command and
668
+ * {@link LocalAgentRegistry.cancel} look up. Two registration kinds share
669
+ * the map:
670
+ *
671
+ * - **facade-tracked** ({@link trackRun}): runs started through
672
+ * {@link LocalAgentRegistry.start} / {@link LocalAgentRegistry.resume},
673
+ * carrying the fused AbortController whose signal reached
674
+ * `ctx.subagents.start`; cancel aborts it.
675
+ * - **tool-registered** ({@link trackDelegationRun}): runs the family tool
676
+ * starts directly through `ctx.subagents.start`, carrying an explicit
677
+ * cancel lever (its fused controller); cancel calls it.
678
+ *
679
+ * Every entry clears itself when the run's result settles (any stop
680
+ * reason), and facade entries also stop their heartbeat timer then.
681
+ */
682
+ private readonly runs;
683
+ /**
684
+ * Progress routes held open past their run's result-settle, by child session
685
+ * id — see {@link RUN_PROGRESS_SETTLE_GRACE_MS}. An entry carries only the
686
+ * call's `onProgress` (never a cancel lever: a settled run is not
687
+ * cancellable) and self-destructs on the `settled` report or the grace
688
+ * timer, whichever comes first.
689
+ */
690
+ private readonly settledRuns;
691
+ /**
692
+ * Detach disposers for child sessions the facade reattached into the live
693
+ * store (see {@link LocalAgentRegistry.resume} step 5). Held for the plugin
694
+ * lifetime — the same lifecycle a provider-created child session has — and
695
+ * released together on plugin dispose. The reattach's WRITE handle is NOT
696
+ * here: it lives in {@link childWriteHandles}, shared with
697
+ * {@link syncChildSession}, and closes with the cache on dispose.
698
+ */
699
+ private readonly reattachDisposers;
700
+ /**
701
+ * The registry-owned WRITE handle per child session id (a resolved-value
702
+ * cache: the PROMISE is stored so concurrent acquirers share one open).
703
+ * Host 0.1.5 routes a live session's `session/event` appends into the
704
+ * per-id writer only while a write handle is open, so exactly one handle
705
+ * must exist per child — a second open fails SessionAlreadyOwnedError.
706
+ * Both owners of a child session's durability share this cache: the
707
+ * reattach recipe ({@link reattachChildSession}) and the explicit suffix
708
+ * sync ({@link syncChildSession}). Every handle closes on plugin dispose.
709
+ */
710
+ private readonly childWriteHandles;
711
+ /**
712
+ * @param ctx - context carrying the command registry.
713
+ * @param homesRoot - shared scoped-homes root.
714
+ * @param loginPromptTimeoutMs - device-code prompt wait.
715
+ */
716
+ constructor(ctx: Context, homesRoot: string, loginPromptTimeoutMs: number);
717
+ /**
718
+ * Absolute scoped home for one harness, in the default scope or in a NAMED
719
+ * one.
720
+ *
721
+ * The default scope (`scope` absent) is `<homesRoot>/<name>` and is a pure
722
+ * path computation, exactly as it always was. A named scope is the sibling
723
+ * directory `<homesRoot>/<name>@<scope>` — a name, never a path, so the
724
+ * directory can only ever be under the homes root — and is MATERIALIZED the
725
+ * first time it is named here: `mkdir` 0700 plus this harness's own
726
+ * provisioning, plus the load of that directory's own `delegations.jsonl`,
727
+ * once per (harness, scope) in this host process. Materializing on the read
728
+ * is what makes "a scope exists once something names it" true for every
729
+ * caller at once: `/<name> login --scope`, the status surfaces, a scoped
730
+ * delegation, and the evaluation's mount source.
731
+ *
732
+ * Credentials are never copied between scopes: a new named scope starts
733
+ * empty, reports `credentialState: absent`, and needs its own login.
734
+ * @param name - the harness name.
735
+ * @param scope - the named scope, or undefined for the default one.
736
+ * @returns `<homesRoot>/<name>`, or `<homesRoot>/<name>@<scope>`.
737
+ * @throws when the scope is present but not a `[a-z0-9-]` name.
738
+ */
739
+ homeDir(name: string, scope?: string): string;
740
+ /**
741
+ * Create one named scope's directory the first time it is named, with the
742
+ * same 0700 the default scope's gets, then run the harness's own
743
+ * provisioning and load that directory's persisted delegation mappings.
744
+ * Runs at most once per (harness, scope) per host process; the mkdir itself
745
+ * is idempotent, so a directory a person created by hand is adopted rather
746
+ * than refused. An unregistered harness gets the directory and nothing
747
+ * else — there is no harness to ask for provisioning yet.
748
+ */
749
+ private materializeScopedHome;
750
+ /**
751
+ * Provision ONE scope deliberately, with per-scope inputs, and report what
752
+ * the scope now holds.
753
+ *
754
+ * The difference from the materialization above is the whole reason this
755
+ * exists. Materialization runs once per (harness, scope) per host process,
756
+ * passes no options, and swallows failures — right for "somebody named a
757
+ * scope". A caller that provisions a scope on purpose is asking a question
758
+ * (`compose this preset`), needs the answer (`this is what it composes
759
+ * now`), and needs the failure (a preset that could not be composed must
760
+ * not read as one that was). So this one awaits, propagates, and returns
761
+ * the harness's read-back.
762
+ *
763
+ * A harness with no `provision` hook is not an error: there is simply
764
+ * nothing to do, and the caller's own read-back — the file it was going to
765
+ * check anyway — reports what is actually there.
766
+ * @param name - the harness name.
767
+ * @param scope - the named scope, or undefined for the default scoped home.
768
+ * @param options - per-scope inputs for the harness's provisioning.
769
+ * @returns the scoped home plus whatever the harness read back.
770
+ * @throws whatever the harness's provisioning throws.
771
+ */
772
+ provisionScope(name: string, scope?: string, options?: LocalAgentScopeProvisionOptions): Promise<LocalAgentScopeProvisionResult & {
773
+ homeDir: string;
774
+ }>;
775
+ /**
776
+ * Register one harness: provision its scoped home, register the
777
+ * `/<name> login|sessions` command family, and publish the added event.
778
+ * @param harness - the harness definition.
779
+ * @returns the disposer unregistering the command and the harness.
780
+ */
781
+ register(harness: LocalAgentHarness): () => void;
782
+ /**
783
+ * Resolve one harness by name.
784
+ * @param name - the harness name.
785
+ * @returns the harness, or undefined when it is not registered.
786
+ */
787
+ get(name: string): LocalAgentHarness | undefined;
788
+ /**
789
+ * Resolve the harness that claims one delegation provider — the inverse of
790
+ * {@link LocalAgentHarness.delegationProvider}, for surfaces handed a
791
+ * provider name (a delegation record's) that need the owning harness's
792
+ * knobs (its model broker).
793
+ * @param provider - the `ctx.subagents` provider name.
794
+ * @returns the claiming harness, or undefined when none claims it.
795
+ */
796
+ harnessForProvider(provider: string): LocalAgentHarness | undefined;
797
+ /**
798
+ * Every registered harness name, in registration order.
799
+ * @returns the harness names.
800
+ */
801
+ list(): readonly string[];
802
+ /**
803
+ * Every registered harness as a roster row, in registration order.
804
+ * @returns the roster rows.
805
+ */
806
+ roster(): readonly LocalAgentRosterRow[];
807
+ /**
808
+ * Record a delegation-observed authentication failure (a 401/403 from the
809
+ * CLI's endpoint). Presence probes cannot see a server-side revocation, so
810
+ * this mark downgrades the harness's reported status until a real re-login
811
+ * rewrites the credential marker (see {@link LocalAgentHarness.credentialStamp}).
812
+ * @param name - the harness whose credential failed.
813
+ * @param detail - the endpoint's answer, for the log.
814
+ * @param scope - the scope whose credential failed; absent means the
815
+ * default scope. Each scope holds its own account, so the mark is per
816
+ * scope: one scope's revocation says nothing about another's.
817
+ */
818
+ reportAuthFailure(name: string, detail: string, scope?: string): void;
819
+ /**
820
+ * Record a delegation-observed authentication SUCCESS: a round reached the
821
+ * harness CLI's endpoint and completed, so the scoped credential is known
822
+ * live and the status grades it `verified` instead of `present-unverified`.
823
+ * The counterpart of {@link reportAuthFailure}; providers wire both from the
824
+ * same settle path. A success newer than a failure mark also clears the
825
+ * `rejected` grade — a completed round outranks a stale rejection.
826
+ * @param name - the harness whose credential just worked.
827
+ * @param scope - the scope whose credential just worked; absent means the
828
+ * default scope.
829
+ */
830
+ reportAuthSuccess(name: string, scope?: string): void;
831
+ /**
832
+ * Forget what delegation rounds observed about one harness's credential.
833
+ * Called where the account itself can change — a login starting, a logout
834
+ * landing — because a verdict about the previous credential describes
835
+ * nothing about the next one.
836
+ */
837
+ private forgetCredentialObservations;
838
+ /**
839
+ * Query one harness's auth status, in the default scope or in a named one.
840
+ * @param name - the harness name.
841
+ * @param scope - the named scope to report on; absent means the default
842
+ * scope and the reply is exactly what it always was. A named scope is
843
+ * materialized by the read (see {@link homeDir}), so a scope nobody has
844
+ * logged into reports `credentialState: absent` against a real, empty
845
+ * directory.
846
+ * @returns the status snapshot.
847
+ */
848
+ statusOf(name: string, scope?: string): Promise<LocalAgentStatus>;
849
+ /**
850
+ * Grade one harness's credential from the two channels a status read has:
851
+ * the scoped home's own shape ({@link LocalAgentHarness.isAuthenticated})
852
+ * and what delegation rounds reported. Presence alone can only ever say
853
+ * `present-unverified` — an expired, unrefreshable record has exactly the
854
+ * shape of a working one, and saying so is this grade's whole point.
855
+ * @param harness - the harness to grade.
856
+ * @param homeDir - its scoped home.
857
+ * @param scope - the scope that directory belongs to; the delegation-
858
+ * reported observations are per scope, because each scope holds its own
859
+ * account.
860
+ * @returns the credential grade.
861
+ */
862
+ private credentialStateOf;
863
+ /**
864
+ * One harness's fairness-relevant effective settings — the evaluation
865
+ * condition hash's read side (web-eval frozen decisions 2 to 4: drive,
866
+ * approval boundary, reasoning effort, endpoint route). Read-only: the
867
+ * snapshot reflects the settings in force right now, whatever layer set
868
+ * them (plugin config, scoped-home config a person edited, process env).
869
+ * Pure JSON and credential-free by contract; see
870
+ * {@link LocalAgentEffectiveSettings}.
871
+ * @param name - the harness name.
872
+ * @param scope - the scope to snapshot; absent means the default one.
873
+ * @returns the snapshot, or undefined when the harness declares none.
874
+ */
875
+ effectiveSettings(name: string, scope?: string): Promise<LocalAgentEffectiveSettings | undefined>;
876
+ /**
877
+ * Query one harness's scoped session records.
878
+ * @param name - the harness name.
879
+ * @param scope - the scope to list; absent means the default one. Session
880
+ * records belong to the directory the rounds ran in, so a named scope
881
+ * lists its own and nothing of the default scope's.
882
+ * @returns the session records in the harness's own order.
883
+ */
884
+ sessionsOf(name: string, scope?: string): Promise<readonly LocalAgentSessionRecord[]>;
885
+ /**
886
+ * Record one delegation's CLI-session mapping after its first round
887
+ * settles, so a later resume round can continue the same CLI session. The
888
+ * provider learns the CLI session id only from the settled round's output,
889
+ * so recording happens post-settle. A duplicate child session id replaces
890
+ * the earlier record: a resumed child keeps one mapping. The record is also
891
+ * appended to the owning harness's `delegations.jsonl` (last line per child
892
+ * session wins), so a host restart keeps the mapping resolvable. A settled
893
+ * round's observation that arrived before this record (the provider's
894
+ * record point is its own) is merged in from the pending stash — see
895
+ * {@link recordRoundSettled}.
896
+ * @param record - the delegation's dsh child session id, provider, owning
897
+ * parent session id, and CLI session id.
898
+ */
899
+ recordDelegation(record: LocalAgentDelegationRecord): void;
900
+ /**
901
+ * Resolve the CLI-session mapping a resume tool call needs to continue a
902
+ * previous delegation. Ownership is proven by the recorded parent session
903
+ * id: a caller naming a child session it did not delegate is rejected
904
+ * instead of resuming someone else's conversation context.
905
+ * @param childSessionId - the dsh child session id from the first round.
906
+ * @param claims - the caller's provider and parent session id.
907
+ * @returns the recorded CLI session id to continue.
908
+ * @throws when the child session is unknown, owned by another parent, or
909
+ * served by a different provider than claimed.
910
+ */
911
+ resolveDelegation(childSessionId: string, claims: {
912
+ readonly provider: string;
913
+ readonly parentSessionId: string;
914
+ }): {
915
+ readonly cliSessionId: string;
916
+ };
917
+ /**
918
+ * Stage one delegation intent for a provider's next `start()`. The family
919
+ * resume tool stages exactly one intent per call before awaiting
920
+ * `ctx.subagents.start()`; the provider consumes exactly one per start via
921
+ * {@link takeDelegationIntent}, so the per-(parent, provider) FIFO pairs
922
+ * fresh and resume rounds even under parallel delegations from one parent.
923
+ * @param parentSessionId - the delegating parent session id.
924
+ * @param provider - the `ctx.subagents` provider name.
925
+ * @param intent - the fresh or resume intent for the next start.
926
+ */
927
+ stageDelegationIntent(parentSessionId: string, provider: string, intent: LocalAgentDelegationIntent): void;
928
+ /**
929
+ * Consume the oldest staged intent for one provider start, or `undefined`
930
+ * when the queue is empty (a provider invoked directly without the family
931
+ * tool stages nothing and starts a fresh round).
932
+ * @param parentSessionId - the delegating parent session id.
933
+ * @param provider - the `ctx.subagents` provider name.
934
+ * @returns the oldest staged intent, or undefined when none is staged.
935
+ */
936
+ takeDelegationIntent(parentSessionId: string, provider: string): LocalAgentDelegationIntent | undefined;
937
+ /**
938
+ * Remove one previously staged intent by reference, returning whether it was
939
+ * still queued. The delegation facade calls this when `ctx.subagents.start()`
940
+ * throws: an intent the provider never consumed must not linger in the FIFO,
941
+ * where the NEXT same-(parent, provider) start would misconsume it as its own
942
+ * (orphan-intent crosstalk). When the provider already consumed the intent
943
+ * before throwing, the removal is a no-op and the pairing stays intact.
944
+ * @param parentSessionId - the delegating parent session id.
945
+ * @param provider - the `ctx.subagents` provider name.
946
+ * @param intent - the exact intent object passed to
947
+ * {@link stageDelegationIntent}.
948
+ * @returns whether the intent was removed (false when already consumed or
949
+ * never staged).
950
+ */
951
+ unstageDelegationIntent(parentSessionId: string, provider: string, intent: LocalAgentDelegationIntent): boolean;
952
+ /**
953
+ * Every recorded delegation, in recording order. The invariant companion
954
+ * cross-checks each record's provider against the mounted subagent
955
+ * providers so a provider rename cannot leave a stale resume mapping.
956
+ * @returns the recorded delegations.
957
+ */
958
+ listDelegations(): readonly LocalAgentDelegationRecord[];
959
+ /**
960
+ * Read-only lookup of one delegation by its dsh child session id — the
961
+ * member channel's membership check. Unlike {@link resolveDelegation} this
962
+ * never throws and performs no ownership assertion: the gateway composes it
963
+ * with {@link resume} (whose ownership checks are unchanged) for the
964
+ * human-opened child session, where the record itself is the authorization
965
+ * source.
966
+ * @param childSessionId - the dsh child session id.
967
+ * @returns the record, or undefined when this child was never delegated
968
+ * through the family.
969
+ */
970
+ getDelegation(childSessionId: string): LocalAgentDelegationRecord | undefined;
971
+ /**
972
+ * Read-only lookup of one delegation's facts by its dsh child session id,
973
+ * WITHOUT the CLI-session resume handle — the orchestrator-facing read side
974
+ * (the evaluation reads a cell's recorded `observedModel` and first-round
975
+ * `cwd` through it). Same never-throws contract as
976
+ * {@link getDelegation}; the record's optional fields stay absent when the
977
+ * stream never yielded them.
978
+ * @param childSessionId - the dsh child session id.
979
+ * @returns the record without `cliSessionId`, or undefined when this child
980
+ * was never delegated through the family.
981
+ */
982
+ delegationOf(childSessionId: string): LocalAgentDelegationInfo | undefined;
983
+ /**
984
+ * Stamp a record's `observedModel` with the next recency tick, when the
985
+ * record carries a usable one. Called everywhere a record with an observed
986
+ * model lands in {@link delegations}; a blank value is no observation.
987
+ */
988
+ private noteObservedModel;
989
+ /**
990
+ * The most recent model one provider was OBSERVED running across all its
991
+ * recorded delegations — the display hint behind the cli-builtin layer
992
+ * (nothing names the CLI's compiled default, but "what ran last" is what a
993
+ * round with no model layer would run again). Recency is the observation
994
+ * stamp ({@link observedModelStamps}), not record insertion order: a round
995
+ * that re-observes an old delegation counts as the freshest. Records whose
996
+ * stream never yielded a model (absent or blank) are not candidates.
997
+ * @param provider - the `ctx.subagents` provider name to scan.
998
+ * @returns the newest observed model identifier, or undefined when this
999
+ * provider was never observed running one.
1000
+ */
1001
+ latestObservedModel(provider: string): string | undefined;
1002
+ /**
1003
+ * Record one delegation round's settled observation: merge `observedModel`
1004
+ * into the delegation record when present (in memory and `delegations.jsonl`,
1005
+ * same replace semantics as {@link recordDelegation}) and report the
1006
+ * `settled` run-progress event with the round's observed model and usage.
1007
+ * Providers call it once per settled round, at the point their output
1008
+ * stream has been fully parsed; fields the stream did not yield stay absent
1009
+ * — absence is recorded, never guessed. A record that does not exist yet
1010
+ * (the round's settle beat the provider's record point — e.g. a round whose
1011
+ * CLI session id only parses post-settle) does not lose the observation: it
1012
+ * is stashed and merged by the later {@link recordDelegation}; the event
1013
+ * still reports either way.
1014
+ * @param childSessionId - the dsh child session id of the settled round.
1015
+ * @param round - the round's observed model, version, usage and tool-call
1016
+ * accounting, each optional.
1017
+ */
1018
+ recordRoundSettled(childSessionId: string, round: {
1019
+ readonly observedEffort?: string;
1020
+ readonly observedModel?: string;
1021
+ readonly cliVersion?: string;
1022
+ readonly usage?: TokenUsage;
1023
+ readonly toolCalls?: LocalAgentToolCalls;
1024
+ }): void;
1025
+ /**
1026
+ * Acquire the resume lock for one child session. A dsh child session may
1027
+ * have only one in-flight resume: the provider takes the lock before
1028
+ * spawning the resume CLI and releases it when the run settles, so a second
1029
+ * resume of the same child fails loud instead of racing the first process.
1030
+ * Fresh delegations never take the lock (they mint a new child session).
1031
+ * @param childSessionId - the dsh child session id to resume.
1032
+ * @returns whether the lock was acquired (false when already held).
1033
+ */
1034
+ acquireResumeLock(childSessionId: string): boolean;
1035
+ /**
1036
+ * Release the resume lock for one child session. The provider releases it
1037
+ * on every settle path (completed, failed, cancelled) so a deadlock never
1038
+ * strands a later resume. Releasing an unheld lock is a no-op.
1039
+ * @param childSessionId - the dsh child session id.
1040
+ */
1041
+ releaseResumeLock(childSessionId: string): void;
1042
+ /**
1043
+ * Read-only probe of the per-child resume lock. The delegation facade uses
1044
+ * it to fail fast BEFORE staging an intent; the authoritative mutual
1045
+ * exclusion stays with the provider's `acquireResumeLock` inside `start()`.
1046
+ * @param childSessionId - the dsh child session id.
1047
+ * @returns whether a resume of this child session is currently in flight.
1048
+ */
1049
+ isResumeLocked(childSessionId: string): boolean;
1050
+ private memberBridgeAvailable;
1051
+ /** Listener lifecycle owns this flag; it is never persisted across host restarts. */
1052
+ setMemberBridgeAvailable(available: boolean): void;
1053
+ /** Transport/readiness capability only; authenticated generation needs separate acceptance. */
1054
+ canCoordinateRoom(childSessionId: string): boolean;
1055
+ /**
1056
+ * Register one CLI run with the member channel and mint its per-run token.
1057
+ * The provider calls this before spawning the CLI (fresh and resume rounds
1058
+ * alike) and injects the token into the bridge MCP server's environment; the
1059
+ * token is how the host resolves which run — and therefore which member — a
1060
+ * bridge callback belongs to, so a member's identity is never self-reported.
1061
+ * @param run - the run's member identity (child session, parent, provider).
1062
+ * @returns the minted token, invalidated by {@link unregisterMemberRun}.
1063
+ */
1064
+ registerMemberRun(run: LocalAgentMemberRun): string;
1065
+ /**
1066
+ * Resolve a member-channel token to its run, or undefined when the token is
1067
+ * unknown or already invalidated (the run settled).
1068
+ * @param token - the token the bridge presented.
1069
+ * @returns the registered run, or undefined.
1070
+ */
1071
+ resolveMemberRun(token: string): LocalAgentMemberRun | undefined;
1072
+ /**
1073
+ * Invalidate a run's member-channel token on its settle path (any stop
1074
+ * reason). A bridge callback arriving after settle is an unknown token.
1075
+ * @param token - the token to invalidate.
1076
+ */
1077
+ unregisterMemberRun(token: string): void;
1078
+ /**
1079
+ * The loopback socket path the member-bridge listener owns. Providers inject
1080
+ * it into the bridge's environment so the stdio MCP server knows where to
1081
+ * call back.
1082
+ * @returns the unix socket path under the shared homes root.
1083
+ */
1084
+ memberBridgeSocketPath(): string;
1085
+ /**
1086
+ * The stdio MCP server entry for the member bridge, for providers to write
1087
+ * into their CLI's scoped MCP config. The bridge script ships inside this
1088
+ * package and runs under the same Node that runs the host.
1089
+ * @returns the command + args declaring the bridge server.
1090
+ */
1091
+ memberBridgeCommand(): {
1092
+ command: string;
1093
+ args: string[];
1094
+ };
1095
+ /** Prepare a roster-owned identity without a paid prompt; repeated calls coalesce. */
1096
+ prepareMember(parentSessionId: string, provider: string, childSessionId: string, options?: DelegationCallOptions): Promise<string>;
1097
+ private prepareMemberNow;
1098
+ isPreparedMember(childSessionId: string): boolean;
1099
+ /**
1100
+ * Start a FRESH delegation on one subagent provider — the programmatic
1101
+ * equivalent of the family tool's fresh call, for plugins acting on the
1102
+ * user's behalf (room, orchestrators). One call performs the provider
1103
+ * pre-check, resolves the live parent agent, stages exactly one
1104
+ * `{ kind: 'fresh' }` intent, and starts the provider in the same
1105
+ * synchronous flow, so the per-(parent, provider) FIFO pairing holds. When
1106
+ * the start throws, the staged intent is rolled back via
1107
+ * {@link unstageDelegationIntent} (a no-op once the provider consumed it).
1108
+ *
1109
+ * The returned run is tracked under its `run.id` (== the new dsh child
1110
+ * session id) until `run.result` settles; {@link cancel} aborts it by that
1111
+ * id. Note on trust: cordis plugins share one process, so the ownership
1112
+ * checks here and in {@link resume} guard against caller MISTAKES, not
1113
+ * against a malicious plugin — their real protection target is untrusted
1114
+ * model text, which never carries the resume handle (first-class parameter
1115
+ * + intent channel only).
1116
+ * @param parentSessionId - the delegating parent session id; must have a
1117
+ * live agent (`ctx.agents.get`, a harness hard constraint).
1118
+ * @param provider - the `ctx.subagents` provider name.
1119
+ * @param prompt - the child user message content blocks.
1120
+ * @param options - optional label and caller-owned abort signal.
1121
+ * @returns the published subagent run.
1122
+ * @throws when the subagents/agents service is not mounted, the provider is
1123
+ * not registered, or the parent session has no live agent — in every case
1124
+ * BEFORE anything is staged.
1125
+ */
1126
+ start(parentSessionId: string, provider: string, prompt: ContentBlock[], options?: DelegationCallOptions): Promise<SubagentRun>;
1127
+ /**
1128
+ * Resume a recorded delegation: continue the SAME CLI session inside the
1129
+ * SAME dsh child session. One call performs, in order:
1130
+ *
1131
+ * 1. provider pre-check (`ctx.subagents.getProvider`) — fails loud WITHOUT
1132
+ * staging, so an unknown provider never leaves an orphan intent;
1133
+ * 2. `resolveDelegation` ownership check (unchanged semantics): the handle
1134
+ * must name a delegation THIS parent session recorded through THIS
1135
+ * provider;
1136
+ * 3. read-only resume-lock probe ({@link isResumeLocked}) — fails fast
1137
+ * without staging; the provider still takes the authoritative lock inside
1138
+ * its `start()`;
1139
+ * 4. live parent agent resolution (`ctx.agents.get`) — a harness hard
1140
+ * constraint, since `ctx.subagents.start` requires a live parent;
1141
+ * 5. child-session REATTACH when `ctx.sessions.get(childSessionId)` is
1142
+ * undefined (recipe below; `opts.reattach === false` disables this step
1143
+ * and fails loud on a non-live child instead);
1144
+ * 6. stage `{ kind: 'resume', childSessionId, cliSessionId }` and, in the
1145
+ * same synchronous flow, `await ctx.subagents.start(provider, …)`;
1146
+ * 7. on start failure, roll the intent back via
1147
+ * {@link unstageDelegationIntent} (no-op once consumed);
1148
+ * 8. on success, track the run under `childSessionId` for {@link cancel}
1149
+ * until `run.result` settles.
1150
+ *
1151
+ * **Reattach recipe** (callers replicating this flow — e.g. room — copy
1152
+ * exactly this sequence):
1153
+ *
1154
+ * ```ts
1155
+ * const handle = await ctx.sessionPersistence.open(SessionId(childSessionId), 'write')
1156
+ * try {
1157
+ * const cold = await handle.read(0)
1158
+ * const session = ctx.sessions.prepare(SessionId(childSessionId), {
1159
+ * seed: [...cold.events],
1160
+ * meta: structuredClone(handle.header),
1161
+ * inheritedEventCount: handle.inheritedEventCount,
1162
+ * eventState: cold.eventState,
1163
+ * })
1164
+ * // The seeded constructor's session/end-seed marker lands before enter()
1165
+ * // installs publication hooks — push the unstored suffix through the
1166
+ * // handle directly, or the writer's contiguous cursor diverges.
1167
+ * const unstored = session.snapshotEvents().slice(cold.events.length)
1168
+ * if (unstored.length > 0) await handle.append(unstored)
1169
+ * const detach = ctx.sessions.enter(session)
1170
+ * try { ctx.sessions.announce(session) }
1171
+ * catch (error) { detach(); throw error }
1172
+ * // Hold BOTH for the reattached lifetime: the backend routes live
1173
+ * // session/event appends into the per-id writer only while the write
1174
+ * // handle is open; closing it ends persistence for the session.
1175
+ * } catch (error) {
1176
+ * await handle.close().catch(() => {})
1177
+ * throw error
1178
+ * }
1179
+ * ```
1180
+ *
1181
+ * Hold `detach` and `handle` for the plugin lifetime. Announce the restored
1182
+ * live session after entering it, rolling back detach if announcement fails.
1183
+ * History followers need this lifecycle edge to replay the constructor's
1184
+ * end-seed suffix before the provider publishes subsequent events.
1185
+ * @param parentSessionId - the delegating parent session id; must match the
1186
+ * recorded one and have a live agent.
1187
+ * @param provider - the `ctx.subagents` provider that owns the CLI session.
1188
+ * @param childSessionId - the dsh child session id from the first round (the
1189
+ * resume handle).
1190
+ * @param prompt - the follow-up user message content blocks.
1191
+ * @param options - optional label and caller-owned abort signal.
1192
+ * @returns the published subagent run.
1193
+ * @throws on unknown provider, unknown/foreign-parent/wrong-provider handle,
1194
+ * an in-flight resume of the same child, or a missing live parent agent —
1195
+ * all BEFORE anything is staged.
1196
+ */
1197
+ resume(parentSessionId: string, provider: string, childSessionId: string, prompt: ContentBlock[], options?: DelegationCallOptions): Promise<SubagentRun>;
1198
+ /**
1199
+ * Cancel an in-flight facade-started run by its dsh child session id: aborts
1200
+ * the internal controller whose (fused) signal was passed to
1201
+ * `ctx.subagents.start`, which is the canonical cancellation channel for a
1202
+ * one-shot run. The provider settles the run with an `aborted`-class stop
1203
+ * reason. A caller holding the `SubagentRun` may also `run.dispose()`.
1204
+ * @param childSessionId - the dsh child session id of the run to cancel.
1205
+ * @returns whether an in-flight run was found and signalled (false on a
1206
+ * miss — never a silent success, never a throw).
1207
+ */
1208
+ cancel(childSessionId: string): boolean;
1209
+ /**
1210
+ * Register a NON-facade in-flight delegation run with the active-delegation
1211
+ * registry, keyed by the dsh child session id, so the `/local-agent stop`
1212
+ * command and {@link cancel} can reach it. This is the family TOOL's entry
1213
+ * point: the tool starts its runs through `ctx.subagents.start` directly
1214
+ * (not the facade), so without this registration its in-flight runs would
1215
+ * be invisible to the stop command. The caller owns the cancel lever — a
1216
+ * controller fused with the run's request signal, aborted exactly like the
1217
+ * facade's tracked controller. The entry clears itself when the run's
1218
+ * result settles (any stop reason); an existing facade entry for the same
1219
+ * child session wins (it already owns cancellation and progress).
1220
+ * @param childSessionId - the dsh child session id (the run id).
1221
+ * @param run - the published subagent run.
1222
+ * @param cancel - the lever that aborts the run's request signal.
1223
+ */
1224
+ trackDelegationRun(childSessionId: string, run: SubagentRun, cancel: () => void): void;
1225
+ /**
1226
+ * Whether the active-delegation registry holds an in-flight run for the
1227
+ * child session — the registry's read side for the `/local-agent stop`
1228
+ * command's miss report (the command cancels through {@link cancel}).
1229
+ * @param childSessionId - the dsh child session id.
1230
+ * @returns whether a run is currently in flight under that id.
1231
+ */
1232
+ isDelegationActive(childSessionId: string): boolean;
1233
+ /**
1234
+ * The child session ids with an in-flight delegation run — the gateway's
1235
+ * `activeDelegations` Remote exposes this to the browser so surfaces can
1236
+ * mark one-shot rows as running.
1237
+ * @returns the in-flight child session ids.
1238
+ */
1239
+ activeDelegations(): readonly string[];
1240
+ /**
1241
+ * Read the kimi transcript lines already mirrored into one child session,
1242
+ * so a resumed round mirrors only its delta instead of duplicating earlier
1243
+ * messages. Absent means the first round has not mirrored yet. Reads the
1244
+ * dedicated offset map (never gated on a delegation record).
1245
+ * @param childSessionId - the dsh child session id.
1246
+ * @returns the mirrored transcript-line count, or undefined.
1247
+ */
1248
+ kimiMirroredLines(childSessionId: string): number | undefined;
1249
+ /**
1250
+ * Advance the kimi transcript-line mirror offset for one child session.
1251
+ * The kimi provider calls this after every mirror (fresh or resumed) with
1252
+ * the new total transcript-line count. Writes the dedicated offset map so
1253
+ * the offset survives even without a delegation record; the record's own
1254
+ * field is mirrored for completeness.
1255
+ * @param childSessionId - the dsh child session id.
1256
+ * @param lines - the total transcript lines mirrored so far.
1257
+ */
1258
+ setKimiMirroredLines(childSessionId: string, lines: number): void;
1259
+ /** Resolve the subagents service or fail loud naming the missing service. */
1260
+ private requireSubagents;
1261
+ /** Fail loud on an unknown provider BEFORE any intent is staged. */
1262
+ private requireProvider;
1263
+ /** Resolve the live parent Agent or fail loud (harness hard constraint). */
1264
+ private requireLiveParent;
1265
+ /**
1266
+ * Ensure a delegated child session is live in this process, reattaching it
1267
+ * from persistence when it is not — a host restart evicts every child from
1268
+ * the live store. This is the public entry of the facade resume's reattach
1269
+ * recipe for callers that start resume rounds WITHOUT the facade (the
1270
+ * family subagent tool starts runs directly through `ctx.subagents.start`;
1271
+ * without this call the provider refuses the resume as "not live").
1272
+ * Idempotent; a child that was never persisted fails through the
1273
+ * persistence layer's own not-found error.
1274
+ * @param childSessionId - the dsh child session id (the resume handle).
1275
+ */
1276
+ ensureChildLive(childSessionId: string): Promise<void>;
1277
+ /**
1278
+ * Restore a persisted child session into the live store when it is absent —
1279
+ * the reattach recipe documented on {@link resume}. Announce the restored
1280
+ * lifecycle so open history followers receive the constructor suffix before
1281
+ * subsequent appends. Hold detach in {@link reattachDisposers} until plugin
1282
+ * dispose; the write handle comes from the shared
1283
+ * {@link childWriteHandles} cache so a later {@link syncChildSession}
1284
+ * reuses it instead of failing SessionAlreadyOwnedError on a second open.
1285
+ */
1286
+ private reattachChildSession;
1287
+ /**
1288
+ * Sync a live delegated child session's events into durable storage through
1289
+ * the registry-cached write handle, creating the stored session on first
1290
+ * sync. The host's live write-behind routes `session/event` appends into
1291
+ * the per-id writer only while a write handle is open AND a checkpoint
1292
+ * drains it — a delegated mirror session has no agent loop, so nothing ever
1293
+ * checkpoints it and production child logs stayed header-only. This explicit
1294
+ * suffix sync is the mirror's durability path: read the stored prefix,
1295
+ * append only what is missing (idempotent under repetition and under a
1296
+ * working write-behind), then flush. A failure downgrades to a warn and the
1297
+ * handle stays cached for the next sync — a persistence hiccup must never
1298
+ * fail the delegation round.
1299
+ * @param session - the live child session to sync.
1300
+ */
1301
+ syncChildSession(session: Session, strict?: boolean): Promise<void>;
1302
+ /**
1303
+ * Get or open the child session's registry-owned WRITE handle (the
1304
+ * {@link childWriteHandles} cache). When the caller carries the session
1305
+ * header (a sync), a missing stored session is created from it; a create
1306
+ * that loses a stat/create race adopts the winner's stored session. A
1307
+ * failed acquisition is evicted so the next call retries from scratch.
1308
+ */
1309
+ private acquireChildWriteHandle;
1310
+ /**
1311
+ * Evict one child session's cached write handle and close it. Used when an
1312
+ * owner gives the session up (a failed reattach); normal lifetimes close
1313
+ * with the cache on plugin dispose.
1314
+ */
1315
+ private releaseChildWriteHandle;
1316
+ /**
1317
+ * Track one facade-started run under its child session id for
1318
+ * {@link cancel} and progress routing; the entry (and its heartbeat timer)
1319
+ * clears itself when the run's result settles (any stop reason, resolved or
1320
+ * rejected). While tracked, the facade emits a `heartbeat` progress every
1321
+ * {@link RUN_PROGRESS_HEARTBEAT_MS}; the timer is unref'd so it never keeps
1322
+ * the process alive.
1323
+ */
1324
+ private trackRun;
1325
+ /** Close one parked progress route, if any. */
1326
+ private dropSettledRun;
1327
+ /**
1328
+ * The provider-facing progress reporting channel: a provider reports a
1329
+ * run's data-bearing progress (mirror counts now, live deltas with M3), and
1330
+ * the registry forwards it — as the `localAgent/run-progress` cordis event,
1331
+ * and to the `onProgress` callback when the child session has a
1332
+ * facade-tracked run. Reports for untracked child sessions still emit the
1333
+ * event (a provider-only run must stay observable) and invoke no callback.
1334
+ * The facade's own heartbeat rides the same path.
1335
+ * @param childSessionId - the dsh child session id of the running delegation.
1336
+ * @param progress - the progress payload.
1337
+ */
1338
+ reportRunProgress(childSessionId: string, progress: LocalAgentRunProgress): void;
1339
+ /**
1340
+ * Resolve one harness or fail loud. */
1341
+ private requireHarness;
1342
+ /**
1343
+ * Append one delegation record to the owning harness's `delegations.jsonl`.
1344
+ * Durability is synchronous (recording happens once per fresh-round settle)
1345
+ * and best-effort: a provider with no claiming harness or a filesystem
1346
+ * failure keeps the in-memory record and warns — recording must never break
1347
+ * a settling run.
1348
+ */
1349
+ private persistDelegation;
1350
+ /**
1351
+ * Load one scoped home's persisted delegation mappings into the in-memory
1352
+ * maps. The default scope runs synchronously inside {@link register} before
1353
+ * `localAgent/harness-added` fires — unchanged; a named scope's file is read
1354
+ * when that scope's directory is materialized ({@link homeDir}). Malformed
1355
+ * or foreign-provider lines skip with a warn; the last line per child
1356
+ * session wins (matching {@link recordDelegation}'s replace semantics), and
1357
+ * a record's `kimiMirroredLines` restores the mirror offset.
1358
+ * @param harness - the harness whose log is read.
1359
+ * @param scope - the scope whose directory holds the log; absent means the
1360
+ * default scope.
1361
+ */
1362
+ private loadDelegations;
1363
+ /**
1364
+ * Dispatch `/login`, `/status`, `/sessions` and `/logout` for one harness,
1365
+ * in the default scope or — with `--scope <name>` — in a named one. The
1366
+ * flag is parsed off the input before the verb is matched, so every family
1367
+ * verb takes it and an input without it is dispatched exactly as before.
1368
+ * A harness's own extra subcommands receive the RAW input, flag included:
1369
+ * the family does not rewrite a subcommand it does not own.
1370
+ */
1371
+ private handle;
1372
+ /**
1373
+ * Run one login flow. The device-code variant spawns the CLI in the scoped
1374
+ * home and captures its prompt from stderr/stdout; the reply surfaces that
1375
+ * prompt immediately while the child keeps polling in the background. The
1376
+ * manual variant spawns nothing — the reply carries the terminal
1377
+ * instructions and a watch polls the credential probe. A second login while
1378
+ * one is pending replaces it — a stale or abandoned attempt (browser never
1379
+ * opened, user gave up) must not hold the slot forever, and the user's
1380
+ * retry should just get a fresh start. An abandoned device-code login with
1381
+ * no retry self-heals when the CLI's own device code expires and its
1382
+ * polling child exits (kimi/codex do); no idle timer is added because a
1383
+ * too-short one would kill a user who is genuinely authorizing (the manual
1384
+ * watch is bounded instead). A harness without a login flow answers with an
1385
+ * error instead of spawning anything.
1386
+ * Each scope logs in on its own: credentials are never copied between
1387
+ * scopes, so `--scope <name>` authorizes THAT directory's account and the
1388
+ * pending-login slot is per (harness, scope) too — a login in one scope
1389
+ * neither replaces nor observes another's.
1390
+ * @param harness - the harness whose login flow runs.
1391
+ * @param scope - the scope to log in; absent means the default one.
1392
+ * @returns the command result: the device-code prompt, or the manual
1393
+ * handoff's terminal instructions.
1394
+ */
1395
+ private login;
1396
+ /**
1397
+ * The shared credential watch behind the manual and pty login variants:
1398
+ * polls the probe until the credential lands (presence AND, when the
1399
+ * harness exposes a credential stamp, a stamp rewritten after the watch
1400
+ * started — a leftover marker must not conclude a login), the window
1401
+ * expires, or a new login replaces the watch. Outcomes are logged; the
1402
+ * surfaces' own status polling picks the landed credential up.
1403
+ * @param harness - the harness being logged in.
1404
+ * @param probe - the credential probe.
1405
+ * @param controller - the pending login's controller (done resolves here).
1406
+ * @param scope - the scope being logged in; absent means the default one.
1407
+ */
1408
+ private watchCredential;
1409
+ /**
1410
+ * The manual-handoff login: the reply carries the exact command the user
1411
+ * runs in their own terminal, and a bounded watch polls the credential probe
1412
+ * (default: the harness's `isAuthenticated`) until the credential lands, the
1413
+ * window expires, or a new login replaces the watch. The reply cannot carry
1414
+ * the outcome — the command channel has already returned — so success and
1415
+ * timeout are logged, and the surfaces' own status polling (the settings
1416
+ * section re-probes every few seconds) picks the landed credential up.
1417
+ * @param harness - the harness declaring the manual flow.
1418
+ * @param login - the manual variant declaration.
1419
+ * @param scope - the scope being logged in; absent means the default one.
1420
+ * @returns the instructions as the command success text.
1421
+ */
1422
+ private manualLogin;
1423
+ /**
1424
+ * The pty login: spawn the CLI under a pseudo-terminal wrapper so its
1425
+ * TTY-only auth flow runs — current claude auto-opens the user's browser
1426
+ * itself. The OAuth URL is captured from the merged output as a fallback,
1427
+ * and when the CLI prompts for a code the user pastes it through
1428
+ * `/<name> code <value>` (written to the child's stdin). Completion is the
1429
+ * shared credential watch. Windows has no script(1) wrapper; there the
1430
+ * reply falls back to the manual instruction.
1431
+ * @param harness - the harness declaring the pty flow.
1432
+ * @param login - the pty variant declaration.
1433
+ * @param scope - the scope being logged in; absent means the default one.
1434
+ * The CLI runs with its scoped-home variable pointed at THAT directory,
1435
+ * so the grant it writes lands in the scope that asked for it.
1436
+ * @returns the browser/paste instructions as the command success text.
1437
+ */
1438
+ private ptyLogin;
1439
+ /**
1440
+ * Deliver the user's pasted OAuth code to a pending pty login's stdin.
1441
+ * @param harness - the harness whose login is pending.
1442
+ * @param code - the pasted code.
1443
+ * @param scope - the scope whose login is pending; absent means the default
1444
+ * one. The pending-login slot is per scope, so the code reaches the login
1445
+ * that asked for it.
1446
+ * @returns the command result.
1447
+ */
1448
+ private submitLoginCode;
1449
+ /**
1450
+ * Spawn the harness login command and capture its device-code prompt. The
1451
+ * scoped-home variable points the CLI at the scope being logged in, so the
1452
+ * grant lands in that directory and nowhere else.
1453
+ */
1454
+ private runLogin;
1455
+ }
1456
+ /**
1457
+ * Mount the local-agent core: provide the harness registry and the family
1458
+ * command listing the registered harnesses (the client roster channel).
1459
+ * @param ctx - plugin context carrying the command registry.
1460
+ * @param config - shared scoped-homes root and login prompt wait.
1461
+ */
1462
+ export declare function apply(ctx: Context, config: Config): Promise<void>;
1463
+ //# sourceMappingURL=index.d.ts.map