@stratusagent/gateway 0.14.6 → 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.
- package/dist/index.d.ts +5 -1227
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +187 -301
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +130 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +147 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/review.d.ts +46 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +233 -0
- package/dist/review.js.map +1 -0
- package/dist/schedules.d.ts +12 -0
- package/dist/schedules.d.ts.map +1 -1
- package/dist/schedules.js +19 -0
- package/dist/schedules.js.map +1 -1
- package/dist/session-keys.d.ts +101 -0
- package/dist/session-keys.d.ts.map +1 -0
- package/dist/session-keys.js +148 -0
- package/dist/session-keys.js.map +1 -0
- package/dist/types.d.ts +1103 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -6,25 +6,30 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
-
import { createHash,
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import { filePathsOf, isUnaddressedTurn, carriesKey, leastTrusted, senderTrustOf, SENDER_TRUST_METADATA_KEY, workItemState, turnMessageOf, turnKeyOf, leftForNextStart, lastResponseIsAnswer, MAX_IDEMPOTENCY_KEY_LENGTH, recordTurnFailure, completeAnsweredTurn, abortErrorFor, sessionTrustOf, sessionWriteTrust, AgentRegistry, AgentRunner, InMemorySessionStore, MEMORY_PASS_MAX_MODEL_CALLS, MEMORY_PASS_RULES, ChannelRegistry, ContributionRegistry, createRoutedMemoryStore, EventBus, RunAbortedError, SkillRegistry, ToolRegistry, createSkillReadTool, missingSkillRequirements, describeToolAllowlistFinding, unmatchedToolAllowlist, CONTEXT_FLOOR_METADATA_KEY, CONTEXT_SUMMARY_METADATA_KEY, PENDING_APPROVAL_METADATA_KEY, SESSION_TAINTED_BY_METADATA_KEY, SESSION_TRUST_METADATA_KEY, latestTurnReply, readPendingApproval, conversationContextFrom, CONVERSATION_METADATA_KEY, STRATUS_SKILL_ID, imagesWithinReplayBudget, matchesToolAllowlist, resolveToolRisk, } from '@stratusagent/core';
|
|
13
|
-
import { CREDENTIAL_REQUEST_TOOL_NAME, createCredentialRequestTool, createDelegateTool, DELEGATE_TOOL_NAME, createForgetTool, createPinTool, createMessageSendTool, createMessageSayTool, createMessageReadTool, createChannelArchiveTool, createChannelCreateTool, createChannelInviteTool, createChannelJoinTool, directMessageReadRefusal, createRecallTool, createRememberTool, createScheduleTools, DELEGATED_BY_METADATA_KEY, DELEGATED_SESSION_ID_MARKER, DEFAULT_MAX_DELEGATION_DEPTH, DELEGATION_DEPTH_METADATA_KEY, delegatingSessionIdOf, isDelegatedSession, outstandingDelegationFor, ROOT_SESSION_ID_METADATA_KEY, SCHEDULE_ID_METADATA_KEY, SCHEDULED_TURN_METADATA_KEY, withoutDelegation, loadStratusSkill, STRATUS_SKILL_PATH, credentialVariableProblem, } from '@stratusagent/agents';
|
|
9
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
10
|
+
import { filePathsOf, isUnaddressedTurn, carriesKey, leastTrusted, senderTrustOf, SENDER_TRUST_METADATA_KEY, workItemState, turnMessageOf, turnKeyOf, leftForNextStart, lastResponseIsAnswer, MAX_IDEMPOTENCY_KEY_LENGTH, recordTurnFailure, completeAnsweredTurn, abortErrorFor, sessionTrustOf, sessionWriteTrust, AgentRegistry, AgentRunner, InMemorySessionStore, MEMORY_PASS_MAX_MODEL_CALLS, MEMORY_PASS_RULES, ChannelRegistry, ContributionRegistry, createRoutedMemoryStore, EventBus, RunAbortedError, SkillRegistry, ToolRegistry, createSkillReadTool, missingSkillRequirements, describeToolAllowlistFinding, unmatchedToolAllowlist, PENDING_APPROVAL_METADATA_KEY, SESSION_TRUST_METADATA_KEY, latestTurnReply, readPendingApproval, conversationContextFrom, CONVERSATION_METADATA_KEY, STRATUS_SKILL_ID, imagesWithinReplayBudget, matchesToolAllowlist, resolveToolRisk, } from '@stratusagent/core';
|
|
11
|
+
import { CREDENTIAL_REQUEST_TOOL_NAME, createCredentialRequestTool, createDelegateTool, createForgetTool, createPinTool, createMessageSendTool, createMessageSayTool, createMessageReadTool, createChannelArchiveTool, createChannelCreateTool, createChannelInviteTool, createChannelJoinTool, directMessageReadRefusal, createRecallTool, createRememberTool, createScheduleTools, DELEGATED_BY_METADATA_KEY, DELEGATED_SESSION_ID_MARKER, DEFAULT_MAX_DELEGATION_DEPTH, DELEGATION_DEPTH_METADATA_KEY, delegatingSessionIdOf, isDelegatedSession, outstandingDelegationFor, ROOT_SESSION_ID_METADATA_KEY, SCHEDULE_ID_METADATA_KEY, withoutDelegation, loadStratusSkill, STRATUS_SKILL_PATH, credentialVariableProblem, } from '@stratusagent/agents';
|
|
14
12
|
import { createFleetReader } from "./fleet.js";
|
|
15
13
|
import { createMemoryPassHost } from "./memory-passes.js";
|
|
14
|
+
import { callShownWhole, readReviewReply, REVIEW_RULES, reviewCaseText } from "./review.js";
|
|
16
15
|
import { createEventIntake, summarizeEvents, } from "./event-intake.js";
|
|
17
16
|
import { SqliteEventStore } from "./events.js";
|
|
18
17
|
import { createSchedulerRuntime, isScheduleSessionId, SqliteScheduleStore, SCHEDULE_SESSION_ID_PREFIX, } from "./schedules.js";
|
|
19
18
|
import { ShardedSessionStore } from "./sessions.js";
|
|
19
|
+
import { DEFAULT_CHANNEL_RELOAD_TIMEOUT_MS, DEFAULT_PAUSED_MESSAGE, DEFAULT_RESTART_DRAIN_TIMEOUT_MS, DEFAULT_STOP_DRAIN_TIMEOUT_MS, GatewayStoppingError, HELD_MESSAGE, IntakePausedError, MAX_PAUSED_MESSAGE_LENGTH, PauseMessageError, readIntakeState, RESTARTING_TURN_ERROR, RestartUnsupportedError, STOPPING_TURN_ERROR, writeIntakeState, } from "./lifecycle.js";
|
|
20
|
+
import { ABANDONED_TURN_ERROR, crossIdentityError, CrossIdentityError, DEFAULT_SUB_AGENTS_PER_TURN, EXECUTOR_METADATA_KEY, isSubAgentSession, ORPHANED_DELEGATION_ERROR, RESERVED_SESSION_METADATA_KEYS, reservedSessionMetadataKey, ROLLED_OVER_FROM_METADATA_KEY, ROLLED_OVER_SESSION_ID_MARKER, ROLLED_OVER_TO_METADATA_KEY, ROLLOVER_REPEAT_WINDOW_MS, SUB_AGENT_METADATA_KEY, SUB_AGENT_SESSION_ID_MARKER, subAgentNeverGets, } from "./session-keys.js";
|
|
20
21
|
export { claimHome, homeLockPath, HomeClaimedError, } from "./lock.js";
|
|
21
22
|
export { SqliteSessionStore, ShardedSessionStore, FleetSessionIndex, SessionIdTakenError, tightenSqliteFile, } from "./sessions.js";
|
|
22
23
|
export { SqliteScheduleStore, createSchedulerRuntime, isScheduleSessionId, SCHEDULE_SESSION_ID_PREFIX, } from "./schedules.js";
|
|
23
24
|
export { SqliteEventStore, EVENT_BLOCK_MAX_BYTES, EVENT_DEDUP_WINDOW_MS, EVENT_DELIVERY_ROW_CAP, eventCoalescingKey, eventDispatchKey, eventSessionId, renderEventBlock, } from "./events.js";
|
|
25
|
+
// The review posture's fixed prompt and case, for the review-step eval
|
|
26
|
+
// (`packages/cli/eval/review-step`), which measures the prompt production
|
|
27
|
+
// sends rather than a copy of it.
|
|
28
|
+
export { REVIEW_RULES, readReviewReply, reviewCaseText } from "./review.js";
|
|
24
29
|
export { createEventIntake, mapGithubDelivery, parseStandardWebhookEvent, verifyGithubSignature, verifyStandardWebhookSignature, DEFAULT_EVENT_MAX_BODY_BYTES, EVENT_SOURCE_REQUESTS_PER_MINUTE, } from "./event-intake.js";
|
|
25
30
|
import { createLocalCommandExecutor } from '@stratusagent/executor-local';
|
|
26
31
|
import { loadPlugins, } from '@stratusagent/plugins';
|
|
27
|
-
import { BUILTIN_EXECUTOR_NAME, BUILTIN_MEMORY_STORE_NAME, BUILTIN_PROVIDER_NAMES, createDemoTool, createFileCredentialResolver, resolveDaemonCredential, createHomeMemoryStore, drainSharedMemory, hasBracketedLegacyStateIn, createRuntimeProvider, describeServingModel, DEFAULT_STRATUS_AGENT, isRegisteredProviderName, loadChannelTransportSecrets, registeredProviderNameOf, loadOperatorSkills, loadRosterSouls, FALLBACK_ACTIVE_METADATA_KEY, loadSoulFile, grantSoulCredential, withSoulFileLock, addNamedCredential, appendCredentialAudit, replaceNamedCredential, NamedCredentialMissingError, namedCredentialSource, NamedCredentialExistsError, CREDENTIAL_NAME_PATTERN, quoteShellArg, migrateLegacyMemory, PROVIDER_STATE_METADATA_KEYS, ConfigFileError, loadConfigFile, resolveConfigLocation, resolveConfiguredSoul, resolveRuntimeConfig, runsOnHarness, applySoulPins, assertStateCompatible, unlistedToolsFor, stratusHomePath,
|
|
32
|
+
import { BUILTIN_EXECUTOR_NAME, BUILTIN_MEMORY_STORE_NAME, BUILTIN_PROVIDER_NAMES, createDemoTool, createFileCredentialResolver, credentialValuesFor, resolveDaemonCredential, createHomeMemoryStore, drainSharedMemory, hasBracketedLegacyStateIn, createRuntimeProvider, describeServingModel, DEFAULT_STRATUS_AGENT, isRegisteredProviderName, loadChannelTransportSecrets, registeredProviderNameOf, loadOperatorSkills, loadRosterSouls, FALLBACK_ACTIVE_METADATA_KEY, loadSoulFile, grantSoulCredential, withSoulFileLock, addNamedCredential, appendCredentialAudit, replaceNamedCredential, NamedCredentialMissingError, namedCredentialSource, NamedCredentialExistsError, CREDENTIAL_NAME_PATTERN, quoteShellArg, migrateLegacyMemory, PROVIDER_STATE_METADATA_KEYS, ConfigFileError, loadConfigFile, resolveConfigLocation, resolveConfiguredSoul, resolveRuntimeConfig, runsOnHarness, applySoulPins, assertStateCompatible, unlistedToolsFor, stratusHomePath, fleetDbIn, createAgentWorkspaces, createPluginStateDirectories, createHostProtectedPaths, foldedAgentId, } from '@stratusagent/state';
|
|
28
33
|
const DEFAULT_IDLE_TIMEOUT_MS = 120_000;
|
|
29
34
|
/**
|
|
30
35
|
* How long a parked call waits for a person. Long enough to survive a
|
|
@@ -48,142 +53,6 @@ const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
|
48
53
|
* question even when the rule answering it is not gateway code.
|
|
49
54
|
*/
|
|
50
55
|
export { applySoulPins } from '@stratusagent/state';
|
|
51
|
-
/** What a turn cut short by an announced restart records as its failure. */
|
|
52
|
-
export const RESTARTING_TURN_ERROR = 'Run aborted: stratusd is restarting';
|
|
53
|
-
/** The error a turn still running when a stop's drain window closes is aborted with. */
|
|
54
|
-
export const STOPPING_TURN_ERROR = 'Run aborted: stratusd is stopping';
|
|
55
|
-
/**
|
|
56
|
-
* How long an announced restart lets in-flight turns finish before aborting
|
|
57
|
-
* them. The same as a stop's window (`DEFAULT_STOP_DRAIN_TIMEOUT_MS`), so the
|
|
58
|
-
* two windows an operator can hit agree.
|
|
59
|
-
*/
|
|
60
|
-
export const DEFAULT_RESTART_DRAIN_TIMEOUT_MS = 30_000;
|
|
61
|
-
/** How long a roster reload waits on one channel's `reload()`. */
|
|
62
|
-
export const DEFAULT_CHANNEL_RELOAD_TIMEOUT_MS = 30_000;
|
|
63
|
-
/**
|
|
64
|
-
* How long a stop lets in-flight turns finish before aborting them. The
|
|
65
|
-
* daemon's documented drain ("up to 30 seconds"), and shorter than the
|
|
66
|
-
* service units' stop timeouts, so the abort happens in this process
|
|
67
|
-
* rather than as the manager's SIGKILL. That matters beyond the turn's
|
|
68
|
-
* record: a shell command runs in its own process group, which launchd's
|
|
69
|
-
* kill of the job does not reach, so only the turn's abort stops it.
|
|
70
|
-
*/
|
|
71
|
-
export const DEFAULT_STOP_DRAIN_TIMEOUT_MS = 30_000;
|
|
72
|
-
/** What a paused daemon says when nobody gave it a sentence of its own. */
|
|
73
|
-
export const DEFAULT_PAUSED_MESSAGE = "I'm paused right now and not taking new work. Please try again later.";
|
|
74
|
-
/** What a held gateway answers a dispatch with, before it is promoted. */
|
|
75
|
-
export const HELD_MESSAGE = "I'm starting up and not taking work yet. Please try again in a moment.";
|
|
76
|
-
/** The longest pause message, so a reply to a refused message stays one. */
|
|
77
|
-
export const MAX_PAUSED_MESSAGE_LENGTH = 500;
|
|
78
|
-
/** A pause message the gateway will not take: empty, or too long. */
|
|
79
|
-
export class PauseMessageError extends Error {
|
|
80
|
-
constructor(message) {
|
|
81
|
-
super(message);
|
|
82
|
-
this.name = 'PauseMessageError';
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* A dispatch refused because intake is paused. Its message is the sentence
|
|
87
|
-
* to show whoever sent the work, as it is: a channel posts it as the reply,
|
|
88
|
-
* not as a failure.
|
|
89
|
-
*/
|
|
90
|
-
export class IntakePausedError extends Error {
|
|
91
|
-
constructor(message) {
|
|
92
|
-
super(message);
|
|
93
|
-
this.name = 'IntakePausedError';
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* A dispatch refused because the daemon is restarting or stopping. Channels
|
|
98
|
-
* stay connected while a restart drains, so they hear people during it, and
|
|
99
|
-
* this is what tells them the message reached a daemon on its way down: a
|
|
100
|
-
* channel can say so and keep the message for the next one. Matched by name
|
|
101
|
-
* there, as channel packages do not import the gateway.
|
|
102
|
-
*/
|
|
103
|
-
export class GatewayStoppingError extends Error {
|
|
104
|
-
/** True for a restart, which comes back on its own; false for a stop. */
|
|
105
|
-
restarting;
|
|
106
|
-
constructor(message, restarting) {
|
|
107
|
-
super(message);
|
|
108
|
-
this.name = 'GatewayStoppingError';
|
|
109
|
-
this.restarting = restarting;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* The intake state a previous process left, or open when there is none.
|
|
114
|
-
*
|
|
115
|
-
* Fails closed: a file that exists but cannot be read or parsed starts the
|
|
116
|
-
* daemon paused, with a warning naming it. A pause exists to stop work (a
|
|
117
|
-
* spending cap, a maintenance window), and a file nobody can read is no
|
|
118
|
-
* evidence that it was lifted.
|
|
119
|
-
*/
|
|
120
|
-
const readIntakeState = async (env, warn) => {
|
|
121
|
-
const file = intakeStatePath(env);
|
|
122
|
-
let raw;
|
|
123
|
-
try {
|
|
124
|
-
raw = await readFile(file, 'utf8');
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
if (error.code === 'ENOENT') {
|
|
128
|
-
return { paused: false };
|
|
129
|
-
}
|
|
130
|
-
warn(`intake: ${file} could not be read (${error.message}); starting paused. Resume intake to clear it.`);
|
|
131
|
-
return { paused: true, since: new Date().toISOString(), unreadable: true };
|
|
132
|
-
}
|
|
133
|
-
try {
|
|
134
|
-
const parsed = JSON.parse(raw);
|
|
135
|
-
// Only a pause is ever written here (resuming removes the file), so
|
|
136
|
-
// anything else in it is not a record this build wrote, and the same
|
|
137
|
-
// rule as an unreadable file applies.
|
|
138
|
-
if (typeof parsed !== 'object' || parsed === null || parsed.paused !== true) {
|
|
139
|
-
warn(`intake: ${file} does not hold a pause record; starting paused. Resume intake to clear it.`);
|
|
140
|
-
return { paused: true, since: new Date().toISOString(), unreadable: true };
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
paused: true,
|
|
144
|
-
...(typeof parsed.message === 'string' && parsed.message.trim().length > 0 ? { message: parsed.message } : {}),
|
|
145
|
-
since: typeof parsed.since === 'string' ? parsed.since : new Date().toISOString(),
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
warn(`intake: ${file} is not valid JSON; starting paused. Resume intake to clear it.`);
|
|
150
|
-
return { paused: true, since: new Date().toISOString(), unreadable: true };
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
/**
|
|
154
|
-
* Record a pause durably (0600, replaced in one atomic step), or remove the
|
|
155
|
-
* record when intake reopens: open is the default, so nothing on disk
|
|
156
|
-
* means open.
|
|
157
|
-
*/
|
|
158
|
-
const writeIntakeState = async (env, state) => {
|
|
159
|
-
const file = intakeStatePath(env);
|
|
160
|
-
if (!state.paused) {
|
|
161
|
-
await rm(file, { force: true });
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
165
|
-
const staging = `${file}.${randomBytes(8).toString('hex')}`;
|
|
166
|
-
try {
|
|
167
|
-
await writeFileSynced(staging, `${JSON.stringify(state)}\n`, { flag: 'wx', mode: 0o600 });
|
|
168
|
-
await rename(staging, file);
|
|
169
|
-
await syncDirectory(path.dirname(file));
|
|
170
|
-
}
|
|
171
|
-
finally {
|
|
172
|
-
await rm(staging, { force: true });
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
/**
|
|
176
|
-
* `restart()` on a gateway whose host gave it no way back. Exported so the
|
|
177
|
-
* control API can answer it as "not supported here" rather than as a
|
|
178
|
-
* failure of the daemon.
|
|
179
|
-
*/
|
|
180
|
-
export class RestartUnsupportedError extends Error {
|
|
181
|
-
constructor() {
|
|
182
|
-
super('This daemon cannot restart itself: the host that started it did not say how. '
|
|
183
|
-
+ 'Restart it from the service manager (stratus service stop, then start) or from the terminal it runs in.');
|
|
184
|
-
this.name = 'RestartUnsupportedError';
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
56
|
/**
|
|
188
57
|
* The most text one merged user message may hold (card 423). The runner's
|
|
189
58
|
* overflow recovery can drop earlier transcript but never part of the
|
|
@@ -253,143 +122,6 @@ const formCredentialValue = (value) => value.replace(/^[\r\n]+|[\r\n]+$/g, '');
|
|
|
253
122
|
* so it has none of its own and is fine as sent.
|
|
254
123
|
*/
|
|
255
124
|
const isFlattenedPem = (value) => /^\s*-----BEGIN [A-Z0-9 ]+-----/.test(value) && !value.includes('\n');
|
|
256
|
-
/**
|
|
257
|
-
* Recorded on a turn the previous process was still running when it died.
|
|
258
|
-
*
|
|
259
|
-
* Exported because "distinguishable" is the whole point: a surface, a
|
|
260
|
-
* test, or an operator reading `lastError` has to be able to tell a turn
|
|
261
|
-
* nobody finished apart from one a provider or a tool actually failed, and
|
|
262
|
-
* matching on prose that lives in one place beats each caller inventing
|
|
263
|
-
* its own guess at the wording.
|
|
264
|
-
*/
|
|
265
|
-
export const ABANDONED_TURN_ERROR = 'stratusd stopped while this turn was still running; it was not resumed. Send the message again.';
|
|
266
|
-
/**
|
|
267
|
-
* The longest `DispatchInput.idempotencyKey` accepted. A platform message id
|
|
268
|
-
* is far shorter; the bound is so a key cannot be used to grow every row it
|
|
269
|
-
* is stored on.
|
|
270
|
-
*/
|
|
271
|
-
/**
|
|
272
|
-
* Recorded on a delegated sub-session that was parked on a human when the
|
|
273
|
-
* previous process died.
|
|
274
|
-
*
|
|
275
|
-
* Its parent — the turn whose `agent.delegate` call was awaiting the reply
|
|
276
|
-
* — was running, so the restart settles it as abandoned (above). Recovering
|
|
277
|
-
* the child anyway would re-ask a person to approve a call whose result no
|
|
278
|
-
* turn will ever read, and then run it: a command executed, tokens spent,
|
|
279
|
-
* and a reply delivered to nobody. Distinguishable from the parent's error
|
|
280
|
-
* because the remedy differs — there is no message to send again here;
|
|
281
|
-
* the parent's is the one to repeat.
|
|
282
|
-
*/
|
|
283
|
-
/**
|
|
284
|
-
* Session metadata keys the daemon writes for itself, which no external
|
|
285
|
-
* dispatch may supply.
|
|
286
|
-
*
|
|
287
|
-
* Metadata is one bag: the keys a channel attaches to say where a turn is
|
|
288
|
-
* happening sit beside the ones the kernel, the scheduler, the fallback
|
|
289
|
-
* wrapper, and `agent.delegate` write to remember what a session *is* —
|
|
290
|
-
* and the control API forwards a caller's `metadata` into a new session as
|
|
291
|
-
* given. A caller who can write `delegatedBy` can have the restart sweep
|
|
292
|
-
* fail its own parked turn as an orphan; one who can write `fallbackActive`
|
|
293
|
-
* bills its conversation to the fallback model from the first turn. The
|
|
294
|
-
* scheduler already refused to trust its own keys for exactly this reason
|
|
295
|
-
* (see `activeFirings`); the durable ones cannot be re-derived in memory,
|
|
296
|
-
* so they are refused at the door instead.
|
|
297
|
-
*/
|
|
298
|
-
/**
|
|
299
|
-
* A rolled-over session: the fresh row records where its predecessor's
|
|
300
|
-
* transcript went, and the archived row records which live id continues
|
|
301
|
-
* the conversation. See `Gateway.rolloverSession`.
|
|
302
|
-
*/
|
|
303
|
-
export const ROLLED_OVER_FROM_METADATA_KEY = 'rolledOverFrom';
|
|
304
|
-
export const ROLLED_OVER_TO_METADATA_KEY = 'rolledOverTo';
|
|
305
|
-
/** The segment a rollover mints into the archived transcript's id. */
|
|
306
|
-
export const ROLLED_OVER_SESSION_ID_MARKER = ':rolledover:';
|
|
307
|
-
/**
|
|
308
|
-
* How long after a rollover a repeated key is still looked for in the
|
|
309
|
-
* archived transcript. A rollover moves every keyed message out of the
|
|
310
|
-
* live row, so a redelivery arriving after one would otherwise read as a
|
|
311
|
-
* new message and run again. Platforms redeliver within minutes — Slack
|
|
312
|
-
* gives up after about five — and past the window the archive is not
|
|
313
|
-
* loaded at all, since every new message to the fresh row would pay for a
|
|
314
|
-
* read that can no longer find anything.
|
|
315
|
-
*/
|
|
316
|
-
const ROLLOVER_REPEAT_WINDOW_MS = 60 * 60 * 1000;
|
|
317
|
-
/**
|
|
318
|
-
* The one refusal for a dispatch naming an agent other than its session's.
|
|
319
|
-
* Its own type so a repeat attached to a claimant can tell the claimant was
|
|
320
|
-
* refused for who it named — the item then still has no owner — from the
|
|
321
|
-
* claimant's turn failing. Internal: callers read the message.
|
|
322
|
-
*/
|
|
323
|
-
class CrossIdentityError extends Error {
|
|
324
|
-
constructor(sessionId, owner, claimed) {
|
|
325
|
-
super(`Session ${sessionId} belongs to agent ${owner}, not ${claimed} — sessions never cross agent identities.`);
|
|
326
|
-
this.name = 'CrossIdentityError';
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
const crossIdentityError = (sessionId, owner, claimed) => new CrossIdentityError(sessionId, owner, claimed);
|
|
330
|
-
/** Where the session's tool calls ran: `local-command`, or a contributed executor's registered name. */
|
|
331
|
-
export const EXECUTOR_METADATA_KEY = 'executor';
|
|
332
|
-
/**
|
|
333
|
-
* Marks a sub-agent's session (roadmap 24). Reserved: it is what makes the
|
|
334
|
-
* daemon deny the session's gated calls without asking anyone, so a caller
|
|
335
|
-
* that could set it could also clear it.
|
|
336
|
-
*/
|
|
337
|
-
export const SUB_AGENT_METADATA_KEY = 'subAgent';
|
|
338
|
-
export const SUB_AGENT_SESSION_ID_MARKER = ':subagent:';
|
|
339
|
-
/** How many sub-agents one parent turn runs at once unless the host says otherwise. */
|
|
340
|
-
export const DEFAULT_SUB_AGENTS_PER_TURN = 4;
|
|
341
|
-
export const isSubAgentSession = (session) => session.metadata?.[SUB_AGENT_METADATA_KEY] === true && session.id.includes(SUB_AGENT_SESSION_ID_MARKER);
|
|
342
|
-
/**
|
|
343
|
-
* Tools a sub-agent never gets, whatever its parent holds. `agent.spawn`
|
|
344
|
-
* would fan out inside a fan-out; `agent.delegate` runs a roster agent
|
|
345
|
-
* under that agent's own allowlist, which is the way out of a narrowed
|
|
346
|
-
* slice; `memory.*` would let it persist what nobody reviewed; and
|
|
347
|
-
* `credential.request` asks a person, which a sub-agent never does.
|
|
348
|
-
*/
|
|
349
|
-
const subAgentNeverGets = (name) => name === 'agent.spawn'
|
|
350
|
-
|| name === DELEGATE_TOOL_NAME
|
|
351
|
-
|| name.startsWith('memory.')
|
|
352
|
-
|| name === CREDENTIAL_REQUEST_TOOL_NAME;
|
|
353
|
-
export const RESERVED_SESSION_METADATA_KEYS = [
|
|
354
|
-
PENDING_APPROVAL_METADATA_KEY,
|
|
355
|
-
FALLBACK_ACTIVE_METADATA_KEY,
|
|
356
|
-
DELEGATED_BY_METADATA_KEY,
|
|
357
|
-
ROOT_SESSION_ID_METADATA_KEY,
|
|
358
|
-
DELEGATION_DEPTH_METADATA_KEY,
|
|
359
|
-
SCHEDULED_TURN_METADATA_KEY,
|
|
360
|
-
SCHEDULE_ID_METADATA_KEY,
|
|
361
|
-
// The trust label is the runner's to write and only ever goes down; a
|
|
362
|
-
// caller who could seed it would be seeding it upward.
|
|
363
|
-
SESSION_TRUST_METADATA_KEY,
|
|
364
|
-
SESSION_TAINTED_BY_METADATA_KEY,
|
|
365
|
-
ROLLED_OVER_FROM_METADATA_KEY,
|
|
366
|
-
ROLLED_OVER_TO_METADATA_KEY,
|
|
367
|
-
// Which executor ran the turn's tool calls is the daemon's record of
|
|
368
|
-
// where a command ran, and the one audit question a caller must not be
|
|
369
|
-
// able to answer for it.
|
|
370
|
-
EXECUTOR_METADATA_KEY,
|
|
371
|
-
// The context floor is an index into a specific transcript, so it means
|
|
372
|
-
// nothing next to a different one. Reserved for both halves of that: a
|
|
373
|
-
// caller cannot seed it, and a rollover drops it with the transcript it
|
|
374
|
-
// indexed — otherwise the fresh session inherits an absolute floor over
|
|
375
|
-
// no messages and, once it grows past that index, silently stops sending
|
|
376
|
-
// its own earliest turns, with no overflow and no trim event to say so.
|
|
377
|
-
CONTEXT_FLOOR_METADATA_KEY,
|
|
378
|
-
// The summary that stands in below that floor, for both of the same
|
|
379
|
-
// reasons, and one more: the model reads it as something it wrote itself,
|
|
380
|
-
// so a caller who could seed it could put words in the agent's mouth.
|
|
381
|
-
CONTEXT_SUMMARY_METADATA_KEY,
|
|
382
|
-
SUB_AGENT_METADATA_KEY,
|
|
383
|
-
];
|
|
384
|
-
/**
|
|
385
|
-
* The first reserved key `metadata` carries, or undefined when it carries
|
|
386
|
-
* none. Exported so the control API can refuse the request where the
|
|
387
|
-
* caller can see it, with the same rule the gateway applies at dispatch.
|
|
388
|
-
*/
|
|
389
|
-
export const reservedSessionMetadataKey = (metadata) => metadata === undefined
|
|
390
|
-
? undefined
|
|
391
|
-
: RESERVED_SESSION_METADATA_KEYS.find((key) => Object.prototype.hasOwnProperty.call(metadata, key));
|
|
392
|
-
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.';
|
|
393
125
|
/**
|
|
394
126
|
* The always-on Stratus process. One gateway holds the roster, the durable
|
|
395
127
|
* session store, the shared event bus, and a pool of runners keyed by
|
|
@@ -766,6 +498,7 @@ export const createGateway = (options = {}) => {
|
|
|
766
498
|
...(request.origin !== undefined ? { origin: request.origin } : {}),
|
|
767
499
|
...(oneShot ? { oneShot: true } : {}),
|
|
768
500
|
...(always !== undefined ? { always } : {}),
|
|
501
|
+
...(request.reviewNote !== undefined ? { reviewNote: request.reviewNote } : {}),
|
|
769
502
|
parkedAt: request.parkedAt ?? new Date().toISOString(),
|
|
770
503
|
...(expiresAt ? { expiresAt } : {}),
|
|
771
504
|
...(request.session.metadata ? { metadata: request.session.metadata } : {}),
|
|
@@ -816,6 +549,7 @@ export const createGateway = (options = {}) => {
|
|
|
816
549
|
...(request.origin !== undefined ? { origin: request.origin } : {}),
|
|
817
550
|
...(oneShot ? { oneShot: true } : {}),
|
|
818
551
|
...(always !== undefined ? { always } : {}),
|
|
552
|
+
...(request.reviewNote !== undefined ? { reviewNote: request.reviewNote } : {}),
|
|
819
553
|
...(request.session.metadata ? { metadata: request.session.metadata } : {}),
|
|
820
554
|
...(delegatedFrom ? { delegatedFrom } : {}),
|
|
821
555
|
...(scheduleDestination ? { scheduleDestination } : {}),
|
|
@@ -886,6 +620,17 @@ export const createGateway = (options = {}) => {
|
|
|
886
620
|
const configuredApprovals = typeof options.approvals === 'function'
|
|
887
621
|
? options.approvals({
|
|
888
622
|
request: requestApproval,
|
|
623
|
+
// Late-bound: the side call is built further down this function.
|
|
624
|
+
review: (request) => reviewCall(request),
|
|
625
|
+
// Rejects when the soul or the credential store can't be read: the
|
|
626
|
+
// permission engine then asks a person, rather than checking the
|
|
627
|
+
// call against no secrets at all.
|
|
628
|
+
secretsFor: async (agentId) => {
|
|
629
|
+
const { definition } = await refreshAgent(agentId);
|
|
630
|
+
return credentialValuesFor(createFileCredentialResolver(env), definition);
|
|
631
|
+
},
|
|
632
|
+
// Late-bound like `review`: the set is declared with the side call.
|
|
633
|
+
offPath: (task) => keepUntilStopped(task),
|
|
889
634
|
destinations: {
|
|
890
635
|
isPreauthorized: (session, destination) => scheduler.isPreauthorized(session, destination),
|
|
891
636
|
accountFor: (agentId, destination) => {
|
|
@@ -2760,6 +2505,25 @@ export const createGateway = (options = {}) => {
|
|
|
2760
2505
|
* session sufficient.
|
|
2761
2506
|
*/
|
|
2762
2507
|
const activeTurns = new Map();
|
|
2508
|
+
/**
|
|
2509
|
+
* The runtime each running turn resolved when it started, by session. A
|
|
2510
|
+
* reviewer call for one of its calls runs on it (`reviewCall`), so a soul
|
|
2511
|
+
* or config edited mid-turn can't send that turn's request to a provider
|
|
2512
|
+
* that wasn't serving it. Exact for the same reason `activeTurns` is: one
|
|
2513
|
+
* turn per session at a time, set and cleared inside the session chain.
|
|
2514
|
+
*/
|
|
2515
|
+
const turnRuntimes = new Map();
|
|
2516
|
+
const onTurnRuntime = async (sessionId, runtime, work) => {
|
|
2517
|
+
turnRuntimes.set(sessionId, runtime);
|
|
2518
|
+
try {
|
|
2519
|
+
return await work();
|
|
2520
|
+
}
|
|
2521
|
+
finally {
|
|
2522
|
+
if (turnRuntimes.get(sessionId) === runtime) {
|
|
2523
|
+
turnRuntimes.delete(sessionId);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
};
|
|
2763
2527
|
/**
|
|
2764
2528
|
* Keyed dispatches not yet settled, by session and key — what a repeat
|
|
2765
2529
|
* attaches to. In memory on purpose: it only has to cover the turns this
|
|
@@ -2981,7 +2745,7 @@ export const createGateway = (options = {}) => {
|
|
|
2981
2745
|
// has switched for good, the primary is never called again, and only
|
|
2982
2746
|
// the fallback counts.
|
|
2983
2747
|
const hostedLoop = input.idempotencyKey !== undefined && reachesHarness(config, switchedToFallback);
|
|
2984
|
-
return settleLeftForNextStart(input, withWatchdog(input.sessionId, input.signal, effectiveStreams, fallbackStreams, async (signal) => {
|
|
2748
|
+
return settleLeftForNextStart(input, onTurnRuntime(input.sessionId, { source, config }, () => withWatchdog(input.sessionId, input.signal, effectiveStreams, fallbackStreams, async (signal) => {
|
|
2985
2749
|
// The preflight above (agent refresh, config resolution, session
|
|
2986
2750
|
// load) awaited filesystem work; the entry check has long passed.
|
|
2987
2751
|
// Recheck before the runner touches durable state, or a dispatch
|
|
@@ -3064,7 +2828,7 @@ export const createGateway = (options = {}) => {
|
|
|
3064
2828
|
...(steer !== undefined ? { steer: () => steer(signal) } : {}),
|
|
3065
2829
|
signal,
|
|
3066
2830
|
});
|
|
3067
|
-
}));
|
|
2831
|
+
})));
|
|
3068
2832
|
};
|
|
3069
2833
|
/**
|
|
3070
2834
|
* Finishes the turns that were parked on a human when this daemon last
|
|
@@ -3235,11 +2999,11 @@ export const createGateway = (options = {}) => {
|
|
|
3235
2999
|
}
|
|
3236
3000
|
}
|
|
3237
3001
|
try {
|
|
3238
|
-
await runner.recoverPendingApproval(sessionId, {
|
|
3002
|
+
await onTurnRuntime(sessionId, { source, config: recoveredConfig }, () => runner.recoverPendingApproval(sessionId, {
|
|
3239
3003
|
denyPending: expired,
|
|
3240
3004
|
runtime: runtimeContextFor(source, recoveredConfig, session.metadata?.[FALLBACK_ACTIVE_METADATA_KEY] === true, session.metadata),
|
|
3241
3005
|
signal: controller.signal,
|
|
3242
|
-
});
|
|
3006
|
+
}));
|
|
3243
3007
|
}
|
|
3244
3008
|
finally {
|
|
3245
3009
|
untrackTurn(sessionId, controller);
|
|
@@ -3449,10 +3213,10 @@ export const createGateway = (options = {}) => {
|
|
|
3449
3213
|
session.agent = source.definition;
|
|
3450
3214
|
await store.save(session);
|
|
3451
3215
|
const runner = runnerFor(config, source.definition.id);
|
|
3452
|
-
return withWatchdog(session.id, undefined, effectiveStreams, fallbackStreams, async (signal) => await runner.continueTurn(session.id, {
|
|
3216
|
+
return onTurnRuntime(session.id, { source, config }, () => withWatchdog(session.id, undefined, effectiveStreams, fallbackStreams, async (signal) => await runner.continueTurn(session.id, {
|
|
3453
3217
|
runtime: runtimeContextFor(source, config, switchedToFallback, session.metadata),
|
|
3454
3218
|
signal,
|
|
3455
|
-
}) ?? session);
|
|
3219
|
+
}) ?? session));
|
|
3456
3220
|
};
|
|
3457
3221
|
/**
|
|
3458
3222
|
* A repeated idempotency key that reached the session's chain, so nothing
|
|
@@ -3686,6 +3450,8 @@ export const createGateway = (options = {}) => {
|
|
|
3686
3450
|
return turn;
|
|
3687
3451
|
};
|
|
3688
3452
|
// ---- memory passes (spec 962) -------------------------------------------
|
|
3453
|
+
/** The session ids of the side runs running now: reviewer calls and memory passes. */
|
|
3454
|
+
const sideRuns = new Set();
|
|
3689
3455
|
/**
|
|
3690
3456
|
* A pass's one model turn: the agent's provider and credentials, exactly
|
|
3691
3457
|
* the model asked for and never the fallback (cost stays what the
|
|
@@ -3694,18 +3460,16 @@ export const createGateway = (options = {}) => {
|
|
|
3694
3460
|
* go through the same provider-call guards as every turn, and its events
|
|
3695
3461
|
* through the same bus, so a budget sees and counts them.
|
|
3696
3462
|
*/
|
|
3697
|
-
const
|
|
3698
|
-
const source = await refreshAgent(run.agentId);
|
|
3699
|
-
const resolved = await runtimeForAgent(source);
|
|
3463
|
+
const runSideModel = async (run) => {
|
|
3464
|
+
const source = run.runtime?.source ?? await refreshAgent(run.agentId);
|
|
3465
|
+
const resolved = run.runtime?.config ?? await runtimeForAgent(source);
|
|
3700
3466
|
const { fallback: _fallback, ...primary } = resolved;
|
|
3701
3467
|
const config = (run.model !== undefined ? { ...primary, model: run.model } : primary);
|
|
3702
3468
|
const tools = new ToolRegistry();
|
|
3703
3469
|
for (const tool of run.tools) {
|
|
3704
3470
|
tools.register(tool);
|
|
3705
3471
|
}
|
|
3706
|
-
|
|
3707
|
-
// pass's model-call cap.
|
|
3708
|
-
const maxTurns = MEMORY_PASS_MAX_MODEL_CALLS - 1;
|
|
3472
|
+
const { maxTurns } = run;
|
|
3709
3473
|
let passRunner;
|
|
3710
3474
|
const provider = createRuntimeProvider(config, () => { }, async (session, call, context) => {
|
|
3711
3475
|
if (!passRunner) {
|
|
@@ -3724,25 +3488,126 @@ export const createGateway = (options = {}) => {
|
|
|
3724
3488
|
const definition = {
|
|
3725
3489
|
id: source.definition.id,
|
|
3726
3490
|
name: source.definition.name,
|
|
3727
|
-
instructions:
|
|
3491
|
+
instructions: run.instructions,
|
|
3728
3492
|
tools: run.tools.map((tool) => tool.name),
|
|
3729
3493
|
skills: [],
|
|
3730
3494
|
delegates: [],
|
|
3731
3495
|
...(source.definition.credentials !== undefined ? { credentials: [...source.definition.credentials] } : {}),
|
|
3732
3496
|
};
|
|
3733
|
-
|
|
3497
|
+
// Its events go on the shared bus like a turn's, for what counts provider
|
|
3498
|
+
// calls there (`session.usage`), and are known as a side run's for as
|
|
3499
|
+
// long as it runs (`isSideRun`), so a surface that shows conversations
|
|
3500
|
+
// can leave it out.
|
|
3501
|
+
sideRuns.add(run.sessionId);
|
|
3502
|
+
try {
|
|
3503
|
+
return await passRunner.run({
|
|
3504
|
+
sessionId: run.sessionId,
|
|
3505
|
+
agent: definition,
|
|
3506
|
+
userMessage: run.prompt,
|
|
3507
|
+
metadata: {
|
|
3508
|
+
provider: config.provider,
|
|
3509
|
+
...(config.provider !== 'demo' && config.model !== undefined ? { model: config.model } : {}),
|
|
3510
|
+
[SENDER_TRUST_METADATA_KEY]: run.trust,
|
|
3511
|
+
},
|
|
3512
|
+
signal: run.signal,
|
|
3513
|
+
});
|
|
3514
|
+
}
|
|
3515
|
+
finally {
|
|
3516
|
+
sideRuns.delete(run.sessionId);
|
|
3517
|
+
}
|
|
3518
|
+
};
|
|
3519
|
+
const runMemoryPassModel = async (run) => {
|
|
3520
|
+
await runSideModel({
|
|
3521
|
+
agentId: run.agentId,
|
|
3734
3522
|
sessionId: run.sessionId,
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3523
|
+
model: run.model,
|
|
3524
|
+
instructions: MEMORY_PASS_RULES,
|
|
3525
|
+
prompt: run.prompt,
|
|
3526
|
+
// The slice's label: the pass reads at the conversation's trust.
|
|
3527
|
+
trust: run.trust,
|
|
3528
|
+
tools: run.tools,
|
|
3529
|
+
// A step budget of n is n tool steps and one wrap-up, so this is the
|
|
3530
|
+
// pass's model-call cap.
|
|
3531
|
+
maxTurns: MEMORY_PASS_MAX_MODEL_CALLS - 1,
|
|
3743
3532
|
signal: run.signal,
|
|
3744
3533
|
});
|
|
3745
3534
|
};
|
|
3535
|
+
// ---- the review posture's reviewer (spec 964) ----------------------------
|
|
3536
|
+
/**
|
|
3537
|
+
* Reviewer calls still running, and the shadow reviews the policy hands
|
|
3538
|
+
* over whole (`ApprovalTransport.offPath`). A turn awaits its own, but a
|
|
3539
|
+
* shadow review runs off the call path and can outlast its turn, still
|
|
3540
|
+
* reading credentials before its reviewer call starts, and a provider
|
|
3541
|
+
* that ignores its abort after the review's timeout keeps one going with
|
|
3542
|
+
* nothing waiting on it. A shutdown aborts these once the turns have
|
|
3543
|
+
* drained and waits for them, so none is still using a provider when the
|
|
3544
|
+
* plugins are disposed, and no decision they report is lost.
|
|
3545
|
+
*/
|
|
3546
|
+
const reviewsRunning = new Set();
|
|
3547
|
+
const stopReviews = new AbortController();
|
|
3548
|
+
const keepUntilStopped = (task) => {
|
|
3549
|
+
reviewsRunning.add(task);
|
|
3550
|
+
const untrack = () => {
|
|
3551
|
+
reviewsRunning.delete(task);
|
|
3552
|
+
};
|
|
3553
|
+
task.then(untrack, untrack);
|
|
3554
|
+
};
|
|
3555
|
+
const reviewCall = async (request) => {
|
|
3556
|
+
if (request.command !== undefined && !callShownWhole(request.call)) {
|
|
3557
|
+
// A command line the case can't show whole could hide its end, and
|
|
3558
|
+
// the reviewer would judge only the part it saw: a person decides.
|
|
3559
|
+
return { failed: 'command-too-long' };
|
|
3560
|
+
}
|
|
3561
|
+
const firing = scheduler.firingPromptFor(request.session);
|
|
3562
|
+
const prompt = reviewCaseText({
|
|
3563
|
+
session: request.session,
|
|
3564
|
+
call: request.call,
|
|
3565
|
+
tool: request.tool,
|
|
3566
|
+
risk: request.risk,
|
|
3567
|
+
...(request.command !== undefined ? { command: request.command } : {}),
|
|
3568
|
+
...(request.origin !== undefined ? { origin: request.origin } : {}),
|
|
3569
|
+
...(request.destination !== undefined ? { destination: request.destination } : {}),
|
|
3570
|
+
...(firing !== undefined ? { firing } : {}),
|
|
3571
|
+
delegated: isSubAgentSession(request.session),
|
|
3572
|
+
});
|
|
3573
|
+
if (stopReviews.signal.aborted) {
|
|
3574
|
+
return { failed: 'cancelled' };
|
|
3575
|
+
}
|
|
3576
|
+
const signal = AbortSignal.any([request.signal, stopReviews.signal]);
|
|
3577
|
+
// On the reviewed turn's own runtime while it runs. A review after it
|
|
3578
|
+
// has ended (a shadow review can outlast its turn) takes the agent's
|
|
3579
|
+
// current one, which is where the session's next turn sends the same
|
|
3580
|
+
// conversation anyway.
|
|
3581
|
+
const runtime = turnRuntimes.get(request.session.id);
|
|
3582
|
+
const running = runSideModel({
|
|
3583
|
+
agentId: request.session.agent.id,
|
|
3584
|
+
sessionId: `review:${randomUUID()}`,
|
|
3585
|
+
model: request.model,
|
|
3586
|
+
instructions: REVIEW_RULES,
|
|
3587
|
+
prompt,
|
|
3588
|
+
// The case quotes a call an outside page may have shaped.
|
|
3589
|
+
trust: 'external',
|
|
3590
|
+
tools: [],
|
|
3591
|
+
maxTurns: 1,
|
|
3592
|
+
signal,
|
|
3593
|
+
...(runtime !== undefined ? { runtime } : {}),
|
|
3594
|
+
});
|
|
3595
|
+
keepUntilStopped(running);
|
|
3596
|
+
let answered;
|
|
3597
|
+
try {
|
|
3598
|
+
answered = await running;
|
|
3599
|
+
}
|
|
3600
|
+
catch (error) {
|
|
3601
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3602
|
+
return { failed: /budget/i.test(message) ? 'budget' : signal.aborted ? 'cancelled' : 'provider-error' };
|
|
3603
|
+
}
|
|
3604
|
+
if (answered.status === 'failed') {
|
|
3605
|
+
return { failed: signal.aborted ? 'cancelled' : 'provider-error' };
|
|
3606
|
+
}
|
|
3607
|
+
const reply = [...answered.messages].reverse().find((message) => message.role === 'assistant' && message.content.trim().length > 0);
|
|
3608
|
+
const parsed = reply === undefined ? undefined : readReviewReply(reply.content);
|
|
3609
|
+
return parsed === undefined ? { failed: 'unparseable' } : { reply: parsed };
|
|
3610
|
+
};
|
|
3746
3611
|
const memoryPassHost = createMemoryPassHost({
|
|
3747
3612
|
// Beside each agent's memory.jsonl, under the home the memory store uses.
|
|
3748
3613
|
stateDir: stratusHomePath(env),
|
|
@@ -4817,6 +4682,12 @@ export const createGateway = (options = {}) => {
|
|
|
4817
4682
|
while (inflight.size > 0) {
|
|
4818
4683
|
await Promise.allSettled([...inflight]);
|
|
4819
4684
|
}
|
|
4685
|
+
// Then the reviews no turn is waiting on: aborted rather than drained,
|
|
4686
|
+
// since what they report is about turns already over, and waited for.
|
|
4687
|
+
stopReviews.abort(new Error('The daemon is stopping.'));
|
|
4688
|
+
while (reviewsRunning.size > 0) {
|
|
4689
|
+
await Promise.allSettled([...reviewsRunning]);
|
|
4690
|
+
}
|
|
4820
4691
|
// Then the channels, whose stop awaits the rendering they still have
|
|
4821
4692
|
// in flight: the outcomes of the turns that just finished.
|
|
4822
4693
|
await stopChannels();
|
|
@@ -4847,6 +4718,7 @@ export const createGateway = (options = {}) => {
|
|
|
4847
4718
|
// Before the loop, so a turn queued behind one aborted here, which
|
|
4848
4719
|
// starts as soon as that one settles, is aborted the same way.
|
|
4849
4720
|
abortingWith = kind === 'stop' ? STOPPING_TURN_ERROR : RESTARTING_TURN_ERROR;
|
|
4721
|
+
stopReviews.abort(new Error('The daemon is stopping.'));
|
|
4850
4722
|
for (const running of turnControllers.values()) {
|
|
4851
4723
|
for (const controller of running) {
|
|
4852
4724
|
controller.abort(shutdownAbort());
|
|
@@ -5178,6 +5050,9 @@ export const createGateway = (options = {}) => {
|
|
|
5178
5050
|
activeTurnId(sessionId) {
|
|
5179
5051
|
return activeTurns.get(sessionId);
|
|
5180
5052
|
},
|
|
5053
|
+
isSideRun(sessionId) {
|
|
5054
|
+
return sideRuns.has(sessionId);
|
|
5055
|
+
},
|
|
5181
5056
|
abortTurns(sessionId, reason) {
|
|
5182
5057
|
const running = turnControllers.get(sessionId) ?? new Set();
|
|
5183
5058
|
for (const controller of running) {
|
|
@@ -5239,11 +5114,18 @@ export const createGateway = (options = {}) => {
|
|
|
5239
5114
|
const provenanceByName = toolProvenance();
|
|
5240
5115
|
return tools.describe().map((descriptor) => {
|
|
5241
5116
|
const provenance = provenanceByName.get(descriptor.name);
|
|
5117
|
+
const tool = tools.get(descriptor.name);
|
|
5118
|
+
const hooks = [
|
|
5119
|
+
['command', tool?.commandFor],
|
|
5120
|
+
['origin', tool?.originFor],
|
|
5121
|
+
['destination', tool?.destinationFor],
|
|
5122
|
+
].filter(([, hook]) => hook !== undefined).map(([name]) => name);
|
|
5242
5123
|
return {
|
|
5243
5124
|
name: descriptor.name,
|
|
5244
5125
|
...(descriptor.description ? { description: descriptor.description } : {}),
|
|
5245
5126
|
risk: descriptor.risk ?? 'gated',
|
|
5246
5127
|
...(provenance ? { package: provenance.package, trusted: provenance.trusted } : {}),
|
|
5128
|
+
...(hooks.length === 1 ? { scopedBy: hooks[0] } : {}),
|
|
5247
5129
|
};
|
|
5248
5130
|
});
|
|
5249
5131
|
},
|
|
@@ -5389,4 +5271,8 @@ export const createGateway = (options = {}) => {
|
|
|
5389
5271
|
};
|
|
5390
5272
|
return gateway;
|
|
5391
5273
|
};
|
|
5274
|
+
// Restart, stop and pause, in their own module (see the top of lifecycle.ts).
|
|
5275
|
+
export { DEFAULT_CHANNEL_RELOAD_TIMEOUT_MS, DEFAULT_PAUSED_MESSAGE, DEFAULT_RESTART_DRAIN_TIMEOUT_MS, DEFAULT_STOP_DRAIN_TIMEOUT_MS, GatewayStoppingError, HELD_MESSAGE, IntakePausedError, MAX_PAUSED_MESSAGE_LENGTH, PauseMessageError, RESTARTING_TURN_ERROR, RestartUnsupportedError, STOPPING_TURN_ERROR, } from "./lifecycle.js";
|
|
5276
|
+
// Reserved session keys and markers, in their own module (see the top of session-keys.ts).
|
|
5277
|
+
export { ABANDONED_TURN_ERROR, DEFAULT_SUB_AGENTS_PER_TURN, EXECUTOR_METADATA_KEY, isSubAgentSession, ORPHANED_DELEGATION_ERROR, RESERVED_SESSION_METADATA_KEYS, reservedSessionMetadataKey, ROLLED_OVER_FROM_METADATA_KEY, ROLLED_OVER_SESSION_ID_MARKER, ROLLED_OVER_TO_METADATA_KEY, SUB_AGENT_METADATA_KEY, SUB_AGENT_SESSION_ID_MARKER, } from "./session-keys.js";
|
|
5392
5278
|
//# sourceMappingURL=index.js.map
|