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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.en.md +181 -0
  2. package/README.i18n.yaml +2 -0
  3. package/README.md +181 -0
  4. package/cordis.patch.yml +21 -0
  5. package/lib/client.js +8832 -0
  6. package/lib/index.js +5281 -0
  7. package/lib/invariant.js +85 -0
  8. package/lib/member-bridge.js +298 -0
  9. package/lib/typert.host.d.ts +3 -0
  10. package/lib/typert.host.js +3137 -0
  11. package/lib/typert.remote-client.d.ts +67 -0
  12. package/lib/typert.remote-client.js +1971 -0
  13. package/lib/types/cli-version.d.ts +82 -0
  14. package/lib/types/cli-version.js +165 -0
  15. package/lib/types/client/AuthStatusDot.d.ts +17 -0
  16. package/lib/types/client/AuthStatusDot.js +9 -0
  17. package/lib/types/client/HarnessModelPicker.d.ts +17 -0
  18. package/lib/types/client/HarnessModelPicker.js +48 -0
  19. package/lib/types/client/LocalAgentRecordsAction.d.ts +41 -0
  20. package/lib/types/client/LocalAgentRecordsAction.js +74 -0
  21. package/lib/types/client/MemberComposer.d.ts +79 -0
  22. package/lib/types/client/MemberComposer.js +308 -0
  23. package/lib/types/client/MemberConfiguration.d.ts +9 -0
  24. package/lib/types/client/MemberConfiguration.js +46 -0
  25. package/lib/types/client/MemberInboxView.d.ts +12 -0
  26. package/lib/types/client/MemberInboxView.js +38 -0
  27. package/lib/types/client/MemberLiveNode.d.ts +14 -0
  28. package/lib/types/client/MemberLiveNode.js +74 -0
  29. package/lib/types/client/MemberPaintDiagnostics.d.ts +7 -0
  30. package/lib/types/client/MemberPaintDiagnostics.js +20 -0
  31. package/lib/types/client/ModelConfigurationFields.d.ts +15 -0
  32. package/lib/types/client/ModelConfigurationFields.js +23 -0
  33. package/lib/types/client/ProviderAuthBlock.d.ts +59 -0
  34. package/lib/types/client/ProviderAuthBlock.js +164 -0
  35. package/lib/types/client/auth-status.d.ts +28 -0
  36. package/lib/types/client/auth-status.js +56 -0
  37. package/lib/types/client/icons.d.ts +22 -0
  38. package/lib/types/client/icons.js +6 -0
  39. package/lib/types/client/index.d.ts +62 -0
  40. package/lib/types/client/index.js +135 -0
  41. package/lib/types/client/live-node.d.ts +14 -0
  42. package/lib/types/client/live-node.js +34 -0
  43. package/lib/types/client/live-output.d.ts +34 -0
  44. package/lib/types/client/live-output.js +94 -0
  45. package/lib/types/client/live-paint.d.ts +40 -0
  46. package/lib/types/client/live-paint.js +74 -0
  47. package/lib/types/client/locales.d.ts +120 -0
  48. package/lib/types/client/locales.js +227 -0
  49. package/lib/types/client/member-configuration.d.ts +41 -0
  50. package/lib/types/client/member-configuration.js +114 -0
  51. package/lib/types/client/member-dock.d.ts +94 -0
  52. package/lib/types/client/member-dock.js +102 -0
  53. package/lib/types/client/member-feed.d.ts +24 -0
  54. package/lib/types/client/member-feed.js +134 -0
  55. package/lib/types/client/request-id.d.ts +3 -0
  56. package/lib/types/client/request-id.js +6 -0
  57. package/lib/types/client/settings-scope.d.ts +74 -0
  58. package/lib/types/client/settings-scope.js +98 -0
  59. package/lib/types/client/use-configuration-menu.d.ts +10 -0
  60. package/lib/types/client/use-configuration-menu.js +61 -0
  61. package/lib/types/container.d.ts +69 -0
  62. package/lib/types/container.js +129 -0
  63. package/lib/types/env.d.ts +17 -0
  64. package/lib/types/env.js +89 -0
  65. package/lib/types/gateway.d.ts +162 -0
  66. package/lib/types/gateway.js +470 -0
  67. package/lib/types/index.d.ts +1463 -0
  68. package/lib/types/index.js +2704 -0
  69. package/lib/types/invariant.d.ts +16 -0
  70. package/lib/types/invariant.js +105 -0
  71. package/lib/types/live-flush.d.ts +21 -0
  72. package/lib/types/live-flush.js +59 -0
  73. package/lib/types/live-stream.d.ts +41 -0
  74. package/lib/types/live-stream.js +178 -0
  75. package/lib/types/member-bridge.d.ts +50 -0
  76. package/lib/types/member-bridge.js +216 -0
  77. package/lib/types/member-channel.d.ts +86 -0
  78. package/lib/types/member-channel.js +232 -0
  79. package/lib/types/member-control-storage.d.ts +10 -0
  80. package/lib/types/member-control-storage.js +100 -0
  81. package/lib/types/member-control.d.ts +62 -0
  82. package/lib/types/member-control.js +359 -0
  83. package/lib/types/member-controls.d.ts +23 -0
  84. package/lib/types/member-controls.js +143 -0
  85. package/lib/types/member-feed.d.ts +4 -0
  86. package/lib/types/member-feed.js +45 -0
  87. package/lib/types/member-inbox.d.ts +30 -0
  88. package/lib/types/member-inbox.js +225 -0
  89. package/lib/types/model-directory.d.ts +43 -0
  90. package/lib/types/model-directory.js +192 -0
  91. package/lib/types/prepared-members.d.ts +15 -0
  92. package/lib/types/prepared-members.js +61 -0
  93. package/lib/types/settings-face.d.ts +61 -0
  94. package/lib/types/settings-face.js +61 -0
  95. package/lib/types/types.d.ts +959 -0
  96. package/lib/types/types.js +24 -0
  97. package/locale/en.json +6 -0
  98. package/locale/zh.json +6 -0
  99. package/package.json +112 -0
