dsh-keys-manager 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +203 -16
- package/README.zh-CN.md +125 -0
- package/bundle/cordis.patch.yml +5 -0
- package/icon.svg +12 -0
- package/lib/client/format.d.ts +8 -0
- package/lib/client/index.d.ts +3 -0
- package/lib/client/locales.d.ts +4 -0
- package/lib/client/runtime.d.ts +21 -0
- package/lib/client/store.d.ts +4 -0
- package/lib/client/styles.d.ts +2 -0
- package/lib/client/types.d.ts +130 -0
- package/lib/client/ui.d.ts +18 -0
- package/lib/client.js +2106 -0
- package/lib/core.d.ts +115 -0
- package/lib/core.js +282 -0
- package/lib/core.js.map +1 -0
- package/lib/domain.d.ts +246 -0
- package/lib/domain.js +9 -0
- package/lib/domain.js.map +1 -0
- package/lib/host-types.d.ts +240 -0
- package/lib/host-types.js +2 -0
- package/lib/host-types.js.map +1 -0
- package/lib/index.d.ts +860 -0
- package/lib/index.js +4157 -0
- package/lib/index.js.map +1 -0
- package/lib/providers.d.ts +28 -0
- package/lib/providers.js +1308 -0
- package/lib/providers.js.map +1 -0
- package/lib/store.d.ts +62 -0
- package/lib/store.js +432 -0
- package/lib/store.js.map +1 -0
- package/locale/en.json +25 -0
- package/locale/zh.json +25 -0
- package/package.json +79 -14
- package/scripts/build-client.mjs +53 -0
- package/scripts/prepare-release.mjs +243 -0
- package/scripts/verify-dist.mjs +30 -0
- package/index.js +0 -30
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
import type { AccountAttemptView, StateView } from './domain.js';
|
|
2
|
+
import type { CredentialsLike, GenerateOptionsLike, HostContext, KeySelection, RequestErrorAction, AgentRequestPayload, RequestErrorPayload, SettingsLike, StreamChunkLike } from './host-types.js';
|
|
3
|
+
import { KeysManagerStore } from './store.js';
|
|
4
|
+
export declare const name = "keys-manager";
|
|
5
|
+
export declare const inject: string[];
|
|
6
|
+
export declare const provide: string[];
|
|
7
|
+
/** The credential-record scope pi-ai stores its per-provider grants under (llm-pi-ai/src/auth.ts:29). */
|
|
8
|
+
export declare const OAUTH_GRANT_RECORD_SCOPE = "llm-pi-ai";
|
|
9
|
+
/**
|
|
10
|
+
* Stable failure code for a harvest whose state flush rejected AND whose
|
|
11
|
+
* credential-side rollback then failed to write.
|
|
12
|
+
*
|
|
13
|
+
* The state flush is the durability boundary `run` enforces, and the rollback
|
|
14
|
+
* that follows it deletes/restores the credential records the rolled-back row
|
|
15
|
+
* referenced. When those compensating writes fail too, the attempt's reason
|
|
16
|
+
* starts with `<code>: ` and keeps the original flush error verbatim after it,
|
|
17
|
+
* so an upstream caller can branch on {@link isFlushRollbackCompensationFailure}
|
|
18
|
+
* instead of matching prose. A flush failure whose rollback completed keeps its
|
|
19
|
+
* original error unchanged — this code is the only added spelling.
|
|
20
|
+
*/
|
|
21
|
+
export declare const FLUSH_ROLLBACK_COMPENSATION_FAILED = "FLUSH_ROLLBACK_COMPENSATION_FAILED";
|
|
22
|
+
/** Whether an attempt error carries {@link FLUSH_ROLLBACK_COMPENSATION_FAILED}. */
|
|
23
|
+
export declare function isFlushRollbackCompensationFailure(error: string | undefined): boolean;
|
|
24
|
+
/** Service keybinding consumed by the Typert Gateway. */
|
|
25
|
+
interface TypertRemoteBinding {
|
|
26
|
+
service: KeysManagerService;
|
|
27
|
+
serviceKey: string;
|
|
28
|
+
namespace: string;
|
|
29
|
+
}
|
|
30
|
+
export declare class KeysManagerService {
|
|
31
|
+
readonly ctx: HostContext;
|
|
32
|
+
readonly store: KeysManagerStore;
|
|
33
|
+
private readonly als;
|
|
34
|
+
private readonly attempts;
|
|
35
|
+
private readonly stepAttempts;
|
|
36
|
+
private readonly streamAttempts;
|
|
37
|
+
private readonly linkedAttempts;
|
|
38
|
+
private readonly retryBindings;
|
|
39
|
+
private readonly requestSteps;
|
|
40
|
+
private attemptSequence;
|
|
41
|
+
private readonly switches;
|
|
42
|
+
private warnings;
|
|
43
|
+
settings: SettingsLike | undefined;
|
|
44
|
+
private rawCredentials;
|
|
45
|
+
private readonly credentialOriginals;
|
|
46
|
+
private readonly credentialWrappers;
|
|
47
|
+
private brokerOwners;
|
|
48
|
+
private readonly bindingCache;
|
|
49
|
+
private lastBindingPruneAt;
|
|
50
|
+
private readonly selectionQueues;
|
|
51
|
+
private queue;
|
|
52
|
+
private closing;
|
|
53
|
+
/** Interactive sign-in attempts, by attempt id. */
|
|
54
|
+
private readonly loginAttempts;
|
|
55
|
+
/** The provider → attempt id index for the single-attempt rule. */
|
|
56
|
+
private readonly loginsInFlight;
|
|
57
|
+
private loginSequence;
|
|
58
|
+
/** The account row whose grant payload currently IS the projection, per provider. */
|
|
59
|
+
private readonly activeAccounts;
|
|
60
|
+
/**
|
|
61
|
+
* Account rows committed to removal (C6-1/I1 removal-aware oracle), marked
|
|
62
|
+
* by {@link removeOAuthGrant} before its first awaited delete. A multi-row
|
|
63
|
+
* release deletes every row's credential first and the provider record
|
|
64
|
+
* last, and it never splices its rows, so while it runs the provider record
|
|
65
|
+
* and the row object both still look live even though the row's grant is
|
|
66
|
+
* already gone. Object identity alone therefore cannot tell "still serving"
|
|
67
|
+
* from "mid-removal"; this mark can. A successful removal leaves it set —
|
|
68
|
+
* the row it names is either spliced out (removeAccount/deleteKey) or left
|
|
69
|
+
* flagged `valueMissing` (release) — but a removal whose awaited steps
|
|
70
|
+
* THROW must clear it: the row is then still live and still managed, and a
|
|
71
|
+
* stale mark would silently skip its rotation sync until a restart. It
|
|
72
|
+
* marks the live object, so a row object recreated under the same id is
|
|
73
|
+
* unaffected.
|
|
74
|
+
*/
|
|
75
|
+
private readonly retiringAccounts;
|
|
76
|
+
/**
|
|
77
|
+
* Per-provider projection generation — the write counter for
|
|
78
|
+
* {@link activeAccounts} claims (round-3 finding 3) and the attribution
|
|
79
|
+
* identity of a projected grant (round-6 finding SF1). It is bumped exactly
|
|
80
|
+
* when a grant becomes the provider's projection (a manager-side
|
|
81
|
+
* re-projection or the seam's committed grant at the settlement broadcast),
|
|
82
|
+
* never at a later bookkeeping step. A hydration result whose generation no
|
|
83
|
+
* longer matches is discarded instead of overwriting a newer projection
|
|
84
|
+
* with a stale read.
|
|
85
|
+
*/
|
|
86
|
+
private readonly ownershipGenerations;
|
|
87
|
+
/**
|
|
88
|
+
* Per-account-row projection generation in effect when the row's CURRENT
|
|
89
|
+
* cooldown was earned (round-6 finding SF1). `applyFailureCooldown` stamps
|
|
90
|
+
* it at failure time, so a harvest can tell the previous grant's window
|
|
91
|
+
* from one the freshly projected grant earned while the harvest was still
|
|
92
|
+
* queued. A row with no entry carries a window that predates any observed
|
|
93
|
+
* attribution (one loaded from disk, for example); the next projection
|
|
94
|
+
* treats it as the older generation's.
|
|
95
|
+
*/
|
|
96
|
+
private readonly cooldownGenerations;
|
|
97
|
+
/**
|
|
98
|
+
* Per-provider chain of pending grant reconciliations (round-9 finding
|
|
99
|
+
* P1): two refresh echoes for the same route can arrive before the first
|
|
100
|
+
* write-back settles, and the bodies run read → write → commit. Arrival
|
|
101
|
+
* order is preserved so the older observation can never commit after the
|
|
102
|
+
* newer one and leave the row on a stale generation.
|
|
103
|
+
*
|
|
104
|
+
* Round-8 finding I-000106: activation joins the same chain. The only
|
|
105
|
+
* writer of `row.oauth.grantRef` is {@link reconcileGrantRecord}, which runs
|
|
106
|
+
* outside the mutation queue, so activation's record read and its projection
|
|
107
|
+
* write must be mutually exclusive with that writer — otherwise a rotation
|
|
108
|
+
* landing between them leaves the projection on a generation the row no
|
|
109
|
+
* longer names.
|
|
110
|
+
*/
|
|
111
|
+
private readonly grantReconciliations;
|
|
112
|
+
/**
|
|
113
|
+
* Providers whose projection deletion this manager initiated, mapped to
|
|
114
|
+
* the moment it was initiated. The `record-updated` echo of our own
|
|
115
|
+
* demotion consumes the marker (one-shot) and stays silent while the
|
|
116
|
+
* marker is inside its TTL window; past the window the marker is expired
|
|
117
|
+
* and a removal warns as genuinely out-of-band again.
|
|
118
|
+
*/
|
|
119
|
+
private readonly intentionalProjectionRemovals;
|
|
120
|
+
/**
|
|
121
|
+
* In-flight projection demotions by provider (round-5 finding C1): the
|
|
122
|
+
* oauth failure path cools the account and demotes asynchronously, and the
|
|
123
|
+
* failover retry must not re-enter the stream while the projection still
|
|
124
|
+
* resolves — pi-ai prefers the stored grant, so the key pool would never be
|
|
125
|
+
* reached. The request-error listener awaits this entry before deciding.
|
|
126
|
+
*/
|
|
127
|
+
private readonly projectionDemotions;
|
|
128
|
+
/**
|
|
129
|
+
* MF3 load-time hydration result (round-8 finding I-000108). The
|
|
130
|
+
* constructor starts the ownership read, but a restarted process can serve
|
|
131
|
+
* a stream before it settles. Until this provider's read lands,
|
|
132
|
+
* {@link activeAccounts} may not know the owner, so `wrapStream` would
|
|
133
|
+
* select and mutate a key (cursor, `lastUsedAt`, sticky binding) even though
|
|
134
|
+
* pi-ai serves the persisted grant, and an OAuth failure would be classified
|
|
135
|
+
* as externally authenticated instead of cooling the account.
|
|
136
|
+
*
|
|
137
|
+
* The promise never rejects (hydration catches and warns per provider), but
|
|
138
|
+
* it can stay pending forever. Requests therefore never subscribe to it
|
|
139
|
+
* (round-10 should-fix SF2: a per-call `then`/`await` would be retained on a
|
|
140
|
+
* promise that never settles); they wait per provider through
|
|
141
|
+
* {@link hydratedProviders} and {@link hydrationWaiters} instead.
|
|
142
|
+
*/
|
|
143
|
+
private projectionHydration;
|
|
144
|
+
/**
|
|
145
|
+
* Upper bound on one stream's wait for its provider's hydration read
|
|
146
|
+
* (round-9 should-fix). The hydration read has no timeout of its own, so
|
|
147
|
+
* without a bound one stalled `readRecord` would gate every managed stream.
|
|
148
|
+
* Injectable so tests can exercise the bound quickly.
|
|
149
|
+
*/
|
|
150
|
+
private hydrationWaitMs;
|
|
151
|
+
/**
|
|
152
|
+
* Providers whose hydration read has landed — owner found or not
|
|
153
|
+
* (round-10 should-fix SF1). Hydration walks the providers serially and
|
|
154
|
+
* writes each owner as it goes, so one stalled read must not make an
|
|
155
|
+
* already-read provider look unknown: a provider whose own read landed is
|
|
156
|
+
* ready immediately, and the bounded wait's "unknown" fallback is confined
|
|
157
|
+
* to the requested provider's own unresolved read.
|
|
158
|
+
*/
|
|
159
|
+
private readonly hydratedProviders;
|
|
160
|
+
/**
|
|
161
|
+
* One resolver set per provider still being waited on (round-10 should-fix
|
|
162
|
+
* SF2). A wait registers exactly one entry here and removes it when the wait
|
|
163
|
+
* ends — on timeout or on the late landing — so a read that never settles
|
|
164
|
+
* cannot retain anything per request. {@link noteProviderHydrated} is the
|
|
165
|
+
* only notification channel; the shared hydration promise is never
|
|
166
|
+
* subscribed to.
|
|
167
|
+
*/
|
|
168
|
+
private readonly hydrationWaiters;
|
|
169
|
+
/**
|
|
170
|
+
* True once the load-time hydration read is over (round-10 SF1). From then
|
|
171
|
+
* on no provider's read can land, so attribution is known for every provider
|
|
172
|
+
* — including one adopted after the read, whose OAuth rows did not exist
|
|
173
|
+
* when the projection was looked for.
|
|
174
|
+
*/
|
|
175
|
+
private hydrationSettled;
|
|
176
|
+
/** Providers whose bounded-wait warning already fired (warn once). */
|
|
177
|
+
private readonly hydrationWaitWarned;
|
|
178
|
+
/** Channels whose lost-subscription warning already fired (warn once). */
|
|
179
|
+
private readonly subscriptionLostWarnings;
|
|
180
|
+
/** Disposer for the `authorization/settled` subscription. */
|
|
181
|
+
private settledDisposer;
|
|
182
|
+
/** Disposer for the `credentials/record-updated` subscription. */
|
|
183
|
+
private recordUpdatedDisposer;
|
|
184
|
+
readonly typertRemote: TypertRemoteBinding;
|
|
185
|
+
constructor(ctx: HostContext, store: KeysManagerStore);
|
|
186
|
+
/**
|
|
187
|
+
* MF3 load-time hydration: a projection outlives this process — pi-ai
|
|
188
|
+
* wrote `llm-pi-ai/<provider>` during a previous run and it survived in
|
|
189
|
+
* the credential store. Match each persisted payload back to an account
|
|
190
|
+
* row (the exact rule the rotation reconciler applies, via
|
|
191
|
+
* {@link matchOwningAccount}) so removeAccount / deleteManagedCredentials
|
|
192
|
+
* demote the projection and `onGrantRecordUpdated` can mark valueMissing
|
|
193
|
+
* after a restart. An orphan projection — no matching row, e.g. a foreign
|
|
194
|
+
* or stale grant — is left untouched: the manager never adopts a grant it
|
|
195
|
+
* does not own, and out-of-band updates keep surfacing the native-
|
|
196
|
+
* management warning.
|
|
197
|
+
*
|
|
198
|
+
* Returns its own completion (round-8 finding I-000108), and the returned
|
|
199
|
+
* promise is already caught, so a failed hydration degrades to "no owner"
|
|
200
|
+
* instead of wedging every request. Request attribution does not subscribe
|
|
201
|
+
* to that promise (round-10 should-fix SF2 — it may stay pending forever);
|
|
202
|
+
* each request waits on its own provider's landing instead.
|
|
203
|
+
*/
|
|
204
|
+
private hydrateProjectionOwnership;
|
|
205
|
+
/**
|
|
206
|
+
* Record that one provider's hydration read is over — owner or not (SF1) —
|
|
207
|
+
* and wake the requests waiting on that provider alone (SF2).
|
|
208
|
+
*/
|
|
209
|
+
private noteProviderHydrated;
|
|
210
|
+
/** Release every provider's readiness wait when hydration cannot proceed. */
|
|
211
|
+
private settleHydrationWaits;
|
|
212
|
+
/**
|
|
213
|
+
* Wait for one provider's own hydration read to land, at most `ms`, and
|
|
214
|
+
* report whether it landed.
|
|
215
|
+
*
|
|
216
|
+
* Registers exactly one waiter entry for this wait and removes it when the
|
|
217
|
+
* wait ends either way (round-10 should-fix SF2): nothing is attached to the
|
|
218
|
+
* shared hydration promise, so a read that never settles cannot retain one
|
|
219
|
+
* reaction per request. A late landing still wakes the wait through
|
|
220
|
+
* {@link noteProviderHydrated}, and the generation guard in
|
|
221
|
+
* {@link hydrateProjectionOwnership} still decides whether its result lands
|
|
222
|
+
* (I-000108).
|
|
223
|
+
*/
|
|
224
|
+
private waitForProviderHydration;
|
|
225
|
+
/**
|
|
226
|
+
* Live per-provider waiter entries. A documented internal counter for the
|
|
227
|
+
* round-10 SF2 invariant: a wait that ended must leave no entry behind, so
|
|
228
|
+
* this never grows with the number of timed-out requests.
|
|
229
|
+
*/
|
|
230
|
+
private hydrationWaiterCount;
|
|
231
|
+
/**
|
|
232
|
+
* Wait (bounded) until projection-ownership hydration can attribute this
|
|
233
|
+
* request, and report whether it did.
|
|
234
|
+
*
|
|
235
|
+
* A provider without OAuth rows can never own a projection, so it never
|
|
236
|
+
* waits — a stalled read for some other provider must not delay it
|
|
237
|
+
* (round-9 should-fix: this used to gate every managed provider, API-key-
|
|
238
|
+
* only ones included).
|
|
239
|
+
*
|
|
240
|
+
* Returns false when the bound elapsed first. The caller then treats
|
|
241
|
+
* attribution as unknown (no owner) and keeps key state untouched: a
|
|
242
|
+
* projection may still be serving, and selecting/mutating a key here would
|
|
243
|
+
* repeat the I-000108 corruption. The hydration promise is not cancelled,
|
|
244
|
+
* so a late result still lands under the generation guard and later
|
|
245
|
+
* requests are attributed normally.
|
|
246
|
+
*
|
|
247
|
+
* Readiness is decided PER PROVIDER (round-10 should-fix SF1). Hydration
|
|
248
|
+
* writes owners progressively, so racing the one global promise would
|
|
249
|
+
* discard an owner this provider already confirmed just because another
|
|
250
|
+
* provider's read stalled. A provider whose own read has landed — with an
|
|
251
|
+
* owner or with none — is ready without waiting, and the "unknown" fallback
|
|
252
|
+
* is confined to the requested provider's own unresolved read.
|
|
253
|
+
*/
|
|
254
|
+
private awaitProjectionOwnership;
|
|
255
|
+
/**
|
|
256
|
+
* Record that a newly committed grant became the provider's projection and
|
|
257
|
+
* return that projection's generation (round-6 finding SF1).
|
|
258
|
+
*
|
|
259
|
+
* The bump happens at the moment the projection is observable — pi-ai's
|
|
260
|
+
* committed grant at the settlement broadcast, or a manager-side
|
|
261
|
+
* re-projection (activation, rotation reconcile, harvest claim) — so every
|
|
262
|
+
* failure stamped afterwards belongs to that projection. It must never be
|
|
263
|
+
* deferred to a later bookkeeping step: the harvest waits behind the
|
|
264
|
+
* mutation queue and the projection read while the new grant already
|
|
265
|
+
* serves, and a failure landing there must not be mistaken for the
|
|
266
|
+
* previous grant's.
|
|
267
|
+
*/
|
|
268
|
+
private noteProjectionCommitted;
|
|
269
|
+
/**
|
|
270
|
+
* Claim the projection owner for one account row, bumping the provider's
|
|
271
|
+
* ownership generation so an in-flight hydration read of an older state
|
|
272
|
+
* is discarded instead of overwriting the claim (round-3 finding 3).
|
|
273
|
+
*/
|
|
274
|
+
private claimProjectionOwner;
|
|
275
|
+
/**
|
|
276
|
+
* Log without ever echoing secret material. */
|
|
277
|
+
warn(message: string): void;
|
|
278
|
+
/** Rebuild the broker-reference → provider index. */
|
|
279
|
+
private rebuildBrokerMap;
|
|
280
|
+
/** Serialize mutations, rejecting submissions that arrive during shutdown. */
|
|
281
|
+
private run;
|
|
282
|
+
/**
|
|
283
|
+
* Periodically drop expired transient state: retry pins after `PIN_TTL_MS`,
|
|
284
|
+
* the remaining attempt/step records after `ATTEMPT_TTL_MS`.
|
|
285
|
+
*/
|
|
286
|
+
pruneTransient(now?: number): void;
|
|
287
|
+
/** Stop accepting mutations, drain the queue, then persist the final snapshot. */
|
|
288
|
+
close(): Promise<void>;
|
|
289
|
+
/** Push a user-facing warning that rides the next state response. */
|
|
290
|
+
private pushWarning;
|
|
291
|
+
private takeWarnings;
|
|
292
|
+
/** Current settings service, if the composition mounted one. */
|
|
293
|
+
private settingsService;
|
|
294
|
+
/** Current raw credential provider. */
|
|
295
|
+
private credentialsRaw;
|
|
296
|
+
/**
|
|
297
|
+
* The credential store's record half, reached through the raw provider:
|
|
298
|
+
* the broker wrappers only ever replace the reference methods, so the
|
|
299
|
+
* record methods of the mounted credential provider are always intact on
|
|
300
|
+
* the raw instance. `llm-pi-ai` writes its grants through exactly these
|
|
301
|
+
* methods, so the manager's activation projection must use them too.
|
|
302
|
+
*/
|
|
303
|
+
private credentialRecords;
|
|
304
|
+
/**
|
|
305
|
+
* Resolve the OAuth authorization flow one provider offers.
|
|
306
|
+
*
|
|
307
|
+
* The flow key format is pinned by `llm-pi-ai/login.ts:149-171` +
|
|
308
|
+
* `auth.ts:29,36-38`: `recordKeyFor(providerId)` = `llm-pi-ai/<providerId>`,
|
|
309
|
+
* with the oauth method offered first (`login.ts:40-47`). A provider with
|
|
310
|
+
* no such flow (not OAuth-capable, or not installed) resolves to
|
|
311
|
+
* undefined, which callers must treat as "sign-in unavailable".
|
|
312
|
+
*
|
|
313
|
+
* Availability is read from the live seam on every call, never cached:
|
|
314
|
+
* the authorization service may mount after this plugin, so a route whose
|
|
315
|
+
* flow is not enumerable yet stays account-unsupported until the flow list
|
|
316
|
+
* exists (the client re-reads state after that ordering settles).
|
|
317
|
+
*/
|
|
318
|
+
oauthFlowFor(provider: string): {
|
|
319
|
+
key: string;
|
|
320
|
+
method: string;
|
|
321
|
+
} | undefined;
|
|
322
|
+
/**
|
|
323
|
+
* C6-2: can this route's OAuth sign-in actually begin right now? Account
|
|
324
|
+
* capability is gated on this instead of the catalog's `nativeAuth.oauth`
|
|
325
|
+
* flag alone, so a route without a registered pi-ai flow never offers an
|
|
326
|
+
* Add account that would fail. The check is lazy — see {@link oauthFlowFor}.
|
|
327
|
+
*/
|
|
328
|
+
private oauthFlowAvailable;
|
|
329
|
+
/**
|
|
330
|
+
* Warn once per channel: a lost subscription silently disables a whole
|
|
331
|
+
* feature (harvest never runs; rotations never sync), so the degradation
|
|
332
|
+
* must be observable instead of indistinguishable from success.
|
|
333
|
+
*/
|
|
334
|
+
private warnSubscriptionLost;
|
|
335
|
+
/**
|
|
336
|
+
* Subscribe to the seam's settled broadcast (authorization/src/index.ts:57,
|
|
337
|
+
* :337-351). A settled attempt for a key this service is driving harvests
|
|
338
|
+
* the committed grant; everything else is ignored — another surface's
|
|
339
|
+
* sign-in is that surface's business, and the grant it leaves behind is
|
|
340
|
+
* reconciled through `credentials/record-updated` instead.
|
|
341
|
+
*/
|
|
342
|
+
private watchAuthorizationSettled;
|
|
343
|
+
/** One settled broadcast: finalize the matching attempt, harvest on success. */
|
|
344
|
+
private onAuthorizationSettled;
|
|
345
|
+
/**
|
|
346
|
+
* Start one interactive OAuth sign-in. Returns immediately with the
|
|
347
|
+
* attempt view; the attempt walks `pending → (prompt ⇄ pending) →
|
|
348
|
+
* ready | failed | cancelled`, and every transition is observable through
|
|
349
|
+
* {@link getAccountAttempt} / {@link listAccountAttempts}.
|
|
350
|
+
*/
|
|
351
|
+
beginAccountLogin(provider: string, alias?: string, targetAccountId?: string): Promise<AccountAttemptView>;
|
|
352
|
+
/** Resolve the attempt's parked prompt with the human's answer. */
|
|
353
|
+
answerAccountPrompt(attemptId: string, value: string): Promise<void>;
|
|
354
|
+
/** One attempt's serializable view, or undefined when unknown. */
|
|
355
|
+
getAccountAttempt(attemptId: string): AccountAttemptView | undefined;
|
|
356
|
+
/** Remote: poll one attempt's serializable state (T3 dialog channel). */
|
|
357
|
+
accountAttempt(attemptId: string): AccountAttemptView | undefined;
|
|
358
|
+
/** Every attempt, newest first, for T3 surfaces. */
|
|
359
|
+
listAccountAttempts(): AccountAttemptView[];
|
|
360
|
+
/**
|
|
361
|
+
* Detach the parked prompt. With a reason the flow's awaited promise is
|
|
362
|
+
* rejected too — a parked prompt dropped un-rejected would leave the
|
|
363
|
+
* seam's flow (and the provider's single-flight slot) wedged until its
|
|
364
|
+
* own timeout, so every attempt-ending path passes one.
|
|
365
|
+
*/
|
|
366
|
+
private dropPrompt;
|
|
367
|
+
/** Finalize an attempt with a terminal status exactly once. */
|
|
368
|
+
private finishLoginAttempt;
|
|
369
|
+
/** Finalize an attempt as failed with the given reason. */
|
|
370
|
+
private failLoginAttempt;
|
|
371
|
+
/**
|
|
372
|
+
* Turn a committed sign-in into pool state: persist the grant pi-ai wrote
|
|
373
|
+
* to `llm-pi-ai/<provider>` according to the settlement-time capture,
|
|
374
|
+
* under the plugin-scope account ref, and upsert the `kind: 'oauth'` row.
|
|
375
|
+
* Any failure fails the attempt loudly — a login whose grant could not be
|
|
376
|
+
* captured must not look like success.
|
|
377
|
+
*
|
|
378
|
+
* `committed` is the F2 provenance promise started by
|
|
379
|
+
* {@link captureSettledProjection}: the projection payload observed at the
|
|
380
|
+
* settlement broadcast, never a later live read. The harvest persists
|
|
381
|
+
* exactly that payload and compensation may only revert exactly that
|
|
382
|
+
* payload, so a foreign grant cannot become this attempt's authority just
|
|
383
|
+
* because it landed before the queued mutation ran.
|
|
384
|
+
*
|
|
385
|
+
* `projectionGeneration` is the generation {@link noteProjectionCommitted}
|
|
386
|
+
* recorded at that same broadcast (R6-SF1): it names the projection whose
|
|
387
|
+
* failures own a cooldown, so the stale-window reset below clears only a
|
|
388
|
+
* window attributed to an OLDER projection.
|
|
389
|
+
*/
|
|
390
|
+
private harvestLogin;
|
|
391
|
+
/**
|
|
392
|
+
* Capture this attempt's committed projection payload (F2 provenance).
|
|
393
|
+
*
|
|
394
|
+
* The seam reports `authorized` only after the flow's commit is observed
|
|
395
|
+
* and still present, so a read STARTED at that broadcast observes the
|
|
396
|
+
* record this attempt wrote. The read is deliberately not queued on
|
|
397
|
+
* `run`: a release parked ahead of the harvest must not delay provenance
|
|
398
|
+
* long enough for a foreign writer to become this attempt's authority.
|
|
399
|
+
*
|
|
400
|
+
* The seam offers no writer identity or record version, so this capture is
|
|
401
|
+
* the closest provenance the manager can obtain: `undefined` means the
|
|
402
|
+
* committed payload is unknown, and compensation then refuses to touch the
|
|
403
|
+
* projection instead of guessing. See I-000090 for the seam capability
|
|
404
|
+
* (compare-and-delete / versioned records) that would make it exact.
|
|
405
|
+
*/
|
|
406
|
+
private captureSettledProjection;
|
|
407
|
+
/**
|
|
408
|
+
* Undo the in-memory half of a harvest whose state document was never made
|
|
409
|
+
* durable (a rejected `store.flush()` after the mutation resolved).
|
|
410
|
+
*
|
|
411
|
+
* Runs synchronously from the failure handler so no queued mutation,
|
|
412
|
+
* debounced write, or retry can observe — or persist — the rolled-back row
|
|
413
|
+
* between the rejected flush and this undo. The credential rollback that
|
|
414
|
+
* follows ({@link compensateFailedFlush}) deletes the grant the row
|
|
415
|
+
* referenced, so leaving the upsert in memory would make the next
|
|
416
|
+
* successful flush point at a deleted record.
|
|
417
|
+
*/
|
|
418
|
+
private undoHarvestRow;
|
|
419
|
+
/**
|
|
420
|
+
* Undo the credential writes of a harvest whose state flush rejected.
|
|
421
|
+
*
|
|
422
|
+
* Differs from {@link compensateFailedHarvest} in one respect: the row
|
|
423
|
+
* upsert already happened, so the plugin-scope record cannot simply be
|
|
424
|
+
* deleted. A same-ref re-authentication overwrote the record the restored
|
|
425
|
+
* row references, and a changed-ref re-authentication deleted the
|
|
426
|
+
* superseded generation before the flush failed — both are reverted here,
|
|
427
|
+
* each write guarded by the exact payload this login committed so a
|
|
428
|
+
* foreign writer's record is never clobbered. The `llm-pi-ai` projection is
|
|
429
|
+
* then demoted exactly like any failed harvest (ownership-checked, never a
|
|
430
|
+
* foreign grant), so no committed credential outlives the rolled-back row
|
|
431
|
+
* without a pool owner.
|
|
432
|
+
*
|
|
433
|
+
* Resolves with the write failures the rollback hit, if any: a refusal to
|
|
434
|
+
* touch the surface when provenance is unknown is a deliberate skip, not a
|
|
435
|
+
* failure, but a rejected compensating write means the credential surface
|
|
436
|
+
* may still hold records the rolled-back row no longer owns. The caller
|
|
437
|
+
* tags the attempt with {@link FLUSH_ROLLBACK_COMPENSATION_FAILED} so that
|
|
438
|
+
* state is distinguishable from a clean rollback (each failure is also
|
|
439
|
+
* warned about here, message by message).
|
|
440
|
+
*/
|
|
441
|
+
private compensateFailedFlush;
|
|
442
|
+
/**
|
|
443
|
+
* Roll a failed harvest back to a clean credential surface: the
|
|
444
|
+
* plugin-scope grant copy is deleted when it was written; the
|
|
445
|
+
* `llm-pi-ai` projection is deleted only while it still carries EXACTLY
|
|
446
|
+
* this login's settlement-time committed payload, so compensation can
|
|
447
|
+
* never delete a grant another account owns.
|
|
448
|
+
*
|
|
449
|
+
* Provenance unknown (the settlement-time capture found no usable record)
|
|
450
|
+
* refuses both deletions with a warning: without the committed payload
|
|
451
|
+
* there is no proof of ownership, and deleting "whatever is live" is the
|
|
452
|
+
* round-4 defect (I-000083).
|
|
453
|
+
*/
|
|
454
|
+
private compensateFailedHarvest;
|
|
455
|
+
/**
|
|
456
|
+
* Delete the `llm-pi-ai` projection only while it still carries exactly
|
|
457
|
+
* this login's settlement-time committed payload. Shared by the
|
|
458
|
+
* failed-harvest compensation and the flush-failure rollback.
|
|
459
|
+
*
|
|
460
|
+
* Resolves `ok: false` only when the compare/delete writes themselves
|
|
461
|
+
* rejected; an ownership mismatch is the guard doing its job (the live
|
|
462
|
+
* record belongs to someone else) and counts as a clean skip.
|
|
463
|
+
*/
|
|
464
|
+
private revertHarvestProjection;
|
|
465
|
+
/** Expire wedged sign-ins and drop long-settled attempt records. */
|
|
466
|
+
private pruneLoginAttempts;
|
|
467
|
+
/** Keep the attempt cache bounded: evict the oldest settled entries first. */
|
|
468
|
+
private capLoginAttempts;
|
|
469
|
+
/**
|
|
470
|
+
* Subscribe to the credential seam's post-commit record broadcast
|
|
471
|
+
* (credentials/src/index.ts:271-277; the event carries the key only, so
|
|
472
|
+
* the handler re-reads the record for its payload).
|
|
473
|
+
*/
|
|
474
|
+
private watchGrantRecordUpdated;
|
|
475
|
+
/**
|
|
476
|
+
* Reconcile one `llm-pi-ai/<provider>` record change with the pool.
|
|
477
|
+
*
|
|
478
|
+
* The manager writes this record only through {@link activateAccount}
|
|
479
|
+
* (D1); pi-ai rewrites it on every lazy refresh and owns every login. A
|
|
480
|
+
* change whose payload belongs to the active account is a rotation and
|
|
481
|
+
* syncs back into the owning row; anything else the manager did not write
|
|
482
|
+
* is native management of the route and surfaces as a warning instead of
|
|
483
|
+
* being adopted silently.
|
|
484
|
+
*
|
|
485
|
+
* C6-1: reconciliation writes a plugin-scope record for the rotated
|
|
486
|
+
* generation and then mutates the owning row. removeAccount /
|
|
487
|
+
* releaseProvider mutate the same row but cannot be serialized against
|
|
488
|
+
* that write — the credential seam offers only a per-record lock
|
|
489
|
+
* (`modifyRecord`), never a transaction across the record write and the
|
|
490
|
+
* pool row. So {@link reconcileGrantRecord} revalidates the row immediately
|
|
491
|
+
* before AND after the awaited write and compensates by deleting the record
|
|
492
|
+
* it just wrote. An orphan record must never outlive a removed account.
|
|
493
|
+
*
|
|
494
|
+
* Round-9 finding P1: two echoes for the same provider can
|
|
495
|
+
* arrive before the first reconciliation commits. Their write-backs then
|
|
496
|
+
* race and an out-of-order completion could move the row back onto an
|
|
497
|
+
* older generation, stranding the newer record outside manager state. The
|
|
498
|
+
* bodies are therefore chained per provider: one observation's
|
|
499
|
+
* read → write → commit window is never interleaved with another's, and
|
|
500
|
+
* {@link reconcileGrantRecord} additionally re-checks that its generation
|
|
501
|
+
* is still current before committing.
|
|
502
|
+
*/
|
|
503
|
+
private onGrantRecordUpdated;
|
|
504
|
+
/**
|
|
505
|
+
* Append one body to the provider's per-route grant chain and return it.
|
|
506
|
+
*
|
|
507
|
+
* The chain is the single serialization point for every operation that can
|
|
508
|
+
* repoint `row.oauth.grantRef` or reproject the route: the echo reconciler
|
|
509
|
+
* (round-9 P1 ordering) and, since round-8 finding I-000106, activation.
|
|
510
|
+
* The stored tail never rejects, so one failed body cannot stop the chain;
|
|
511
|
+
* callers that must observe the failure await the returned promise (the
|
|
512
|
+
* fire-and-forget echo caller attaches its own catch).
|
|
513
|
+
*/
|
|
514
|
+
private scheduleGrantReconciliation;
|
|
515
|
+
/**
|
|
516
|
+
* The body of {@link onGrantRecordUpdated}. Pool state is read live, and
|
|
517
|
+
* every awaited step around the write is guarded by revalidation: a
|
|
518
|
+
* removal is allowed to complete while the write is in flight.
|
|
519
|
+
*/
|
|
520
|
+
private reconcileGrantRecord;
|
|
521
|
+
/**
|
|
522
|
+
* C6-1 revalidation: the row captured before an awaited credential write
|
|
523
|
+
* must still be the provider's live row — same object identity, still an
|
|
524
|
+
* OAuth account. A remove/release landing in between detaches the row
|
|
525
|
+
* object; object identity is what tells "this account is still there"
|
|
526
|
+
* apart from "some same-id row was recreated".
|
|
527
|
+
*
|
|
528
|
+
* C6-1/I1: a removal that is still IN PROGRESS is not visible through
|
|
529
|
+
* identity alone. A multi-row release deletes each row's grant first and
|
|
530
|
+
* the provider record last, and it never splices its rows, so after row X's
|
|
531
|
+
* grant is deleted the provider record and row X both still look live while
|
|
532
|
+
* the release awaits later rows. Syncing a rotation into that window would
|
|
533
|
+
* commit `grantRef = rotatedRef` on a row nothing will serve, and the
|
|
534
|
+
* release's final provider deletion leaves that record orphaned. The
|
|
535
|
+
* removal-aware check is {@link retiringAccounts}, set before the removal's
|
|
536
|
+
* first awaited delete.
|
|
537
|
+
*
|
|
538
|
+
* R7-SF3: `valueMissing` is NOT a removal signal — it only says the row is
|
|
539
|
+
* currently not serving. A demoted row is still managed, and a projection
|
|
540
|
+
* that reappears for it must be re-adopted (the caller proves the
|
|
541
|
+
* projection belongs to this row before claiming it). This guard therefore
|
|
542
|
+
* keeps the credential-write semantics strict: writing to a demoted row is
|
|
543
|
+
* pointless until the adoption path has cleared the flag.
|
|
544
|
+
*/
|
|
545
|
+
private accountRowStillCurrent;
|
|
546
|
+
/**
|
|
547
|
+
* R7-SF3 adoption proof: the row is live and still managed, whether or not
|
|
548
|
+
* it is currently serving. "Retiring" (removal in progress) refuses
|
|
549
|
+
* everything; "demoted" (`valueMissing`) refuses nothing here — the caller
|
|
550
|
+
* has already proven that the live projection belongs to this exact row
|
|
551
|
+
* ({@link matchOwningAccount}) and that no newer projection superseded the
|
|
552
|
+
* observation (generation guard).
|
|
553
|
+
*/
|
|
554
|
+
private accountRowIsManaged;
|
|
555
|
+
/**
|
|
556
|
+
* Round-5 review (R5-MF1): whether a live account row of `provider` still
|
|
557
|
+
* names `ref` as its grant. The settled harvest can adopt the generation a
|
|
558
|
+
* reconciliation just wrote while that reconciliation is still awaiting its
|
|
559
|
+
* staleness probe — the row's `grantRef` then IS the record the cleanup
|
|
560
|
+
* would delete, so deleting it destroys the account's own live grant.
|
|
561
|
+
* A row already marked for removal does not count: its removal deletes the
|
|
562
|
+
* refs it names, so keeping the record would strand it instead.
|
|
563
|
+
*/
|
|
564
|
+
private grantRefServed;
|
|
565
|
+
/**
|
|
566
|
+
* Delete a rotation record a reconciliation wrote but must not keep, unless
|
|
567
|
+
* a live row already serves it (R5-MF1). Cleanup is best-effort with a
|
|
568
|
+
* warning, exactly like the harvest-time superseded-record cleanup.
|
|
569
|
+
*/
|
|
570
|
+
private discardRotationRecord;
|
|
571
|
+
/**
|
|
572
|
+
* Round-9 P1 revalidation: the generation a reconciliation observed is
|
|
573
|
+
* still the one the route holds, and the ownership claim this body made (if
|
|
574
|
+
* any) is still the newest. Two independent writes can supersede it while
|
|
575
|
+
* the plugin-scope write-back is in flight: pi-ai refreshing again (the
|
|
576
|
+
* projection's refresh digest — the ref's identity — moves on) and an
|
|
577
|
+
* activation or login claiming the projection (the ownership generation
|
|
578
|
+
* moves). Committing the older ref in either case would move the row
|
|
579
|
+
* backwards and strand the newer record outside manager state.
|
|
580
|
+
*
|
|
581
|
+
* A failed re-read cannot prove staleness, so it must not block the sync:
|
|
582
|
+
* the commit then proceeds exactly as it did before this guard existed.
|
|
583
|
+
*/
|
|
584
|
+
private rotationStillCurrent;
|
|
585
|
+
/**
|
|
586
|
+
* Make one account's grant payload the route's stored credential
|
|
587
|
+
* (D1 activation projection).
|
|
588
|
+
*
|
|
589
|
+
* Round-8 finding I-000106 (must-fix): the decide snapshot used to be read
|
|
590
|
+
* before the seam's lock, and `reconcileGrantRecord` runs OUTSIDE the
|
|
591
|
+
* mutation queue — so a rotation echo could repoint `row.oauth.grantRef` and
|
|
592
|
+
* delete the record being projected between the read and the write, leaving
|
|
593
|
+
* the route on an older generation than the row names. The whole
|
|
594
|
+
* read → project → claim section therefore runs on the provider's grant
|
|
595
|
+
* chain ({@link scheduleGrantReconciliation}) — the same chain the
|
|
596
|
+
* reconciler bodies use. A rotation committed before this body is observed
|
|
597
|
+
* in `row.oauth.grantRef`; one that arrives while the body runs is queued
|
|
598
|
+
* behind it and reconciles the generation this body projected. Either way
|
|
599
|
+
* the projection holds exactly the generation the row names.
|
|
600
|
+
*
|
|
601
|
+
* The seam's per-key lock still serializes the read-decide-replace steps of
|
|
602
|
+
* `modifyRecord` (credentials/src/index.ts:236-250) against other writers.
|
|
603
|
+
* The account whose payload lands becomes the pool's active account and is
|
|
604
|
+
* cascade-selectable again.
|
|
605
|
+
*/
|
|
606
|
+
activateAccount(provider: string, accountId: string): Promise<StateView>;
|
|
607
|
+
/**
|
|
608
|
+
* Demote the cascade to the key pool by deleting the projection record:
|
|
609
|
+
* pi-ai's per-request resolution prefers the stored credential and only
|
|
610
|
+
* falls through to the broker `apiKeyEnv` when none is stored
|
|
611
|
+
* (research, resolve.js:26-55).
|
|
612
|
+
*
|
|
613
|
+
* Recovery is deliberately manual: the cooled account stays demoted until
|
|
614
|
+
* the user re-activates it or signs in again. An automatic re-activation
|
|
615
|
+
* on cooldown expiry would replay the failing grant every request; the
|
|
616
|
+
* user (who can see whether the account recovered upstream) is the
|
|
617
|
+
* arbiter. `fallbackToKeys` marks every account row value-missing so the
|
|
618
|
+
* live arbitration — projection presence plus probe fail-skip over a
|
|
619
|
+
* kind-agnostic cursor ring — keeps serving only real candidates.
|
|
620
|
+
*
|
|
621
|
+
* `accountId` names the row whose failure triggers this demotion (R7-MF).
|
|
622
|
+
* It is resolved when the queued body runs, so the demotion deletes only a
|
|
623
|
+
* projection that still belongs to THAT row: activating another account
|
|
624
|
+
* while the failing request is in flight must not lose the new account's
|
|
625
|
+
* healthy projection.
|
|
626
|
+
*
|
|
627
|
+
* R14-M3: the targetless form (omitted `accountId`) is TEST-ONLY. It
|
|
628
|
+
* resolves the provider's active account when the queued body runs — the
|
|
629
|
+
* same execution-time race R7-MF removed from the failure path — so it is
|
|
630
|
+
* kept only because the tests drive the demotion without a failure event.
|
|
631
|
+
* Production callers must name the row; the failure path always passes
|
|
632
|
+
* `key.id`.
|
|
633
|
+
*/
|
|
634
|
+
fallbackToKeys(provider: string, accountId?: string): Promise<void>;
|
|
635
|
+
/**
|
|
636
|
+
* Atomic decide-and-delete of the `llm-pi-ai/<provider>` projection for one
|
|
637
|
+
* row (round-3 finding 1): the payload compare runs INSIDE the credential
|
|
638
|
+
* seam's per-key lock — `modifyRecord` hands `mutate` the record as it
|
|
639
|
+
* stands while the write is exclusive, so a native sign-in that already
|
|
640
|
+
* replaced the projection is visible here and the delete is skipped. The
|
|
641
|
+
* seam exposes no delete-inside-modify primitive: a `mutate` may only
|
|
642
|
+
* return a replacement or `undefined` (leave untouched), and calling
|
|
643
|
+
* `deleteRecord` inside the mutate would deadlock the provider's own
|
|
644
|
+
* serialized operation queue (credentials-local `enqueue`). The closest
|
|
645
|
+
* equivalent — the exact primitives this method uses — are a decide-only
|
|
646
|
+
* modify (returns `undefined`, no write) and a second pre-delete re-read
|
|
647
|
+
* against the decided-against snapshot ({@link projectionStillMatches},
|
|
648
|
+
* F2 step-1 hardening): a native writer landing between the decide and
|
|
649
|
+
* the delete is therefore visible before the delete runs, and its grant
|
|
650
|
+
* survives. The residual window is the sub-microtask gap between the
|
|
651
|
+
* re-read releasing the lock and the delete's own queue entry; a native
|
|
652
|
+
* writer landing there is still deleted, because closing that gap needs a
|
|
653
|
+
* compare-and-delete primitive the seam does not expose (store issue
|
|
654
|
+
* I-000090). `tests/host.test.ts` ("a native grant landing after the final
|
|
655
|
+
* comparison is still lost") pins that exact outcome so the seam fix has a
|
|
656
|
+
* test to flip.
|
|
657
|
+
*
|
|
658
|
+
* This is the only delete authority: a natively replaced grant — another
|
|
659
|
+
* account signed in, a native api-key record, an unrecognized payload —
|
|
660
|
+
* never matches and must survive the demotion of a stale row, and the
|
|
661
|
+
* async hydration window must not decide ownership (round-2 review,
|
|
662
|
+
* steps 1–2).
|
|
663
|
+
*/
|
|
664
|
+
private decideAndDeleteProjection;
|
|
665
|
+
/**
|
|
666
|
+
* F2 pre-delete re-read, shared by the demotion and harvest-compensation
|
|
667
|
+
* decide-then-delete pairs: `true` while the live projection still holds
|
|
668
|
+
* exactly the decided-against payload. Runs inside `modifyRecord`'s
|
|
669
|
+
* per-key lock (the same primitive as the decide), so a native writer
|
|
670
|
+
* that already replaced the record is visible here and the delete is
|
|
671
|
+
* skipped instead of destroying the foreign grant. The lock is released
|
|
672
|
+
* before the delete, so a writer landing in that final gap is still lost:
|
|
673
|
+
* closing it needs the seam's compare-and-delete (store issue I-000090).
|
|
674
|
+
*/
|
|
675
|
+
private projectionStillMatches;
|
|
676
|
+
/**
|
|
677
|
+
* The fallback body; callers own the mutation queue. `target` narrows the
|
|
678
|
+
* ownership check to one explicit row: removal call sites pass the removed
|
|
679
|
+
* row object, the failure path passes the failing row's id (R7-MF) — an
|
|
680
|
+
* id is re-resolved here because the failure's demotion is queued and the
|
|
681
|
+
* row object may be detached by the time it runs. Without a target the
|
|
682
|
+
* provider's active account decides.
|
|
683
|
+
*/
|
|
684
|
+
private fallbackToKeysUnqueued;
|
|
685
|
+
/**
|
|
686
|
+
* Drop one OAuth account's plugin-scope grant payload and — only while
|
|
687
|
+
* this row still owns the live projection — demote the cascade to the key
|
|
688
|
+
* pool first, so the route falls through to the API-key pool instead of
|
|
689
|
+
* serving a grant whose owning row is gone. The demotion re-reads
|
|
690
|
+
* `llm-pi-ai/<provider>` inside this mutation and runs it through the
|
|
691
|
+
* ownership oracle ({@link matchOwningAccount}); a natively replaced
|
|
692
|
+
* projection (another account signed in, a native api-key record) is left
|
|
693
|
+
* untouched. Non-oauth rows are a no-op; every OAuth-removal call site
|
|
694
|
+
* goes through this helper.
|
|
695
|
+
*/
|
|
696
|
+
private removeOAuthGrant;
|
|
697
|
+
/**
|
|
698
|
+
* Drop one account: its plugin-scope grant payload, its row, and — when
|
|
699
|
+
* it was the active projection — the projection itself, so the route
|
|
700
|
+
* falls back to the key pool.
|
|
701
|
+
*/
|
|
702
|
+
removeAccount(provider: string, accountId: string): Promise<StateView>;
|
|
703
|
+
/** Install the broker layer on one credential provider instance. */
|
|
704
|
+
attachCredentials(credentials: CredentialsLike): void;
|
|
705
|
+
/** Restore the credential provider methods when our fiber unloads. */
|
|
706
|
+
detachCredentials(credentials: CredentialsLike): void;
|
|
707
|
+
/** Resolve a credential through the unwrapped provider. */
|
|
708
|
+
private resolveOriginal;
|
|
709
|
+
/**
|
|
710
|
+
* Probe one credential reference while distinguishing absence from a
|
|
711
|
+
* transient credential-store failure. Selection must never persist a
|
|
712
|
+
* permanent `valueMissing` marker for an error.
|
|
713
|
+
*/
|
|
714
|
+
private probeKey;
|
|
715
|
+
/** Write one key literal through the unwrapped provider. */
|
|
716
|
+
private setOriginal;
|
|
717
|
+
/**
|
|
718
|
+
* Remove one key literal through the unwrapped provider. A rejected unset is
|
|
719
|
+
* only ignored when the credential is confirmed absent; a transient or
|
|
720
|
+
* permission failure must keep the metadata so a later retry can finish.
|
|
721
|
+
*/
|
|
722
|
+
private unsetOriginal;
|
|
723
|
+
/** All settings descriptors, with an explicit unreadable state. */
|
|
724
|
+
private describeSettings;
|
|
725
|
+
/** Find one live settings descriptor by entry id. */
|
|
726
|
+
private namespace;
|
|
727
|
+
/** Apply one path-addressed settings write through the live revision. */
|
|
728
|
+
private mutateSettings;
|
|
729
|
+
/** Prune expired bindings at most once a minute, without a restart. */
|
|
730
|
+
private pruneBindings;
|
|
731
|
+
/** Build the complete UI state. */
|
|
732
|
+
buildState(): StateView;
|
|
733
|
+
/** Remote: complete manager state. */
|
|
734
|
+
getState(): StateView;
|
|
735
|
+
/** Find one directory entry by route id. */
|
|
736
|
+
private directoryEntry;
|
|
737
|
+
/**
|
|
738
|
+
* Classify the live binding. An unreadable settings snapshot is `unknown`,
|
|
739
|
+
* not a confirmed mismatch: transient settings failures must not disable
|
|
740
|
+
* key-pool recovery for a provider that is still managed.
|
|
741
|
+
*/
|
|
742
|
+
private bindingState;
|
|
743
|
+
/** Candidate credential refs from which an existing key can be imported. */
|
|
744
|
+
private importRefs;
|
|
745
|
+
/** Create and store one key record without adding it to a provider. */
|
|
746
|
+
private createKeyRecord;
|
|
747
|
+
/** Find the first existing key value worth importing. */
|
|
748
|
+
private findImport;
|
|
749
|
+
/** Remote: take over one existing provider route. */
|
|
750
|
+
adoptProvider(provider: string): Promise<StateView>;
|
|
751
|
+
/** Discover models for a new extra provider, falling back to the seed catalog. */
|
|
752
|
+
private discoverModels;
|
|
753
|
+
/** Remote: install and adopt one extra built-in provider. */
|
|
754
|
+
addExtraProvider(extraId: string, alias: string, value: string): Promise<StateView>;
|
|
755
|
+
/** Add one key; caller already owns the mutation queue. */
|
|
756
|
+
private addKeyLocked;
|
|
757
|
+
/** Remote: add one key to a managed provider. */
|
|
758
|
+
addKey(provider: string, alias: string, value: string): Promise<StateView>;
|
|
759
|
+
/** Remote: rename or pause/resume one key. */
|
|
760
|
+
updateKey(provider: string, keyId: string, patch: unknown): Promise<StateView>;
|
|
761
|
+
/** Remote: delete one key, releasing the provider when it was the last one. */
|
|
762
|
+
deleteKey(provider: string, keyId: string): Promise<StateView>;
|
|
763
|
+
/** Read one live profile for release-time cleanup decisions. */
|
|
764
|
+
private profileAt;
|
|
765
|
+
/** Restore the exact live profile captured before release mutated it. */
|
|
766
|
+
private restoreProfileSnapshot;
|
|
767
|
+
/** Remove every managed credential, recording progress before a retry. */
|
|
768
|
+
private deleteManagedCredentials;
|
|
769
|
+
/** Restore the provider profile and drop management; caller owns the queue. */
|
|
770
|
+
private releaseLocked;
|
|
771
|
+
/** Remote: stop managing a provider and restore its original key reference. */
|
|
772
|
+
releaseProvider(provider: string): Promise<StateView>;
|
|
773
|
+
/** Remote: update manager policy. */
|
|
774
|
+
setSettings(patch: unknown): Promise<StateView>;
|
|
775
|
+
/**
|
|
776
|
+
* Serialize the first selection for a sticky session/provider pair so two
|
|
777
|
+
* concurrent requests cannot advance the cursor and bind different keys.
|
|
778
|
+
*/
|
|
779
|
+
selectKey(providerId: string, sessionId: string | undefined, step?: {
|
|
780
|
+
turn: number;
|
|
781
|
+
step: number;
|
|
782
|
+
}): Promise<KeySelection>;
|
|
783
|
+
/** Pin the key a retry must use, scoped to the failing step. */
|
|
784
|
+
private pinRetry;
|
|
785
|
+
/** Record the turn/step the agent loop is about to request for one provider. */
|
|
786
|
+
noteRequestStep(payload: AgentRequestPayload, providerId: string): void;
|
|
787
|
+
/** Drop one step's retry pin once its attempt settles. */
|
|
788
|
+
private clearRetry;
|
|
789
|
+
/** Selection body; callers reach it through {@link selectKey}. */
|
|
790
|
+
private doSelectKey;
|
|
791
|
+
/** Record a terminal failure without deciding policy yet. */
|
|
792
|
+
private recordFailure;
|
|
793
|
+
/** Record a successful terminal finish. */
|
|
794
|
+
private recordSuccess;
|
|
795
|
+
/**
|
|
796
|
+
* Codex P2: record a projection-served success on the owning account row —
|
|
797
|
+
* `lastUsedAt`/`totalUses` move and the failure streak resets, symmetric to
|
|
798
|
+
* the MF2 failure attribution.
|
|
799
|
+
*/
|
|
800
|
+
private recordProjectionSuccess;
|
|
801
|
+
/** Mark one key cooling, preserving escalation across recovery windows. */
|
|
802
|
+
private markFailure;
|
|
803
|
+
/**
|
|
804
|
+
* Queue one projection demotion for a failed account and publish its
|
|
805
|
+
* promise so the request-error handler can wait for the projection to be
|
|
806
|
+
* gone (round-5 finding C1). `keyId` is the failing row (R7-MF), so the
|
|
807
|
+
* queued body deletes only a projection that still belongs to it. R14-M1:
|
|
808
|
+
* both the cooldown-extension branch and the dedupe branch start the
|
|
809
|
+
* demotion; it is idempotent when the projection is already gone.
|
|
810
|
+
*/
|
|
811
|
+
private demoteProjection;
|
|
812
|
+
/**
|
|
813
|
+
* Wait for any in-flight projection demotion on `providerId` (round-5
|
|
814
|
+
* finding C1). Resolves immediately when nothing is pending, and always
|
|
815
|
+
* resolves — the recorded promise already absorbed its own error.
|
|
816
|
+
*/
|
|
817
|
+
private settleProjectionDemotion;
|
|
818
|
+
/**
|
|
819
|
+
* Whether the key pool can serve a request right now (round-5 finding C1):
|
|
820
|
+
* a selectable non-account row other than `excludeKeyId`. Account rows are
|
|
821
|
+
* excluded because their reference is not broker-resolvable — only a
|
|
822
|
+
* projected grant serves them.
|
|
823
|
+
*/
|
|
824
|
+
private hasFailoverCandidate;
|
|
825
|
+
/** Prune stale attempt records. */
|
|
826
|
+
private pruneAttempts;
|
|
827
|
+
/** Waterfall listener: bind a key to the request and observe its terminal chunk. */
|
|
828
|
+
streamListener(options: GenerateOptionsLike, next: () => AsyncIterable<StreamChunkLike>): AsyncIterable<StreamChunkLike>;
|
|
829
|
+
/**
|
|
830
|
+
* The account row whose grant payload currently IS the live projection,
|
|
831
|
+
* verified against the provider's rows (MF2/MF3 read side). A stale map
|
|
832
|
+
* entry (row removed) resolves to null.
|
|
833
|
+
*/
|
|
834
|
+
private oauthOwnerFor;
|
|
835
|
+
/** Wrap one managed stream so credential resolution sees this key. */
|
|
836
|
+
private wrapStream;
|
|
837
|
+
/**
|
|
838
|
+
* Delegate recovery to the next agent/request-error listener. Preserve the
|
|
839
|
+
* current key only when that listener actually requests a retry; a declined
|
|
840
|
+
* (undefined) recovery is terminal and must still cool the exhausted key.
|
|
841
|
+
*/
|
|
842
|
+
private delegateAndMaybeCool;
|
|
843
|
+
/** Record one failed stream attempt for turn/step attribution. */
|
|
844
|
+
private recordStreamAttempt;
|
|
845
|
+
/** Consume the stream record linked to this exact failure object. */
|
|
846
|
+
private takeLinkedAttempt;
|
|
847
|
+
/** Claim the next unclaimed stream attempt after a step's last sequence. */
|
|
848
|
+
private claimStreamAttempt;
|
|
849
|
+
/**
|
|
850
|
+
* Find a metadata-selectable alternate whose credential actually resolves.
|
|
851
|
+
* A key whose credential was removed outside the manager must not cause the
|
|
852
|
+
* current, working key to be cooled and the session rebound.
|
|
853
|
+
*/
|
|
854
|
+
private findVerifiedAlternate;
|
|
855
|
+
/** Waterfall listener: bounded retry, then key switch and cool-down. */
|
|
856
|
+
requestErrorListener(payload: RequestErrorPayload, next: () => Promise<RequestErrorAction>): Promise<RequestErrorAction>;
|
|
857
|
+
}
|
|
858
|
+
/** Host entry. */
|
|
859
|
+
export declare function apply(ctx: HostContext): void;
|
|
860
|
+
export {};
|