@fastagent-sh/fastagent 0.14.0 → 0.15.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 +3 -3
- package/dist/agent.d.ts +20 -0
- package/dist/agent.js +9 -0
- package/dist/channels/context-buffer.d.ts +30 -0
- package/dist/channels/context-buffer.js +98 -0
- package/dist/channels/control.d.ts +28 -0
- package/dist/channels/control.js +214 -0
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +8 -11
- package/dist/channels/feishu/context-buffer.js +6 -67
- package/dist/channels/feishu/feishu-api.d.ts +11 -3
- package/dist/channels/feishu/feishu-api.js +20 -2
- package/dist/channels/feishu/feishu.d.ts +28 -16
- package/dist/channels/feishu/feishu.js +210 -188
- package/dist/channels/feishu/invoke-turn.d.ts +7 -20
- package/dist/channels/feishu/invoke-turn.js +10 -62
- package/dist/channels/feishu/model.d.ts +2 -1
- package/dist/channels/feishu/normalize.d.ts +2 -1
- package/dist/channels/feishu/owned-threads.d.ts +2 -6
- package/dist/channels/feishu/parse.d.ts +1 -1
- package/dist/channels/feishu/preview.d.ts +4 -7
- package/dist/channels/feishu/preview.js +24 -140
- package/dist/channels/feishu/register-app.d.ts +2 -1
- package/dist/channels/feishu/register-webhook.d.ts +2 -1
- package/dist/channels/feishu/setup-mode.d.ts +8 -0
- package/dist/channels/feishu/setup-mode.js +2 -0
- package/dist/channels/feishu/ws-ingress.d.ts +28 -0
- package/dist/channels/feishu/ws-ingress.js +136 -0
- package/dist/channels/github/github.js +8 -6
- package/dist/channels/http.d.ts +14 -0
- package/dist/channels/http.js +35 -2
- package/dist/channels/invoke-turn-kit.d.ts +65 -0
- package/dist/channels/invoke-turn-kit.js +87 -0
- package/dist/channels/lark/lark.d.ts +4 -2
- package/dist/channels/lark/lark.js +4 -1
- package/dist/channels/lark/onboard.d.ts +8 -4
- package/dist/channels/lark/onboard.js +8 -0
- package/dist/channels/preview-kit.d.ts +109 -0
- package/dist/channels/preview-kit.js +183 -0
- package/dist/channels/seen.d.ts +5 -0
- package/dist/channels/seen.js +35 -0
- package/dist/channels/slack/bot-auth.d.ts +15 -0
- package/dist/channels/slack/bot-auth.js +146 -0
- package/dist/channels/slack/config-api.d.ts +60 -0
- package/dist/channels/slack/config-api.js +149 -0
- package/dist/channels/slack/context-buffer.d.ts +24 -0
- package/dist/channels/slack/context-buffer.js +37 -0
- package/dist/channels/slack/invoke-turn.d.ts +19 -0
- package/dist/channels/slack/invoke-turn.js +63 -0
- package/dist/channels/slack/manifest.d.ts +49 -0
- package/dist/channels/slack/manifest.js +69 -0
- package/dist/channels/slack/model.d.ts +67 -0
- package/dist/channels/slack/model.js +2 -0
- package/dist/channels/slack/onboard.d.ts +41 -0
- package/dist/channels/slack/onboard.js +120 -0
- package/dist/channels/slack/onboarding-state.d.ts +31 -0
- package/dist/channels/slack/onboarding-state.js +69 -0
- package/dist/channels/slack/owned-threads.d.ts +6 -0
- package/dist/channels/slack/owned-threads.js +43 -0
- package/dist/channels/slack/parse.d.ts +23 -0
- package/dist/channels/slack/parse.js +81 -0
- package/dist/channels/slack/preview.d.ts +24 -0
- package/dist/channels/slack/preview.js +359 -0
- package/dist/channels/slack/reaction.d.ts +24 -0
- package/dist/channels/slack/reaction.js +62 -0
- package/dist/channels/slack/register-webhook.d.ts +10 -0
- package/dist/channels/slack/register-webhook.js +49 -0
- package/dist/channels/slack/scaffold/channel.ts +33 -0
- package/dist/channels/slack/scaffold/slack-send.ts +171 -0
- package/dist/channels/slack/setup-server.d.ts +17 -0
- package/dist/channels/slack/setup-server.js +103 -0
- package/dist/channels/slack/slack-api.d.ts +77 -0
- package/dist/channels/slack/slack-api.js +415 -0
- package/dist/channels/slack/slack.d.ts +58 -0
- package/dist/channels/slack/slack.js +451 -0
- package/dist/channels/slack/welcomed.d.ts +5 -0
- package/dist/channels/slack/welcomed.js +32 -0
- package/dist/channels/state.js +3 -3
- package/dist/channels/stop-command.d.ts +6 -0
- package/dist/channels/stop-command.js +36 -0
- package/dist/channels/tasks.d.ts +13 -0
- package/dist/channels/tasks.js +10 -0
- package/dist/channels/telegram/context-buffer.d.ts +8 -17
- package/dist/channels/telegram/context-buffer.js +6 -85
- package/dist/channels/telegram/invoke-turn.d.ts +5 -22
- package/dist/channels/telegram/invoke-turn.js +11 -58
- package/dist/channels/telegram/preview.d.ts +4 -7
- package/dist/channels/telegram/preview.js +24 -142
- package/dist/channels/telegram/telegram.js +23 -9
- package/dist/channels/{feishu/text.d.ts → text.d.ts} +1 -1
- package/dist/channels/{feishu/text.js → text.js} +1 -1
- package/dist/channels/turn-queue.js +1 -1
- package/dist/channels/turn-store.d.ts +1 -1
- package/dist/channels/turn-store.js +2 -3
- package/dist/cli/add-feishu.d.ts +27 -0
- package/dist/{cli-add-feishu.js → cli/add-feishu.js} +167 -44
- package/dist/cli/add-slack.d.ts +10 -0
- package/dist/cli/add-slack.js +204 -0
- package/dist/{cli-auth.d.ts → cli/auth-view.d.ts} +1 -1
- package/dist/{cli-auth.js → cli/auth-view.js} +1 -1
- package/dist/cli/commands/add.d.ts +4 -0
- package/dist/cli/commands/add.js +152 -20
- package/dist/cli/commands/attach.d.ts +82 -0
- package/dist/cli/commands/attach.js +559 -0
- package/dist/cli/commands/chat.d.ts +1 -0
- package/dist/cli/commands/chat.js +6 -1
- package/dist/cli/commands/deploy.d.ts +2 -0
- package/dist/cli/commands/deploy.js +78 -22
- package/dist/cli/commands/dev.js +11 -5
- package/dist/cli/commands/fire.js +3 -3
- package/dist/cli/commands/info.js +2 -2
- package/dist/cli/commands/invoke.js +2 -3
- package/dist/cli/commands/login.js +14 -25
- package/dist/cli/commands/models.js +1 -1
- package/dist/cli/commands/start.js +15 -8
- package/dist/cli/commands/tool.js +2 -1
- package/dist/cli/fail.d.ts +0 -2
- package/dist/cli/fail.js +1 -1
- package/dist/{invoke-stream.d.ts → cli/invoke-stream.d.ts} +1 -1
- package/dist/{invoke-stream.js → cli/invoke-stream.js} +4 -0
- package/dist/cli/kernel.d.ts +3 -2
- package/dist/cli/models-view.d.ts +21 -0
- package/dist/cli/models-view.js +66 -0
- package/dist/cli/program.js +79 -21
- package/dist/cli/serve.d.ts +38 -18
- package/dist/cli/serve.js +197 -39
- package/dist/cli/shared.d.ts +26 -6
- package/dist/cli/shared.js +163 -33
- package/dist/collect.d.ts +14 -3
- package/dist/collect.js +24 -0
- package/dist/core.d.ts +3 -1
- package/dist/core.js +2 -0
- package/dist/deploy/docker/plan.d.ts +2 -2
- package/dist/deploy/docker/plan.js +8 -6
- package/dist/deploy/fly/plan.d.ts +3 -1
- package/dist/deploy/fly/plan.js +12 -7
- package/dist/deploy/fly/run.d.ts +5 -4
- package/dist/deploy/fly/run.js +16 -5
- package/dist/deploy/preflight.d.ts +10 -4
- package/dist/deploy/preflight.js +53 -9
- package/dist/deploy/railway/plan.d.ts +3 -1
- package/dist/deploy/railway/plan.js +12 -4
- package/dist/deploy/railway/run.d.ts +5 -4
- package/dist/deploy/railway/run.js +15 -5
- package/dist/deploy/runner.d.ts +2 -1
- package/dist/deploy/secrets.d.ts +2 -1
- package/dist/deploy/secrets.js +23 -3
- package/dist/dev-supervisor.d.ts +0 -2
- package/dist/dev-supervisor.js +7 -3
- package/dist/engines/pi/channel.d.ts +22 -16
- package/dist/engines/pi/channel.js +90 -60
- package/dist/engines/pi/chat.d.ts +4 -16
- package/dist/engines/pi/chat.js +8 -261
- package/dist/engines/pi/config.d.ts +14 -9
- package/dist/engines/pi/config.js +33 -18
- package/dist/engines/pi/create.d.ts +22 -2
- package/dist/engines/pi/create.js +20 -14
- package/dist/engines/pi/harness.d.ts +49 -0
- package/dist/engines/pi/harness.js +90 -2
- package/dist/engines/pi/invoke.d.ts +55 -2
- package/dist/engines/pi/invoke.js +304 -19
- package/dist/engines/pi/login.d.ts +11 -0
- package/dist/engines/pi/login.js +16 -4
- package/dist/engines/pi/models.d.ts +56 -10
- package/dist/engines/pi/models.js +61 -23
- package/dist/engines/pi/session-builder.d.ts +16 -0
- package/dist/engines/pi/session-builder.js +308 -0
- package/dist/engines/pi/session-control.d.ts +50 -0
- package/dist/engines/pi/session-control.js +604 -0
- package/dist/engines/pi/sessions.d.ts +17 -2
- package/dist/engines/pi/sessions.js +9 -0
- package/dist/engines/pi/tool-context.d.ts +18 -11
- package/dist/engines/pi/tool-context.js +3 -9
- package/dist/engines/pi/tool.d.ts +6 -8
- package/dist/engines/pi/tool.js +6 -1
- package/dist/engines/pi/wake-tool.d.ts +0 -3
- package/dist/engines/pi/wake-tool.js +9 -7
- package/dist/engines/pi/workspace.d.ts +52 -0
- package/dist/engines/pi/workspace.js +72 -15
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/host/node.d.ts +23 -6
- package/dist/host/node.js +5 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/observe.js +3 -0
- package/dist/pi.d.ts +5 -4
- package/dist/pi.js +2 -1
- package/dist/scaffold/add-channel.d.ts +19 -6
- package/dist/scaffold/add-channel.js +126 -17
- package/dist/scaffold/templates/fastagent.config.mjs +4 -3
- package/dist/schedule/wakeups.d.ts +0 -3
- package/dist/schedule/wakeups.js +1 -1
- package/dist/session-remote.d.ts +53 -0
- package/dist/session-remote.js +336 -0
- package/dist/session.d.ts +265 -0
- package/dist/session.js +37 -0
- package/dist/slack.d.ts +2 -0
- package/dist/slack.js +2 -0
- package/dist/tunnel.d.ts +7 -4
- package/dist/tunnel.js +21 -10
- package/package.json +19 -7
- package/dist/channels/feishu/seen.d.ts +0 -5
- package/dist/channels/feishu/seen.js +0 -47
- package/dist/cli-add-feishu.d.ts +0 -8
- package/dist/cli-models.d.ts +0 -11
- package/dist/cli-models.js +0 -20
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
11
11
|
import { basename, dirname, join, resolve } from "node:path";
|
|
12
12
|
import { registerFeishuWebhook } from "../../channels/feishu/register-webhook.js";
|
|
13
|
+
import { readSlackBotAuthEnv } from "../../channels/slack/bot-auth.js";
|
|
14
|
+
import { registerSlackWebhook } from "../../channels/slack/register-webhook.js";
|
|
13
15
|
import { registerTelegramWebhook } from "../../channels/telegram/register-webhook.js";
|
|
14
16
|
import { isGeneratedDockerfile } from "../../deploy/container.js";
|
|
15
17
|
import { composeHasTunnelService, dockerWebhookPaths, isGeneratedCompose, planDockerDeploy, toDockerProjectName, } from "../../deploy/docker/plan.js";
|
|
@@ -22,12 +24,13 @@ import { deployRailwayRun } from "../../deploy/railway/run.js";
|
|
|
22
24
|
import { spawnRunner } from "../../deploy/runner.js";
|
|
23
25
|
import { assembleSecrets } from "../../deploy/secrets.js";
|
|
24
26
|
import { loadDotEnv } from "../../env.js";
|
|
25
|
-
import { loadConfig, resolveAgentDir,
|
|
27
|
+
import { loadConfig, resolveAgentDir, resolveModelSpec, resolveStateRoot } from "../../engines/pi/config.js";
|
|
26
28
|
import { installProxyFetch } from "../../proxy.js";
|
|
27
29
|
import { openExternalUrl } from "../../open-url.js";
|
|
28
30
|
import { exists } from "../../scaffold/init.js";
|
|
29
31
|
import { announceWebhooks } from "../../tunnel.js";
|
|
30
32
|
import { failStartup, failUsage } from "../fail.js";
|
|
33
|
+
import { resolveFirstRunModel } from "../shared.js";
|
|
31
34
|
export async function runDeploy(host, dirArg, opts) {
|
|
32
35
|
const target = resolve(dirArg);
|
|
33
36
|
if (opts.tunnel && host !== "docker") {
|
|
@@ -36,6 +39,11 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
36
39
|
}
|
|
37
40
|
loadDotEnv(target); // a custom provider/tool may read a key at config load
|
|
38
41
|
installProxyFetch(); // post-deploy channel API calls must honor HTTP(S)_PROXY under Node
|
|
42
|
+
// First-run funnel, FULL picker: the write-back lands the model in fastagent.config.* — exactly what
|
|
43
|
+
// the model-travel gate below requires (--model/env don't reach the deployed box) — and an inline
|
|
44
|
+
// login stores the credential `--run` then carries. Runs BEFORE loadConfig; the read-back sees the
|
|
45
|
+
// rewritten file because loadConfig cache-busts on mtime (a failed write-back still gates, correctly).
|
|
46
|
+
await resolveFirstRunModel(target, { model: opts.model, authPath: opts.authPath, input: opts.input });
|
|
39
47
|
const { config } = await loadConfig(target).catch(failStartup);
|
|
40
48
|
const agentDir = resolveAgentDir(target, config);
|
|
41
49
|
const modelSpec = resolveModelSpec(opts.model, config);
|
|
@@ -49,13 +57,14 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
49
57
|
modelSpec,
|
|
50
58
|
run: !!opts.run,
|
|
51
59
|
force: !!opts.force,
|
|
52
|
-
|
|
60
|
+
authPathFlag: opts.authPath, // flag > FASTAGENT_AUTH_PATH > default — resolved by preflight (one owner)
|
|
53
61
|
}).catch(failStartup);
|
|
54
62
|
if (!pre.ok)
|
|
55
63
|
failStartup(new Error(`deploy stopped: ${pre.gate}`));
|
|
56
64
|
for (const m of pre.messages)
|
|
57
65
|
console.error(`[fastagent] ${m.level}: ${m.text}`);
|
|
58
|
-
const { channels, hasTimeTriggers, modelAuth, authPath, container, port, extraSecrets } = pre;
|
|
66
|
+
const { channels, routeChannels, longConnectionChannels, hasTimeTriggers, modelAuth, authPath, container, port, extraSecrets, } = pre;
|
|
67
|
+
const hasDeclaredChannels = routeChannels.length + longConnectionChannels.length > 0;
|
|
59
68
|
// Docker: one app service + loopback port + state volume. `--tunnel` shapes the generated topology
|
|
60
69
|
// with an optional Quick Tunnel service; `--run` alone decides whether Docker receives side effects.
|
|
61
70
|
if (host === "docker") {
|
|
@@ -66,8 +75,20 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
66
75
|
console.error(`[fastagent] warn: --into-linked is Railway-only — ignored for local Docker`);
|
|
67
76
|
}
|
|
68
77
|
const projectName = toDockerProjectName(basename(target));
|
|
69
|
-
const dockerPlan = (tunnel) => planDockerDeploy({
|
|
70
|
-
|
|
78
|
+
const dockerPlan = (tunnel) => planDockerDeploy({
|
|
79
|
+
projectName,
|
|
80
|
+
port,
|
|
81
|
+
modelAuth,
|
|
82
|
+
channels,
|
|
83
|
+
longConnectionChannels,
|
|
84
|
+
tunnel,
|
|
85
|
+
extraSecrets,
|
|
86
|
+
...container,
|
|
87
|
+
});
|
|
88
|
+
const requestedTunnel = !!opts.tunnel && (!hasDeclaredChannels || routeChannels.length > 0);
|
|
89
|
+
if (opts.tunnel && hasDeclaredChannels && routeChannels.length === 0) {
|
|
90
|
+
console.error(`[fastagent] note: --tunnel skipped — every channel uses a long connection`);
|
|
91
|
+
}
|
|
71
92
|
let plan = dockerPlan(requestedTunnel);
|
|
72
93
|
// An existing Compose file is authoritative: shape its comparison/runbook from the topology on disk,
|
|
73
94
|
// regardless of the current flag. `--force` is the explicit reset to the requested generated shape.
|
|
@@ -85,13 +106,13 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
85
106
|
if (opts.run) {
|
|
86
107
|
return runDeployDocker({
|
|
87
108
|
target,
|
|
88
|
-
agentDir,
|
|
89
109
|
composeFile: plan.composePath,
|
|
90
110
|
port,
|
|
91
111
|
requireTunnel: requestedTunnel,
|
|
92
112
|
modelAuth,
|
|
93
113
|
authPath,
|
|
94
114
|
channels,
|
|
115
|
+
longConnectionChannels,
|
|
95
116
|
extraSecrets,
|
|
96
117
|
});
|
|
97
118
|
}
|
|
@@ -114,7 +135,15 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
114
135
|
const serviceName = basename(target)
|
|
115
136
|
.replace(/[^a-zA-Z0-9-]+/g, "-")
|
|
116
137
|
.replace(/^-+|-+$/g, "") || "agent";
|
|
117
|
-
const plan = planRailwayDeploy({
|
|
138
|
+
const plan = planRailwayDeploy({
|
|
139
|
+
serviceName,
|
|
140
|
+
modelAuth,
|
|
141
|
+
channels,
|
|
142
|
+
longConnectionChannels,
|
|
143
|
+
extraSecrets,
|
|
144
|
+
hasTimeTriggers,
|
|
145
|
+
...container,
|
|
146
|
+
});
|
|
118
147
|
await writeArtifacts(target, plan.artifacts, {
|
|
119
148
|
force: !!opts.force,
|
|
120
149
|
neverForce: container.kitDir ? [".dockerignore"] : [],
|
|
@@ -126,6 +155,7 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
126
155
|
modelAuth,
|
|
127
156
|
authPath,
|
|
128
157
|
channels,
|
|
158
|
+
longConnectionChannels,
|
|
129
159
|
extraSecrets,
|
|
130
160
|
intoLinked: !!opts.intoLinked,
|
|
131
161
|
});
|
|
@@ -171,14 +201,16 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
171
201
|
// Under `--run` this is a GATE (same discipline as the model-travel gate): a full deploy whose schedules
|
|
172
202
|
// silently never fire is worse than a crash-loop — nothing fails visibly when a cron instant passes on a
|
|
173
203
|
// sleeping machine, and unlike github's min=0 there is no legitimate trade to accept here.
|
|
174
|
-
if (flyTomlExists && !opts.force && hasTimeTriggers) {
|
|
204
|
+
if (flyTomlExists && !opts.force && (hasTimeTriggers || longConnectionChannels.length > 0)) {
|
|
175
205
|
const min = parseFlyMinMachines(await readFile(flyTomlPath, "utf8"));
|
|
176
206
|
if ((min ?? 0) === 0) {
|
|
177
207
|
// undefined = the line is absent — Fly's platform default for min_machines_running is 0, so a
|
|
178
208
|
// hand-written fly.toml without the line scales to zero exactly like an explicit 0.
|
|
209
|
+
const reason = hasTimeTriggers
|
|
210
|
+
? `schedules/self-scheduling need a running machine (no external wake-up)`
|
|
211
|
+
: `long-connection channel (${longConnectionChannels.join(", ")}) needs an always-on outbound connection`;
|
|
179
212
|
const msg = `your kept fly.toml scales to zero (min_machines_running = ${min ?? "absent → platform default 0"}), but ` +
|
|
180
|
-
|
|
181
|
-
`or pass --force to regenerate.`;
|
|
213
|
+
`${reason}. Set min_machines_running = 1, or pass --force to regenerate.`;
|
|
182
214
|
if (opts.run)
|
|
183
215
|
failStartup(new Error(`deploy stopped: ${msg}`));
|
|
184
216
|
console.error(`[fastagent] warn: ${msg}`);
|
|
@@ -189,6 +221,7 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
189
221
|
port,
|
|
190
222
|
modelAuth,
|
|
191
223
|
channels,
|
|
224
|
+
longConnectionChannels,
|
|
192
225
|
extraSecrets,
|
|
193
226
|
hasTimeTriggers,
|
|
194
227
|
...container,
|
|
@@ -199,8 +232,18 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
199
232
|
force: !!opts.force,
|
|
200
233
|
neverForce: container.kitDir ? [".dockerignore"] : [],
|
|
201
234
|
});
|
|
202
|
-
if (opts.run)
|
|
203
|
-
return runDeployFly({
|
|
235
|
+
if (opts.run) {
|
|
236
|
+
return runDeployFly({
|
|
237
|
+
target,
|
|
238
|
+
appName,
|
|
239
|
+
modelAuth,
|
|
240
|
+
authPath,
|
|
241
|
+
channels,
|
|
242
|
+
longConnectionChannels,
|
|
243
|
+
flyTomlPath,
|
|
244
|
+
extraSecrets,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
204
247
|
console.log(plan.runbook.join("\n"));
|
|
205
248
|
}
|
|
206
249
|
/**
|
|
@@ -238,19 +281,26 @@ async function writeArtifacts(target, artifacts, options) {
|
|
|
238
281
|
console.error(`[fastagent] wrote ${a.path}`);
|
|
239
282
|
}
|
|
240
283
|
}
|
|
284
|
+
function deployEnvironment(target, channels) {
|
|
285
|
+
if (!channels.includes("slack"))
|
|
286
|
+
return process.env;
|
|
287
|
+
const latest = readSlackBotAuthEnv(join(resolveStateRoot(target), "channels", "slack", "bot-auth.json"));
|
|
288
|
+
return { ...process.env, ...latest };
|
|
289
|
+
}
|
|
241
290
|
/**
|
|
242
291
|
* `deploy docker --run`: carry local credentials into Compose's child environment, then reconcile the
|
|
243
292
|
* user-owned local topology. Docker owns container/network/volume lifecycle. A Compose tunnel service,
|
|
244
293
|
* when present, yields an ephemeral URL that reuses the same webhook announcer as `dev --tunnel`.
|
|
245
294
|
*/
|
|
246
295
|
async function runDeployDocker(params) {
|
|
247
|
-
const { target,
|
|
296
|
+
const { target, composeFile, port, requireTunnel, modelAuth, authPath, channels, longConnectionChannels, extraSecrets, } = params;
|
|
248
297
|
const { secrets, missingSecrets, needsModelCredential } = assembleSecrets({
|
|
249
298
|
modelAuth,
|
|
250
299
|
authFile: (await exists(authPath)) ? await readFile(authPath) : undefined,
|
|
251
300
|
channels,
|
|
301
|
+
longConnectionChannels,
|
|
252
302
|
extraSecrets,
|
|
253
|
-
env:
|
|
303
|
+
env: deployEnvironment(target, channels),
|
|
254
304
|
});
|
|
255
305
|
const outcome = await deployDockerRun({ composeFile, port, secrets, missingSecrets, needsModelCredential, requireTunnel }, spawnRunner("docker", target), (message) => console.error(`[fastagent] ${message}`));
|
|
256
306
|
if (!outcome.ok)
|
|
@@ -263,12 +313,16 @@ async function runDeployDocker(params) {
|
|
|
263
313
|
// Docker Desktop commonly injects a host proxy. The Quick Tunnel hostname may be resolvable only
|
|
264
314
|
// through it, exactly like provider/channel APIs; use the same Node dispatcher as dev/start/login.
|
|
265
315
|
installProxyFetch();
|
|
266
|
-
await announceWebhooks(
|
|
316
|
+
await announceWebhooks(target, outcome.tunnelUrl, {
|
|
317
|
+
openUrl: openExternalUrl,
|
|
318
|
+
routeChannels: channels.filter((kind) => !longConnectionChannels.includes(kind)),
|
|
319
|
+
stateRoot: resolveStateRoot(target),
|
|
320
|
+
});
|
|
267
321
|
console.error(`[fastagent] note: Quick Tunnel URLs are ephemeral — after the tunnel container/Docker daemon ` +
|
|
268
322
|
`restarts, re-run this deploy so webhooks receive the new URL`);
|
|
269
323
|
return;
|
|
270
324
|
}
|
|
271
|
-
const paths = dockerWebhookPaths(channels);
|
|
325
|
+
const paths = dockerWebhookPaths(channels.filter((kind) => !longConnectionChannels.includes(kind)));
|
|
272
326
|
if (paths.length > 0) {
|
|
273
327
|
console.error(`[fastagent] note: public ingress is operator-owned — configure your tunnel/proxy, then wire the ` +
|
|
274
328
|
`default webhook path(s): ${paths.join(", ")} (or your remapped channel routes)`);
|
|
@@ -282,7 +336,7 @@ async function runDeployDocker(params) {
|
|
|
282
336
|
* behind the shared {@link spawnRunner} seam (spawned `fly`, cwd = the workspace so the build context is the agent).
|
|
283
337
|
*/
|
|
284
338
|
async function runDeployFly(params) {
|
|
285
|
-
const { target, appName, modelAuth, authPath, channels, flyTomlPath, extraSecrets } = params;
|
|
339
|
+
const { target, appName, modelAuth, authPath, channels, longConnectionChannels, flyTomlPath, extraSecrets } = params;
|
|
286
340
|
const fly = spawnRunner("fly", target);
|
|
287
341
|
// Fail fast if flyctl is absent (spawn ENOENT → 127), with the install link — not a confusing auth gate.
|
|
288
342
|
if ((await fly(["version"], { capture: true })).code === 127) {
|
|
@@ -293,15 +347,16 @@ async function runDeployFly(params) {
|
|
|
293
347
|
modelAuth,
|
|
294
348
|
authFile: (await exists(authPath)) ? await readFile(authPath) : undefined,
|
|
295
349
|
channels,
|
|
350
|
+
longConnectionChannels,
|
|
296
351
|
extraSecrets,
|
|
297
|
-
env:
|
|
352
|
+
env: deployEnvironment(target, channels),
|
|
298
353
|
});
|
|
299
354
|
// Model credential has its OWN remediation (login), distinct from a missing secret's (.env) — gate it
|
|
300
355
|
// here, not through missingSecrets, so the message isn't a contradictory mash of both.
|
|
301
356
|
if (needsModelCredential) {
|
|
302
357
|
failStartup(new Error(`deploy stopped: no model credential — run \`fastagent login\`, or set a provider API key in .env, then re-run`));
|
|
303
358
|
}
|
|
304
|
-
const outcome = await deployFlyRun({ appName, region, secrets, missingSecrets, channels, flyConfig: "fly.toml" }, fly, (m) => console.error(`[fastagent] ${m}`), (baseUrl) => registerTelegramWebhook(baseUrl), (baseUrl, kind) => registerFeishuWebhook(baseUrl, kind));
|
|
359
|
+
const outcome = await deployFlyRun({ appName, region, secrets, missingSecrets, channels, longConnectionChannels, flyConfig: "fly.toml" }, fly, (m) => console.error(`[fastagent] ${m}`), (baseUrl) => registerTelegramWebhook(baseUrl), (baseUrl, kind) => registerFeishuWebhook(baseUrl, kind), (baseUrl) => registerSlackWebhook(baseUrl, { stateRoot: resolveStateRoot(target) }));
|
|
305
360
|
if (!outcome.ok)
|
|
306
361
|
failStartup(new Error(`deploy stopped: ${outcome.gate}`));
|
|
307
362
|
console.error(`[fastagent] deployed → https://${appName}.fly.dev`);
|
|
@@ -314,7 +369,7 @@ async function runDeployFly(params) {
|
|
|
314
369
|
* webhook) lives in {@link deployRailwayRun}; see there for why Railway differs from Fly.
|
|
315
370
|
*/
|
|
316
371
|
async function runDeployRailway(params) {
|
|
317
|
-
const { target, name, modelAuth, authPath, channels, extraSecrets, intoLinked } = params;
|
|
372
|
+
const { target, name, modelAuth, authPath, channels, longConnectionChannels, extraSecrets, intoLinked } = params;
|
|
318
373
|
const railway = spawnRunner("railway", target);
|
|
319
374
|
// Fail fast if the railway CLI is absent (spawn ENOENT → 127), with the install link.
|
|
320
375
|
if ((await railway(["--version"], { capture: true })).code === 127) {
|
|
@@ -324,14 +379,15 @@ async function runDeployRailway(params) {
|
|
|
324
379
|
modelAuth,
|
|
325
380
|
authFile: (await exists(authPath)) ? await readFile(authPath) : undefined,
|
|
326
381
|
channels,
|
|
382
|
+
longConnectionChannels,
|
|
327
383
|
extraSecrets,
|
|
328
|
-
env:
|
|
384
|
+
env: deployEnvironment(target, channels),
|
|
329
385
|
});
|
|
330
386
|
// Model credential has its OWN remediation (login), distinct from a missing secret's (.env).
|
|
331
387
|
if (needsModelCredential) {
|
|
332
388
|
failStartup(new Error(`deploy stopped: no model credential — run \`fastagent login\`, or set a provider API key in .env, then re-run`));
|
|
333
389
|
}
|
|
334
|
-
const outcome = await deployRailwayRun({ name, mountPath: "/data", secrets, missingSecrets, channels, intoLinked }, railway, (m) => console.error(`[fastagent] ${m}`), (baseUrl) => registerTelegramWebhook(baseUrl), (baseUrl, kind) => registerFeishuWebhook(baseUrl, kind));
|
|
390
|
+
const outcome = await deployRailwayRun({ name, mountPath: "/data", secrets, missingSecrets, channels, longConnectionChannels, intoLinked }, railway, (m) => console.error(`[fastagent] ${m}`), (baseUrl) => registerTelegramWebhook(baseUrl), (baseUrl, kind) => registerFeishuWebhook(baseUrl, kind), (baseUrl) => registerSlackWebhook(baseUrl, { stateRoot: resolveStateRoot(target) }));
|
|
335
391
|
if (!outcome.ok)
|
|
336
392
|
failStartup(new Error(`deploy stopped: ${outcome.gate}`));
|
|
337
393
|
console.error(`[fastagent] deployed → ${outcome.url}`);
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { runDevSupervisor } from "../../dev-supervisor.js";
|
|
8
8
|
import { loadDotEnv } from "../../env.js";
|
|
9
|
-
import { resolveAuthPathOverride } from "../../engines/pi/config.js";
|
|
10
9
|
import { reportDefinitionWarnings, reportModuleLoadFailures, reportToolCollisions } from "../../engines/pi/report.js";
|
|
11
10
|
import { createPiAgentFromWorkspace } from "../../engines/pi/workspace.js";
|
|
12
11
|
import { log, setLogLevel } from "../../log.js";
|
|
13
12
|
import { logAgentLoop } from "../../observe.js";
|
|
14
13
|
import { installProxyFetch } from "../../proxy.js";
|
|
15
14
|
import { failStartup } from "../fail.js";
|
|
16
|
-
import { maybeTunnel, routesFor, serve, startSchedules } from "../serve.js";
|
|
15
|
+
import { maybeTunnel, mountSessionControl, routesFor, serve, startSchedules } from "../serve.js";
|
|
17
16
|
import { parsePort, reportAuth, resolveFirstRunModel } from "../shared.js";
|
|
18
17
|
export async function runDev(dirArg, opts) {
|
|
19
18
|
const dir = resolve(dirArg);
|
|
@@ -42,7 +41,7 @@ async function serveOnce(dir, opts) {
|
|
|
42
41
|
installProxyFetch();
|
|
43
42
|
const a = await createPiAgentFromWorkspace(dir, {
|
|
44
43
|
model: opts.model,
|
|
45
|
-
authPath:
|
|
44
|
+
authPath: opts.authPath, // flag > FASTAGENT_AUTH_PATH > default — resolved by the opener (one owner)
|
|
46
45
|
serving: true, // long-running serve: the scheduler poller runs (wake mounts iff config.selfSchedule)
|
|
47
46
|
}).catch(failStartup);
|
|
48
47
|
log.info(`[fastagent] dir: ${dir}`);
|
|
@@ -55,9 +54,16 @@ async function serveOnce(dir, opts) {
|
|
|
55
54
|
// Trace each turn's agent loop (tool calls + reply) to the log at debug level — shown in dev, gated
|
|
56
55
|
// out in start (level info), keeping end-user content out of production logs. Wired in both postures.
|
|
57
56
|
const traced = logAgentLoop(a.agent);
|
|
58
|
-
const
|
|
57
|
+
const routed = await routesFor(a.agentDir, traced, a.stateRoot, a.sessionControl).catch(failStartup);
|
|
58
|
+
const withControl = mountSessionControl(routed.routes, a.sessionControl, a.stateRoot, {
|
|
59
|
+
tunnel: opts.tunnel ?? false,
|
|
60
|
+
agent: traced, // the remote data plane (POST /control/invoke) drives the SAME traced agent
|
|
61
|
+
});
|
|
59
62
|
await startSchedules(a.agentDir, traced, a.stateRoot, a.config.selfSchedule ?? false);
|
|
60
|
-
serve(routes, portFlag ?? a.config.http?.port ?? 8787, (p) =>
|
|
63
|
+
serve({ ...routed, routes: withControl.routes }, portFlag ?? a.config.http?.port ?? 8787, (p) => {
|
|
64
|
+
withControl.announce(p);
|
|
65
|
+
maybeTunnel(dir, routed.routeChannels, p, opts.tunnel ?? false, a.stateRoot);
|
|
66
|
+
});
|
|
61
67
|
}
|
|
62
68
|
/** The agents/skills/tools/collisions report lines. */
|
|
63
69
|
function reportAgentsSkillsTools(a) {
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { relative, resolve, sep } from "node:path";
|
|
8
8
|
import { loadDotEnv } from "../../env.js";
|
|
9
|
-
import { loadConfig, resolveAgentDir
|
|
9
|
+
import { loadConfig, resolveAgentDir } from "../../engines/pi/config.js";
|
|
10
10
|
import { reportModuleLoadFailures } from "../../engines/pi/report.js";
|
|
11
11
|
import { createPiAgentFromWorkspace } from "../../engines/pi/workspace.js";
|
|
12
|
-
import { runInvokeStream } from "
|
|
12
|
+
import { runInvokeStream } from "../invoke-stream.js";
|
|
13
13
|
import { installProxyFetch } from "../../proxy.js";
|
|
14
14
|
import { loadSchedules } from "../../schedule/discover.js";
|
|
15
15
|
import { scheduleSession } from "../../schedule/scheduler.js";
|
|
@@ -35,7 +35,7 @@ export async function runFire(name, dirArg, opts) {
|
|
|
35
35
|
}
|
|
36
36
|
const { agent, modelSpec, authPath } = await createPiAgentFromWorkspace(fireDir, {
|
|
37
37
|
model: opts.model,
|
|
38
|
-
authPath:
|
|
38
|
+
authPath: opts.authPath, // flag > FASTAGENT_AUTH_PATH > default — resolved by the opener (one owner)
|
|
39
39
|
}).catch(failStartup);
|
|
40
40
|
console.error(`[fastagent] fire: ${name} (${modelSpec})`);
|
|
41
41
|
await reportAuth(modelSpec, authPath);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { loadDotEnv } from "../../env.js";
|
|
4
4
|
import { discoverChannelFiles } from "../../engines/pi/channel.js";
|
|
5
|
-
import {
|
|
5
|
+
import { defaultSessionsDir, loadConfig, resolveAgentDir, resolveAuthPath, resolveModelSpec, resolveSessionsDirOverride, resolveStateRoot, } from "../../engines/pi/config.js";
|
|
6
6
|
import { resolveWorkspaceTools } from "../../engines/pi/create.js";
|
|
7
7
|
import { loadAgentDefinition } from "../../engines/pi/definition.js";
|
|
8
8
|
import { reportDefinitionWarnings, reportModuleLoadFailures, reportToolCollisions } from "../../engines/pi/report.js";
|
|
@@ -52,7 +52,7 @@ export async function runInfo(dirArg, opts) {
|
|
|
52
52
|
// create them, info must not).
|
|
53
53
|
const stateRoot = resolveStateRoot(dir);
|
|
54
54
|
const sessionsDir = resolveSessionsDirOverride(opts.sessionsDir) ?? defaultSessionsDir(stateRoot);
|
|
55
|
-
const authPath =
|
|
55
|
+
const authPath = resolveAuthPath(dir, opts.authPath); // flag > FASTAGENT_AUTH_PATH > default — the one owner
|
|
56
56
|
if (opts.json) {
|
|
57
57
|
console.log(JSON.stringify({
|
|
58
58
|
dir,
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { loadDotEnv } from "../../env.js";
|
|
5
|
-
import { resolveAuthPathOverride } from "../../engines/pi/config.js";
|
|
6
5
|
import { createPiAgentFromWorkspace } from "../../engines/pi/workspace.js";
|
|
7
|
-
import { runInvokeStream } from "
|
|
6
|
+
import { runInvokeStream } from "../invoke-stream.js";
|
|
8
7
|
import { installProxyFetch } from "../../proxy.js";
|
|
9
8
|
import { failStartup } from "../fail.js";
|
|
10
9
|
import { reportAuth, resolveFirstRunModel } from "../shared.js";
|
|
@@ -15,7 +14,7 @@ export async function runInvoke(message, dirArg, opts) {
|
|
|
15
14
|
await resolveFirstRunModel(invokeDir, opts);
|
|
16
15
|
const { agent, modelSpec, authPath } = await createPiAgentFromWorkspace(invokeDir, {
|
|
17
16
|
model: opts.model,
|
|
18
|
-
authPath:
|
|
17
|
+
authPath: opts.authPath, // flag > FASTAGENT_AUTH_PATH > default — resolved by the opener (one owner)
|
|
19
18
|
}).catch(failStartup);
|
|
20
19
|
console.error(`[fastagent] invoke: ${invokeDir} (${modelSpec})`);
|
|
21
20
|
await reportAuth(modelSpec, authPath);
|
|
@@ -8,21 +8,19 @@
|
|
|
8
8
|
* so the secret can never land untracked — a flow that then fails (bad provider, abort) leaves that
|
|
9
9
|
* empty state dir behind, by design (no secret without its `.gitignore`). Skipped for the HOME-global dir.
|
|
10
10
|
*/
|
|
11
|
-
import { autocomplete, isCancel, log as clackLog, password, select, text as clackText } from "@clack/prompts";
|
|
12
11
|
import { loadDotEnv } from "../../env.js";
|
|
13
|
-
import {
|
|
12
|
+
import { resolveAuthPath, resolveStateRoot } from "../../engines/pi/config.js";
|
|
14
13
|
import { ensureStateRootSelfIgnored, isUnderDir } from "../../engines/pi/definition.js";
|
|
15
|
-
import {
|
|
16
|
-
import { openExternalUrl } from "../../open-url.js";
|
|
14
|
+
import { LoginCancelled } from "../../engines/pi/login.js";
|
|
17
15
|
import { installProxyFetch } from "../../proxy.js";
|
|
18
16
|
import { failStartup } from "../fail.js";
|
|
19
|
-
import { isInteractive } from "../shared.js";
|
|
17
|
+
import { isInteractive, loginWithKeyCheck } from "../shared.js";
|
|
20
18
|
export async function runLogin(provider, opts) {
|
|
21
19
|
const loginDir = process.cwd();
|
|
22
20
|
loadDotEnv(loginDir); // FASTAGENT_AUTH_PATH / a proxy (HTTPS_PROXY) may be configured in the project .env
|
|
23
21
|
installProxyFetch(); // the OAuth token exchange must go through HTTPS_PROXY (region-locked providers)
|
|
24
22
|
const stateRoot = resolveStateRoot(loginDir);
|
|
25
|
-
const authPath =
|
|
23
|
+
const authPath = resolveAuthPath(loginDir, opts.authPath); // flag > FASTAGENT_AUTH_PATH > default — the one owner
|
|
26
24
|
// login is the command that CREATES the credential file, so the leak guard binds HERE too (not only
|
|
27
25
|
// in the opener): on an adapted project dir, a `login` before the first dev/start would otherwise
|
|
28
26
|
// leave the secret untracked-but-committable. Unlike the opener (which populates the WHOLE root, so
|
|
@@ -39,25 +37,16 @@ export async function runLogin(provider, opts) {
|
|
|
39
37
|
if (opts.input === false || !isInteractive()) {
|
|
40
38
|
failStartup(new Error(`login is interactive (it shows a menu and opens a browser) — run it in a terminal, not a pipe/CI`));
|
|
41
39
|
}
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
// loginWithKeyCheck: an entered API key is verified with one minimal request; a rejected key (401)
|
|
41
|
+
// re-prompts in place, so a returned result is always a stored-and-not-definitively-bad credential.
|
|
42
|
+
const result = await loginWithKeyCheck(provider, authPath).catch((error) => {
|
|
43
|
+
if (error instanceof LoginCancelled) {
|
|
44
|
+
// A decision, not a failure — neutral wording; non-zero exit because no credential was stored.
|
|
45
|
+
console.error(`[fastagent] login cancelled`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
failStartup(error);
|
|
49
|
+
});
|
|
44
50
|
console.error(`[fastagent] logged in to ${result.provider} (${result.method}) — saved to ${authPath}`);
|
|
45
51
|
process.exit(0); // the undici proxy agent's keep-alive sockets would otherwise hold the event loop open
|
|
46
52
|
}
|
|
47
|
-
/** Login terminal IO via @clack/prompts: a searchable list once long, a hidden prompt for keys. */
|
|
48
|
-
function terminalLoginIO() {
|
|
49
|
-
return {
|
|
50
|
-
async select(message, options) {
|
|
51
|
-
const r = await (options.length > 7 ? autocomplete : select)({ message, options });
|
|
52
|
-
return isCancel(r) ? undefined : r;
|
|
53
|
-
},
|
|
54
|
-
async prompt(message, opts) {
|
|
55
|
-
const r = opts?.hidden
|
|
56
|
-
? await password({ message, signal: opts.signal })
|
|
57
|
-
: await clackText({ message, signal: opts?.signal });
|
|
58
|
-
return isCancel(r) ? undefined : r;
|
|
59
|
-
},
|
|
60
|
-
note: (message) => clackLog.info(message),
|
|
61
|
-
openUrl: openExternalUrl,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* substring. This module pulls the pi model catalog (heavy) — it is lazy-imported by the spec, so
|
|
4
4
|
* only an actual `models` invocation pays for it.
|
|
5
5
|
*/
|
|
6
|
-
import { formatModelsCommand } from "
|
|
6
|
+
import { formatModelsCommand } from "../models-view.js";
|
|
7
7
|
import { listModels } from "../../engines/pi/config.js";
|
|
8
8
|
import { createPiModels } from "../../engines/pi/models.js";
|
|
9
9
|
export function runModels(search) {
|
|
@@ -6,7 +6,7 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
6
6
|
import { dirname, resolve } from "node:path";
|
|
7
7
|
import { authSeedBytes } from "../../deploy/fly/run.js";
|
|
8
8
|
import { loadDotEnv } from "../../env.js";
|
|
9
|
-
import {
|
|
9
|
+
import { resolveAuthPath, resolveSessionsDirOverride } from "../../engines/pi/config.js";
|
|
10
10
|
import { isUnderDir } from "../../engines/pi/definition.js";
|
|
11
11
|
import { reportDefinitionWarnings, reportModuleLoadFailures, reportToolCollisions } from "../../engines/pi/report.js";
|
|
12
12
|
import { createPiAgentFromWorkspace } from "../../engines/pi/workspace.js";
|
|
@@ -15,7 +15,7 @@ import { logAgentLoop } from "../../observe.js";
|
|
|
15
15
|
import { installProxyFetch } from "../../proxy.js";
|
|
16
16
|
import { exists } from "../../scaffold/init.js";
|
|
17
17
|
import { failStartup } from "../fail.js";
|
|
18
|
-
import { maybeTunnel, routesFor, serve, startSchedules } from "../serve.js";
|
|
18
|
+
import { maybeTunnel, mountSessionControl, routesFor, serve, startSchedules } from "../serve.js";
|
|
19
19
|
import { parsePort, reportAuth, resolveFirstRunModel } from "../shared.js";
|
|
20
20
|
export async function runStart(dirArg, opts) {
|
|
21
21
|
const dir = resolve(dirArg);
|
|
@@ -26,14 +26,14 @@ export async function runStart(dirArg, opts) {
|
|
|
26
26
|
await resolveFirstRunModel(dir, opts);
|
|
27
27
|
// A `deploy --run` may carry the operator's local credential as FASTAGENT_AUTH_SEED —
|
|
28
28
|
// materialize it onto the writable state root BEFORE the opener resolves auth (once, absent-only).
|
|
29
|
-
|
|
30
|
-
await maybeSeedAuth(
|
|
29
|
+
// Same resolveAuthPath the opener uses — ONE owner of the flag > env > default chain.
|
|
30
|
+
await maybeSeedAuth(resolveAuthPath(dir, opts.authPath));
|
|
31
31
|
// The same opener dev uses (single assembly source), just no watch.
|
|
32
32
|
const sessionsDirOverride = resolveSessionsDirOverride(opts.sessionsDir);
|
|
33
|
-
const { agent, definition, agentDir, config, modelSpec, stateRoot, sessionsDir, authPath, toolNames, deferredToolNames, toolCollisions, toolFailures, } = await createPiAgentFromWorkspace(dir, {
|
|
33
|
+
const { agent, definition, agentDir, config, modelSpec, stateRoot, sessionsDir, authPath, toolNames, deferredToolNames, toolCollisions, toolFailures, sessionControl, } = await createPiAgentFromWorkspace(dir, {
|
|
34
34
|
model: opts.model,
|
|
35
35
|
sessionsDir: sessionsDirOverride,
|
|
36
|
-
authPath:
|
|
36
|
+
authPath: opts.authPath,
|
|
37
37
|
serving: true, // long-running serve: the scheduler poller runs (wake mounts iff config.selfSchedule)
|
|
38
38
|
}).catch(failStartup);
|
|
39
39
|
log.info(`[fastagent] start: ${dir}`);
|
|
@@ -66,9 +66,16 @@ export async function runStart(dirArg, opts) {
|
|
|
66
66
|
reportDefinitionWarnings(definition.collisions, definition.diagnostics);
|
|
67
67
|
// Same debug turn trace as dev; gated out here by the info level (see dev.ts serveOnce).
|
|
68
68
|
const traced = logAgentLoop(agent);
|
|
69
|
-
const
|
|
69
|
+
const routed = await routesFor(agentDir, traced, stateRoot, sessionControl).catch(failStartup);
|
|
70
|
+
const withControl = mountSessionControl(routed.routes, sessionControl, stateRoot, {
|
|
71
|
+
tunnel: opts.tunnel ?? false,
|
|
72
|
+
agent: traced,
|
|
73
|
+
});
|
|
70
74
|
await startSchedules(agentDir, traced, stateRoot, config.selfSchedule ?? false);
|
|
71
|
-
serve(routes, portFlag ?? parsePort(process.env.PORT, "PORT env", "env") ?? config.http?.port ?? 8787, (p) =>
|
|
75
|
+
serve({ ...routed, routes: withControl.routes }, portFlag ?? parsePort(process.env.PORT, "PORT env", "env") ?? config.http?.port ?? 8787, (p) => {
|
|
76
|
+
withControl.announce(p);
|
|
77
|
+
maybeTunnel(dir, routed.routeChannels, p, opts.tunnel ?? false, stateRoot);
|
|
78
|
+
});
|
|
72
79
|
// No graceful drain: webhook turns run fire-and-forget; SIGTERM just exits mid-turn. Whether an
|
|
73
80
|
// in-flight turn is LOST depends on the channel: the Telegram channel persists turn intent pre-ACK
|
|
74
81
|
// and replays it next start (turn-store.ts, L1 durable execution, at-least-once); HTTP and other
|
|
@@ -4,6 +4,7 @@ import { loadDotEnv } from "../../env.js";
|
|
|
4
4
|
import { loadConfig, resolveAgentDir } from "../../engines/pi/config.js";
|
|
5
5
|
import { resolveWorkspaceTools } from "../../engines/pi/create.js";
|
|
6
6
|
import { reportModuleLoadFailures } from "../../engines/pi/report.js";
|
|
7
|
+
import { turnContext } from "../../engines/pi/tool-context.js";
|
|
7
8
|
import { failStartup, failUsage } from "../fail.js";
|
|
8
9
|
export async function runTool(name, argsJson, dirArg) {
|
|
9
10
|
const toolDir = resolve(dirArg);
|
|
@@ -29,7 +30,7 @@ export async function runTool(name, argsJson, dirArg) {
|
|
|
29
30
|
catch {
|
|
30
31
|
failUsage(`invalid JSON args: ${argsJson}`); // malformed input syntax = usage error, exit 2
|
|
31
32
|
}
|
|
32
|
-
const result = await tool.execute(`cli-${name}`, args).catch(failStartup);
|
|
33
|
+
const result = await turnContext.run({ cwd: toolDir }, () => tool.execute(`cli-${name}`, args)).catch(failStartup);
|
|
33
34
|
const out = result?.details !== undefined
|
|
34
35
|
? result.details
|
|
35
36
|
: (result?.content ?? []).map((c) => ("text" in c ? c.text : "")).join("");
|
package/dist/cli/fail.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/** stderr renders color: a color TTY, with Node's `hasColors()` carrying the NO_COLOR/TERM=dumb veto. */
|
|
2
|
-
export declare function stderrHasColors(): boolean;
|
|
3
1
|
/**
|
|
4
2
|
* The ONE error prefix every error message carries — bold red when stderr renders color, plain
|
|
5
3
|
* otherwise. Errors are the only place the CLI uses color at all.
|
package/dist/cli/fail.js
CHANGED
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
* An errored tool inside an otherwise-completed turn still surfaces (the diagnostic the operator
|
|
7
7
|
* needs); tool_ended carries no name, so it is remembered from the matching tool_started.
|
|
8
8
|
*/
|
|
9
|
-
import type { AgentEvent } from "
|
|
9
|
+
import type { AgentEvent } from "../agent.ts";
|
|
10
10
|
export declare function runInvokeStream(events: AsyncIterable<AgentEvent>, out: (text: string) => void, err: (line: string) => void): Promise<number>;
|
|
@@ -14,6 +14,10 @@ export async function runInvokeStream(events, out, err) {
|
|
|
14
14
|
if (event.isError)
|
|
15
15
|
err(`[tool] ${toolName.get(event.id) ?? event.id} failed`);
|
|
16
16
|
break;
|
|
17
|
+
case "retrying":
|
|
18
|
+
// Operator-facing: include the reason (channels show a neutral customer line instead).
|
|
19
|
+
err(`[fastagent] transient failure — retrying (${event.attempt}/${event.maxAttempts} in ${event.delayMs}ms): ${event.reason}`);
|
|
20
|
+
break;
|
|
17
21
|
case "failed":
|
|
18
22
|
err(`[fastagent] failed: ${event.details}${event.retryable ? " (retryable)" : ""}`);
|
|
19
23
|
exitCode = 1;
|
package/dist/cli/kernel.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { Command } from "commander";
|
|
15
15
|
/** One positional argument, in commander syntax: `<name>` required, `[dir]` optional. */
|
|
16
|
-
|
|
16
|
+
interface ArgSpec {
|
|
17
17
|
name: string;
|
|
18
18
|
description: string;
|
|
19
19
|
default?: string;
|
|
@@ -29,7 +29,7 @@ export interface FlagSpec {
|
|
|
29
29
|
/** Mutually exclusive with these {@link optionKey} values — validated at build time. */
|
|
30
30
|
conflicts?: string[];
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
interface ExampleSpec {
|
|
33
33
|
cmd: string;
|
|
34
34
|
note?: string;
|
|
35
35
|
}
|
|
@@ -87,3 +87,4 @@ export interface ProgramOptions {
|
|
|
87
87
|
export declare function optionKey(flags: string): string;
|
|
88
88
|
/** Build the commander program for `specs`. The CLI entry parses with it; tests inject the IO seams. */
|
|
89
89
|
export declare function buildProgram(specs: readonly CommandSpec[], options?: ProgramOptions): Command;
|
|
90
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ProviderAuthStatus } from "../engines/pi/models.ts";
|
|
2
|
+
/** One first-run picker entry (@clack/prompts option shape). */
|
|
3
|
+
export interface ModelPickerOption {
|
|
4
|
+
value: string;
|
|
5
|
+
label: string;
|
|
6
|
+
hint: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The first-run picker menu: the FULL model catalog, each spec annotated with its provider's auth
|
|
10
|
+
* status — ready first (usable now, with the credential source so "which account pays" is visible at
|
|
11
|
+
* the decision point), then the rest with their remedy. Order within each group preserves `specs`
|
|
12
|
+
* (sorted by the caller). A broken provider (expired/corrupt credential) is annotated, not dropped —
|
|
13
|
+
* fail visibly.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildModelPickerOptions(specs: string[], statuses: Map<string, ProviderAuthStatus>): ModelPickerOption[];
|
|
16
|
+
/** The output of `fastagent models [search]`: the spec `lines` to print to stdout (a case-insensitive
|
|
17
|
+
* substring filter; no search → all), and an stderr `error` diagnostic when a search matches nothing. */
|
|
18
|
+
export declare function formatModelsCommand(specs: string[], search?: string): {
|
|
19
|
+
lines: string[];
|
|
20
|
+
error?: string;
|
|
21
|
+
};
|