@fastagent-sh/fastagent 0.15.0 → 0.16.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 +13 -3
- package/dist/channels/agentcore-limits.d.ts +7 -0
- package/dist/channels/agentcore-limits.js +9 -0
- package/dist/channels/agentcore-state.d.ts +83 -0
- package/dist/channels/agentcore-state.js +258 -0
- package/dist/channels/agentcore.d.ts +98 -0
- package/dist/channels/agentcore.js +260 -0
- package/dist/channels/busy.d.ts +23 -0
- package/dist/channels/busy.js +53 -0
- package/dist/channels/context-buffer.d.ts +5 -0
- package/dist/channels/context-buffer.js +5 -0
- package/dist/channels/feishu/context-buffer.d.ts +13 -8
- package/dist/channels/feishu/context-buffer.js +60 -6
- package/dist/channels/feishu/feishu-api.js +4 -1
- package/dist/channels/feishu/feishu.d.ts +0 -12
- package/dist/channels/feishu/feishu.js +143 -54
- package/dist/channels/feishu/invoke-turn.js +35 -18
- package/dist/channels/feishu/model.d.ts +0 -1
- package/dist/channels/feishu/normalize.js +0 -1
- package/dist/channels/feishu/parse.d.ts +21 -7
- package/dist/channels/feishu/parse.js +24 -7
- package/dist/channels/feishu/preview.js +3 -2
- package/dist/channels/feishu/scaffold/channel.ts +9 -8
- package/dist/channels/feishu/scaffold/feishu-send.ts +6 -4
- package/dist/channels/feishu/setup-mode.d.ts +30 -1
- package/dist/channels/feishu/setup-mode.js +27 -1
- package/dist/channels/github/github.js +8 -1
- package/dist/channels/http.js +1 -1
- package/dist/channels/lark/scaffold/channel.ts +9 -8
- package/dist/channels/lark/scaffold/lark-send.ts +6 -4
- package/dist/channels/preview-kit.d.ts +7 -1
- package/dist/channels/preview-kit.js +3 -2
- package/dist/channels/slack/parse.d.ts +16 -1
- package/dist/channels/slack/parse.js +46 -3
- package/dist/channels/slack/preview.d.ts +1 -2
- package/dist/channels/slack/preview.js +68 -24
- package/dist/channels/slack/scaffold/channel.ts +5 -5
- package/dist/channels/slack/slack-api.d.ts +3 -23
- package/dist/channels/slack/slack-api.js +6 -22
- package/dist/channels/slack/slack.d.ts +13 -20
- package/dist/channels/slack/slack.js +95 -50
- package/dist/channels/state.d.ts +11 -4
- package/dist/channels/state.js +19 -12
- package/dist/channels/tasks.d.ts +0 -6
- package/dist/channels/tasks.js +16 -1
- package/dist/channels/telegram/parse.d.ts +0 -7
- package/dist/channels/telegram/parse.js +4 -2
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -3
- package/dist/channels/telegram/telegram.js +1 -1
- package/dist/channels/text.d.ts +14 -0
- package/dist/channels/text.js +14 -0
- package/dist/channels/thread-participants.d.ts +21 -0
- package/dist/channels/thread-participants.js +132 -0
- package/dist/channels/turn-queue.js +7 -0
- package/dist/cli/add-feishu.d.ts +7 -4
- package/dist/cli/add-feishu.js +57 -37
- package/dist/cli/add-slack.d.ts +2 -1
- package/dist/cli/add-slack.js +6 -11
- package/dist/cli/commands/add.js +50 -51
- package/dist/cli/commands/attach.js +8 -4
- package/dist/cli/commands/chat.js +8 -8
- package/dist/cli/commands/deploy.d.ts +1 -1
- package/dist/cli/commands/deploy.js +323 -77
- package/dist/cli/commands/dev.js +24 -22
- package/dist/cli/commands/fire.js +15 -16
- package/dist/cli/commands/info.js +36 -29
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.js +65 -53
- package/dist/cli/commands/invoke.js +9 -6
- package/dist/cli/commands/login.js +35 -21
- package/dist/cli/commands/schedule.js +6 -8
- package/dist/cli/commands/start.js +93 -37
- package/dist/cli/commands/tool.js +22 -18
- package/dist/cli/fail.d.ts +17 -0
- package/dist/cli/fail.js +24 -0
- package/dist/cli/program.js +57 -36
- package/dist/cli/serve.d.ts +26 -6
- package/dist/cli/serve.js +62 -15
- package/dist/cli/shared.d.ts +15 -2
- package/dist/cli/shared.js +30 -18
- package/dist/deploy/agentcore/plan.d.ts +117 -0
- package/dist/deploy/agentcore/plan.js +721 -0
- package/dist/deploy/agentcore/run.d.ts +73 -0
- package/dist/deploy/agentcore/run.js +412 -0
- package/dist/deploy/agentcore/zip.d.ts +17 -0
- package/dist/deploy/agentcore/zip.js +68 -0
- package/dist/deploy/container.d.ts +26 -25
- package/dist/deploy/container.js +93 -89
- package/dist/deploy/docker/plan.d.ts +1 -1
- package/dist/deploy/docker/plan.js +12 -17
- package/dist/deploy/fly/plan.d.ts +2 -0
- package/dist/deploy/fly/plan.js +27 -19
- package/dist/deploy/fly/run.d.ts +12 -1
- package/dist/deploy/fly/run.js +36 -2
- package/dist/deploy/preflight.d.ts +11 -5
- package/dist/deploy/preflight.js +217 -65
- package/dist/deploy/railway/plan.d.ts +7 -0
- package/dist/deploy/railway/plan.js +41 -16
- package/dist/deploy/railway/run.d.ts +8 -1
- package/dist/deploy/railway/run.js +7 -2
- package/dist/deploy/runner.d.ts +5 -2
- package/dist/deploy/runner.js +9 -3
- package/dist/dev-supervisor.d.ts +11 -8
- package/dist/dev-supervisor.js +53 -51
- package/dist/engines/pi/auth.d.ts +8 -7
- package/dist/engines/pi/auth.js +12 -10
- package/dist/engines/pi/channel.d.ts +1 -1
- package/dist/engines/pi/channel.js +5 -5
- package/dist/engines/pi/chat.js +2 -2
- package/dist/engines/pi/config.d.ts +6 -46
- package/dist/engines/pi/config.js +21 -108
- package/dist/engines/pi/create.d.ts +8 -8
- package/dist/engines/pi/create.js +13 -12
- package/dist/engines/pi/definition.d.ts +7 -26
- package/dist/engines/pi/definition.js +8 -54
- package/dist/engines/pi/login.d.ts +1 -1
- package/dist/engines/pi/models.d.ts +3 -3
- package/dist/engines/pi/models.js +1 -1
- package/dist/engines/pi/{workspace.d.ts → open.d.ts} +29 -23
- package/dist/engines/pi/{workspace.js → open.js} +27 -29
- package/dist/engines/pi/session-builder.d.ts +2 -2
- package/dist/engines/pi/session-builder.js +11 -11
- package/dist/engines/pi/tool.js +4 -0
- package/dist/env.d.ts +16 -4
- package/dist/env.js +43 -5
- package/dist/host/node.d.ts +2 -2
- package/dist/loader.d.ts +2 -2
- package/dist/loader.js +3 -3
- package/dist/log.d.ts +1 -1
- package/dist/log.js +1 -1
- package/dist/paths.d.ts +138 -0
- package/dist/paths.js +326 -0
- package/dist/pi.d.ts +1 -1
- package/dist/pi.js +2 -2
- package/dist/runtime.d.ts +7 -5
- package/dist/runtime.js +2 -2
- package/dist/scaffold/add-channel.d.ts +7 -3
- package/dist/scaffold/add-channel.js +55 -29
- package/dist/scaffold/init.d.ts +32 -41
- package/dist/scaffold/init.js +161 -185
- package/dist/scaffold/templates/env.example +15 -6
- package/dist/scaffold/templates/fastagent.config.mjs +1 -1
- package/dist/scaffold/templates/gitignore +14 -6
- package/dist/scaffold/templates/persona.md +4 -2
- package/dist/scaffold/templates/secrets.gitignore +5 -0
- package/dist/scaffold/templates.d.ts +1 -7
- package/dist/scaffold/templates.js +3 -25
- package/dist/scaffold/vendor-skill.d.ts +2 -2
- package/dist/scaffold/vendor-skill.js +13 -13
- package/dist/schedule/discover.js +4 -4
- package/dist/schedule/scheduler.d.ts +40 -1
- package/dist/schedule/scheduler.js +89 -56
- package/dist/schedule/state.js +1 -1
- package/dist/schedule/wake-alarm.d.ts +47 -0
- package/dist/schedule/wake-alarm.js +136 -0
- package/dist/schedule/wakeups.d.ts +1 -0
- package/dist/schedule/wakeups.js +18 -0
- package/dist/tunnel.d.ts +3 -3
- package/dist/tunnel.js +7 -7
- package/package.json +4 -1
- package/dist/channels/feishu/owned-threads.d.ts +0 -7
- package/dist/channels/feishu/owned-threads.js +0 -47
- package/dist/channels/slack/owned-threads.d.ts +0 -6
- package/dist/channels/slack/owned-threads.js +0 -43
- package/dist/scaffold/templates/gitignore.agentdir-root +0 -5
- package/dist/scaffold/templates/gitignore.kit +0 -2
- package/dist/workspace.d.ts +0 -9
- package/dist/workspace.js +0 -45
package/dist/cli/serve.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { chmodSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
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";
|
|
7
10
|
import { controlRoutes } from "../channels/control.js";
|
|
8
11
|
import { INVOKE_EXAMPLE_BODY, createInvokeHandler } from "../channels/http.js";
|
|
9
12
|
import { text } from "../channels/respond.js";
|
|
@@ -13,15 +16,15 @@ import { parseRouteKey, router, serveNode } from "../host/node.js";
|
|
|
13
16
|
import { log } from "../log.js";
|
|
14
17
|
import { openExternalUrl } from "../open-url.js";
|
|
15
18
|
import { loadSchedules } from "../schedule/discover.js";
|
|
16
|
-
import { createScheduler } from "../schedule/scheduler.js";
|
|
19
|
+
import { createScheduler, fireScheduleOnce } from "../schedule/scheduler.js";
|
|
17
20
|
import { announceWebhooks, startCloudflareTunnel } from "../tunnel.js";
|
|
18
21
|
import { failStartup } from "./fail.js";
|
|
19
22
|
/**
|
|
20
23
|
* The surface this deployment serves: default `GET /health` plus discovered channels, or the default
|
|
21
24
|
* POST `/invoke` only when neither a route nor a long-connection channel was declared.
|
|
22
25
|
*/
|
|
23
|
-
export async function routesFor(
|
|
24
|
-
const { routes, longConnections, routeChannels, collisions, failures } = await loadChannels(
|
|
26
|
+
export async function routesFor(agentDir, agent, stateRoot, control, options = {}) {
|
|
27
|
+
const { routes, longConnections, routeChannels, collisions, failures } = await loadChannels(agentDir, {
|
|
25
28
|
agent,
|
|
26
29
|
stateRoot,
|
|
27
30
|
control,
|
|
@@ -34,7 +37,7 @@ export async function routesFor(workspaceDir, agent, stateRoot, control) {
|
|
|
34
37
|
throw new Error(`channel setup is invalid (${failures.length} load failure(s), ${collisions.length} route collision(s)) — ` +
|
|
35
38
|
`fix it, or rename an intentionally disabled file to *.disabled`);
|
|
36
39
|
}
|
|
37
|
-
const builtinInvoke = Object.keys(routes).length === 0 && longConnections.length === 0;
|
|
40
|
+
const builtinInvoke = options.builtinInvoke !== false && Object.keys(routes).length === 0 && longConnections.length === 0;
|
|
38
41
|
const channels = builtinInvoke ? { "POST /invoke": createInvokeHandler(agent) } : routes;
|
|
39
42
|
const healthCovered = Object.keys(channels).some((key) => {
|
|
40
43
|
const entry = parseRouteKey(key);
|
|
@@ -53,7 +56,7 @@ export async function routesFor(workspaceDir, agent, stateRoot, control) {
|
|
|
53
56
|
};
|
|
54
57
|
}
|
|
55
58
|
/**
|
|
56
|
-
* Mount the session control plane (`/control/*`) when the
|
|
59
|
+
* Mount the session control plane (`/control/*`) when the agent enabled it
|
|
57
60
|
* (`config.sessionControl`): merge the bearer-authenticated routes and return an announcer that
|
|
58
61
|
* writes `<stateRoot>/control.json` — `{ url, token }`, 0600 — once the port is known. The file is
|
|
59
62
|
* the LOCAL discovery channel (`fastagent attach`, a local desktop app); filesystem permissions are
|
|
@@ -132,6 +135,46 @@ export function mountSessionControl(routes, control, stateRoot, options = {}) {
|
|
|
132
135
|
},
|
|
133
136
|
};
|
|
134
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Mount the AgentCore Runtime adapter (`POST /invocations` + `GET /ping`) over the serving routes —
|
|
140
|
+
* the deployed container's ONLY reachable surface (channels/agentcore.ts). Wired by `start` when
|
|
141
|
+
* `FASTAGENT_AGENTCORE=1` (set by the generated deploy artifacts, never by hand). A channel colliding
|
|
142
|
+
* on either path fails startup, same disposition as the control-plane mount: the adapter's paths are
|
|
143
|
+
* the platform's contract, so a channel shadowing them would silently unserve the whole deployment.
|
|
144
|
+
*/
|
|
145
|
+
export function mountAgentcore(routes, options) {
|
|
146
|
+
const { agent, stateRoot, schedules, onStateReady } = options;
|
|
147
|
+
const mounted = agentcoreRoutes({
|
|
148
|
+
routes,
|
|
149
|
+
agent,
|
|
150
|
+
stateRoot,
|
|
151
|
+
isBusy: () => activeWork() > 0,
|
|
152
|
+
// Cross-deploy durability: AgentCore wipes the state mount on every runtime version update, so
|
|
153
|
+
// the state root is restored from (and pushed to) an S3 snapshot through presigned URLs the
|
|
154
|
+
// forwarder mints per envelope. Always wired on this path — the platform gives no other way to
|
|
155
|
+
// keep an agent's memory across a deploy.
|
|
156
|
+
stateSync: createStateSync({ stateRoot }),
|
|
157
|
+
// What separates a forwarder envelope from any IAM principal's InvokeAgentRuntime call. Absent =
|
|
158
|
+
// no forwarder in this topology, so only the public `invoke` kind is servable.
|
|
159
|
+
ingressSecret: process.env.FASTAGENT_INGRESS_SECRET,
|
|
160
|
+
onStateReady,
|
|
161
|
+
fire: schedules.length === 0
|
|
162
|
+
? undefined
|
|
163
|
+
: (name, slot) => {
|
|
164
|
+
const schedule = schedules.find((s) => s.name === name);
|
|
165
|
+
if (!schedule)
|
|
166
|
+
throw new UnknownScheduleError(name);
|
|
167
|
+
return fireScheduleOnce({ agent, stateRoot, schedule, slot });
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
const mountedPaths = new Set(Object.keys(mounted).map((key) => parseRouteKey(key).path));
|
|
171
|
+
const collisions = Object.keys(routes).filter((key) => mountedPaths.has(parseRouteKey(key).path));
|
|
172
|
+
if (collisions.length > 0) {
|
|
173
|
+
throw new Error(`channel route(s) ${collisions.map((key) => `"${key}"`).join(", ")} collide with the AgentCore adapter ` +
|
|
174
|
+
`(/invocations, /ping) — rename the channel route`);
|
|
175
|
+
}
|
|
176
|
+
return { ...routes, ...mounted };
|
|
177
|
+
}
|
|
135
178
|
/**
|
|
136
179
|
* Bind HTTP, open long-connection channels, and report ready only when both forms are usable. Each
|
|
137
180
|
* adapter owns reconnects; a terminal close rejects `closed` and fails the process visibly. Abort is
|
|
@@ -217,32 +260,36 @@ export function serve(surface, port, onListening) {
|
|
|
217
260
|
});
|
|
218
261
|
}
|
|
219
262
|
/** Start a Cloudflare tunnel for route channels only. */
|
|
220
|
-
export function maybeTunnel(
|
|
263
|
+
export function maybeTunnel(agentDir, routeChannels, boundPort, tunnel, stateRoot) {
|
|
221
264
|
if (!tunnel || process.env.FASTAGENT_DEV_WORKER === "1")
|
|
222
265
|
return;
|
|
223
266
|
void startCloudflareTunnel(boundPort).then((instance) => {
|
|
224
267
|
if (!instance)
|
|
225
268
|
return;
|
|
226
|
-
void announceWebhooks(
|
|
269
|
+
void announceWebhooks(agentDir, instance.url, { openUrl: openExternalUrl, routeChannels, stateRoot });
|
|
227
270
|
const cleanup = () => instance.close();
|
|
228
271
|
process.once("SIGINT", cleanup);
|
|
229
272
|
process.once("SIGTERM", cleanup);
|
|
230
273
|
});
|
|
231
274
|
}
|
|
232
275
|
/**
|
|
233
|
-
* Load and start the
|
|
234
|
-
* there are static schedules OR `selfSchedule` is on. Best-effort stop on process signals.
|
|
276
|
+
* Load and start the agent's `schedules/` — a time-trigger firing the agent on each cron. Starts iff
|
|
277
|
+
* there are static schedules OR `selfSchedule` is on. Best-effort stop on process signals. Returns the
|
|
278
|
+
* loaded schedules so a serving surface that needs them (the AgentCore adapter's fire binding) shares
|
|
279
|
+
* ONE load instead of re-discovering. `externalClock` (AgentCore) arms no resident cron timers.
|
|
235
280
|
*/
|
|
236
|
-
export async function startSchedules(
|
|
237
|
-
const { schedules, failures } = await loadSchedules(
|
|
281
|
+
export async function startSchedules(agentDir, agent, stateRoot, selfSchedule, options = {}) {
|
|
282
|
+
const { schedules, failures } = await loadSchedules(agentDir).catch(failStartup);
|
|
238
283
|
reportModuleLoadFailures(failures);
|
|
239
284
|
if (schedules.length === 0 && !selfSchedule)
|
|
240
|
-
return;
|
|
241
|
-
const scheduler = createScheduler({ agent, stateRoot, schedules });
|
|
285
|
+
return schedules;
|
|
286
|
+
const scheduler = createScheduler({ agent, stateRoot, schedules, externalClock: options.externalClock });
|
|
242
287
|
scheduler.start();
|
|
243
|
-
if (schedules.length > 0)
|
|
244
|
-
log.info(`[fastagent] schedules: ${schedules.map((s) => s.name).join(", ")}`);
|
|
288
|
+
if (schedules.length > 0) {
|
|
289
|
+
log.info(`[fastagent] schedules: ${schedules.map((s) => s.name).join(", ")}${options.externalClock ? " (external clock — no resident cron timers)" : ""}`);
|
|
290
|
+
}
|
|
245
291
|
const stop = () => scheduler.stop();
|
|
246
292
|
process.once("SIGINT", stop);
|
|
247
293
|
process.once("SIGTERM", stop);
|
|
294
|
+
return schedules;
|
|
248
295
|
}
|
package/dist/cli/shared.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { type LoginIO, type LoginMethod, type LoginResult } from "../engines/pi/login.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The padded label writer for the STARTUP report (`dev`/`start`, stderr via the log level). Hand-spaced
|
|
4
|
+
* labels drift out of alignment the moment a longer one appears — which is exactly what happened when
|
|
5
|
+
* `workspace:` joined `config:`/`model:`/`state:`. `info` keeps its own writer on purpose: its report is
|
|
6
|
+
* stdout DATA (pipeable, its own label set, its own width), not a log line — the shared thing is the
|
|
7
|
+
* policy (pad, never hand-space), not a constant.
|
|
8
|
+
*/
|
|
9
|
+
export declare function reportLine(label: string, value: string): void;
|
|
10
|
+
/** The workspace hint under the `agent:`/`workspace:` pair, when there is one ({@link workspaceHint}):
|
|
11
|
+
* you pointed at the agent, and the project around it is probably what you meant. A hint, so it renders
|
|
12
|
+
* as one and is silent otherwise — `dev` and `start` both print the pair, so both ask for it. */
|
|
13
|
+
export declare function reportWorkspaceHint(hint: string | undefined): void;
|
|
2
14
|
/** Both stdin and stdout are a terminal — the precondition for an interactive prompt. */
|
|
3
15
|
export declare function isInteractive(): boolean;
|
|
4
16
|
/**
|
|
@@ -19,9 +31,10 @@ export declare function reportAuth(modelSpec: string, authPath: string): Promise
|
|
|
19
31
|
* already set; on a non-TTY (CI, a piped stdin), with `--no-input`, on cancel, or on a failed login
|
|
20
32
|
* it stays quiet and lets the caller raise its own clear error (`missing model`, or deploy's
|
|
21
33
|
* model-travel gate). The pick is exported to FASTAGENT_MODEL so a spawned `dev` worker inherits it,
|
|
22
|
-
* and best-effort written back to the config so the next run is quiet.
|
|
34
|
+
* and best-effort written back to the config so the next run is quiet. `agentDir` is the resolved
|
|
35
|
+
* AGENT DIR (resolvePlacement().agentDir) — config and auth both live there.
|
|
23
36
|
*/
|
|
24
|
-
export declare function resolveFirstRunModel(
|
|
37
|
+
export declare function resolveFirstRunModel(agentDir: string, options?: {
|
|
25
38
|
model?: string;
|
|
26
39
|
authPath?: string;
|
|
27
40
|
input?: boolean;
|
package/dist/cli/shared.js
CHANGED
|
@@ -8,14 +8,30 @@ import { relative } from "node:path";
|
|
|
8
8
|
import { autocomplete, isCancel, log as clackLog, password, select, text as clackText } from "@clack/prompts";
|
|
9
9
|
import { buildModelPickerOptions } from "./models-view.js";
|
|
10
10
|
import { fastagentCredentialStore } from "../engines/pi/auth.js";
|
|
11
|
-
import { isValidPort, listModels, loadConfig, providerOf, resolveAuthPath, resolveModel, resolveModelSpec,
|
|
12
|
-
import { ensureStateRootSelfIgnored, isUnderDir } from "../engines/pi/definition.js";
|
|
11
|
+
import { isValidPort, listModels, loadConfig, providerOf, resolveAuthPath, resolveModel, resolveModelSpec, rewriteConfigModel, } from "../engines/pi/config.js";
|
|
13
12
|
import { LoginCancelled, loginFlow } from "../engines/pi/login.js";
|
|
14
13
|
import { createPiModels, probeApiKey, probeAuthSource, providerAuthStatuses } from "../engines/pi/models.js";
|
|
15
14
|
import { formatAuthReport } from "./auth-view.js";
|
|
16
15
|
import { log } from "../log.js";
|
|
17
16
|
import { openExternalUrl } from "../open-url.js";
|
|
18
17
|
import { failStartup, failUsage } from "./fail.js";
|
|
18
|
+
/**
|
|
19
|
+
* The padded label writer for the STARTUP report (`dev`/`start`, stderr via the log level). Hand-spaced
|
|
20
|
+
* labels drift out of alignment the moment a longer one appears — which is exactly what happened when
|
|
21
|
+
* `workspace:` joined `config:`/`model:`/`state:`. `info` keeps its own writer on purpose: its report is
|
|
22
|
+
* stdout DATA (pipeable, its own label set, its own width), not a log line — the shared thing is the
|
|
23
|
+
* policy (pad, never hand-space), not a constant.
|
|
24
|
+
*/
|
|
25
|
+
export function reportLine(label, value) {
|
|
26
|
+
log.info(`[fastagent] ${`${label}:`.padEnd(11)}${value}`);
|
|
27
|
+
}
|
|
28
|
+
/** The workspace hint under the `agent:`/`workspace:` pair, when there is one ({@link workspaceHint}):
|
|
29
|
+
* you pointed at the agent, and the project around it is probably what you meant. A hint, so it renders
|
|
30
|
+
* as one and is silent otherwise — `dev` and `start` both print the pair, so both ask for it. */
|
|
31
|
+
export function reportWorkspaceHint(hint) {
|
|
32
|
+
if (hint)
|
|
33
|
+
reportLine("hint", hint);
|
|
34
|
+
}
|
|
19
35
|
/** Both stdin and stdout are a terminal — the precondition for an interactive prompt. */
|
|
20
36
|
export function isInteractive() {
|
|
21
37
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -65,23 +81,24 @@ export async function reportAuth(modelSpec, authPath) {
|
|
|
65
81
|
* already set; on a non-TTY (CI, a piped stdin), with `--no-input`, on cancel, or on a failed login
|
|
66
82
|
* it stays quiet and lets the caller raise its own clear error (`missing model`, or deploy's
|
|
67
83
|
* model-travel gate). The pick is exported to FASTAGENT_MODEL so a spawned `dev` worker inherits it,
|
|
68
|
-
* and best-effort written back to the config so the next run is quiet.
|
|
84
|
+
* and best-effort written back to the config so the next run is quiet. `agentDir` is the resolved
|
|
85
|
+
* AGENT DIR (resolvePlacement().agentDir) — config and auth both live there.
|
|
69
86
|
*/
|
|
70
|
-
export async function resolveFirstRunModel(
|
|
71
|
-
const { config, path: configPath } = await loadConfig(
|
|
87
|
+
export async function resolveFirstRunModel(agentDir, options = {}) {
|
|
88
|
+
const { config, path: configPath } = await loadConfig(agentDir).catch(failStartup);
|
|
72
89
|
if (resolveModelSpec(options.model, config))
|
|
73
90
|
return; // already set (flag > FASTAGENT_MODEL > config)
|
|
74
91
|
if (options.input === false)
|
|
75
92
|
return; // --no-input: never prompt (clig) — the opener raises the clear error
|
|
76
93
|
if (!isInteractive())
|
|
77
94
|
return; // CI/deploy: the opener throws the actionable missing-model error
|
|
78
|
-
const authPath = resolveAuthPath(
|
|
95
|
+
const authPath = resolveAuthPath(agentDir, options.authPath);
|
|
79
96
|
const models = createPiModels({ authPath });
|
|
80
|
-
const chosen = await pickWithCredentials(
|
|
97
|
+
const chosen = await pickWithCredentials(models, authPath);
|
|
81
98
|
if (chosen === undefined)
|
|
82
99
|
return; // cancelled (or auth probe failed): the caller raises its clear missing-model error
|
|
83
100
|
process.env.FASTAGENT_MODEL = chosen; // this process + any spawned dev worker inherits it
|
|
84
|
-
await persistModelChoice(
|
|
101
|
+
await persistModelChoice(agentDir, configPath, chosen);
|
|
85
102
|
}
|
|
86
103
|
/**
|
|
87
104
|
* The credential-aware pick: full catalog annotated per provider, then the post-pick auth policy —
|
|
@@ -89,7 +106,7 @@ export async function resolveFirstRunModel(workspaceDir, options = {}) {
|
|
|
89
106
|
* independent of credentials), or the inline login for the rest. Returns the chosen spec, or
|
|
90
107
|
* undefined when the pick should be discarded (picker cancel, login cancel, a failed auth probe).
|
|
91
108
|
*/
|
|
92
|
-
async function pickWithCredentials(
|
|
109
|
+
async function pickWithCredentials(models, authPath) {
|
|
93
110
|
let statuses;
|
|
94
111
|
try {
|
|
95
112
|
statuses = await providerAuthStatuses(models);
|
|
@@ -124,11 +141,6 @@ async function pickWithCredentials(workspaceDir, models, authPath) {
|
|
|
124
141
|
}
|
|
125
142
|
return chosen;
|
|
126
143
|
}
|
|
127
|
-
// Inline login. Same leak guard as `login`: self-ignore the state root BEFORE a credential
|
|
128
|
-
// can land in-tree, so the secret is never untracked-but-committable.
|
|
129
|
-
const stateRoot = resolveStateRoot(workspaceDir);
|
|
130
|
-
if (isUnderDir(authPath, stateRoot))
|
|
131
|
-
await ensureStateRootSelfIgnored(workspaceDir, stateRoot);
|
|
132
144
|
try {
|
|
133
145
|
// Verified against the CHOSEN model — the exact request the agent is about to make; a rejected
|
|
134
146
|
// key re-prompts inside the loop, so reaching here means a usable (or at worst unverifiable) key.
|
|
@@ -224,10 +236,10 @@ function terminalLoginIO() {
|
|
|
224
236
|
/**
|
|
225
237
|
* Best-effort persist the picked model so the next run does not prompt. Rewrites the commented
|
|
226
238
|
* `model:` placeholder the scaffold writes / an existing `model:` line, or re-inserts the line into a
|
|
227
|
-
* scaffold-shaped config (the hand-deleted-to-reset case); anything else (
|
|
228
|
-
*
|
|
239
|
+
* scaffold-shaped config (the hand-deleted-to-reset case); anything else (a hand-shaped config) is
|
|
240
|
+
* left untouched with a printed hint. Never throws — persistence is a convenience.
|
|
229
241
|
*/
|
|
230
|
-
async function persistModelChoice(
|
|
242
|
+
async function persistModelChoice(agentDir, configPath, spec) {
|
|
231
243
|
const hint = () => console.error(
|
|
232
244
|
// No "using it for this run" promise: deploy's model-travel gate rightly ignores the un-persisted pick.
|
|
233
245
|
`[fastagent] picked ${spec} — set \`model: ${JSON.stringify(spec)}\` in your config to persist`);
|
|
@@ -238,7 +250,7 @@ async function persistModelChoice(workspaceDir, configPath, spec) {
|
|
|
238
250
|
if (!replaced)
|
|
239
251
|
return hint();
|
|
240
252
|
await writeFile(configPath, replaced);
|
|
241
|
-
console.error(`[fastagent] saved model ${JSON.stringify(spec)} to ${relative(
|
|
253
|
+
console.error(`[fastagent] saved model ${JSON.stringify(spec)} to ${relative(agentDir, configPath)}`);
|
|
242
254
|
}
|
|
243
255
|
catch {
|
|
244
256
|
hint();
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ChannelKind } from "../../scaffold/add-channel.ts";
|
|
2
|
+
import { type Artifact, type ContainerInput } from "../container.ts";
|
|
3
|
+
/** The one schedule fact the plan needs (from loadSchedules) — name + cron + tz. */
|
|
4
|
+
export interface ScheduleFact {
|
|
5
|
+
name: string;
|
|
6
|
+
cron: string;
|
|
7
|
+
tz?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AgentcorePlanInput extends ContainerInput {
|
|
10
|
+
/** Base name (dir basename) — shapes the runtime name, stack name, ECR repo, session id. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** What satisfies model auth locally: an env-var name, an OAuth/stored label, or undefined. */
|
|
13
|
+
modelAuth: string | undefined;
|
|
14
|
+
/** Known first-party channels — each contributes its secret metadata + webhook step. */
|
|
15
|
+
channels: ChannelKind[];
|
|
16
|
+
/** ALL route-channel basenames (customs included) — any of them requires the forwarder. */
|
|
17
|
+
routeChannels: string[];
|
|
18
|
+
/** Extra secret env-var names (fastagent.config deploy.secrets). */
|
|
19
|
+
extraSecrets?: string[];
|
|
20
|
+
/** Static schedules — each becomes an EventBridge Scheduler rule targeting the forwarder. */
|
|
21
|
+
schedules: ScheduleFact[];
|
|
22
|
+
/** Wake tool enabled — DEGRADED here (fires only while a session happens to be awake); warned. */
|
|
23
|
+
selfSchedule: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface AgentcorePlan {
|
|
26
|
+
/** template + forwarder + Dockerfile/.dockerignore — written by the CLI (kept unless --force). */
|
|
27
|
+
artifacts: Artifact[];
|
|
28
|
+
/** The ordered, values-resolved deploy runbook — printed to stdout. */
|
|
29
|
+
runbook: string[];
|
|
30
|
+
/** Cron expressions EventBridge cannot express — surfaced as runbook warnings, not silent drops. */
|
|
31
|
+
untranslatableSchedules: {
|
|
32
|
+
name: string;
|
|
33
|
+
reason: string;
|
|
34
|
+
}[];
|
|
35
|
+
}
|
|
36
|
+
/** SessionStorage mount = FASTAGENT_STATE_DIR (AgentCore requires exactly `/mnt/<one-level>`). It is
|
|
37
|
+
* a fast LOCAL disk only: the platform wipes it on every runtime version update (= every deploy).
|
|
38
|
+
* Durability across deploys comes from the S3 snapshot (channels/agentcore-state.ts). */
|
|
39
|
+
export declare const MOUNT = "/mnt/state";
|
|
40
|
+
/**
|
|
41
|
+
* How long an idle session keeps its microVM. Memory is billed per second across the WHOLE session
|
|
42
|
+
* — idle included, at the peak level reached — so this tail is the standing cost of every burst of
|
|
43
|
+
* activity, while CPU stops billing the moment the agent stops working. 3 minutes rather than the
|
|
44
|
+
* platform's 15: the tail shrinks 5×, and the cost is a cold start (image + Node + snapshot restore)
|
|
45
|
+
* for anyone who returns after a longer gap. `/ping` reports HealthyBusy while work is in flight, so
|
|
46
|
+
* this timer only ever starts once the agent has genuinely settled — a long turn is never cut short.
|
|
47
|
+
* AWS accepts 60–28800.
|
|
48
|
+
*/
|
|
49
|
+
export declare const IDLE_TIMEOUT_SECONDS = 180;
|
|
50
|
+
/** The platform ceiling on one session's compute (8 h). The session ID outlives it: the next invoke
|
|
51
|
+
* simply gets fresh compute with the same storage. */
|
|
52
|
+
export declare const MAX_LIFETIME_SECONDS = 28800;
|
|
53
|
+
/** The state snapshot's object key in the deployment bucket (one object; see agentcore-state.ts). */
|
|
54
|
+
export declare const STATE_KEY = "state/snapshot.json.gz";
|
|
55
|
+
/** The forwarder artifact. Named `index.js` because it IS the Lambda deployment package's entry:
|
|
56
|
+
* zipping it as-is produces a valid package (`Handler: index.handler`), with nothing to rename. */
|
|
57
|
+
export declare const FORWARDER_FILE = "lambda/index.js";
|
|
58
|
+
/** The deployment bucket: forwarder code + the state snapshot. Account-suffixed for S3's GLOBAL
|
|
59
|
+
* namespace, and created OUTSIDE the stack (like the ECR repo) so a `delete-stack` cannot take the
|
|
60
|
+
* agent's memory with it. Bucket names cap at 63 chars; `name` is already gated to 40. */
|
|
61
|
+
export declare function stateBucketName(name: string, account: string): string;
|
|
62
|
+
/** AgentCore env values max 2048 chars — a real OAuth auth.json's base64 exceeds it, so the seed is
|
|
63
|
+
* CHUNKED across FASTAGENT_AUTH_SEED + _2… (collectAuthSeed reassembles at boot). 2000 keeps margin. */
|
|
64
|
+
export declare const AUTH_SEED_CHUNK_SIZE = 2000;
|
|
65
|
+
export declare const AUTH_SEED_MAX_CHUNKS = 4;
|
|
66
|
+
/** The generated template's filename (namespaced under the kit in the agentDir layout). */
|
|
67
|
+
export declare const TEMPLATE_FILE = "agentcore.template.yaml";
|
|
68
|
+
/** The generated template's first-line marker — the ONE source for both the generator and the
|
|
69
|
+
* "did fastagent generate this?" check (deploy's drift gate), so they cannot drift apart. */
|
|
70
|
+
export declare const GENERATED_TEMPLATE_MARKER = "# Generated by `fastagent deploy agentcore`";
|
|
71
|
+
/** Whether an on-disk template is fastagent-generated (vs hand-written — kept, never gated). */
|
|
72
|
+
export declare function isGeneratedAgentcoreTemplate(content: string): boolean;
|
|
73
|
+
/** Runtime name (`[a-zA-Z][a-zA-Z0-9_]{0,47}`) from a dir basename. */
|
|
74
|
+
export declare function toRuntimeName(basename: string): string;
|
|
75
|
+
/** The ONE fixed ingress session id (webhooks + schedule fires) — ≥ 33 chars (the API minimum),
|
|
76
|
+
* deterministic (the Lambda holds it in env), padded so any name clears the floor. */
|
|
77
|
+
export declare function ingressSessionId(name: string): string;
|
|
78
|
+
/** CFN parameter logical id for a secret env-var name: TELEGRAM_BOT_TOKEN → TelegramBotToken
|
|
79
|
+
* (parameter names must be alphanumeric). Deterministic — run.ts builds the same mapping. */
|
|
80
|
+
export declare function cfnParamName(envName: string): string;
|
|
81
|
+
/**
|
|
82
|
+
* Translate a 5-field cron into EventBridge Scheduler's `cron(m h dom mon dow *)`, or say why it
|
|
83
|
+
* can't be. The two dialects disagree exactly where silent translation would misfire:
|
|
84
|
+
* - EventBridge numbers day-of-week 1–7 (1 = Sunday); standard cron uses 0–6 (0/7 = Sunday) —
|
|
85
|
+
* numeric dow values and range endpoints are remapped ({@link mapDowField}); steps and names
|
|
86
|
+
* pass through; a range that wraps under renumbering is refused.
|
|
87
|
+
* - EventBridge requires `?` in dom or dow: a `*` on either side becomes `?`; BOTH restricted is
|
|
88
|
+
* standard cron's OR semantics, which EventBridge cannot express — refused, never approximated.
|
|
89
|
+
* - A 6-field (seconds) expression and L/# day-of-week forms are refused for the same reason.
|
|
90
|
+
*/
|
|
91
|
+
export declare function toEventBridgeCron(cron: string): {
|
|
92
|
+
expression: string;
|
|
93
|
+
} | {
|
|
94
|
+
error: string;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* The forwarder Lambda source — the ONLY string both the template's inline ZipFile and the readable
|
|
98
|
+
* `lambda/forwarder.js` artifact are generated from (one source, no drift). Zero-dependency: the
|
|
99
|
+
* Lambda Node runtime bundles AWS SDK v3. CommonJS ON PURPOSE: CloudFormation inline code always
|
|
100
|
+
* lands as `index.js`, where ESM `import` is a syntax error (found by the first real deploy). Two
|
|
101
|
+
* event shapes: a Function URL webhook (reconstructed verbatim into a `webhook` envelope; the
|
|
102
|
+
* channel's REAL response rides back inside the transport reply and is re-emitted byte-exact —
|
|
103
|
+
* Feishu's URL-verification challenge depends on it), and an EventBridge Scheduler fire
|
|
104
|
+
* (`{ scheduleFire }`, slot = the scheduled instant — the container's idempotency key). MUST stay
|
|
105
|
+
* under CloudFormation's 4096-byte inline-code cap.
|
|
106
|
+
*/
|
|
107
|
+
export declare function forwarderSource(): string;
|
|
108
|
+
/**
|
|
109
|
+
* The EventBridge physical name for a schedule. A schedule's local name is an arbitrary MODULE FILE
|
|
110
|
+
* NAME (`schedules/晨报.ts`, `schedules/deploy check.ts`), while AWS requires `[0-9A-Za-z-_.]+` within
|
|
111
|
+
* 64 chars — and the `fa-<agent>-` prefix already eats up to 44 of them. So: sanitize, bound the
|
|
112
|
+
* readable part, and end with a hash of the ORIGINAL name, which keeps distinct schedules distinct
|
|
113
|
+
* where sanitizing or truncation would have merged them (one rule silently firing for two).
|
|
114
|
+
*/
|
|
115
|
+
export declare function scheduleResourceName(agent: string, schedule: string): string;
|
|
116
|
+
/** Compute the AgentCore deploy plan from the resolved definition. */
|
|
117
|
+
export declare function planAgentcoreDeploy(input: AgentcorePlanInput): AgentcorePlan;
|