@fastagent-sh/fastagent 0.18.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/README.md +25 -7
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/atomic-write.d.ts +23 -0
- package/dist/atomic-write.js +53 -0
- package/dist/{host/node.d.ts → channel.d.ts} +24 -28
- package/dist/channel.js +1 -0
- package/dist/channels/agentcore-limits.d.ts +9 -0
- package/dist/channels/agentcore-limits.js +9 -0
- package/dist/channels/agentcore-service.d.ts +55 -0
- package/dist/channels/agentcore-service.js +107 -0
- package/dist/channels/agentcore.d.ts +22 -20
- package/dist/channels/agentcore.js +20 -11
- package/dist/channels/busy.d.ts +1 -1
- package/dist/channels/busy.js +1 -1
- package/dist/channels/control.d.ts +55 -7
- package/dist/channels/control.js +312 -67
- package/dist/channels/discover.d.ts +59 -0
- package/dist/{engines/pi/channel.js → channels/discover.js} +19 -34
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +1 -1
- package/dist/channels/feishu/context-buffer.js +4 -4
- package/dist/channels/feishu/feishu-api.js +6 -9
- package/dist/channels/feishu/feishu.d.ts +1 -1
- package/dist/channels/feishu/feishu.js +41 -24
- package/dist/channels/feishu/invoke-turn.d.ts +1 -1
- package/dist/channels/feishu/invoke-turn.js +3 -4
- package/dist/channels/feishu/preview.d.ts +1 -1
- package/dist/channels/feishu/preview.js +7 -3
- package/dist/channels/feishu/ws-ingress.d.ts +1 -1
- package/dist/channels/github/github.d.ts +1 -1
- package/dist/channels/http.d.ts +11 -7
- package/dist/channels/http.js +1 -109
- package/dist/channels/kit/attachment-path.d.ts +12 -0
- package/dist/channels/kit/attachment-path.js +43 -0
- package/dist/channels/{context-buffer.d.ts → kit/context-buffer.d.ts} +9 -0
- package/dist/channels/{context-buffer.js → kit/context-buffer.js} +13 -2
- package/dist/channels/{invoke-turn-kit.d.ts → kit/invoke-turn-kit.d.ts} +14 -3
- package/dist/channels/{invoke-turn-kit.js → kit/invoke-turn-kit.js} +17 -6
- package/dist/channels/{preview-kit.d.ts → kit/preview-kit.d.ts} +3 -1
- package/dist/channels/{seen.js → kit/seen.js} +1 -1
- package/dist/channels/kit/signature.d.ts +12 -0
- package/dist/channels/kit/signature.js +17 -0
- package/dist/channels/{state.js → kit/state.js} +4 -7
- package/dist/channels/{stop-command.d.ts → kit/stop-command.d.ts} +1 -1
- package/dist/channels/{stop-command.js → kit/stop-command.js} +5 -5
- package/dist/channels/{tasks.d.ts → kit/tasks.d.ts} +1 -1
- package/dist/channels/{tasks.js → kit/tasks.js} +11 -5
- package/dist/channels/{thread-participants.js → kit/thread-participants.js} +1 -1
- package/dist/channels/{turn-queue.js → kit/turn-queue.js} +2 -2
- package/dist/channels/{turn-store.d.ts → kit/turn-store.d.ts} +19 -3
- package/dist/channels/{turn-store.js → kit/turn-store.js} +30 -3
- package/dist/channels/lark/lark.d.ts +1 -1
- package/dist/channels/serve.d.ts +73 -0
- package/dist/channels/serve.js +243 -0
- package/dist/channels/slack/bot-auth.js +3 -14
- package/dist/channels/slack/context-buffer.d.ts +2 -2
- package/dist/channels/slack/context-buffer.js +2 -2
- package/dist/channels/slack/invoke-turn.d.ts +2 -2
- package/dist/channels/slack/invoke-turn.js +13 -6
- 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/parse.js +2 -2
- package/dist/channels/slack/preview.d.ts +1 -1
- package/dist/channels/slack/preview.js +11 -3
- 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 +23 -55
- package/dist/channels/slack/slack.d.ts +1 -1
- package/dist/channels/slack/slack.js +20 -20
- package/dist/channels/slack/welcomed.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +2 -2
- package/dist/channels/telegram/context-buffer.js +2 -2
- package/dist/channels/telegram/invoke-turn.d.ts +1 -1
- package/dist/channels/telegram/invoke-turn.js +1 -1
- package/dist/channels/telegram/parse.d.ts +21 -1
- package/dist/channels/telegram/parse.js +67 -13
- package/dist/channels/telegram/preview.d.ts +1 -1
- package/dist/channels/telegram/preview.js +6 -2
- 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 +3 -3
- package/dist/channels/telegram/telegram.js +60 -72
- package/dist/channels/telegram/turn-store.d.ts +2 -2
- package/dist/channels/telegram/turn-store.js +2 -2
- 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 +25 -40
- package/dist/cli/commands/fire.js +1 -1
- package/dist/cli/commands/info.js +7 -5
- package/dist/cli/commands/init.js +1 -2
- package/dist/cli/commands/schedule.js +1 -1
- package/dist/cli/commands/start.js +80 -121
- package/dist/cli/commands/tool.js +5 -7
- package/dist/cli/program.js +1 -1
- package/dist/cli/serve.d.ts +22 -70
- package/dist/cli/serve.js +66 -257
- package/dist/cli/shared.d.ts +40 -10
- package/dist/cli/shared.js +53 -6
- package/dist/core.d.ts +3 -5
- package/dist/core.js +7 -5
- 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 +27 -23
- 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 +11 -6
- package/dist/engines/pi/agent-session-factory.d.ts +104 -0
- package/dist/engines/pi/agent-session-factory.js +295 -0
- package/dist/engines/pi/auth.js +43 -43
- package/dist/engines/pi/config.d.ts +5 -5
- package/dist/engines/pi/config.js +1 -1
- package/dist/engines/pi/create.d.ts +67 -42
- package/dist/engines/pi/create.js +142 -72
- package/dist/engines/pi/definition.d.ts +42 -8
- package/dist/engines/pi/definition.js +114 -11
- package/dist/engines/pi/invoke-session.d.ts +22 -19
- package/dist/engines/pi/invoke-session.js +286 -100
- package/dist/engines/pi/login.js +39 -20
- package/dist/engines/pi/models.d.ts +14 -0
- package/dist/engines/pi/models.js +9 -1
- package/dist/engines/pi/open.d.ts +10 -8
- package/dist/engines/pi/open.js +21 -11
- package/dist/engines/pi/report.d.ts +0 -4
- package/dist/engines/pi/report.js +0 -7
- package/dist/engines/pi/search-tools.js +1 -1
- package/dist/engines/pi/service.d.ts +23 -0
- package/dist/engines/pi/service.js +27 -0
- package/dist/engines/pi/session-builder.js +87 -118
- package/dist/engines/pi/session-control.d.ts +28 -14
- package/dist/engines/pi/session-control.js +570 -438
- package/dist/engines/pi/session-inheritance.d.ts +30 -0
- package/dist/engines/pi/session-inheritance.js +261 -0
- 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 +15 -3
- package/dist/engines/pi/session-settings.js +22 -1
- package/dist/engines/pi/session-store.d.ts +140 -0
- package/dist/engines/pi/session-store.js +666 -0
- package/dist/engines/pi/tool-context.d.ts +36 -13
- package/dist/engines/pi/tool-context.js +51 -4
- package/dist/engines/pi/tool.d.ts +5 -5
- package/dist/engines/pi/tool.js +3 -3
- package/dist/engines/pi/turn-kit.d.ts +32 -6
- package/dist/engines/pi/turn-kit.js +28 -1
- package/dist/engines/pi/wake-tool.js +1 -1
- package/dist/env.js +1 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/loader.d.ts +49 -7
- package/dist/loader.js +79 -18
- package/dist/log.d.ts +3 -0
- package/dist/log.js +5 -1
- package/dist/node.d.ts +16 -0
- package/dist/node.js +20 -0
- package/dist/paths.d.ts +26 -0
- package/dist/paths.js +37 -2
- package/dist/pi.d.ts +14 -8
- package/dist/pi.js +6 -5
- package/dist/scaffold/add-channel.js +2 -2
- package/dist/scaffold/init.js +7 -2
- package/dist/scaffold/templates/persona.md +2 -2
- package/dist/scaffold/templates/tools/fetch-url.ts +0 -2
- package/dist/schedule/discover.js +3 -15
- package/dist/schedule/state.js +4 -9
- package/dist/schedule/wake-alarm.d.ts +13 -7
- package/dist/schedule/wake-alarm.js +88 -46
- package/dist/schedule/wakeups.d.ts +2 -2
- package/dist/schedule/wakeups.js +11 -8
- package/dist/service.d.ts +157 -0
- package/dist/service.js +376 -0
- package/dist/session-remote.d.ts +8 -3
- package/dist/session-remote.js +162 -92
- package/dist/session.d.ts +229 -93
- 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 +14 -5
- package/dist/engines/pi/channel.d.ts +0 -37
- package/dist/engines/pi/harness.d.ts +0 -115
- package/dist/engines/pi/harness.js +0 -154
- package/dist/engines/pi/invoke.d.ts +0 -60
- package/dist/engines/pi/invoke.js +0 -459
- package/dist/engines/pi/read-image.d.ts +0 -4
- package/dist/engines/pi/read-image.js +0 -62
- package/dist/engines/pi/sessions.d.ts +0 -60
- package/dist/engines/pi/sessions.js +0 -464
- package/dist/host/node.js +0 -52
- /package/dist/channels/{preview-kit.js → kit/preview-kit.js} +0 -0
- /package/dist/channels/{seen.d.ts → kit/seen.d.ts} +0 -0
- /package/dist/channels/{state.d.ts → kit/state.d.ts} +0 -0
- /package/dist/channels/{text.d.ts → kit/text.d.ts} +0 -0
- /package/dist/channels/{text.js → kit/text.js} +0 -0
- /package/dist/channels/{thread-participants.d.ts → kit/thread-participants.d.ts} +0 -0
- /package/dist/channels/{turn-queue.d.ts → kit/turn-queue.d.ts} +0 -0
package/dist/cli/serve.js
CHANGED
|
@@ -1,188 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* What `dev` (its worker) and `start` need beyond the service itself: binding a port, the shutdown
|
|
3
|
+
* order, the startup report, and the optional Cloudflare quick tunnel.
|
|
4
|
+
*
|
|
5
|
+
* The ASSEMBLY is not here — it lives in `src/service.ts`, which a public entry may import and this
|
|
6
|
+
* directory may not be (it decides process-level things: `fail.ts` calls `process.exit`).
|
|
4
7
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { createStateSync } from "../channels/agentcore-state.js";
|
|
8
|
-
import { agentcoreRoutes, UnknownScheduleError } from "../channels/agentcore.js";
|
|
9
|
-
import { activeWork } from "../channels/busy.js";
|
|
10
|
-
import { controlRoutes } from "../channels/control.js";
|
|
11
|
-
import { INVOKE_EXAMPLE_BODY, createInvokeHandler } from "../channels/http.js";
|
|
12
|
-
import { text } from "../channels/respond.js";
|
|
13
|
-
import { loadChannels } from "../engines/pi/channel.js";
|
|
14
|
-
import { reportModuleLoadFailures } from "../engines/pi/report.js";
|
|
8
|
+
import { INVOKE_EXAMPLE_BODY } from "../channels/http.js";
|
|
15
9
|
import { answersLocalhost, bindLabel, classifyBind, clientHost } from "../bind.js";
|
|
16
|
-
import {
|
|
10
|
+
import { serveNode } from "../channels/serve.js";
|
|
17
11
|
import { log } from "../log.js";
|
|
18
12
|
import { openExternalUrl } from "../open-url.js";
|
|
19
|
-
import {
|
|
20
|
-
import { createScheduler, fireScheduleOnce } from "../schedule/scheduler.js";
|
|
13
|
+
import { declaredChannels } from "../channels/discover.js";
|
|
21
14
|
import { announceWebhooks, startCloudflareTunnel } from "../tunnel.js";
|
|
22
15
|
import { failStartup, failUsage } from "./fail.js";
|
|
23
|
-
/**
|
|
24
|
-
* The surface this deployment serves: default `GET /health` plus discovered channels, or the default
|
|
25
|
-
* POST `/invoke` only when neither a route nor a long-connection channel was declared.
|
|
26
|
-
*/
|
|
27
|
-
export async function routesFor(agentDir, agent, stateRoot, control, options = {}) {
|
|
28
|
-
const { routes, longConnections, routeChannels, collisions, failures } = await loadChannels(agentDir, {
|
|
29
|
-
agent,
|
|
30
|
-
stateRoot,
|
|
31
|
-
control,
|
|
32
|
-
});
|
|
33
|
-
for (const c of collisions) {
|
|
34
|
-
console.error(`[fastagent] warn: channel route "${c.route}" (${c.source}) collides with an earlier channel — not mounted`);
|
|
35
|
-
}
|
|
36
|
-
reportModuleLoadFailures(failures);
|
|
37
|
-
if (failures.length > 0 || collisions.length > 0) {
|
|
38
|
-
throw new Error(`channel setup is invalid (${failures.length} load failure(s), ${collisions.length} route collision(s)) — ` +
|
|
39
|
-
`fix it, or rename an intentionally disabled file to *.disabled`);
|
|
40
|
-
}
|
|
41
|
-
const builtinInvoke = options.builtinInvoke !== false && Object.keys(routes).length === 0 && longConnections.length === 0;
|
|
42
|
-
const channels = builtinInvoke ? { "POST /invoke": createInvokeHandler(agent) } : routes;
|
|
43
|
-
const healthCovered = Object.keys(channels).some((key) => {
|
|
44
|
-
const entry = parseRouteKey(key);
|
|
45
|
-
return entry.path === "/health" && (entry.method === undefined || entry.method === "GET");
|
|
46
|
-
});
|
|
47
|
-
let ready = longConnections.length === 0;
|
|
48
|
-
const health = () => (ready ? text("ok\n", 200) : text("starting\n", 503));
|
|
49
|
-
return {
|
|
50
|
-
routes: healthCovered ? channels : { "GET /health": health, ...channels },
|
|
51
|
-
longConnections,
|
|
52
|
-
routeChannels,
|
|
53
|
-
builtinInvoke,
|
|
54
|
-
markReady() {
|
|
55
|
-
ready = true;
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Mount the session control plane (`/control/*`) when the agent enabled it
|
|
61
|
-
* (`config.sessionControl`): merge the bearer-authenticated routes and return an announcer that
|
|
62
|
-
* writes `<stateRoot>/control.json` — `{ url, token }`, 0600 — once the port is known. The file is
|
|
63
|
-
* the LOCAL discovery channel (`fastagent attach`, a local desktop app); filesystem permissions are
|
|
64
|
-
* its trust boundary, and each boot overwrites it with a fresh per-boot token. A user channel
|
|
65
|
-
* colliding on `/control/*` fails startup — the same disposition as a channel-channel collision
|
|
66
|
-
* (routesFor): `sessionControl` is an explicit opt-in, so declaring both is a configuration error,
|
|
67
|
-
* and silently shadowing either side would serve a surface the author didn't write.
|
|
68
|
-
*/
|
|
69
|
-
export function mountSessionControl(routes, control, stateRoot, options = {}) {
|
|
70
|
-
if (!control)
|
|
71
|
-
return { routes, announce: () => { } };
|
|
72
|
-
const token = crypto.randomUUID();
|
|
73
|
-
const mounted = controlRoutes(control, { token, agent: options.agent });
|
|
74
|
-
// PATH-level collision, matching the router's semantics (an any-method "/control/dispatch"
|
|
75
|
-
// channel key would dodge an exact-key check yet still shadow the method-qualified control
|
|
76
|
-
// route at match time — the router matches by path first).
|
|
77
|
-
const mountedPaths = new Set(Object.keys(mounted).map((key) => parseRouteKey(key).path));
|
|
78
|
-
const collisions = Object.keys(routes).filter((key) => mountedPaths.has(parseRouteKey(key).path));
|
|
79
|
-
if (collisions.length > 0) {
|
|
80
|
-
throw new Error(`channel route(s) ${collisions.map((key) => `"${key}"`).join(", ")} collide with the session control plane — ` +
|
|
81
|
-
`rename the channel route or disable sessionControl in fastagent.config`);
|
|
82
|
-
}
|
|
83
|
-
return {
|
|
84
|
-
routes: { ...routes, ...mounted },
|
|
85
|
-
announce: (boundPort) => {
|
|
86
|
-
// The state root normally exists (the opener mkdirs the sessions dir under it), but an
|
|
87
|
-
// external --sessions-dir leaves it uncreated — and announce runs inside serve's listening
|
|
88
|
-
// callback, where a throw is an unhandled rejection, not a one-line startup diagnostic.
|
|
89
|
-
mkdirSync(stateRoot, { recursive: true, mode: 0o700 });
|
|
90
|
-
const path = join(stateRoot, "control.json");
|
|
91
|
-
// Atomic (tmp+rename, the state.ts pattern): attach re-reads this file exactly during the
|
|
92
|
-
// restart window — a torn read would be misdiagnosed as "serve gone".
|
|
93
|
-
const tmp = `${path}.tmp`;
|
|
94
|
-
writeFileSync(tmp, `${JSON.stringify({ url: `http://${clientHost(options.host)}:${boundPort}`, token })}\n`, {
|
|
95
|
-
mode: 0o600,
|
|
96
|
-
});
|
|
97
|
-
chmodSync(tmp, 0o600); // an existing file keeps its old mode on rewrite — pin it
|
|
98
|
-
renameSync(tmp, path);
|
|
99
|
-
log.info(`[fastagent] session control on /control/* (token in ${path})`);
|
|
100
|
-
// The serve binds ALL interfaces by DEFAULT (containers require it), so /control/* is
|
|
101
|
-
// LAN-reachable with the bearer token as the only protection — the tunnel and deploy paths warn
|
|
102
|
-
// loudly, and the LAN path must not be the silent third way past the local trust story. A
|
|
103
|
-
// loopback bind closes exactly that reach, so it earns silence.
|
|
104
|
-
const bind = classifyBind(options.host);
|
|
105
|
-
if (bind !== "loopback") {
|
|
106
|
-
log.warn(`[fastagent] the port binds ${bind === "wildcard" ? "all interfaces" : `${options.host} (off this machine)`}: ` +
|
|
107
|
-
"/control/* is reachable on your LAN, protected only by the bearer token — bind loopback " +
|
|
108
|
-
"(--bind 127.0.0.1), firewall the port, or wrap it for real exposure (docs: design §14)");
|
|
109
|
-
}
|
|
110
|
-
if (options.tunnel) {
|
|
111
|
-
// Local trust = the token + its file permissions; --tunnel takes the whole port PUBLIC
|
|
112
|
-
// (beyond even the LAN reach the mount already warned about). The operator asked for the tunnel (webhooks), but must not DISCOVER the control
|
|
113
|
-
// plane went public with it — say it loudly.
|
|
114
|
-
log.warn("[fastagent] --tunnel exposes /control/* (steer/abort/set_model) at the public tunnel URL, " +
|
|
115
|
-
"protected ONLY by the bearer token — wrap it with real auth before sharing that URL (docs: design §14)");
|
|
116
|
-
}
|
|
117
|
-
// Best-effort lifecycle end: a clean exit removes the discovery file so a later `attach`
|
|
118
|
-
// fails with "cannot read" (accurate) instead of a stale token's misleading 401/ECONNREFUSED.
|
|
119
|
-
const unlink = () => {
|
|
120
|
-
try {
|
|
121
|
-
rmSync(path, { force: true });
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
/* the file is advisory — exit must not fail on it */
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
// Signal handlers MUST NOT absorb termination: registering any listener disables Node's
|
|
128
|
-
// default kill, so clean up and RE-RAISE. The mechanism: `process.kill` delivery is ASYNC —
|
|
129
|
-
// it lands after the current emit completes, so every listener of this same emit (scheduler
|
|
130
|
-
// stop, tunnel close — regardless of registration order) runs first, and the re-raised
|
|
131
|
-
// signal then hits the default action because each `once` handler is already consumed. When
|
|
132
|
-
// some listener exits the process itself (the tunnel path calls process.exit(0)), the
|
|
133
|
-
// re-raise is harmless redundancy. Without this, the first Ctrl+C would leave the serve
|
|
134
|
-
// alive minus its control.json, and dev's watch restart (SIGTERM → wait for exit → respawn)
|
|
135
|
-
// would hang on a worker that never exits.
|
|
136
|
-
const unlinkAndReraise = (signal) => () => {
|
|
137
|
-
unlink();
|
|
138
|
-
process.kill(process.pid, signal);
|
|
139
|
-
};
|
|
140
|
-
process.once("SIGINT", unlinkAndReraise("SIGINT"));
|
|
141
|
-
process.once("SIGTERM", unlinkAndReraise("SIGTERM"));
|
|
142
|
-
process.once("exit", unlink);
|
|
143
|
-
},
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Mount the AgentCore Runtime adapter (`POST /invocations` + `GET /ping`) over the serving routes —
|
|
148
|
-
* the deployed container's ONLY reachable surface (channels/agentcore.ts). Wired by `start` when
|
|
149
|
-
* `FASTAGENT_AGENTCORE=1` (set by the generated deploy artifacts, never by hand). A channel colliding
|
|
150
|
-
* on either path fails startup, same disposition as the control-plane mount: the adapter's paths are
|
|
151
|
-
* the platform's contract, so a channel shadowing them would silently unserve the whole deployment.
|
|
152
|
-
*/
|
|
153
|
-
export function mountAgentcore(routes, options) {
|
|
154
|
-
const { agent, stateRoot, schedules, onStateReady, lazyChannels } = options;
|
|
155
|
-
const mounted = agentcoreRoutes({
|
|
156
|
-
routes: lazyChannels ?? routes,
|
|
157
|
-
agent,
|
|
158
|
-
stateRoot,
|
|
159
|
-
isBusy: () => activeWork() > 0,
|
|
160
|
-
// Cross-deploy durability: AgentCore wipes the state mount on every runtime version update, so
|
|
161
|
-
// the state root is restored from (and pushed to) an S3 snapshot through presigned URLs the
|
|
162
|
-
// forwarder mints per envelope. Always wired on this path — the platform gives no other way to
|
|
163
|
-
// keep an agent's memory across a deploy.
|
|
164
|
-
stateSync: createStateSync({ stateRoot }),
|
|
165
|
-
// What separates a forwarder envelope from any IAM principal's InvokeAgentRuntime call. Absent =
|
|
166
|
-
// no forwarder in this topology, so only the public `invoke` kind is servable.
|
|
167
|
-
ingressSecret: process.env.FASTAGENT_INGRESS_SECRET,
|
|
168
|
-
onStateReady,
|
|
169
|
-
fire: schedules.length === 0
|
|
170
|
-
? undefined
|
|
171
|
-
: (name, slot) => {
|
|
172
|
-
const schedule = schedules.find((s) => s.name === name);
|
|
173
|
-
if (!schedule)
|
|
174
|
-
throw new UnknownScheduleError(name);
|
|
175
|
-
return fireScheduleOnce({ agent, stateRoot, schedule, slot });
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
const mountedPaths = new Set(Object.keys(mounted).map((key) => parseRouteKey(key).path));
|
|
179
|
-
const collisions = Object.keys(routes).filter((key) => mountedPaths.has(parseRouteKey(key).path));
|
|
180
|
-
if (collisions.length > 0) {
|
|
181
|
-
throw new Error(`channel route(s) ${collisions.map((key) => `"${key}"`).join(", ")} collide with the AgentCore adapter ` +
|
|
182
|
-
`(/invocations, /ping) — rename the channel route`);
|
|
183
|
-
}
|
|
184
|
-
return { ...routes, ...mounted };
|
|
185
|
-
}
|
|
186
16
|
/**
|
|
187
17
|
* Refuse `--tunnel` with a bind that cloudflared cannot reach: it dials the NAME `localhost:<port>`
|
|
188
18
|
* (the dev supervisor's tunnel too), so anything outside `127.0.0.1`/`::1`/wildcard — including a
|
|
@@ -205,6 +35,20 @@ export function assertTunnelBindable(host, tunnel, source) {
|
|
|
205
35
|
failUsage(message);
|
|
206
36
|
failStartup(new Error(message));
|
|
207
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* The "we are serving" report: the supervisor message `dev`'s watcher waits for, the addresses, and
|
|
40
|
+
* what mounted. One function because both commands must say the same thing at the same moment —
|
|
41
|
+
* after readiness, never at socket bind.
|
|
42
|
+
*/
|
|
43
|
+
export function reportServing(service, host, boundPort) {
|
|
44
|
+
process.send?.({ type: "ready", port: boundPort, routeChannels: service.channels.routes });
|
|
45
|
+
for (const line of readyAddressLines(host, boundPort, service.channels.builtinInvoke))
|
|
46
|
+
log.info(line);
|
|
47
|
+
log.info(`[fastagent] routes: ${Object.keys(service.routes).join(", ") || "(none)"}`);
|
|
48
|
+
if (service.channels.longConnections.length > 0) {
|
|
49
|
+
log.info(`[fastagent] long connections: ${service.channels.longConnections.join(", ")}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
208
52
|
/**
|
|
209
53
|
* The startup lines that name WHERE the serve is: the bind report, and the curl the reader copies.
|
|
210
54
|
* ONE function because they are one message — they were two, and `--bind` updated the first while the
|
|
@@ -224,81 +68,64 @@ export function readyAddressLines(host, boundPort, builtinInvoke) {
|
|
|
224
68
|
}
|
|
225
69
|
return lines;
|
|
226
70
|
}
|
|
71
|
+
/** What the CLI gives a service to stop in, and the hard exit that follows it. The order matters:
|
|
72
|
+
* a forced exit before the service answers would report a clean shutdown over a stuck channel. */
|
|
73
|
+
export const SHUTDOWN_GRACE_MS = 800;
|
|
74
|
+
const FORCED_EXIT_MS = 1_500;
|
|
227
75
|
/**
|
|
228
|
-
* Bind HTTP
|
|
229
|
-
*
|
|
230
|
-
*
|
|
76
|
+
* Bind HTTP and report ready — but only once the SERVICE is, which is not the same moment: a bound
|
|
77
|
+
* socket is not a serving agent while a declared long-connection channel is still dialling, so this
|
|
78
|
+
* awaits `hooks.ready` (mountAgentService owns the connections themselves) before announcing
|
|
79
|
+
* anything. Signals are the sole clean-shutdown command; `host` unset binds all interfaces.
|
|
231
80
|
*/
|
|
232
|
-
export function serve(
|
|
81
|
+
export function serve(handler, bind, hooks = {}) {
|
|
233
82
|
const { port, host } = bind;
|
|
234
|
-
const hosted = serveNode(
|
|
235
|
-
const abort = new AbortController();
|
|
83
|
+
const hosted = serveNode(handler, { port, host });
|
|
236
84
|
let stopping = false;
|
|
237
85
|
const stop = (exitCode) => {
|
|
238
86
|
if (stopping)
|
|
239
87
|
return;
|
|
240
88
|
stopping = true;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
.
|
|
89
|
+
// Bounded: shutdown must not hang on a channel that will not close, so the deadline fires
|
|
90
|
+
// regardless. No drain — an in-flight turn is cut, which is the existing contract.
|
|
91
|
+
// Later than the service's own close deadline (SHUTDOWN_GRACE_MS below), or the process leaves
|
|
92
|
+
// at 0 before `close()` has said a channel would not stop.
|
|
93
|
+
const deadline = setTimeout(() => {
|
|
94
|
+
log.error(`[fastagent] shutdown did not finish within ${FORCED_EXIT_MS}ms; exiting`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}, FORCED_EXIT_MS);
|
|
97
|
+
// Stop accepting FIRST, before anything is awaited. `onShutdown` waits for long connections to
|
|
98
|
+
// close, and a socket still listening through that wait would dispatch new work into channels
|
|
99
|
+
// and a scheduler that are already shutting down.
|
|
100
|
+
const closingServer = hosted.close();
|
|
101
|
+
hosted.closeAllConnections();
|
|
102
|
+
// A cleanup that failed is not a clean exit: `close()` reports a channel that would not stop,
|
|
103
|
+
// and swallowing it here would end the process at 0 over a resource still holding on.
|
|
104
|
+
void Promise.allSettled([closingServer, Promise.resolve(hooks.onShutdown?.())]).then((outcomes) => {
|
|
105
|
+
let code = exitCode;
|
|
106
|
+
for (const outcome of outcomes) {
|
|
107
|
+
if (outcome.status === "rejected") {
|
|
108
|
+
log.error(`[fastagent] shutdown failed: ${String(outcome.reason)}`);
|
|
109
|
+
code = 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
247
112
|
clearTimeout(deadline);
|
|
248
|
-
process.exit(
|
|
113
|
+
process.exit(code);
|
|
249
114
|
});
|
|
250
|
-
// Preserve the existing no-drain shutdown contract: stop accepting first, then cut active streams.
|
|
251
|
-
hosted.closeAllConnections();
|
|
252
115
|
};
|
|
253
116
|
process.once("SIGINT", () => stop(0));
|
|
254
117
|
process.once("SIGTERM", () => stop(0));
|
|
255
118
|
hosted.listening.then(async (boundPort) => {
|
|
256
119
|
try {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
typeof run.closed?.then !== "function") {
|
|
263
|
-
throw new Error(`${connection.name} connect(signal) must return { ready: Promise, closed: Promise }`);
|
|
264
|
-
}
|
|
265
|
-
void run.closed.then(() => {
|
|
266
|
-
if (!abort.signal.aborted)
|
|
267
|
-
failStartup(new Error(`${connection.name} closed unexpectedly`));
|
|
268
|
-
}, (error) => {
|
|
269
|
-
if (!abort.signal.aborted)
|
|
270
|
-
failStartup(new Error(`${connection.name} failed: ${String(error)}`));
|
|
271
|
-
});
|
|
272
|
-
return { connection, run };
|
|
273
|
-
});
|
|
274
|
-
await Promise.all(runs.map(async ({ connection, run }) => {
|
|
275
|
-
await run.ready;
|
|
276
|
-
if (!abort.signal.aborted)
|
|
277
|
-
log.info(`[fastagent] long connection ready: ${connection.name}`);
|
|
278
|
-
}));
|
|
279
|
-
// Shutdown raced startup: a pre-ready abort settles `ready` as cancellation, not readiness —
|
|
280
|
-
// stop() already owns the exit; don't mark ready or report a surface being torn down.
|
|
281
|
-
if (abort.signal.aborted)
|
|
120
|
+
// A bound socket is NOT a serving agent: a declared socket-mode channel still has to come
|
|
121
|
+
// up, and reporting ready before it does tells the supervisor (and --tunnel, and the
|
|
122
|
+
// operator) that a surface is live while a channel is dead.
|
|
123
|
+
await hooks.ready;
|
|
124
|
+
if (stopping)
|
|
282
125
|
return;
|
|
283
|
-
|
|
284
|
-
process.send?.({
|
|
285
|
-
type: "ready",
|
|
286
|
-
port: boundPort,
|
|
287
|
-
routeChannels: surface.routeChannels,
|
|
288
|
-
});
|
|
289
|
-
for (const line of readyAddressLines(host, boundPort, surface.builtinInvoke))
|
|
290
|
-
log.info(line);
|
|
291
|
-
log.info(`[fastagent] routes: ${Object.keys(surface.routes).join(", ") || "(none)"}`);
|
|
292
|
-
if (surface.longConnections.length > 0) {
|
|
293
|
-
log.info(`[fastagent] long connections: ${surface.longConnections.map((connection) => connection.name).join(", ")}`);
|
|
294
|
-
}
|
|
295
|
-
onListening?.(boundPort);
|
|
126
|
+
hooks.onListening?.(boundPort);
|
|
296
127
|
}
|
|
297
128
|
catch (error) {
|
|
298
|
-
abort.abort();
|
|
299
|
-
const closing = hosted.close().catch(() => { });
|
|
300
|
-
hosted.closeAllConnections();
|
|
301
|
-
await closing;
|
|
302
129
|
failStartup(error);
|
|
303
130
|
}
|
|
304
131
|
}, (error) => {
|
|
@@ -320,30 +147,12 @@ export function maybeTunnel(agentDir, routeChannels, boundPort, tunnel, stateRoo
|
|
|
320
147
|
void startCloudflareTunnel(boundPort).then((instance) => {
|
|
321
148
|
if (!instance)
|
|
322
149
|
return;
|
|
323
|
-
void announceWebhooks(agentDir, instance.url,
|
|
150
|
+
void announceWebhooks(agentDir, instance.url, declaredChannels(routeChannels), {
|
|
151
|
+
openUrl: openExternalUrl,
|
|
152
|
+
stateRoot,
|
|
153
|
+
});
|
|
324
154
|
const cleanup = () => instance.close();
|
|
325
155
|
process.once("SIGINT", cleanup);
|
|
326
156
|
process.once("SIGTERM", cleanup);
|
|
327
157
|
});
|
|
328
158
|
}
|
|
329
|
-
/**
|
|
330
|
-
* Load and start the agent's `schedules/` — a time-trigger firing the agent on each cron. Starts iff
|
|
331
|
-
* there are static schedules OR `selfSchedule` is on. Best-effort stop on process signals. Returns the
|
|
332
|
-
* loaded schedules so a serving surface that needs them (the AgentCore adapter's fire binding) shares
|
|
333
|
-
* ONE load instead of re-discovering. `externalClock` (AgentCore) arms no resident cron timers.
|
|
334
|
-
*/
|
|
335
|
-
export async function startSchedules(agentDir, agent, stateRoot, selfSchedule, options = {}) {
|
|
336
|
-
const { schedules, failures } = await loadSchedules(agentDir).catch(failStartup);
|
|
337
|
-
reportModuleLoadFailures(failures);
|
|
338
|
-
if (schedules.length === 0 && !selfSchedule)
|
|
339
|
-
return schedules;
|
|
340
|
-
const scheduler = createScheduler({ agent, stateRoot, schedules, externalClock: options.externalClock });
|
|
341
|
-
scheduler.start();
|
|
342
|
-
if (schedules.length > 0) {
|
|
343
|
-
log.info(`[fastagent] schedules: ${schedules.map((s) => s.name).join(", ")}${options.externalClock ? " (external clock — no resident cron timers)" : ""}`);
|
|
344
|
-
}
|
|
345
|
-
const stop = () => scheduler.stop();
|
|
346
|
-
process.once("SIGINT", stop);
|
|
347
|
-
process.once("SIGTERM", stop);
|
|
348
|
-
return schedules;
|
|
349
|
-
}
|
package/dist/cli/shared.d.ts
CHANGED
|
@@ -1,16 +1,46 @@
|
|
|
1
1
|
import { type LoginIO, type LoginMethod, type LoginResult } from "../engines/pi/login.ts";
|
|
2
|
+
import type { LoadedDefinition } from "../engines/pi/definition.ts";
|
|
3
|
+
import type { ModuleLoadFailure } from "../loader.ts";
|
|
4
|
+
import type { ToolCollision } from "../engines/pi/tool.ts";
|
|
5
|
+
/** What the startup report reads off an opened directory — a structural subset of the opener's return,
|
|
6
|
+
* spelled out so this module does not depend on that function's whole shape. */
|
|
7
|
+
export interface ReportableAssembly {
|
|
8
|
+
agentDir: string;
|
|
9
|
+
workspace: string;
|
|
10
|
+
modelSpec: string;
|
|
11
|
+
authPath: string;
|
|
12
|
+
config: {
|
|
13
|
+
thinkingLevel?: string;
|
|
14
|
+
};
|
|
15
|
+
definition: LoadedDefinition;
|
|
16
|
+
toolNames: string[];
|
|
17
|
+
deferredToolNames: string[];
|
|
18
|
+
toolCollisions: ToolCollision[];
|
|
19
|
+
toolFailures: ModuleLoadFailure[];
|
|
20
|
+
}
|
|
2
21
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
22
|
+
* What `dev` and `start` say about the directory they just opened, in the order they say it.
|
|
23
|
+
*
|
|
24
|
+
* ONE function because it is one report: the two commands wrote it out line by line, and the copies
|
|
25
|
+
* had already diverged over which lines exist at all — `dev` naming the config file, `start` naming
|
|
26
|
+
* state and sessions. A line added to one of two hand-written copies is invisible in the other.
|
|
27
|
+
*
|
|
28
|
+
* The divergence itself is PRESERVED, not resolved: `start`'s `state:`/`sessions:` pair introduces the
|
|
29
|
+
* persistence warnings that follow it in production posture, and `dev`'s `config:` has no counterpart
|
|
30
|
+
* there. Both are passed as explicit extras by the caller, so the asymmetry is visible at the call
|
|
31
|
+
* site rather than buried in two copies of a list. Whether it is RIGHT is a separate question from
|
|
32
|
+
* whether it has one owner.
|
|
33
|
+
*
|
|
34
|
+
* Findings (skill collisions, definition diagnostics) CLOSE the report — they are about the assembly
|
|
35
|
+
* just printed. What a command says next is its own posture talk, not report: `start`'s persistence
|
|
36
|
+
* notes now follow them rather than precede them.
|
|
8
37
|
*/
|
|
9
|
-
export declare function
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
38
|
+
export declare function reportAssembly(a: ReportableAssembly, extras?: {
|
|
39
|
+
/** Printed between `workspace:`/`hint:` and `model:` (`dev` names the config file here). */
|
|
40
|
+
beforeModel?: [label: string, value: string][];
|
|
41
|
+
/** Printed after the tool lines, before findings (`start` names state + sessions here). */
|
|
42
|
+
afterTools?: [label: string, value: string][];
|
|
43
|
+
}): Promise<void>;
|
|
14
44
|
/** Both stdin and stdout are a terminal — the precondition for an interactive prompt. */
|
|
15
45
|
export declare function isInteractive(): boolean;
|
|
16
46
|
/**
|
package/dist/cli/shared.js
CHANGED
|
@@ -12,27 +12,74 @@ import { isValidPort, listModels, loadConfig, providerOf, resolveAuthPath, resol
|
|
|
12
12
|
import { LoginCancelled, loginFlow } from "../engines/pi/login.js";
|
|
13
13
|
import { createPiModelRuntime, createPiModels, probeApiKey, probeAuthSource, providerAuthStatuses, } from "../engines/pi/models.js";
|
|
14
14
|
import { formatAuthReport } from "./auth-view.js";
|
|
15
|
-
import {
|
|
15
|
+
import { CODING_TOOL_NAMES } from "../engines/pi/create.js";
|
|
16
|
+
import { reportFindingsIfChanged, reportToolCollisions } from "../engines/pi/report.js";
|
|
17
|
+
import { workspaceHint } from "../paths.js";
|
|
18
|
+
import { log, reportModuleLoadFailures } from "../log.js";
|
|
16
19
|
import { openExternalUrl } from "../open-url.js";
|
|
17
20
|
import { bindAddress, isBindAddress } from "../bind.js";
|
|
18
21
|
import { failStartup, failUsage } from "./fail.js";
|
|
19
22
|
/**
|
|
20
23
|
* The padded label writer for the STARTUP report (`dev`/`start`, stderr via the log level). Hand-spaced
|
|
21
24
|
* labels drift out of alignment the moment a longer one appears — which is exactly what happened when
|
|
22
|
-
* `
|
|
25
|
+
* `codingTools:` joined `workspace:`/`config:`/`model:`/`state:`. `info` keeps its own writer on purpose: its report is
|
|
23
26
|
* stdout DATA (pipeable, its own label set, its own width), not a log line — the shared thing is the
|
|
24
27
|
* policy (pad, never hand-space), not a constant.
|
|
28
|
+
*
|
|
29
|
+
* Private to this module: {@link reportAssembly} is the report, and a command reaching past it for a
|
|
30
|
+
* line of its own is how the two copies of that report came to differ.
|
|
25
31
|
*/
|
|
26
|
-
|
|
27
|
-
log.info(`[fastagent] ${`${label}:`.padEnd(
|
|
32
|
+
function reportLine(label, value) {
|
|
33
|
+
log.info(`[fastagent] ${`${label}:`.padEnd(13)}${value}`);
|
|
28
34
|
}
|
|
29
35
|
/** The workspace hint under the `agent:`/`workspace:` pair, when there is one ({@link workspaceHint}):
|
|
30
36
|
* you pointed at the agent, and the project around it is probably what you meant. A hint, so it renders
|
|
31
|
-
* as one and is silent otherwise
|
|
32
|
-
|
|
37
|
+
* as one and is silent otherwise. */
|
|
38
|
+
function reportWorkspaceHint(hint) {
|
|
33
39
|
if (hint)
|
|
34
40
|
reportLine("hint", hint);
|
|
35
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* What `dev` and `start` say about the directory they just opened, in the order they say it.
|
|
44
|
+
*
|
|
45
|
+
* ONE function because it is one report: the two commands wrote it out line by line, and the copies
|
|
46
|
+
* had already diverged over which lines exist at all — `dev` naming the config file, `start` naming
|
|
47
|
+
* state and sessions. A line added to one of two hand-written copies is invisible in the other.
|
|
48
|
+
*
|
|
49
|
+
* The divergence itself is PRESERVED, not resolved: `start`'s `state:`/`sessions:` pair introduces the
|
|
50
|
+
* persistence warnings that follow it in production posture, and `dev`'s `config:` has no counterpart
|
|
51
|
+
* there. Both are passed as explicit extras by the caller, so the asymmetry is visible at the call
|
|
52
|
+
* site rather than buried in two copies of a list. Whether it is RIGHT is a separate question from
|
|
53
|
+
* whether it has one owner.
|
|
54
|
+
*
|
|
55
|
+
* Findings (skill collisions, definition diagnostics) CLOSE the report — they are about the assembly
|
|
56
|
+
* just printed. What a command says next is its own posture talk, not report: `start`'s persistence
|
|
57
|
+
* notes now follow them rather than precede them.
|
|
58
|
+
*/
|
|
59
|
+
export async function reportAssembly(a, extras = {}) {
|
|
60
|
+
reportLine("agent", a.agentDir);
|
|
61
|
+
reportLine("workspace", a.workspace);
|
|
62
|
+
reportWorkspaceHint(workspaceHint(a));
|
|
63
|
+
for (const [label, value] of extras.beforeModel ?? [])
|
|
64
|
+
reportLine(label, value);
|
|
65
|
+
reportLine("model", `${a.modelSpec}${a.config.thinkingLevel ? ` (thinking: ${a.config.thinkingLevel})` : ""}`);
|
|
66
|
+
await reportAuth(a.agentDir, a.modelSpec, a.authPath);
|
|
67
|
+
reportLine("context", a.definition.contextFiles.map((f) => f.path).join(", ") || "(none)");
|
|
68
|
+
if (a.definition.persona)
|
|
69
|
+
reportLine("persona", "persona.md");
|
|
70
|
+
reportLine("skills", a.definition.skills.map((s) => s.name).join(", ") || "(none)");
|
|
71
|
+
reportLine("codingTools", CODING_TOOL_NAMES.join(", "));
|
|
72
|
+
if (a.toolNames.length > 0)
|
|
73
|
+
reportLine("tools", a.toolNames.join(", "));
|
|
74
|
+
if (a.deferredToolNames.length > 0) {
|
|
75
|
+
reportLine("deferred", `${a.deferredToolNames.join(", ")} (activated via search_tools)`);
|
|
76
|
+
}
|
|
77
|
+
reportToolCollisions(a.toolCollisions);
|
|
78
|
+
reportModuleLoadFailures(a.toolFailures);
|
|
79
|
+
for (const [label, value] of extras.afterTools ?? [])
|
|
80
|
+
reportLine(label, value);
|
|
81
|
+
reportFindingsIfChanged(a.definition.dir, a.definition);
|
|
82
|
+
}
|
|
36
83
|
/** Both stdin and stdout are a terminal — the precondition for an interactive prompt. */
|
|
37
84
|
export function isInteractive() {
|
|
38
85
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
package/dist/core.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
export type { Agent, AgentEvent, ImageRef, Json, Prompt, Scope } from "./agent.ts";
|
|
2
2
|
export { collect, AgentFailure, type CollectResult } from "./collect.ts";
|
|
3
3
|
export type { ModuleLoadFailure } from "./loader.ts";
|
|
4
|
-
export { createInvokeHandler
|
|
5
|
-
export
|
|
4
|
+
export { createInvokeHandler } from "./channels/http.ts";
|
|
5
|
+
export type { WireEvent } from "./channels/control.ts";
|
|
6
6
|
export { ControlRequestError, connectAgent, connectSessionControl, type RemoteEndpointOptions, } from "./session-remote.ts";
|
|
7
7
|
export { readBodyCapped } from "./channels/body.ts";
|
|
8
8
|
export { text, textHeaders } from "./channels/respond.ts";
|
|
9
|
-
export {
|
|
9
|
+
export type { ChannelContext, ChannelHandler, ChannelModule, LongConnection, LongConnectionChannelModule, Routes, } from "./channel.ts";
|
|
10
10
|
export { defineSchedule, type LoadedSchedule, type Schedule } from "./schedule/schedule.ts";
|
|
11
|
-
export { discoverScheduleFiles, loadSchedules } from "./schedule/discover.ts";
|
|
12
|
-
export { createScheduler, scheduleSession, type Scheduler, type SchedulerOptions } from "./schedule/scheduler.ts";
|
package/dist/core.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export { collect, AgentFailure } from "./collect.js";
|
|
2
|
-
export { createInvokeHandler
|
|
3
|
-
export { controlRoutes } from "./channels/control.js";
|
|
2
|
+
export { createInvokeHandler } from "./channels/http.js";
|
|
4
3
|
export { ControlRequestError, connectAgent, connectSessionControl, } from "./session-remote.js";
|
|
5
4
|
export { readBodyCapped } from "./channels/body.js";
|
|
6
5
|
export { text, textHeaders } from "./channels/respond.js";
|
|
7
|
-
|
|
6
|
+
// Mounting only. Composing a route table (`router`) and owning a prefix (`PrefixMount`) are how
|
|
7
|
+
// `createAgentService` assembles a service — not something a caller has to reproduce.
|
|
8
|
+
// Binding to a Node server lives at `/node`: it is the only runtime-specific piece here, and the
|
|
9
|
+
// only one that costs a third-party package.
|
|
10
|
+
// `defineSchedule` is what a `schedules/*.ts` file is written against. Discovering those files and
|
|
11
|
+
// running the clock is what `createAgentService` does with them — parts a caller does not reproduce.
|
|
8
12
|
export { defineSchedule } from "./schedule/schedule.js";
|
|
9
|
-
export { discoverScheduleFiles, loadSchedules } from "./schedule/discover.js";
|
|
10
|
-
export { createScheduler, scheduleSession } from "./schedule/scheduler.js";
|
|
@@ -29,7 +29,7 @@ export type AgentcoreLogsOutcome = {
|
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
31
31
|
* Find and tail one AgentCore log source. Discovery is dynamic rather than spelling `-DEFAULT`:
|
|
32
|
-
* endpoint naming belongs to AWS, and an edited stack may use a different endpoint.
|
|
33
|
-
*
|
|
32
|
+
* endpoint naming belongs to AWS, and an edited stack may use a different endpoint. The whole group
|
|
33
|
+
* is tailed — see the file header for why a stream filter cannot narrow it and must not be added.
|
|
34
34
|
*/
|
|
35
35
|
export declare function tailAgentcoreLogs(plan: AgentcoreLogsPlan, aws: CliRunner, announce?: (message: string) => void): Promise<AgentcoreLogsOutcome>;
|
|
@@ -17,8 +17,8 @@ function parseLogGroupNames(stdout) {
|
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Find and tail one AgentCore log source. Discovery is dynamic rather than spelling `-DEFAULT`:
|
|
20
|
-
* endpoint naming belongs to AWS, and an edited stack may use a different endpoint.
|
|
21
|
-
*
|
|
20
|
+
* endpoint naming belongs to AWS, and an edited stack may use a different endpoint. The whole group
|
|
21
|
+
* is tailed — see the file header for why a stream filter cannot narrow it and must not be added.
|
|
22
22
|
*/
|
|
23
23
|
export async function tailAgentcoreLogs(plan, aws, announce = () => { }) {
|
|
24
24
|
const stack = `fastagent-${plan.name}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DeclaredChannel } from "../../channels/discover.ts";
|
|
2
2
|
import { type Artifact, type ContainerInput } from "../container.ts";
|
|
3
3
|
/** The one schedule fact the plan needs (from loadSchedules) — name + cron + tz. */
|
|
4
4
|
export interface ScheduleFact {
|
|
@@ -11,10 +11,9 @@ export interface AgentcorePlanInput extends ContainerInput {
|
|
|
11
11
|
name: string;
|
|
12
12
|
/** What satisfies model auth locally: an env-var name, an OAuth/stored label, or undefined. */
|
|
13
13
|
modelAuth: string | undefined;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
routeChannels: string[];
|
|
14
|
+
/** Every declared channel and its ingress — the source of the secret list, the webhook steps, and
|
|
15
|
+
* whether the forwarder is needed at all (ANY webhook channel requires it, customs included). */
|
|
16
|
+
channels: readonly DeclaredChannel[];
|
|
18
17
|
/** Extra secret env-var names (fastagent.config deploy.secrets). */
|
|
19
18
|
extraSecrets?: string[];
|
|
20
19
|
/** Static schedules — each becomes an EventBridge Scheduler rule targeting the forwarder. */
|
|
@@ -118,15 +117,18 @@ export declare function toEventBridgeCron(cron: string): {
|
|
|
118
117
|
error: string;
|
|
119
118
|
};
|
|
120
119
|
/**
|
|
121
|
-
* The forwarder Lambda source — the ONLY string both the
|
|
120
|
+
* The forwarder Lambda source — the ONLY string both the deployment package and the readable
|
|
122
121
|
* `lambda/forwarder.js` artifact are generated from (one source, no drift). Zero-dependency: the
|
|
123
|
-
* Lambda Node runtime bundles AWS SDK v3. CommonJS ON PURPOSE:
|
|
124
|
-
*
|
|
122
|
+
* Lambda Node runtime bundles AWS SDK v3. CommonJS ON PURPOSE: the package's entry lands as
|
|
123
|
+
* `index.js` ({@link zipSingleFile}), where ESM `import` is a syntax error. Two
|
|
125
124
|
* event shapes: a Function URL webhook (reconstructed verbatim into a `webhook` envelope; the
|
|
126
125
|
* channel's REAL response rides back inside the transport reply and is re-emitted byte-exact —
|
|
127
126
|
* Feishu's URL-verification challenge depends on it), and an EventBridge Scheduler fire
|
|
128
|
-
* (`{ scheduleFire }`, slot = the scheduled instant — the container's idempotency key).
|
|
129
|
-
*
|
|
127
|
+
* (`{ scheduleFire }`, slot = the scheduled instant — the container's idempotency key).
|
|
128
|
+
*
|
|
129
|
+
* It ships as an S3 object rather than inline `ZipFile`, so there is no 4096-byte ceiling on it — a
|
|
130
|
+
* constraint this comment asserted for long after the template stopped inlining, which is the kind
|
|
131
|
+
* of stale rule that stops the next person making a correct change.
|
|
130
132
|
*/
|
|
131
133
|
export declare function forwarderSource(): string;
|
|
132
134
|
/**
|