@zenera/server 1.2.0 → 1.2.2
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.md +204 -231
- package/dist/auth.d.ts +2 -0
- package/dist/config.d.ts +15 -1
- package/dist/config.js +4 -0
- package/dist/http.js +5 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/plugins.d.ts +19 -0
- package/dist/plugins.js +47 -0
- package/dist/runs.d.ts +8 -0
- package/dist/runs.js +17 -0
- package/dist/server.d.ts +5 -2
- package/dist/server.js +21 -9
- package/dist/status.d.ts +7 -0
- package/dist/status.js +7 -0
- package/dist/store.d.ts +4 -0
- package/dist/store.js +4 -1
- package/dist/userState.d.ts +99 -0
- package/dist/userState.js +461 -0
- package/package.json +4 -4
- package/dist/state.d.ts +0 -24
- package/dist/state.js +0 -55
- package/dist/token.d.ts +0 -14
- package/dist/token.js +0 -30
package/dist/plugins.js
CHANGED
|
@@ -7,6 +7,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
9
|
import { pathToFileURL } from 'node:url';
|
|
10
|
+
import { DEFAULTS } from "./config.js";
|
|
11
|
+
import { USER_LAYOUT } from "./userState.js";
|
|
10
12
|
export function envRunPlugins(env = process.env) {
|
|
11
13
|
const plugin = (env.ZEN_PLUGINS ?? '')
|
|
12
14
|
.split(',')
|
|
@@ -26,4 +28,49 @@ export function envRunPlugins(env = process.env) {
|
|
|
26
28
|
return mcpPlugin ? [mcpPlugin({ brokerUrl, token: mcpToken })] : [];
|
|
27
29
|
};
|
|
28
30
|
}
|
|
31
|
+
/** The store `ZEN_STATE_STORE` names, made and checked; a broken one stops the server starting. */
|
|
32
|
+
export async function envStateStore(env, at) {
|
|
33
|
+
const path = env.ZEN_STATE_STORE?.trim();
|
|
34
|
+
if (!path)
|
|
35
|
+
return undefined;
|
|
36
|
+
const mod = (await import(__rewriteRelativeImportExtension(pathToFileURL(path).href)));
|
|
37
|
+
if (typeof mod.userStateStore !== 'function') {
|
|
38
|
+
throw new Error(`${path} exports no userStateStore(options)`);
|
|
39
|
+
}
|
|
40
|
+
const store = await mod.userStateStore({
|
|
41
|
+
...at,
|
|
42
|
+
layout: USER_LAYOUT,
|
|
43
|
+
env,
|
|
44
|
+
log: {
|
|
45
|
+
info: (message) => console.log(`[state] ${message}`),
|
|
46
|
+
warn: (message) => console.warn(`[state] ${message}`),
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
const ok = store &&
|
|
50
|
+
typeof store.name === 'string' &&
|
|
51
|
+
typeof store.durable === 'boolean' &&
|
|
52
|
+
typeof store.load === 'function' &&
|
|
53
|
+
typeof store.checkpoint === 'function';
|
|
54
|
+
if (!ok)
|
|
55
|
+
throw new Error(`${path}: userStateStore() did not return a store`);
|
|
56
|
+
return store;
|
|
57
|
+
}
|
|
58
|
+
function whole(text, fallback, max = Number.MAX_SAFE_INTEGER) {
|
|
59
|
+
const n = Number(text?.trim());
|
|
60
|
+
return text?.trim() && Number.isInteger(n) && n >= 0 ? Math.min(n, max) : fallback;
|
|
61
|
+
}
|
|
62
|
+
const MB = 2 ** 20;
|
|
63
|
+
export function envServerLimits(env) {
|
|
64
|
+
const limits = DEFAULTS.commandLimits;
|
|
65
|
+
return {
|
|
66
|
+
checkpointSeconds: whole(env.ZEN_STATE_CHECKPOINT_SECONDS, DEFAULTS.checkpointSeconds),
|
|
67
|
+
systemSubject: env.ZEN_SERVER_SYSTEM_SUBJECT?.trim() || DEFAULTS.systemSubject,
|
|
68
|
+
userDiskQuotaBytes: whole(env.ZEN_RUNTIME_USER_DISK_MB, 0) * MB,
|
|
69
|
+
commandLimits: {
|
|
70
|
+
nice: whole(env.ZEN_RUNTIME_NICE, limits.nice ?? 0, 19),
|
|
71
|
+
fileSizeBytes: whole(env.ZEN_RUNTIME_MAX_FILE_MB, 0) * MB,
|
|
72
|
+
tmpBytes: whole(env.ZEN_RUNTIME_TMP_MB, 0) * MB,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
29
76
|
//# sourceMappingURL=plugins.js.map
|
package/dist/runs.d.ts
CHANGED
|
@@ -4,6 +4,14 @@ export declare function activeRuns(): number;
|
|
|
4
4
|
export declare function currentRun(t: ChatThread): {
|
|
5
5
|
runId: string;
|
|
6
6
|
} | null;
|
|
7
|
+
/** One owner's runs going now, with the thread each is on and when it ends. */
|
|
8
|
+
export declare function ownerRuns(owner: string): {
|
|
9
|
+
threadId: string;
|
|
10
|
+
runId: string;
|
|
11
|
+
done: Promise<void>;
|
|
12
|
+
}[];
|
|
13
|
+
/** Ends one owner's runs as interrupted, saying why; resolves once they have saved. */
|
|
14
|
+
export declare function stopOwnerRuns(owner: string, message: string): Promise<void>;
|
|
7
15
|
/** Runs may start again: a new server in a process whose previous one stopped. */
|
|
8
16
|
export declare function acceptRuns(): void;
|
|
9
17
|
export declare function stopAllRuns(timeoutMs: number): Promise<void>;
|
package/dist/runs.js
CHANGED
|
@@ -32,6 +32,19 @@ export function currentRun(t) {
|
|
|
32
32
|
const run = runs.get(t.id);
|
|
33
33
|
return run && run.owner === t.owner ? { runId: run.runId } : null;
|
|
34
34
|
}
|
|
35
|
+
/** One owner's runs going now, with the thread each is on and when it ends. */
|
|
36
|
+
export function ownerRuns(owner) {
|
|
37
|
+
return [...runs.entries()]
|
|
38
|
+
.filter(([, run]) => run.owner === owner)
|
|
39
|
+
.map(([threadId, run]) => ({ threadId, runId: run.runId, done: run.done }));
|
|
40
|
+
}
|
|
41
|
+
/** Ends one owner's runs as interrupted, saying why; resolves once they have saved. */
|
|
42
|
+
export async function stopOwnerRuns(owner, message) {
|
|
43
|
+
const mine = [...runs.values()].filter((run) => run.owner === owner);
|
|
44
|
+
for (const run of mine)
|
|
45
|
+
run.controller.abort(new RunStop('interrupted', message));
|
|
46
|
+
await Promise.all(mine.map((run) => run.done));
|
|
47
|
+
}
|
|
35
48
|
// The newest user message of an AG-UI RunAgentInput; the thread's history is the runtime's own.
|
|
36
49
|
function inputOf(body) {
|
|
37
50
|
const messages = body?.messages;
|
|
@@ -236,6 +249,7 @@ async function execute(store, t, run, turn, memory, user, mcpToken) {
|
|
|
236
249
|
}
|
|
237
250
|
await log.end().catch((err) => console.error(`run ${runId}: saving its log:`, err));
|
|
238
251
|
runs.delete(t.id);
|
|
252
|
+
store.changed?.(t, 'run-end');
|
|
239
253
|
}
|
|
240
254
|
}
|
|
241
255
|
/** Runs may start again: a new server in a process whose previous one stopped. */
|
|
@@ -260,6 +274,8 @@ export function runsRouter(store) {
|
|
|
260
274
|
const resume = resumeOf(req.body);
|
|
261
275
|
const turn = resume ? { resume } : { message: inputOf(req.body) };
|
|
262
276
|
const runId = ID.test(req.body?.runId ?? '') ? String(req.body.runId) : randomUUID();
|
|
277
|
+
// Over its storage quota, a user gets no new run (checked before the slot is taken).
|
|
278
|
+
await store.admit?.(t);
|
|
263
279
|
if (stopping) {
|
|
264
280
|
res.set('Retry-After', '5');
|
|
265
281
|
throw new HttpError(503, 'the server is stopping, try again shortly');
|
|
@@ -345,6 +361,7 @@ export function runsRouter(store) {
|
|
|
345
361
|
await run.done;
|
|
346
362
|
}
|
|
347
363
|
await store.removeThread(t);
|
|
364
|
+
store.changed?.(t, 'changed');
|
|
348
365
|
res.status(204).end();
|
|
349
366
|
});
|
|
350
367
|
return router;
|
package/dist/server.d.ts
CHANGED
|
@@ -14,8 +14,11 @@ export interface AgentServer {
|
|
|
14
14
|
app: Express;
|
|
15
15
|
settings: ServerSettings;
|
|
16
16
|
listen(on: ListenOn): Promise<Server>;
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Stops taking runs, ends the running ones as interrupted (they save), saves every user
|
|
19
|
+
* (within `saveMs`), closes the listener.
|
|
20
|
+
*/
|
|
21
|
+
stop(graceMs?: number, saveMs?: number): Promise<void>;
|
|
19
22
|
}
|
|
20
23
|
export declare function createServer(opts: ServerOptions): AgentServer;
|
|
21
24
|
/** The env `zen server start --container` sets on the container it runs, port on loopback. */
|
package/dist/server.js
CHANGED
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
// GET /api/sessions - the caller's sessions, newest activity first
|
|
12
12
|
// /api/sessions/:tid... - history, runs (start, follow, replay, cancel), delete
|
|
13
13
|
// /api/sessions/:tid/{runs,trace,memory}... with `debug` - the Test tab's views of a chat
|
|
14
|
-
//
|
|
14
|
+
// /api/admin/{users,users/:sub/release,drain,undrain} - the host's view of this server's users
|
|
15
|
+
// Every /api/sessions request first has the caller's data loaded (userState.ts): a store plugin
|
|
16
|
+
// may keep users elsewhere and move them between servers; the token's `gen` is their lease.
|
|
17
|
+
// createServer - the app, configured; `listen` recovers users and starts it, `stop` ends runs,
|
|
18
|
+
// saves every user and closes it
|
|
15
19
|
import express, {} from 'express';
|
|
16
20
|
import { configure, DEFAULTS } from "./config.js";
|
|
17
21
|
import { historyRouter } from "./history.js";
|
|
@@ -26,6 +30,7 @@ import { serverStatus, setListening } from "./status.js";
|
|
|
26
30
|
import { privateStore } from "./store.js";
|
|
27
31
|
import { sessionsRouter } from "./sessions.js";
|
|
28
32
|
import { traceRouter } from "./trace.js";
|
|
33
|
+
import { loadCaller, loadThread, userStateRouter, userStates } from "./userState.js";
|
|
29
34
|
export function createServer(opts) {
|
|
30
35
|
const { chatPage, ...runtime } = opts;
|
|
31
36
|
const settings = { ...DEFAULTS, ...runtime, hidden: [...(opts.hidden ?? [])] };
|
|
@@ -58,6 +63,9 @@ export function createServer(opts) {
|
|
|
58
63
|
app.get('/api/activity', (_req, res) => {
|
|
59
64
|
res.json({ busy: activeRuns() > 0, runs: activeRuns() });
|
|
60
65
|
});
|
|
66
|
+
app.use('/api/admin', userStateRouter());
|
|
67
|
+
app.use('/api/sessions', loadCaller);
|
|
68
|
+
app.use('/api/sessions/:tid', loadThread);
|
|
61
69
|
app.use('/api/sessions', sessionsRouter());
|
|
62
70
|
app.use('/api/sessions', runsRouter(privateStore));
|
|
63
71
|
app.use('/api/sessions', historyRouter(privateStore));
|
|
@@ -72,19 +80,23 @@ export function createServer(opts) {
|
|
|
72
80
|
return {
|
|
73
81
|
app,
|
|
74
82
|
settings,
|
|
75
|
-
listen: ({ port, host = '127.0.0.1' }) =>
|
|
83
|
+
listen: async ({ port, host = '127.0.0.1' }) => {
|
|
76
84
|
if (settings.auth.mode === 'insecure' && !LOOPBACK.has(host) && !insecureHere()) {
|
|
77
|
-
|
|
78
|
-
return;
|
|
85
|
+
throw new Error(`an insecure server listens on loopback only, not ${host}`);
|
|
79
86
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
// Users whose folders survived a restart are known (and saved) before anyone calls.
|
|
88
|
+
await userStates.start();
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
setListening(`${host}:${port}`);
|
|
91
|
+
// Express 5 hands a listen error (EADDRINUSE) to this callback instead of throwing it.
|
|
92
|
+
server = app.listen(port, host, (err) => err ? reject(err) : resolve(server));
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
stop: async (graceMs = 20_000, saveMs = 60_000) => {
|
|
85
96
|
// No new connections, and no new runs on the ones still open, before the drain.
|
|
86
97
|
const closed = new Promise((resolve) => server ? server.close(() => resolve()) : resolve());
|
|
87
98
|
await stopAllRuns(graceMs);
|
|
99
|
+
await userStates.shutdown(saveMs);
|
|
88
100
|
// Followers of runs that have ended, idle keep-alives: nothing left to answer.
|
|
89
101
|
server?.closeAllConnections();
|
|
90
102
|
await closed;
|
package/dist/status.d.ts
CHANGED
|
@@ -47,6 +47,13 @@ export interface ServerStatus {
|
|
|
47
47
|
};
|
|
48
48
|
/** what each user's memory starts as, when there is anything */
|
|
49
49
|
memorySeed?: string;
|
|
50
|
+
/** where users' data is kept, and how many are on this server now */
|
|
51
|
+
state: {
|
|
52
|
+
store: string;
|
|
53
|
+
durable: boolean;
|
|
54
|
+
users: number;
|
|
55
|
+
draining: boolean;
|
|
56
|
+
};
|
|
50
57
|
}
|
|
51
58
|
/** Where the server listens, for the report. */
|
|
52
59
|
export declare function setListening(where: string | undefined): void;
|
package/dist/status.js
CHANGED
|
@@ -11,6 +11,7 @@ import { createRequire } from 'node:module';
|
|
|
11
11
|
import { settings } from "./config.js";
|
|
12
12
|
import { LAYOUT_VERSION, openLayout } from "./layout.js";
|
|
13
13
|
import { memorySeed } from "./store.js";
|
|
14
|
+
import { userStates } from "./userState.js";
|
|
14
15
|
const require = createRequire(import.meta.url);
|
|
15
16
|
function versionOf(pkg) {
|
|
16
17
|
try {
|
|
@@ -105,6 +106,12 @@ export async function serverStatus(env = process.env) {
|
|
|
105
106
|
image,
|
|
106
107
|
root,
|
|
107
108
|
...(memorySeed() ? { memorySeed: memorySeed() } : {}),
|
|
109
|
+
state: {
|
|
110
|
+
store: s.stateStore?.name ?? 'local',
|
|
111
|
+
durable: s.stateStore?.durable ?? false,
|
|
112
|
+
users: userStates.size,
|
|
113
|
+
draining: userStates.draining,
|
|
114
|
+
},
|
|
108
115
|
};
|
|
109
116
|
}
|
|
110
117
|
/** Why the server must not start, if anything. An image that was never labelled is not one. */
|
package/dist/store.d.ts
CHANGED
|
@@ -25,6 +25,10 @@ export interface ChatStore {
|
|
|
25
25
|
maxOwnerRuns: number;
|
|
26
26
|
/** Why a run is refused when the owner has that many going. */
|
|
27
27
|
ownerBusy: string;
|
|
28
|
+
/** Before a run takes its slot: throw an HttpError to refuse it (e.g. storage over quota). */
|
|
29
|
+
admit?(t: ChatThread): Promise<void>;
|
|
30
|
+
/** After a run ended or the thread was deleted: the owner's data changed. */
|
|
31
|
+
changed?(t: ChatThread, reason: 'run-end' | 'changed'): void;
|
|
28
32
|
}
|
|
29
33
|
/** Where one user's things are. */
|
|
30
34
|
export declare function userDir(user: string): string;
|
package/dist/store.js
CHANGED
|
@@ -28,6 +28,7 @@ import { settings } from "./config.js";
|
|
|
28
28
|
import { HttpError } from "./http.js";
|
|
29
29
|
import { identify } from "./identity.js";
|
|
30
30
|
import { applyProjectEnv } from "./projectEnv.js";
|
|
31
|
+
import { userStates } from "./userState.js";
|
|
31
32
|
/** Where one user's things are. */
|
|
32
33
|
export function userDir(user) {
|
|
33
34
|
return join(settings().root, 'users', user);
|
|
@@ -112,6 +113,8 @@ export const privateStore = {
|
|
|
112
113
|
return settings().maxUserRuns;
|
|
113
114
|
},
|
|
114
115
|
ownerBusy: 'your other chats are still answering',
|
|
116
|
+
admit: (t) => userStates.admit(t.owner),
|
|
117
|
+
changed: (t, reason) => userStates.changed(t.owner, reason),
|
|
115
118
|
};
|
|
116
119
|
export function usesMemory() {
|
|
117
120
|
try {
|
|
@@ -153,7 +156,7 @@ function sandboxFactory(s) {
|
|
|
153
156
|
...(existsSync(AGENT_APP_DIR) ? [AGENT_APP_DIR] : []),
|
|
154
157
|
...s.hidden,
|
|
155
158
|
];
|
|
156
|
-
return (opts) => new BwrapSandbox(opts, { hidden });
|
|
159
|
+
return (opts) => new BwrapSandbox(opts, { hidden, limits: s.commandLimits });
|
|
157
160
|
}
|
|
158
161
|
export async function openEngineFor(session, memoryDir,
|
|
159
162
|
// Who the run acts for; null is nobody, and stops the engine reading ZEN_USER_* from this process.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Router, type NextFunction, type Request, type Response } from 'express';
|
|
2
|
+
/** Where things are in a user's folder, for stores that cannot import this package. */
|
|
3
|
+
export declare const USER_LAYOUT: {
|
|
4
|
+
/** the user's memory graph; checkpoints carry a consistent copy instead (`memoryDir`) */
|
|
5
|
+
readonly memory: "memory";
|
|
6
|
+
/** one folder per chat thread */
|
|
7
|
+
readonly sessions: "sessions";
|
|
8
|
+
/** inside a session folder: its metadata, which the session list reads */
|
|
9
|
+
readonly sessionMeta: ".data/session.json";
|
|
10
|
+
/** the server's own record of the user's lease; never a store's to save */
|
|
11
|
+
readonly marker: ".zen-user.json";
|
|
12
|
+
};
|
|
13
|
+
export interface UserRef {
|
|
14
|
+
/** the user's folder name under `<root>/users/` */
|
|
15
|
+
id: string;
|
|
16
|
+
/** that folder: everything of theirs on this server */
|
|
17
|
+
dir: string;
|
|
18
|
+
/** the host's lease generation (the token's `gen`); undefined when the host does not lease */
|
|
19
|
+
generation?: number;
|
|
20
|
+
}
|
|
21
|
+
export type CheckpointReason = 'run-end' | 'changed' | 'periodic' | 'release' | 'shutdown' | 'recover';
|
|
22
|
+
export interface UserSnapshot {
|
|
23
|
+
/** a consistent copy of the user's memory made for this call, removed after it; none: no memory */
|
|
24
|
+
memoryDir?: string;
|
|
25
|
+
/** threads with a run going: their files may change while they are read */
|
|
26
|
+
activeThreads: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface UserStateStore {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
/** true: the store keeps users elsewhere, so a released user's local copy is deleted */
|
|
31
|
+
readonly durable: boolean;
|
|
32
|
+
/** Before the user's first request here: fetch what they need into `user.dir`. */
|
|
33
|
+
load(user: UserRef): Promise<void>;
|
|
34
|
+
/** Before a request names one thread: fetch it if `load` left it for later. */
|
|
35
|
+
loadSession?(user: UserRef, threadId: string): Promise<void>;
|
|
36
|
+
/** Save the user's folder as it is now (memory from `snapshot.memoryDir`). */
|
|
37
|
+
checkpoint(user: UserRef, reason: CheckpointReason, snapshot: UserSnapshot): Promise<void>;
|
|
38
|
+
/** After the final checkpoint, before the local copy goes. */
|
|
39
|
+
release?(user: UserRef): Promise<void>;
|
|
40
|
+
/** On start, for users whose folders survived a restart of this process. */
|
|
41
|
+
recover?(users: UserRef[]): Promise<void>;
|
|
42
|
+
close?(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/** Thrown by a store when a newer lease generation owns the user: this copy must go. */
|
|
45
|
+
export declare class StaleGenerationError extends Error {
|
|
46
|
+
readonly code = "stale_generation";
|
|
47
|
+
}
|
|
48
|
+
export declare const localStore: UserStateStore;
|
|
49
|
+
type Status = 'loading' | 'ready' | 'releasing';
|
|
50
|
+
export interface UserInfo {
|
|
51
|
+
id: string;
|
|
52
|
+
generation?: number;
|
|
53
|
+
status: Status;
|
|
54
|
+
activeRuns: number;
|
|
55
|
+
unsaved: boolean;
|
|
56
|
+
lastUsed: string;
|
|
57
|
+
lastCheckpointAt?: string;
|
|
58
|
+
lastError?: string;
|
|
59
|
+
usageBytes?: number;
|
|
60
|
+
}
|
|
61
|
+
declare class UserStates {
|
|
62
|
+
#private;
|
|
63
|
+
get draining(): boolean;
|
|
64
|
+
get size(): number;
|
|
65
|
+
/** The user's data, ready; their folder is theirs to use once this resolves. */
|
|
66
|
+
acquire(id: string, generation?: number): Promise<void>;
|
|
67
|
+
/** Before a request names a thread: the store fetches it if it left it for later. */
|
|
68
|
+
session(id: string, threadId: string): Promise<void>;
|
|
69
|
+
/** Something of the user's changed (a run ended, a thread went): save it. */
|
|
70
|
+
changed(id: string, reason?: CheckpointReason): void;
|
|
71
|
+
/** Saves the user now, after any save already queued; one at a time per user. */
|
|
72
|
+
checkpoint(id: string, reason: CheckpointReason): Promise<void>;
|
|
73
|
+
/** Saves the user for the last time and lets them go; refused while their runs go on. */
|
|
74
|
+
release(id: string): Promise<'released' | 'absent'>;
|
|
75
|
+
/** No new users; each one here released once their runs end. */
|
|
76
|
+
drain(timeoutMs: number): Promise<{
|
|
77
|
+
released: string[];
|
|
78
|
+
failed: Record<string, string>;
|
|
79
|
+
}>;
|
|
80
|
+
/** Takes users again: a drain that was called off. */
|
|
81
|
+
undrain(): void;
|
|
82
|
+
list(withUsage?: boolean): Promise<UserInfo[]>;
|
|
83
|
+
/** Over the soft quota: refuse new runs (checked before one starts). */
|
|
84
|
+
admit(id: string): Promise<void>;
|
|
85
|
+
/** Recovers users whose folders survived a restart, and starts the timers. */
|
|
86
|
+
start(): Promise<void>;
|
|
87
|
+
/** After the runs stopped: everyone saved once more, as far as the time allows. */
|
|
88
|
+
shutdown(timeoutMs: number): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
export declare const userStates: UserStates;
|
|
91
|
+
/** Every user-scoped route: the caller's data loaded first, and their thread when one is named. */
|
|
92
|
+
export declare function loadCaller(req: Request, _res: Response, next: NextFunction): Promise<void>;
|
|
93
|
+
export declare function loadThread(req: Request, _res: Response, next: NextFunction): Promise<void>;
|
|
94
|
+
/** /api/admin: only for the host's own calls (its system subject, never a user's thread token). */
|
|
95
|
+
export declare function userStateRouter(): Router;
|
|
96
|
+
/** Bytes under a user's folder, links not followed. */
|
|
97
|
+
export declare function userUsageBytes(id: string): Promise<number>;
|
|
98
|
+
export {};
|
|
99
|
+
//# sourceMappingURL=userState.d.ts.map
|