@nylorun/runtime 0.27.0-beta → 0.29.0-beta
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/CHANGELOG.md +67 -0
- package/README.md +1 -1
- package/dist/api/a2a/card.d.ts +4 -3
- package/dist/api/a2a/card.js +3 -3
- package/dist/api/a2a/routes.js +18 -7
- package/dist/api/ag-ui/routes.js +4 -7
- package/dist/api/components.d.ts +5854 -2718
- package/dist/api/components.js +39 -30
- package/dist/api/http/app.js +9 -4
- package/dist/api/http/body.d.ts +2 -0
- package/dist/api/http/body.js +20 -0
- package/dist/api/http/define.d.ts +4 -0
- package/dist/api/http/define.js +20 -9
- package/dist/api/http/routes/agent-manifests.d.ts +10 -0
- package/dist/api/http/routes/agent-manifests.js +135 -0
- package/dist/api/http/routes/agents.d.ts +11 -0
- package/dist/api/http/routes/agents.js +418 -0
- package/dist/api/http/routes/artifacts.js +2 -2
- package/dist/api/http/routes/coverage.d.ts +4 -4
- package/dist/api/http/routes/coverage.js +12 -6
- package/dist/api/http/routes/files.js +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.d.ts +1 -1
- package/dist/api/http/routes/me.js +1 -2
- package/dist/api/http/routes/memories.d.ts +9 -0
- package/dist/api/http/routes/memories.js +152 -0
- package/dist/api/http/routes/reads.d.ts +1 -137
- package/dist/api/http/routes/reads.js +31 -12
- package/dist/api/http/routes/sessions.js +21 -50
- package/dist/api/http/routes/tenant.js +13 -10
- package/dist/api/http/routes/triggers.d.ts +10 -0
- package/dist/api/http/routes/triggers.js +183 -0
- package/dist/api/openapi.js +69 -47
- package/dist/artifacts/disk.d.ts +10 -10
- package/dist/artifacts/disk.js +27 -27
- package/dist/artifacts/quota.d.ts +1 -1
- package/dist/artifacts/quota.js +2 -2
- package/dist/artifacts/service.js +2 -2
- package/dist/core/flow-host.d.ts +0 -1
- package/dist/gates/egress-credentials.js +1 -1
- package/dist/gates/egress.d.ts +5 -5
- package/dist/gates/egress.js +6 -6
- package/dist/gates/http-tool.js +2 -2
- package/dist/gates/meter.js +22 -5
- package/dist/gates/policy.d.ts +8 -3
- package/dist/gates/policy.js +7 -2
- package/dist/gates/tenant-vaults.d.ts +1 -1
- package/dist/harness/pod.d.ts +2 -0
- package/dist/harness/setup.js +17 -6
- package/dist/harness-api/record.js +11 -0
- package/dist/harness-api/server.js +4 -0
- package/dist/harness-api/start.d.ts +11 -7
- package/dist/harness-api/start.js +49 -5
- package/dist/harness-api/workspace.js +2 -2
- package/dist/host/config-for.d.ts +2 -0
- package/dist/host/config-for.js +1 -0
- package/dist/host/http.d.ts +2 -2
- package/dist/host/http.js +5 -2
- package/dist/host/main.js +1 -0
- package/dist/keys/keys.d.ts +15 -4
- package/dist/keys/keys.js +16 -0
- package/dist/keys/webhooks.d.ts +25 -0
- package/dist/keys/webhooks.js +62 -0
- package/dist/management-openapi.json +313 -133
- package/dist/mcp/connect.d.ts +2 -2
- package/dist/mcp/connect.js +3 -3
- package/dist/mcp/preview.js +7 -7
- package/dist/memory/service.d.ts +97 -0
- package/dist/memory/service.js +335 -0
- package/dist/memory/tool.d.ts +36 -0
- package/dist/memory/tool.js +84 -0
- package/dist/model/pi-model.js +7 -0
- package/dist/openapi.json +13120 -9034
- package/dist/reads/types.d.ts +3 -3
- package/dist/record/agent.d.ts +27 -0
- package/dist/record/agent.js +46 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/sandbox/join.js +5 -5
- package/dist/sandbox/placement.d.ts +1 -1
- package/dist/sandbox/placement.js +3 -3
- package/dist/sandbox/pods/lifecycle.d.ts +6 -6
- package/dist/sandbox/pods/lifecycle.js +6 -6
- package/dist/sandbox/pods/reconcile.js +10 -2
- package/dist/sandbox/resolve.d.ts +3 -3
- package/dist/sandbox/resolve.js +7 -7
- package/dist/sandbox/setup.d.ts +3 -1
- package/dist/sandbox/setup.js +14 -4
- package/dist/store/postgres/drizzle/0032_agents.sql +54 -0
- package/dist/store/postgres/drizzle/0033_agent_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0034_protocol_17_session_queue.sql +13 -0
- package/dist/store/postgres/drizzle/0035_t4_triggers.sql +41 -0
- package/dist/store/postgres/drizzle/0036_t5_memories.sql +30 -0
- package/dist/store/postgres/drizzle/0037_model_usage_cost_split.sql +4 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +42 -0
- package/dist/store/postgres/reads.js +44 -87
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/schema.d.ts +1408 -123
- package/dist/store/postgres/schema.js +194 -38
- package/dist/store/postgres/store.js +329 -79
- package/dist/store/postgres/tenant.js +1 -2
- package/dist/store/types.d.ts +155 -50
- package/dist/store/types.js +0 -1
- package/dist/tenant/advance.js +20 -8
- package/dist/tenant/agent-manifests.d.ts +72 -0
- package/dist/tenant/agent-manifests.js +359 -0
- package/dist/tenant/agent-versions.d.ts +126 -0
- package/dist/tenant/agent-versions.js +389 -0
- package/dist/tenant/agents.d.ts +230 -0
- package/dist/tenant/{environments.js → agents.js} +464 -320
- package/dist/tenant/auth.d.ts +1 -1
- package/dist/tenant/auth.js +3 -3
- package/dist/tenant/commands.d.ts +63 -2
- package/dist/tenant/commands.js +240 -83
- package/dist/tenant/context.d.ts +46 -19
- package/dist/tenant/context.js +6 -2
- package/dist/tenant/effects.js +7 -16
- package/dist/tenant/identity-file.d.ts +6 -6
- package/dist/tenant/identity-file.js +12 -10
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/limits.d.ts +0 -3
- package/dist/tenant/limits.js +0 -3
- package/dist/tenant/memories.d.ts +38 -0
- package/dist/tenant/memories.js +119 -0
- package/dist/tenant/reset.js +2 -2
- package/dist/tenant/run-grants.d.ts +6 -0
- package/dist/tenant/run-grants.js +4 -0
- package/dist/tenant/runtime.d.ts +1 -1
- package/dist/tenant/runtime.js +8 -0
- package/dist/tenant/sessions.d.ts +28 -45
- package/dist/tenant/sessions.js +85 -208
- package/dist/tenant/status.js +6 -7
- package/dist/tenant/sweep.js +12 -3
- package/dist/tenant/transcript.d.ts +16 -16
- package/dist/tenant/triggers.d.ts +143 -0
- package/dist/tenant/triggers.js +715 -0
- package/dist/tenant/turn-timing.d.ts +11 -1
- package/dist/tenant/turn-timing.js +3 -1
- package/dist/tenant/types.d.ts +5 -0
- package/dist/tenant/worker.d.ts +2 -0
- package/dist/tenant/worker.js +5 -0
- package/dist/traces/map.d.ts +1 -1
- package/dist/traces/map.js +7 -1
- package/dist/tuning.d.ts +7 -0
- package/dist/tuning.js +19 -0
- package/dist/vault/service.d.ts +13 -8
- package/dist/vault/service.js +61 -17
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -12
- package/dist/api/http/routes/environment-manifests.d.ts +0 -9
- package/dist/api/http/routes/environment-manifests.js +0 -118
- package/dist/api/http/routes/environments.d.ts +0 -11
- package/dist/api/http/routes/environments.js +0 -332
- package/dist/record/environment.d.ts +0 -27
- package/dist/record/environment.js +0 -46
- package/dist/tenant/environment-manifests.d.ts +0 -57
- package/dist/tenant/environment-manifests.js +0 -208
- package/dist/tenant/environments.d.ts +0 -172
package/dist/tenant/auth.d.ts
CHANGED
|
@@ -48,6 +48,6 @@ export declare function accessOf(scope: AuthScope): SessionAccess | undefined;
|
|
|
48
48
|
export declare function ownerOf(scope: AuthScope): string | undefined;
|
|
49
49
|
/** The application principal behind the request, acting for a subject or not. */
|
|
50
50
|
export declare function requirePrincipal(scope: AuthScope): string;
|
|
51
|
-
/** True when a token caller may reach `agentId` (its
|
|
51
|
+
/** True when a token caller may reach Agent `agentId` (its grants). Other callers always may. */
|
|
52
52
|
export declare function mayUseAgent(scope: AuthScope, agentId: string): boolean;
|
|
53
53
|
export declare function requireApplication(scope: AuthScope): string;
|
package/dist/tenant/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SCOPES_HEADER, SUBJECT_HEADER, } from "@nylorun/core/compatibility";
|
|
2
|
-
import { parseSubjectHeaders, TOKEN_SCOPES } from "@nylorun/core/contracts";
|
|
2
|
+
import { agentGranted, parseSubjectHeaders, TOKEN_SCOPES } from "@nylorun/core/contracts";
|
|
3
3
|
import { hashToken } from "../core/bearer.js";
|
|
4
4
|
import { fail } from "./http.js";
|
|
5
5
|
import { verifyIssuerToken } from "./issuers.js";
|
|
@@ -168,9 +168,9 @@ export function requirePrincipal(scope) {
|
|
|
168
168
|
return scope.principalId;
|
|
169
169
|
return fail(403, "Application credential required");
|
|
170
170
|
}
|
|
171
|
-
/** True when a token caller may reach `agentId` (its
|
|
171
|
+
/** True when a token caller may reach Agent `agentId` (its grants). Other callers always may. */
|
|
172
172
|
export function mayUseAgent(scope, agentId) {
|
|
173
|
-
return scope.kind !== "token" || scope.agents === "*" || scope.agents
|
|
173
|
+
return scope.kind !== "token" || scope.agents === "*" || agentGranted(scope.agents, agentId);
|
|
174
174
|
}
|
|
175
175
|
export function requireApplication(scope) {
|
|
176
176
|
if (scope.kind === "application")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { EventWriter, SessionCommand } from "@nylorun/core/contracts";
|
|
2
|
-
import
|
|
1
|
+
import type { EventWriter, MessageDelivery, MessageEventBody, SessionCommand } from "@nylorun/core/contracts";
|
|
2
|
+
import type { Tx } from "../store/types.js";
|
|
3
|
+
import { type AuthScope, type Session, type TenantContext } from "./context.js";
|
|
3
4
|
import type { TraceContext } from "../traces/ids.js";
|
|
4
5
|
export declare function command(ctx: TenantContext, id: string, input: SessionCommand, scope: AuthScope,
|
|
5
6
|
/**
|
|
@@ -8,7 +9,67 @@ export declare function command(ctx: TenantContext, id: string, input: SessionCo
|
|
|
8
9
|
*/
|
|
9
10
|
options?: {
|
|
10
11
|
trace?: TraceContext;
|
|
12
|
+
/**
|
|
13
|
+
* What a message to a busy session does when it says nothing (`delivery`): the route's
|
|
14
|
+
* answer to the caller's protocol (`queue` from 17). Core's own messages and the AG-UI and
|
|
15
|
+
* A2A adapters, which stream the run they start, take `reject`.
|
|
16
|
+
*/
|
|
17
|
+
delivery?: MessageDelivery;
|
|
11
18
|
}): Promise<unknown>;
|
|
19
|
+
export declare const takesMessages: (s: Session) => boolean;
|
|
20
|
+
/** A message ready to start a turn: as sent, its engine input and who sent it. */
|
|
21
|
+
export interface TurnMessage {
|
|
22
|
+
command: MessageEventBody;
|
|
23
|
+
/** An agent's message input, or a workflow's input value. */
|
|
24
|
+
input: unknown;
|
|
25
|
+
/** The artifacts its file parts named. */
|
|
26
|
+
fileArtifacts: string[];
|
|
27
|
+
writer: EventWriter;
|
|
28
|
+
/** Set for a message that waited in the session's queue. */
|
|
29
|
+
queuedMessageId?: string;
|
|
30
|
+
trace?: TraceContext;
|
|
31
|
+
/** What sent it when not a client (track T4): a trigger's run, or the session's own wake. */
|
|
32
|
+
cause?: MessageCause;
|
|
33
|
+
}
|
|
34
|
+
/** What sent a message when not a client (`command.message`'s `cause`). */
|
|
35
|
+
export interface MessageCause {
|
|
36
|
+
kind: "trigger" | "self";
|
|
37
|
+
triggerId: string;
|
|
38
|
+
runId: string;
|
|
39
|
+
}
|
|
40
|
+
/** How a message was taken: it started a turn, or waits in the session's queue. */
|
|
41
|
+
export type MessageAnswer = {
|
|
42
|
+
status: "accepted";
|
|
43
|
+
turnId: string;
|
|
44
|
+
cursor: string;
|
|
45
|
+
} | {
|
|
46
|
+
status: "queued";
|
|
47
|
+
queuedMessageId: string;
|
|
48
|
+
position: number;
|
|
49
|
+
cursor: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Takes a resolved message on session `s` (locked) in transaction `t`: it starts a turn when the
|
|
53
|
+
* session takes messages and none wait, else it waits in the queue behind them (protocol 17), and
|
|
54
|
+
* an idle session whose queue was left waiting (its Agent was unavailable when the last turn
|
|
55
|
+
* ended) starts a turn with all of them, this one last. Past `sessionQueuedMessages` it is
|
|
56
|
+
* `429 queue_full`. `waiting` is the queue's length, read under the session's lock.
|
|
57
|
+
*/
|
|
58
|
+
export declare function acceptMessage(ctx: TenantContext, t: Tx, s: Session, id: string, message: TurnMessage, waiting?: number): Promise<MessageAnswer>;
|
|
59
|
+
/** Who checks a queued message's turn start: core, since the sender's grants were checked when it was queued. */
|
|
60
|
+
export declare const QUEUE_SCOPE: AuthScope;
|
|
61
|
+
/**
|
|
62
|
+
* Starts the next turn of session `s` (locked) from its queue, when it takes messages and messages
|
|
63
|
+
* wait (protocol 17, D76): in the transaction that ended the last turn, or a cancel's. Our engine
|
|
64
|
+
* takes every queued message that carries the same turn manifest as the first, a workflow one.
|
|
65
|
+
* Writes `queue.taken`. Returns nothing when nothing started: an empty queue, or an Agent that
|
|
66
|
+
* cannot take a turn now, whose messages wait for the next message or turn end.
|
|
67
|
+
*/
|
|
68
|
+
export declare function startQueuedTurn(ctx: TenantContext, t: Tx, s: Session, id: string): Promise<{
|
|
69
|
+
turnId: string;
|
|
70
|
+
taken: string[];
|
|
71
|
+
cursor: string;
|
|
72
|
+
} | undefined>;
|
|
12
73
|
/**
|
|
13
74
|
* Who sent a command (R4.9), from its credential and never its body: a client over the Runtime
|
|
14
75
|
* API (an application, or a person through it or a token), or core itself.
|
package/dist/tenant/commands.js
CHANGED
|
@@ -14,6 +14,12 @@
|
|
|
14
14
|
* same transaction; the engine host sees it before its next effect and before settlement on
|
|
15
15
|
* any Worker. It then aborts an advance running on this process (`ctx.abortLocal`); every
|
|
16
16
|
* other process aborts its own on the signal (`control.ts`).
|
|
17
|
+
*
|
|
18
|
+
* A message to a session whose turn runs or waits (protocol 17, D76) is queued
|
|
19
|
+
* (`session_queue`, `message.queued`) unless it asks `delivery: "reject"`, which is
|
|
20
|
+
* `409 session_busy`. A turn that ends takes every queued message in the same transaction
|
|
21
|
+
* (`startQueuedTurn`): client messages and queued ones start a turn through one door,
|
|
22
|
+
* `beginMessageTurn`, which writes each one's `command.message`.
|
|
17
23
|
*/
|
|
18
24
|
import { randomUUID } from "node:crypto";
|
|
19
25
|
import { createDurableCheckpoint, createFlowCheckpoint, resumeFlowCheckpoint, } from "@nylorun/harness/run";
|
|
@@ -21,10 +27,11 @@ import { commandKey, cancelQueuedEffects, flowInteractionOf, foreignInteractionC
|
|
|
21
27
|
import { resolveMessageManifest } from "../core/turn-manifest.js";
|
|
22
28
|
import { canonical } from "../store/canonical.js";
|
|
23
29
|
import { lockedSession, } from "./context.js";
|
|
24
|
-
import { fail } from "./http.js";
|
|
30
|
+
import { fail, HttpError } from "./http.js";
|
|
31
|
+
import { readRuntimeLimits } from "./runtime-limits.js";
|
|
25
32
|
import { settleToolRow } from "../harness-api/tool-journal.js";
|
|
26
33
|
import { accessOf } from "./auth.js";
|
|
27
|
-
import {
|
|
34
|
+
import { checkAgentTurn } from "./agents.js";
|
|
28
35
|
import { turnManifestOf, variantStore } from "./session.js";
|
|
29
36
|
import { slimModelEffects } from "./slim.js";
|
|
30
37
|
import { resolveMessageParts } from "../artifacts/parts.js";
|
|
@@ -42,6 +49,7 @@ export async function command(ctx, id, input, scope,
|
|
|
42
49
|
options = {}) {
|
|
43
50
|
const { store } = ctx;
|
|
44
51
|
const cascadeCancelIds = [];
|
|
52
|
+
let cancelledTurn = null;
|
|
45
53
|
const response = await store.tx(async (t) => {
|
|
46
54
|
const s = await lockedSession(t, id, accessOf(scope));
|
|
47
55
|
const command = input;
|
|
@@ -64,8 +72,11 @@ options = {}) {
|
|
|
64
72
|
let held;
|
|
65
73
|
// The turn a `cancel` ended, if one was active.
|
|
66
74
|
let cancelledTurnId = null;
|
|
75
|
+
/** A message's answer: accepted with its turn, or queued (protocol 17). */
|
|
76
|
+
let answer;
|
|
67
77
|
if (command.type === "cancel") {
|
|
68
78
|
cancelledTurnId = s.activeTurnId;
|
|
79
|
+
cancelledTurn = cancelledTurnId;
|
|
69
80
|
const workflowCancel = isWorkflowManifest(s.manifest);
|
|
70
81
|
// Linked sessions this one started: a workflow's agents, or flow agents an agent
|
|
71
82
|
// uses as tools.
|
|
@@ -128,84 +139,25 @@ options = {}) {
|
|
|
128
139
|
}
|
|
129
140
|
}
|
|
130
141
|
else {
|
|
131
|
-
// A session opened before protocol 11 (migration 0020), 13 (migration 0024)
|
|
132
|
-
// (migrations 0029, 0030) keeps its history but takes no turn.
|
|
142
|
+
// A session opened before protocol 11 (migration 0020), 13 (migration 0024), 15
|
|
143
|
+
// (migrations 0029, 0030) or 16 (migration 0032) keeps its history but takes no turn.
|
|
133
144
|
if (s.closedReason !== undefined)
|
|
134
|
-
fail(409, s.closedReason === "
|
|
135
|
-
? "This session was opened before protocol
|
|
136
|
-
: s.closedReason === "
|
|
137
|
-
? "This session was opened before protocol
|
|
138
|
-
:
|
|
145
|
+
fail(409, s.closedReason === "protocol_16"
|
|
146
|
+
? "This session was opened before protocol 16, from an agent definition: save the agent as an agent manifest (nylorun migrate v8, then nylorun apply), make an Agent from it and open a new session on it."
|
|
147
|
+
: s.closedReason === "protocol_11"
|
|
148
|
+
? "This session was opened before protocol 11: open a new session on an Agent."
|
|
149
|
+
: s.closedReason === "protocol_15"
|
|
150
|
+
? "This session was opened before protocol 15, and its pinned manifest holds a runtime block: our harness now writes the model's instructions and defines its tools itself. Open a new session."
|
|
151
|
+
: "This session was opened before protocol 13, on a manifest the Runtime no longer runs: register the agent again (manifest 7) and open a new session.", { code: "session_closed" });
|
|
139
152
|
if (command.type === "message") {
|
|
140
|
-
|
|
141
|
-
|
|
153
|
+
const delivery = command.delivery ?? options.delivery ?? "reject";
|
|
154
|
+
const waiting = await t.countQueuedMessages(id);
|
|
155
|
+
if (!takesMessages(s) && delivery === "reject")
|
|
156
|
+
fail(409, "Session has active or unresolved work", { code: "session_busy" });
|
|
142
157
|
// Every turn start on a sandbox: it exists, the token's grants reach it, and no other
|
|
143
|
-
// session's turn holds it.
|
|
144
|
-
await
|
|
145
|
-
|
|
146
|
-
// A new turn folds from the latest snapshot: a cancel or failure only reverts this turn.
|
|
147
|
-
if (s.history?.snapshot !== undefined)
|
|
148
|
-
s.history = { ...s.history, from: s.history.snapshot };
|
|
149
|
-
s.activeTurnId = randomUUID();
|
|
150
|
-
startTurn(s);
|
|
151
|
-
if (options.trace)
|
|
152
|
-
s.turnTrace = options.trace;
|
|
153
|
-
else
|
|
154
|
-
delete s.turnTrace;
|
|
155
|
-
// File parts name artifacts the caller may read; each is pinned to a version now. Any
|
|
156
|
-
// file goes (protocol 15): the harness decides how the model sees it, and a workflow
|
|
157
|
-
// passes it on as data.
|
|
158
|
-
const parts = "parts" in command
|
|
159
|
-
? await resolveMessageParts(t, command.parts, id, accessOf(scope))
|
|
160
|
-
: undefined;
|
|
161
|
-
// The files the session's messages named: `read_artifact` reads them again (protocol 12).
|
|
162
|
-
if (parts) {
|
|
163
|
-
const named = parts.pinned.flatMap((part) => (part.type === "file" ? [part.artifactId] : []));
|
|
164
|
-
if (named.length > 0)
|
|
165
|
-
s.fileArtifacts = [...new Set([...(s.fileArtifacts ?? []), ...named])];
|
|
166
|
-
}
|
|
167
|
-
const data = "data" in command ? command.data : undefined;
|
|
168
|
-
if (isWorkflowManifest(s.manifest)) {
|
|
169
|
-
const input = "content" in command
|
|
170
|
-
? command.content
|
|
171
|
-
: parts
|
|
172
|
-
? { parts: parts.pinned }
|
|
173
|
-
: data;
|
|
174
|
-
s.checkpoint = createFlowCheckpoint({
|
|
175
|
-
manifest: s.manifest,
|
|
176
|
-
sessionId: id,
|
|
177
|
-
turnId: s.activeTurnId,
|
|
178
|
-
input,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
// Optional message.manifest: validate as variant, pin by hash, apply this turn only.
|
|
183
|
-
const resolved = resolveMessageManifest({
|
|
184
|
-
pinned: s.manifest,
|
|
185
|
-
pinnedHash: s.manifestHash,
|
|
186
|
-
messageManifest: command.manifest,
|
|
187
|
-
store: variantStore(s),
|
|
188
|
-
});
|
|
189
|
-
if (!resolved.ok) {
|
|
190
|
-
fail(400, resolved.message);
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
s.checkpoint = createDurableCheckpoint({
|
|
194
|
-
manifest: resolved.manifest,
|
|
195
|
-
sessionId: id,
|
|
196
|
-
turnId: s.activeTurnId,
|
|
197
|
-
input: "content" in command
|
|
198
|
-
? command.content
|
|
199
|
-
: parts
|
|
200
|
-
? { content: parts.engine }
|
|
201
|
-
: typeof data === "string"
|
|
202
|
-
? data
|
|
203
|
-
: JSON.stringify(data),
|
|
204
|
-
state: s.state,
|
|
205
|
-
info: s.info,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
}
|
|
158
|
+
// session's turn holds it. A queued message is checked when it is queued, too.
|
|
159
|
+
await checkAgentTurn(t, s, scope, ctx);
|
|
160
|
+
answer = await acceptMessage(ctx, t, s, id, await turnMessage(t, s, id, command, scope, options.trace), waiting);
|
|
209
161
|
}
|
|
210
162
|
else if (command.type === "approve" &&
|
|
211
163
|
(held = await heldCall(t, s, command.interactionId)) !== undefined) {
|
|
@@ -278,13 +230,15 @@ options = {}) {
|
|
|
278
230
|
segment: (s.checkpoint?.segment ?? 0) + 1,
|
|
279
231
|
});
|
|
280
232
|
}
|
|
233
|
+
}
|
|
234
|
+
if (command.type !== "cancel" && command.type !== "message") {
|
|
281
235
|
// A paused turn goes on: its wait for a person is not its running time (R7.1).
|
|
282
236
|
resumeTurn(s);
|
|
283
237
|
s.status = "runnable";
|
|
284
238
|
s.waits = undefined;
|
|
285
239
|
// A flow answers each interaction in the same segment, as a held tool call's approval is
|
|
286
240
|
// answered (R5 step 2): the interaction names the cause.
|
|
287
|
-
const cause =
|
|
241
|
+
const cause = isWorkflowManifest(s.manifest) || held !== undefined
|
|
288
242
|
? command.interactionId
|
|
289
243
|
: s.checkpoint.segment;
|
|
290
244
|
const commandWake = {
|
|
@@ -292,8 +246,11 @@ options = {}) {
|
|
|
292
246
|
dedupeKey: `${command.type}:${s.activeTurnId}:${cause}`,
|
|
293
247
|
};
|
|
294
248
|
await t.wake(id, commandWake);
|
|
295
|
-
event = await t.event(id, s.activeTurnId, `command.${command.type}`, command
|
|
249
|
+
event = await t.event(id, s.activeTurnId, `command.${command.type}`, command, commandWriter(scope));
|
|
296
250
|
}
|
|
251
|
+
// A cancel ends the turn, not the queue: the messages waiting start the next one.
|
|
252
|
+
if (command.type === "cancel")
|
|
253
|
+
await startQueuedTurn(ctx, t, s, id);
|
|
297
254
|
await t.put("sessions", id, s);
|
|
298
255
|
// Direct cancel of a linked agent fails that node with agent.cancelled (WF-R54). The agent
|
|
299
256
|
// session is locked above, before wakeLinkedWorkflow locks its workflow.
|
|
@@ -306,16 +263,18 @@ options = {}) {
|
|
|
306
263
|
error: "Agent turn was cancelled",
|
|
307
264
|
});
|
|
308
265
|
const response = {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
266
|
+
...(answer ?? {
|
|
267
|
+
status: "accepted",
|
|
268
|
+
turnId: command.type === "cancel" ? null : s.activeTurnId,
|
|
269
|
+
cursor: event.cursor,
|
|
270
|
+
}),
|
|
312
271
|
requestId: command.requestId,
|
|
313
272
|
};
|
|
314
273
|
await t.put("commands", key, { command, response });
|
|
315
274
|
return response;
|
|
316
275
|
});
|
|
317
276
|
if (input.type === "cancel")
|
|
318
|
-
ctx.abortLocal(id);
|
|
277
|
+
ctx.abortLocal(id, cancelledTurn ?? undefined);
|
|
319
278
|
// Cascade cancel linked agents deepest-first under existing fencing (WF-R53).
|
|
320
279
|
for (const agentId of cascadeCancelIds) {
|
|
321
280
|
try {
|
|
@@ -332,6 +291,204 @@ options = {}) {
|
|
|
332
291
|
}
|
|
333
292
|
return response;
|
|
334
293
|
}
|
|
294
|
+
/** Statuses in which a session takes a message as a new turn; in the others a turn runs or waits. */
|
|
295
|
+
const TAKES_MESSAGES = ["idle", "completed", "failed", "cancelled"];
|
|
296
|
+
export const takesMessages = (s) => TAKES_MESSAGES.includes(s.status);
|
|
297
|
+
/**
|
|
298
|
+
* Takes a resolved message on session `s` (locked) in transaction `t`: it starts a turn when the
|
|
299
|
+
* session takes messages and none wait, else it waits in the queue behind them (protocol 17), and
|
|
300
|
+
* an idle session whose queue was left waiting (its Agent was unavailable when the last turn
|
|
301
|
+
* ended) starts a turn with all of them, this one last. Past `sessionQueuedMessages` it is
|
|
302
|
+
* `429 queue_full`. `waiting` is the queue's length, read under the session's lock.
|
|
303
|
+
*/
|
|
304
|
+
export async function acceptMessage(ctx, t, s, id, message, waiting) {
|
|
305
|
+
const queued = waiting ?? (await t.countQueuedMessages(id));
|
|
306
|
+
const takes = takesMessages(s);
|
|
307
|
+
if (takes && queued === 0) {
|
|
308
|
+
const event = await beginMessageTurn(t, s, id, [message]);
|
|
309
|
+
return { status: "accepted", turnId: s.activeTurnId, cursor: event.cursor };
|
|
310
|
+
}
|
|
311
|
+
// The turn runs or waits, or earlier messages still wait: this one waits behind them.
|
|
312
|
+
const { sessionQueuedMessages } = await readRuntimeLimits(t);
|
|
313
|
+
if (queued >= sessionQueuedMessages)
|
|
314
|
+
fail(429, `Session ${id} already holds ${queued} queued messages, the Runtime limit sessionQueuedMessages (PUT /v1/tenant/limits); they start a turn when the running one ends.`, { code: "queue_full" });
|
|
315
|
+
const queuedMessageId = randomUUID();
|
|
316
|
+
await t.enqueueMessage({
|
|
317
|
+
id: queuedMessageId,
|
|
318
|
+
sessionId: id,
|
|
319
|
+
command: message.command,
|
|
320
|
+
input: message.input,
|
|
321
|
+
fileArtifacts: message.fileArtifacts,
|
|
322
|
+
writer: message.writer,
|
|
323
|
+
trace: message.trace ?? null,
|
|
324
|
+
cause: message.cause ?? null,
|
|
325
|
+
createdAt: new Date().toISOString(),
|
|
326
|
+
});
|
|
327
|
+
const position = queued + 1;
|
|
328
|
+
const event = await t.event(id, s.activeTurnId, "message.queued", { queuedMessageId, position, requestId: message.command.requestId }, message.writer);
|
|
329
|
+
const started = takes ? await startQueuedTurn(ctx, t, s, id) : undefined;
|
|
330
|
+
if (started?.taken.includes(queuedMessageId))
|
|
331
|
+
return { status: "accepted", turnId: started.turnId, cursor: started.cursor };
|
|
332
|
+
return { status: "queued", queuedMessageId, position, cursor: event.cursor };
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Resolves a message for a turn, now or when it leaves the queue: file parts name artifacts the
|
|
336
|
+
* caller may read, each pinned to a version (any file goes since protocol 15: the harness decides
|
|
337
|
+
* how the model sees it, and a workflow passes it on as data), and a turn manifest must be a
|
|
338
|
+
* variant of the pinned agent.
|
|
339
|
+
*/
|
|
340
|
+
async function turnMessage(t, s, id, command, scope, trace) {
|
|
341
|
+
const parts = "parts" in command ? await resolveMessageParts(t, command.parts, id, accessOf(scope)) : undefined;
|
|
342
|
+
const fileArtifacts = (parts?.pinned ?? []).flatMap((part) => (part.type === "file" ? [part.artifactId] : []));
|
|
343
|
+
const data = "data" in command ? command.data : undefined;
|
|
344
|
+
let input;
|
|
345
|
+
if (isWorkflowManifest(s.manifest))
|
|
346
|
+
input =
|
|
347
|
+
"content" in command
|
|
348
|
+
? command.content
|
|
349
|
+
: parts
|
|
350
|
+
? { parts: parts.pinned }
|
|
351
|
+
: data;
|
|
352
|
+
else {
|
|
353
|
+
const resolved = resolveMessageManifest({
|
|
354
|
+
pinned: s.manifest,
|
|
355
|
+
pinnedHash: s.manifestHash,
|
|
356
|
+
messageManifest: command.manifest,
|
|
357
|
+
store: variantStore(s),
|
|
358
|
+
});
|
|
359
|
+
if (!resolved.ok)
|
|
360
|
+
fail(400, resolved.message);
|
|
361
|
+
input =
|
|
362
|
+
"content" in command
|
|
363
|
+
? command.content
|
|
364
|
+
: parts
|
|
365
|
+
? { content: parts.engine }
|
|
366
|
+
: typeof data === "string"
|
|
367
|
+
? data
|
|
368
|
+
: JSON.stringify(data);
|
|
369
|
+
}
|
|
370
|
+
return { command, input, fileArtifacts, writer: commandWriter(scope), ...(trace ? { trace } : {}) };
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Starts a turn on session `s` (locked, taking messages) with `messages`, in order: the one door
|
|
374
|
+
* for a client's message and queued ones (protocol 17). Our engine reads more than one as
|
|
375
|
+
* `{kind: "messages", messages}`, each its own user message; a workflow takes one. The checkpoint
|
|
376
|
+
* is made before the session changes, so a manifest that no longer resolves changes nothing.
|
|
377
|
+
* Writes each message's `command.message` and returns the last.
|
|
378
|
+
*/
|
|
379
|
+
async function beginMessageTurn(t, s, id, messages) {
|
|
380
|
+
const turnId = randomUUID();
|
|
381
|
+
const first = messages[0];
|
|
382
|
+
let checkpoint;
|
|
383
|
+
if (isWorkflowManifest(s.manifest))
|
|
384
|
+
checkpoint = createFlowCheckpoint({
|
|
385
|
+
manifest: s.manifest,
|
|
386
|
+
sessionId: id,
|
|
387
|
+
turnId,
|
|
388
|
+
input: first.input,
|
|
389
|
+
});
|
|
390
|
+
else {
|
|
391
|
+
const resolved = resolveMessageManifest({
|
|
392
|
+
pinned: s.manifest,
|
|
393
|
+
pinnedHash: s.manifestHash,
|
|
394
|
+
messageManifest: first.command.manifest,
|
|
395
|
+
store: variantStore(s),
|
|
396
|
+
});
|
|
397
|
+
if (!resolved.ok)
|
|
398
|
+
return fail(400, resolved.message);
|
|
399
|
+
checkpoint = createDurableCheckpoint({
|
|
400
|
+
manifest: resolved.manifest,
|
|
401
|
+
sessionId: id,
|
|
402
|
+
turnId,
|
|
403
|
+
input: (messages.length === 1
|
|
404
|
+
? first.input
|
|
405
|
+
: { kind: "messages", messages: messages.map((message) => message.input) }),
|
|
406
|
+
state: s.state,
|
|
407
|
+
info: s.info,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
s.turnStartState = s.state;
|
|
411
|
+
// A new turn folds from the latest snapshot: a cancel or failure only reverts this turn.
|
|
412
|
+
if (s.history?.snapshot !== undefined)
|
|
413
|
+
s.history = { ...s.history, from: s.history.snapshot };
|
|
414
|
+
s.activeTurnId = turnId;
|
|
415
|
+
startTurn(s);
|
|
416
|
+
if (first.trace)
|
|
417
|
+
s.turnTrace = first.trace;
|
|
418
|
+
else
|
|
419
|
+
delete s.turnTrace;
|
|
420
|
+
// A trigger's run or a booked wake among the messages started it (track T4).
|
|
421
|
+
const cause = messages.find((message) => message.cause)?.cause;
|
|
422
|
+
if (cause)
|
|
423
|
+
s.turnCause = { kind: cause.kind, triggerId: cause.triggerId, runId: cause.runId };
|
|
424
|
+
else
|
|
425
|
+
delete s.turnCause;
|
|
426
|
+
// The files the session's messages named: `read_artifact` reads them again (protocol 12).
|
|
427
|
+
const named = messages.flatMap((message) => message.fileArtifacts);
|
|
428
|
+
if (named.length > 0)
|
|
429
|
+
s.fileArtifacts = [...new Set([...(s.fileArtifacts ?? []), ...named])];
|
|
430
|
+
s.checkpoint = checkpoint;
|
|
431
|
+
s.status = "runnable";
|
|
432
|
+
s.waits = undefined;
|
|
433
|
+
await t.wake(id, { reason: "message", dedupeKey: `message:${turnId}:${checkpoint.segment}` });
|
|
434
|
+
let event;
|
|
435
|
+
for (const message of messages)
|
|
436
|
+
event = await t.event(id, turnId, "command.message", {
|
|
437
|
+
...message.command,
|
|
438
|
+
...(message.queuedMessageId === undefined ? {} : { queuedMessageId: message.queuedMessageId }),
|
|
439
|
+
...(message.trace ? { trace: message.trace } : {}),
|
|
440
|
+
...(message.cause ? { cause: message.cause } : {}),
|
|
441
|
+
}, message.writer);
|
|
442
|
+
return event;
|
|
443
|
+
}
|
|
444
|
+
/** Who checks a queued message's turn start: core, since the sender's grants were checked when it was queued. */
|
|
445
|
+
export const QUEUE_SCOPE = { kind: "application", principalId: "queue" };
|
|
446
|
+
/**
|
|
447
|
+
* Starts the next turn of session `s` (locked) from its queue, when it takes messages and messages
|
|
448
|
+
* wait (protocol 17, D76): in the transaction that ended the last turn, or a cancel's. Our engine
|
|
449
|
+
* takes every queued message that carries the same turn manifest as the first, a workflow one.
|
|
450
|
+
* Writes `queue.taken`. Returns nothing when nothing started: an empty queue, or an Agent that
|
|
451
|
+
* cannot take a turn now, whose messages wait for the next message or turn end.
|
|
452
|
+
*/
|
|
453
|
+
export async function startQueuedTurn(ctx, t, s, id) {
|
|
454
|
+
if (s.closedReason !== undefined || !takesMessages(s) || s.activeTurnId !== null)
|
|
455
|
+
return undefined;
|
|
456
|
+
const queued = await t.queuedMessages(id);
|
|
457
|
+
if (queued.length === 0)
|
|
458
|
+
return undefined;
|
|
459
|
+
const manifestOf = (row) => canonical(row.command.manifest ?? null);
|
|
460
|
+
const batch = isWorkflowManifest(s.manifest) ? [queued[0]] : [];
|
|
461
|
+
if (batch.length === 0)
|
|
462
|
+
for (const row of queued) {
|
|
463
|
+
if (manifestOf(row) !== manifestOf(queued[0]))
|
|
464
|
+
break;
|
|
465
|
+
batch.push(row);
|
|
466
|
+
}
|
|
467
|
+
try {
|
|
468
|
+
await checkAgentTurn(t, s, QUEUE_SCOPE, ctx);
|
|
469
|
+
await beginMessageTurn(t, s, id, batch.map((row) => ({
|
|
470
|
+
command: row.command,
|
|
471
|
+
input: row.input,
|
|
472
|
+
fileArtifacts: row.fileArtifacts,
|
|
473
|
+
writer: row.writer,
|
|
474
|
+
queuedMessageId: row.id,
|
|
475
|
+
...(row.trace ? { trace: row.trace } : {}),
|
|
476
|
+
...(row.cause ? { cause: row.cause } : {}),
|
|
477
|
+
})));
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
if (error instanceof HttpError)
|
|
481
|
+
return undefined;
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
const taken = batch.map((row) => row.id);
|
|
485
|
+
await t.deleteQueuedMessages(taken);
|
|
486
|
+
const event = await t.event(id, s.activeTurnId, "queue.taken", {
|
|
487
|
+
turnId: s.activeTurnId,
|
|
488
|
+
queuedMessageIds: taken,
|
|
489
|
+
});
|
|
490
|
+
return { turnId: s.activeTurnId, taken, cursor: event.cursor };
|
|
491
|
+
}
|
|
335
492
|
/** Core's own commands (R4.9): the flow host's, and a drain's cancels. */
|
|
336
493
|
const CORE_PRINCIPALS = { "flow-host": "flow-host", drain: "core" };
|
|
337
494
|
/**
|
package/dist/tenant/context.d.ts
CHANGED
|
@@ -40,19 +40,28 @@ import type { TraceOptions } from "../traces/options.js";
|
|
|
40
40
|
import type { TraceContext } from "../traces/ids.js";
|
|
41
41
|
export interface Session {
|
|
42
42
|
id: string;
|
|
43
|
+
/** The Agent the session runs on (protocol 16; a definition id before). */
|
|
43
44
|
agentId: string;
|
|
44
45
|
ownerUserId: string;
|
|
45
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Session pin: the loop manifest the session runs, its Agent's manifest version's harness
|
|
48
|
+
* section as the harness runs it (`loopManifestOf`), or a flow's leaf agent.
|
|
49
|
+
*/
|
|
46
50
|
manifest: any;
|
|
47
51
|
manifestHash: string;
|
|
48
52
|
/**
|
|
49
|
-
* The
|
|
50
|
-
*
|
|
53
|
+
* The agent manifest version the session runs (protocol 16): its Agent's when it opened, and
|
|
54
|
+
* again at a turn's first segment when its Agent's version changed since (T3, `repinTurn`).
|
|
55
|
+
* Absent on a session opened before protocol 16.
|
|
51
56
|
*/
|
|
52
|
-
|
|
57
|
+
definition?: {
|
|
58
|
+
name: string;
|
|
59
|
+
version: number;
|
|
60
|
+
};
|
|
61
|
+
/** The pinned version's `harness.loop`: passed to the harness, never read by the Runtime. */
|
|
62
|
+
loop?: Record<string, unknown>;
|
|
53
63
|
/** Validated turn-manifest variants, keyed by hash (loops.md §4.2). */
|
|
54
64
|
variants?: Record<string, AgentManifest>;
|
|
55
|
-
implementationVersion: string;
|
|
56
65
|
info?: any;
|
|
57
66
|
status: string;
|
|
58
67
|
activeTurnId: string | null;
|
|
@@ -83,26 +92,35 @@ export interface Session {
|
|
|
83
92
|
* and the context core hands on (to the harness, tools and sandbox commands) is in it.
|
|
84
93
|
*/
|
|
85
94
|
turnTrace?: TraceContext;
|
|
95
|
+
/**
|
|
96
|
+
* What started the active turn when not a client (track T4): the trigger's run, or the session's
|
|
97
|
+
* own booked wake. A memory version written in the turn names its run (track T5).
|
|
98
|
+
*/
|
|
99
|
+
turnCause?: {
|
|
100
|
+
kind: "trigger" | "self";
|
|
101
|
+
triggerId: string;
|
|
102
|
+
runId: string;
|
|
103
|
+
};
|
|
86
104
|
creation: unknown;
|
|
87
105
|
mcpSnapshot?: McpSnapshot;
|
|
88
106
|
mcpDiagnostics?: readonly McpDiagnostic[];
|
|
89
107
|
/**
|
|
90
|
-
* The
|
|
91
|
-
* session's (the
|
|
108
|
+
* The Agent's environment the session runs on: since protocol 16 always its Agent's id. Its
|
|
109
|
+
* sandbox is the session's (the id keys the workspace) and its vault holds the session's
|
|
92
110
|
* credentials. It outlives the session.
|
|
93
111
|
*/
|
|
94
112
|
environmentId?: string;
|
|
95
|
-
/** The
|
|
113
|
+
/** The Agent's recipe version when the session last pinned (shown, never enforced). */
|
|
96
114
|
environmentVersion?: number;
|
|
97
115
|
/**
|
|
98
|
-
* The harness that runs the session's turns (R4.1), pinned when it opened: its
|
|
99
|
-
* harness
|
|
116
|
+
* The harness that runs the session's turns (R4.1), pinned when it opened: its manifest's
|
|
117
|
+
* `harness.id`, else `nylorun`. Absent on a session opened before R4: ours (`harnessOf`).
|
|
100
118
|
*/
|
|
101
119
|
harness?: string;
|
|
102
120
|
/**
|
|
103
121
|
* A flow agent's linked session: the session whose turn opened the flow tree. Its sessions
|
|
104
122
|
* share the tree's Environment, and their turns nest in the root's, so they do not hold the
|
|
105
|
-
* Environment against each other (`
|
|
123
|
+
* Environment against each other (`checkAgentTurn`).
|
|
106
124
|
*/
|
|
107
125
|
flowRootId?: string;
|
|
108
126
|
/**
|
|
@@ -114,7 +132,8 @@ export interface Session {
|
|
|
114
132
|
* Why the session takes no new turn: `protocol_11` for a session opened before protocol 11
|
|
115
133
|
* (migration 0020), `protocol_13` for one opened before protocol 13 (migration 0024),
|
|
116
134
|
* `protocol_15` for an agent session that pinned a `runtime` block before protocol 15
|
|
117
|
-
* (migrations 0029, 0030)
|
|
135
|
+
* (migrations 0029, 0030), `protocol_16` for one opened from a definition before protocol 16
|
|
136
|
+
* (migration 0032). Its history stays readable.
|
|
118
137
|
*/
|
|
119
138
|
closedReason?: string;
|
|
120
139
|
/**
|
|
@@ -138,7 +157,7 @@ export type AuthScope = {
|
|
|
138
157
|
}
|
|
139
158
|
/**
|
|
140
159
|
* A trusted issuer's token (Host feature `trusted-issuers`, F9-D12): one subject, with the
|
|
141
|
-
* issuer's scopes
|
|
160
|
+
* issuer's scopes and Agent grants, until `expiresAt` (ms). Only its expiry ends it.
|
|
142
161
|
*/
|
|
143
162
|
| {
|
|
144
163
|
kind: "token";
|
|
@@ -147,9 +166,8 @@ export type AuthScope = {
|
|
|
147
166
|
subject: string;
|
|
148
167
|
/** The issuer's scopes the token holds, which may add `studio`. */
|
|
149
168
|
scopes: ReadonlySet<IssuerScope>;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
environments?: readonly string[];
|
|
169
|
+
/** The Agents it reaches: `*`, else exact ids or `p/*` prefixes; empty reaches none. */
|
|
170
|
+
agents: readonly string[] | "*";
|
|
153
171
|
expiresAt: number;
|
|
154
172
|
/** The token's `jti`, or a hash of it. */
|
|
155
173
|
tokenId: string;
|
|
@@ -264,7 +282,7 @@ export interface TenantContext {
|
|
|
264
282
|
abortLocal(sessionId: string, turnId?: string): void;
|
|
265
283
|
/**
|
|
266
284
|
* Sandbox pods (F7.2): the sandboxes service and the Runtime image pods copy the engine from.
|
|
267
|
-
* Absent without a cluster (`nylorun local sandbox enable`): kind `pod` is `
|
|
285
|
+
* Absent without a cluster (`nylorun local sandbox enable`): kind `pod` is `agent_unavailable`.
|
|
268
286
|
*/
|
|
269
287
|
readonly pods?: TenantPods;
|
|
270
288
|
/**
|
|
@@ -274,6 +292,12 @@ export interface TenantContext {
|
|
|
274
292
|
* sent); `true` without sandbox pods, where there is nothing to send.
|
|
275
293
|
*/
|
|
276
294
|
sandboxSignal(sandboxId: string, signal: SandboxSignal): Promise<boolean>;
|
|
295
|
+
/**
|
|
296
|
+
* Seam: arm a durable timer (`DurableExecution.timer`; track T4's triggers), outside any tx. The
|
|
297
|
+
* latest time for a key wins. `false` while the Tenant is closing (not armed): what it was for
|
|
298
|
+
* is in Postgres, and the sweep arms it again.
|
|
299
|
+
*/
|
|
300
|
+
timer(key: string, at: Date): Promise<boolean>;
|
|
277
301
|
/** This Tenant's execution invocations that need an operator, for Tenant status. */
|
|
278
302
|
readonly stuckInvocations?: () => Promise<StuckInvocation[]>;
|
|
279
303
|
/** Callbacks the Tenant sweep runs after its own steps (`sweep.ts`). */
|
|
@@ -283,12 +307,15 @@ export interface TenantContext {
|
|
|
283
307
|
}
|
|
284
308
|
/**
|
|
285
309
|
* What a request acting for a person may reach: that person's sessions, and, for a token
|
|
286
|
-
* caller, only sessions
|
|
310
|
+
* caller, only sessions on the Agents its grants reach (ids and `p/*` prefixes). Undefined means
|
|
311
|
+
* the whole Tenant.
|
|
287
312
|
*/
|
|
288
313
|
export interface SessionAccess {
|
|
289
314
|
readonly owner: string;
|
|
290
|
-
readonly agents?:
|
|
315
|
+
readonly agents?: readonly string[];
|
|
291
316
|
}
|
|
317
|
+
/** Whether `access` reaches sessions on Agent `agentId`. */
|
|
318
|
+
export declare function reachesAgent(access: SessionAccess | undefined, agentId: string): boolean;
|
|
292
319
|
/** The session, or a 404. Reads without locking. */
|
|
293
320
|
export declare function sessionOf(t: Tx, id: string, access?: SessionAccess): Promise<Session>;
|
|
294
321
|
/** Locks the session for the rest of the transaction and returns it, or a 404. */
|