@fastagent-sh/fastagent 0.17.1 → 0.19.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 +12 -1
- package/dist/agent.js +1 -1
- package/dist/atomic-write.d.ts +19 -0
- package/dist/atomic-write.js +48 -0
- package/dist/{host/node.d.ts → channel.d.ts} +24 -28
- package/dist/channel.js +1 -0
- package/dist/channels/agentcore-service.d.ts +54 -0
- package/dist/channels/agentcore-service.js +111 -0
- package/dist/channels/agentcore.d.ts +12 -2
- package/dist/channels/agentcore.js +6 -4
- package/dist/channels/busy.d.ts +1 -1
- package/dist/channels/busy.js +1 -1
- package/dist/channels/control.d.ts +16 -6
- package/dist/channels/control.js +106 -4
- package/dist/{engines/pi/channel.d.ts → channels/discover.d.ts} +2 -2
- package/dist/{engines/pi/channel.js → channels/discover.js} +8 -12
- 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.d.ts +4 -2
- package/dist/channels/feishu/feishu-api.js +1 -1
- package/dist/channels/feishu/feishu.d.ts +1 -1
- package/dist/channels/feishu/feishu.js +45 -15
- package/dist/channels/feishu/invoke-turn.d.ts +9 -3
- package/dist/channels/feishu/invoke-turn.js +152 -34
- package/dist/channels/feishu/parse.js +6 -0
- 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 +16 -111
- package/dist/channels/{context-buffer.js → kit/context-buffer.js} +2 -2
- package/dist/channels/{invoke-turn-kit.d.ts → kit/invoke-turn-kit.d.ts} +18 -4
- package/dist/channels/{invoke-turn-kit.js → kit/invoke-turn-kit.js} +23 -8
- 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/state.d.ts +6 -0
- package/dist/channels/{state.js → kit/state.js} +4 -24
- package/dist/channels/{stop-command.d.ts → kit/stop-command.d.ts} +1 -1
- package/dist/channels/{stop-command.js → kit/stop-command.js} +2 -2
- package/dist/channels/{tasks.js → kit/tasks.js} +1 -1
- package/dist/channels/{thread-participants.d.ts → kit/thread-participants.d.ts} +7 -0
- package/dist/channels/{thread-participants.js → kit/thread-participants.js} +4 -1
- package/dist/channels/{turn-queue.js → kit/turn-queue.js} +2 -2
- package/dist/channels/{turn-store.js → kit/turn-store.js} +1 -1
- 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 +1 -1
- package/dist/channels/slack/invoke-turn.js +9 -6
- 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/slack-api.js +1 -1
- package/dist/channels/slack/slack.d.ts +1 -1
- package/dist/channels/slack/slack.js +8 -12
- 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 +2 -2
- package/dist/channels/telegram/parse.js +2 -2
- package/dist/channels/telegram/preview.d.ts +1 -1
- package/dist/channels/telegram/preview.js +6 -2
- package/dist/channels/telegram/telegram.d.ts +1 -1
- package/dist/channels/telegram/telegram.js +3 -3
- package/dist/channels/telegram/turn-store.d.ts +2 -2
- package/dist/channels/telegram/turn-store.js +2 -2
- package/dist/cli/commands/add.js +1 -1
- package/dist/cli/commands/deploy.js +13 -5
- package/dist/cli/commands/dev.js +26 -14
- package/dist/cli/commands/fire.js +2 -2
- package/dist/cli/commands/info.js +28 -5
- package/dist/cli/commands/invoke.js +1 -1
- package/dist/cli/commands/schedule.js +1 -1
- package/dist/cli/commands/start.js +64 -95
- package/dist/cli/commands/tool.js +5 -7
- package/dist/cli/program.js +1 -1
- package/dist/cli/serve.d.ts +18 -57
- package/dist/cli/serve.js +57 -243
- package/dist/cli/shared.d.ts +5 -3
- package/dist/cli/shared.js +14 -7
- package/dist/collect.d.ts +30 -4
- package/dist/collect.js +39 -6
- package/dist/core.d.ts +3 -5
- package/dist/core.js +7 -5
- package/dist/deploy/preflight.d.ts +8 -2
- package/dist/deploy/preflight.js +22 -4
- package/dist/deploy/secrets.d.ts +3 -0
- package/dist/deploy/secrets.js +6 -0
- package/dist/dev-supervisor.js +16 -4
- package/dist/engines/pi/agent-session-factory.d.ts +104 -0
- package/dist/engines/pi/agent-session-factory.js +314 -0
- package/dist/engines/pi/config.d.ts +4 -4
- package/dist/engines/pi/config.js +1 -1
- package/dist/engines/pi/create.d.ts +56 -42
- package/dist/engines/pi/create.js +150 -75
- package/dist/engines/pi/definition.d.ts +41 -7
- package/dist/engines/pi/definition.js +115 -11
- package/dist/engines/pi/invoke-session.d.ts +35 -0
- package/dist/engines/pi/invoke-session.js +357 -0
- package/dist/engines/pi/login.js +7 -4
- package/dist/engines/pi/models.d.ts +59 -11
- package/dist/engines/pi/models.js +64 -9
- package/dist/engines/pi/open.d.ts +9 -6
- package/dist/engines/pi/open.js +20 -10
- package/dist/engines/pi/report.d.ts +0 -4
- package/dist/engines/pi/report.js +0 -7
- package/dist/engines/pi/service.d.ts +23 -0
- package/dist/engines/pi/service.js +19 -0
- package/dist/engines/pi/session-builder.js +88 -90
- package/dist/engines/pi/session-control.d.ts +11 -9
- package/dist/engines/pi/session-control.js +114 -77
- package/dist/engines/pi/session-inheritance.d.ts +44 -0
- package/dist/engines/pi/session-inheritance.js +242 -0
- package/dist/engines/pi/session-settings.d.ts +14 -2
- package/dist/engines/pi/session-settings.js +18 -0
- package/dist/engines/pi/session-store.d.ts +70 -0
- package/dist/engines/pi/session-store.js +313 -0
- package/dist/engines/pi/tool-context.d.ts +18 -8
- package/dist/engines/pi/tool-context.js +23 -1
- package/dist/engines/pi/tool.d.ts +5 -5
- package/dist/engines/pi/tool.js +1 -1
- package/dist/engines/pi/{invoke.d.ts → turn-kit.d.ts} +46 -45
- package/dist/engines/pi/turn-kit.js +188 -0
- package/dist/engines/pi/wake-tool.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- 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 +13 -0
- package/dist/paths.js +16 -0
- package/dist/pi.d.ts +15 -8
- package/dist/pi.js +7 -6
- package/dist/scaffold/templates/fastagent.config.mjs +2 -0
- package/dist/scaffold/templates/persona.md +2 -2
- package/dist/schedule/state.js +4 -9
- package/dist/schedule/wakeups.d.ts +1 -1
- package/dist/schedule/wakeups.js +1 -1
- package/dist/service.d.ts +158 -0
- package/dist/service.js +354 -0
- package/dist/session-remote.d.ts +1 -1
- package/dist/session-remote.js +10 -2
- package/dist/session.d.ts +4 -2
- package/dist/session.js +1 -1
- package/package.json +14 -5
- package/dist/channels/state.d.ts +0 -16
- package/dist/engines/pi/harness.d.ts +0 -112
- package/dist/engines/pi/harness.js +0 -153
- package/dist/engines/pi/invoke.js +0 -618
- 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 -44
- package/dist/engines/pi/sessions.js +0 -182
- package/dist/host/node.js +0 -52
- /package/dist/channels/{context-buffer.d.ts → kit/context-buffer.d.ts} +0 -0
- /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/{tasks.d.ts → kit/tasks.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/{turn-queue.d.ts → kit/turn-queue.d.ts} +0 -0
- /package/dist/channels/{turn-store.d.ts → kit/turn-store.d.ts} +0 -0
package/dist/cli/serve.js
CHANGED
|
@@ -1,61 +1,17 @@
|
|
|
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 { loadSchedules } from "../schedule/discover.js";
|
|
20
|
-
import { createScheduler, fireScheduleOnce } from "../schedule/scheduler.js";
|
|
21
13
|
import { announceWebhooks, startCloudflareTunnel } from "../tunnel.js";
|
|
22
14
|
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
15
|
/**
|
|
60
16
|
* Mount the session control plane (`/control/*`) when the agent enabled it
|
|
61
17
|
* (`config.sessionControl`): merge the bearer-authenticated routes and return an announcer that
|
|
@@ -66,123 +22,6 @@ export async function routesFor(agentDir, agent, stateRoot, control, options = {
|
|
|
66
22
|
* (routesFor): `sessionControl` is an explicit opt-in, so declaring both is a configuration error,
|
|
67
23
|
* and silently shadowing either side would serve a surface the author didn't write.
|
|
68
24
|
*/
|
|
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
25
|
/**
|
|
187
26
|
* Refuse `--tunnel` with a bind that cloudflared cannot reach: it dials the NAME `localhost:<port>`
|
|
188
27
|
* (the dev supervisor's tunnel too), so anything outside `127.0.0.1`/`::1`/wildcard — including a
|
|
@@ -214,6 +53,20 @@ export function assertTunnelBindable(host, tunnel, source) {
|
|
|
214
53
|
* A wildcard bind is every interface, and naming one address there would understate it — but the curl
|
|
215
54
|
* still needs one to dial, which is what `clientHost` gives (loopback for a wildcard, itself otherwise).
|
|
216
55
|
*/
|
|
56
|
+
/**
|
|
57
|
+
* The "we are serving" report: the supervisor message `dev`'s watcher waits for, the addresses, and
|
|
58
|
+
* what mounted. One function because both commands must say the same thing at the same moment —
|
|
59
|
+
* after readiness, never at socket bind.
|
|
60
|
+
*/
|
|
61
|
+
export function reportServing(service, host, boundPort) {
|
|
62
|
+
process.send?.({ type: "ready", port: boundPort, routeChannels: service.channels.routes });
|
|
63
|
+
for (const line of readyAddressLines(host, boundPort, service.channels.builtinInvoke))
|
|
64
|
+
log.info(line);
|
|
65
|
+
log.info(`[fastagent] routes: ${Object.keys(service.routes).join(", ") || "(none)"}`);
|
|
66
|
+
if (service.channels.longConnections.length > 0) {
|
|
67
|
+
log.info(`[fastagent] long connections: ${service.channels.longConnections.join(", ")}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
217
70
|
export function readyAddressLines(host, boundPort, builtinInvoke) {
|
|
218
71
|
const dial = `${clientHost(host)}:${boundPort}`;
|
|
219
72
|
const lines = [
|
|
@@ -229,76 +82,58 @@ export function readyAddressLines(host, boundPort, builtinInvoke) {
|
|
|
229
82
|
* adapter owns reconnects; a terminal close rejects `closed` and fails the process visibly. Abort is
|
|
230
83
|
* the sole clean-shutdown command. `host` unset binds all interfaces.
|
|
231
84
|
*/
|
|
232
|
-
|
|
85
|
+
/** What the CLI gives a service to stop in, and the hard exit that follows it. The order matters:
|
|
86
|
+
* a forced exit before the service answers would report a clean shutdown over a stuck channel. */
|
|
87
|
+
export const SHUTDOWN_GRACE_MS = 800;
|
|
88
|
+
const FORCED_EXIT_MS = 1_500;
|
|
89
|
+
export function serve(handler, bind, hooks = {}) {
|
|
233
90
|
const { port, host } = bind;
|
|
234
|
-
const hosted = serveNode(
|
|
235
|
-
const abort = new AbortController();
|
|
91
|
+
const hosted = serveNode(handler, { port, host });
|
|
236
92
|
let stopping = false;
|
|
237
93
|
const stop = (exitCode) => {
|
|
238
94
|
if (stopping)
|
|
239
95
|
return;
|
|
240
96
|
stopping = true;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
.
|
|
97
|
+
// Bounded: shutdown must not hang on a channel that will not close, so the deadline fires
|
|
98
|
+
// regardless. No drain — an in-flight turn is cut, which is the existing contract.
|
|
99
|
+
// Later than the service's own close deadline (SHUTDOWN_GRACE_MS below), or the process leaves
|
|
100
|
+
// at 0 before `close()` has said a channel would not stop.
|
|
101
|
+
const deadline = setTimeout(() => {
|
|
102
|
+
log.error(`[fastagent] shutdown did not finish within ${FORCED_EXIT_MS}ms; exiting`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}, FORCED_EXIT_MS);
|
|
105
|
+
// Stop accepting FIRST, before anything is awaited. `onShutdown` waits for long connections to
|
|
106
|
+
// close, and a socket still listening through that wait would dispatch new work into channels
|
|
107
|
+
// and a scheduler that are already shutting down.
|
|
108
|
+
const closingServer = hosted.close();
|
|
109
|
+
hosted.closeAllConnections();
|
|
110
|
+
// A cleanup that failed is not a clean exit: `close()` reports a channel that would not stop,
|
|
111
|
+
// and swallowing it here would end the process at 0 over a resource still holding on.
|
|
112
|
+
void Promise.allSettled([closingServer, Promise.resolve(hooks.onShutdown?.())]).then((outcomes) => {
|
|
113
|
+
let code = exitCode;
|
|
114
|
+
for (const outcome of outcomes) {
|
|
115
|
+
if (outcome.status === "rejected") {
|
|
116
|
+
log.error(`[fastagent] shutdown failed: ${String(outcome.reason)}`);
|
|
117
|
+
code = 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
247
120
|
clearTimeout(deadline);
|
|
248
|
-
process.exit(
|
|
121
|
+
process.exit(code);
|
|
249
122
|
});
|
|
250
|
-
// Preserve the existing no-drain shutdown contract: stop accepting first, then cut active streams.
|
|
251
|
-
hosted.closeAllConnections();
|
|
252
123
|
};
|
|
253
124
|
process.once("SIGINT", () => stop(0));
|
|
254
125
|
process.once("SIGTERM", () => stop(0));
|
|
255
126
|
hosted.listening.then(async (boundPort) => {
|
|
256
127
|
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)
|
|
128
|
+
// A bound socket is NOT a serving agent: a declared socket-mode channel still has to come
|
|
129
|
+
// up, and reporting ready before it does tells the supervisor (and --tunnel, and the
|
|
130
|
+
// operator) that a surface is live while a channel is dead.
|
|
131
|
+
await hooks.ready;
|
|
132
|
+
if (stopping)
|
|
282
133
|
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);
|
|
134
|
+
hooks.onListening?.(boundPort);
|
|
296
135
|
}
|
|
297
136
|
catch (error) {
|
|
298
|
-
abort.abort();
|
|
299
|
-
const closing = hosted.close().catch(() => { });
|
|
300
|
-
hosted.closeAllConnections();
|
|
301
|
-
await closing;
|
|
302
137
|
failStartup(error);
|
|
303
138
|
}
|
|
304
139
|
}, (error) => {
|
|
@@ -326,24 +161,3 @@ export function maybeTunnel(agentDir, routeChannels, boundPort, tunnel, stateRoo
|
|
|
326
161
|
process.once("SIGTERM", cleanup);
|
|
327
162
|
});
|
|
328
163
|
}
|
|
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
|
@@ -2,7 +2,7 @@ import { type LoginIO, type LoginMethod, type LoginResult } from "../engines/pi/
|
|
|
2
2
|
/**
|
|
3
3
|
* The padded label writer for the STARTUP report (`dev`/`start`, stderr via the log level). Hand-spaced
|
|
4
4
|
* labels drift out of alignment the moment a longer one appears — which is exactly what happened when
|
|
5
|
-
* `
|
|
5
|
+
* `codingTools:` joined `workspace:`/`config:`/`model:`/`state:`. `info` keeps its own writer on purpose: its report is
|
|
6
6
|
* stdout DATA (pipeable, its own label set, its own width), not a log line — the shared thing is the
|
|
7
7
|
* policy (pad, never hand-space), not a constant.
|
|
8
8
|
*/
|
|
@@ -26,8 +26,10 @@ export declare function parsePort(value: string | undefined, source: string, fro
|
|
|
26
26
|
* a node bind failure, or worse, as a "the interface you bound" diagnostic downstream.
|
|
27
27
|
*/
|
|
28
28
|
export declare function parseBind(value: string | undefined): string | undefined;
|
|
29
|
-
/** Report which source provides the model's credentials, surfacing a remediation hint at startup. Non-blocking.
|
|
30
|
-
|
|
29
|
+
/** Report which source provides the model's credentials, surfacing a remediation hint at startup. Non-blocking.
|
|
30
|
+
* Probes through the AGENT's model surface (`agentDir` carries its models.json), so a custom endpoint is
|
|
31
|
+
* reported like any built-in rather than as an unknown provider. */
|
|
32
|
+
export declare function reportAuth(agentDir: string, modelSpec: string, authPath: string): Promise<void>;
|
|
31
33
|
/**
|
|
32
34
|
* First-run model resolution for every assembly command (dev/start/invoke/fire/chat/deploy): ONE
|
|
33
35
|
* funnel, no dead ends. When no model is set (flag/env/config) and we're on a TTY, show the FULL
|
package/dist/cli/shared.js
CHANGED
|
@@ -10,7 +10,7 @@ import { buildModelPickerOptions } from "./models-view.js";
|
|
|
10
10
|
import { fastagentCredentialStore } from "../engines/pi/auth.js";
|
|
11
11
|
import { isValidPort, listModels, loadConfig, providerOf, resolveAuthPath, resolveModel, resolveModelSpec, rewriteConfigModel, } from "../engines/pi/config.js";
|
|
12
12
|
import { LoginCancelled, loginFlow } from "../engines/pi/login.js";
|
|
13
|
-
import { createPiModels, probeApiKey, probeAuthSource, providerAuthStatuses } from "../engines/pi/models.js";
|
|
13
|
+
import { createPiModelRuntime, createPiModels, probeApiKey, probeAuthSource, providerAuthStatuses, } from "../engines/pi/models.js";
|
|
14
14
|
import { formatAuthReport } from "./auth-view.js";
|
|
15
15
|
import { log } from "../log.js";
|
|
16
16
|
import { openExternalUrl } from "../open-url.js";
|
|
@@ -19,12 +19,12 @@ import { failStartup, failUsage } from "./fail.js";
|
|
|
19
19
|
/**
|
|
20
20
|
* The padded label writer for the STARTUP report (`dev`/`start`, stderr via the log level). Hand-spaced
|
|
21
21
|
* labels drift out of alignment the moment a longer one appears — which is exactly what happened when
|
|
22
|
-
* `
|
|
22
|
+
* `codingTools:` joined `workspace:`/`config:`/`model:`/`state:`. `info` keeps its own writer on purpose: its report is
|
|
23
23
|
* stdout DATA (pipeable, its own label set, its own width), not a log line — the shared thing is the
|
|
24
24
|
* policy (pad, never hand-space), not a constant.
|
|
25
25
|
*/
|
|
26
26
|
export function reportLine(label, value) {
|
|
27
|
-
log.info(`[fastagent] ${`${label}:`.padEnd(
|
|
27
|
+
log.info(`[fastagent] ${`${label}:`.padEnd(13)}${value}`);
|
|
28
28
|
}
|
|
29
29
|
/** The workspace hint under the `agent:`/`workspace:` pair, when there is one ({@link workspaceHint}):
|
|
30
30
|
* you pointed at the agent, and the project around it is probably what you meant. A hint, so it renders
|
|
@@ -70,10 +70,13 @@ export function parseBind(value) {
|
|
|
70
70
|
failUsage(`invalid --bind "${value}": must be an IP address or "localhost"`);
|
|
71
71
|
return bindAddress(trimmed); // a name never travels past this point — see bind.ts
|
|
72
72
|
}
|
|
73
|
-
/** Report which source provides the model's credentials, surfacing a remediation hint at startup. Non-blocking.
|
|
74
|
-
|
|
73
|
+
/** Report which source provides the model's credentials, surfacing a remediation hint at startup. Non-blocking.
|
|
74
|
+
* Probes through the AGENT's model surface (`agentDir` carries its models.json), so a custom endpoint is
|
|
75
|
+
* reported like any built-in rather than as an unknown provider. */
|
|
76
|
+
export async function reportAuth(agentDir, modelSpec, authPath) {
|
|
75
77
|
const provider = providerOf(modelSpec);
|
|
76
|
-
const
|
|
78
|
+
const models = await createPiModelRuntime({ agentDir, authPath }).catch(failStartup);
|
|
79
|
+
const source = await probeAuthSource(models, modelSpec);
|
|
77
80
|
// Only when nothing satisfies auth do we read the store (refresh-FREE) to tell "nothing stored" from
|
|
78
81
|
// "stored but unusable" — see formatAuthReport for why. store.read warns on a corrupt file itself.
|
|
79
82
|
const stored = source === undefined
|
|
@@ -107,7 +110,9 @@ export async function resolveFirstRunModel(agentDir, options = {}) {
|
|
|
107
110
|
if (!isInteractive())
|
|
108
111
|
return; // CI/deploy: the opener throws the actionable missing-model error
|
|
109
112
|
const authPath = resolveAuthPath(agentDir, options.authPath);
|
|
110
|
-
|
|
113
|
+
// The picker lists the AGENT's surface: built-ins plus whatever its models.json declares, so a
|
|
114
|
+
// self-hosted endpoint is pickable on first run instead of being invisible until hand-set.
|
|
115
|
+
const models = await createPiModelRuntime({ agentDir, authPath }).catch(failStartup);
|
|
111
116
|
const chosen = await pickWithCredentials(models, authPath);
|
|
112
117
|
if (chosen === undefined)
|
|
113
118
|
return; // cancelled (or auth probe failed): the caller raises its clear missing-model error
|
|
@@ -208,6 +213,8 @@ seams = {}) {
|
|
|
208
213
|
* more than keeping a doubtful one.
|
|
209
214
|
*/
|
|
210
215
|
async function verifyApiKeyLogin(provider, authPath, spec) {
|
|
216
|
+
// Built-ins only: `login` itself offers built-in providers (login.ts), and a models.json endpoint
|
|
217
|
+
// authenticates from its own `apiKey` (env/command), so there is no stored credential to verify here.
|
|
211
218
|
const models = createPiModels({ authPath });
|
|
212
219
|
const model = spec ? resolveModel(models, spec) : models.getProvider(provider)?.getModels()[0];
|
|
213
220
|
if (!model) {
|
package/dist/collect.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Stream helpers around the SPEC's two stream disciplines.
|
|
3
|
+
*
|
|
4
|
+
* Caller side: `collect` (buffered consumption, SPEC §7) reduces an AgentEvent stream to a final
|
|
5
|
+
* value, encoding the terminal discipline (failed → throw, missing terminal → error) — streaming
|
|
6
|
+
* consumers for-await themselves.
|
|
7
|
+
*
|
|
8
|
+
* Agent side: the cancellation protocol (SPEC MUST 3), in two halves that only work together —
|
|
9
|
+
* `abortFirstIterator` delivers the consumer's knock, `cancellableStream` is what an engine wraps
|
|
10
|
+
* its turn in to receive it.
|
|
6
11
|
*/
|
|
7
12
|
import type { AgentEvent, Json } from "./agent.ts";
|
|
8
13
|
/**
|
|
@@ -13,8 +18,29 @@ import type { AgentEvent, Json } from "./agent.ts";
|
|
|
13
18
|
* settles the suspension), then delegates to `gen.return`, swallowing its rejection (the
|
|
14
19
|
* generator's own catch/finally already surfaced the outcome). `throw()` tears down identically
|
|
15
20
|
* and rethrows the caller's error deterministically instead of poking a completed generator.
|
|
21
|
+
*
|
|
22
|
+
* Cancellation also SILENCES the stream, and that belongs here rather than in each producer: a
|
|
23
|
+
* generator parked in an await can still reach a `yield` on its way out (an error path that
|
|
24
|
+
* yields a terminal, say), and that yield satisfies the pending `next()` — handing a terminal
|
|
25
|
+
* event to a consumer that already walked away, which SPEC MUST 3 forbids. Deciding it once, at
|
|
26
|
+
* the protocol boundary, is what keeps every producer from having to re-ask "is anyone still
|
|
27
|
+
* listening?" before each yield.
|
|
16
28
|
*/
|
|
17
29
|
export declare function abortFirstIterator<T>(gen: AsyncGenerator<T>, cancel: () => void): AsyncIterator<T>;
|
|
30
|
+
/** What a turn generator gets so a consumer walking away can stop it. */
|
|
31
|
+
export interface CancelHooks {
|
|
32
|
+
/** Publish the door: how to abort the engine work, once there is engine work to abort. */
|
|
33
|
+
onCancelReady: (cancel: () => void) => void;
|
|
34
|
+
/** The latch, for the window where the door is armed but the engine is still idle — knocking then
|
|
35
|
+
* does nothing, so a turn must read this before committing to work no one is waiting for. */
|
|
36
|
+
wasCancelled: () => boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Wrap a turn generator in the cancellation protocol: cancelling the returned stream latches the
|
|
40
|
+
* intent AND knocks on whatever door the generator published, in that order (the latch must be set
|
|
41
|
+
* before the knock, or a turn checking it mid-flight could miss the cancel it just received).
|
|
42
|
+
*/
|
|
43
|
+
export declare function cancellableStream<T>(start: (hooks: CancelHooks) => AsyncGenerator<T>): AsyncIterable<T>;
|
|
18
44
|
/** Exception form of a failed event (thrown by collect). Carries the failed event's fields verbatim, so
|
|
19
45
|
* a buffered consumer can branch on `code` (SPEC §8 failure subdivision) just like a streaming one. */
|
|
20
46
|
export declare class AgentFailure extends Error {
|
package/dist/collect.js
CHANGED
|
@@ -6,22 +6,55 @@
|
|
|
6
6
|
* settles the suspension), then delegates to `gen.return`, swallowing its rejection (the
|
|
7
7
|
* generator's own catch/finally already surfaced the outcome). `throw()` tears down identically
|
|
8
8
|
* and rethrows the caller's error deterministically instead of poking a completed generator.
|
|
9
|
+
*
|
|
10
|
+
* Cancellation also SILENCES the stream, and that belongs here rather than in each producer: a
|
|
11
|
+
* generator parked in an await can still reach a `yield` on its way out (an error path that
|
|
12
|
+
* yields a terminal, say), and that yield satisfies the pending `next()` — handing a terminal
|
|
13
|
+
* event to a consumer that already walked away, which SPEC MUST 3 forbids. Deciding it once, at
|
|
14
|
+
* the protocol boundary, is what keeps every producer from having to re-ask "is anyone still
|
|
15
|
+
* listening?" before each yield.
|
|
9
16
|
*/
|
|
10
17
|
export function abortFirstIterator(gen, cancel) {
|
|
18
|
+
let cancelled = false;
|
|
19
|
+
const teardown = async () => {
|
|
20
|
+
cancelled = true;
|
|
21
|
+
cancel();
|
|
22
|
+
await gen.return(undefined).catch(() => { });
|
|
23
|
+
};
|
|
11
24
|
return {
|
|
12
|
-
next
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
25
|
+
async next() {
|
|
26
|
+
const result = await gen.next();
|
|
27
|
+
return cancelled ? { done: true, value: undefined } : result;
|
|
28
|
+
},
|
|
29
|
+
async return() {
|
|
30
|
+
await teardown();
|
|
16
31
|
return { done: true, value: undefined };
|
|
17
32
|
},
|
|
18
33
|
async throw(error) {
|
|
19
|
-
|
|
20
|
-
await gen.return(undefined).catch(() => { });
|
|
34
|
+
await teardown();
|
|
21
35
|
throw error;
|
|
22
36
|
},
|
|
23
37
|
};
|
|
24
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Wrap a turn generator in the cancellation protocol: cancelling the returned stream latches the
|
|
41
|
+
* intent AND knocks on whatever door the generator published, in that order (the latch must be set
|
|
42
|
+
* before the knock, or a turn checking it mid-flight could miss the cancel it just received).
|
|
43
|
+
*/
|
|
44
|
+
export function cancellableStream(start) {
|
|
45
|
+
let door;
|
|
46
|
+
let cancelled = false;
|
|
47
|
+
const iterator = abortFirstIterator(start({
|
|
48
|
+
onCancelReady: (cancel) => {
|
|
49
|
+
door = cancel;
|
|
50
|
+
},
|
|
51
|
+
wasCancelled: () => cancelled,
|
|
52
|
+
}), () => {
|
|
53
|
+
cancelled = true;
|
|
54
|
+
door?.();
|
|
55
|
+
});
|
|
56
|
+
return { [Symbol.asyncIterator]: () => iterator };
|
|
57
|
+
}
|
|
25
58
|
/** Exception form of a failed event (thrown by collect). Carries the failed event's fields verbatim, so
|
|
26
59
|
* a buffered consumer can branch on `code` (SPEC §8 failure subdivision) just like a streaming one. */
|
|
27
60
|
export class AgentFailure extends Error {
|
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";
|
|
@@ -19,9 +19,15 @@ interface DeployFacts {
|
|
|
19
19
|
* has no external wake-up, so the deployment must keep one machine running: the fly plan forces
|
|
20
20
|
* `min_machines_running=1`, the railway runbook forbids App Sleeping. */
|
|
21
21
|
hasTimeTriggers: boolean;
|
|
22
|
-
/** What satisfies model auth locally
|
|
23
|
-
*
|
|
22
|
+
/** What satisfies model auth locally — an env-var name, an OAuth/stored label, or undefined. Drives the
|
|
23
|
+
* runbook's secret guidance and `--run`'s credential carry. For a models.json endpoint keyed from the
|
|
24
|
+
* environment this is the VARIABLE NAME (see {@link modelCredentialCarry}), not the display label, so
|
|
25
|
+
* the value carries like any provider key. */
|
|
24
26
|
modelAuth: string | undefined;
|
|
27
|
+
/** The definition itself carries the model key (a models.json literal `apiKey`, or a `!command` run on
|
|
28
|
+
* the host), so there is nothing for `--run` to carry AND nothing to gate: `fastagent login` cannot
|
|
29
|
+
* serve a custom provider, so gating on it would strand a correctly configured agent. */
|
|
30
|
+
modelKeyInDefinition: boolean;
|
|
25
31
|
/** The project-level auth file `--run` reads to carry the credential (probed with the same path). */
|
|
26
32
|
authPath: string;
|
|
27
33
|
/** Container facts shared by the plan and the generated Dockerfile — ONE source, so they can't drift. */
|