@@ -0,0 +1,62 @@
1
+ import type { LocalAgentAppliedConfiguration, LocalAgentControlReceipt, LocalAgentMemberConfiguration, LocalAgentMemberControlState, LocalAgentResolvedConfiguration } from './types.ts';
2
+ export interface MemberControlRecord {
3
+ state: LocalAgentMemberControlState;
4
+ requests: Record<string, {
5
+ signature: string;
6
+ receipt: LocalAgentControlReceipt;
7
+ }>;
8
+ }
9
+ /** write must commit atomically or throw; no accepted receipt precedes durable storage. */
10
+ export interface MemberControlStorage {
11
+ read(memberId: string): MemberControlRecord | undefined;
12
+ write(memberId: string, record: MemberControlRecord): void;
13
+ }
14
+ export type { MemberConfigurationAdapter } from './types.ts';
15
+ import type { MemberConfigurationAdapter } from './types.ts';
16
+ /**
17
+ * One durable, replaceable configuration slot. Intent acceptance never waits
18
+ * for a running model turn or an in-flight native control. Native effects are
19
+ * serialized separately and must converge before the next admission.
20
+ */
21
+ export declare class MemberConfigurationController {
22
+ readonly memberId: string;
23
+ private readonly storage;
24
+ private readonly adapter;
25
+ private record;
26
+ private recovering;
27
+ private worker;
28
+ private activeLease;
29
+ private admitting;
30
+ private storageFailure;
31
+ private readonly listeners;
32
+ constructor(memberId: string, storage: MemberControlStorage, adapter: MemberConfigurationAdapter, initial: {
33
+ selection: LocalAgentMemberConfiguration;
34
+ resolved?: LocalAgentResolvedConfiguration;
35
+ lockedReason?: string;
36
+ });
37
+ read(): LocalAgentMemberControlState;
38
+ /** A failed pre-prompt native binding requires reconciliation before another admission. */
39
+ rejectAdmission(error: unknown): void;
40
+ private change;
41
+ private receipt;
42
+ private duplicate;
43
+ private validRequestId;
44
+ select(requestId: string, expectedRevision: number, selection: LocalAgentMemberConfiguration): Promise<LocalAgentControlReceipt>;
45
+ cancel(requestId: string, expectedRevision: number): LocalAgentControlReceipt;
46
+ /** Failed intent stays in place. Retry first reconciles the uncertain operation. */
47
+ retry(expectedRevision: number): Promise<void>;
48
+ private kick;
49
+ private work;
50
+ private completeOperation;
51
+ private preparedSelection;
52
+ private assertFrozen;
53
+ private reconcile;
54
+ private drain;
55
+ /** The returned immutable lease belongs to one entire model/tool round. */
56
+ admit(roundId: string): Promise<{
57
+ configuration: LocalAgentAppliedConfiguration;
58
+ release: () => void;
59
+ }>;
60
+ follow(signal: AbortSignal): AsyncIterable<LocalAgentMemberControlState>;
61
+ }
62
+ //# sourceMappingURL=member-control.d.ts.map
@@ -0,0 +1,359 @@
1
+ function normalize(selection) {
2
+ const choice = (value) => {
3
+ if (value?.mode === 'inherit' || value?.mode === 'default')
4
+ return { mode: value.mode };
5
+ if (value?.mode === 'value' && typeof value.value === 'string' && value.value.trim() !== '' && !/[\r\n\0]/.test(value.value)) {
6
+ return { mode: 'value', value: value.value.trim() };
7
+ }
8
+ throw new Error('Invalid model or effort selection');
9
+ };
10
+ return { model: choice(selection?.model), effort: choice(selection?.effort) };
11
+ }
12
+ /**
13
+ * One durable, replaceable configuration slot. Intent acceptance never waits
14
+ * for a running model turn or an in-flight native control. Native effects are
15
+ * serialized separately and must converge before the next admission.
16
+ */
17
+ export class MemberConfigurationController {
18
+ memberId;
19
+ storage;
20
+ adapter;
21
+ record;
22
+ recovering;
23
+ worker;
24
+ activeLease = false;
25
+ admitting = false;
26
+ storageFailure;
27
+ listeners = new Set();
28
+ constructor(memberId, storage, adapter, initial) {
29
+ this.memberId = memberId;
30
+ this.storage = storage;
31
+ this.adapter = adapter;
32
+ const existing = storage.read(memberId);
33
+ this.recovering = existing !== undefined;
34
+ this.record = existing ?? {
35
+ state: { memberId, revision: 0, current: { revision: 0, selection: normalize(initial.selection), resolved: initial.resolved ?? {} }, status: 'idle', ...initial.lockedReason === undefined ? {} : { lockedReason: initial.lockedReason } },
36
+ requests: {},
37
+ };
38
+ if (this.record.state.memberId !== memberId)
39
+ throw new Error('Member control identity mismatch');
40
+ if (existing === undefined)
41
+ storage.write(memberId, this.record);
42
+ }
43
+ read() {
44
+ const state = structuredClone(this.record.state);
45
+ return this.storageFailure === undefined ? state : { ...state, status: 'failed', error: this.storageFailure.message };
46
+ }
47
+ /** A failed pre-prompt native binding requires reconciliation before another admission. */
48
+ rejectAdmission(error) {
49
+ this.recovering = true;
50
+ if (this.storageFailure !== undefined || this.record.state.status === 'failed')
51
+ return;
52
+ this.change(record => {
53
+ record.state.status = 'failed';
54
+ record.state.error = error instanceof Error ? error.message : 'Native round admission failed';
55
+ });
56
+ }
57
+ change(mutate) {
58
+ const next = structuredClone(this.record);
59
+ mutate(next);
60
+ try {
61
+ this.storage.write(this.memberId, next);
62
+ }
63
+ catch (error) {
64
+ this.storageFailure = error instanceof Error ? error : new Error('Member configuration could not be persisted');
65
+ throw this.storageFailure;
66
+ }
67
+ this.storageFailure = undefined;
68
+ this.record = next;
69
+ for (const listener of this.listeners)
70
+ listener();
71
+ }
72
+ receipt(requestId, status, error) {
73
+ return { requestId, revision: this.record.state.revision, status, ...error === undefined ? {} : { error } };
74
+ }
75
+ duplicate(requestId, signature) {
76
+ const existing = Object.hasOwn(this.record.requests, requestId) ? this.record.requests[requestId] : undefined;
77
+ return existing === undefined ? undefined : existing.signature === signature
78
+ ? structuredClone(existing.receipt) : this.receipt(requestId, 'conflict', 'Request ID was already used for a different operation');
79
+ }
80
+ validRequestId(requestId) {
81
+ return typeof requestId === 'string' && requestId.length > 0 && requestId.length <= 160
82
+ && !Object.hasOwn(Object.prototype, requestId) && !/[\r\n\0]/.test(requestId);
83
+ }
84
+ async select(requestId, expectedRevision, selection) {
85
+ if (!this.validRequestId(requestId))
86
+ return this.receipt(requestId, 'unsupported', 'Invalid control request ID');
87
+ let next;
88
+ try {
89
+ next = normalize(selection);
90
+ }
91
+ catch (error) {
92
+ return this.receipt(requestId, 'unsupported', String(error));
93
+ }
94
+ const signature = JSON.stringify(['select', next]);
95
+ const duplicate = this.duplicate(requestId, signature);
96
+ if (duplicate !== undefined)
97
+ return duplicate;
98
+ const locked = this.record.state.lockedReason;
99
+ if (locked !== undefined)
100
+ return this.receipt(requestId, 'locked', locked);
101
+ if (this.record.state.revision !== expectedRevision)
102
+ return this.receipt(requestId, 'conflict', 'Configuration changed; refresh before submitting');
103
+ try {
104
+ await this.adapter.validate(next);
105
+ }
106
+ catch (error) {
107
+ return this.receipt(requestId, 'unsupported', error instanceof Error ? error.message : String(error));
108
+ }
109
+ // Validation yields; admission or another entrance may have changed state.
110
+ const repeated = this.duplicate(requestId, signature);
111
+ if (repeated !== undefined)
112
+ return repeated;
113
+ if (this.record.state.revision !== expectedRevision)
114
+ return this.receipt(requestId, 'conflict', 'Configuration changed during validation');
115
+ this.change(record => {
116
+ const state = record.state;
117
+ const previous = state.pending;
118
+ if (previous !== undefined && previous.revision !== state.operation?.revision) {
119
+ const receipt = record.requests[previous.requestId]?.receipt;
120
+ if (receipt !== undefined) {
121
+ receipt.status = 'cancelled';
122
+ receipt.error = 'Replaced by a newer selection';
123
+ }
124
+ }
125
+ state.revision++;
126
+ state.pending = { revision: state.revision, requestId, kind: 'selection', selection: next };
127
+ state.status = 'pending';
128
+ delete state.error;
129
+ record.requests[requestId] = { signature, receipt: { requestId, revision: state.revision, status: 'pending' } };
130
+ });
131
+ this.kick();
132
+ return structuredClone(this.record.requests[requestId].receipt);
133
+ }
134
+ cancel(requestId, expectedRevision) {
135
+ if (!this.validRequestId(requestId))
136
+ return this.receipt(requestId, 'unsupported', 'Invalid control request ID');
137
+ const signature = JSON.stringify(['cancel', expectedRevision]);
138
+ const duplicate = this.duplicate(requestId, signature);
139
+ if (duplicate !== undefined)
140
+ return duplicate;
141
+ if (this.record.state.revision !== expectedRevision)
142
+ return this.receipt(requestId, 'conflict', 'Pending selection changed; refresh before cancelling');
143
+ if (this.record.state.pending === undefined)
144
+ return this.receipt(requestId, 'applied', 'The selection has already applied');
145
+ this.change(record => {
146
+ const state = record.state;
147
+ const previous = state.pending;
148
+ const old = record.requests[previous.requestId]?.receipt;
149
+ if (old !== undefined)
150
+ old.status = 'cancelled';
151
+ state.revision++;
152
+ delete state.error;
153
+ if (state.operation === undefined) {
154
+ delete state.pending;
155
+ state.status = 'idle';
156
+ }
157
+ else {
158
+ // An older control is already on the wire. Restore the last applied
159
+ // selection before releasing admission, even if that older ack lands.
160
+ state.pending = { revision: state.revision, requestId, kind: 'cancel', selection: structuredClone(state.current.selection) };
161
+ state.status = 'pending';
162
+ }
163
+ record.requests[requestId] = { signature, receipt: { requestId, revision: state.revision, status: state.pending === undefined ? 'cancelled' : 'pending' } };
164
+ });
165
+ this.kick();
166
+ return structuredClone(this.record.requests[requestId].receipt);
167
+ }
168
+ /** Failed intent stays in place. Retry first reconciles the uncertain operation. */
169
+ retry(expectedRevision) {
170
+ if (this.record.state.revision !== expectedRevision)
171
+ return Promise.reject(new Error('Configuration revision conflict'));
172
+ if (this.record.state.status !== 'failed' && this.storageFailure === undefined)
173
+ return this.worker ?? Promise.resolve();
174
+ this.change(record => { record.state.status = record.state.pending === undefined ? 'idle' : 'pending'; delete record.state.error; });
175
+ this.recovering = true;
176
+ return this.work();
177
+ }
178
+ kick() {
179
+ if (!this.activeLease && !this.admitting)
180
+ void this.work().catch(() => { });
181
+ }
182
+ work() {
183
+ if (this.worker !== undefined)
184
+ return this.worker;
185
+ const worker = this.drain();
186
+ this.worker = worker;
187
+ const clear = () => {
188
+ if (this.worker !== worker)
189
+ return;
190
+ this.worker = undefined;
191
+ if (this.record.state.pending !== undefined && this.record.state.status !== 'failed' && !this.activeLease && this.storageFailure === undefined)
192
+ this.kick();
193
+ };
194
+ void worker.then(clear, clear);
195
+ return worker;
196
+ }
197
+ completeOperation(record, resolved) {
198
+ const operation = record.state.operation;
199
+ record.state.current = { revision: operation.revision, selection: operation.selection, resolved };
200
+ const receipt = record.requests[operation.requestId]?.receipt;
201
+ if (receipt !== undefined)
202
+ receipt.status = operation.kind === 'cancel' ? 'cancelled' : 'applied';
203
+ if (record.state.pending?.revision === operation.revision)
204
+ delete record.state.pending;
205
+ delete record.state.operation;
206
+ record.state.status = record.state.pending === undefined ? 'idle' : 'pending';
207
+ delete record.state.error;
208
+ }
209
+ preparedSelection() {
210
+ const frozen = this.record.state.frozen;
211
+ const selection = structuredClone(frozen?.selection ?? this.record.state.current.selection);
212
+ if (frozen?.resolved.model !== undefined)
213
+ selection.model = { mode: 'value', value: frozen.resolved.model };
214
+ if (frozen?.resolved.effort !== undefined)
215
+ selection.effort = { mode: 'value', value: frozen.resolved.effort };
216
+ return selection;
217
+ }
218
+ assertFrozen(resolved) {
219
+ const frozen = this.record.state.frozen;
220
+ if (frozen !== undefined && (resolved.model !== frozen.resolved.model || resolved.effort !== frozen.resolved.effort)) {
221
+ throw new Error('Frozen evaluation configuration changed; admission is blocked');
222
+ }
223
+ }
224
+ async reconcile() {
225
+ this.change(record => { record.state.status = 'reconciling'; });
226
+ const snapshot = this.read();
227
+ if (snapshot.frozen !== undefined)
228
+ snapshot.current = { ...snapshot.frozen, selection: this.preparedSelection() };
229
+ const inspected = await this.adapter.reconcile(snapshot);
230
+ this.assertFrozen(inspected.resolved);
231
+ if (inspected.active)
232
+ throw new Error('The native session still has an active round');
233
+ if (inspected.matches === 'unknown' || (inspected.matches === 'operation' && this.record.state.operation === undefined)) {
234
+ throw new Error('Native configuration could not be reconciled; admission remains blocked');
235
+ }
236
+ this.change(record => {
237
+ if (inspected.matches === 'operation')
238
+ this.completeOperation(record, inspected.resolved);
239
+ else {
240
+ record.state.current.resolved = inspected.resolved;
241
+ delete record.state.operation;
242
+ record.state.status = record.state.pending === undefined ? 'idle' : 'pending';
243
+ }
244
+ delete record.state.round;
245
+ delete record.state.error;
246
+ });
247
+ this.recovering = false;
248
+ }
249
+ async drain() {
250
+ if (this.activeLease)
251
+ return;
252
+ if (this.storageFailure !== undefined)
253
+ throw this.storageFailure;
254
+ if (this.record.state.status === 'failed')
255
+ throw new Error(this.record.state.error ?? 'Configuration requires retry or cancellation');
256
+ try {
257
+ if (this.recovering || this.record.state.operation !== undefined)
258
+ await this.reconcile();
259
+ while (!this.activeLease && this.record.state.pending !== undefined) {
260
+ this.change(record => {
261
+ const pending = record.state.pending;
262
+ record.state.operation = { ...pending, previous: structuredClone(record.state.current) };
263
+ record.state.status = 'applying';
264
+ record.requests[pending.requestId].receipt.status = 'applying';
265
+ });
266
+ const operation = structuredClone(this.record.state.operation);
267
+ const resolved = await this.adapter.apply(operation.selection, operation.previous, `${this.memberId}:${operation.revision}`);
268
+ this.change(record => { this.completeOperation(record, resolved); });
269
+ }
270
+ }
271
+ catch (error) {
272
+ this.recovering = true;
273
+ this.change(record => {
274
+ record.state.status = 'failed';
275
+ record.state.error = error instanceof Error ? error.message : String(error);
276
+ const requestId = record.state.pending?.requestId;
277
+ if (requestId !== undefined)
278
+ record.requests[requestId].receipt.status = 'failed';
279
+ });
280
+ throw error;
281
+ }
282
+ }
283
+ /** The returned immutable lease belongs to one entire model/tool round. */
284
+ async admit(roundId) {
285
+ if (this.activeLease || this.admitting)
286
+ throw new Error('The member already has an active or admitting round');
287
+ this.admitting = true;
288
+ try {
289
+ for (;;) {
290
+ await this.work();
291
+ if (this.record.state.status === 'failed')
292
+ throw new Error(this.record.state.error);
293
+ if (this.record.state.pending !== undefined)
294
+ continue;
295
+ const revision = this.record.state.revision;
296
+ const resolved = await this.adapter.prepare?.(this.preparedSelection());
297
+ // A selection made during native preparation belongs before this
298
+ // admission, and must be confirmed before any prompt leaves core.
299
+ if (this.record.state.revision !== revision || this.record.state.pending !== undefined)
300
+ continue;
301
+ if (resolved !== undefined) {
302
+ this.assertFrozen(resolved);
303
+ this.change(record => { record.state.current.resolved = resolved; });
304
+ }
305
+ if (this.record.state.lockedReason !== undefined && this.record.state.frozen === undefined) {
306
+ this.change(record => { record.state.frozen = structuredClone(record.state.current); });
307
+ }
308
+ break;
309
+ }
310
+ const configuration = structuredClone(this.record.state.current);
311
+ this.change(record => { record.state.round = { id: roundId, configuration: structuredClone(configuration) }; });
312
+ Object.freeze(configuration.selection.model);
313
+ Object.freeze(configuration.selection.effort);
314
+ Object.freeze(configuration.selection);
315
+ Object.freeze(configuration.resolved);
316
+ Object.freeze(configuration);
317
+ this.activeLease = true;
318
+ let released = false;
319
+ return { configuration, release: () => {
320
+ if (released)
321
+ return;
322
+ this.change(record => { delete record.state.round; });
323
+ released = true;
324
+ this.activeLease = false;
325
+ this.kick();
326
+ } };
327
+ }
328
+ catch (error) {
329
+ this.rejectAdmission(error);
330
+ throw error;
331
+ }
332
+ finally {
333
+ this.admitting = false;
334
+ if (!this.activeLease)
335
+ this.kick();
336
+ }
337
+ }
338
+ async *follow(signal) {
339
+ let changed = true;
340
+ let wake;
341
+ const notify = () => { changed = true; wake?.(); wake = undefined; };
342
+ this.listeners.add(notify);
343
+ signal.addEventListener('abort', notify, { once: true });
344
+ try {
345
+ while (!signal.aborted) {
346
+ if (changed) {
347
+ changed = false;
348
+ yield this.read();
349
+ }
350
+ else
351
+ await new Promise(resolve => { wake = resolve; });
352
+ }
353
+ }
354
+ finally {
355
+ this.listeners.delete(notify);
356
+ signal.removeEventListener('abort', notify);
357
+ }
358
+ }
359
+ }
@@ -0,0 +1,23 @@
1
+ import type { SubagentRun } from '@deepseek-ai/dsh-subagent';
2
+ import { MemberConfigurationController } from './member-control.ts';
3
+ import type { MemberConfigurationAdapter, MemberControlStorage } from './member-control.ts';
4
+ import type { LocalAgentAppliedConfiguration, LocalAgentMemberBinding } from './types.ts';
5
+ /** The provider boundary owns the lease; facade and direct tool starts share it. */
6
+ export declare class MemberControls {
7
+ private readonly storage;
8
+ private readonly adapter;
9
+ private readonly onError;
10
+ private readonly admitted;
11
+ configurationOf(run: object): LocalAgentAppliedConfiguration | undefined;
12
+ private readonly members;
13
+ constructor(storage: MemberControlStorage, adapter: (binding: LocalAgentMemberBinding) => MemberConfigurationAdapter, onError: (error: unknown) => void);
14
+ get(binding: LocalAgentMemberBinding): MemberConfigurationController;
15
+ binding(memberId: string): LocalAgentMemberBinding | undefined;
16
+ private readonly tails;
17
+ private readonly waiting;
18
+ queuedCount(memberId: string): number;
19
+ /** Every provider entry shares one FIFO through whole-round settlement. */
20
+ run(binding: LocalAgentMemberBinding, start: (configuration: LocalAgentAppliedConfiguration) => Promise<SubagentRun>, signal?: AbortSignal, onAdmitted?: () => void | Promise<void>): Promise<SubagentRun>;
21
+ private runNow;
22
+ }
23
+ //# sourceMappingURL=member-controls.d.ts.map
@@ -0,0 +1,143 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { MemberConfigurationController } from './member-control.js';
3
+ /** The provider boundary owns the lease; facade and direct tool starts share it. */
4
+ export class MemberControls {
5
+ storage;
6
+ adapter;
7
+ onError;
8
+ admitted = new WeakMap();
9
+ configurationOf(run) {
10
+ const configuration = this.admitted.get(run);
11
+ return configuration === undefined ? undefined : structuredClone(configuration);
12
+ }
13
+ members = new Map();
14
+ constructor(storage, adapter, onError) {
15
+ this.storage = storage;
16
+ this.adapter = adapter;
17
+ this.onError = onError;
18
+ }
19
+ get(binding) {
20
+ const known = this.members.get(binding.childSessionId);
21
+ if (known !== undefined) {
22
+ for (const key of ['provider', 'parentSessionId', 'cwd', 'scope', 'model', 'effort', 'configurationLock']) {
23
+ if (known.binding[key] !== binding[key])
24
+ throw new Error(`Member configuration identity changed: ${key}`);
25
+ }
26
+ return known.control;
27
+ }
28
+ // Resolve the currently mounted broker for every operation, including
29
+ // after a provider settings reload. Never retain a disposed driver.
30
+ const current = () => this.adapter(binding);
31
+ const control = new MemberConfigurationController(binding.childSessionId, this.storage, {
32
+ validate: selection => current().validate(selection),
33
+ apply: (selection, previous, operationId) => current().apply(selection, previous, operationId),
34
+ reconcile: state => current().reconcile(state),
35
+ prepare: async (selection) => {
36
+ const adapter = current();
37
+ await adapter.validate(selection);
38
+ if (adapter.prepare === undefined)
39
+ throw new Error('Provider exposes no configuration admission preparation');
40
+ return adapter.prepare(selection);
41
+ },
42
+ }, {
43
+ selection: { model: { mode: 'inherit' }, effort: { mode: 'inherit' } },
44
+ ...binding.configurationLock === undefined ? {} : { lockedReason: binding.configurationLock },
45
+ });
46
+ this.members.set(binding.childSessionId, { binding: structuredClone(binding), control });
47
+ return control;
48
+ }
49
+ binding(memberId) {
50
+ const binding = this.members.get(memberId)?.binding;
51
+ return binding === undefined ? undefined : structuredClone(binding);
52
+ }
53
+ tails = new Map();
54
+ waiting = new Map();
55
+ queuedCount(memberId) { return this.waiting.get(memberId)?.size ?? 0; }
56
+ /** Every provider entry shares one FIFO through whole-round settlement. */
57
+ async run(binding, start, signal, onAdmitted) {
58
+ signal?.throwIfAborted();
59
+ this.get(binding); // Reject a foreign binding before accepting it into this member's queue.
60
+ const key = binding.childSessionId;
61
+ const previous = this.tails.get(key) ?? Promise.resolve();
62
+ const id = randomUUID();
63
+ const waiting = this.waiting.get(key) ?? new Set();
64
+ waiting.add(id);
65
+ this.waiting.set(key, waiting);
66
+ let queued = true;
67
+ let cancelled = false;
68
+ let resolve;
69
+ let reject;
70
+ const receipt = new Promise((yes, no) => { resolve = yes; reject = no; });
71
+ const remove = () => {
72
+ waiting.delete(id);
73
+ if (waiting.size === 0 && this.waiting.get(key) === waiting)
74
+ this.waiting.delete(key);
75
+ };
76
+ const abort = () => {
77
+ if (!queued)
78
+ return;
79
+ cancelled = true;
80
+ remove();
81
+ reject(signal?.reason ?? new Error('Queued member round cancelled'));
82
+ };
83
+ signal?.addEventListener('abort', abort, { once: true });
84
+ const task = previous.catch(() => { }).then(async () => {
85
+ queued = false;
86
+ signal?.removeEventListener('abort', abort);
87
+ remove();
88
+ if (cancelled)
89
+ return;
90
+ try {
91
+ signal?.throwIfAborted();
92
+ const run = await this.runNow(binding, start, signal, onAdmitted);
93
+ resolve(run);
94
+ await run.result;
95
+ }
96
+ catch (error) {
97
+ reject(error);
98
+ }
99
+ });
100
+ this.tails.set(key, task);
101
+ void task.finally(() => { if (this.tails.get(key) === task)
102
+ this.tails.delete(key); });
103
+ return receipt;
104
+ }
105
+ async runNow(binding, start, signal, onAdmitted) {
106
+ const control = this.get(binding);
107
+ const lease = await control.admit(randomUUID());
108
+ let nativeStarted = false;
109
+ try {
110
+ signal?.throwIfAborted();
111
+ await onAdmitted?.();
112
+ signal?.throwIfAborted();
113
+ nativeStarted = true;
114
+ const run = await start(lease.configuration);
115
+ this.admitted.set(run, lease.configuration);
116
+ const release = () => { try {
117
+ lease.release();
118
+ }
119
+ catch (error) {
120
+ this.onError(error);
121
+ } };
122
+ void run.result.then(release, release);
123
+ return run;
124
+ }
125
+ catch (error) {
126
+ // Cancelling a request is not a failed model/effort configuration.
127
+ if (nativeStarted && !signal?.aborted)
128
+ try {
129
+ control.rejectAdmission(error);
130
+ }
131
+ catch (persistError) {
132
+ this.onError(persistError);
133
+ }
134
+ try {
135
+ lease.release();
136
+ }
137
+ catch (releaseError) {
138
+ this.onError(releaseError);
139
+ }
140
+ throw error;
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,4 @@
1
+ import type { LocalAgentMemberFeedEvent, LocalAgentMemberFeedRequest } from './types.ts';
2
+ /** One outstanding pull per source: slow clients do not accumulate event queues. */
3
+ export declare function mergeMemberFeeds(requests: readonly LocalAgentMemberFeedRequest[], follow: (request: LocalAgentMemberFeedRequest, signal: AbortSignal) => AsyncIterable<LocalAgentMemberFeedEvent>, signal: AbortSignal): AsyncIterable<LocalAgentMemberFeedEvent>;
4
+ //# sourceMappingURL=member-feed.d.ts.map
@@ -0,0 +1,45 @@
1
+ /** One outstanding pull per source: slow clients do not accumulate event queues. */
2
+ export async function* mergeMemberFeeds(requests, follow, signal) {
3
+ if (requests.length > 96)
4
+ throw new Error('Too many member feed channels');
5
+ const controller = new AbortController();
6
+ let stop;
7
+ const stopped = new Promise(resolve => { stop = () => { controller.abort(); resolve(undefined); }; });
8
+ signal.addEventListener('abort', stop, { once: true });
9
+ const iterators = [];
10
+ const pending = new Map();
11
+ const unique = new Map(requests.map(request => [JSON.stringify([request.memberId, request.channel]), request]));
12
+ try {
13
+ if (signal.aborted)
14
+ return;
15
+ for (const request of unique.values()) {
16
+ const source = (async function* () {
17
+ try {
18
+ yield* follow(request, controller.signal);
19
+ }
20
+ catch (error) {
21
+ if (!controller.signal.aborted)
22
+ yield { memberId: request.memberId, channel: 'error', source: request.channel, message: error instanceof Error ? error.message : String(error) };
23
+ }
24
+ })()[Symbol.asyncIterator]();
25
+ const index = iterators.push(source) - 1;
26
+ pending.set(index, source.next().then(result => ({ index, result })));
27
+ }
28
+ while (!signal.aborted && pending.size > 0) {
29
+ const next = await Promise.race([...pending.values(), stopped]);
30
+ if (next === undefined)
31
+ return;
32
+ pending.delete(next.index);
33
+ if (next.result.done)
34
+ continue;
35
+ yield next.result.value;
36
+ pending.set(next.index, iterators[next.index].next().then(result => ({ index: next.index, result })));
37
+ }
38
+ }
39
+ finally {
40
+ signal.removeEventListener('abort', stop);
41
+ controller.abort();
42
+ for (const iterator of iterators)
43
+ void iterator.return?.().catch(() => { });
44
+ }
45
+ }
@@ -0,0 +1,30 @@
1
+ import type { SubagentRun } from '@deepseek-ai/dsh-subagent';
2
+ import type { LocalAgentMemberInbox, LocalAgentMemberInput } from './types.ts';
3
+ type Execute = (memberId: string, text: string, signal: AbortSignal, admitted: () => void) => Promise<SubagentRun>;
4
+ /** Durable human input; the provider FIFO remains the sole execution order. */
5
+ export declare class MemberInbox {
6
+ private readonly directory;
7
+ private readonly execute;
8
+ private readonly onError;
9
+ private readonly states;
10
+ private readonly active;
11
+ private readonly storageErrors;
12
+ private closed;
13
+ constructor(directory: string, execute: Execute, onError: (error: unknown) => void);
14
+ private path;
15
+ private load;
16
+ private save;
17
+ read(memberId: string): LocalAgentMemberInbox;
18
+ assertAdmission(memberId: string): void;
19
+ enqueue(memberId: string, text: string, requestId: string): LocalAgentMemberInput;
20
+ pause(memberId: string): void;
21
+ resume(memberId: string): void;
22
+ cancelStarting(memberId: string): boolean;
23
+ cancel(memberId: string, id: string): void;
24
+ reconcile(memberId: string, id: string, outcome: 'done' | 'cancelled', evidence: string): void;
25
+ private update;
26
+ private pump;
27
+ dispose(): void;
28
+ }
29
+ export {};
30
+ //# sourceMappingURL=member-inbox.d.ts.map