@qilin-agent/cli 3.1.3 → 3.1.5
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.i18n.yaml +14 -14
- package/README.md +12 -3
- package/README.zh.md +12 -3
- package/lib/bin.js +101 -7
- package/lib/tenant-runtime-ChD53FKq.js +489 -0
- package/lib/tenants-roster-Bhr9x2Jw.js +252 -0
- package/lib/types/args.d.ts +10 -1
- package/lib/types/scheduler-yield.d.ts +19 -0
- package/lib/types/tenant-proxy.d.ts +101 -0
- package/lib/types/tenant-runtime.d.ts +338 -0
- package/lib/types/tenants-roster.d.ts +138 -0
- package/lib/types/tenants.d.ts +175 -0
- package/lib/types/usage-ledger.d.ts +143 -0
- package/lib/types/web-entry.d.ts +97 -0
- package/lib/web-entry-C--0C6wG.js +1500 -0
- package/package.json +125 -117
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One tenant's Web runtime: its own Harness home under the deployment home, the
|
|
3
|
+
* child process that serves it, and the launch-token URL a browser follows into
|
|
4
|
+
* it.
|
|
5
|
+
*
|
|
6
|
+
* A tenant is a deployment of its own. The child runs with `QILIN_HOME` set to
|
|
7
|
+
* this tenant's directory, so plugins, model settings, credentials, sessions,
|
|
8
|
+
* storages, attachments, skills, and presets all resolve inside it and nowhere
|
|
9
|
+
* else. The only shared file is the deployment's account roster, which the
|
|
10
|
+
* tenant's home layer points at while admitting exactly its own account.
|
|
11
|
+
* @module @qilin-agent/cli/tenant-runtime
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Resolve every bare module name in the hub's own Node options to an absolute
|
|
15
|
+
* path.
|
|
16
|
+
*
|
|
17
|
+
* The child starts in its own tenant directory, so a bare specifier such as the
|
|
18
|
+
* `tsx/esm` a source launch passes to `--import` resolves against the wrong
|
|
19
|
+
* tree and the child dies before it prints anything. Resolving here keeps the
|
|
20
|
+
* child on the hub's own loader regardless of where it runs.
|
|
21
|
+
* @param execArgv - the hub's `process.execArgv`.
|
|
22
|
+
* @returns the same options with loader values made absolute.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveLoaderArguments(execArgv: readonly string[]): string[];
|
|
25
|
+
/** What one tenant runtime's child process is launched with. */
|
|
26
|
+
export interface TenantLaunch {
|
|
27
|
+
/** Profile the child boots. */
|
|
28
|
+
readonly profile: string;
|
|
29
|
+
/** Interface the child binds. */
|
|
30
|
+
readonly host: string;
|
|
31
|
+
/** Overlay files the child composes with, as absolute paths. */
|
|
32
|
+
readonly patches: readonly string[];
|
|
33
|
+
/** Arguments the served application receives, in argv order. */
|
|
34
|
+
readonly appArguments: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One tenant runtime's arguments, after the Node executable.
|
|
38
|
+
*
|
|
39
|
+
* The launcher stops reading its own flags at the first token it does not own,
|
|
40
|
+
* and `--host` belongs to the served application: everything from there on
|
|
41
|
+
* reaches that application. An overlay belongs to the launcher, so it travels
|
|
42
|
+
* before it, and the deployment's own application arguments follow `--no-open`
|
|
43
|
+
* so a runtime never opens a browser of its own.
|
|
44
|
+
* @param launch - the profile, interface, overlays, and application arguments.
|
|
45
|
+
* @returns argv for one child process.
|
|
46
|
+
*/
|
|
47
|
+
export declare function tenantChildArguments(launch: TenantLaunch): string[];
|
|
48
|
+
/** Lifecycle state of one tenant runtime, as the hub's status read reports it. */
|
|
49
|
+
export type TenantRuntimeState = 'stopped' | 'starting' | 'running' | 'failed';
|
|
50
|
+
/** What one tenant runtime needs from the hub. */
|
|
51
|
+
export interface TenantRuntimeOptions {
|
|
52
|
+
/**
|
|
53
|
+
* Deployment home: it holds the account roster and one directory per tenant,
|
|
54
|
+
* and every tenant runtime's own harness home lives under it.
|
|
55
|
+
*/
|
|
56
|
+
readonly home: string;
|
|
57
|
+
/** Account identity this runtime serves, which also names its tenant directory. */
|
|
58
|
+
readonly accountId: string;
|
|
59
|
+
/**
|
|
60
|
+
* Directory this runtime's harness home lives in, when it is not the account
|
|
61
|
+
* identity: a deployment with no accounts yet has no identity to name one.
|
|
62
|
+
*/
|
|
63
|
+
readonly directoryName?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Whether this runtime exists so a deployment with no accounts can be
|
|
66
|
+
* initialized, which selects the first-run layer it boots with.
|
|
67
|
+
*/
|
|
68
|
+
readonly bootstrap?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Registration policy the first-run runtime applies: a deployment that
|
|
71
|
+
* accepts new accounts lets the product create them there, one that does
|
|
72
|
+
* not admits only the accounts its roster already holds.
|
|
73
|
+
*/
|
|
74
|
+
readonly registration?: 'open' | 'closed';
|
|
75
|
+
/**
|
|
76
|
+
* Whether the browser reaches this runtime through an address that terminates
|
|
77
|
+
* TLS, so its account surface marks every session cookie `Secure`.
|
|
78
|
+
*/
|
|
79
|
+
readonly sessionCookieSecure?: boolean;
|
|
80
|
+
/** Endpoint this runtime reports its account's token spend to, and its token. */
|
|
81
|
+
readonly usage?: {
|
|
82
|
+
readonly url: string;
|
|
83
|
+
readonly token: string;
|
|
84
|
+
} | undefined;
|
|
85
|
+
/** Profile each runtime boots. */
|
|
86
|
+
readonly profile: string;
|
|
87
|
+
/**
|
|
88
|
+
* Overlay files each runtime's composition applies, as absolute paths: the
|
|
89
|
+
* child starts in its own tenant directory, where an operator's relative path
|
|
90
|
+
* names nothing.
|
|
91
|
+
*/
|
|
92
|
+
readonly patches: readonly string[];
|
|
93
|
+
/** Arguments the served application receives, in argv order. */
|
|
94
|
+
readonly appArguments: readonly string[];
|
|
95
|
+
/** Node entry of the qilin CLI the hub re-executes. */
|
|
96
|
+
readonly binPath: string;
|
|
97
|
+
/**
|
|
98
|
+
* Node options the hub itself started with, so the child runs the same way.
|
|
99
|
+
*
|
|
100
|
+
* A source launch reaches the CLI through a loader named by a bare specifier
|
|
101
|
+
* (`--import tsx/esm`). The child starts in its own tenant directory, where
|
|
102
|
+
* that name does not resolve, so the hub resolves every bare loader name to an
|
|
103
|
+
* absolute path before spawning. See {@link resolveLoaderArguments}.
|
|
104
|
+
*/
|
|
105
|
+
readonly execArgv: readonly string[];
|
|
106
|
+
/**
|
|
107
|
+
* Interface this runtime binds. It defaults to loopback, which is what a
|
|
108
|
+
* single-address deployment wants: the hub's address is the only reachable
|
|
109
|
+
* one. A deployment that gives every account an origin of its own names the
|
|
110
|
+
* interface a browser reaches instead.
|
|
111
|
+
*/
|
|
112
|
+
readonly host?: string | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* First wait before retrying a runtime that failed to start. It doubles per
|
|
115
|
+
* consecutive failure, up to {@link BACKOFF_CEILING_MS}.
|
|
116
|
+
*/
|
|
117
|
+
readonly restartBackoffMilliseconds: number;
|
|
118
|
+
/**
|
|
119
|
+
* Where one line of this runtime's own diagnostics goes.
|
|
120
|
+
* @param line - one line the runtime wrote to its standard error.
|
|
121
|
+
*/
|
|
122
|
+
readonly log?: (line: string) => void;
|
|
123
|
+
}
|
|
124
|
+
/** What the hub's status read reports about one tenant runtime. */
|
|
125
|
+
export interface TenantRuntimeStatus {
|
|
126
|
+
/** Account identity this runtime serves. */
|
|
127
|
+
readonly accountId: string;
|
|
128
|
+
/** Lifecycle state. */
|
|
129
|
+
readonly state: TenantRuntimeState;
|
|
130
|
+
/** Process id while a child is running. */
|
|
131
|
+
readonly pid: number | undefined;
|
|
132
|
+
/** Origin the browser is handed to, without the launch token. */
|
|
133
|
+
readonly origin: string | undefined;
|
|
134
|
+
/** How many times this tenant's runtime has started. */
|
|
135
|
+
readonly starts: number;
|
|
136
|
+
/** Why the last start failed, when it did. */
|
|
137
|
+
readonly detail: string;
|
|
138
|
+
/** Consecutive failed starts since the last one that reached its URL. */
|
|
139
|
+
readonly consecutiveFailures: number;
|
|
140
|
+
/** Epoch milliseconds before which a start is refused, after failures. */
|
|
141
|
+
readonly retryAt: number | undefined;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Whether an account identity may name one tenant directory.
|
|
145
|
+
*
|
|
146
|
+
* A leading dot is reserved: the deployment keeps its own directories beside the
|
|
147
|
+
* tenants (`tenants/.usage` holds the token ledger, `tenants/.bootstrap` the
|
|
148
|
+
* first-run runtime), so an identity that read as one would take over the
|
|
149
|
+
* deployment's own bookkeeping instead of a home of its own.
|
|
150
|
+
*
|
|
151
|
+
* The account file is a shared document, so its ids reach path construction in
|
|
152
|
+
* more than one process; an id that is a path segment or names a directory would
|
|
153
|
+
* place a tenant's home somewhere other than under the deployment's own tenants.
|
|
154
|
+
* @param accountId - the identity stored in the roster.
|
|
155
|
+
* @returns true when the identity is one plain directory name.
|
|
156
|
+
*/
|
|
157
|
+
export declare function isTenantIdentity(accountId: string): boolean;
|
|
158
|
+
/** Environment variable naming the deployment home to a tenant runtime. */
|
|
159
|
+
export declare const DEPLOYMENT_HOME_VARIABLE = "QILIN_DEPLOYMENT_HOME";
|
|
160
|
+
/** Environment variable naming the agent-config root (user skills and agent documents). */
|
|
161
|
+
export declare const AGENTS_HOME_VARIABLE = "QILIN_AGENTS_HOME";
|
|
162
|
+
/**
|
|
163
|
+
* Environment variable naming the account one tenant runtime serves.
|
|
164
|
+
*
|
|
165
|
+
* The served document carries it back to the browser, and the client keeps its
|
|
166
|
+
* own local storage inside that account's space: one origin can serve several
|
|
167
|
+
* accounts behind this deployment, so a storage name that does not carry an
|
|
168
|
+
* account would be shared by all of them. Absent for the first-run runtime,
|
|
169
|
+
* which serves no account yet.
|
|
170
|
+
*/
|
|
171
|
+
export declare const ACCOUNT_VARIABLE = "QILIN_ACCOUNT_ID";
|
|
172
|
+
/** Environment variable naming the deployment's usage endpoint, when it has one. */
|
|
173
|
+
export declare const USAGE_URL_VARIABLE = "QILIN_USAGE_URL";
|
|
174
|
+
/**
|
|
175
|
+
* Environment variable carrying the token that endpoint accepts.
|
|
176
|
+
*
|
|
177
|
+
* It identifies the runtime that presents it, so the hub books the report to the
|
|
178
|
+
* account that token was minted for rather than to whatever the body claims.
|
|
179
|
+
*/
|
|
180
|
+
export declare const USAGE_TOKEN_VARIABLE = "QILIN_USAGE_TOKEN";
|
|
181
|
+
/**
|
|
182
|
+
* The environment one tenant runtime starts with.
|
|
183
|
+
*
|
|
184
|
+
* The tenant's harness home is the isolation: every root the runtime resolves
|
|
185
|
+
* comes from `QILIN_HOME`. The agent-config root is the exception that has to be
|
|
186
|
+
* named explicitly, because it defaults to the shared `~/.agents` (or whatever
|
|
187
|
+
* the hub inherited), and user skills and agent documents belong to one account.
|
|
188
|
+
* The deployment home arrives by name so the roster layer can read the shared
|
|
189
|
+
* account file; nothing else of the deployment's is exposed.
|
|
190
|
+
*
|
|
191
|
+
* Credential-shaped names do not cross either: a key the hub was launched with
|
|
192
|
+
* authenticates the hub, and handing it to every account would bill one
|
|
193
|
+
* account's requests to whoever started the deployment. A tenant reaches a
|
|
194
|
+
* provider through its own credential record, or through an entry the
|
|
195
|
+
* deployment layers for that tenant on purpose. The rule is the harness's one
|
|
196
|
+
* credential heuristic; every other inherited name, including the deployment's
|
|
197
|
+
* own `QILIN_*` policy, still crosses.
|
|
198
|
+
* @param tenantDir - the tenant's own harness home.
|
|
199
|
+
* @param deploymentHome - the shared deployment home holding the account roster.
|
|
200
|
+
* The account this runtime serves is named too, so the document it hands the
|
|
201
|
+
* browser can say whose storage the page may use.
|
|
202
|
+
* @param base - the environment the hub itself runs with.
|
|
203
|
+
* @param accountId - the account this runtime serves, or undefined for the
|
|
204
|
+
* first-run runtime that serves no account yet.
|
|
205
|
+
* @param usage - endpoint the runtime reports its account's token spend to, and
|
|
206
|
+
* the token that endpoint accepts; absent when the deployment keeps no ledger.
|
|
207
|
+
* @returns the environment mapping for the child process.
|
|
208
|
+
*/
|
|
209
|
+
export declare function tenantChildEnvironment(tenantDir: string, deploymentHome: string, base?: NodeJS.ProcessEnv, accountId?: string, usage?: {
|
|
210
|
+
readonly url: string;
|
|
211
|
+
readonly token: string;
|
|
212
|
+
}): NodeJS.ProcessEnv;
|
|
213
|
+
/** Addresses that only the machine itself reaches. */
|
|
214
|
+
export declare const LOOPBACK_HOSTS: readonly string[];
|
|
215
|
+
/**
|
|
216
|
+
* Marks the process as one account's runtime rather than the entry that serves
|
|
217
|
+
* the public address. The supervisor sets it for every child it starts; a
|
|
218
|
+
* deployment that starts a single-account runtime directly sets it too.
|
|
219
|
+
*/
|
|
220
|
+
export declare const TENANT_VARIABLE = "QILIN_TENANT";
|
|
221
|
+
/**
|
|
222
|
+
* Layer of the runtime a deployment uses before it has any account.
|
|
223
|
+
*
|
|
224
|
+
* That runtime reads and writes the same roster, and admits whoever the roster
|
|
225
|
+
* holds: the product's own first-run form creates the first account, and the
|
|
226
|
+
* hub then serves it from a runtime of its own.
|
|
227
|
+
* @param secureCookies - whether the browser reaches this deployment over
|
|
228
|
+
* TLS, so its account surface marks session cookies `Secure`.
|
|
229
|
+
* @returns the layer document as YAML.
|
|
230
|
+
*/
|
|
231
|
+
export declare function bootstrapPatchYaml(registration?: 'open' | 'closed', secureCookies?: boolean): string;
|
|
232
|
+
/** Directory name of the runtime a deployment without accounts uses. */
|
|
233
|
+
export declare const BOOTSTRAP_DIRECTORY = ".bootstrap";
|
|
234
|
+
/**
|
|
235
|
+
* One tenant's harness home under the deployment home.
|
|
236
|
+
*
|
|
237
|
+
* The name is one path segment — an account identity, or the first-run
|
|
238
|
+
* directory — so a tenant home is always a sibling of every other one.
|
|
239
|
+
* @param home - deployment home.
|
|
240
|
+
* @param name - account identity or first-run directory name.
|
|
241
|
+
* @returns the tenant's harness home.
|
|
242
|
+
*/
|
|
243
|
+
export declare function tenantDirectory(home: string, name: string): string;
|
|
244
|
+
/**
|
|
245
|
+
* Build one tenant's harness-home layer: the account roster it reads and the
|
|
246
|
+
* single account it admits.
|
|
247
|
+
*
|
|
248
|
+
* The roster stays in the deployment home so one command manages every account,
|
|
249
|
+
* while the tenant's own home holds everything else. The deployment home
|
|
250
|
+
* arrives through the environment rather than as a literal, so a path with
|
|
251
|
+
* quotes or a trailing backslash cannot change the document's meaning.
|
|
252
|
+
* @param accountId - account identity this tenant serves.
|
|
253
|
+
* @param secureCookies - whether the browser reaches this deployment over
|
|
254
|
+
* TLS, so its account surface marks session cookies `Secure`.
|
|
255
|
+
* @returns the layer document as YAML.
|
|
256
|
+
* @throws Error when the identity is not usable as one tenant directory name.
|
|
257
|
+
*/
|
|
258
|
+
export declare function tenantPatchYaml(accountId: string, secureCookies?: boolean): string;
|
|
259
|
+
/** One tenant's Web runtime, started on demand and reclaimed when idle. */
|
|
260
|
+
/**
|
|
261
|
+
* What a deployment supervisor needs from one account's runtime: the handoff
|
|
262
|
+
* clock, the lifecycle facts its status read reports, starting and stopping it,
|
|
263
|
+
* and the origin to forward a browser request to.
|
|
264
|
+
*
|
|
265
|
+
* {@link TenantRuntime} is the process-spawning implementation. A caller that
|
|
266
|
+
* owns the process boundary — a deployment embedding the supervisor, or a test
|
|
267
|
+
* driving the routing, the cap, and the handoffs — supplies its own.
|
|
268
|
+
*/
|
|
269
|
+
export interface TenantHandle {
|
|
270
|
+
/** Epoch milliseconds of the last browser handoff; idle reclamation measures it. */
|
|
271
|
+
lastUsedAt: number;
|
|
272
|
+
/** Lifecycle and diagnostic facts the status read reports. */
|
|
273
|
+
status(): TenantRuntimeStatus;
|
|
274
|
+
/** Start this runtime if it is not serving, resolving once it answers its URL. */
|
|
275
|
+
ensure(): Promise<string>;
|
|
276
|
+
/** Stop it and wait until it is gone. */
|
|
277
|
+
stop(): Promise<void>;
|
|
278
|
+
/** Origin to forward one browser request to, when it is serving. */
|
|
279
|
+
proxyTarget(): string | undefined;
|
|
280
|
+
}
|
|
281
|
+
export declare class TenantRuntime implements TenantHandle {
|
|
282
|
+
private readonly options;
|
|
283
|
+
private child;
|
|
284
|
+
private exited;
|
|
285
|
+
private launchUrl;
|
|
286
|
+
private state;
|
|
287
|
+
private detail;
|
|
288
|
+
private output;
|
|
289
|
+
private starting;
|
|
290
|
+
private consecutiveFailures;
|
|
291
|
+
private retryAt;
|
|
292
|
+
private intentional;
|
|
293
|
+
/** Epoch milliseconds of the last browser handoff; idle reclamation measures it. */
|
|
294
|
+
lastUsedAt: number;
|
|
295
|
+
/** How many times this tenant's runtime has started. */
|
|
296
|
+
starts: number;
|
|
297
|
+
/**
|
|
298
|
+
* @param options - the shared home, the account this runtime serves, and how to start it.
|
|
299
|
+
*/
|
|
300
|
+
constructor(options: TenantRuntimeOptions);
|
|
301
|
+
/**
|
|
302
|
+
* Where the hub sends this runtime's traffic.
|
|
303
|
+
*
|
|
304
|
+
* The runtime's own URL carries a launch token, which the hub never spends:
|
|
305
|
+
* every request keeps the path the browser asked for, so the application's
|
|
306
|
+
* account gate answers it. Only the origin is a target.
|
|
307
|
+
* @returns the runtime's origin, or undefined when it is not serving yet.
|
|
308
|
+
*/
|
|
309
|
+
proxyTarget(): string | undefined;
|
|
310
|
+
/** Absolute path of this tenant's harness-home layer. */
|
|
311
|
+
get patchPath(): string;
|
|
312
|
+
/**
|
|
313
|
+
* This tenant's harness home: everything the runtime owns lives under it,
|
|
314
|
+
* and the deployment home holds only the roster and these directories.
|
|
315
|
+
*/
|
|
316
|
+
get tenantDir(): string;
|
|
317
|
+
/** What the hub's status read reports about this tenant. */
|
|
318
|
+
status(): TenantRuntimeStatus;
|
|
319
|
+
/**
|
|
320
|
+
* Return this tenant's launch URL, starting the runtime when it is not serving.
|
|
321
|
+
* @returns the launch-token URL of the running runtime.
|
|
322
|
+
* @throws Error when the runtime did not become ready; the state then reports `failed`.
|
|
323
|
+
*/
|
|
324
|
+
ensure(): Promise<string>;
|
|
325
|
+
/**
|
|
326
|
+
* Count one failed start or one crash of a runtime that had been serving, and
|
|
327
|
+
* schedule the earliest time the next attempt may run.
|
|
328
|
+
* @param reason - what to report as the current detail.
|
|
329
|
+
*/
|
|
330
|
+
private recordFailure;
|
|
331
|
+
/** Ask this tenant's runtime to exit, holding it to the shutdown deadline. */
|
|
332
|
+
stop(): Promise<void>;
|
|
333
|
+
/** Start one child, wait for its URL, and keep its handles. */
|
|
334
|
+
private start;
|
|
335
|
+
/** Write this tenant's home layer when the file does not already hold it. */
|
|
336
|
+
private writePatch;
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=tenant-runtime.d.ts.map
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `qilin tenants` roster operations: the account file every tenant runtime on
|
|
3
|
+
* this Harness home shares, and the tenant directory each account owns.
|
|
4
|
+
*
|
|
5
|
+
* A tenant runtime admits exactly the account it serves, so a new account
|
|
6
|
+
* cannot be created through a tenant's own Web surface (its registration is
|
|
7
|
+
* closed). These commands are how a deployment adds, re-keys, and retires the
|
|
8
|
+
* accounts its tenant runtimes serve.
|
|
9
|
+
* @module @qilin-agent/cli/tenants-roster
|
|
10
|
+
*/
|
|
11
|
+
import { type AccountRecord } from '@qilin-agent/accounts-local';
|
|
12
|
+
/** One roster row: one account and the tenant directory it owns. */
|
|
13
|
+
export interface RosterEntry {
|
|
14
|
+
/** Opaque account identity, which also names the tenant directory. */
|
|
15
|
+
readonly id: string;
|
|
16
|
+
/** Login name. */
|
|
17
|
+
readonly username: string;
|
|
18
|
+
/** Stored address, or null. */
|
|
19
|
+
readonly email: string | null;
|
|
20
|
+
/** Epoch milliseconds when the account was created. */
|
|
21
|
+
readonly createdAt: number;
|
|
22
|
+
/** Absolute tenant directory under the shared home. */
|
|
23
|
+
readonly tenantDirectory: string;
|
|
24
|
+
/** Whether that directory exists, which is whether the account ever ran. */
|
|
25
|
+
readonly tenantDataPresent: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** What one roster mutation was asked to do. */
|
|
28
|
+
export interface AddAccountRequest {
|
|
29
|
+
/** Login name, normalized before validation. */
|
|
30
|
+
readonly username: string;
|
|
31
|
+
/** Address, normalized before validation; omitted stores none. */
|
|
32
|
+
readonly email?: string | undefined;
|
|
33
|
+
/** Plaintext password, validated against the shared minimum length. */
|
|
34
|
+
readonly password: string;
|
|
35
|
+
/** Create this deployment's administrator, which may already have one. */
|
|
36
|
+
readonly admin?: boolean | undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Where a mutation reads the password it applies. */
|
|
39
|
+
export type PasswordSource = {
|
|
40
|
+
readonly kind: 'prompt';
|
|
41
|
+
} | {
|
|
42
|
+
readonly kind: 'stdin';
|
|
43
|
+
};
|
|
44
|
+
/** One roster subcommand of `qilin tenants`. */
|
|
45
|
+
export type RosterRequest = {
|
|
46
|
+
readonly command: 'accounts';
|
|
47
|
+
} | {
|
|
48
|
+
readonly command: 'add';
|
|
49
|
+
readonly username: string;
|
|
50
|
+
readonly email: string | undefined;
|
|
51
|
+
readonly admin: boolean;
|
|
52
|
+
readonly password: PasswordSource;
|
|
53
|
+
} | {
|
|
54
|
+
readonly command: 'passwd';
|
|
55
|
+
readonly username: string;
|
|
56
|
+
readonly password: PasswordSource;
|
|
57
|
+
} | {
|
|
58
|
+
readonly command: 'remove';
|
|
59
|
+
readonly username: string;
|
|
60
|
+
readonly purgeData: boolean;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Every account of one shared home with the tenant directory it owns.
|
|
64
|
+
* @param home - absolute shared Harness home.
|
|
65
|
+
* @returns one entry per account, oldest first.
|
|
66
|
+
*/
|
|
67
|
+
export declare function rosterEntries(home: string): Promise<RosterEntry[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Create one account in the shared roster.
|
|
70
|
+
* @param home - absolute shared Harness home.
|
|
71
|
+
* @param request - the login name, address, password, and administrator seat.
|
|
72
|
+
* @returns the created account.
|
|
73
|
+
* @throws Error when the name or address is refused or already taken, or when
|
|
74
|
+
* the deployment already has an administrator.
|
|
75
|
+
*/
|
|
76
|
+
export declare function addAccount(home: string, request: AddAccountRequest): Promise<AccountRecord>;
|
|
77
|
+
/**
|
|
78
|
+
* Replace one account's password, retiring the sessions the previous one issued.
|
|
79
|
+
* @param home - absolute shared Harness home.
|
|
80
|
+
* @param username - the account's login name, normalized before lookup.
|
|
81
|
+
* @param password - the replacement password.
|
|
82
|
+
* @returns the stored successor.
|
|
83
|
+
* @throws Error when no account has that name or the password is too short.
|
|
84
|
+
*/
|
|
85
|
+
export declare function setAccountPassword(home: string, username: string, password: string): Promise<AccountRecord>;
|
|
86
|
+
/** What one removal did. */
|
|
87
|
+
export interface RemovalResult {
|
|
88
|
+
/** The removed account. */
|
|
89
|
+
readonly account: AccountRecord;
|
|
90
|
+
/** Tenant directory removed with `--purge-data`, when one existed. */
|
|
91
|
+
readonly purgedDirectory: string | undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Remove one account from the shared roster.
|
|
95
|
+
*
|
|
96
|
+
* A running tenant runtime keeps serving the account until it is reclaimed or
|
|
97
|
+
* restarted; a runtime started afterwards fails loud, because the overlay
|
|
98
|
+
* admits an account the roster no longer holds.
|
|
99
|
+
* @param home - absolute shared Harness home.
|
|
100
|
+
* @param username - the account's login name, normalized before lookup.
|
|
101
|
+
* @param purgeData - also remove that account's tenant directory.
|
|
102
|
+
* @returns the removed account and, when asked, the removed directory.
|
|
103
|
+
* @throws Error when no account has that name, or when the tenant directory is not where it belongs.
|
|
104
|
+
*/
|
|
105
|
+
export declare function removeAccount(home: string, username: string, purgeData: boolean): Promise<RemovalResult>;
|
|
106
|
+
/**
|
|
107
|
+
* Read the password one mutation applies.
|
|
108
|
+
* @param source - whether to prompt or read standard input.
|
|
109
|
+
* @param label - the account being changed, named in the confirmation prompt.
|
|
110
|
+
* @returns the password.
|
|
111
|
+
* @throws Error when an interactive entry is not confirmed, or when standard input is a terminal without a prompt.
|
|
112
|
+
*/
|
|
113
|
+
export declare function readPassword(source: PasswordSource, label: string): Promise<string>;
|
|
114
|
+
/**
|
|
115
|
+
* Print the roster of one shared home.
|
|
116
|
+
* @param home - absolute shared Harness home.
|
|
117
|
+
* @param write - where each line goes.
|
|
118
|
+
* @returns the printed entries.
|
|
119
|
+
*/
|
|
120
|
+
export declare function printRoster(home: string, write: (line: string) => void): Promise<RosterEntry[]>;
|
|
121
|
+
/**
|
|
122
|
+
* Create the tenant directory one account owns, so its first runtime starts in
|
|
123
|
+
* a directory that exists even before a browser visits it.
|
|
124
|
+
* @param home - absolute shared Harness home.
|
|
125
|
+
* @param accountId - the account whose directory to create.
|
|
126
|
+
* @returns the absolute directory.
|
|
127
|
+
*/
|
|
128
|
+
export declare function ensureTenantDirectory(home: string, accountId: string): Promise<string>;
|
|
129
|
+
/**
|
|
130
|
+
* Run one roster subcommand over this Harness home and print what it did.
|
|
131
|
+
*
|
|
132
|
+
* Failures are reported as a message and a non-zero exit code rather than a
|
|
133
|
+
* stack trace: every refusal here is something the operator can correct.
|
|
134
|
+
* @param request - the roster subcommand and its arguments.
|
|
135
|
+
* @returns the process exit code: 0 when the request was carried out, 1 when it was refused.
|
|
136
|
+
*/
|
|
137
|
+
export declare function runTenantsRoster(request: RosterRequest): Promise<number>;
|
|
138
|
+
//# sourceMappingURL=tenants-roster.d.ts.map
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `qilin tenants serve`: one hub over a shared Harness home, one isolated Web
|
|
3
|
+
* runtime per account. Sessions, tasks, and workspaces stay with the account
|
|
4
|
+
* that owns them; plugins, model settings, credentials, and the account file
|
|
5
|
+
* stay shared, so every tenant runtime is that one deployment.
|
|
6
|
+
*
|
|
7
|
+
* The hub authenticates nobody: a handoff is a redirect, not a credential.
|
|
8
|
+
* Each tenant runtime keeps the shared home's account gate and admits only its
|
|
9
|
+
* own account, so the sign-in happens at the runtime and the runtime is the
|
|
10
|
+
* boundary. The hub must be reachable where its account list is not a secret —
|
|
11
|
+
* it binds loopback by default and lists usernames.
|
|
12
|
+
*
|
|
13
|
+
* Idle reclamation counts from the last handoff. A browser that reached a tenant
|
|
14
|
+
* origin keeps talking to that origin directly, which the hub cannot observe, so
|
|
15
|
+
* a deployment that leaves long-lived tabs open raises `--idle-ms` or disables
|
|
16
|
+
* reclamation with 0; `--single-origin` moves that same traffic through the hub
|
|
17
|
+
* instead, and reclamation then counts proxied requests.
|
|
18
|
+
*
|
|
19
|
+
* With `--single-origin` the hub serves one origin and routes by a routing
|
|
20
|
+
* cookie it sets at the handoff: `/api`, the mux upgrade, and media requests are
|
|
21
|
+
* absolute paths on the page origin, so only cookie routing can attribute them
|
|
22
|
+
* to a tenant. The cookie selects a runtime, it does not authorize: the runtime
|
|
23
|
+
* still answers with its own account gate, so a forged cookie reaches a
|
|
24
|
+
* runtime that does not recognize the caller.
|
|
25
|
+
*
|
|
26
|
+
* `--max-tenants` bounds how many runtimes live at once: a handoff that would
|
|
27
|
+
* exceed the bound reclaims the least recently used one, and every tenant's
|
|
28
|
+
* Sessions stay on disk. A runtime that fails to start is not retried on every
|
|
29
|
+
* visit: the hub doubles a wait per consecutive failure, and `/status` reports
|
|
30
|
+
* the count and the earliest retry.
|
|
31
|
+
* @module @qilin-agent/cli/tenants
|
|
32
|
+
*/
|
|
33
|
+
import { type TenantHandle, type TenantRuntimeOptions } from './tenant-runtime.ts';
|
|
34
|
+
/** Certificate and key the public address terminates TLS with. */
|
|
35
|
+
export interface DeploymentTlsFiles {
|
|
36
|
+
/** PEM certificate chain file. */
|
|
37
|
+
readonly certFile: string;
|
|
38
|
+
/** Unencrypted PEM private key file. */
|
|
39
|
+
readonly keyFile: string;
|
|
40
|
+
}
|
|
41
|
+
/** What `qilin tenants serve` was asked to do. */
|
|
42
|
+
export interface TenantsServeOptions {
|
|
43
|
+
/** Interface the hub binds. */
|
|
44
|
+
readonly host: string;
|
|
45
|
+
/** Port the hub binds; 0 picks a free one. */
|
|
46
|
+
readonly port: number;
|
|
47
|
+
/** Profile each tenant runtime boots. */
|
|
48
|
+
readonly profile: string;
|
|
49
|
+
/** Idle window after which a tenant runtime is reclaimed. */
|
|
50
|
+
readonly idleMilliseconds: number;
|
|
51
|
+
/**
|
|
52
|
+
* Most tenant runtimes the hub keeps alive at once. 0 keeps every one;
|
|
53
|
+
* starting a runtime past a positive count reclaims the least recently used.
|
|
54
|
+
*/
|
|
55
|
+
readonly maxTenants: number;
|
|
56
|
+
/**
|
|
57
|
+
* First wait before retrying a runtime that failed to start; one runtime
|
|
58
|
+
* doubles it per consecutive failure. {@link TenantRuntime} owns the ceiling.
|
|
59
|
+
*/
|
|
60
|
+
readonly restartBackoffMilliseconds: number;
|
|
61
|
+
/**
|
|
62
|
+
* Serve every tenant through the hub's own origin instead of redirecting to a
|
|
63
|
+
* runtime's port: the hub routes each request to the tenant its routing
|
|
64
|
+
* cookie names, so one browser drives one tenant at a time, and reclamation
|
|
65
|
+
* counts real traffic rather than a handoff.
|
|
66
|
+
*/
|
|
67
|
+
readonly singleOrigin: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Whether the deployment lets the product create accounts beyond the
|
|
70
|
+
* roster's first. Closed admits only the accounts the roster holds, so the
|
|
71
|
+
* operator creates them with `qilin tenants add`.
|
|
72
|
+
*/
|
|
73
|
+
readonly registration: 'open' | 'closed';
|
|
74
|
+
/**
|
|
75
|
+
* Overlay files each runtime composes with, as absolute paths: a child starts
|
|
76
|
+
* in its own tenant directory, whatever directory the operator launched from.
|
|
77
|
+
*/
|
|
78
|
+
readonly patches: readonly string[];
|
|
79
|
+
/** Arguments the served application receives, in argv order. */
|
|
80
|
+
readonly appArguments: readonly string[];
|
|
81
|
+
/**
|
|
82
|
+
* TLS the public address terminates with. Account runtimes stay on loopback
|
|
83
|
+
* and speak plain HTTP behind it, which is why this is only served when every
|
|
84
|
+
* account is reached through this address.
|
|
85
|
+
*/
|
|
86
|
+
readonly tls?: DeploymentTlsFiles | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* Interface every tenant runtime binds. It defaults to loopback, which is the
|
|
89
|
+
* only reachable interface when {@link singleOrigin} is set: the hub's
|
|
90
|
+
* address is the deployment's only address. A deployment that gives every
|
|
91
|
+
* account an origin of its own names the interface a browser reaches, and
|
|
92
|
+
* each account's cookies then live in their own jar.
|
|
93
|
+
*/
|
|
94
|
+
readonly tenantHost?: string | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* Called once the address is serving, with the address itself, so the caller
|
|
97
|
+
* that owns the deployment's user-facing behavior can act on it.
|
|
98
|
+
*/
|
|
99
|
+
readonly onListening?: ((url: string) => void) | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Deployment home holding the roster and every tenant directory. Defaults to
|
|
102
|
+
* {@link resolveQilinHome}, which is the home the process was started with.
|
|
103
|
+
*/
|
|
104
|
+
readonly home?: string | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* How one account's runtime is created. Defaults to {@link TenantRuntime},
|
|
107
|
+
* which spawns a child process; a caller that owns the process boundary
|
|
108
|
+
* supplies its own implementation of {@link TenantHandle}.
|
|
109
|
+
*/
|
|
110
|
+
readonly createRuntime?: ((options: TenantRuntimeOptions) => TenantHandle) | undefined;
|
|
111
|
+
/**
|
|
112
|
+
* Aborting this ends the deployment the way a termination signal does: the
|
|
113
|
+
* hub stops accepting, every runtime stops, and the returned promise resolves
|
|
114
|
+
* with 0.
|
|
115
|
+
*/
|
|
116
|
+
readonly signal?: AbortSignal | undefined;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Whether every runtime of this deployment must mark its session cookies
|
|
120
|
+
* `Secure`.
|
|
121
|
+
*
|
|
122
|
+
* The browser reaches the deployment through the hub, so a deployment that
|
|
123
|
+
* terminates TLS itself is the one case where the client's own connection is
|
|
124
|
+
* encrypted; a runtime behind a plain-HTTP hub cannot know otherwise, and it
|
|
125
|
+
* never guesses.
|
|
126
|
+
* @param options - the deployment's own TLS material, when it terminates TLS.
|
|
127
|
+
* @returns true only when this deployment terminates TLS.
|
|
128
|
+
*/
|
|
129
|
+
export declare function secureSessionCookies(options: {
|
|
130
|
+
readonly tls?: DeploymentTlsFiles | undefined;
|
|
131
|
+
}): boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Serve the hub until it is asked to stop.
|
|
134
|
+
* @param options - the interface, port, tenant profile, and idle window.
|
|
135
|
+
* @returns the process exit code, 0 after a clean shutdown.
|
|
136
|
+
*/
|
|
137
|
+
/**
|
|
138
|
+
* What the deployment's own address and its tenant interface mean for reaching
|
|
139
|
+
* them, as the lines a start prints.
|
|
140
|
+
*
|
|
141
|
+
* A hub on loopback is reachable from this machine only, and account runtimes
|
|
142
|
+
* bind loopback unless `--tenant-host` names another interface. Naming a
|
|
143
|
+
* reachable one exposes every runtime there directly — plain HTTP, no entry in
|
|
144
|
+
* between — which an operator has to know before treating the hub address as the
|
|
145
|
+
* only door.
|
|
146
|
+
* @param host - interface the entry bound.
|
|
147
|
+
* @param tenantHost - interface each account runtime binds, when the invocation named one.
|
|
148
|
+
* @param port - port the entry bound.
|
|
149
|
+
* @returns one line per fact the operator needs, in print order.
|
|
150
|
+
*/
|
|
151
|
+
export declare function addressNotices(host: string, tenantHost: string | undefined, port: number): string[];
|
|
152
|
+
/**
|
|
153
|
+
* The runtime a handoff stops to make room, or undefined when there is room or
|
|
154
|
+
* nothing may be reclaimed yet.
|
|
155
|
+
*
|
|
156
|
+
* Only a running runtime is a candidate: one that is still starting has no last
|
|
157
|
+
* use to compare, so the next handoff reclaims it once it is serving. The
|
|
158
|
+
* runtime about to start is never its own candidate.
|
|
159
|
+
* @param live - every runtime holding a child, in any state.
|
|
160
|
+
* @param target - the runtime about to start.
|
|
161
|
+
* @param maxTenants - cap on live runtimes; zero or less means no cap.
|
|
162
|
+
* @returns the runtime to stop, if any.
|
|
163
|
+
*/
|
|
164
|
+
export declare function reclaimFor(live: readonly TenantHandle[], target: TenantHandle, maxTenants: number): TenantHandle | undefined;
|
|
165
|
+
/**
|
|
166
|
+
* One queue that runs handoffs one at a time.
|
|
167
|
+
*
|
|
168
|
+
* Two handoffs that passed the cap together would both start a runtime, so each
|
|
169
|
+
* handoff's make-room-and-start runs behind the previous one. A handoff that
|
|
170
|
+
* fails settles its own promise and leaves the queue ready for the next.
|
|
171
|
+
* @returns a function that queues one handoff.
|
|
172
|
+
*/
|
|
173
|
+
export declare function createHandoffQueue(): <T>(work: () => Promise<T>) => Promise<T>;
|
|
174
|
+
export declare function runTenantsServe(options: TenantsServeOptions): Promise<number>;
|
|
175
|
+
//# sourceMappingURL=tenants.d.ts.map
|