@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
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/** `fastagent info [dir] [--json]`: print what the directory ASSEMBLES into, WITHOUT booting a server. Read-only. */
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { loadDotEnv } from "../../env.js";
|
|
4
|
-
import { discoverChannelFiles } from "../../
|
|
4
|
+
import { discoverChannelFiles } from "../../channels/discover.js";
|
|
5
5
|
import { defaultSessionsDir, loadConfig, resolveAuthPath, resolveModel, resolveModelSpec, resolveSessionsDirOverride, } from "../../engines/pi/config.js";
|
|
6
6
|
import { createPiModelRuntime } from "../../engines/pi/models.js";
|
|
7
7
|
import { resolveStateRoot, workspaceHint } from "../../paths.js";
|
|
8
|
-
import { resolveAgentTools } from "../../engines/pi/create.js";
|
|
8
|
+
import { CODING_TOOL_NAMES, resolveAgentTools } from "../../engines/pi/create.js";
|
|
9
9
|
import { loadAgentDefinition } from "../../engines/pi/definition.js";
|
|
10
|
-
import { reportFindingsIfChanged,
|
|
11
|
-
import { log } from "../../log.js";
|
|
10
|
+
import { reportFindingsIfChanged, reportToolCollisions } from "../../engines/pi/report.js";
|
|
11
|
+
import { reportModuleLoadFailures, log } from "../../log.js";
|
|
12
12
|
import { nextRun } from "../../schedule/cron.js";
|
|
13
13
|
import { loadSchedules } from "../../schedule/discover.js";
|
|
14
14
|
import { failStartup, placementOrExit } from "../fail.js";
|
|
@@ -25,7 +25,7 @@ export async function runInfo(dirArg, opts) {
|
|
|
25
25
|
// tool), is isolated the same way everywhere (G2): info, dev, AND start report it and keep going with
|
|
26
26
|
// the tools that loaded. The `error`/`.catch` below only fires for a whole-load fault (an unreadable
|
|
27
27
|
// tools/ dir), not a single bad file.
|
|
28
|
-
const tools = await resolveAgentTools(config, agentDir)
|
|
28
|
+
const tools = await resolveAgentTools(config, agentDir, workspace)
|
|
29
29
|
.then((r) => ({
|
|
30
30
|
names: r.toolNames,
|
|
31
31
|
deferred: r.deferredToolNames,
|
|
@@ -78,6 +78,7 @@ export async function runInfo(dirArg, opts) {
|
|
|
78
78
|
model: modelSpec ?? null,
|
|
79
79
|
modelError: modelError ?? null,
|
|
80
80
|
thinkingLevel: config.thinkingLevel ?? null,
|
|
81
|
+
codingTools: [...CODING_TOOL_NAMES],
|
|
81
82
|
context: definition.contextFiles.map((f) => f.path),
|
|
82
83
|
persona: definition.persona !== undefined,
|
|
83
84
|
skills: definition.skills.map((skill) => ({ name: skill.name, description: skill.description })),
|
|
@@ -114,6 +115,7 @@ export async function runInfo(dirArg, opts) {
|
|
|
114
115
|
cont(`⚠ does not resolve: ${modelError}`);
|
|
115
116
|
if (config.thinkingLevel)
|
|
116
117
|
line("thinking", config.thinkingLevel);
|
|
118
|
+
line("codingTools", CODING_TOOL_NAMES.join(", "));
|
|
117
119
|
line("context", definition.contextFiles.map((f) => f.path).join(", ") || "(none)");
|
|
118
120
|
line("persona", definition.persona ? "persona.md" : "(none)");
|
|
119
121
|
line("skills", definition.skills.map((skill) => skill.name).join(", ") || "(none)");
|
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { basename, join, resolve } from "node:path";
|
|
10
|
-
import { DEFAULT_AGENT_DIRNAME, SECRETS_DIRNAME, agentsAt } from "../../paths.js";
|
|
10
|
+
import { DEFAULT_AGENT_DIRNAME, SECRETS_DIRNAME, agentsAt, displayPath } from "../../paths.js";
|
|
11
11
|
import { detectRuntime, readPackageJson } from "../../runtime.js";
|
|
12
12
|
import { agentDirName, agentDirNameError, scaffoldAgent } from "../../scaffold/init.js";
|
|
13
|
-
import { displayPath } from "../../paths.js";
|
|
14
13
|
import { failStartup, failUsage } from "../fail.js";
|
|
15
14
|
export async function runInit(dirArg, opts) {
|
|
16
15
|
const dir = resolve(dirArg);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { loadDotEnv } from "../../env.js";
|
|
8
8
|
import { resolveStateRoot } from "../../paths.js";
|
|
9
|
-
import { reportModuleLoadFailures } from "../../
|
|
9
|
+
import { reportModuleLoadFailures } from "../../log.js";
|
|
10
10
|
import { readRuns } from "../../schedule/audit.js";
|
|
11
11
|
import { nextRun } from "../../schedule/cron.js";
|
|
12
12
|
import { loadSchedules } from "../../schedule/discover.js";
|
|
@@ -2,26 +2,24 @@
|
|
|
2
2
|
* `fastagent start [dir]`: run the agent in production posture — the SAME assembly as dev (your
|
|
3
3
|
* directory is the agent), just no file-watching. No build step: start reads the definition directly.
|
|
4
4
|
*/
|
|
5
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
5
|
import { dirname, resolve } from "node:path";
|
|
7
|
-
import {
|
|
6
|
+
import { writeFileAtomic } from "../../atomic-write.js";
|
|
7
|
+
import { authSeedBytes, collectAuthSeed } from "../../deploy/secrets.js";
|
|
8
8
|
import { loadDotEnv } from "../../env.js";
|
|
9
9
|
import { resolveAuthPath, resolveSessionsDirOverride } from "../../engines/pi/config.js";
|
|
10
|
-
import { resolveSecretsDir,
|
|
11
|
-
import { isUnderDir } from "../../engines/pi/definition.js";
|
|
12
|
-
import { reportFindingsIfChanged, reportModuleLoadFailures, reportToolCollisions } from "../../engines/pi/report.js";
|
|
13
|
-
import { createPiAgentFromDir } from "../../engines/pi/open.js";
|
|
10
|
+
import { SECRET_FILE_MODE, ensureSecretsDir, resolveSecretsDir, isUnderDir, exists } from "../../paths.js";
|
|
14
11
|
import { log, setLogLevel } from "../../log.js";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
12
|
+
import { createPiAgentFromDir } from "../../engines/pi/open.js";
|
|
13
|
+
import { mountAgentService } from "../../service.js";
|
|
17
14
|
import { logAgentLoop } from "../../observe.js";
|
|
18
15
|
import { installProxyFetch } from "../../proxy.js";
|
|
19
|
-
import { exists } from "../../paths.js";
|
|
20
16
|
import { bindAddress } from "../../bind.js";
|
|
21
|
-
import {
|
|
17
|
+
import { isAgentcoreRuntime, mountAgentcoreService } from "../../channels/agentcore-service.js";
|
|
18
|
+
import { createWakeAlarmSink } from "../../schedule/wake-alarm.js";
|
|
19
|
+
import { setWakeupsSink } from "../../schedule/wakeups.js";
|
|
22
20
|
import { failStartup, placementOrExit } from "../fail.js";
|
|
23
|
-
import { assertTunnelBindable, maybeTunnel,
|
|
24
|
-
import { parseBind, parsePort,
|
|
21
|
+
import { SHUTDOWN_GRACE_MS, assertTunnelBindable, maybeTunnel, reportServing, serve } from "../serve.js";
|
|
22
|
+
import { parseBind, parsePort, reportAssembly, resolveFirstRunModel } from "../shared.js";
|
|
25
23
|
export async function runStart(dirArg, opts) {
|
|
26
24
|
const dir = resolve(dirArg);
|
|
27
25
|
// Flag validation first: a bad --port is a USAGE error (exit 2), and reporting it must not depend on
|
|
@@ -39,30 +37,16 @@ export async function runStart(dirArg, opts) {
|
|
|
39
37
|
await maybeSeedAuth(resolveAuthPath(placement.agentDir, opts.authPath));
|
|
40
38
|
// The same opener dev uses (single assembly source), just no watch.
|
|
41
39
|
const sessionsDirOverride = resolveSessionsDirOverride(opts.sessionsDir);
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
await reportAuth(agentDir, modelSpec, authPath);
|
|
53
|
-
reportLine("context", definition.contextFiles.map((f) => f.path).join(", ") || "(none)");
|
|
54
|
-
if (definition.persona)
|
|
55
|
-
reportLine("persona", "persona.md");
|
|
56
|
-
reportLine("skills", definition.skills.map((s) => s.name).join(", ") || "(none)");
|
|
57
|
-
if (toolNames.length > 0)
|
|
58
|
-
reportLine("tools", toolNames.join(", "));
|
|
59
|
-
if (deferredToolNames.length > 0) {
|
|
60
|
-
reportLine("deferred", `${deferredToolNames.join(", ")} (activated via search_tools)`);
|
|
61
|
-
}
|
|
62
|
-
reportToolCollisions(toolCollisions);
|
|
63
|
-
reportModuleLoadFailures(toolFailures);
|
|
64
|
-
reportLine("state", stateRoot);
|
|
65
|
-
reportLine("sessions", sessionsDir);
|
|
40
|
+
const opened = await openStartDir(dir, opts, sessionsDirOverride);
|
|
41
|
+
const { agent, agentDir, config, stateRoot, sessionsDir } = opened;
|
|
42
|
+
// The same report `dev` prints; `state:`/`sessions:` are start's own extras, and the persistence
|
|
43
|
+
// notes below are why (see reportAssembly on the asymmetry).
|
|
44
|
+
await reportAssembly(opened, {
|
|
45
|
+
afterTools: [
|
|
46
|
+
["state", stateRoot],
|
|
47
|
+
["sessions", sessionsDir],
|
|
48
|
+
],
|
|
49
|
+
});
|
|
66
50
|
// State defaults under the agent dir, which a redeploy may replace wholesale. Gate on where the
|
|
67
51
|
// state root ACTUALLY resolved (inside the agent dir?), not on the raw env var: an empty
|
|
68
52
|
// `FASTAGENT_STATE_DIR=""` reads as unset (resolveStateRoot) and still lands in-agent, so a raw
|
|
@@ -81,102 +65,52 @@ export async function runStart(dirArg, opts) {
|
|
|
81
65
|
log.info(`[fastagent] note: secrets (.env, rotated auth.json) live under the definition dir; point ` +
|
|
82
66
|
`FASTAGENT_SECRETS_DIR at a persistent volume so a redeploy that replaces the dir does not wipe them.`);
|
|
83
67
|
}
|
|
84
|
-
reportFindingsIfChanged(definition.dir, definition);
|
|
85
|
-
// AgentCore Runtime posture (FASTAGENT_AGENTCORE=1, set by the generated deploy artifacts): the
|
|
86
|
-
// adapter (POST /invocations + GET /ping) is the container's only reachable surface, and cron
|
|
87
|
-
// slots arrive from the external clock through it — so no resident cron timers. In particular,
|
|
88
|
-
// do NOT mount the ordinary unauthenticated POST /invoke fallback: a selfSchedule-only topology
|
|
89
|
-
// needs a public Function URL for wake callbacks, and its forwarder can relay arbitrary paths.
|
|
90
|
-
const agentcore = process.env.FASTAGENT_AGENTCORE === "1";
|
|
91
68
|
// Same debug turn trace as dev; gated out here by the info level (see dev.ts serveOnce).
|
|
92
69
|
const traced = logAgentLoop(agent);
|
|
93
|
-
// On AgentCore the channels are constructed LAZILY (mountAgentcore's lazyChannels, resolved on the
|
|
94
|
-
// first envelope after the state-snapshot restore): construction loads channel state and replays
|
|
95
|
-
// turn intent, and at boot the state mount is PRE-RESTORE — empty after every version update — so
|
|
96
|
-
// an eager build would cache that emptiness (thread participation, delivery dedup, pending turns)
|
|
97
|
-
// and then clobber the restored files with it. Everywhere else the state root is durable at boot,
|
|
98
|
-
// so channels mount eagerly and a broken channel fails startup.
|
|
99
|
-
const routed = agentcore
|
|
100
|
-
? undefined
|
|
101
|
-
: await routesFor(agentDir, traced, stateRoot, sessionControl, { builtinInvoke: true }).catch(failStartup);
|
|
102
70
|
// `http.host` enters here the way the flag enters `parseBind` — through `bindAddress`, so a
|
|
103
71
|
// configured `localhost` is an ADDRESS by the time anything binds, renders or dials it.
|
|
104
72
|
const configured = config.http?.host;
|
|
105
73
|
const host = bindFlag ?? (configured === undefined ? undefined : bindAddress(configured));
|
|
106
74
|
assertTunnelBindable(host, opts.tunnel ?? false, bindFlag ? "flag" : "config");
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
const schedules = await startSchedules(agentDir, traced, stateRoot, config.selfSchedule ?? false, {
|
|
134
|
-
externalClock: agentcore,
|
|
135
|
-
});
|
|
136
|
-
let routes = withControl.routes;
|
|
137
|
-
if (agentcore) {
|
|
138
|
-
// The lazy channel surface the adapter resolves post-restore. Control routes ride along so a
|
|
139
|
-
// forwarder-relayed /control/* request dispatches the same as on a direct host. Long-connection
|
|
140
|
-
// channels cannot serve here — scale-to-zero severs a resident connection and nothing
|
|
141
|
-
// re-establishes it — so their presence is a configuration error, surfaced per envelope and by
|
|
142
|
-
// the deploy driver's health probe (there is no boot to fail on this host).
|
|
143
|
-
const lazyChannels = async () => {
|
|
144
|
-
const surface = await routesFor(agentDir, traced, stateRoot, sessionControl, { builtinInvoke: false });
|
|
145
|
-
if (surface.longConnections.length > 0) {
|
|
146
|
-
throw new Error(`long-connection channel(s) ${surface.longConnections.map((c) => c.name).join(", ")} cannot serve on ` +
|
|
147
|
-
`AgentCore (scale-to-zero severs resident connections) — use the channel's webhook form`);
|
|
148
|
-
}
|
|
149
|
-
// mountSessionControl's PATH-level collision rule, re-asserted here: with no channels at boot
|
|
150
|
-
// its own check ran against an empty base, and a spread merge would silently let control win —
|
|
151
|
-
// but a channel on /control/* is the same configuration error it is on every other host.
|
|
152
|
-
const controlPaths = new Set(Object.keys(withControl.routes).map((key) => parseRouteKey(key).path));
|
|
153
|
-
const collisions = Object.keys(surface.routes).filter((key) => controlPaths.has(parseRouteKey(key).path));
|
|
154
|
-
if (collisions.length > 0) {
|
|
155
|
-
throw new Error(`channel route(s) ${collisions.map((key) => `"${key}"`).join(", ")} collide with the session control ` +
|
|
156
|
-
`plane — rename the channel route or disable sessionControl in fastagent.config`);
|
|
157
|
-
}
|
|
158
|
-
return { ...surface.routes, ...withControl.routes };
|
|
159
|
-
};
|
|
160
|
-
try {
|
|
161
|
-
routes = mountAgentcore(routes, { agent: traced, stateRoot, schedules, onStateReady, lazyChannels });
|
|
162
|
-
}
|
|
163
|
-
catch (e) {
|
|
164
|
-
failStartup(e);
|
|
165
|
-
}
|
|
166
|
-
log.info(`[fastagent] agentcore: serving POST /invocations + GET /ping (FASTAGENT_AGENTCORE=1)`);
|
|
167
|
-
}
|
|
168
|
-
serve({
|
|
169
|
-
...(routed ?? { longConnections: [], routeChannels: [], builtinInvoke: false, markReady() { } }),
|
|
170
|
-
routes,
|
|
171
|
-
}, { port: portFlag ?? parsePort(process.env.PORT, "PORT env", "env") ?? config.http?.port ?? 8787, host }, (p) => {
|
|
172
|
-
withControl.announce(p);
|
|
173
|
-
maybeTunnel(agentDir, routed?.routeChannels ?? [], p, opts.tunnel ?? false, stateRoot);
|
|
75
|
+
const control = { tunnel: opts.tunnel ?? false, ...(host !== undefined ? { host } : {}) };
|
|
76
|
+
// ONE branch for the whole posture. AgentCore assembles differently — lazy channels over a
|
|
77
|
+
// pre-restore state mount, an external clock, no resident connections — but it yields the same
|
|
78
|
+
// AgentService, so everything below this point is common.
|
|
79
|
+
// The wake-alarm sink is a PROCESS-global (schedule/wakeups.ts) — this process IS the AgentCore
|
|
80
|
+
// deployment, so the process entry installs it. A service can be closed; a global cannot be
|
|
81
|
+
// handed to something that can, without inventing an ownership the singleton does not have.
|
|
82
|
+
const onStateReady = isAgentcoreRuntime() && config.selfSchedule ? armWakeAlarms(stateRoot) : undefined;
|
|
83
|
+
const service = await (isAgentcoreRuntime()
|
|
84
|
+
? mountAgentcoreService(opened, { wrapAgent: () => traced, control, onStateReady })
|
|
85
|
+
: mountAgentService(opened, {
|
|
86
|
+
wrapAgent: () => traced,
|
|
87
|
+
closeTimeoutMs: SHUTDOWN_GRACE_MS,
|
|
88
|
+
control,
|
|
89
|
+
onChannelClosed: (name, error) => failStartup(new Error(`${name} ${error === undefined ? "closed unexpectedly" : `failed: ${String(error)}`}`)),
|
|
90
|
+
})).catch(failStartup);
|
|
91
|
+
serve(service.handler, { port: portFlag ?? parsePort(process.env.PORT, "PORT env", "env") ?? config.http?.port ?? 8787, host }, {
|
|
92
|
+
ready: service.ready,
|
|
93
|
+
onListening: (p) => {
|
|
94
|
+
reportServing(service, host, p);
|
|
95
|
+
service.announce(p);
|
|
96
|
+
maybeTunnel(agentDir, service.channels.routes, p, opts.tunnel ?? false, stateRoot);
|
|
97
|
+
},
|
|
98
|
+
onShutdown: () => service.close(),
|
|
174
99
|
});
|
|
175
100
|
// No graceful drain: webhook turns run fire-and-forget; SIGTERM just exits mid-turn. Whether an
|
|
176
101
|
// in-flight turn is LOST depends on the channel: the Telegram channel persists turn intent pre-ACK
|
|
177
102
|
// and replays it next start (turn-store.ts, L1 durable execution, at-least-once); HTTP and other
|
|
178
103
|
// channels have no such layer, so their in-flight turns are still lost (the asker re-invokes).
|
|
179
104
|
}
|
|
105
|
+
/** The opener, kept as its own call so `opened` can also feed the shared assembly below. */
|
|
106
|
+
function openStartDir(dir, opts, sessionsDir) {
|
|
107
|
+
return createPiAgentFromDir(dir, {
|
|
108
|
+
model: opts.model,
|
|
109
|
+
sessionsDir,
|
|
110
|
+
authPath: opts.authPath,
|
|
111
|
+
serving: true, // long-running serve: the scheduler poller runs (wake mounts iff config.selfSchedule)
|
|
112
|
+
}).catch(failStartup);
|
|
113
|
+
}
|
|
180
114
|
/**
|
|
181
115
|
* Materialize `FASTAGENT_AUTH_SEED` (base64 of an auth.json, set by `deploy --run`) into the
|
|
182
116
|
* writable secrets dir ONCE — only when the seed is set AND the auth file is absent, so a refreshed
|
|
@@ -189,7 +123,32 @@ async function maybeSeedAuth(authPath) {
|
|
|
189
123
|
const bytes = authSeedBytes(collectAuthSeed(process.env), await exists(authPath));
|
|
190
124
|
if (!bytes)
|
|
191
125
|
return;
|
|
192
|
-
await
|
|
193
|
-
|
|
126
|
+
await ensureSecretsDir(dirname(authPath));
|
|
127
|
+
// Same spelling as the credential store: the mode is applied before the content is reachable, so
|
|
128
|
+
// the seed can never be published at some other file's looser permissions. It does NOT close the
|
|
129
|
+
// gap between the `exists` check and the write — the rename would overwrite a file created in
|
|
130
|
+
// between, against this function's absent-only promise. That window needs two `start` processes on
|
|
131
|
+
// one secrets dir, which no supported deployment runs (one container, one volume).
|
|
132
|
+
writeFileAtomic(authPath, bytes, SECRET_FILE_MODE);
|
|
194
133
|
log.info(`[fastagent] seeded ${authPath} from FASTAGENT_AUTH_SEED (first boot)`);
|
|
195
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Install the wake-ALARM sink before the scheduler starts — the boot wake pump may advance a
|
|
137
|
+
* recurring entry, and that save must already re-arm its alarm. Returns the post-restore reconcile:
|
|
138
|
+
* running it at boot would read the pre-restore state mount, see no pending wake-ups, and conclude
|
|
139
|
+
* there is nothing to re-arm.
|
|
140
|
+
*/
|
|
141
|
+
function armWakeAlarms(stateRoot) {
|
|
142
|
+
const secret = process.env.FASTAGENT_WAKE_SECRET;
|
|
143
|
+
if (!secret) {
|
|
144
|
+
log.warn(`[fastagent] FASTAGENT_WAKE_SECRET is not set — wake-ups fire only while a session is awake ` +
|
|
145
|
+
`(redeploy with the current template to fix)`);
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const sink = createWakeAlarmSink({ secret });
|
|
149
|
+
setWakeupsSink(sink);
|
|
150
|
+
log.info(`[fastagent] wake alarms: EventBridge-backed via the forwarder`);
|
|
151
|
+
// Boot reconcile: pending wake-ups may exist while their alarms were lost (a deploy replaced the
|
|
152
|
+
// forwarder, a sink call failed). The sink re-reads the store, so a bare notification is enough.
|
|
153
|
+
return () => sink(stateRoot);
|
|
154
|
+
}
|
|
@@ -4,7 +4,7 @@ import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node";
|
|
|
4
4
|
import { loadDotEnv } from "../../env.js";
|
|
5
5
|
import { loadConfig } from "../../engines/pi/config.js";
|
|
6
6
|
import { resolveAgentTools } from "../../engines/pi/create.js";
|
|
7
|
-
import { reportModuleLoadFailures } from "../../
|
|
7
|
+
import { reportModuleLoadFailures } from "../../log.js";
|
|
8
8
|
import { turnContext } from "../../engines/pi/tool-context.js";
|
|
9
9
|
import { failStartup, failUsage, placementOrExit } from "../fail.js";
|
|
10
10
|
export async function runTool(name, argsJson, dirArg) {
|
|
@@ -14,10 +14,10 @@ export async function runTool(name, argsJson, dirArg) {
|
|
|
14
14
|
const { agentDir, workspace } = placementOrExit(resolve(dirArg));
|
|
15
15
|
loadDotEnv(agentDir); // a tool may read a key from .env
|
|
16
16
|
const { config } = await loadConfig(agentDir).catch(failStartup);
|
|
17
|
-
// The same tool set dev/start mount (
|
|
17
|
+
// The same tool set dev/start mount (all coding tools + config.tools + discovered, deduped), so the runner
|
|
18
18
|
// exercises exactly what gets served — a shadowed tool is surfaced, not silently run. Resolve the
|
|
19
19
|
// placement like the openers, so `fastagent tool` finds the SAME tools/ as dev/start.
|
|
20
|
-
const { tools, toolCollisions, toolFailures } = await resolveAgentTools(config, agentDir).catch(failStartup);
|
|
20
|
+
const { tools, toolCollisions, toolFailures } = await resolveAgentTools(config, agentDir, workspace).catch(failStartup);
|
|
21
21
|
for (const c of toolCollisions) {
|
|
22
22
|
console.error(`[fastagent] warn: tool "${c.name}" (${c.source}) is shadowed by a default/config tool — not mounted`);
|
|
23
23
|
}
|
|
@@ -26,10 +26,8 @@ export async function runTool(name, argsJson, dirArg) {
|
|
|
26
26
|
if (!tool) {
|
|
27
27
|
failStartup(new Error(`unknown tool "${name}". available: ${tools.map((t) => t.name).join(", ") || "(none)"}`));
|
|
28
28
|
}
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// (AsyncLocalStorage), and pi's default coding tools take the ExecutionEnv from the harness's TOOL
|
|
32
|
-
// context — which no harness supplies here, so this stands in for it, rooted at the same workspace.
|
|
29
|
+
// Authored tools read cwd from turnContext; coding tools are already rooted at the workspace. Keep
|
|
30
|
+
// the fifth-argument env for lower-level MountedTools that explicitly consume it.
|
|
33
31
|
const env = new NodeExecutionEnv({ cwd: workspace });
|
|
34
32
|
const result = await turnContext
|
|
35
33
|
.run({ cwd: workspace }, () => tool.execute(`cli-${name}`, args, undefined, undefined, { env }))
|
package/dist/cli/program.js
CHANGED
|
@@ -171,7 +171,7 @@ const tool = {
|
|
|
171
171
|
DIR_ARG,
|
|
172
172
|
],
|
|
173
173
|
examples: [{ cmd: `fastagent tool add '{"a":2,"b":3}'` }],
|
|
174
|
-
notes: "Mounts the same tool set dev/start serve (
|
|
174
|
+
notes: "Mounts the same tool set dev/start serve (all coding tools + config.tools + discovered tools/, " +
|
|
175
175
|
"deduped), so a shadowed or broken tool is surfaced here exactly as it would be when serving.",
|
|
176
176
|
run: async (args) => (await import("./commands/tool.js")).runTool(args[0], args[1], args[2]),
|
|
177
177
|
};
|
package/dist/cli/serve.d.ts
CHANGED
|
@@ -1,59 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import { type Routes } from "../host/node.ts";
|
|
4
|
-
import type { LoadedSchedule } from "../schedule/schedule.ts";
|
|
5
|
-
import type { SessionControl } from "../session.ts";
|
|
6
|
-
export interface ServingSurface {
|
|
7
|
-
routes: Routes;
|
|
8
|
-
longConnections: LoadedLongConnectionChannel[];
|
|
9
|
-
/** Route-channel basenames; the tunnel registers only this subset. */
|
|
10
|
-
routeChannels: string[];
|
|
11
|
-
builtinInvoke: boolean;
|
|
12
|
-
/** Marks the built-in health route ready after every long-connection channel first connects. */
|
|
13
|
-
markReady(): void;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* The surface this deployment serves: default `GET /health` plus discovered channels, or the default
|
|
17
|
-
* POST `/invoke` only when neither a route nor a long-connection channel was declared.
|
|
18
|
-
*/
|
|
19
|
-
export declare function routesFor(agentDir: string, agent: Agent, stateRoot: string, control?: SessionControl, options?: {
|
|
20
|
-
builtinInvoke?: boolean;
|
|
21
|
-
}): Promise<ServingSurface>;
|
|
22
|
-
/**
|
|
23
|
-
* Mount the session control plane (`/control/*`) when the agent enabled it
|
|
24
|
-
* (`config.sessionControl`): merge the bearer-authenticated routes and return an announcer that
|
|
25
|
-
* writes `<stateRoot>/control.json` — `{ url, token }`, 0600 — once the port is known. The file is
|
|
26
|
-
* the LOCAL discovery channel (`fastagent attach`, a local desktop app); filesystem permissions are
|
|
27
|
-
* its trust boundary, and each boot overwrites it with a fresh per-boot token. A user channel
|
|
28
|
-
* colliding on `/control/*` fails startup — the same disposition as a channel-channel collision
|
|
29
|
-
* (routesFor): `sessionControl` is an explicit opt-in, so declaring both is a configuration error,
|
|
30
|
-
* and silently shadowing either side would serve a surface the author didn't write.
|
|
31
|
-
*/
|
|
32
|
-
export declare function mountSessionControl(routes: Routes, control: SessionControl | undefined, stateRoot: string, options?: {
|
|
33
|
-
tunnel?: boolean;
|
|
34
|
-
agent?: Agent;
|
|
35
|
-
host?: string;
|
|
36
|
-
}): {
|
|
37
|
-
routes: Routes;
|
|
38
|
-
announce: (boundPort: number) => void;
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* Mount the AgentCore Runtime adapter (`POST /invocations` + `GET /ping`) over the serving routes —
|
|
42
|
-
* the deployed container's ONLY reachable surface (channels/agentcore.ts). Wired by `start` when
|
|
43
|
-
* `FASTAGENT_AGENTCORE=1` (set by the generated deploy artifacts, never by hand). A channel colliding
|
|
44
|
-
* on either path fails startup, same disposition as the control-plane mount: the adapter's paths are
|
|
45
|
-
* the platform's contract, so a channel shadowing them would silently unserve the whole deployment.
|
|
46
|
-
*/
|
|
47
|
-
export declare function mountAgentcore(routes: Routes, options: {
|
|
48
|
-
agent: Agent;
|
|
49
|
-
stateRoot: string;
|
|
50
|
-
schedules: LoadedSchedule[];
|
|
51
|
-
onStateReady?: () => void;
|
|
52
|
-
/** The serving path's LAZY channel surface: constructed by the adapter on the first envelope
|
|
53
|
-
* AFTER the state-snapshot restore, never at boot (channels/agentcore.ts). When absent,
|
|
54
|
-
* `routes` is the dispatch target — for wirings whose state root is already authoritative. */
|
|
55
|
-
lazyChannels?: () => Promise<Routes>;
|
|
56
|
-
}): Routes;
|
|
1
|
+
import type { ChannelHandler } from "../channel.ts";
|
|
2
|
+
import type { AgentService } from "../service.ts";
|
|
57
3
|
/**
|
|
58
4
|
* Refuse `--tunnel` with a bind that cloudflared cannot reach: it dials the NAME `localhost:<port>`
|
|
59
5
|
* (the dev supervisor's tunnel too), so anything outside `127.0.0.1`/`::1`/wildcard — including a
|
|
@@ -64,6 +10,12 @@ export declare function mountAgentcore(routes: Routes, options: {
|
|
|
64
10
|
* came from config is broken runtime configuration (1).
|
|
65
11
|
*/
|
|
66
12
|
export declare function assertTunnelBindable(host: string | undefined, tunnel: boolean, source: "flag" | "config"): void;
|
|
13
|
+
/**
|
|
14
|
+
* The "we are serving" report: the supervisor message `dev`'s watcher waits for, the addresses, and
|
|
15
|
+
* what mounted. One function because both commands must say the same thing at the same moment —
|
|
16
|
+
* after readiness, never at socket bind.
|
|
17
|
+
*/
|
|
18
|
+
export declare function reportServing(service: AgentService, host: string | undefined, boundPort: number): void;
|
|
67
19
|
/**
|
|
68
20
|
* The startup lines that name WHERE the serve is: the bind report, and the curl the reader copies.
|
|
69
21
|
* ONE function because they are one message — they were two, and `--bind` updated the first while the
|
|
@@ -74,23 +26,23 @@ export declare function assertTunnelBindable(host: string | undefined, tunnel: b
|
|
|
74
26
|
* still needs one to dial, which is what `clientHost` gives (loopback for a wildcard, itself otherwise).
|
|
75
27
|
*/
|
|
76
28
|
export declare function readyAddressLines(host: string | undefined, boundPort: number, builtinInvoke: boolean): string[];
|
|
29
|
+
/** What the CLI gives a service to stop in, and the hard exit that follows it. The order matters:
|
|
30
|
+
* a forced exit before the service answers would report a clean shutdown over a stuck channel. */
|
|
31
|
+
export declare const SHUTDOWN_GRACE_MS = 800;
|
|
77
32
|
/**
|
|
78
|
-
* Bind HTTP
|
|
79
|
-
*
|
|
80
|
-
*
|
|
33
|
+
* Bind HTTP and report ready — but only once the SERVICE is, which is not the same moment: a bound
|
|
34
|
+
* socket is not a serving agent while a declared long-connection channel is still dialling, so this
|
|
35
|
+
* awaits `hooks.ready` (mountAgentService owns the connections themselves) before announcing
|
|
36
|
+
* anything. Signals are the sole clean-shutdown command; `host` unset binds all interfaces.
|
|
81
37
|
*/
|
|
82
|
-
export declare function serve(
|
|
38
|
+
export declare function serve(handler: ChannelHandler, bind: {
|
|
83
39
|
port: number;
|
|
84
40
|
host?: string;
|
|
85
|
-
},
|
|
41
|
+
}, hooks?: {
|
|
42
|
+
/** Awaited before anything is reported ready — see the listening handler. */
|
|
43
|
+
ready?: Promise<void>;
|
|
44
|
+
onListening?: (boundPort: number) => void;
|
|
45
|
+
onShutdown?: () => Promise<void> | void;
|
|
46
|
+
}): void;
|
|
86
47
|
/** Start a Cloudflare tunnel for route channels only. */
|
|
87
48
|
export declare function maybeTunnel(agentDir: string, routeChannels: string[], boundPort: number, tunnel: boolean, stateRoot?: string): void;
|
|
88
|
-
/**
|
|
89
|
-
* Load and start the agent's `schedules/` — a time-trigger firing the agent on each cron. Starts iff
|
|
90
|
-
* there are static schedules OR `selfSchedule` is on. Best-effort stop on process signals. Returns the
|
|
91
|
-
* loaded schedules so a serving surface that needs them (the AgentCore adapter's fire binding) shares
|
|
92
|
-
* ONE load instead of re-discovering. `externalClock` (AgentCore) arms no resident cron timers.
|
|
93
|
-
*/
|
|
94
|
-
export declare function startSchedules(agentDir: string, agent: Agent, stateRoot: string, selfSchedule: boolean, options?: {
|
|
95
|
-
externalClock?: boolean;
|
|
96
|
-
}): Promise<LoadedSchedule[]>;
|