@fastagent-sh/fastagent 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/channels/agentcore-limits.d.ts +7 -0
- package/dist/channels/agentcore-limits.js +9 -0
- package/dist/channels/agentcore-state.d.ts +83 -0
- package/dist/channels/agentcore-state.js +258 -0
- package/dist/channels/agentcore.d.ts +98 -0
- package/dist/channels/agentcore.js +260 -0
- package/dist/channels/busy.d.ts +23 -0
- package/dist/channels/busy.js +53 -0
- package/dist/channels/context-buffer.d.ts +5 -0
- package/dist/channels/context-buffer.js +5 -0
- package/dist/channels/feishu/context-buffer.d.ts +13 -8
- package/dist/channels/feishu/context-buffer.js +60 -6
- package/dist/channels/feishu/feishu-api.js +4 -1
- package/dist/channels/feishu/feishu.d.ts +0 -12
- package/dist/channels/feishu/feishu.js +143 -54
- package/dist/channels/feishu/invoke-turn.js +35 -18
- package/dist/channels/feishu/model.d.ts +0 -1
- package/dist/channels/feishu/normalize.js +0 -1
- package/dist/channels/feishu/parse.d.ts +21 -7
- package/dist/channels/feishu/parse.js +24 -7
- package/dist/channels/feishu/preview.js +3 -2
- package/dist/channels/feishu/scaffold/channel.ts +9 -8
- package/dist/channels/feishu/scaffold/feishu-send.ts +6 -4
- package/dist/channels/feishu/setup-mode.d.ts +30 -1
- package/dist/channels/feishu/setup-mode.js +27 -1
- package/dist/channels/github/github.js +8 -1
- package/dist/channels/http.js +1 -1
- package/dist/channels/lark/scaffold/channel.ts +9 -8
- package/dist/channels/lark/scaffold/lark-send.ts +6 -4
- package/dist/channels/preview-kit.d.ts +7 -1
- package/dist/channels/preview-kit.js +3 -2
- package/dist/channels/slack/parse.d.ts +16 -1
- package/dist/channels/slack/parse.js +46 -3
- package/dist/channels/slack/preview.d.ts +1 -2
- package/dist/channels/slack/preview.js +68 -24
- package/dist/channels/slack/scaffold/channel.ts +5 -5
- package/dist/channels/slack/slack-api.d.ts +3 -23
- package/dist/channels/slack/slack-api.js +6 -22
- package/dist/channels/slack/slack.d.ts +13 -20
- package/dist/channels/slack/slack.js +95 -50
- package/dist/channels/state.d.ts +11 -4
- package/dist/channels/state.js +19 -12
- package/dist/channels/tasks.d.ts +0 -6
- package/dist/channels/tasks.js +16 -1
- package/dist/channels/telegram/parse.d.ts +0 -7
- package/dist/channels/telegram/parse.js +4 -2
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -3
- package/dist/channels/telegram/telegram.js +1 -1
- package/dist/channels/text.d.ts +14 -0
- package/dist/channels/text.js +14 -0
- package/dist/channels/thread-participants.d.ts +21 -0
- package/dist/channels/thread-participants.js +132 -0
- package/dist/channels/turn-queue.js +7 -0
- package/dist/cli/add-feishu.d.ts +7 -4
- package/dist/cli/add-feishu.js +57 -37
- package/dist/cli/add-slack.d.ts +2 -1
- package/dist/cli/add-slack.js +6 -11
- package/dist/cli/commands/add.js +50 -51
- package/dist/cli/commands/attach.js +8 -4
- package/dist/cli/commands/chat.js +8 -8
- package/dist/cli/commands/deploy.d.ts +1 -1
- package/dist/cli/commands/deploy.js +323 -77
- package/dist/cli/commands/dev.js +24 -22
- package/dist/cli/commands/fire.js +15 -16
- package/dist/cli/commands/info.js +36 -29
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.js +65 -53
- package/dist/cli/commands/invoke.js +9 -6
- package/dist/cli/commands/login.js +35 -21
- package/dist/cli/commands/schedule.js +6 -8
- package/dist/cli/commands/start.js +93 -37
- package/dist/cli/commands/tool.js +22 -18
- package/dist/cli/fail.d.ts +17 -0
- package/dist/cli/fail.js +24 -0
- package/dist/cli/program.js +57 -36
- package/dist/cli/serve.d.ts +26 -6
- package/dist/cli/serve.js +62 -15
- package/dist/cli/shared.d.ts +15 -2
- package/dist/cli/shared.js +30 -18
- package/dist/deploy/agentcore/plan.d.ts +117 -0
- package/dist/deploy/agentcore/plan.js +721 -0
- package/dist/deploy/agentcore/run.d.ts +73 -0
- package/dist/deploy/agentcore/run.js +412 -0
- package/dist/deploy/agentcore/zip.d.ts +17 -0
- package/dist/deploy/agentcore/zip.js +68 -0
- package/dist/deploy/container.d.ts +26 -25
- package/dist/deploy/container.js +93 -89
- package/dist/deploy/docker/plan.d.ts +1 -1
- package/dist/deploy/docker/plan.js +12 -17
- package/dist/deploy/fly/plan.d.ts +2 -0
- package/dist/deploy/fly/plan.js +27 -19
- package/dist/deploy/fly/run.d.ts +12 -1
- package/dist/deploy/fly/run.js +36 -2
- package/dist/deploy/preflight.d.ts +11 -5
- package/dist/deploy/preflight.js +217 -65
- package/dist/deploy/railway/plan.d.ts +7 -0
- package/dist/deploy/railway/plan.js +41 -16
- package/dist/deploy/railway/run.d.ts +8 -1
- package/dist/deploy/railway/run.js +7 -2
- package/dist/deploy/runner.d.ts +5 -2
- package/dist/deploy/runner.js +9 -3
- package/dist/dev-supervisor.d.ts +11 -8
- package/dist/dev-supervisor.js +53 -51
- package/dist/engines/pi/auth.d.ts +8 -7
- package/dist/engines/pi/auth.js +12 -10
- package/dist/engines/pi/channel.d.ts +1 -1
- package/dist/engines/pi/channel.js +5 -5
- package/dist/engines/pi/chat.js +2 -2
- package/dist/engines/pi/config.d.ts +6 -46
- package/dist/engines/pi/config.js +21 -108
- package/dist/engines/pi/create.d.ts +8 -8
- package/dist/engines/pi/create.js +13 -12
- package/dist/engines/pi/definition.d.ts +7 -26
- package/dist/engines/pi/definition.js +8 -54
- package/dist/engines/pi/login.d.ts +1 -1
- package/dist/engines/pi/models.d.ts +3 -3
- package/dist/engines/pi/models.js +1 -1
- package/dist/engines/pi/{workspace.d.ts → open.d.ts} +29 -23
- package/dist/engines/pi/{workspace.js → open.js} +27 -29
- package/dist/engines/pi/session-builder.d.ts +2 -2
- package/dist/engines/pi/session-builder.js +11 -11
- package/dist/engines/pi/tool.js +4 -0
- package/dist/env.d.ts +16 -4
- package/dist/env.js +43 -5
- package/dist/host/node.d.ts +2 -2
- package/dist/loader.d.ts +2 -2
- package/dist/loader.js +3 -3
- package/dist/log.d.ts +1 -1
- package/dist/log.js +1 -1
- package/dist/paths.d.ts +138 -0
- package/dist/paths.js +326 -0
- package/dist/pi.d.ts +1 -1
- package/dist/pi.js +2 -2
- package/dist/runtime.d.ts +7 -5
- package/dist/runtime.js +2 -2
- package/dist/scaffold/add-channel.d.ts +7 -3
- package/dist/scaffold/add-channel.js +55 -29
- package/dist/scaffold/init.d.ts +32 -41
- package/dist/scaffold/init.js +161 -185
- package/dist/scaffold/templates/env.example +15 -6
- package/dist/scaffold/templates/fastagent.config.mjs +1 -1
- package/dist/scaffold/templates/gitignore +14 -6
- package/dist/scaffold/templates/persona.md +4 -2
- package/dist/scaffold/templates/secrets.gitignore +5 -0
- package/dist/scaffold/templates.d.ts +1 -7
- package/dist/scaffold/templates.js +3 -25
- package/dist/scaffold/vendor-skill.d.ts +2 -2
- package/dist/scaffold/vendor-skill.js +13 -13
- package/dist/schedule/discover.js +4 -4
- package/dist/schedule/scheduler.d.ts +40 -1
- package/dist/schedule/scheduler.js +89 -56
- package/dist/schedule/state.js +1 -1
- package/dist/schedule/wake-alarm.d.ts +47 -0
- package/dist/schedule/wake-alarm.js +136 -0
- package/dist/schedule/wakeups.d.ts +1 -0
- package/dist/schedule/wakeups.js +18 -0
- package/dist/tunnel.d.ts +3 -3
- package/dist/tunnel.js +7 -7
- package/package.json +4 -1
- package/dist/channels/feishu/owned-threads.d.ts +0 -7
- package/dist/channels/feishu/owned-threads.js +0 -47
- package/dist/channels/slack/owned-threads.d.ts +0 -6
- package/dist/channels/slack/owned-threads.js +0 -43
- package/dist/scaffold/templates/gitignore.agentdir-root +0 -5
- package/dist/scaffold/templates/gitignore.kit +0 -2
- package/dist/workspace.d.ts +0 -9
- package/dist/workspace.js +0 -45
package/dist/dev-supervisor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `fastagent dev` process supervisor: re-spawn the CLI as a worker (`FASTAGENT_DEV_WORKER=1`) and
|
|
3
|
-
* restart it on debounced edits to the
|
|
3
|
+
* restart it on debounced edits to the agent's CODE inputs. Each restart is a fresh process
|
|
4
4
|
* (always-latest, no stale module cache). The supervisor never exits on a bad edit — the worker fails
|
|
5
5
|
* loudly and it waits for the next save.
|
|
6
6
|
*
|
|
@@ -13,65 +13,61 @@
|
|
|
13
13
|
import { spawn } from "node:child_process";
|
|
14
14
|
import { relative, sep } from "node:path";
|
|
15
15
|
import { watch as watchTree } from "chokidar";
|
|
16
|
-
import {
|
|
16
|
+
import { AGENT_CONFIG_NAMES, resolveStateRoot } from "./paths.js";
|
|
17
|
+
import { isUnderDir } from "./engines/pi/definition.js";
|
|
18
|
+
import { dotEnvPath } from "./env.js";
|
|
17
19
|
import { log } from "./log.js";
|
|
18
20
|
import { installProxyFetch } from "./proxy.js";
|
|
19
21
|
import { openExternalUrl } from "./open-url.js";
|
|
20
22
|
import { announceWebhooks, startCloudflareTunnel } from "./tunnel.js";
|
|
21
|
-
/** What the dev watcher restarts on (
|
|
22
|
-
const WATCHED_HINT = "tools/, channels/, package.json
|
|
23
|
+
/** What the dev watcher restarts on (agent-dir-relative): the process-bound code inputs only. */
|
|
24
|
+
const WATCHED_HINT = "tools/, channels/, schedules/, package.json, fastagent.config.*, .secrets/.env";
|
|
23
25
|
/**
|
|
24
|
-
* chokidar `ignored` matcher for the narrow watch scope (true = ignore)
|
|
25
|
-
* the
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* chokidar `ignored` matcher for the narrow watch scope (true = ignore), rooted at the AGENT DIR. When
|
|
27
|
+
* the agent sits INSIDE the workspace that means the surrounding tree never triggers a restart at all;
|
|
28
|
+
* when the agent IS the workspace the root is that tree, and the allowlist below is what keeps the
|
|
29
|
+
* author's own files out of scope. Ignoring a directory prunes the whole subtree, so everything outside it —
|
|
30
|
+
* `.state/` machine state, node_modules, .git, and any file/dir the agent writes as work product —
|
|
31
|
+
* costs no watchers and triggers no restarts. Helper code imported from OUTSIDE tools//channels/ is
|
|
32
|
+
* out of scope by design (keep it under tools/, or restart manually) — the startup log names the set.
|
|
29
33
|
*/
|
|
30
|
-
export function devWatchIgnored(
|
|
34
|
+
export function devWatchIgnored(root, envFile) {
|
|
35
|
+
// The `.env` is allow-listed by its RESOLVED path, not by the `.secrets` name: FASTAGENT_SECRETS_DIR
|
|
36
|
+
// can put it in an in-agent directory called anything, and a name-based rule would prune the very
|
|
37
|
+
// file the worker loads (a credential edit would then silently never restart it).
|
|
38
|
+
const envRel = relative(root, envFile).split(sep);
|
|
31
39
|
return (path) => {
|
|
32
|
-
if (path ===
|
|
33
|
-
return false; // the
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
40
|
+
if (path === root)
|
|
41
|
+
return false; // the root itself must not be pruned
|
|
42
|
+
const rel = relative(root, path);
|
|
43
|
+
// Code inputs at the agent dir root: config, package.json, and the dirs loaded once per worker
|
|
44
|
+
// (a restart is their only re-read). Everything else (skills/, persona.md, AGENTS.md) is
|
|
45
|
+
// live-read — pruned, no restart.
|
|
46
|
+
// The config NAMES come from paths.ts, not a regex spelled here: adding a name there must not
|
|
47
|
+
// silently stop `dev` restarting on edits to it.
|
|
48
|
+
if (AGENT_CONFIG_NAMES.includes(rel))
|
|
37
49
|
return false;
|
|
38
|
-
|
|
39
|
-
// Run-root (cwd) inputs: .env + fastagent.config.* live where config lives, not in agentDir.
|
|
40
|
-
if (rel === ".env")
|
|
50
|
+
if (rel === "package.json")
|
|
41
51
|
return false;
|
|
42
|
-
|
|
52
|
+
const segments = rel.split(sep);
|
|
53
|
+
if (segments[0] === "tools" || segments[0] === "channels" || segments[0] === "schedules")
|
|
54
|
+
return false;
|
|
55
|
+
// The `.env` restarts too (credentials are process-bound). Keep it AND its ancestor directories
|
|
56
|
+
// un-pruned so chokidar can descend to it; every sibling inside them (auth.json, .env.example)
|
|
57
|
+
// prunes normally. An out-of-agent `.env` yields a `..`-prefixed envRel that matches nothing here
|
|
58
|
+
// — the supervisor warns about that case instead of pretending to watch it.
|
|
59
|
+
if (segments.length <= envRel.length && segments.every((seg, i) => seg === envRel[i]))
|
|
43
60
|
return false;
|
|
44
|
-
// Agent code inputs live in agentDir: tools/, channels/, schedules/ (loaded once per worker — a
|
|
45
|
-
// restart is their only re-read), package.json (its own deps). Everything else under agentDir
|
|
46
|
-
// (skills/, persona.md, AGENTS.md) is live-read — pruned, no restart.
|
|
47
|
-
const relAgent = relative(agentDir, path);
|
|
48
|
-
if (!relAgent.startsWith("..")) {
|
|
49
|
-
const [head] = relAgent.split(sep);
|
|
50
|
-
if (head === "tools" || head === "channels" || head === "schedules")
|
|
51
|
-
return false;
|
|
52
|
-
if (relAgent === "package.json")
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
61
|
return true;
|
|
56
62
|
};
|
|
57
63
|
}
|
|
58
|
-
/** Spawn the dev worker and restart it on
|
|
59
|
-
export async function runDevSupervisor(
|
|
60
|
-
// The
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
// of scope for watch-scope re-sync (it triggers a worker restart regardless).
|
|
66
|
-
// A genuine config error (not just a missing agentDir key) — debug-log it here so the silence is not
|
|
67
|
-
// total before the spawned worker crash-loops and surfaces the real message; default agentDir=dir.
|
|
68
|
-
const config = await loadConfig(dir)
|
|
69
|
-
.then((r) => r.config)
|
|
70
|
-
.catch((err) => {
|
|
71
|
-
log.debug(`[fastagent] dev: config load failed while resolving agentDir (worker will report): ${String(err)}`);
|
|
72
|
-
return {};
|
|
73
|
-
});
|
|
74
|
-
const agentDir = resolveAgentDir(dir, config);
|
|
64
|
+
/** Spawn the dev worker and restart it on agent-dir edits; supervise its lifecycle until the process exits. */
|
|
65
|
+
export async function runDevSupervisor(placement, options = {}) {
|
|
66
|
+
// The placement arrives RESOLVED from the command (which already routed its refusal through
|
|
67
|
+
// failStartup): re-resolving here would duplicate the rule and surface the same user-fixable
|
|
68
|
+
// refusal as a raw stack. The watch root is the AGENT DIR — every restart-relevant code input lives
|
|
69
|
+
// under it, so the surrounding workspace costs no watchers at all. Placement is assumed STATIC for
|
|
70
|
+
// the session (creating/removing `fastagent/` mid-session is out of scope for watch re-sync).
|
|
75
71
|
let worker;
|
|
76
72
|
let reloadPending = false;
|
|
77
73
|
let everServed = false; // has any worker successfully bound (sent `ready`) yet?
|
|
@@ -100,10 +96,10 @@ export async function runDevSupervisor(dir, options = {}) {
|
|
|
100
96
|
void startCloudflareTunnel(m.port).then((t) => {
|
|
101
97
|
if (t) {
|
|
102
98
|
tunnel = t;
|
|
103
|
-
void announceWebhooks(
|
|
99
|
+
void announceWebhooks(placement.agentDir, t.url, {
|
|
104
100
|
openUrl: openExternalUrl,
|
|
105
101
|
routeChannels: m.routeChannels,
|
|
106
|
-
stateRoot: resolveStateRoot(
|
|
102
|
+
stateRoot: resolveStateRoot(placement.agentDir),
|
|
107
103
|
});
|
|
108
104
|
}
|
|
109
105
|
});
|
|
@@ -141,9 +137,9 @@ export async function runDevSupervisor(dir, options = {}) {
|
|
|
141
137
|
};
|
|
142
138
|
// chokidar gives reliable cross-platform recursion + structural ignore that native fs.watch
|
|
143
139
|
// cannot; devWatchIgnored (above) narrows the scope to the process-bound code inputs.
|
|
144
|
-
const watcher = watchTree(
|
|
140
|
+
const watcher = watchTree(placement.agentDir, {
|
|
145
141
|
ignoreInitial: true, // the startup scan is not a change
|
|
146
|
-
ignored: devWatchIgnored(
|
|
142
|
+
ignored: devWatchIgnored(placement.agentDir, dotEnvPath(placement.agentDir)),
|
|
147
143
|
});
|
|
148
144
|
watcher.on("all", () => {
|
|
149
145
|
clearTimeout(timer);
|
|
@@ -151,6 +147,12 @@ export async function runDevSupervisor(dir, options = {}) {
|
|
|
151
147
|
});
|
|
152
148
|
watcher.on("error", (error) => log.warn(`[fastagent] file watching error (${error.message}); some edits may need a manual restart`));
|
|
153
149
|
log.info(`[fastagent] watching ${WATCHED_HINT} — code edits restart the dev worker (--no-watch to disable); AGENTS.md/persona.md/skills edits go live next turn without a restart`);
|
|
150
|
+
// FASTAGENT_SECRETS_DIR can move the `.env` OUT of the agent dir entirely; the watcher follows it
|
|
151
|
+
// anywhere inside (the resolved path is allow-listed above), but outside the watch root the worker
|
|
152
|
+
// would load a file no watcher sees. Say so once instead of leaving the hint above lying.
|
|
153
|
+
if (!isUnderDir(dotEnvPath(placement.agentDir), placement.agentDir)) {
|
|
154
|
+
log.warn(`[fastagent] .env lives outside the agent dir (FASTAGENT_SECRETS_DIR → ${dotEnvPath(placement.agentDir)}) — it is NOT watched; restart dev after editing it`);
|
|
155
|
+
}
|
|
154
156
|
const shutdown = () => {
|
|
155
157
|
worker?.kill("SIGTERM");
|
|
156
158
|
tunnel?.close();
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { CredentialStore } from "@earendil-works/pi-ai";
|
|
2
2
|
/**
|
|
3
|
-
* The GLOBAL fastagent credentials file (distinct from pi's `~/.pi`)
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* The GLOBAL fastagent credentials file (distinct from pi's `~/.pi`), under the user-global machinery
|
|
4
|
+
* home `~/.fastagent/` — which carries the same unified shape as a workspace (`.secrets/auth.json`).
|
|
5
|
+
* The project-level default is `<agentDir>/.secrets/auth.json` (computed by the opener and by
|
|
6
|
+
* `fastagent login`); this is only the `loginFlow()` PROGRAMMATIC fallback (when a caller omits
|
|
7
|
+
* `authPath`) and the path to point `--auth-path`/`FASTAGENT_AUTH_PATH` at to deliberately share ONE
|
|
8
|
+
* credential file across projects (safe — one file, one lock-serialized refresh lifecycle). The
|
|
9
|
+
* `fastagent login` CLI is project-level by default, never this.
|
|
9
10
|
*/
|
|
10
11
|
export declare const GLOBAL_AUTH_PATH: string;
|
|
11
12
|
export interface FastagentAuthOptions {
|
|
@@ -13,5 +14,5 @@ export interface FastagentAuthOptions {
|
|
|
13
14
|
warn?: (message: string) => void;
|
|
14
15
|
}
|
|
15
16
|
/** A read-write `CredentialStore` backed by the given credentials file (default {@link GLOBAL_AUTH_PATH};
|
|
16
|
-
* the directory opener passes the project-level `<
|
|
17
|
+
* the directory opener passes the project-level `<root>/.secrets/auth.json`). */
|
|
17
18
|
export declare function fastagentCredentialStore(authPath?: string, options?: FastagentAuthOptions): CredentialStore;
|
package/dist/engines/pi/auth.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth for the pi engine: a read-WRITE {@link CredentialStore} over a fastagent credentials file,
|
|
3
3
|
* consumed by the `Models` collection (models.ts). The path is project-level by default
|
|
4
|
-
* (`<
|
|
5
|
-
* location used as the override target / login default, not an implicit per-provider fallback.
|
|
4
|
+
* (`<agentDir>/.secrets/auth.json`, resolved by the opener); {@link GLOBAL_AUTH_PATH} is the
|
|
5
|
+
* global location used as the override target / login default, not an implicit per-provider fallback.
|
|
6
6
|
*
|
|
7
7
|
* Project-level default + NO implicit project↔global fallback, for two reasons: (1) isolation — each
|
|
8
8
|
* agent can use a different account/subscription; (2) fail-visibly — a missing credential surfaces at
|
|
@@ -27,18 +27,20 @@
|
|
|
27
27
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
28
28
|
import { homedir } from "node:os";
|
|
29
29
|
import { dirname, join } from "node:path";
|
|
30
|
+
import { GLOBAL_HOME_DIR, SECRETS_DIRNAME } from "../../paths.js";
|
|
30
31
|
import { log } from "../../log.js";
|
|
31
32
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
32
33
|
import lockfile from "proper-lockfile";
|
|
33
34
|
/**
|
|
34
|
-
* The GLOBAL fastagent credentials file (distinct from pi's `~/.pi`)
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
35
|
+
* The GLOBAL fastagent credentials file (distinct from pi's `~/.pi`), under the user-global machinery
|
|
36
|
+
* home `~/.fastagent/` — which carries the same unified shape as a workspace (`.secrets/auth.json`).
|
|
37
|
+
* The project-level default is `<agentDir>/.secrets/auth.json` (computed by the opener and by
|
|
38
|
+
* `fastagent login`); this is only the `loginFlow()` PROGRAMMATIC fallback (when a caller omits
|
|
39
|
+
* `authPath`) and the path to point `--auth-path`/`FASTAGENT_AUTH_PATH` at to deliberately share ONE
|
|
40
|
+
* credential file across projects (safe — one file, one lock-serialized refresh lifecycle). The
|
|
41
|
+
* `fastagent login` CLI is project-level by default, never this.
|
|
40
42
|
*/
|
|
41
|
-
export const GLOBAL_AUTH_PATH = join(homedir(),
|
|
43
|
+
export const GLOBAL_AUTH_PATH = join(homedir(), GLOBAL_HOME_DIR, SECRETS_DIRNAME, "auth.json");
|
|
42
44
|
/** A valid stored credential, or undefined — a foreign/old entry reads as not-configured, not a crash. */
|
|
43
45
|
function pick(creds, providerId) {
|
|
44
46
|
const cred = creds[providerId];
|
|
@@ -183,7 +185,7 @@ function parseForWrite(raw, where) {
|
|
|
183
185
|
return creds;
|
|
184
186
|
}
|
|
185
187
|
/** A read-write `CredentialStore` backed by the given credentials file (default {@link GLOBAL_AUTH_PATH};
|
|
186
|
-
* the directory opener passes the project-level `<
|
|
188
|
+
* the directory opener passes the project-level `<root>/.secrets/auth.json`). */
|
|
187
189
|
export function fastagentCredentialStore(authPath = GLOBAL_AUTH_PATH, options = {}) {
|
|
188
190
|
const warn = options.warn ?? ((message) => log.warn(message));
|
|
189
191
|
return {
|
|
@@ -23,7 +23,7 @@ export declare function inspectChannels(dir: string): Promise<{
|
|
|
23
23
|
}>;
|
|
24
24
|
/**
|
|
25
25
|
* Channel file basenames under `<dir>/channels/` — the authoring view (`fastagent info`), which lists
|
|
26
|
-
* WITHOUT importing. A symlinked channels directory must remain inside the
|
|
26
|
+
* WITHOUT importing. A symlinked channels directory must remain inside the agent dir.
|
|
27
27
|
*/
|
|
28
28
|
export declare function discoverChannelFiles(dir: string): Promise<string[]>;
|
|
29
29
|
/** Discover, validate, and bind all channel modules. No long connection is opened here; the CLI owns it. */
|
|
@@ -6,7 +6,7 @@ import { readdir } from "node:fs/promises";
|
|
|
6
6
|
import { isAbsolute, join } from "node:path";
|
|
7
7
|
import { parseRouteKey, } from "../../host/node.js";
|
|
8
8
|
import { isModuleFile, loadModuleDir } from "../../loader.js";
|
|
9
|
-
import {
|
|
9
|
+
import { assertInsideAgentDir } from "../../paths.js";
|
|
10
10
|
function longConnectionModule(value) {
|
|
11
11
|
return value !== null && typeof value === "object" && typeof value.connect === "function";
|
|
12
12
|
}
|
|
@@ -21,7 +21,7 @@ function validateLongConnectionModule(value, label) {
|
|
|
21
21
|
* long-connection channels. There is no second ingress/lifecycle declaration to keep in sync.
|
|
22
22
|
*/
|
|
23
23
|
export async function inspectChannels(dir) {
|
|
24
|
-
await
|
|
24
|
+
await assertInsideAgentDir(dir, "channels");
|
|
25
25
|
const { modules, failures } = await loadModuleDir(join(dir, "channels"));
|
|
26
26
|
const channels = [];
|
|
27
27
|
const routeChannels = [];
|
|
@@ -49,10 +49,10 @@ export async function inspectChannels(dir) {
|
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
51
|
* Channel file basenames under `<dir>/channels/` — the authoring view (`fastagent info`), which lists
|
|
52
|
-
* WITHOUT importing. A symlinked channels directory must remain inside the
|
|
52
|
+
* WITHOUT importing. A symlinked channels directory must remain inside the agent dir.
|
|
53
53
|
*/
|
|
54
54
|
export async function discoverChannelFiles(dir) {
|
|
55
|
-
await
|
|
55
|
+
await assertInsideAgentDir(dir, "channels");
|
|
56
56
|
let names;
|
|
57
57
|
try {
|
|
58
58
|
names = await readdir(join(dir, "channels"));
|
|
@@ -90,7 +90,7 @@ export async function loadChannels(dir, ctx) {
|
|
|
90
90
|
if (!isAbsolute(ctx.stateRoot)) {
|
|
91
91
|
throw new Error(`ChannelContext.stateRoot must be absolute, got "${ctx.stateRoot}"`);
|
|
92
92
|
}
|
|
93
|
-
await
|
|
93
|
+
await assertInsideAgentDir(dir, "channels");
|
|
94
94
|
const { modules, failures } = await loadModuleDir(join(dir, "channels"));
|
|
95
95
|
const routes = {};
|
|
96
96
|
const longConnections = [];
|
package/dist/engines/pi/chat.js
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
* pi's TUI `/login`, which writes through the same credential store into the workspace auth file).
|
|
9
9
|
*/
|
|
10
10
|
import { InteractiveMode } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import {
|
|
11
|
+
import { buildAgentSessionRuntime } from "./session-builder.js";
|
|
12
12
|
/**
|
|
13
13
|
* Open the workspace's agent in pi's interactive TUI and run until the user exits. The agent is
|
|
14
14
|
* fastagent's assembled agent (same model/tools/skills/prompt/auth as dev/start serve); pi's TUI
|
|
15
15
|
* handles login, rendering, and same-workspace sessions natively.
|
|
16
16
|
*/
|
|
17
17
|
export async function runPiChat(dir, options = {}) {
|
|
18
|
-
const runtime = await
|
|
18
|
+
const runtime = await buildAgentSessionRuntime(dir, options);
|
|
19
19
|
await new InteractiveMode(runtime, {}).run();
|
|
20
20
|
}
|
|
@@ -9,14 +9,6 @@ export interface FastagentConfig {
|
|
|
9
9
|
* "xhigh" | "max"). Unset = pi's default. Authors tune thinking in the pi TUI while vibing — this
|
|
10
10
|
* is the serving-side counterpart (fidelity). Levels a model doesn't support are clamped by pi. */
|
|
11
11
|
thinkingLevel?: ThinkingLevel;
|
|
12
|
-
/**
|
|
13
|
-
* The agent-definition subdirectory (persona.md, skills/, tools/, channels/), relative to the config
|
|
14
|
-
* file's directory. Default: the config directory itself (flat — today's behaviour). Point it at a
|
|
15
|
-
* sibling like `"./agent"` to serve an existing repo as a coding agent: the config dir stays the run
|
|
16
|
-
* root (cwd, whose AGENTS.md the agent reads as ② context), while the agent's own surface lives in the
|
|
17
|
-
* subdir and does not collide with the host's `tools/`/`src/` (core.md scenario grid).
|
|
18
|
-
*/
|
|
19
|
-
agentDir?: string;
|
|
20
12
|
/** Extra custom tools, appended after pi defaults — never replaces them. `FastagentTool` = AgentTool
|
|
21
13
|
* plus the optional `deferred` marker (see defineTool). */
|
|
22
14
|
tools?: FastagentTool[];
|
|
@@ -53,24 +45,13 @@ export interface FastagentConfig {
|
|
|
53
45
|
export declare function defineConfig(config: FastagentConfig): FastagentConfig;
|
|
54
46
|
export interface LoadedConfig {
|
|
55
47
|
config: FastagentConfig;
|
|
56
|
-
/** Config file path; undefined when
|
|
48
|
+
/** Config file path; undefined when the loader was pointed at a directory holding none. */
|
|
57
49
|
path?: string;
|
|
58
50
|
}
|
|
59
51
|
/** A valid bindable port. */
|
|
60
52
|
export declare function isValidPort(n: number): boolean;
|
|
61
|
-
/**
|
|
62
|
-
* loader (below) and `scaffoldWorkspace`'s already-a-workspace refusal both read this, so "is there a
|
|
63
|
-
* config?" can't diverge between them when the set changes. */
|
|
64
|
-
export declare const WORKSPACE_CONFIG_NAMES: readonly ["fastagent.config.ts", "fastagent.config.js", "fastagent.config.mjs"];
|
|
65
|
-
/** Load `<dir>/fastagent.config.ts|.js|.mjs`. No file = zero-config; a wrong-shape file throws. */
|
|
53
|
+
/** Load `<dir>/fastagent.config.ts|.js|.mjs`. No file = defaults; a wrong-shape file throws. */
|
|
66
54
|
export declare function loadConfig(dir: string): Promise<LoadedConfig>;
|
|
67
|
-
/**
|
|
68
|
-
* The agent-definition dir from config: `config.agentDir` resolved against `dir`, or `dir` itself when
|
|
69
|
-
* unset (flat). The ONE place this is computed — every opener (`dev`/`start`/`info`/`tool`/`deploy`/`chat`)
|
|
70
|
-
* calls it, so the "relative to the config dir, default `.`" rule can never diverge. loadConfig has
|
|
71
|
-
* already validated that agentDir stays under `dir`.
|
|
72
|
-
*/
|
|
73
|
-
export declare function resolveAgentDir(dir: string, config: FastagentConfig): string;
|
|
74
55
|
/** The provider prefix of a "provider/modelId" spec. A spec without "/" returns whole — downstream
|
|
75
56
|
* lookups then miss visibly (an unknown-provider error / a login-required hint), never a mangled id
|
|
76
57
|
* (`slice(0, indexOf("/"))` silently drops the last char when "/" is absent). */
|
|
@@ -82,7 +63,7 @@ export declare function listModels(models: Models): string[];
|
|
|
82
63
|
/**
|
|
83
64
|
* Rewrite the `model` in a config file's SOURCE TEXT to `spec`, for the first-run picker's write-back.
|
|
84
65
|
* Handles the scaffold's commented placeholder (`// model: "…"`) and an existing `model:` line; returns
|
|
85
|
-
* null when neither is present (
|
|
66
|
+
* null when neither is present (no config file, or a hand-shaped one) so the caller falls back to a
|
|
86
67
|
* printed hint instead of guessing where to insert. Text-level (not AST) on purpose — it only ever
|
|
87
68
|
* touches a line it recognizes, never reformats the author's file.
|
|
88
69
|
*/
|
|
@@ -95,30 +76,9 @@ export declare function resolveModelSpec(flag: string | undefined, config: Fasta
|
|
|
95
76
|
* Resolved to absolute so the store and the startup report agree regardless of cwd.
|
|
96
77
|
*/
|
|
97
78
|
export declare function resolveSessionsDirOverride(flag: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
* so the store and the startup report agree regardless of cwd. No implicit project↔global fallback (isolation
|
|
102
|
-
* + fail-visibly; see auth.ts); to share one account across projects, point this at the global
|
|
103
|
-
* `~/.fastagent/auth.json` — sharing ONE file is safe under the store's cross-process refresh lock.
|
|
104
|
-
*/
|
|
105
|
-
export declare function resolveAuthPathOverride(flag: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
|
|
106
|
-
/**
|
|
107
|
-
* The resolved state root — the ONE durable machine-state home everything derives from (auth.json,
|
|
108
|
-
* sessions/, channels/<kind>/): `FASTAGENT_STATE_DIR` env > `<dir>/.fastagent`. Absolute, so channels
|
|
109
|
-
* and the startup report agree regardless of cwd. Definition: single lifecycle (precious, survives
|
|
110
|
-
* redeploy), single process — a container mounts ONE volume here. The finer knobs
|
|
111
|
-
* (`FASTAGENT_SESSIONS_DIR`, `FASTAGENT_AUTH_PATH`) still override their specific path on top.
|
|
112
|
-
*
|
|
113
|
-
* `FASTAGENT_STATE_DIR` is an OPERATOR override, so a relative value resolves against `process.cwd()`
|
|
114
|
-
* — the CLI convention its sibling knobs share (`resolveOverridePath`), NOT against `dir`. Only the
|
|
115
|
-
* DEFAULT (`<dir>/.fastagent`) is dir-anchored. Deployments set an absolute path (a mounted volume);
|
|
116
|
-
* a relative value is in-tree — hence self-ignored — only when run from the definition dir (cwd == dir).
|
|
117
|
-
*/
|
|
118
|
-
export declare function resolveStateRoot(dir: string, env?: NodeJS.ProcessEnv): string;
|
|
119
|
-
/** The default credentials file under a resolved state root ({@link resolveStateRoot}). */
|
|
120
|
-
export declare function defaultAuthPath(stateRoot: string): string;
|
|
121
|
-
/** The effective auth file for a workspace: override if present, else the project-level auth.json. */
|
|
79
|
+
/** The default credentials file under a resolved secrets dir ({@link resolveSecretsDir}). */
|
|
80
|
+
export declare function defaultAuthPath(secretsDir: string): string;
|
|
81
|
+
/** The effective auth file for an agent: override if present, else `<secrets dir>/auth.json`. */
|
|
122
82
|
export declare function resolveAuthPath(dir: string, flag: string | undefined, env?: NodeJS.ProcessEnv): string;
|
|
123
83
|
/** The default sessions dir under a resolved state root ({@link resolveStateRoot}). */
|
|
124
84
|
export declare function defaultSessionsDir(stateRoot: string): string;
|
|
@@ -11,15 +11,18 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
11
11
|
* (resolveModel, resolveModelSpec). One concern: everything about fastagent.config.ts.
|
|
12
12
|
*
|
|
13
13
|
* Red line: config describes deployment/runtime choices, never authored identity or expertise (those
|
|
14
|
-
* live in persona.md + skills, with AGENTS.md as project context).
|
|
15
|
-
*
|
|
14
|
+
* live in persona.md + skills, with AGENTS.md as project context). It has no say in PLACEMENT either —
|
|
15
|
+
* that rule lives in the neutral src/paths.ts — but its FILE is what marks a directory as an agent, so a
|
|
16
|
+
* served agent always has one (its contents may be `export default {}`; a model can still come from
|
|
17
|
+
* --model / FASTAGENT_MODEL). The loader below stays tolerant of a missing file because it is a loader:
|
|
18
|
+
* callers that have not resolved a placement (deploy inspection, tests) may point it anywhere.
|
|
16
19
|
*/
|
|
17
|
-
import { existsSync,
|
|
18
|
-
import {
|
|
19
|
-
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
20
|
+
import { existsSync, statSync } from "node:fs";
|
|
21
|
+
import { basename, join } from "node:path";
|
|
20
22
|
import { pathToFileURL } from "node:url";
|
|
21
23
|
import { THINKING_LEVELS } from "./harness.js";
|
|
22
24
|
import { moduleLoadHint } from "../../loader.js";
|
|
25
|
+
import { AGENT_CONFIG_NAMES, resolveOverridePath, resolveSecretsDir } from "../../paths.js";
|
|
23
26
|
/** Identity function for typing and IDE completion (vite/next-style). */
|
|
24
27
|
export function defineConfig(config) {
|
|
25
28
|
return config;
|
|
@@ -41,13 +44,9 @@ function validateStringList(value, key, shape, desc, path) {
|
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
|
-
/**
|
|
45
|
-
* loader (below) and `scaffoldWorkspace`'s already-a-workspace refusal both read this, so "is there a
|
|
46
|
-
* config?" can't diverge between them when the set changes. */
|
|
47
|
-
export const WORKSPACE_CONFIG_NAMES = ["fastagent.config.ts", "fastagent.config.js", "fastagent.config.mjs"];
|
|
48
|
-
/** Load `<dir>/fastagent.config.ts|.js|.mjs`. No file = zero-config; a wrong-shape file throws. */
|
|
47
|
+
/** Load `<dir>/fastagent.config.ts|.js|.mjs`. No file = defaults; a wrong-shape file throws. */
|
|
49
48
|
export async function loadConfig(dir) {
|
|
50
|
-
const found =
|
|
49
|
+
const found = AGENT_CONFIG_NAMES.map((name) => join(dir, name)).filter((path) => existsSync(path));
|
|
51
50
|
if (found.length === 0)
|
|
52
51
|
return { config: {} };
|
|
53
52
|
if (found.length > 1) {
|
|
@@ -75,17 +74,16 @@ export async function loadConfig(dir) {
|
|
|
75
74
|
}
|
|
76
75
|
const c = config;
|
|
77
76
|
// Unknown keys throw: defineConfig only type-protects .ts authors; a typo in a .js/.mjs config
|
|
78
|
-
// (`modle:`) must not silently degrade to
|
|
77
|
+
// (`modle:`) must not silently degrade to defaults.
|
|
79
78
|
for (const key of Object.keys(c)) {
|
|
80
79
|
if (key !== "model" &&
|
|
81
80
|
key !== "thinkingLevel" &&
|
|
82
|
-
key !== "agentDir" &&
|
|
83
81
|
key !== "tools" &&
|
|
84
82
|
key !== "http" &&
|
|
85
83
|
key !== "deploy" &&
|
|
86
84
|
key !== "selfSchedule" &&
|
|
87
85
|
key !== "sessionControl") {
|
|
88
|
-
throw new Error(`${path}: unknown key "${key}" (valid keys: model, thinkingLevel,
|
|
86
|
+
throw new Error(`${path}: unknown key "${key}" (valid keys: model, thinkingLevel, tools, http, deploy, selfSchedule, sessionControl)`);
|
|
89
87
|
}
|
|
90
88
|
}
|
|
91
89
|
if (c.model !== undefined && typeof c.model !== "string") {
|
|
@@ -97,47 +95,6 @@ export async function loadConfig(dir) {
|
|
|
97
95
|
if (c.thinkingLevel !== undefined && !THINKING_LEVELS.has(c.thinkingLevel)) {
|
|
98
96
|
throw new Error(`${path}: "thinkingLevel" must be one of ${[...THINKING_LEVELS].join(", ")}`);
|
|
99
97
|
}
|
|
100
|
-
if (c.agentDir !== undefined && typeof c.agentDir !== "string") {
|
|
101
|
-
throw new Error(`${path}: "agentDir" must be a string (a subdirectory relative to the config file)`);
|
|
102
|
-
}
|
|
103
|
-
if (typeof c.agentDir === "string") {
|
|
104
|
-
// Enforce the documented "subdirectory of the config dir" contract: an escaping agentDir (e.g.
|
|
105
|
-
// "../shared") would still resolve for tool/channel/persona discovery, but `dev`'s chokidar only
|
|
106
|
-
// watches the config dir subtree — edits outside it would silently never trigger a restart. Reject
|
|
107
|
-
// it here (fail visibly) rather than let hot-reload break without a signal.
|
|
108
|
-
const rel = relative(dir, resolve(dir, c.agentDir));
|
|
109
|
-
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
110
|
-
throw new Error(`${path}: "agentDir" ("${c.agentDir}") must be a subdirectory of the config directory, not escape it`);
|
|
111
|
-
}
|
|
112
|
-
// An explicitly declared agentDir that doesn't exist is a typo until proven otherwise ("./agnet"):
|
|
113
|
-
// without this check every opener would assemble an EMPTY agent (no persona, no skills, no tools)
|
|
114
|
-
// with zero errors — the worst silent failure this config can produce. Deliberately NOT auto-created:
|
|
115
|
-
// config load is read-only (no implicit operations), and a mkdir would turn the typo into a served
|
|
116
|
-
// empty agent plus a junk directory.
|
|
117
|
-
// lstat, not stat: a symlink would pass the literal containment check above while its TARGET lives
|
|
118
|
-
// outside the config dir — exactly what that check exists to prevent (dev's watch would silently
|
|
119
|
-
// never see edits). Same rule as init's parent preflight: reject, don't follow.
|
|
120
|
-
const agentDirAbs = resolve(dir, c.agentDir);
|
|
121
|
-
const st = lstatSync(agentDirAbs, { throwIfNoEntry: false });
|
|
122
|
-
if (!st) {
|
|
123
|
-
throw new Error(`${path}: "agentDir" ("${c.agentDir}") does not exist — create it, or fix the path`);
|
|
124
|
-
}
|
|
125
|
-
if (st.isSymbolicLink()) {
|
|
126
|
-
// Separate message: to its user a symlink LOOKS like a working directory — name the reason and the fix.
|
|
127
|
-
throw new Error(`${path}: "agentDir" ("${c.agentDir}") is a symlink — not allowed (its target can live outside the ` +
|
|
128
|
-
`config directory, where dev's watch would never see edits); use a real directory, or point agentDir at the target's real path`);
|
|
129
|
-
}
|
|
130
|
-
if (!st.isDirectory()) {
|
|
131
|
-
throw new Error(`${path}: "agentDir" ("${c.agentDir}") is not a directory`);
|
|
132
|
-
}
|
|
133
|
-
// The leaf lstat can't see a symlinked INTERMEDIATE segment (agentDir "./a/b" with `a` → outside):
|
|
134
|
-
// realpath equality covers every segment under the config dir in one comparison. dir itself is
|
|
135
|
-
// realpath'd on both sides, so a symlinked config-dir path (macOS /tmp) stays legal.
|
|
136
|
-
if (realpathSync(agentDirAbs) !== resolve(realpathSync(dir), relative(dir, agentDirAbs))) {
|
|
137
|
-
throw new Error(`${path}: "agentDir" ("${c.agentDir}") resolves through a symlink — not allowed (the target can ` +
|
|
138
|
-
`live outside the config directory, where dev's watch would never see edits); use the real path`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
98
|
if (c.selfSchedule !== undefined && typeof c.selfSchedule !== "boolean") {
|
|
142
99
|
throw new Error(`${path}: "selfSchedule" must be a boolean`);
|
|
143
100
|
}
|
|
@@ -181,15 +138,6 @@ export async function loadConfig(dir) {
|
|
|
181
138
|
validateStringList(c.deploy?.apt, "deploy.apt", /^[a-z0-9][a-z0-9.+-]*$/, "a Debian package name", path);
|
|
182
139
|
return { config: c, path };
|
|
183
140
|
}
|
|
184
|
-
/**
|
|
185
|
-
* The agent-definition dir from config: `config.agentDir` resolved against `dir`, or `dir` itself when
|
|
186
|
-
* unset (flat). The ONE place this is computed — every opener (`dev`/`start`/`info`/`tool`/`deploy`/`chat`)
|
|
187
|
-
* calls it, so the "relative to the config dir, default `.`" rule can never diverge. loadConfig has
|
|
188
|
-
* already validated that agentDir stays under `dir`.
|
|
189
|
-
*/
|
|
190
|
-
export function resolveAgentDir(dir, config) {
|
|
191
|
-
return resolve(dir, config.agentDir ?? ".");
|
|
192
|
-
}
|
|
193
141
|
/** The provider prefix of a "provider/modelId" spec. A spec without "/" returns whole — downstream
|
|
194
142
|
* lookups then miss visibly (an unknown-provider error / a login-required hint), never a mangled id
|
|
195
143
|
* (`slice(0, indexOf("/"))` silently drops the last char when "/" is absent). */
|
|
@@ -223,7 +171,7 @@ export function listModels(models) {
|
|
|
223
171
|
/**
|
|
224
172
|
* Rewrite the `model` in a config file's SOURCE TEXT to `spec`, for the first-run picker's write-back.
|
|
225
173
|
* Handles the scaffold's commented placeholder (`// model: "…"`) and an existing `model:` line; returns
|
|
226
|
-
* null when neither is present (
|
|
174
|
+
* null when neither is present (no config file, or a hand-shaped one) so the caller falls back to a
|
|
227
175
|
* printed hint instead of guessing where to insert. Text-level (not AST) on purpose — it only ever
|
|
228
176
|
* touches a line it recognizes, never reformats the author's file.
|
|
229
177
|
*/
|
|
@@ -248,19 +196,6 @@ export function rewriteConfigModel(src, spec) {
|
|
|
248
196
|
export function resolveModelSpec(flag, config, env = process.env) {
|
|
249
197
|
return flag ?? env.FASTAGENT_MODEL ?? config.model;
|
|
250
198
|
}
|
|
251
|
-
/**
|
|
252
|
-
* Resolve a user-supplied path override (a CLI flag or an env var) to an absolute path, expanding a
|
|
253
|
-
* leading `~`/`~/` to the home dir FIRST. Path-valued config from `.env` (or any non-shell source)
|
|
254
|
-
* never gets the shell's `~` expansion, so a bare `resolve("~/x")` would silently create a literal `~`
|
|
255
|
-
* directory — a fail-silently footgun for a secret/state path. Expanding here makes `~` mean home
|
|
256
|
-
* everywhere these knobs are read.
|
|
257
|
-
*/
|
|
258
|
-
function resolveOverridePath(raw) {
|
|
259
|
-
if (!raw)
|
|
260
|
-
return undefined;
|
|
261
|
-
const expanded = raw === "~" ? homedir() : raw.startsWith("~/") ? join(homedir(), raw.slice(2)) : raw;
|
|
262
|
-
return resolve(expanded);
|
|
263
|
-
}
|
|
264
199
|
/**
|
|
265
200
|
* `start`'s sessions-dir override: `--sessions-dir` flag > `FASTAGENT_SESSIONS_DIR` env > undefined
|
|
266
201
|
* (the opener then falls back to {@link defaultSessionsDir} under the {@link resolveStateRoot} root).
|
|
@@ -271,43 +206,21 @@ export function resolveSessionsDirOverride(flag, env = process.env) {
|
|
|
271
206
|
}
|
|
272
207
|
/**
|
|
273
208
|
* The auth-file override: `--auth-path` flag > `FASTAGENT_AUTH_PATH` env > undefined (the opener then
|
|
274
|
-
* falls back to {@link defaultAuthPath} under the {@link
|
|
209
|
+
* falls back to {@link defaultAuthPath} under the {@link resolveSecretsDir} dir). Resolved to absolute
|
|
275
210
|
* so the store and the startup report agree regardless of cwd. No implicit project↔global fallback (isolation
|
|
276
211
|
* + fail-visibly; see auth.ts); to share one account across projects, point this at the global
|
|
277
|
-
* `~/.fastagent/auth.json` — sharing ONE file is safe under the store's cross-process refresh lock.
|
|
212
|
+
* `~/.fastagent/.secrets/auth.json` — sharing ONE file is safe under the store's cross-process refresh lock.
|
|
278
213
|
*/
|
|
279
|
-
|
|
214
|
+
function resolveAuthPathOverride(flag, env = process.env) {
|
|
280
215
|
return resolveOverridePath(flag ?? env.FASTAGENT_AUTH_PATH);
|
|
281
216
|
}
|
|
282
|
-
/**
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
*/
|
|
286
|
-
function projectStateDir(dir) {
|
|
287
|
-
return join(dir, ".fastagent");
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* The resolved state root — the ONE durable machine-state home everything derives from (auth.json,
|
|
291
|
-
* sessions/, channels/<kind>/): `FASTAGENT_STATE_DIR` env > `<dir>/.fastagent`. Absolute, so channels
|
|
292
|
-
* and the startup report agree regardless of cwd. Definition: single lifecycle (precious, survives
|
|
293
|
-
* redeploy), single process — a container mounts ONE volume here. The finer knobs
|
|
294
|
-
* (`FASTAGENT_SESSIONS_DIR`, `FASTAGENT_AUTH_PATH`) still override their specific path on top.
|
|
295
|
-
*
|
|
296
|
-
* `FASTAGENT_STATE_DIR` is an OPERATOR override, so a relative value resolves against `process.cwd()`
|
|
297
|
-
* — the CLI convention its sibling knobs share (`resolveOverridePath`), NOT against `dir`. Only the
|
|
298
|
-
* DEFAULT (`<dir>/.fastagent`) is dir-anchored. Deployments set an absolute path (a mounted volume);
|
|
299
|
-
* a relative value is in-tree — hence self-ignored — only when run from the definition dir (cwd == dir).
|
|
300
|
-
*/
|
|
301
|
-
export function resolveStateRoot(dir, env = process.env) {
|
|
302
|
-
return resolveOverridePath(env.FASTAGENT_STATE_DIR) ?? resolve(projectStateDir(dir));
|
|
303
|
-
}
|
|
304
|
-
/** The default credentials file under a resolved state root ({@link resolveStateRoot}). */
|
|
305
|
-
export function defaultAuthPath(stateRoot) {
|
|
306
|
-
return join(stateRoot, "auth.json");
|
|
217
|
+
/** The default credentials file under a resolved secrets dir ({@link resolveSecretsDir}). */
|
|
218
|
+
export function defaultAuthPath(secretsDir) {
|
|
219
|
+
return join(secretsDir, "auth.json");
|
|
307
220
|
}
|
|
308
|
-
/** The effective auth file for
|
|
221
|
+
/** The effective auth file for an agent: override if present, else `<secrets dir>/auth.json`. */
|
|
309
222
|
export function resolveAuthPath(dir, flag, env = process.env) {
|
|
310
|
-
return resolveAuthPathOverride(flag, env) ?? defaultAuthPath(
|
|
223
|
+
return resolveAuthPathOverride(flag, env) ?? defaultAuthPath(resolveSecretsDir(dir, env));
|
|
311
224
|
}
|
|
312
225
|
/** The default sessions dir under a resolved state root ({@link resolveStateRoot}). */
|
|
313
226
|
export function defaultSessionsDir(stateRoot) {
|