@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.
- package/README.en.md +181 -0
- package/README.i18n.yaml +2 -0
- package/README.md +181 -0
- package/cordis.patch.yml +21 -0
- package/lib/client.js +8832 -0
- package/lib/index.js +5281 -0
- package/lib/invariant.js +85 -0
- package/lib/member-bridge.js +298 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +3137 -0
- package/lib/typert.remote-client.d.ts +67 -0
- package/lib/typert.remote-client.js +1971 -0
- package/lib/types/cli-version.d.ts +82 -0
- package/lib/types/cli-version.js +165 -0
- package/lib/types/client/AuthStatusDot.d.ts +17 -0
- package/lib/types/client/AuthStatusDot.js +9 -0
- package/lib/types/client/HarnessModelPicker.d.ts +17 -0
- package/lib/types/client/HarnessModelPicker.js +48 -0
- package/lib/types/client/LocalAgentRecordsAction.d.ts +41 -0
- package/lib/types/client/LocalAgentRecordsAction.js +74 -0
- package/lib/types/client/MemberComposer.d.ts +79 -0
- package/lib/types/client/MemberComposer.js +308 -0
- package/lib/types/client/MemberConfiguration.d.ts +9 -0
- package/lib/types/client/MemberConfiguration.js +46 -0
- package/lib/types/client/MemberInboxView.d.ts +12 -0
- package/lib/types/client/MemberInboxView.js +38 -0
- package/lib/types/client/MemberLiveNode.d.ts +14 -0
- package/lib/types/client/MemberLiveNode.js +74 -0
- package/lib/types/client/MemberPaintDiagnostics.d.ts +7 -0
- package/lib/types/client/MemberPaintDiagnostics.js +20 -0
- package/lib/types/client/ModelConfigurationFields.d.ts +15 -0
- package/lib/types/client/ModelConfigurationFields.js +23 -0
- package/lib/types/client/ProviderAuthBlock.d.ts +59 -0
- package/lib/types/client/ProviderAuthBlock.js +164 -0
- package/lib/types/client/auth-status.d.ts +28 -0
- package/lib/types/client/auth-status.js +56 -0
- package/lib/types/client/icons.d.ts +22 -0
- package/lib/types/client/icons.js +6 -0
- package/lib/types/client/index.d.ts +62 -0
- package/lib/types/client/index.js +135 -0
- package/lib/types/client/live-node.d.ts +14 -0
- package/lib/types/client/live-node.js +34 -0
- package/lib/types/client/live-output.d.ts +34 -0
- package/lib/types/client/live-output.js +94 -0
- package/lib/types/client/live-paint.d.ts +40 -0
- package/lib/types/client/live-paint.js +74 -0
- package/lib/types/client/locales.d.ts +120 -0
- package/lib/types/client/locales.js +227 -0
- package/lib/types/client/member-configuration.d.ts +41 -0
- package/lib/types/client/member-configuration.js +114 -0
- package/lib/types/client/member-dock.d.ts +94 -0
- package/lib/types/client/member-dock.js +102 -0
- package/lib/types/client/member-feed.d.ts +24 -0
- package/lib/types/client/member-feed.js +134 -0
- package/lib/types/client/request-id.d.ts +3 -0
- package/lib/types/client/request-id.js +6 -0
- package/lib/types/client/settings-scope.d.ts +74 -0
- package/lib/types/client/settings-scope.js +98 -0
- package/lib/types/client/use-configuration-menu.d.ts +10 -0
- package/lib/types/client/use-configuration-menu.js +61 -0
- package/lib/types/container.d.ts +69 -0
- package/lib/types/container.js +129 -0
- package/lib/types/env.d.ts +17 -0
- package/lib/types/env.js +89 -0
- package/lib/types/gateway.d.ts +162 -0
- package/lib/types/gateway.js +470 -0
- package/lib/types/index.d.ts +1463 -0
- package/lib/types/index.js +2704 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.js +105 -0
- package/lib/types/live-flush.d.ts +21 -0
- package/lib/types/live-flush.js +59 -0
- package/lib/types/live-stream.d.ts +41 -0
- package/lib/types/live-stream.js +178 -0
- package/lib/types/member-bridge.d.ts +50 -0
- package/lib/types/member-bridge.js +216 -0
- package/lib/types/member-channel.d.ts +86 -0
- package/lib/types/member-channel.js +232 -0
- package/lib/types/member-control-storage.d.ts +10 -0
- package/lib/types/member-control-storage.js +100 -0
- package/lib/types/member-control.d.ts +62 -0
- package/lib/types/member-control.js +359 -0
- package/lib/types/member-controls.d.ts +23 -0
- package/lib/types/member-controls.js +143 -0
- package/lib/types/member-feed.d.ts +4 -0
- package/lib/types/member-feed.js +45 -0
- package/lib/types/member-inbox.d.ts +30 -0
- package/lib/types/member-inbox.js +225 -0
- package/lib/types/model-directory.d.ts +43 -0
- package/lib/types/model-directory.js +192 -0
- package/lib/types/prepared-members.d.ts +15 -0
- package/lib/types/prepared-members.js +61 -0
- package/lib/types/settings-face.d.ts +61 -0
- package/lib/types/settings-face.js +61 -0
- package/lib/types/types.d.ts +959 -0
- package/lib/types/types.js +24 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- 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
|