@stratusagent/gateway 0.14.7 → 0.14.8

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.
@@ -0,0 +1,130 @@
1
+ import { type StateEnvironment } from '@stratusagent/state';
2
+ /** What a turn cut short by an announced restart records as its failure. */
3
+ export declare const RESTARTING_TURN_ERROR = "Run aborted: stratusd is restarting";
4
+ /** The error a turn still running when a stop's drain window closes is aborted with. */
5
+ export declare const STOPPING_TURN_ERROR = "Run aborted: stratusd is stopping";
6
+ /**
7
+ * How long an announced restart lets in-flight turns finish before aborting
8
+ * them. The same as a stop's window (`DEFAULT_STOP_DRAIN_TIMEOUT_MS`), so the
9
+ * two windows an operator can hit agree.
10
+ */
11
+ export declare const DEFAULT_RESTART_DRAIN_TIMEOUT_MS = 30000;
12
+ /** How long a roster reload waits on one channel's `reload()`. */
13
+ export declare const DEFAULT_CHANNEL_RELOAD_TIMEOUT_MS = 30000;
14
+ /**
15
+ * What one channel did with a roster reload. `reloaded`: its `reload()`
16
+ * resolved. `failed`: it rejected, with the reason. `still-reloading`: it
17
+ * had not settled within `channelReloadTimeoutMs`, and finishes on its own.
18
+ */
19
+ export interface ChannelReloadOutcome {
20
+ name: string;
21
+ outcome: 'reloaded' | 'failed' | 'still-reloading';
22
+ reason?: string;
23
+ }
24
+ /**
25
+ * How long a stop lets in-flight turns finish before aborting them. The
26
+ * daemon's documented drain ("up to 30 seconds"), and shorter than the
27
+ * service units' stop timeouts, so the abort happens in this process
28
+ * rather than as the manager's SIGKILL. That matters beyond the turn's
29
+ * record: a shell command runs in its own process group, which launchd's
30
+ * kill of the job does not reach, so only the turn's abort stops it.
31
+ */
32
+ export declare const DEFAULT_STOP_DRAIN_TIMEOUT_MS = 30000;
33
+ export interface RestartRequest {
34
+ /** Why, for the log — "plugin stratus-plugin-github enabled". */
35
+ reason?: string;
36
+ /** Overrides the gateway's drain window for this restart. */
37
+ drainTimeoutMs?: number;
38
+ }
39
+ /** What `restart()` announced: the terms the drain is running under. */
40
+ export interface RestartStatus {
41
+ reason?: string;
42
+ drainTimeoutMs: number;
43
+ /** Turns that were running when the restart was announced. */
44
+ inflight: number;
45
+ }
46
+ /** What the host is handed once the daemon has stopped for a restart. */
47
+ export interface RestartOutcome {
48
+ reason?: string;
49
+ /**
50
+ * Whether everything let go — every turn finished inside the window or
51
+ * after being aborted at it, every channel stopped, and every plugin
52
+ * released what it held. False means a turn ignored its abort for a
53
+ * whole second window, or a channel's stop or a plugin's dispose never
54
+ * settled or rejected, and the gateway shut down around it; a host
55
+ * should exit the process rather than start a new daemon beside a turn
56
+ * still writing, a channel still connected, or a plugin still holding a
57
+ * browser or a socket.
58
+ */
59
+ drained: boolean;
60
+ }
61
+ /** Whether the daemon is taking new work. */
62
+ export interface IntakeState {
63
+ paused: boolean;
64
+ /** What a refused message is answered with. Present only while paused. */
65
+ message?: string;
66
+ /** When the pause began (ISO 8601). Present only while paused. */
67
+ since?: string;
68
+ /**
69
+ * Whether this pause is the one the daemon started in because its intake
70
+ * file could not be read. Reported while paused: true only for that pause,
71
+ * false once a pause written since has replaced it, so `stratus doctor`
72
+ * can tell a startup warning about the file from a pause someone chose.
73
+ * Never written to the file.
74
+ */
75
+ unreadable?: boolean;
76
+ }
77
+ /** What a paused daemon says when nobody gave it a sentence of its own. */
78
+ export declare const DEFAULT_PAUSED_MESSAGE = "I'm paused right now and not taking new work. Please try again later.";
79
+ /** What a held gateway answers a dispatch with, before it is promoted. */
80
+ export declare const HELD_MESSAGE = "I'm starting up and not taking work yet. Please try again in a moment.";
81
+ /** The longest pause message, so a reply to a refused message stays one. */
82
+ export declare const MAX_PAUSED_MESSAGE_LENGTH = 500;
83
+ /** A pause message the gateway will not take: empty, or too long. */
84
+ export declare class PauseMessageError extends Error {
85
+ constructor(message: string);
86
+ }
87
+ /**
88
+ * A dispatch refused because intake is paused. Its message is the sentence
89
+ * to show whoever sent the work, as it is: a channel posts it as the reply,
90
+ * not as a failure.
91
+ */
92
+ export declare class IntakePausedError extends Error {
93
+ constructor(message: string);
94
+ }
95
+ /**
96
+ * A dispatch refused because the daemon is restarting or stopping. Channels
97
+ * stay connected while a restart drains, so they hear people during it, and
98
+ * this is what tells them the message reached a daemon on its way down: a
99
+ * channel can say so and keep the message for the next one. Matched by name
100
+ * there, as channel packages do not import the gateway.
101
+ */
102
+ export declare class GatewayStoppingError extends Error {
103
+ /** True for a restart, which comes back on its own; false for a stop. */
104
+ readonly restarting: boolean;
105
+ constructor(message: string, restarting: boolean);
106
+ }
107
+ /**
108
+ * The intake state a previous process left, or open when there is none.
109
+ *
110
+ * Fails closed: a file that exists but cannot be read or parsed starts the
111
+ * daemon paused, with a warning naming it. A pause exists to stop work (a
112
+ * spending cap, a maintenance window), and a file nobody can read is no
113
+ * evidence that it was lifted.
114
+ */
115
+ export declare const readIntakeState: (env: StateEnvironment, warn: (line: string) => void) => Promise<IntakeState>;
116
+ /**
117
+ * Record a pause durably (0600, replaced in one atomic step), or remove the
118
+ * record when intake reopens: open is the default, so nothing on disk
119
+ * means open.
120
+ */
121
+ export declare const writeIntakeState: (env: StateEnvironment, state: IntakeState) => Promise<void>;
122
+ /**
123
+ * `restart()` on a gateway whose host gave it no way back. Exported so the
124
+ * control API can answer it as "not supported here" rather than as a
125
+ * failure of the daemon.
126
+ */
127
+ export declare class RestartUnsupportedError extends Error {
128
+ constructor();
129
+ }
130
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAUA,OAAO,EAEL,KAAK,gBAAgB,EAGtB,MAAM,qBAAqB,CAAC;AAE7B,4EAA4E;AAC5E,eAAO,MAAM,qBAAqB,wCAAwC,CAAC;AAE3E,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,sCAAsC,CAAC;AAEvE;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,QAAS,CAAC;AACvD,kEAAkE;AAClE,eAAO,MAAM,iCAAiC,QAAS,CAAC;AAExD;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,UAAU,GAAG,QAAQ,GAAG,iBAAiB,CAAC;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,QAAS,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;OASG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,6CAA6C;AAC7C,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,2EAA2E;AAC3E,eAAO,MAAM,sBAAsB,0EAA0E,CAAC;AAE9G,0EAA0E;AAC1E,eAAO,MAAM,YAAY,2EAA2E,CAAC;AAErG,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,qEAAqE;AACrE,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;gBACjB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO;CAKjD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAU,KAAK,gBAAgB,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAAG,OAAO,CAAC,WAAW,CA8B9G,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAU,KAAK,gBAAgB,EAAE,OAAO,WAAW,KAAG,OAAO,CAAC,IAAI,CAe9F,CAAC;AAEF;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;;CAQjD"}
@@ -0,0 +1,147 @@
1
+ /*
2
+ * Restart, stop and pause: the errors a cut-short turn records, the drain
3
+ * windows, what a restart or a roster reload reports, and the intake state a
4
+ * paused daemon keeps on disk. Moved out of `index.ts` as is; `index.ts`
5
+ * re-exports the public names, so the package's surface is unchanged.
6
+ */
7
+ import { randomBytes } from 'node:crypto';
8
+ import { mkdir, readFile, rename, rm } from 'node:fs/promises';
9
+ import path from 'node:path';
10
+ import { intakeStatePath, syncDirectory, writeFileSynced, } from '@stratusagent/state';
11
+ /** What a turn cut short by an announced restart records as its failure. */
12
+ export const RESTARTING_TURN_ERROR = 'Run aborted: stratusd is restarting';
13
+ /** The error a turn still running when a stop's drain window closes is aborted with. */
14
+ export const STOPPING_TURN_ERROR = 'Run aborted: stratusd is stopping';
15
+ /**
16
+ * How long an announced restart lets in-flight turns finish before aborting
17
+ * them. The same as a stop's window (`DEFAULT_STOP_DRAIN_TIMEOUT_MS`), so the
18
+ * two windows an operator can hit agree.
19
+ */
20
+ export const DEFAULT_RESTART_DRAIN_TIMEOUT_MS = 30_000;
21
+ /** How long a roster reload waits on one channel's `reload()`. */
22
+ export const DEFAULT_CHANNEL_RELOAD_TIMEOUT_MS = 30_000;
23
+ /**
24
+ * How long a stop lets in-flight turns finish before aborting them. The
25
+ * daemon's documented drain ("up to 30 seconds"), and shorter than the
26
+ * service units' stop timeouts, so the abort happens in this process
27
+ * rather than as the manager's SIGKILL. That matters beyond the turn's
28
+ * record: a shell command runs in its own process group, which launchd's
29
+ * kill of the job does not reach, so only the turn's abort stops it.
30
+ */
31
+ export const DEFAULT_STOP_DRAIN_TIMEOUT_MS = 30_000;
32
+ /** What a paused daemon says when nobody gave it a sentence of its own. */
33
+ export const DEFAULT_PAUSED_MESSAGE = "I'm paused right now and not taking new work. Please try again later.";
34
+ /** What a held gateway answers a dispatch with, before it is promoted. */
35
+ export const HELD_MESSAGE = "I'm starting up and not taking work yet. Please try again in a moment.";
36
+ /** The longest pause message, so a reply to a refused message stays one. */
37
+ export const MAX_PAUSED_MESSAGE_LENGTH = 500;
38
+ /** A pause message the gateway will not take: empty, or too long. */
39
+ export class PauseMessageError extends Error {
40
+ constructor(message) {
41
+ super(message);
42
+ this.name = 'PauseMessageError';
43
+ }
44
+ }
45
+ /**
46
+ * A dispatch refused because intake is paused. Its message is the sentence
47
+ * to show whoever sent the work, as it is: a channel posts it as the reply,
48
+ * not as a failure.
49
+ */
50
+ export class IntakePausedError extends Error {
51
+ constructor(message) {
52
+ super(message);
53
+ this.name = 'IntakePausedError';
54
+ }
55
+ }
56
+ /**
57
+ * A dispatch refused because the daemon is restarting or stopping. Channels
58
+ * stay connected while a restart drains, so they hear people during it, and
59
+ * this is what tells them the message reached a daemon on its way down: a
60
+ * channel can say so and keep the message for the next one. Matched by name
61
+ * there, as channel packages do not import the gateway.
62
+ */
63
+ export class GatewayStoppingError extends Error {
64
+ /** True for a restart, which comes back on its own; false for a stop. */
65
+ restarting;
66
+ constructor(message, restarting) {
67
+ super(message);
68
+ this.name = 'GatewayStoppingError';
69
+ this.restarting = restarting;
70
+ }
71
+ }
72
+ /**
73
+ * The intake state a previous process left, or open when there is none.
74
+ *
75
+ * Fails closed: a file that exists but cannot be read or parsed starts the
76
+ * daemon paused, with a warning naming it. A pause exists to stop work (a
77
+ * spending cap, a maintenance window), and a file nobody can read is no
78
+ * evidence that it was lifted.
79
+ */
80
+ export const readIntakeState = async (env, warn) => {
81
+ const file = intakeStatePath(env);
82
+ let raw;
83
+ try {
84
+ raw = await readFile(file, 'utf8');
85
+ }
86
+ catch (error) {
87
+ if (error.code === 'ENOENT') {
88
+ return { paused: false };
89
+ }
90
+ warn(`intake: ${file} could not be read (${error.message}); starting paused. Resume intake to clear it.`);
91
+ return { paused: true, since: new Date().toISOString(), unreadable: true };
92
+ }
93
+ try {
94
+ const parsed = JSON.parse(raw);
95
+ // Only a pause is ever written here (resuming removes the file), so
96
+ // anything else in it is not a record this build wrote, and the same
97
+ // rule as an unreadable file applies.
98
+ if (typeof parsed !== 'object' || parsed === null || parsed.paused !== true) {
99
+ warn(`intake: ${file} does not hold a pause record; starting paused. Resume intake to clear it.`);
100
+ return { paused: true, since: new Date().toISOString(), unreadable: true };
101
+ }
102
+ return {
103
+ paused: true,
104
+ ...(typeof parsed.message === 'string' && parsed.message.trim().length > 0 ? { message: parsed.message } : {}),
105
+ since: typeof parsed.since === 'string' ? parsed.since : new Date().toISOString(),
106
+ };
107
+ }
108
+ catch {
109
+ warn(`intake: ${file} is not valid JSON; starting paused. Resume intake to clear it.`);
110
+ return { paused: true, since: new Date().toISOString(), unreadable: true };
111
+ }
112
+ };
113
+ /**
114
+ * Record a pause durably (0600, replaced in one atomic step), or remove the
115
+ * record when intake reopens: open is the default, so nothing on disk
116
+ * means open.
117
+ */
118
+ export const writeIntakeState = async (env, state) => {
119
+ const file = intakeStatePath(env);
120
+ if (!state.paused) {
121
+ await rm(file, { force: true });
122
+ return;
123
+ }
124
+ await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
125
+ const staging = `${file}.${randomBytes(8).toString('hex')}`;
126
+ try {
127
+ await writeFileSynced(staging, `${JSON.stringify(state)}\n`, { flag: 'wx', mode: 0o600 });
128
+ await rename(staging, file);
129
+ await syncDirectory(path.dirname(file));
130
+ }
131
+ finally {
132
+ await rm(staging, { force: true });
133
+ }
134
+ };
135
+ /**
136
+ * `restart()` on a gateway whose host gave it no way back. Exported so the
137
+ * control API can answer it as "not supported here" rather than as a
138
+ * failure of the daemon.
139
+ */
140
+ export class RestartUnsupportedError extends Error {
141
+ constructor() {
142
+ super('This daemon cannot restart itself: the host that started it did not say how. '
143
+ + 'Restart it from the service manager (stratus service stop, then start) or from the terminal it runs in.');
144
+ this.name = 'RestartUnsupportedError';
145
+ }
146
+ }
147
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ import { type JsonObject, type Session } from '@stratusagent/core';
2
+ /**
3
+ * Recorded on a turn the previous process was still running when it died.
4
+ *
5
+ * Exported because "distinguishable" is the whole point: a surface, a
6
+ * test, or an operator reading `lastError` has to be able to tell a turn
7
+ * nobody finished apart from one a provider or a tool actually failed, and
8
+ * matching on prose that lives in one place beats each caller inventing
9
+ * its own guess at the wording.
10
+ */
11
+ export declare const ABANDONED_TURN_ERROR = "stratusd stopped while this turn was still running; it was not resumed. Send the message again.";
12
+ /**
13
+ * The longest `DispatchInput.idempotencyKey` accepted. A platform message id
14
+ * is far shorter; the bound is so a key cannot be used to grow every row it
15
+ * is stored on.
16
+ */
17
+ /**
18
+ * Recorded on a delegated sub-session that was parked on a human when the
19
+ * previous process died.
20
+ *
21
+ * Its parent — the turn whose `agent.delegate` call was awaiting the reply
22
+ * — was running, so the restart settles it as abandoned (above). Recovering
23
+ * the child anyway would re-ask a person to approve a call whose result no
24
+ * turn will ever read, and then run it: a command executed, tokens spent,
25
+ * and a reply delivered to nobody. Distinguishable from the parent's error
26
+ * because the remedy differs — there is no message to send again here;
27
+ * the parent's is the one to repeat.
28
+ */
29
+ /**
30
+ * Session metadata keys the daemon writes for itself, which no external
31
+ * dispatch may supply.
32
+ *
33
+ * Metadata is one bag: the keys a channel attaches to say where a turn is
34
+ * happening sit beside the ones the kernel, the scheduler, the fallback
35
+ * wrapper, and `agent.delegate` write to remember what a session *is* —
36
+ * and the control API forwards a caller's `metadata` into a new session as
37
+ * given. A caller who can write `delegatedBy` can have the restart sweep
38
+ * fail its own parked turn as an orphan; one who can write `fallbackActive`
39
+ * bills its conversation to the fallback model from the first turn. The
40
+ * scheduler already refused to trust its own keys for exactly this reason
41
+ * (see `activeFirings`); the durable ones cannot be re-derived in memory,
42
+ * so they are refused at the door instead.
43
+ */
44
+ /**
45
+ * A rolled-over session: the fresh row records where its predecessor's
46
+ * transcript went, and the archived row records which live id continues
47
+ * the conversation. See `Gateway.rolloverSession`.
48
+ */
49
+ export declare const ROLLED_OVER_FROM_METADATA_KEY = "rolledOverFrom";
50
+ export declare const ROLLED_OVER_TO_METADATA_KEY = "rolledOverTo";
51
+ /** The segment a rollover mints into the archived transcript's id. */
52
+ export declare const ROLLED_OVER_SESSION_ID_MARKER = ":rolledover:";
53
+ /**
54
+ * How long after a rollover a repeated key is still looked for in the
55
+ * archived transcript. A rollover moves every keyed message out of the
56
+ * live row, so a redelivery arriving after one would otherwise read as a
57
+ * new message and run again. Platforms redeliver within minutes — Slack
58
+ * gives up after about five — and past the window the archive is not
59
+ * loaded at all, since every new message to the fresh row would pay for a
60
+ * read that can no longer find anything.
61
+ */
62
+ export declare const ROLLOVER_REPEAT_WINDOW_MS: number;
63
+ /**
64
+ * The one refusal for a dispatch naming an agent other than its session's.
65
+ * Its own type so a repeat attached to a claimant can tell the claimant was
66
+ * refused for who it named — the item then still has no owner — from the
67
+ * claimant's turn failing. Internal: callers read the message.
68
+ */
69
+ export declare class CrossIdentityError extends Error {
70
+ constructor(sessionId: string, owner: string, claimed: string);
71
+ }
72
+ export declare const crossIdentityError: (sessionId: string, owner: string, claimed: string) => Error;
73
+ /** Where the session's tool calls ran: `local-command`, or a contributed executor's registered name. */
74
+ export declare const EXECUTOR_METADATA_KEY = "executor";
75
+ /**
76
+ * Marks a sub-agent's session (roadmap 24). Reserved: it is what makes the
77
+ * daemon deny the session's gated calls without asking anyone, so a caller
78
+ * that could set it could also clear it.
79
+ */
80
+ export declare const SUB_AGENT_METADATA_KEY = "subAgent";
81
+ export declare const SUB_AGENT_SESSION_ID_MARKER = ":subagent:";
82
+ /** How many sub-agents one parent turn runs at once unless the host says otherwise. */
83
+ export declare const DEFAULT_SUB_AGENTS_PER_TURN = 4;
84
+ export declare const isSubAgentSession: (session: Pick<Session, "id" | "metadata">) => boolean;
85
+ /**
86
+ * Tools a sub-agent never gets, whatever its parent holds. `agent.spawn`
87
+ * would fan out inside a fan-out; `agent.delegate` runs a roster agent
88
+ * under that agent's own allowlist, which is the way out of a narrowed
89
+ * slice; `memory.*` would let it persist what nobody reviewed; and
90
+ * `credential.request` asks a person, which a sub-agent never does.
91
+ */
92
+ export declare const subAgentNeverGets: (name: string) => boolean;
93
+ export declare const RESERVED_SESSION_METADATA_KEYS: readonly string[];
94
+ /**
95
+ * The first reserved key `metadata` carries, or undefined when it carries
96
+ * none. Exported so the control API can refuse the request where the
97
+ * caller can see it, with the same rule the gateway applies at dispatch.
98
+ */
99
+ export declare const reservedSessionMetadataKey: (metadata: JsonObject | undefined) => string | undefined;
100
+ export declare const ORPHANED_DELEGATION_ERROR = "stratusd stopped while the turn that delegated this one was still running; the delegation was not resumed. Send the original message again.";
101
+ //# sourceMappingURL=session-keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-keys.d.ts","sourceRoot":"","sources":["../src/session-keys.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,OAAO,EAGb,MAAM,oBAAoB,CAAC;AAY5B;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,oGACkE,CAAC;AAEpG;;;;GAIG;AAEH;;;;;;;;;;;GAWG;AACH;;;;;;;;;;;;;;GAcG;AACH;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,mBAAmB,CAAC;AAC9D,eAAO,MAAM,2BAA2B,iBAAiB,CAAC;AAC1D,sEAAsE;AACtE,eAAO,MAAM,6BAA6B,iBAAiB,CAAC;AAE5D;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,QAAiB,CAAC;AAExD;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI9D;AAED,eAAO,MAAM,kBAAkB,GAAI,WAAW,MAAM,EAAE,OAAO,MAAM,EAAE,SAAS,MAAM,KAAG,KACpC,CAAC;AAEpD,wGAAwG;AACxG,eAAO,MAAM,qBAAqB,aAAa,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,aAAa,CAAC;AACjD,eAAO,MAAM,2BAA2B,eAAe,CAAC;AACxD,uFAAuF;AACvF,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,eAAO,MAAM,iBAAiB,GAAI,SAAS,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,UAAU,CAAC,KAAG,OAC2B,CAAC;AAE1G;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,KAAG,OAIP,CAAC;AAE3C,eAAO,MAAM,8BAA8B,EAAE,SAAS,MAAM,EA8B3D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,GAAI,UAAU,UAAU,GAAG,SAAS,KAAG,MAAM,GAAG,SAGgB,CAAC;AAExG,eAAO,MAAM,yBAAyB,gJACyG,CAAC"}
@@ -0,0 +1,148 @@
1
+ /*
2
+ * What the gateway reserves on a session: the metadata keys a caller may not
3
+ * set, the id markers and keys of rolled-over and sub-agent sessions, and the
4
+ * errors a turn or a delegation cut short by a stopped daemon records. Moved
5
+ * out of `index.ts` as is; `index.ts` re-exports the public names, so the
6
+ * package's surface is unchanged.
7
+ */
8
+ import { CONTEXT_FLOOR_METADATA_KEY, CONTEXT_SUMMARY_METADATA_KEY, PENDING_APPROVAL_METADATA_KEY, SESSION_TAINTED_BY_METADATA_KEY, SESSION_TRUST_METADATA_KEY, } from '@stratusagent/core';
9
+ import { CREDENTIAL_REQUEST_TOOL_NAME, DELEGATE_TOOL_NAME, DELEGATED_BY_METADATA_KEY, DELEGATION_DEPTH_METADATA_KEY, ROOT_SESSION_ID_METADATA_KEY, SCHEDULE_ID_METADATA_KEY, SCHEDULED_TURN_METADATA_KEY, } from '@stratusagent/agents';
10
+ import { FALLBACK_ACTIVE_METADATA_KEY } from '@stratusagent/state';
11
+ /**
12
+ * Recorded on a turn the previous process was still running when it died.
13
+ *
14
+ * Exported because "distinguishable" is the whole point: a surface, a
15
+ * test, or an operator reading `lastError` has to be able to tell a turn
16
+ * nobody finished apart from one a provider or a tool actually failed, and
17
+ * matching on prose that lives in one place beats each caller inventing
18
+ * its own guess at the wording.
19
+ */
20
+ export const ABANDONED_TURN_ERROR = 'stratusd stopped while this turn was still running; it was not resumed. Send the message again.';
21
+ /**
22
+ * The longest `DispatchInput.idempotencyKey` accepted. A platform message id
23
+ * is far shorter; the bound is so a key cannot be used to grow every row it
24
+ * is stored on.
25
+ */
26
+ /**
27
+ * Recorded on a delegated sub-session that was parked on a human when the
28
+ * previous process died.
29
+ *
30
+ * Its parent — the turn whose `agent.delegate` call was awaiting the reply
31
+ * — was running, so the restart settles it as abandoned (above). Recovering
32
+ * the child anyway would re-ask a person to approve a call whose result no
33
+ * turn will ever read, and then run it: a command executed, tokens spent,
34
+ * and a reply delivered to nobody. Distinguishable from the parent's error
35
+ * because the remedy differs — there is no message to send again here;
36
+ * the parent's is the one to repeat.
37
+ */
38
+ /**
39
+ * Session metadata keys the daemon writes for itself, which no external
40
+ * dispatch may supply.
41
+ *
42
+ * Metadata is one bag: the keys a channel attaches to say where a turn is
43
+ * happening sit beside the ones the kernel, the scheduler, the fallback
44
+ * wrapper, and `agent.delegate` write to remember what a session *is* —
45
+ * and the control API forwards a caller's `metadata` into a new session as
46
+ * given. A caller who can write `delegatedBy` can have the restart sweep
47
+ * fail its own parked turn as an orphan; one who can write `fallbackActive`
48
+ * bills its conversation to the fallback model from the first turn. The
49
+ * scheduler already refused to trust its own keys for exactly this reason
50
+ * (see `activeFirings`); the durable ones cannot be re-derived in memory,
51
+ * so they are refused at the door instead.
52
+ */
53
+ /**
54
+ * A rolled-over session: the fresh row records where its predecessor's
55
+ * transcript went, and the archived row records which live id continues
56
+ * the conversation. See `Gateway.rolloverSession`.
57
+ */
58
+ export const ROLLED_OVER_FROM_METADATA_KEY = 'rolledOverFrom';
59
+ export const ROLLED_OVER_TO_METADATA_KEY = 'rolledOverTo';
60
+ /** The segment a rollover mints into the archived transcript's id. */
61
+ export const ROLLED_OVER_SESSION_ID_MARKER = ':rolledover:';
62
+ /**
63
+ * How long after a rollover a repeated key is still looked for in the
64
+ * archived transcript. A rollover moves every keyed message out of the
65
+ * live row, so a redelivery arriving after one would otherwise read as a
66
+ * new message and run again. Platforms redeliver within minutes — Slack
67
+ * gives up after about five — and past the window the archive is not
68
+ * loaded at all, since every new message to the fresh row would pay for a
69
+ * read that can no longer find anything.
70
+ */
71
+ export const ROLLOVER_REPEAT_WINDOW_MS = 60 * 60 * 1000;
72
+ /**
73
+ * The one refusal for a dispatch naming an agent other than its session's.
74
+ * Its own type so a repeat attached to a claimant can tell the claimant was
75
+ * refused for who it named — the item then still has no owner — from the
76
+ * claimant's turn failing. Internal: callers read the message.
77
+ */
78
+ export class CrossIdentityError extends Error {
79
+ constructor(sessionId, owner, claimed) {
80
+ super(`Session ${sessionId} belongs to agent ${owner}, not ${claimed} — sessions never cross agent identities.`);
81
+ this.name = 'CrossIdentityError';
82
+ }
83
+ }
84
+ export const crossIdentityError = (sessionId, owner, claimed) => new CrossIdentityError(sessionId, owner, claimed);
85
+ /** Where the session's tool calls ran: `local-command`, or a contributed executor's registered name. */
86
+ export const EXECUTOR_METADATA_KEY = 'executor';
87
+ /**
88
+ * Marks a sub-agent's session (roadmap 24). Reserved: it is what makes the
89
+ * daemon deny the session's gated calls without asking anyone, so a caller
90
+ * that could set it could also clear it.
91
+ */
92
+ export const SUB_AGENT_METADATA_KEY = 'subAgent';
93
+ export const SUB_AGENT_SESSION_ID_MARKER = ':subagent:';
94
+ /** How many sub-agents one parent turn runs at once unless the host says otherwise. */
95
+ export const DEFAULT_SUB_AGENTS_PER_TURN = 4;
96
+ export const isSubAgentSession = (session) => session.metadata?.[SUB_AGENT_METADATA_KEY] === true && session.id.includes(SUB_AGENT_SESSION_ID_MARKER);
97
+ /**
98
+ * Tools a sub-agent never gets, whatever its parent holds. `agent.spawn`
99
+ * would fan out inside a fan-out; `agent.delegate` runs a roster agent
100
+ * under that agent's own allowlist, which is the way out of a narrowed
101
+ * slice; `memory.*` would let it persist what nobody reviewed; and
102
+ * `credential.request` asks a person, which a sub-agent never does.
103
+ */
104
+ export const subAgentNeverGets = (name) => name === 'agent.spawn'
105
+ || name === DELEGATE_TOOL_NAME
106
+ || name.startsWith('memory.')
107
+ || name === CREDENTIAL_REQUEST_TOOL_NAME;
108
+ export const RESERVED_SESSION_METADATA_KEYS = [
109
+ PENDING_APPROVAL_METADATA_KEY,
110
+ FALLBACK_ACTIVE_METADATA_KEY,
111
+ DELEGATED_BY_METADATA_KEY,
112
+ ROOT_SESSION_ID_METADATA_KEY,
113
+ DELEGATION_DEPTH_METADATA_KEY,
114
+ SCHEDULED_TURN_METADATA_KEY,
115
+ SCHEDULE_ID_METADATA_KEY,
116
+ // The trust label is the runner's to write and only ever goes down; a
117
+ // caller who could seed it would be seeding it upward.
118
+ SESSION_TRUST_METADATA_KEY,
119
+ SESSION_TAINTED_BY_METADATA_KEY,
120
+ ROLLED_OVER_FROM_METADATA_KEY,
121
+ ROLLED_OVER_TO_METADATA_KEY,
122
+ // Which executor ran the turn's tool calls is the daemon's record of
123
+ // where a command ran, and the one audit question a caller must not be
124
+ // able to answer for it.
125
+ EXECUTOR_METADATA_KEY,
126
+ // The context floor is an index into a specific transcript, so it means
127
+ // nothing next to a different one. Reserved for both halves of that: a
128
+ // caller cannot seed it, and a rollover drops it with the transcript it
129
+ // indexed — otherwise the fresh session inherits an absolute floor over
130
+ // no messages and, once it grows past that index, silently stops sending
131
+ // its own earliest turns, with no overflow and no trim event to say so.
132
+ CONTEXT_FLOOR_METADATA_KEY,
133
+ // The summary that stands in below that floor, for both of the same
134
+ // reasons, and one more: the model reads it as something it wrote itself,
135
+ // so a caller who could seed it could put words in the agent's mouth.
136
+ CONTEXT_SUMMARY_METADATA_KEY,
137
+ SUB_AGENT_METADATA_KEY,
138
+ ];
139
+ /**
140
+ * The first reserved key `metadata` carries, or undefined when it carries
141
+ * none. Exported so the control API can refuse the request where the
142
+ * caller can see it, with the same rule the gateway applies at dispatch.
143
+ */
144
+ export const reservedSessionMetadataKey = (metadata) => metadata === undefined
145
+ ? undefined
146
+ : RESERVED_SESSION_METADATA_KEYS.find((key) => Object.prototype.hasOwnProperty.call(metadata, key));
147
+ export const ORPHANED_DELEGATION_ERROR = 'stratusd stopped while the turn that delegated this one was still running; the delegation was not resumed. Send the original message again.';
148
+ //# sourceMappingURL=session-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-keys.js","sourceRoot":"","sources":["../src/session-keys.ts"],"names":[],"mappings":"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"}