@fastagent-sh/fastagent 0.14.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 +16 -6
- package/dist/agent.d.ts +20 -0
- package/dist/agent.js +9 -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 +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 +35 -0
- package/dist/channels/context-buffer.js +103 -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 +15 -13
- package/dist/channels/feishu/context-buffer.js +62 -69
- package/dist/channels/feishu/feishu-api.d.ts +11 -3
- package/dist/channels/feishu/feishu-api.js +24 -3
- package/dist/channels/feishu/feishu.d.ts +27 -27
- package/dist/channels/feishu/feishu.js +318 -207
- package/dist/channels/feishu/invoke-turn.d.ts +7 -20
- package/dist/channels/feishu/invoke-turn.js +44 -79
- package/dist/channels/feishu/model.d.ts +2 -2
- package/dist/channels/feishu/normalize.d.ts +2 -1
- package/dist/channels/feishu/normalize.js +0 -1
- package/dist/channels/feishu/parse.d.ts +22 -8
- package/dist/channels/feishu/parse.js +24 -7
- package/dist/channels/feishu/preview.d.ts +4 -7
- package/dist/channels/feishu/preview.js +27 -142
- package/dist/channels/feishu/register-app.d.ts +2 -1
- package/dist/channels/feishu/register-webhook.d.ts +2 -1
- 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 +37 -0
- package/dist/channels/feishu/setup-mode.js +28 -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 +16 -7
- package/dist/channels/http.d.ts +14 -0
- package/dist/channels/http.js +36 -3
- 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/lark/scaffold/channel.ts +9 -8
- package/dist/channels/lark/scaffold/lark-send.ts +6 -4
- package/dist/channels/preview-kit.d.ts +115 -0
- package/dist/channels/preview-kit.js +184 -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/parse.d.ts +38 -0
- package/dist/channels/slack/parse.js +124 -0
- package/dist/channels/slack/preview.d.ts +23 -0
- package/dist/channels/slack/preview.js +403 -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 +57 -0
- package/dist/channels/slack/slack-api.js +399 -0
- package/dist/channels/slack/slack.d.ts +51 -0
- package/dist/channels/slack/slack.js +496 -0
- package/dist/channels/slack/welcomed.d.ts +5 -0
- package/dist/channels/slack/welcomed.js +32 -0
- package/dist/channels/state.d.ts +11 -4
- package/dist/channels/state.js +22 -15
- package/dist/channels/stop-command.d.ts +6 -0
- package/dist/channels/stop-command.js +36 -0
- package/dist/channels/tasks.d.ts +7 -0
- package/dist/channels/tasks.js +25 -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/parse.d.ts +0 -7
- package/dist/channels/telegram/parse.js +4 -2
- package/dist/channels/telegram/preview.d.ts +4 -7
- package/dist/channels/telegram/preview.js +24 -142
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -3
- package/dist/channels/telegram/telegram.js +24 -10
- package/dist/channels/{feishu/text.d.ts → text.d.ts} +15 -1
- package/dist/channels/{feishu/text.js → text.js} +15 -1
- package/dist/channels/thread-participants.d.ts +21 -0
- package/dist/channels/thread-participants.js +132 -0
- package/dist/channels/turn-queue.js +8 -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 +30 -0
- package/dist/cli/add-feishu.js +366 -0
- package/dist/cli/add-slack.d.ts +11 -0
- package/dist/cli/add-slack.js +199 -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 +191 -60
- package/dist/cli/commands/attach.d.ts +82 -0
- package/dist/cli/commands/attach.js +563 -0
- package/dist/cli/commands/chat.d.ts +1 -0
- package/dist/cli/commands/chat.js +12 -7
- package/dist/cli/commands/deploy.d.ts +3 -1
- package/dist/cli/commands/deploy.js +384 -82
- package/dist/cli/commands/dev.js +34 -26
- package/dist/cli/commands/fire.js +17 -18
- 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 +11 -9
- package/dist/cli/commands/login.js +48 -45
- package/dist/cli/commands/models.js +1 -1
- package/dist/cli/commands/schedule.js +6 -8
- package/dist/cli/commands/start.js +102 -39
- package/dist/cli/commands/tool.js +23 -18
- package/dist/cli/fail.d.ts +17 -2
- package/dist/cli/fail.js +25 -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 +135 -56
- package/dist/cli/serve.d.ts +60 -20
- package/dist/cli/serve.js +252 -47
- package/dist/cli/shared.d.ts +40 -7
- package/dist/cli/shared.js +180 -38
- 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/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 +3 -3
- package/dist/deploy/docker/plan.js +20 -23
- package/dist/deploy/fly/plan.d.ts +5 -1
- package/dist/deploy/fly/plan.js +39 -26
- package/dist/deploy/fly/run.d.ts +17 -5
- package/dist/deploy/fly/run.js +52 -7
- package/dist/deploy/preflight.d.ts +20 -8
- package/dist/deploy/preflight.js +267 -71
- package/dist/deploy/railway/plan.d.ts +10 -1
- package/dist/deploy/railway/plan.js +53 -20
- package/dist/deploy/railway/run.d.ts +13 -5
- package/dist/deploy/railway/run.js +22 -7
- package/dist/deploy/runner.d.ts +7 -3
- package/dist/deploy/runner.js +9 -3
- package/dist/deploy/secrets.d.ts +2 -1
- package/dist/deploy/secrets.js +23 -3
- package/dist/dev-supervisor.d.ts +11 -10
- package/dist/dev-supervisor.js +56 -50
- package/dist/engines/pi/auth.d.ts +8 -7
- package/dist/engines/pi/auth.js +12 -10
- package/dist/engines/pi/channel.d.ts +22 -16
- package/dist/engines/pi/channel.js +92 -62
- package/dist/engines/pi/chat.d.ts +4 -16
- package/dist/engines/pi/chat.js +8 -261
- package/dist/engines/pi/config.d.ts +20 -55
- package/dist/engines/pi/config.js +49 -121
- package/dist/engines/pi/create.d.ts +30 -10
- package/dist/engines/pi/create.js +33 -26
- package/dist/engines/pi/definition.d.ts +7 -26
- package/dist/engines/pi/definition.js +8 -54
- 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 +12 -1
- package/dist/engines/pi/login.js +16 -4
- package/dist/engines/pi/models.d.ts +59 -13
- package/dist/engines/pi/models.js +62 -24
- package/dist/engines/pi/open.d.ts +114 -0
- package/dist/engines/pi/open.js +127 -0
- 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 +10 -1
- package/dist/engines/pi/wake-tool.d.ts +0 -3
- package/dist/engines/pi/wake-tool.js +9 -7
- package/dist/env.d.ts +16 -4
- package/dist/env.js +43 -5
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/host/node.d.ts +25 -8
- 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/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/observe.js +3 -0
- package/dist/paths.d.ts +138 -0
- package/dist/paths.js +326 -0
- package/dist/pi.d.ts +6 -5
- package/dist/pi.js +4 -3
- package/dist/runtime.d.ts +7 -5
- package/dist/runtime.js +2 -2
- package/dist/scaffold/add-channel.d.ts +26 -9
- package/dist/scaffold/add-channel.js +175 -40
- 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 +5 -4
- 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 -3
- package/dist/schedule/wakeups.js +19 -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 +23 -12
- package/package.json +22 -7
- package/dist/channels/feishu/owned-threads.d.ts +0 -11
- package/dist/channels/feishu/owned-threads.js +0 -47
- 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-add-feishu.js +0 -223
- package/dist/cli-models.d.ts +0 -11
- package/dist/cli-models.js +0 -20
- package/dist/engines/pi/workspace.d.ts +0 -56
- package/dist/engines/pi/workspace.js +0 -72
- 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/paths.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLACEMENT: which directory holds the agent, and which directory the agent works ON — plus the
|
|
3
|
+
* machinery paths that follow from it.
|
|
4
|
+
*
|
|
5
|
+
* ONE marker and ONE rule. `fastagent.config.*` declares an agent; the WORKSPACE is the directory you
|
|
6
|
+
* pointed fastagent at. Nothing here reads a directory NAME, so an agent directory can be called
|
|
7
|
+
* anything — and the same tree answers two ways depending on where you aim it: point at the project and
|
|
8
|
+
* the agent directory inside it answers with the project as its workspace; point at the agent directory
|
|
9
|
+
* itself (all a container may have been shipped) and it works on itself. That is not an ambiguity to
|
|
10
|
+
* resolve but the honest answer — an agent alone on a box has no project to work on, and a rule that
|
|
11
|
+
* insisted otherwise would hand it the container root.
|
|
12
|
+
*
|
|
13
|
+
* Engine-neutral by nature (pure fs/path: existence checks and one shallow scan), so it lives here
|
|
14
|
+
* rather than under engines/pi. Not a filing preference: the scaffold, the deploy planners, the dev
|
|
15
|
+
* watcher and env.ts all need these facts, and routing them through the engine would make neutral
|
|
16
|
+
* modules depend on it for something the engine has no say in.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
19
|
+
import { access } from "node:fs/promises";
|
|
20
|
+
import { realpath } from "node:fs/promises";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
23
|
+
/**
|
|
24
|
+
* The directory name `init` gives a nested agent (`<workspace>/fastagent/`) unless `--agent-dir` names
|
|
25
|
+
* another. A DEFAULT, not a rule: resolution reads the config marker and never a name, so renaming an
|
|
26
|
+
* agent directory changes nothing about how it resolves. Visible on purpose — the agent directory holds
|
|
27
|
+
* the AUTHOR's content (persona, skills, tool code: code, not tool configuration), so it follows the
|
|
28
|
+
* repo convention for code (a plain directory), while fastagent's own machinery inside it (`.secrets/`,
|
|
29
|
+
* `.state/`) keeps the dot prefix.
|
|
30
|
+
*/
|
|
31
|
+
export const DEFAULT_AGENT_DIRNAME = "fastagent";
|
|
32
|
+
/** The user-global machinery home under `$HOME` — hidden, per the dotfile convention for per-user
|
|
33
|
+
* tool homes (`~/.cargo`, `~/.docker`); unrelated to {@link DEFAULT_AGENT_DIRNAME}, which only names
|
|
34
|
+
* what `init` creates.
|
|
35
|
+
* It carries the same shape inside it as an agent dir does (`~/.fastagent/.secrets/auth.json`), so the
|
|
36
|
+
* resolvers below need no special case: `login` outside any agent simply hands them this directory. */
|
|
37
|
+
export const GLOBAL_HOME_DIR = ".fastagent";
|
|
38
|
+
/** The secrets segment inside an agent dir (or the global home): every PATH fastagent resolves —
|
|
39
|
+
* `.env`, `.env.example`, auth.json, the scaffold's write — derives from it, so they cannot drift
|
|
40
|
+
* apart. `FASTAGENT_SECRETS_DIR` relocates the RESOLVED dir ({@link resolveSecretsDir}), never this
|
|
41
|
+
* name. The scaffold's ignore templates are real files the author owns from `init` on, so they spell
|
|
42
|
+
* their rules out as literal text — renaming this constant means editing them too. */
|
|
43
|
+
export const SECRETS_DIRNAME = ".secrets";
|
|
44
|
+
/** The state segment inside an agent dir — same rule and same template caveat as {@link SECRETS_DIRNAME}. */
|
|
45
|
+
export const STATE_DIRNAME = ".state";
|
|
46
|
+
/** The config filenames, in load precedence. ONE source: the loader (below) and `scaffoldAgent`'s
|
|
47
|
+
* already-an-agent refusal both read this, so "is there a config?" can't diverge between them. */
|
|
48
|
+
export const AGENT_CONFIG_NAMES = ["fastagent.config.ts", "fastagent.config.js", "fastagent.config.mjs"];
|
|
49
|
+
/** The definition paths an agent LOADS content from — the surface a second agent must not be scaffolded
|
|
50
|
+
* inside ({@link agentDefinitionOwner}), because the outer agent would read it as its own skills/tools.
|
|
51
|
+
* NOT evidence of an agent: `tools/` and `skills/` are ordinary names half the world's repositories
|
|
52
|
+
* use, and the config is the only marker. */
|
|
53
|
+
const LOADED_SURFACE = ["persona.md", "skills", "tools", "channels", "schedules"];
|
|
54
|
+
function isDir(p) {
|
|
55
|
+
return statSync(p, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
56
|
+
}
|
|
57
|
+
/** THE marker: a directory that declares itself an agent with a `fastagent.config.*`. One file decides
|
|
58
|
+
* it, at every position — which is why an agent directory needs no reserved name and why there is no
|
|
59
|
+
* zero-config agent. */
|
|
60
|
+
function hasConfig(p) {
|
|
61
|
+
return isDir(p) && AGENT_CONFIG_NAMES.some((name) => existsSync(join(p, name)));
|
|
62
|
+
}
|
|
63
|
+
/** The agent directories DIRECTLY inside `dir` — the one-level scan that finds an agent without knowing
|
|
64
|
+
* its name. ONE level: deeper is that directory's own workspace, not this one's agent. Sorted, so a
|
|
65
|
+
* refusal names them in a stable order. A missing `dir` yields none (callers resolve paths that may not
|
|
66
|
+
* exist); a permission fault surfaces. */
|
|
67
|
+
function agentChildren(dir) {
|
|
68
|
+
let entries;
|
|
69
|
+
try {
|
|
70
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
const code = e.code;
|
|
74
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
75
|
+
return [];
|
|
76
|
+
throw e;
|
|
77
|
+
}
|
|
78
|
+
return entries
|
|
79
|
+
.filter((e) => !e.isFile() && hasConfig(join(dir, e.name)))
|
|
80
|
+
.map((e) => join(dir, e.name))
|
|
81
|
+
.sort();
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The agents `dir` resolves over: ITSELF when it holds a config, else the ones directly inside it —
|
|
85
|
+
* never both, because aiming at an agent can only mean that agent. Exported for `init`, which asks the
|
|
86
|
+
* same question this lookup asks: which agents would `dir` resolve over, before and after scaffolding.
|
|
87
|
+
*/
|
|
88
|
+
export function agentsAt(dir) {
|
|
89
|
+
const base = resolve(dir);
|
|
90
|
+
return hasConfig(base) ? [base] : agentChildren(base);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* WHICH agent is meant, when a directory holds several: an engineer's, a PM's and a content owner's
|
|
94
|
+
* agent can each drive the same repository, so they must be selectable rather than refused.
|
|
95
|
+
*
|
|
96
|
+
* `FASTAGENT_AGENT` reaches this from the REAL environment only. Placement resolves before anything
|
|
97
|
+
* reads the agent's `.env` — it is what tells the loader which agent's `.env` to read — so a value set
|
|
98
|
+
* there is a silent no-op (the same ordering caveat `FASTAGENT_SECRETS_DIR` carries). A shell, an
|
|
99
|
+
* `.envrc`, or the command itself.
|
|
100
|
+
*
|
|
101
|
+
* It is an ASSERTION, not a preference: it names the agent, and a directory without one
|
|
102
|
+
* by that name resolves to nothing — even when exactly one agent is sitting there. Serving a DIFFERENT
|
|
103
|
+
* agent than the one asked for is the silent-wrong-target this codebase refuses everywhere else, and the
|
|
104
|
+
* uniform rule ("it names the agent") beats one that changes meaning with the sibling count. The stated
|
|
105
|
+
* cost: a `FASTAGENT_AGENT` exported in a shell PROFILE refuses in every unrelated directory it travels
|
|
106
|
+
* into. Scope it per-repo (an `.envrc`) or pass it per-command; the refusal names the way out.
|
|
107
|
+
*
|
|
108
|
+
* With nothing asserted, the {@link DEFAULT_AGENT_DIRNAME} breaks the tie. That is the one place a
|
|
109
|
+
* directory NAME carries weight, and deliberately not an identity rule — the config alone says what IS
|
|
110
|
+
* an agent; the name only decides which already-identified one answers. What it buys: adding a second
|
|
111
|
+
* agent to a working `<workspace>/fastagent/` setup does not break the command everyone already types.
|
|
112
|
+
*/
|
|
113
|
+
function selectAgent(agents, env) {
|
|
114
|
+
const wanted = env.FASTAGENT_AGENT;
|
|
115
|
+
if (wanted)
|
|
116
|
+
return agents.find((a) => basename(a) === wanted);
|
|
117
|
+
const [only, ...rest] = agents;
|
|
118
|
+
return rest.length === 0 ? only : agents.find((a) => basename(a) === DEFAULT_AGENT_DIRNAME);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Resolve `dir` into its placement, or undefined when nothing selects one agent. The whole rule: `dir`
|
|
122
|
+
* is the WORKSPACE, and the agent is a `fastagent.config.*` holder at it or one level inside
|
|
123
|
+
* ({@link agentsAt}), narrowed by {@link selectAgent} when there are several.
|
|
124
|
+
*/
|
|
125
|
+
function findPlacement(dir, env = process.env) {
|
|
126
|
+
const base = resolve(dir);
|
|
127
|
+
const agentDir = selectAgent(agentsAt(base), env);
|
|
128
|
+
return agentDir === undefined ? undefined : { agentDir, workspace: base };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The one-line hint for "you pointed at the agent, but the project around it is what you meant" — or
|
|
132
|
+
* undefined. The workspace being whatever you aimed at is deliberate (a deployed box may hold nothing
|
|
133
|
+
* but the agent), and the cost is that `cd my-agent && fastagent dev` legitimately narrows the agent's
|
|
134
|
+
* WORKSPACE to its own directory: its cwd, its coding tools' root, and deploy's build context. (②
|
|
135
|
+
* context is not affected — that walk climbs ancestors either way.) Resolution must not guess which you
|
|
136
|
+
* wanted, so this is a HINT — and a hint may use the heuristic ("the parent carries an AGENTS.md or a
|
|
137
|
+
* .git") that a rule may not.
|
|
138
|
+
*
|
|
139
|
+
* It suggests a command only after RUNNING the lookup that command would run, because a hint that dead-ends
|
|
140
|
+
* is worse than none: with several agents beside this one, `..` refuses and names them — sending the
|
|
141
|
+
* reader to a refusal whose own advice points back here.
|
|
142
|
+
*/
|
|
143
|
+
export function workspaceHint({ agentDir, workspace }, env = process.env) {
|
|
144
|
+
if (agentDir !== workspace)
|
|
145
|
+
return undefined;
|
|
146
|
+
const parent = dirname(agentDir);
|
|
147
|
+
if (parent === agentDir)
|
|
148
|
+
return undefined;
|
|
149
|
+
// Would `..` actually serve THIS agent? A parent holding a config is an agent itself, and one holding
|
|
150
|
+
// siblings resolves to none without a selector — neither is the project view asked about here.
|
|
151
|
+
if (findPlacement(parent, env)?.agentDir !== agentDir)
|
|
152
|
+
return undefined;
|
|
153
|
+
if (!["AGENTS.md", ".git"].some((name) => existsSync(join(parent, name))))
|
|
154
|
+
return undefined;
|
|
155
|
+
return `${parent} looks like a project — point fastagent at it (\`..\`) to have the agent work ON it`;
|
|
156
|
+
}
|
|
157
|
+
/** The agent dir for `dir`, or undefined when there is none — {@link findPlacement} without the pair,
|
|
158
|
+
* and without the throw. `login` is the caller: it is the one command allowed to run outside an agent,
|
|
159
|
+
* so it needs the answer as a value rather than as a refusal. */
|
|
160
|
+
export function findAgentDir(dir) {
|
|
161
|
+
return findPlacement(dir)?.agentDir;
|
|
162
|
+
}
|
|
163
|
+
/** The agent `dir` sits INSIDE (the nearest proper ancestor that IS an agent dir), or undefined.
|
|
164
|
+
* Module-private: the two questions callers actually ask are "where do I `cd` to?"
|
|
165
|
+
* ({@link placementDeadEnd}) and "would a new agent here become part of an existing one's definition?"
|
|
166
|
+
* ({@link agentDefinitionOwner}) — and those answers differ (an agent's `src/` is inside it, but is not
|
|
167
|
+
* part of what it loads), so exporting the raw containment fact would invite conflating them.
|
|
168
|
+
* Placement resolution deliberately never walks up — the answer must not depend on how deep you stand
|
|
169
|
+
* — but "you are inside an agent, just not at its root" is the likeliest reason resolution fails, and
|
|
170
|
+
* both the refusal below and `login`'s global-fallback decision need to tell that case apart. Uses the
|
|
171
|
+
* same marker as resolution (an ancestor holding a config), so it never claims a position it cannot
|
|
172
|
+
* justify. */
|
|
173
|
+
function enclosingAgentDir(dir) {
|
|
174
|
+
let candidate = dirname(resolve(dir));
|
|
175
|
+
for (let prev = ""; candidate !== prev; prev = candidate, candidate = dirname(candidate)) {
|
|
176
|
+
if (hasConfig(candidate))
|
|
177
|
+
return candidate;
|
|
178
|
+
}
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The agent whose DEFINITION contains `dir` — scaffolding there would make the new agent part of the
|
|
183
|
+
* outer one's loaded surface rather than an agent of its own. Narrower than {@link enclosingAgentDir} on
|
|
184
|
+
* purpose: an agent owns only what it LOADS ({@link LOADED_SURFACE}); the rest of its directory is the
|
|
185
|
+
* author's tree, where a second agent (a monorepo package, say) is a legitimate thing to create.
|
|
186
|
+
* `enclosingAgentDir` answers a different question ("where do I `cd` to?"), and for that an agent's
|
|
187
|
+
* `src/` genuinely IS inside it.
|
|
188
|
+
*/
|
|
189
|
+
export function agentDefinitionOwner(dir) {
|
|
190
|
+
const base = resolve(dir);
|
|
191
|
+
const agent = enclosingAgentDir(base);
|
|
192
|
+
if (!agent)
|
|
193
|
+
return undefined;
|
|
194
|
+
const [head] = relative(agent, base).split(sep);
|
|
195
|
+
return head && LOADED_SURFACE.includes(head) ? agent : undefined;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Why `dir` is not an agent, when it has its OWN way out — or undefined when it is simply not near one.
|
|
199
|
+
* Two positions qualify: standing INSIDE an agent (its `tools/`, its `src/`), and standing on a
|
|
200
|
+
* directory whose several agents nothing selects between. Both matter because the generic advice ("run
|
|
201
|
+
* `fastagent init`") would not help — the agent already exists, one step away.
|
|
202
|
+
*
|
|
203
|
+
* Exported because `login` is the one command allowed to run outside an agent, and it must tell "truly
|
|
204
|
+
* outside" (→ the global credential) from "a dead end" (→ refuse, like every other command).
|
|
205
|
+
*/
|
|
206
|
+
export function placementDeadEnd(dir, env = process.env) {
|
|
207
|
+
const base = resolve(dir);
|
|
208
|
+
const enclosing = enclosingAgentDir(base);
|
|
209
|
+
if (enclosing) {
|
|
210
|
+
return `${base} is inside the agent ${enclosing} but is not its root — \`cd\` there (or to its workspace) and re-run`;
|
|
211
|
+
}
|
|
212
|
+
const agents = agentsAt(base).map((a) => basename(a));
|
|
213
|
+
const listed = `${base} holds ${agents.length} agent${agents.length === 1 ? "" : "s"} (${agents.join(", ")})`;
|
|
214
|
+
// Asserting a name that is not here is a different mistake from asserting none, and it is worth its own
|
|
215
|
+
// message at ANY count: the value sits in the environment (often a shell profile carried in from
|
|
216
|
+
// somewhere else), so echo it back with what is actually here instead of restating the general rule.
|
|
217
|
+
if (env.FASTAGENT_AGENT && agents.length > 0) {
|
|
218
|
+
return (`${listed}, and FASTAGENT_AGENT asserts "${env.FASTAGENT_AGENT}", which is not one of them — set it ` +
|
|
219
|
+
`to one of those, unset it, or scope it to the repository that needs it (an .envrc)`);
|
|
220
|
+
}
|
|
221
|
+
if (agents.length > 1) {
|
|
222
|
+
return (`${listed} and none of them is named "${DEFAULT_AGENT_DIRNAME}" (the default) — pick one with ` +
|
|
223
|
+
`FASTAGENT_AGENT=<name>, or point fastagent at the one you want (it then works on ITSELF)`);
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Resolve a directory into its placement — the ONE owner of the rule ({@link findPlacement}): `dir` is
|
|
229
|
+
* the workspace, and the agent is the single `fastagent.config.*` holder at it or one level inside.
|
|
230
|
+
* Placement is never configured and never detected from surroundings; it is that lookup and the
|
|
231
|
+
* directory you pointed at.
|
|
232
|
+
*
|
|
233
|
+
* Anything else throws (fail visibly). Resolution never walks UP — an agent must not be claimed from
|
|
234
|
+
* arbitrarily deep inside it — but the MESSAGE reads the path, so each dead end gets the exit that fits
|
|
235
|
+
* it ({@link placementDeadEnd}).
|
|
236
|
+
*/
|
|
237
|
+
export function resolvePlacement(dir, env = process.env) {
|
|
238
|
+
const placement = findPlacement(dir, env);
|
|
239
|
+
if (!placement) {
|
|
240
|
+
const base = resolve(dir);
|
|
241
|
+
throw new Error(placementDeadEnd(base, env) ??
|
|
242
|
+
`${base} is not a fastagent agent — no fastagent.config.* here, and no directory holding one ` +
|
|
243
|
+
`directly inside; run \`fastagent init\` to scaffold one`);
|
|
244
|
+
}
|
|
245
|
+
return placement;
|
|
246
|
+
}
|
|
247
|
+
/** How to WRITE a path for someone standing in `cwd`: relative when it is inside `cwd`, absolute when
|
|
248
|
+
* it climbs out (a `../../..` is noise), and undefined when it IS `cwd` (nothing to say). ONE policy,
|
|
249
|
+
* shared by `init`'s `cd` step, `add`'s next-steps paths and `fire`'s "looked in" hint — they all answer
|
|
250
|
+
* the same question, which is a placement-PRESENTATION question, not a scaffolding one. */
|
|
251
|
+
export function displayPath(cwd, dir) {
|
|
252
|
+
const rel = relative(cwd, dir);
|
|
253
|
+
if (rel === "")
|
|
254
|
+
return undefined;
|
|
255
|
+
// "Climbs out" is a path-SEGMENT check — rel is ".." or starts with "../" (or "..\" on Windows). A
|
|
256
|
+
// bare startsWith("..") would wrongly flag an in-cwd directory literally named "..agent".
|
|
257
|
+
const escapes = rel === ".." || /^\.\.[/\\]/.test(rel);
|
|
258
|
+
return escapes ? dir : rel;
|
|
259
|
+
}
|
|
260
|
+
/** Does a path exist? Plain fs, no placement in it — it lives here because `paths.ts` is where the
|
|
261
|
+
* neutral path helpers are, and the scaffolder is not a utility home for the CLI and deploy. */
|
|
262
|
+
export async function exists(p) {
|
|
263
|
+
return access(p).then(() => true, () => false);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Resolve a user-supplied path override (a CLI flag or an env var) to an absolute path, expanding a
|
|
267
|
+
* leading `~`/`~/` to the home dir FIRST. Path-valued config from `.env` (or any non-shell source)
|
|
268
|
+
* never gets the shell's `~` expansion, so a bare `resolve("~/x")` would silently create a literal `~`
|
|
269
|
+
* directory — a fail-silently footgun for a secret/state path. Expanding here makes `~` mean home
|
|
270
|
+
* everywhere these knobs are read.
|
|
271
|
+
*/
|
|
272
|
+
export function resolveOverridePath(raw) {
|
|
273
|
+
if (!raw)
|
|
274
|
+
return undefined;
|
|
275
|
+
const expanded = raw === "~" ? homedir() : raw.startsWith("~/") ? join(homedir(), raw.slice(2)) : raw;
|
|
276
|
+
return resolve(expanded);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* The resolved state root — the durable machine-state home (sessions/, channels/<kind>/, schedule/,
|
|
280
|
+
* control.json): `FASTAGENT_STATE_DIR` env > `<agentDir>/.state`. Absolute, so channels and the
|
|
281
|
+
* startup report agree regardless of cwd. Definition: mutable runtime state — single lifecycle
|
|
282
|
+
* (precious, survives redeploy), single process; a container points this at its mounted volume.
|
|
283
|
+
* Secrets are NOT here — they live under {@link resolveSecretsDir} (a different deploy lifecycle:
|
|
284
|
+
* secret store vs volume). The finer knob (`FASTAGENT_SESSIONS_DIR`) still overrides its path on top.
|
|
285
|
+
*
|
|
286
|
+
* `FASTAGENT_STATE_DIR` is an OPERATOR override, so a relative value resolves against `process.cwd()`
|
|
287
|
+
* — the CLI convention its sibling knobs share (`resolveOverridePath`), NOT against `dir`. Only the
|
|
288
|
+
* DEFAULT (`<root>/.state`) is dir-anchored.
|
|
289
|
+
*/
|
|
290
|
+
export function resolveStateRoot(dir, env = process.env) {
|
|
291
|
+
return resolveOverridePath(env.FASTAGENT_STATE_DIR) ?? join(resolve(dir), STATE_DIRNAME);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* The resolved secrets dir — everything fastagent manages that must NEVER leave the machine (the
|
|
295
|
+
* agent's `.env` + auth.json): `FASTAGENT_SECRETS_DIR` env > `<agentDir>/.secrets`. Split from
|
|
296
|
+
* the state root on deploy lifecycle: secrets travel through the host's secret store (env vars / the
|
|
297
|
+
* auth seed), state through a volume. A deployed box sets both env knobs at its volume (e.g.
|
|
298
|
+
* `/data/.secrets`, `/data/.state`) so a seeded-then-ROTATED OAuth credential persists across
|
|
299
|
+
* restarts. The `.env`'s OWN location resolves from the REAL environment — commands locate and load
|
|
300
|
+
* `.env` before anything else, so a `FASTAGENT_SECRETS_DIR` set INSIDE `.env` still relocates
|
|
301
|
+
* auth.json but cannot move the file it is read from (env.ts dotEnvPath).
|
|
302
|
+
*/
|
|
303
|
+
export function resolveSecretsDir(dir, env = process.env) {
|
|
304
|
+
return resolveOverridePath(env.FASTAGENT_SECRETS_DIR) ?? join(resolve(dir), SECRETS_DIRNAME);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Guard that `<agentDir>/<name>` resolves INSIDE the agent dir — a symlink that escapes (or an
|
|
308
|
+
* absolute target) is rejected, so discovery/scaffolding never reaches out of the definition directory.
|
|
309
|
+
* A missing target is fine (nothing to guard yet).
|
|
310
|
+
*/
|
|
311
|
+
export async function assertInsideAgentDir(agentDir, name) {
|
|
312
|
+
const target = join(agentDir, name);
|
|
313
|
+
const real = await realpath(target).catch((e) => {
|
|
314
|
+
if (e.code === "ENOENT" || e.code === "not_found")
|
|
315
|
+
return undefined;
|
|
316
|
+
throw e;
|
|
317
|
+
});
|
|
318
|
+
if (real === undefined)
|
|
319
|
+
return;
|
|
320
|
+
const root = await realpath(agentDir).catch(() => resolve(agentDir));
|
|
321
|
+
const rel = relative(root, real);
|
|
322
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
323
|
+
throw new Error(`${target} resolves outside the agent dir (${real}) — it must live inside the definition directory; ` +
|
|
324
|
+
`use a real directory or a symlink that stays within it`);
|
|
325
|
+
}
|
|
326
|
+
}
|
package/dist/pi.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
export { createPiAgent, createPiAgentFromDefinition, type CreatePiAgentFromDefinitionOptions, type CreatePiAgentOptions, } from "./engines/pi/create.ts";
|
|
2
2
|
export { defineTool, loadTools, type DefineToolOptions, type FastagentTool, type ToolCollision, type ToolContext, } from "./engines/pi/tool.ts";
|
|
3
|
-
export type { ToolActivation } from "./engines/pi/tool-context.ts";
|
|
3
|
+
export type { ReadonlySessionManager, ToolActivation } from "./engines/pi/tool-context.ts";
|
|
4
4
|
export { z } from "zod";
|
|
5
|
-
export type { AgentTool, ExecutionEnv, Session, Skill, SkillDiagnostic } from "@earendil-works/pi-agent-core";
|
|
5
|
+
export type { AgentTool, ExecutionEnv, Session, SessionTreeEntry, Skill, SkillDiagnostic, } from "@earendil-works/pi-agent-core";
|
|
6
6
|
export { loadChannels, type ChannelCollision } from "./engines/pi/channel.ts";
|
|
7
|
-
export {
|
|
7
|
+
export { createPiAgentFromDir, type CreatePiAgentFromDirOptions, } from "./engines/pi/open.ts";
|
|
8
8
|
export type { LoadedDefinition, SkillCollision } from "./engines/pi/definition.ts";
|
|
9
9
|
export { defineConfig, listModels, resolveModel, type FastagentConfig } from "./engines/pi/config.ts";
|
|
10
|
-
export { inProcessLease, type Lease, type Release } from "./engines/pi/invoke.ts";
|
|
10
|
+
export { inProcessLease, type Lease, type Release, type SessionObserver } from "./engines/pi/invoke.ts";
|
|
11
|
+
export { createPiSessionControl, type CreatePiSessionControlOptions, } from "./engines/pi/session-control.ts";
|
|
11
12
|
export type { AnyModel } from "./engines/pi/harness.ts";
|
|
12
|
-
export { inMemorySessionStore, jsonlSessionStore, type PiSessionStore } from "./engines/pi/sessions.ts";
|
|
13
|
+
export { inMemorySessionStore, jsonlSessionStore, type PiSessionReader, type PiSessionStore, } from "./engines/pi/sessions.ts";
|
|
13
14
|
export { GLOBAL_AUTH_PATH, fastagentCredentialStore, type FastagentAuthOptions } from "./engines/pi/auth.ts";
|
|
14
15
|
export { createPiModels, probeAuthSource, type CreatePiModelsOptions } from "./engines/pi/models.ts";
|
|
15
16
|
export type { Models } from "@earendil-works/pi-ai";
|
package/dist/pi.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
// The pi reference implementation: assembly,
|
|
1
|
+
// The pi reference implementation: assembly, agent discovery, tools, config, models, auth, and state ports.
|
|
2
2
|
export { createPiAgent, createPiAgentFromDefinition, } from "./engines/pi/create.js";
|
|
3
3
|
export { defineTool, loadTools, } from "./engines/pi/tool.js";
|
|
4
4
|
export { z } from "zod";
|
|
5
5
|
export { loadChannels } from "./engines/pi/channel.js";
|
|
6
|
-
export {
|
|
6
|
+
export { createPiAgentFromDir, } from "./engines/pi/open.js";
|
|
7
7
|
export { defineConfig, listModels, resolveModel } from "./engines/pi/config.js";
|
|
8
8
|
export { inProcessLease } from "./engines/pi/invoke.js";
|
|
9
|
-
export {
|
|
9
|
+
export { createPiSessionControl, } from "./engines/pi/session-control.js";
|
|
10
|
+
export { inMemorySessionStore, jsonlSessionStore, } from "./engines/pi/sessions.js";
|
|
10
11
|
export { GLOBAL_AUTH_PATH, fastagentCredentialStore } from "./engines/pi/auth.js";
|
|
11
12
|
export { createPiModels, probeAuthSource } from "./engines/pi/models.js";
|
|
12
13
|
export { createProvider } from "@earendil-works/pi-ai";
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** How to install and run the AGENT: its package.json + lockfile decide, never the surrounding
|
|
2
|
+
* workspace's (whose toolchain is the agent's runtime concern, not fastagent's). */
|
|
3
|
+
export interface AgentRuntime {
|
|
4
|
+
/** The JS runtime the agent targets — drives the generated Dockerfile base + install/run commands
|
|
3
5
|
* and the package-manager hints in `init`/`add`. */
|
|
4
6
|
runtime: "node" | "bun";
|
|
5
7
|
/** For `runtime: "bun"`, the version from package.json's `packageManager: "bun@x"` (undefined if a bun
|
|
@@ -9,14 +11,14 @@ export interface WorkspaceRuntime {
|
|
|
9
11
|
hasLockfile: boolean;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
|
-
* Detect which JS runtime
|
|
14
|
+
* Detect which JS runtime an agent targets: `bun` when package.json's `packageManager` is `bun@…` OR a
|
|
13
15
|
* bun lockfile (bun.lock/bun.lockb) is present, else `node`. `pkg` is the already-parsed package.json (or
|
|
14
16
|
* `{}` when there is none / it is malformed). One source for the deploy Dockerfile and the CLI hints, so
|
|
15
|
-
* they agree on what the
|
|
17
|
+
* they agree on what the agent is.
|
|
16
18
|
*/
|
|
17
19
|
export declare function detectRuntime(dir: string, pkg: {
|
|
18
20
|
packageManager?: unknown;
|
|
19
|
-
}):
|
|
21
|
+
}): AgentRuntime;
|
|
20
22
|
/** Parse `<dir>/package.json`, or `{}` when absent/malformed (a real build surfaces the actual error).
|
|
21
23
|
* The already-parsed input to {@link detectRuntime} and the deploy dep/lockfile checks. */
|
|
22
24
|
export declare function readPackageJson(dir: string): Promise<{
|
package/dist/runtime.js
CHANGED
|
@@ -2,10 +2,10 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
/**
|
|
5
|
-
* Detect which JS runtime
|
|
5
|
+
* Detect which JS runtime an agent targets: `bun` when package.json's `packageManager` is `bun@…` OR a
|
|
6
6
|
* bun lockfile (bun.lock/bun.lockb) is present, else `node`. `pkg` is the already-parsed package.json (or
|
|
7
7
|
* `{}` when there is none / it is malformed). One source for the deploy Dockerfile and the CLI hints, so
|
|
8
|
-
* they agree on what the
|
|
8
|
+
* they agree on what the agent is.
|
|
9
9
|
*/
|
|
10
10
|
export function detectRuntime(dir, pkg) {
|
|
11
11
|
const pm = typeof pkg.packageManager === "string" ? pkg.packageManager : "";
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import type { FeishuSubscriptionMode } from "../channels/feishu/setup-mode.ts";
|
|
2
|
+
export type ChannelKind = "github" | "telegram" | "slack" | "feishu" | "lark";
|
|
3
|
+
/** Group-visibility choice shared by the slack/feishu/lark onboarding flows. Each channel keeps its
|
|
4
|
+
* own channel-level type (`SlackGroupBehavior`, `FeishuGroupBehavior`) — this is the CLI-side value. */
|
|
5
|
+
export type GroupBehavior = "context" | "mentions";
|
|
6
|
+
/** A resolved group-behavior decision plus whether the author actually chose it (flag or prompt).
|
|
7
|
+
* A defaulted "context" (non-interactive, no flag) must never drive a sensitive-scope write. */
|
|
8
|
+
export interface GroupBehaviorChoice {
|
|
9
|
+
behavior: GroupBehavior;
|
|
10
|
+
explicit: boolean;
|
|
11
|
+
}
|
|
2
12
|
/** An env var a scaffolded channel reads. `generate` = a random-string secret the CLI can pre-fill. */
|
|
3
13
|
export interface ChannelEnv {
|
|
4
14
|
name: string;
|
|
@@ -9,8 +19,8 @@ export interface ChannelEnv {
|
|
|
9
19
|
}
|
|
10
20
|
/** The channel kinds `fastagent add <kind>` can scaffold. */
|
|
11
21
|
export declare const CHANNEL_KINDS: ChannelKind[];
|
|
12
|
-
/** The env vars + next-step lines a scaffolded channel needs
|
|
13
|
-
export declare function channelSetup(kind: ChannelKind): {
|
|
22
|
+
/** The mode-specific env vars + next-step lines a scaffolded channel needs. */
|
|
23
|
+
export declare function channelSetup(kind: ChannelKind, ingress?: FeishuSubscriptionMode, groupBehavior?: GroupBehavior): {
|
|
14
24
|
env: ChannelEnv[];
|
|
15
25
|
steps: string[];
|
|
16
26
|
};
|
|
@@ -19,31 +29,38 @@ export declare function channelSetup(kind: ChannelKind): {
|
|
|
19
29
|
* copies it to `.env` finds the vars already there. No-op when there is no `.env.example` or the block
|
|
20
30
|
* is already present. Placeholders only — no real secret lands in the committable template.
|
|
21
31
|
*/
|
|
22
|
-
export declare function appendChannelEnv(dir: string, kind: ChannelKind): Promise<boolean>;
|
|
32
|
+
export declare function appendChannelEnv(dir: string, kind: ChannelKind, ingress?: FeishuSubscriptionMode): Promise<boolean>;
|
|
23
33
|
export interface DotEnvWriteResult {
|
|
24
34
|
/** Generated secret vars written as active `KEY=value` lines. */
|
|
25
35
|
written: string[];
|
|
26
36
|
/** Vars already present with a non-empty active value; left untouched and omitted from next steps. */
|
|
27
37
|
alreadySet: string[];
|
|
38
|
+
/** Set when the secrets dir is an operator-chosen one (`FASTAGENT_SECRETS_DIR`) carrying no
|
|
39
|
+
* `.gitignore`: a secret was just written into a directory fastagent does not own, so the caller
|
|
40
|
+
* states the fact rather than dropping a `*`-ignoring file into someone else's path. */
|
|
41
|
+
unprotectedSecretsDir?: string;
|
|
28
42
|
}
|
|
29
43
|
/**
|
|
30
|
-
* Append generated channel secrets to the
|
|
31
|
-
*
|
|
44
|
+
* Append generated channel secrets to the agent's `.env` (`.secrets/.env` — never `.env.example`)
|
|
45
|
+
* Existing non-empty values are kept — EXCEPT the names listed in
|
|
32
46
|
* `overwrite`: those are authoritative (e.g. the credentials of an app `add feishu` JUST minted —
|
|
33
47
|
* skipping them for a stale value would silently discard a fresh, unrecoverable secret). Manual values
|
|
34
48
|
* (e.g. TELEGRAM_BOT_TOKEN from BotFather) are added only as commented placeholders, so the file is
|
|
35
49
|
* ready to edit while no fake secret is committed to the user's mental model.
|
|
36
50
|
*/
|
|
37
|
-
export declare function appendChannelDotEnv(dir: string, kind: ChannelKind, generated: Record<string, string>, overwrite?: readonly string[]): Promise<DotEnvWriteResult>;
|
|
51
|
+
export declare function appendChannelDotEnv(dir: string, kind: ChannelKind, generated: Record<string, string>, overwrite?: readonly string[], ingress?: FeishuSubscriptionMode): Promise<DotEnvWriteResult>;
|
|
38
52
|
/** Whether a channel file already exists — checked before any mutation, so a no-clobber re-add is side-effect-free. */
|
|
39
53
|
export declare function channelExists(dir: string, kind: ChannelKind): Promise<boolean>;
|
|
40
54
|
/**
|
|
41
55
|
* Scaffold `channels/<kind>.ts` into {@link dir}. Never clobbers an existing file (the glue is
|
|
42
56
|
* authored content). The wx write is the TOCTOU safety net behind {@link channelExists}.
|
|
43
57
|
*/
|
|
44
|
-
export declare function scaffoldChannel(dir: string, kind: ChannelKind
|
|
58
|
+
export declare function scaffoldChannel(dir: string, kind: ChannelKind, options?: {
|
|
59
|
+
ingress?: FeishuSubscriptionMode;
|
|
60
|
+
groupBehavior?: GroupBehavior;
|
|
61
|
+
}): Promise<string>;
|
|
45
62
|
/**
|
|
46
|
-
* Verify the
|
|
63
|
+
* Verify the AGENT DIR is ready to host a channel: an ESM package.json that declares
|
|
47
64
|
* `@fastagent-sh/fastagent` (the channel file imports it). `add` checks and guides, never bootstraps — that
|
|
48
65
|
* is `init`'s job.
|
|
49
66
|
*/
|