@fastagent-sh/fastagent 0.15.0 → 0.16.1
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 +14 -4
- package/dist/bind.d.ts +34 -0
- package/dist/bind.js +74 -0
- 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 +266 -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 +14 -1
- package/dist/cli/commands/deploy.js +330 -78
- package/dist/cli/commands/dev.d.ts +1 -0
- package/dist/cli/commands/dev.js +38 -25
- 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.d.ts +1 -0
- package/dist/cli/commands/start.js +101 -37
- package/dist/cli/commands/tool.js +30 -18
- package/dist/cli/fail.d.ts +17 -0
- package/dist/cli/fail.js +24 -0
- package/dist/cli/program.js +66 -36
- package/dist/cli/serve.d.ts +52 -8
- package/dist/cli/serve.js +133 -32
- package/dist/cli/shared.d.ts +21 -2
- package/dist/cli/shared.js +44 -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 +235 -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 +12 -48
- package/dist/engines/pi/config.js +29 -110
- package/dist/engines/pi/create.d.ts +32 -24
- package/dist/engines/pi/create.js +47 -23
- package/dist/engines/pi/definition.d.ts +7 -26
- package/dist/engines/pi/definition.js +8 -54
- package/dist/engines/pi/harness.d.ts +19 -5
- package/dist/engines/pi/harness.js +3 -5
- 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} +31 -25
- package/dist/engines/pi/{workspace.js → open.js} +27 -29
- package/dist/engines/pi/read-image.d.ts +4 -0
- package/dist/engines/pi/read-image.js +62 -0
- package/dist/engines/pi/search-tools.d.ts +6 -4
- package/dist/engines/pi/search-tools.js +3 -1
- package/dist/engines/pi/session-builder.d.ts +2 -2
- package/dist/engines/pi/session-builder.js +18 -13
- package/dist/engines/pi/tool.d.ts +13 -5
- package/dist/engines/pi/tool.js +4 -0
- package/dist/engines/pi/wake-tool.d.ts +3 -3
- package/dist/env.d.ts +16 -4
- package/dist/env.js +43 -5
- package/dist/host/node.d.ts +4 -2
- package/dist/host/node.js +2 -1
- 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 +2 -2
- 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 +7 -4
- 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/commands/dev.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `fastagent dev`: a SUPERVISOR that spawns a worker (this command with FASTAGENT_DEV_WORKER set) to
|
|
3
|
-
* assemble + serve, restarting it on
|
|
3
|
+
* assemble + serve, restarting it on agent edits. A fresh process per reload means what is served
|
|
4
4
|
* is always the latest code, including modules a tool/config imports.
|
|
5
5
|
*/
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { runDevSupervisor } from "../../dev-supervisor.js";
|
|
8
8
|
import { loadDotEnv } from "../../env.js";
|
|
9
9
|
import { reportDefinitionWarnings, reportModuleLoadFailures, reportToolCollisions } from "../../engines/pi/report.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { createPiAgentFromDir } from "../../engines/pi/open.js";
|
|
11
|
+
import { setLogLevel } from "../../log.js";
|
|
12
12
|
import { logAgentLoop } from "../../observe.js";
|
|
13
13
|
import { installProxyFetch } from "../../proxy.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
14
|
+
import { workspaceHint } from "../../paths.js";
|
|
15
|
+
import { bindAddress } from "../../bind.js";
|
|
16
|
+
import { failStartup, placementOrExit } from "../fail.js";
|
|
17
|
+
import { assertTunnelBindable, maybeTunnel, mountSessionControl, routesFor, serve, startSchedules } from "../serve.js";
|
|
18
|
+
import { parseBind, parsePort, reportAuth, reportLine, resolveFirstRunModel, reportWorkspaceHint } from "../shared.js";
|
|
17
19
|
export async function runDev(dirArg, opts) {
|
|
18
20
|
const dir = resolve(dirArg);
|
|
21
|
+
const placement = placementOrExit(dir);
|
|
19
22
|
setLogLevel("debug"); // dev posture: verbose, includes the debug turn trace (content) — supervisor and worker both
|
|
20
23
|
const isWorker = process.env.FASTAGENT_DEV_WORKER === "1";
|
|
21
24
|
// Pick a model interactively once, in the parent (both watch and --no-watch have a TTY); a spawned
|
|
@@ -23,58 +26,68 @@ export async function runDev(dirArg, opts) {
|
|
|
23
26
|
// the proxy FIRST (as invoke/start do): the picker reads FASTAGENT_MODEL and provider keys from
|
|
24
27
|
// .env, and getAuth's OAuth refresh must go through HTTPS_PROXY. The worker re-loads both in serveOnce.
|
|
25
28
|
if (!isWorker) {
|
|
26
|
-
loadDotEnv(
|
|
29
|
+
loadDotEnv(placement.agentDir);
|
|
27
30
|
installProxyFetch();
|
|
28
|
-
await resolveFirstRunModel(
|
|
31
|
+
await resolveFirstRunModel(placement.agentDir, opts);
|
|
29
32
|
}
|
|
30
33
|
if (isWorker || opts.watch === false) {
|
|
31
34
|
await serveOnce(dir, opts);
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
34
|
-
parsePort(opts.port, "--port", "flag"); // flag-shape
|
|
35
|
-
|
|
37
|
+
parsePort(opts.port, "--port", "flag"); // flag-shape checks before spawning
|
|
38
|
+
// The --bind/--tunnel conflict is decidable from flags alone: refuse it HERE, before a worker and a
|
|
39
|
+
// tunnel exist. The worker repeats the check because `http.host` can supply the address instead.
|
|
40
|
+
assertTunnelBindable(parseBind(opts.bind), opts.tunnel ?? false, "flag");
|
|
41
|
+
await runDevSupervisor(placement, { tunnel: opts.tunnel ?? false });
|
|
36
42
|
}
|
|
37
|
-
/** Assemble the
|
|
43
|
+
/** Assemble the agent and serve it once (the dev worker; also the --no-watch path). */
|
|
38
44
|
async function serveOnce(dir, opts) {
|
|
39
45
|
const portFlag = parsePort(opts.port, "--port", "flag");
|
|
40
|
-
|
|
46
|
+
const bindFlag = parseBind(opts.bind);
|
|
47
|
+
loadDotEnv(placementOrExit(dir).agentDir);
|
|
41
48
|
installProxyFetch();
|
|
42
|
-
const a = await
|
|
49
|
+
const a = await createPiAgentFromDir(dir, {
|
|
43
50
|
model: opts.model,
|
|
44
51
|
authPath: opts.authPath, // flag > FASTAGENT_AUTH_PATH > default — resolved by the opener (one owner)
|
|
45
52
|
serving: true, // long-running serve: the scheduler poller runs (wake mounts iff config.selfSchedule)
|
|
46
53
|
}).catch(failStartup);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
reportLine("agent", a.agentDir);
|
|
55
|
+
reportLine("workspace", a.workspace);
|
|
56
|
+
reportWorkspaceHint(workspaceHint(a));
|
|
57
|
+
reportLine("config", a.configPath ?? "(none)");
|
|
58
|
+
reportLine("model", `${a.modelSpec}${a.config.thinkingLevel ? ` (thinking: ${a.config.thinkingLevel})` : ""}`);
|
|
52
59
|
await reportAuth(a.modelSpec, a.authPath);
|
|
53
60
|
reportAgentsSkillsTools(a);
|
|
54
61
|
// Trace each turn's agent loop (tool calls + reply) to the log at debug level — shown in dev, gated
|
|
55
62
|
// out in start (level info), keeping end-user content out of production logs. Wired in both postures.
|
|
56
63
|
const traced = logAgentLoop(a.agent);
|
|
57
64
|
const routed = await routesFor(a.agentDir, traced, a.stateRoot, a.sessionControl).catch(failStartup);
|
|
65
|
+
// `http.host` enters here the way the flag enters `parseBind` — through `bindAddress`, so a
|
|
66
|
+
// configured `localhost` is an ADDRESS by the time anything binds, renders or dials it.
|
|
67
|
+
const configured = a.config.http?.host;
|
|
68
|
+
const host = bindFlag ?? (configured === undefined ? undefined : bindAddress(configured));
|
|
69
|
+
assertTunnelBindable(host, opts.tunnel ?? false, bindFlag ? "flag" : "config");
|
|
58
70
|
const withControl = mountSessionControl(routed.routes, a.sessionControl, a.stateRoot, {
|
|
59
71
|
tunnel: opts.tunnel ?? false,
|
|
60
72
|
agent: traced, // the remote data plane (POST /control/invoke) drives the SAME traced agent
|
|
73
|
+
host,
|
|
61
74
|
});
|
|
62
75
|
await startSchedules(a.agentDir, traced, a.stateRoot, a.config.selfSchedule ?? false);
|
|
63
|
-
serve({ ...routed, routes: withControl.routes }, portFlag ?? a.config.http?.port ?? 8787, (p) => {
|
|
76
|
+
serve({ ...routed, routes: withControl.routes }, { port: portFlag ?? a.config.http?.port ?? 8787, host }, (p) => {
|
|
64
77
|
withControl.announce(p);
|
|
65
|
-
maybeTunnel(
|
|
78
|
+
maybeTunnel(a.agentDir, routed.routeChannels, p, opts.tunnel ?? false, a.stateRoot);
|
|
66
79
|
});
|
|
67
80
|
}
|
|
68
81
|
/** The agents/skills/tools/collisions report lines. */
|
|
69
82
|
function reportAgentsSkillsTools(a) {
|
|
70
|
-
|
|
83
|
+
reportLine("context", a.definition.contextFiles.map((f) => f.path).join(", ") || "(none)");
|
|
71
84
|
if (a.definition.persona)
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
reportLine("persona", "persona.md");
|
|
86
|
+
reportLine("skills", a.definition.skills.map((s) => s.name).join(", ") || "(none)");
|
|
74
87
|
if (a.toolNames.length > 0)
|
|
75
|
-
|
|
88
|
+
reportLine("tools", a.toolNames.join(", "));
|
|
76
89
|
if (a.deferredToolNames.length > 0) {
|
|
77
|
-
|
|
90
|
+
reportLine("deferred", `${a.deferredToolNames.join(", ")} (activated via search_tools)`);
|
|
78
91
|
}
|
|
79
92
|
reportToolCollisions(a.toolCollisions);
|
|
80
93
|
reportModuleLoadFailures(a.toolFailures);
|
|
@@ -4,36 +4,35 @@
|
|
|
4
4
|
* the schedule's stable session (faithful to the served behavior). Does NOT advance the schedule's fire
|
|
5
5
|
* state — a test run must never make the scheduler skip the real next run.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
8
|
import { loadDotEnv } from "../../env.js";
|
|
9
|
-
import {
|
|
9
|
+
import { displayPath } from "../../paths.js";
|
|
10
10
|
import { reportModuleLoadFailures } from "../../engines/pi/report.js";
|
|
11
|
-
import {
|
|
11
|
+
import { createPiAgentFromDir } from "../../engines/pi/open.js";
|
|
12
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";
|
|
16
|
-
import { failStartup } from "../fail.js";
|
|
16
|
+
import { failStartup, placementOrExit } from "../fail.js";
|
|
17
17
|
import { reportAuth, resolveFirstRunModel } from "../shared.js";
|
|
18
18
|
export async function runFire(name, dirArg, opts) {
|
|
19
19
|
const fireDir = resolve(dirArg);
|
|
20
|
-
|
|
20
|
+
const placement = placementOrExit(fireDir);
|
|
21
|
+
loadDotEnv(placement.agentDir);
|
|
21
22
|
installProxyFetch();
|
|
22
|
-
await resolveFirstRunModel(
|
|
23
|
-
// Schedules are agent surface — discover them where dev/start/`schedule list` do (
|
|
24
|
-
//
|
|
25
|
-
const {
|
|
26
|
-
const fireAgentDir = resolveAgentDir(fireDir, fireConfig);
|
|
27
|
-
const { schedules, failures } = await loadSchedules(fireAgentDir).catch(failStartup);
|
|
23
|
+
await resolveFirstRunModel(placement.agentDir, opts);
|
|
24
|
+
// Schedules are agent surface — discover them where dev/start/`schedule list` do (the agent
|
|
25
|
+
// dir), so `fire` sees the same set the scheduler serves.
|
|
26
|
+
const { schedules, failures } = await loadSchedules(placement.agentDir).catch(failStartup);
|
|
28
27
|
reportModuleLoadFailures(failures);
|
|
29
28
|
const schedule = schedules.find((s) => s.name === name);
|
|
30
29
|
if (!schedule) {
|
|
31
|
-
// Name the discovery path
|
|
32
|
-
// "wrong place", not "broken file".
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
// Name the discovery path: a schedule misplaced in the workspace (outside the agent dir)
|
|
31
|
+
// should read as "wrong place", not "broken file".
|
|
32
|
+
failStartup(new Error(`unknown schedule "${name}" (looked in ${displayPath(process.cwd(), join(placement.agentDir, "schedules")) ?? "schedules"}). ` +
|
|
33
|
+
`available: ${schedules.map((s) => s.name).join(", ") || "(none)"}`));
|
|
35
34
|
}
|
|
36
|
-
const { agent, modelSpec, authPath } = await
|
|
35
|
+
const { agent, modelSpec, authPath } = await createPiAgentFromDir(fireDir, {
|
|
37
36
|
model: opts.model,
|
|
38
37
|
authPath: opts.authPath, // flag > FASTAGENT_AUTH_PATH > default — resolved by the opener (one owner)
|
|
39
38
|
}).catch(failStartup);
|
|
@@ -2,27 +2,29 @@
|
|
|
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 { defaultSessionsDir, loadConfig,
|
|
6
|
-
import {
|
|
5
|
+
import { defaultSessionsDir, loadConfig, resolveAuthPath, resolveModelSpec, resolveSessionsDirOverride, } from "../../engines/pi/config.js";
|
|
6
|
+
import { resolveStateRoot, workspaceHint } from "../../paths.js";
|
|
7
|
+
import { resolveAgentTools } from "../../engines/pi/create.js";
|
|
7
8
|
import { loadAgentDefinition } from "../../engines/pi/definition.js";
|
|
8
9
|
import { reportDefinitionWarnings, reportModuleLoadFailures, reportToolCollisions } from "../../engines/pi/report.js";
|
|
9
10
|
import { log } from "../../log.js";
|
|
10
11
|
import { nextRun } from "../../schedule/cron.js";
|
|
11
12
|
import { loadSchedules } from "../../schedule/discover.js";
|
|
12
|
-
import { failStartup } from "../fail.js";
|
|
13
|
+
import { failStartup, placementOrExit } from "../fail.js";
|
|
13
14
|
export async function runInfo(dirArg, opts) {
|
|
14
15
|
const dir = resolve(dirArg);
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
const { agentDir, workspace } = placementOrExit(dir);
|
|
17
|
+
loadDotEnv(agentDir); // skills/tools may read env at load time
|
|
18
|
+
const { config, path: configPath } = await loadConfig(agentDir).catch(failStartup);
|
|
17
19
|
const modelSpec = resolveModelSpec(opts.model, config);
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
const definition = await loadAgentDefinition(agentDir, { cwd:
|
|
20
|
+
// agentDir = where the agent lives (definition + config + machinery); workspace = what it works ON
|
|
21
|
+
// (its cwd, whose AGENTS.md ancestors are ② context).
|
|
22
|
+
const definition = await loadAgentDefinition(agentDir, { cwd: workspace }).catch(failStartup);
|
|
21
23
|
// A tool that fails to load, for any reason (a missing dep, a top-level throw, or just not being a
|
|
22
24
|
// tool), is isolated the same way everywhere (G2): info, dev, AND start report it and keep going with
|
|
23
25
|
// the tools that loaded. The `error`/`.catch` below only fires for a whole-load fault (an unreadable
|
|
24
26
|
// tools/ dir), not a single bad file.
|
|
25
|
-
const tools = await
|
|
27
|
+
const tools = await resolveAgentTools(config, agentDir)
|
|
26
28
|
.then((r) => ({
|
|
27
29
|
names: r.toolNames,
|
|
28
30
|
deferred: r.deferredToolNames,
|
|
@@ -50,13 +52,13 @@ export async function runInfo(dirArg, opts) {
|
|
|
50
52
|
}));
|
|
51
53
|
// The default sessions/auth paths WITHOUT creating anything (info is read-only; dev/start mkdir/login
|
|
52
54
|
// create them, info must not).
|
|
53
|
-
const stateRoot = resolveStateRoot(
|
|
55
|
+
const stateRoot = resolveStateRoot(agentDir);
|
|
54
56
|
const sessionsDir = resolveSessionsDirOverride(opts.sessionsDir) ?? defaultSessionsDir(stateRoot);
|
|
55
|
-
const authPath = resolveAuthPath(
|
|
57
|
+
const authPath = resolveAuthPath(agentDir, opts.authPath); // flag > FASTAGENT_AUTH_PATH > default — the one owner
|
|
56
58
|
if (opts.json) {
|
|
57
59
|
console.log(JSON.stringify({
|
|
58
|
-
dir,
|
|
59
60
|
agentDir,
|
|
61
|
+
workspace,
|
|
60
62
|
configPath: configPath ?? null,
|
|
61
63
|
model: modelSpec ?? null,
|
|
62
64
|
thinkingLevel: config.thinkingLevel ?? null,
|
|
@@ -80,25 +82,30 @@ export async function runInfo(dirArg, opts) {
|
|
|
80
82
|
}, null, 2));
|
|
81
83
|
return;
|
|
82
84
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
// One padded label writer: hand-spaced labels drifted out of alignment the moment a longer one
|
|
86
|
+
// (agent/workspace/selfSchedule) joined the report.
|
|
87
|
+
const line = (label, value) => console.log(`${`${label}:`.padEnd(13)} ${value}`);
|
|
88
|
+
line("agent", agentDir);
|
|
89
|
+
line("workspace", workspace);
|
|
90
|
+
const hint = workspaceHint({ agentDir, workspace });
|
|
91
|
+
if (hint)
|
|
92
|
+
line("hint", hint);
|
|
93
|
+
line("config", configPath ?? "(none)");
|
|
94
|
+
line("model", modelSpec ?? "(not set — pass --model, set FASTAGENT_MODEL, or config.model)");
|
|
88
95
|
if (config.thinkingLevel)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
line("thinking", config.thinkingLevel);
|
|
97
|
+
line("context", definition.contextFiles.map((f) => f.path).join(", ") || "(none)");
|
|
98
|
+
line("persona", definition.persona ? "persona.md" : "(none)");
|
|
99
|
+
line("skills", definition.skills.map((skill) => skill.name).join(", ") || "(none)");
|
|
100
|
+
line("tools", tools.error ? "(could not load — see warning below)" : tools.names.join(", ") || "(none)");
|
|
94
101
|
if (tools.deferred.length > 0)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
line("deferred", `${tools.deferred.join(", ")} (activated via search_tools)`);
|
|
103
|
+
line("channels", channels.join(", ") || "(none)");
|
|
104
|
+
line("schedules", schedules.map((s) => `${s.name} (next ${s.next ?? "never"})`).join(", ") || "(none)");
|
|
105
|
+
line("selfSchedule", config.selfSchedule ? "on (mounts the wake tool when serving)" : "off");
|
|
106
|
+
line("state", stateRoot);
|
|
107
|
+
line("sessions", sessionsDir);
|
|
108
|
+
line("auth", authPath);
|
|
102
109
|
reportToolCollisions(tools.collisions);
|
|
103
110
|
reportModuleLoadFailures(tools.failures);
|
|
104
111
|
reportModuleLoadFailures(sched.failures);
|
|
@@ -2,7 +2,7 @@ export interface InitOptions {
|
|
|
2
2
|
minimal: boolean;
|
|
3
3
|
/** false ⇔ `--no-install`. */
|
|
4
4
|
install: boolean;
|
|
5
|
-
flat
|
|
5
|
+
/** The agent directory's name inside `dir` — undefined = the default, `"."` = `dir` itself (`--flat`). */
|
|
6
6
|
agentDir?: string;
|
|
7
7
|
}
|
|
8
8
|
export declare function runInit(dirArg: string, opts: InitOptions): Promise<void>;
|
|
@@ -1,73 +1,85 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `fastagent init [dir]`: scaffold a runnable agent and install its dependencies.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* `fastagent init [dir]`: scaffold a runnable agent and install its dependencies. Where the files land is
|
|
3
|
+
* a default plus one knob, never a detection and never a prompt — non-interactive executors (coding
|
|
4
|
+
* agents) get deterministic behavior they can read. By default the agent goes into `./fastagent/` and the
|
|
5
|
+
* directory around it is untouched; `--agent-dir <name>` names that directory anything, and `--agent-dir .`
|
|
6
|
+
* (spelled `--flat`) makes the directory itself the agent (a standalone agent repo, a monorepo package).
|
|
6
7
|
*/
|
|
7
8
|
import { spawn } from "node:child_process";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
9
|
+
import { basename, join, resolve } from "node:path";
|
|
10
|
+
import { DEFAULT_AGENT_DIRNAME, SECRETS_DIRNAME, agentsAt } from "../../paths.js";
|
|
11
|
+
import { detectRuntime, readPackageJson } from "../../runtime.js";
|
|
12
|
+
import { agentDirName, agentDirNameError, scaffoldAgent } from "../../scaffold/init.js";
|
|
13
|
+
import { displayPath } from "../../paths.js";
|
|
10
14
|
import { failStartup, failUsage } from "../fail.js";
|
|
11
15
|
export async function runInit(dirArg, opts) {
|
|
12
16
|
const dir = resolve(dirArg);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const rel = relative(dir, resolve(dir, opts.agentDir)).split(sep).join("/");
|
|
21
|
-
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute(rel)) {
|
|
22
|
-
// An invalid flag VALUE is a usage error (exit 2), same class as a value the parser rejects.
|
|
23
|
-
failUsage(`--agent-dir ("${opts.agentDir}") must be a subdirectory of ${dir}`);
|
|
24
|
-
}
|
|
25
|
-
agentDir = `./${rel}`;
|
|
26
|
-
}
|
|
27
|
-
else if (!opts.flat) {
|
|
28
|
-
signals = await detectHostSignals(dir).catch(failStartup);
|
|
29
|
-
if (signals.length > 0)
|
|
30
|
-
agentDir = "./agent";
|
|
31
|
-
}
|
|
32
|
-
const { complete, created, skipped, patched, intoNonEmpty, warnings } = await scaffoldWorkspace(dir, {
|
|
17
|
+
// A rejected flag VALUE is the usage class, same as one the parser rejects — so it is checked here,
|
|
18
|
+
// where exit 2 lives, not inside the scaffolder (whose throws are runtime failures, exit 1).
|
|
19
|
+
const requested = agentDirName(opts.agentDir);
|
|
20
|
+
const invalid = agentDirNameError(requested);
|
|
21
|
+
if (invalid)
|
|
22
|
+
failUsage(`--agent-dir "${requested}" ${invalid}`);
|
|
23
|
+
const { complete, agentDir: rel, created, kept, } = await scaffoldAgent(dir, {
|
|
33
24
|
minimal: opts.minimal,
|
|
34
|
-
agentDir,
|
|
25
|
+
agentDir: requested,
|
|
35
26
|
}).catch(failStartup);
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
const flat = rel === ".";
|
|
28
|
+
// The agent dir is where the manifest lives, so any install runs there — never against a surrounding
|
|
29
|
+
// workspace's package.json (its deps are its own concern). With `.` they are the same directory.
|
|
30
|
+
const agentDir = resolve(dir, rel);
|
|
31
|
+
console.error(`[fastagent] initialized ${dir}${complete ? "" : " (minimal)"} — ${flat ? "the directory IS the agent" : `agent in ./${rel}/`}`);
|
|
32
|
+
console.error(` created: ${created.join(", ")}`);
|
|
33
|
+
// A second agent beside an existing one is a supported shape, not an accident (an engineer's, a PM's
|
|
34
|
+
// and a content owner's agent can drive one repository), but it changes how this workspace resolves
|
|
35
|
+
// from now on — so say it HERE, at the moment it becomes true, instead of at the next command's
|
|
36
|
+
// refusal. The default name is the tie-break, so a workspace keeping `fastagent/` needs nothing.
|
|
37
|
+
const siblings = agentsAt(dir).map((a) => basename(a));
|
|
38
|
+
if (siblings.length > 1) {
|
|
39
|
+
const pick = siblings.includes(DEFAULT_AGENT_DIRNAME)
|
|
40
|
+
? `\`${DEFAULT_AGENT_DIRNAME}\` answers by default; set FASTAGENT_AGENT=<name> for another`
|
|
41
|
+
: `set FASTAGENT_AGENT=<name> (in your shell or .envrc) to pick one`;
|
|
42
|
+
console.error(`[fastagent] note: ${dir} now holds ${siblings.length} agents (${siblings.join(", ")}) — ${pick}`);
|
|
40
43
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
console.error(` kept existing: ${
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
if (kept.length > 0) {
|
|
45
|
+
// Adopting a directory: its own files win, always. Say which ones — and for the two that carry a
|
|
46
|
+
// consequence, say what that consequence is, because keeping them silently leaves the agent broken
|
|
47
|
+
// (package.json) or its machinery committable (.gitignore).
|
|
48
|
+
console.error(` kept your existing: ${kept.join(", ")}`);
|
|
49
|
+
if (kept.includes(join(rel, "package.json")) && complete) {
|
|
50
|
+
const add = detectRuntime(agentDir, await readPackageJson(agentDir)).runtime === "bun" ? "bun add" : "npm install";
|
|
51
|
+
console.error(`[fastagent] note: your package.json is untouched, so it does not list @fastagent-sh/fastagent — ` +
|
|
52
|
+
`run \`${add} @fastagent-sh/fastagent\` there or the scaffolded tools/ cannot resolve`);
|
|
53
|
+
}
|
|
54
|
+
const keptSecretsIgnore = kept.includes(join(rel, SECRETS_DIRNAME, ".gitignore"));
|
|
55
|
+
if (kept.includes(join(rel, ".gitignore"))) {
|
|
56
|
+
// The "credentials are covered either way" reassurance holds only when `.secrets/.gitignore` is
|
|
57
|
+
// OURS. On an adopted directory it may be the author's too, and then we know nothing about it —
|
|
58
|
+
// `add <channel>` defends that file with a write; `init` must not claim more than it did.
|
|
59
|
+
console.error(`[fastagent] note: your .gitignore is untouched — make sure it ignores node_modules, .state ` +
|
|
60
|
+
`and .cache` +
|
|
61
|
+
(keptSecretsIgnore ? `` : ` (.secrets/ carries its own .gitignore, so credentials are covered either way)`));
|
|
62
|
+
}
|
|
63
|
+
if (keptSecretsIgnore) {
|
|
64
|
+
console.error(`[fastagent] note: your ${join(rel, SECRETS_DIRNAME, ".gitignore")} is untouched — verify it ignores ` +
|
|
65
|
+
`everything but .env.example, because credentials land in that directory`);
|
|
66
|
+
}
|
|
50
67
|
}
|
|
51
|
-
|
|
52
|
-
console.error(`[fastagent] warn: ${w}`);
|
|
53
|
-
// Install deps only for a complete agent whose package.json we just wrote (a kept one is not ours).
|
|
54
|
-
// The manifest lives with the kit (agentDir when set), so the install runs there — never against a
|
|
55
|
-
// host repo's own package.json.
|
|
56
|
-
const kitDir = resolve(dir, agentDir ?? ".");
|
|
57
|
-
const willInstall = complete && opts.install && created.includes(join(agentDir ?? ".", "package.json"));
|
|
68
|
+
const willInstall = complete && opts.install && !kept.includes(join(rel, "package.json"));
|
|
58
69
|
let installFailed = false;
|
|
59
70
|
if (willInstall) {
|
|
60
|
-
console.error(`[fastagent] installing dependencies (npm install${
|
|
61
|
-
installFailed = (await npmInstall(
|
|
71
|
+
console.error(`[fastagent] installing dependencies (npm install${flat ? "" : ` in ${rel}`})…`);
|
|
72
|
+
installFailed = (await npmInstall(agentDir)) !== 0;
|
|
62
73
|
if (installFailed)
|
|
63
|
-
console.error(`[fastagent] warn: npm install failed — run it manually in ${
|
|
74
|
+
console.error(`[fastagent] warn: npm install failed — run it manually in ${agentDir} before \`fastagent dev\``);
|
|
64
75
|
}
|
|
65
76
|
console.error(` next steps:`);
|
|
66
|
-
const cdTarget =
|
|
77
|
+
const cdTarget = displayPath(process.cwd(), dir);
|
|
67
78
|
if (cdTarget)
|
|
68
79
|
console.error(` cd ${cdTarget}`);
|
|
69
|
-
if (complete && (!
|
|
70
|
-
console.error(` ${
|
|
80
|
+
if (complete && (!willInstall || installFailed)) {
|
|
81
|
+
console.error(` ${flat ? "npm install" : `(cd ${rel} && npm install)`}`);
|
|
82
|
+
}
|
|
71
83
|
console.error(` fastagent dev # serve locally and iterate`);
|
|
72
84
|
console.error(` fastagent add skill <owner/repo/path> # vendor more skills from GitHub`);
|
|
73
85
|
}
|
|
@@ -2,21 +2,24 @@
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { loadDotEnv } from "../../env.js";
|
|
5
|
-
import {
|
|
5
|
+
import { createPiAgentFromDir } from "../../engines/pi/open.js";
|
|
6
6
|
import { runInvokeStream } from "../invoke-stream.js";
|
|
7
7
|
import { installProxyFetch } from "../../proxy.js";
|
|
8
|
-
import { failStartup } from "../fail.js";
|
|
8
|
+
import { failStartup, placementOrExit } from "../fail.js";
|
|
9
9
|
import { reportAuth, resolveFirstRunModel } from "../shared.js";
|
|
10
10
|
export async function runInvoke(message, dirArg, opts) {
|
|
11
11
|
const invokeDir = resolve(dirArg);
|
|
12
|
-
|
|
12
|
+
const placement = placementOrExit(invokeDir);
|
|
13
|
+
loadDotEnv(placement.agentDir);
|
|
13
14
|
installProxyFetch();
|
|
14
|
-
await resolveFirstRunModel(
|
|
15
|
-
const { agent, modelSpec, authPath } = await
|
|
15
|
+
await resolveFirstRunModel(placement.agentDir, opts);
|
|
16
|
+
const { agent, modelSpec, authPath } = await createPiAgentFromDir(invokeDir, {
|
|
16
17
|
model: opts.model,
|
|
17
18
|
authPath: opts.authPath, // flag > FASTAGENT_AUTH_PATH > default — resolved by the opener (one owner)
|
|
18
19
|
}).catch(failStartup);
|
|
19
|
-
|
|
20
|
+
// BOTH directories, like dev/start: from the workspace, `placement.workspace` alone equals the dir you
|
|
21
|
+
// typed, so it cannot tell you which agent actually ran.
|
|
22
|
+
console.error(`[fastagent] invoke: ${placement.agentDir} (workspace ${placement.workspace}, ${modelSpec})`);
|
|
20
23
|
await reportAuth(modelSpec, authPath);
|
|
21
24
|
// Fresh session per invoke (one-shot, no resume). runInvokeStream maps events→IO: reply→stdout,
|
|
22
25
|
// tool/failure→stderr, exit 1 iff the turn failed (so CI can gate on it).
|
|
@@ -1,39 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `fastagent login [provider]`: authenticate a model provider into the project-level auth file
|
|
3
|
-
* (`<
|
|
4
|
-
* the PROVIDER (not a dir), so the
|
|
5
|
-
*
|
|
3
|
+
* (`<agentDir>/.secrets/auth.json`) by default, or `--auth-path`/`FASTAGENT_AUTH_PATH`. The
|
|
4
|
+
* positional is the PROVIDER (not a dir), so the agent resolves from cwd — `cd` into your agent
|
|
5
|
+
* before logging in. OUTSIDE an agent there is no project credential to write, so the target is the
|
|
6
|
+
* user-global `~/.fastagent/.secrets/auth.json` (which is what running it from $HOME has always
|
|
7
|
+
* meant) — announced on stderr BEFORE the flow, because that is a different credential than the one
|
|
8
|
+
* `dev`/`start` in a real agent would read, and a silent scope switch is exactly the surprise this
|
|
9
|
+
* command must not ship.
|
|
6
10
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
11
|
+
* fastagent writes no `.gitignore` here or anywhere else at runtime: `init` scaffolds the agent's one
|
|
12
|
+
* ignore file (covering `.secrets/`) and the author owns it from then on. The credential store creates
|
|
13
|
+
* its own directory, mode 0700.
|
|
10
14
|
*/
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
11
17
|
import { loadDotEnv } from "../../env.js";
|
|
12
|
-
import { resolveAuthPath
|
|
13
|
-
import {
|
|
18
|
+
import { resolveAuthPath } from "../../engines/pi/config.js";
|
|
19
|
+
import { GLOBAL_HOME_DIR, findAgentDir, placementDeadEnd } from "../../paths.js";
|
|
14
20
|
import { LoginCancelled } from "../../engines/pi/login.js";
|
|
15
21
|
import { installProxyFetch } from "../../proxy.js";
|
|
16
|
-
import { failStartup } from "../fail.js";
|
|
22
|
+
import { failStartup, placementOrExit } from "../fail.js";
|
|
17
23
|
import { isInteractive, loginWithKeyCheck } from "../shared.js";
|
|
18
24
|
export async function runLogin(provider, opts) {
|
|
19
|
-
const
|
|
25
|
+
const cwd = process.cwd();
|
|
26
|
+
const agentDir = findAgentDir(cwd);
|
|
27
|
+
// "Outside an agent" must mean exactly that: a position with its own way out (inside an agent, or on a
|
|
28
|
+
// directory holding several) is a dead end every other command refuses, and taking the global fallback
|
|
29
|
+
// there would write a credential to a different scope under a message saying there is no agent here.
|
|
30
|
+
if (!agentDir && placementDeadEnd(cwd))
|
|
31
|
+
placementOrExit(cwd);
|
|
32
|
+
// Outside any agent the target is the user-global machinery home — handed over explicitly, so the
|
|
33
|
+
// path resolvers need no "is this $HOME?" special case to infer it.
|
|
34
|
+
const loginDir = agentDir ?? join(homedir(), GLOBAL_HOME_DIR);
|
|
20
35
|
loadDotEnv(loginDir); // FASTAGENT_AUTH_PATH / a proxy (HTTPS_PROXY) may be configured in the project .env
|
|
21
36
|
installProxyFetch(); // the OAuth token exchange must go through HTTPS_PROXY (region-locked providers)
|
|
22
|
-
const stateRoot = resolveStateRoot(loginDir);
|
|
23
37
|
const authPath = resolveAuthPath(loginDir, opts.authPath); // flag > FASTAGENT_AUTH_PATH > default — the one owner
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
// Announce when the FALLBACK is what decided the target: outside an agent with no explicit path,
|
|
39
|
+
// the credential lands somewhere no agent will read. An explicit `--auth-path`/FASTAGENT_AUTH_PATH
|
|
40
|
+
// is the user naming the file, so there is no surprise to announce (and announcing a path this run
|
|
41
|
+
// does not write would be worse than saying nothing). The resolved path goes in the message.
|
|
42
|
+
if (!agentDir && !opts.authPath && !process.env.FASTAGENT_AUTH_PATH) {
|
|
43
|
+
console.error(`[fastagent] no agent here (no fastagent.config.*, here or one level inside) — ` +
|
|
44
|
+
`logging in GLOBALLY (${authPath}). An agent reads its own .secrets/auth.json: \`cd\` into one ` +
|
|
45
|
+
`first, or point this run at it with --auth-path.`);
|
|
46
|
+
}
|
|
32
47
|
// login is inherently interactive — loginFlow renders provider/method menus and opens a browser (or
|
|
33
48
|
// prompts for a key). In a non-TTY (a pipe, CI, a coding-agent shell) the menu can't receive keystrokes
|
|
34
49
|
// and would hang; --no-input asks for the same posture explicitly. Fail fast with the reason instead
|
|
35
|
-
// of stalling on an unanswerable prompt.
|
|
36
|
-
// prep, so a later terminal login is safe.)
|
|
50
|
+
// of stalling on an unanswerable prompt.
|
|
37
51
|
if (opts.input === false || !isInteractive()) {
|
|
38
52
|
failStartup(new Error(`login is interactive (it shows a menu and opens a browser) — run it in a terminal, not a pipe/CI`));
|
|
39
53
|
}
|
|
@@ -5,20 +5,20 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { loadDotEnv } from "../../env.js";
|
|
8
|
-
import {
|
|
8
|
+
import { resolveStateRoot } from "../../paths.js";
|
|
9
9
|
import { reportModuleLoadFailures } from "../../engines/pi/report.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";
|
|
13
13
|
import { listWakeups, removeWakeup } from "../../schedule/wakeups.js";
|
|
14
|
-
import { failStartup } from "../fail.js";
|
|
14
|
+
import { failStartup, placementOrExit } from "../fail.js";
|
|
15
15
|
/**
|
|
16
16
|
* `fastagent schedule history <name> [dir]`: print the run audit for one schedule (or "wake") — fired
|
|
17
17
|
* time, outcome, duration, reply/error. Read-only (reads `<stateRoot>/schedule/runs.jsonl`); the answer
|
|
18
18
|
* to "did last night's run silently fail?". Text mode previews the reply/error; --json is the full record.
|
|
19
19
|
*/
|
|
20
20
|
export function runScheduleHistory(name, dirArg, json) {
|
|
21
|
-
const target = resolve(dirArg);
|
|
21
|
+
const { agentDir: target } = placementOrExit(resolve(dirArg));
|
|
22
22
|
loadDotEnv(target); // FASTAGENT_STATE_DIR may live in .env — read the SAME state root the scheduler wrote
|
|
23
23
|
const runs = readRuns(resolveStateRoot(target), name);
|
|
24
24
|
if (json) {
|
|
@@ -45,11 +45,9 @@ export function runScheduleHistory(name, dirArg, json) {
|
|
|
45
45
|
/** `fastagent schedule list [dir]`: everything that will fire — BOTH producers: the static `schedules/`
|
|
46
46
|
* files (with their next instant) and the agent's pending self-scheduled wake-ups. Read-only. */
|
|
47
47
|
export async function runScheduleList(dirArg, json) {
|
|
48
|
-
const target = resolve(dirArg);
|
|
48
|
+
const { agentDir: target } = placementOrExit(resolve(dirArg));
|
|
49
49
|
loadDotEnv(target);
|
|
50
|
-
const {
|
|
51
|
-
const agentDir = resolveAgentDir(target, config);
|
|
52
|
-
const { schedules, failures } = await loadSchedules(agentDir).catch(failStartup);
|
|
50
|
+
const { schedules, failures } = await loadSchedules(target).catch(failStartup);
|
|
53
51
|
reportModuleLoadFailures(failures);
|
|
54
52
|
const wakeups = listWakeups(resolveStateRoot(target));
|
|
55
53
|
if (json) {
|
|
@@ -75,7 +73,7 @@ export async function runScheduleList(dirArg, json) {
|
|
|
75
73
|
/** `fastagent schedule cancel <id> [dir]`: remove a pending wake-up — the operator's kill switch (the
|
|
76
74
|
* agent's own is the `unwake` tool). Unlike unwake it is NOT session-scoped: the operator owns the box. */
|
|
77
75
|
export function runScheduleCancel(id, dirArg) {
|
|
78
|
-
const target = resolve(dirArg);
|
|
76
|
+
const { agentDir: target } = placementOrExit(resolve(dirArg));
|
|
79
77
|
loadDotEnv(target);
|
|
80
78
|
if (removeWakeup(resolveStateRoot(target), id)) {
|
|
81
79
|
// ponytail: the store's load→save is lock-free — a serving scheduler's claim-advance can race this
|