@fastagent-sh/fastagent 0.19.0 → 0.20.0
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/atomic-write.d.ts +11 -7
- package/dist/atomic-write.js +16 -11
- package/dist/channels/agentcore-limits.d.ts +9 -0
- package/dist/channels/agentcore-limits.js +9 -0
- package/dist/channels/agentcore-service.d.ts +12 -11
- package/dist/channels/agentcore-service.js +14 -18
- package/dist/channels/agentcore.d.ts +11 -19
- package/dist/channels/agentcore.js +14 -7
- package/dist/channels/control.d.ts +50 -12
- package/dist/channels/control.js +239 -96
- package/dist/channels/discover.d.ts +26 -4
- package/dist/channels/discover.js +12 -23
- package/dist/channels/feishu/feishu-api.js +5 -8
- package/dist/channels/feishu/feishu.js +35 -18
- package/dist/channels/kit/attachment-path.d.ts +12 -0
- package/dist/channels/kit/attachment-path.js +43 -0
- package/dist/channels/kit/context-buffer.d.ts +9 -0
- package/dist/channels/kit/context-buffer.js +11 -0
- package/dist/channels/kit/signature.d.ts +12 -0
- package/dist/channels/kit/signature.js +17 -0
- package/dist/channels/kit/stop-command.js +3 -3
- package/dist/channels/kit/tasks.d.ts +1 -1
- package/dist/channels/kit/tasks.js +10 -4
- package/dist/channels/kit/turn-store.d.ts +19 -3
- package/dist/channels/kit/turn-store.js +29 -2
- package/dist/channels/slack/invoke-turn.d.ts +1 -1
- package/dist/channels/slack/invoke-turn.js +5 -1
- package/dist/channels/slack/onboard.js +5 -5
- package/dist/channels/slack/onboarding-state.d.ts +7 -3
- package/dist/channels/slack/onboarding-state.js +13 -20
- package/dist/channels/slack/register-webhook.js +1 -1
- package/dist/channels/slack/scaffold/slack-send.ts +2 -17
- package/dist/channels/slack/slack-api.js +22 -54
- package/dist/channels/slack/slack.js +13 -13
- package/dist/channels/telegram/parse.d.ts +21 -1
- package/dist/channels/telegram/parse.js +65 -11
- package/dist/channels/telegram/scaffold/channel.ts +7 -3
- package/dist/channels/telegram/telegram-api.js +5 -6
- package/dist/channels/telegram/telegram.d.ts +2 -2
- package/dist/channels/telegram/telegram.js +57 -69
- package/dist/cli/add-slack.js +6 -5
- package/dist/cli/commands/add.js +1 -5
- package/dist/cli/commands/attach.d.ts +2 -4
- package/dist/cli/commands/attach.js +17 -15
- package/dist/cli/commands/deploy.d.ts +50 -5
- package/dist/cli/commands/deploy.js +183 -134
- package/dist/cli/commands/dev.js +4 -31
- package/dist/cli/commands/info.js +1 -2
- package/dist/cli/commands/init.js +1 -2
- package/dist/cli/commands/start.js +31 -41
- package/dist/cli/serve.d.ts +12 -21
- package/dist/cli/serve.js +20 -25
- package/dist/cli/shared.d.ts +40 -10
- package/dist/cli/shared.js +51 -4
- package/dist/deploy/agentcore/logs.d.ts +2 -2
- package/dist/deploy/agentcore/logs.js +2 -2
- package/dist/deploy/agentcore/plan.d.ts +12 -10
- package/dist/deploy/agentcore/plan.js +39 -31
- package/dist/deploy/agentcore/run.d.ts +5 -4
- package/dist/deploy/agentcore/run.js +13 -36
- package/dist/deploy/channel-ingress.d.ts +73 -0
- package/dist/deploy/channel-ingress.js +101 -0
- package/dist/deploy/docker/plan.d.ts +3 -7
- package/dist/deploy/docker/plan.js +4 -15
- package/dist/deploy/docker/run.d.ts +18 -1
- package/dist/deploy/docker/run.js +10 -0
- package/dist/deploy/fly/plan.d.ts +4 -5
- package/dist/deploy/fly/plan.js +7 -22
- package/dist/deploy/fly/run.d.ts +9 -23
- package/dist/deploy/fly/run.js +32 -68
- package/dist/deploy/preflight.d.ts +6 -7
- package/dist/deploy/preflight.js +26 -22
- package/dist/deploy/railway/plan.d.ts +4 -5
- package/dist/deploy/railway/plan.js +11 -26
- package/dist/deploy/railway/run.d.ts +9 -9
- package/dist/deploy/railway/run.js +40 -40
- package/dist/deploy/runner.js +22 -2
- package/dist/deploy/secrets.d.ts +18 -9
- package/dist/deploy/secrets.js +64 -12
- package/dist/dev-supervisor.js +3 -4
- package/dist/engines/pi/agent-session-factory.js +20 -39
- package/dist/engines/pi/auth.js +43 -43
- package/dist/engines/pi/config.d.ts +1 -1
- package/dist/engines/pi/create.d.ts +14 -2
- package/dist/engines/pi/create.js +2 -2
- package/dist/engines/pi/definition.d.ts +1 -1
- package/dist/engines/pi/definition.js +3 -4
- package/dist/engines/pi/login.js +32 -16
- package/dist/engines/pi/open.d.ts +1 -2
- package/dist/engines/pi/open.js +1 -1
- package/dist/engines/pi/search-tools.js +1 -1
- package/dist/engines/pi/service.d.ts +1 -1
- package/dist/engines/pi/service.js +8 -0
- package/dist/engines/pi/session-builder.js +6 -33
- package/dist/engines/pi/session-control.d.ts +19 -6
- package/dist/engines/pi/session-control.js +565 -470
- package/dist/engines/pi/session-inheritance.d.ts +8 -22
- package/dist/engines/pi/session-inheritance.js +95 -76
- package/dist/engines/pi/session-markers.d.ts +48 -0
- package/dist/engines/pi/session-markers.js +59 -0
- package/dist/engines/pi/session-settings.d.ts +4 -4
- package/dist/engines/pi/session-settings.js +7 -4
- package/dist/engines/pi/session-store.d.ts +83 -13
- package/dist/engines/pi/session-store.js +408 -55
- package/dist/engines/pi/tool-context.d.ts +21 -8
- package/dist/engines/pi/tool-context.js +29 -4
- package/dist/engines/pi/tool.js +2 -2
- package/dist/env.js +1 -2
- package/dist/loader.d.ts +49 -7
- package/dist/loader.js +79 -18
- package/dist/paths.d.ts +19 -0
- package/dist/paths.js +27 -2
- package/dist/scaffold/add-channel.js +2 -2
- package/dist/scaffold/init.js +7 -2
- package/dist/scaffold/templates/tools/fetch-url.ts +0 -2
- package/dist/schedule/discover.js +3 -15
- package/dist/schedule/wake-alarm.d.ts +13 -7
- package/dist/schedule/wake-alarm.js +88 -46
- package/dist/schedule/wakeups.d.ts +1 -1
- package/dist/schedule/wakeups.js +10 -7
- package/dist/service.d.ts +1 -2
- package/dist/service.js +27 -5
- package/dist/session-remote.d.ts +7 -2
- package/dist/session-remote.js +162 -92
- package/dist/session.d.ts +226 -92
- package/dist/session.js +61 -23
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +1 -1
- package/dist/tunnel.d.ts +18 -3
- package/dist/tunnel.js +32 -40
- package/package.json +1 -1
package/dist/atomic-write.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Write a file so a reader sees the whole thing or nothing: same-directory temp, then rename.
|
|
3
3
|
*
|
|
4
|
-
* The temp name is fixed (`<path>.tmp`).
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* The temp name is fixed (`<path>.tmp`). It holds wherever one process writes one state root: a
|
|
5
|
+
* deployment runs one container, `dev`'s supervisor respawns its worker only after the old one has
|
|
6
|
+
* EXITED (dev-supervisor.ts), and these writes are synchronous, so an exited process has none in
|
|
7
|
+
* flight. Slack's onboarding state is the one file with two writers — `add slack`, and the
|
|
8
|
+
* config-token rotation inside `--tunnel` webhook registration — so a second terminal running
|
|
9
|
+
* `add slack --replace-config` can overlap a live `dev --tunnel`. Kept fixed anyway: that window is
|
|
10
|
+
* a single write at tunnel startup, its repair is the `add slack --replace-config` the registration
|
|
11
|
+
* failure already prints, and the fixed name is the seam several channel tests use to inject a write
|
|
12
|
+
* failure by occupying that path with a directory. Revisit for a writer that is neither rare nor
|
|
13
|
+
* self-repairing.
|
|
10
14
|
*
|
|
11
15
|
* `mode` is applied to the temp first, so the content is never briefly world-readable.
|
|
12
16
|
*
|
|
@@ -16,4 +20,4 @@
|
|
|
16
20
|
* It runs on the temp, before the rename — the final path is then never observable with the wrong
|
|
17
21
|
* permissions, which a chmod after the rename cannot promise.
|
|
18
22
|
*/
|
|
19
|
-
export declare function writeFileAtomic(path: string, data: string, mode?: number): void;
|
|
23
|
+
export declare function writeFileAtomic(path: string, data: string | Buffer, mode?: number): void;
|
package/dist/atomic-write.js
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* One spelling of "a reader sees the whole file or none of it"
|
|
3
|
-
*
|
|
4
|
-
* handling.
|
|
2
|
+
* One spelling of "a reader sees the whole file or none of it", after five copies of it drifted
|
|
3
|
+
* apart: two identical, three with different temp names and different permission handling.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Synchronous, and there is no async sibling: every caller either sits on a path where a KB-sized
|
|
6
|
+
* write must complete BEFORE a transport ACK (channel state — an ACKed delivery is not redelivered)
|
|
7
|
+
* or on a CLI/startup path where the cost is not observable. An async spelling would buy one of them
|
|
8
|
+
* nothing and cost this module a second set of rules to keep true.
|
|
8
9
|
*/
|
|
9
10
|
import { chmodSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
10
11
|
import { dirname } from "node:path";
|
|
11
12
|
/**
|
|
12
13
|
* Write a file so a reader sees the whole thing or nothing: same-directory temp, then rename.
|
|
13
14
|
*
|
|
14
|
-
* The temp name is fixed (`<path>.tmp`).
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* The temp name is fixed (`<path>.tmp`). It holds wherever one process writes one state root: a
|
|
16
|
+
* deployment runs one container, `dev`'s supervisor respawns its worker only after the old one has
|
|
17
|
+
* EXITED (dev-supervisor.ts), and these writes are synchronous, so an exited process has none in
|
|
18
|
+
* flight. Slack's onboarding state is the one file with two writers — `add slack`, and the
|
|
19
|
+
* config-token rotation inside `--tunnel` webhook registration — so a second terminal running
|
|
20
|
+
* `add slack --replace-config` can overlap a live `dev --tunnel`. Kept fixed anyway: that window is
|
|
21
|
+
* a single write at tunnel startup, its repair is the `add slack --replace-config` the registration
|
|
22
|
+
* failure already prints, and the fixed name is the seam several channel tests use to inject a write
|
|
23
|
+
* failure by occupying that path with a directory. Revisit for a writer that is neither rare nor
|
|
24
|
+
* self-repairing.
|
|
20
25
|
*
|
|
21
26
|
* `mode` is applied to the temp first, so the content is never briefly world-readable.
|
|
22
27
|
*
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HOST's body ceilings, and the one place they are computed.
|
|
3
|
+
*
|
|
4
|
+
* Lambda Function URLs cap a request at 6 MB, so the forwarder cannot deliver more than that no
|
|
5
|
+
* matter what the adapter accepts; the body arrives base64-encoded (×4/3) inside a JSON envelope, so
|
|
6
|
+
* the ORIGINAL body ceiling is smaller still. This is a real capability difference from a resident
|
|
7
|
+
* host — the GitHub channel's own contract is 25 MiB — which is why `deploy agentcore` states it at
|
|
8
|
+
* plan time rather than letting an oversized payload surface as an opaque 502.
|
|
9
|
+
*/
|
|
1
10
|
/**
|
|
2
11
|
* Maximum original webhook body after reserving JSON-envelope overhead and base64 expansion.
|
|
3
12
|
* Enforced by both the public forwarder and the runtime adapter.
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HOST's body ceilings, and the one place they are computed.
|
|
3
|
+
*
|
|
4
|
+
* Lambda Function URLs cap a request at 6 MB, so the forwarder cannot deliver more than that no
|
|
5
|
+
* matter what the adapter accepts; the body arrives base64-encoded (×4/3) inside a JSON envelope, so
|
|
6
|
+
* the ORIGINAL body ceiling is smaller still. This is a real capability difference from a resident
|
|
7
|
+
* host — the GitHub channel's own contract is 25 MiB — which is why `deploy agentcore` states it at
|
|
8
|
+
* plan time rather than letting an oversized payload surface as an opaque 502.
|
|
9
|
+
*/
|
|
1
10
|
/** AWS Lambda Function URLs accept request payloads up to 6 MB. */
|
|
2
11
|
const FUNCTION_URL_REQUEST_LIMIT = 6 * 1000 * 1000;
|
|
3
12
|
/**
|
|
@@ -18,7 +18,7 @@ import type { Agent } from "../agent.ts";
|
|
|
18
18
|
import type { Routes } from "../channel.ts";
|
|
19
19
|
import type { LoadedSchedule } from "../schedule/schedule.ts";
|
|
20
20
|
import { type AgentService, type MountableAgent } from "../service.ts";
|
|
21
|
-
import { type
|
|
21
|
+
import { type AgentcoreAdapterOptions } from "./agentcore.ts";
|
|
22
22
|
export interface MountAgentcoreServiceOptions {
|
|
23
23
|
/** Wrap the opened agent before anything binds to it (the CLI's turn trace). */
|
|
24
24
|
wrapAgent?: (agent: Agent) => Agent;
|
|
@@ -34,21 +34,22 @@ export interface MountAgentcoreServiceOptions {
|
|
|
34
34
|
export declare function isAgentcoreRuntime(): boolean;
|
|
35
35
|
export declare function mountAgentcoreService(opened: MountableAgent, options?: MountAgentcoreServiceOptions): Promise<AgentService>;
|
|
36
36
|
/**
|
|
37
|
-
* Mount the AgentCore Runtime adapter (`POST /invocations` + `GET /ping`)
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* the
|
|
37
|
+
* Mount the AgentCore Runtime adapter (`POST /invocations` + `GET /ping`) — the deployed container's
|
|
38
|
+
* ONLY reachable surface (channels/agentcore.ts). Wired by `start` when `FASTAGENT_AGENTCORE=1` (set
|
|
39
|
+
* by the generated deploy artifacts, never by hand).
|
|
40
|
+
*
|
|
41
|
+
* The adapter IS the surface: the agent's channels live in a table INSIDE the envelope dispatch, a
|
|
42
|
+
* separate namespace from these two paths, so a channel route named `/invocations` is reached
|
|
43
|
+
* through the Function URL as itself and cannot shadow anything.
|
|
42
44
|
*/
|
|
43
|
-
export declare function mountAgentcore(
|
|
45
|
+
export declare function mountAgentcore(options: {
|
|
44
46
|
agent: Agent;
|
|
45
47
|
stateRoot: string;
|
|
46
48
|
schedules: readonly LoadedSchedule[];
|
|
47
49
|
onStateReady?: () => void;
|
|
48
50
|
/** Cancels the adapter's process-global registrations on close. */
|
|
49
51
|
signal?: AbortSignal;
|
|
50
|
-
/** The
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
lazyChannels?: () => Promise<RouteSurface>;
|
|
52
|
+
/** The channel surface, constructed on the first envelope AFTER the state-snapshot restore — never
|
|
53
|
+
* at boot, where the mount is pre-restore (channels/agentcore.ts). */
|
|
54
|
+
channels: AgentcoreAdapterOptions["channels"];
|
|
54
55
|
}): Routes;
|
|
@@ -4,7 +4,7 @@ import { assertNoControlPlaneCollision, mountSessionControl, routesFor, startSch
|
|
|
4
4
|
import { UnknownScheduleError, agentcoreRoutes } from "./agentcore.js";
|
|
5
5
|
import { createStateSync } from "./agentcore-state.js";
|
|
6
6
|
import { activeWork } from "./busy.js";
|
|
7
|
-
import {
|
|
7
|
+
import { router } from "./serve.js";
|
|
8
8
|
/** Is this process running inside the AgentCore Runtime? Set by the generated deploy artifacts. */
|
|
9
9
|
export function isAgentcoreRuntime() {
|
|
10
10
|
return process.env.FASTAGENT_AGENTCORE === "1";
|
|
@@ -32,13 +32,13 @@ export async function mountAgentcoreService(opened, options = {}) {
|
|
|
32
32
|
};
|
|
33
33
|
// The adapter registers process-global listeners; this is what takes them down on close.
|
|
34
34
|
const closed = new AbortController();
|
|
35
|
-
const adapterRoutes = mountAgentcore({
|
|
35
|
+
const adapterRoutes = mountAgentcore({
|
|
36
36
|
signal: closed.signal,
|
|
37
37
|
agent,
|
|
38
38
|
stateRoot,
|
|
39
39
|
schedules: scheduled.schedules,
|
|
40
40
|
onStateReady: options.onStateReady,
|
|
41
|
-
lazyChannels,
|
|
41
|
+
channels: lazyChannels,
|
|
42
42
|
});
|
|
43
43
|
const handler = router(adapterRoutes, withControl.mounts);
|
|
44
44
|
log.info(`[fastagent] agentcore: serving POST /invocations + GET /ping (FASTAGENT_AGENTCORE=1)`);
|
|
@@ -70,16 +70,18 @@ export async function mountAgentcoreService(opened, options = {}) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
|
-
* Mount the AgentCore Runtime adapter (`POST /invocations` + `GET /ping`)
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* the
|
|
73
|
+
* Mount the AgentCore Runtime adapter (`POST /invocations` + `GET /ping`) — the deployed container's
|
|
74
|
+
* ONLY reachable surface (channels/agentcore.ts). Wired by `start` when `FASTAGENT_AGENTCORE=1` (set
|
|
75
|
+
* by the generated deploy artifacts, never by hand).
|
|
76
|
+
*
|
|
77
|
+
* The adapter IS the surface: the agent's channels live in a table INSIDE the envelope dispatch, a
|
|
78
|
+
* separate namespace from these two paths, so a channel route named `/invocations` is reached
|
|
79
|
+
* through the Function URL as itself and cannot shadow anything.
|
|
78
80
|
*/
|
|
79
|
-
export function mountAgentcore(
|
|
80
|
-
const { agent, stateRoot, schedules, onStateReady,
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
export function mountAgentcore(options) {
|
|
82
|
+
const { agent, stateRoot, schedules, onStateReady, channels, signal } = options;
|
|
83
|
+
return agentcoreRoutes({
|
|
84
|
+
channels,
|
|
83
85
|
agent,
|
|
84
86
|
stateRoot,
|
|
85
87
|
isBusy: () => activeWork() > 0,
|
|
@@ -102,10 +104,4 @@ export function mountAgentcore(routes, options) {
|
|
|
102
104
|
return fireScheduleOnce({ agent, stateRoot, schedule, slot });
|
|
103
105
|
},
|
|
104
106
|
});
|
|
105
|
-
const collisions = Object.keys(routes).filter((key) => Object.keys(mounted).some((adapterKey) => routeKeysConflict(key, adapterKey)));
|
|
106
|
-
if (collisions.length > 0) {
|
|
107
|
-
throw new Error(`channel route(s) ${collisions.map((key) => `"${key}"`).join(", ")} collide with the AgentCore adapter ` +
|
|
108
|
-
`(/invocations, /ping) — rename the channel route`);
|
|
109
|
-
}
|
|
110
|
-
return { ...routes, ...mounted };
|
|
111
107
|
}
|
|
@@ -3,14 +3,6 @@ import type { StateSync, StateUrls } from "./agentcore-state.ts";
|
|
|
3
3
|
import type { Routes } from "../channel.ts";
|
|
4
4
|
import { type PrefixMount } from "../channels/serve.ts";
|
|
5
5
|
import type { ScheduleFireOutcome } from "../schedule/scheduler.ts";
|
|
6
|
-
/**
|
|
7
|
-
* The HOST's webhook body limit, and the one place it is computed. Lambda Function URLs cap a request
|
|
8
|
-
* at 6 MB, so the forwarder cannot deliver more than that no matter what the adapter accepts; the
|
|
9
|
-
* body arrives base64-encoded (×4/3) inside a JSON envelope, so the ORIGINAL body ceiling is smaller
|
|
10
|
-
* still. This is a real capability difference from a resident host — the GitHub channel's own
|
|
11
|
-
* contract is 25 MiB — so `deploy agentcore` says so at plan time rather than letting an oversized
|
|
12
|
-
* payload surface as an opaque 502.
|
|
13
|
-
*/
|
|
14
6
|
/** What the forwarder Lambda / EventBridge deliver in the `/invocations` payload. Every kind may
|
|
15
7
|
* carry `wake` — the forwarder's self-resolved public URL, which the adapter persists so the wake
|
|
16
8
|
* ALARM sink (schedule/wake-alarm.ts) can call back without the URL being baked anywhere. */
|
|
@@ -79,13 +71,14 @@ export interface RouteSurface {
|
|
|
79
71
|
mounts?: readonly PrefixMount[];
|
|
80
72
|
}
|
|
81
73
|
export interface AgentcoreAdapterOptions {
|
|
82
|
-
/** The serving routes a direct deployment would mount (channels or the builtin invoke + health)
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
|
|
74
|
+
/** The serving routes a direct deployment would mount (channels or the builtin invoke + health),
|
|
75
|
+
* built LAZILY: channel construction loads channel state and replays durable turn intent, so on
|
|
76
|
+
* AgentCore it must not run until the state root is authoritative — which happens at the first
|
|
77
|
+
* envelope's `stateSync.ready()` (the restore URLs only an envelope carries), never at boot, where
|
|
78
|
+
* the mount is pre-restore (empty after every version update). A factory, not a value: there is no
|
|
79
|
+
* moment during construction at which the right answer is knowable. May answer synchronously — the
|
|
80
|
+
* resolution chain normalizes it either way. */
|
|
81
|
+
channels: () => Promise<RouteSurface> | RouteSurface;
|
|
89
82
|
agent: Agent;
|
|
90
83
|
/** Where the forwarder URL from envelopes is persisted for the wake-alarm sink (the state root). */
|
|
91
84
|
stateRoot: string;
|
|
@@ -109,10 +102,9 @@ export interface AgentcoreAdapterOptions {
|
|
|
109
102
|
onStateReady?: () => void;
|
|
110
103
|
}
|
|
111
104
|
/**
|
|
112
|
-
* Build the AgentCore serving surface: `{ "POST /invocations", "GET /ping" }
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* a local `curl` debug surface.
|
|
105
|
+
* Build the AgentCore serving surface: `{ "POST /invocations", "GET /ping" }` — the whole of what the
|
|
106
|
+
* platform routes into the container. The agent's own channels are not beside these: they are a table
|
|
107
|
+
* inside the envelope dispatch below, reached only by unwrapping a forwarder envelope.
|
|
116
108
|
*/
|
|
117
109
|
export declare function agentcoreRoutes(options: AgentcoreAdapterOptions): Routes;
|
|
118
110
|
/** Thrown by the mount-site `fire` binding when the envelope names a schedule this workspace does
|
|
@@ -48,14 +48,13 @@ function secretMatches(actual, expected) {
|
|
|
48
48
|
return actualBytes.length === expectedBytes.length && timingSafeEqual(actualBytes, expectedBytes);
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
-
* Build the AgentCore serving surface: `{ "POST /invocations", "GET /ping" }
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* a local `curl` debug surface.
|
|
51
|
+
* Build the AgentCore serving surface: `{ "POST /invocations", "GET /ping" }` — the whole of what the
|
|
52
|
+
* platform routes into the container. The agent's own channels are not beside these: they are a table
|
|
53
|
+
* inside the envelope dispatch below, reached only by unwrapping a forwarder envelope.
|
|
55
54
|
*/
|
|
56
55
|
export function agentcoreRoutes(options) {
|
|
57
|
-
const {
|
|
58
|
-
// Lazy channel construction (see AgentcoreAdapterOptions.
|
|
56
|
+
const { channels, agent, stateRoot, isBusy, fire, stateSync, ingressSecret, onStateReady } = options;
|
|
57
|
+
// Lazy channel construction (see AgentcoreAdapterOptions.channels) — resolved ONCE per process, on
|
|
59
58
|
// the first trusted envelope after the state root is authoritative, and the outcome is cached
|
|
60
59
|
// EITHER WAY. Success: the same resident channels a direct host keeps. Failure too: construction
|
|
61
60
|
// is an ACTIVATION with side effects — loadChannels builds every healthy channel (starting its
|
|
@@ -71,7 +70,7 @@ export function agentcoreRoutes(options) {
|
|
|
71
70
|
// The factory runs INSIDE the chain: a synchronous throw must land in the cached rejection,
|
|
72
71
|
// not escape before `dispatchP` is assigned (which would silently re-run the activation).
|
|
73
72
|
dispatchP = Promise.resolve()
|
|
74
|
-
.then(
|
|
73
|
+
.then(channels)
|
|
75
74
|
.then((surface) => router(surface.routes, surface.mounts));
|
|
76
75
|
dispatchP.catch(() => { }); // observed here so the CACHED rejection is never "unhandled"
|
|
77
76
|
}
|
|
@@ -301,6 +300,14 @@ export function agentcoreRoutes(options) {
|
|
|
301
300
|
};
|
|
302
301
|
// Settle-then-snapshot: when the envelope leaves nothing in flight, its writes are final now (a
|
|
303
302
|
// background turn instead reports through the idle edge above).
|
|
303
|
+
//
|
|
304
|
+
// A public `invoke` is the one kind this MISSES, measurably: it answers with an unconsumed SSE
|
|
305
|
+
// stream, so the turn runs after this line and its records wait for the next envelope's snapshot.
|
|
306
|
+
// Counting it as in-flight work would close that and cost more than it buys — the stream drains at
|
|
307
|
+
// the CLIENT's pace, so one parked reader pins /ping at HealthyBusy and defeats the idle reclaim,
|
|
308
|
+
// which is the quota-exhaustion failure the ping contract below warns about. The kinds that carry
|
|
309
|
+
// conversations (webhook, schedule-fire, wake-poke) all run their turn inside the request or
|
|
310
|
+
// through `beginWork`, so they land on one of the two edges; `invoke` is the direct/debug door.
|
|
304
311
|
const invocations = async (req) => {
|
|
305
312
|
const response = await handleInvocation(req);
|
|
306
313
|
if (stateSync && !isBusy())
|
|
@@ -1,7 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The session control plane over HTTP + SSE — the Phase 3 transport (design §13). Engine-neutral:
|
|
3
|
+
* consumes only the `SessionControl` contract. One transport serves every remote consumer (Web
|
|
4
|
+
* panel, desktop app, `fastagent attach`); the embedded API stays semantic-only and the ENVELOPE
|
|
5
|
+
* lives here: `id` (request correlation — implicit in HTTP), `epoch` (serving-process incarnation
|
|
6
|
+
* — INFORMATIONAL, for consumers correlating across connections; a restart surfaces as its
|
|
7
|
+
* connections dropping, so no one fences on it), `seq` (per-connection monotonic, detects loss in
|
|
8
|
+
* transit).
|
|
9
|
+
*
|
|
10
|
+
* SECURITY: these routes steer and stop runs, rewrite session properties, and delete records — a
|
|
11
|
+
* remote-control surface. The bearer token is REQUIRED (there is no unauthenticated mode) and is the only auth the framework owns; anything
|
|
12
|
+
* beyond a shared secret (principals, per-permission split, audit) is the wrapping host's job
|
|
13
|
+
* (design §14). Locally the serving process generates a per-boot token and writes it to
|
|
14
|
+
* `<stateRoot>/control.json` for local discovery — filesystem permissions guard the token, and the
|
|
15
|
+
* token guards the routes. That premise is shared-filesystem: a deployment breaks it, so there the
|
|
16
|
+
* DEPLOYER supplies the token ({@link CONTROL_TOKEN_ENV}) and both sides know it. How far those routes REACH is the bind address: all interfaces by
|
|
17
|
+
* default (containers require it), so the port is LAN-reachable and the mount warns accordingly —
|
|
18
|
+
* `--bind 127.0.0.1` (or `http.host`) closes exactly that reach, and the warning goes quiet because
|
|
19
|
+
* there is none left to state.
|
|
20
|
+
*/
|
|
2
21
|
import type { Agent } from "../agent.ts";
|
|
3
|
-
import type
|
|
22
|
+
import { type SessionControl, type SessionEvent } from "../session.ts";
|
|
4
23
|
import { type PrefixMount } from "./serve.ts";
|
|
24
|
+
/** A plane handler: the request, plus the session id the path named (`""` where the path has none). */
|
|
25
|
+
type PlaneHandler = (req: Request, session: string) => Response | Promise<Response>;
|
|
26
|
+
/** The plane's route table: `"<METHOD> <path>"` → handler, where at most one path segment is
|
|
27
|
+
* {@link SESSION_SEGMENT}. */
|
|
28
|
+
export type PlaneRoutes = Record<string, PlaneHandler>;
|
|
29
|
+
/** The token, when the DEPLOYER owns it rather than the box (`mountSessionControl` reads it, `deploy`
|
|
30
|
+
* carries it). Declared here with the prefix because both are the plane's public names: the serving
|
|
31
|
+
* side and the deploy side must spell it identically, and a rename that hits only one of them fails
|
|
32
|
+
* silently — the box mints its own and every caller the runbook told gets a 401. */
|
|
33
|
+
export declare const CONTROL_TOKEN_ENV = "FASTAGENT_CONTROL_TOKEN";
|
|
5
34
|
/** The SSE payload: one control-plane event in its transport envelope. */
|
|
6
35
|
export interface WireEvent {
|
|
7
36
|
sessionId: string;
|
|
@@ -23,16 +52,25 @@ export interface ControlPlaneOptions {
|
|
|
23
52
|
agent?: Agent;
|
|
24
53
|
}
|
|
25
54
|
/**
|
|
26
|
-
* Create the control plane as a mountable prefix owner
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* The plane OWNS
|
|
30
|
-
*
|
|
55
|
+
* Create the control plane as a mountable prefix owner — a RESTful surface over
|
|
56
|
+
* {@link CONTROL_PREFIX}: sessions are a collection, a session is a resource, its history and event
|
|
57
|
+
* stream are sub-resources, and the things that HAPPEN to a run are posted to `…/actions`.
|
|
58
|
+
* The plane OWNS the prefix: it answers its own 404/405/preflight and puts CORS headers on every
|
|
59
|
+
* reply — see {@link planeApp}.
|
|
31
60
|
*/
|
|
32
61
|
export declare function createControlPlane(control: SessionControl, options: ControlPlaneOptions): PrefixMount;
|
|
33
62
|
/** Mount a plane route table as a {@link PrefixMount} — the plane owns a PREFIX, while a route
|
|
34
|
-
* table is a set of
|
|
35
|
-
export declare function mountControlPlane(routes:
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
|
|
63
|
+
* table is a set of paths, at most one segment of which is a session id. */
|
|
64
|
+
export declare function mountControlPlane(routes: PlaneRoutes): PrefixMount;
|
|
65
|
+
/**
|
|
66
|
+
* The plane's route table. Exported so the conformance sweeps derive their route list from what is
|
|
67
|
+
* actually mounted, rather than from a hand-kept copy that cannot notice a new route.
|
|
68
|
+
*
|
|
69
|
+
* The shape mirrors the contract: a collection, a resource, its sub-resources, and one action
|
|
70
|
+
* endpoint. What a session IS gets `GET`; what a session HAS gets `PATCH` (properties, last-wins);
|
|
71
|
+
* what happens TO a run gets `POST …/actions` (not a property — an event in time). `PUT` on a
|
|
72
|
+
* session id is the fork, and it is a PUT because a fork is idempotent by construction: the id is
|
|
73
|
+
* the caller's, the body says where the history came from, and repeating it changes nothing.
|
|
74
|
+
*/
|
|
75
|
+
export declare function controlPlaneRoutes(control: SessionControl, options: ControlPlaneOptions): PlaneRoutes;
|
|
76
|
+
export {};
|