@fastagent-sh/fastagent 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +14 -4
  2. package/dist/bind.d.ts +34 -0
  3. package/dist/bind.js +74 -0
  4. package/dist/channels/agentcore-limits.d.ts +7 -0
  5. package/dist/channels/agentcore-limits.js +9 -0
  6. package/dist/channels/agentcore-state.d.ts +83 -0
  7. package/dist/channels/agentcore-state.js +266 -0
  8. package/dist/channels/agentcore.d.ts +98 -0
  9. package/dist/channels/agentcore.js +260 -0
  10. package/dist/channels/busy.d.ts +23 -0
  11. package/dist/channels/busy.js +53 -0
  12. package/dist/channels/context-buffer.d.ts +5 -0
  13. package/dist/channels/context-buffer.js +5 -0
  14. package/dist/channels/feishu/context-buffer.d.ts +13 -8
  15. package/dist/channels/feishu/context-buffer.js +60 -6
  16. package/dist/channels/feishu/feishu-api.js +4 -1
  17. package/dist/channels/feishu/feishu.d.ts +0 -12
  18. package/dist/channels/feishu/feishu.js +143 -54
  19. package/dist/channels/feishu/invoke-turn.js +35 -18
  20. package/dist/channels/feishu/model.d.ts +0 -1
  21. package/dist/channels/feishu/normalize.js +0 -1
  22. package/dist/channels/feishu/parse.d.ts +21 -7
  23. package/dist/channels/feishu/parse.js +24 -7
  24. package/dist/channels/feishu/preview.js +3 -2
  25. package/dist/channels/feishu/scaffold/channel.ts +9 -8
  26. package/dist/channels/feishu/scaffold/feishu-send.ts +6 -4
  27. package/dist/channels/feishu/setup-mode.d.ts +30 -1
  28. package/dist/channels/feishu/setup-mode.js +27 -1
  29. package/dist/channels/github/github.js +8 -1
  30. package/dist/channels/http.js +1 -1
  31. package/dist/channels/lark/scaffold/channel.ts +9 -8
  32. package/dist/channels/lark/scaffold/lark-send.ts +6 -4
  33. package/dist/channels/preview-kit.d.ts +7 -1
  34. package/dist/channels/preview-kit.js +3 -2
  35. package/dist/channels/slack/parse.d.ts +16 -1
  36. package/dist/channels/slack/parse.js +46 -3
  37. package/dist/channels/slack/preview.d.ts +1 -2
  38. package/dist/channels/slack/preview.js +68 -24
  39. package/dist/channels/slack/scaffold/channel.ts +5 -5
  40. package/dist/channels/slack/slack-api.d.ts +3 -23
  41. package/dist/channels/slack/slack-api.js +6 -22
  42. package/dist/channels/slack/slack.d.ts +13 -20
  43. package/dist/channels/slack/slack.js +95 -50
  44. package/dist/channels/state.d.ts +11 -4
  45. package/dist/channels/state.js +19 -12
  46. package/dist/channels/tasks.d.ts +0 -6
  47. package/dist/channels/tasks.js +16 -1
  48. package/dist/channels/telegram/parse.d.ts +0 -7
  49. package/dist/channels/telegram/parse.js +4 -2
  50. package/dist/channels/telegram/scaffold/telegram-send.ts +6 -3
  51. package/dist/channels/telegram/telegram.js +1 -1
  52. package/dist/channels/text.d.ts +14 -0
  53. package/dist/channels/text.js +14 -0
  54. package/dist/channels/thread-participants.d.ts +21 -0
  55. package/dist/channels/thread-participants.js +132 -0
  56. package/dist/channels/turn-queue.js +7 -0
  57. package/dist/cli/add-feishu.d.ts +7 -4
  58. package/dist/cli/add-feishu.js +57 -37
  59. package/dist/cli/add-slack.d.ts +2 -1
  60. package/dist/cli/add-slack.js +6 -11
  61. package/dist/cli/commands/add.js +50 -51
  62. package/dist/cli/commands/attach.js +8 -4
  63. package/dist/cli/commands/chat.js +8 -8
  64. package/dist/cli/commands/deploy.d.ts +14 -1
  65. package/dist/cli/commands/deploy.js +330 -78
  66. package/dist/cli/commands/dev.d.ts +1 -0
  67. package/dist/cli/commands/dev.js +38 -25
  68. package/dist/cli/commands/fire.js +15 -16
  69. package/dist/cli/commands/info.js +36 -29
  70. package/dist/cli/commands/init.d.ts +1 -1
  71. package/dist/cli/commands/init.js +65 -53
  72. package/dist/cli/commands/invoke.js +9 -6
  73. package/dist/cli/commands/login.js +35 -21
  74. package/dist/cli/commands/schedule.js +6 -8
  75. package/dist/cli/commands/start.d.ts +1 -0
  76. package/dist/cli/commands/start.js +101 -37
  77. package/dist/cli/commands/tool.js +30 -18
  78. package/dist/cli/fail.d.ts +17 -0
  79. package/dist/cli/fail.js +24 -0
  80. package/dist/cli/program.js +66 -36
  81. package/dist/cli/serve.d.ts +52 -8
  82. package/dist/cli/serve.js +133 -32
  83. package/dist/cli/shared.d.ts +21 -2
  84. package/dist/cli/shared.js +44 -18
  85. package/dist/deploy/agentcore/plan.d.ts +117 -0
  86. package/dist/deploy/agentcore/plan.js +721 -0
  87. package/dist/deploy/agentcore/run.d.ts +73 -0
  88. package/dist/deploy/agentcore/run.js +412 -0
  89. package/dist/deploy/agentcore/zip.d.ts +17 -0
  90. package/dist/deploy/agentcore/zip.js +68 -0
  91. package/dist/deploy/container.d.ts +26 -25
  92. package/dist/deploy/container.js +93 -89
  93. package/dist/deploy/docker/plan.d.ts +1 -1
  94. package/dist/deploy/docker/plan.js +12 -17
  95. package/dist/deploy/fly/plan.d.ts +2 -0
  96. package/dist/deploy/fly/plan.js +27 -19
  97. package/dist/deploy/fly/run.d.ts +12 -1
  98. package/dist/deploy/fly/run.js +36 -2
  99. package/dist/deploy/preflight.d.ts +11 -5
  100. package/dist/deploy/preflight.js +235 -65
  101. package/dist/deploy/railway/plan.d.ts +7 -0
  102. package/dist/deploy/railway/plan.js +41 -16
  103. package/dist/deploy/railway/run.d.ts +8 -1
  104. package/dist/deploy/railway/run.js +7 -2
  105. package/dist/deploy/runner.d.ts +5 -2
  106. package/dist/deploy/runner.js +9 -3
  107. package/dist/dev-supervisor.d.ts +11 -8
  108. package/dist/dev-supervisor.js +53 -51
  109. package/dist/engines/pi/auth.d.ts +8 -7
  110. package/dist/engines/pi/auth.js +12 -10
  111. package/dist/engines/pi/channel.d.ts +1 -1
  112. package/dist/engines/pi/channel.js +5 -5
  113. package/dist/engines/pi/chat.js +2 -2
  114. package/dist/engines/pi/config.d.ts +12 -48
  115. package/dist/engines/pi/config.js +29 -110
  116. package/dist/engines/pi/create.d.ts +32 -24
  117. package/dist/engines/pi/create.js +47 -23
  118. package/dist/engines/pi/definition.d.ts +7 -26
  119. package/dist/engines/pi/definition.js +8 -54
  120. package/dist/engines/pi/harness.d.ts +19 -5
  121. package/dist/engines/pi/harness.js +3 -5
  122. package/dist/engines/pi/login.d.ts +1 -1
  123. package/dist/engines/pi/models.d.ts +3 -3
  124. package/dist/engines/pi/models.js +1 -1
  125. package/dist/engines/pi/{workspace.d.ts → open.d.ts} +31 -25
  126. package/dist/engines/pi/{workspace.js → open.js} +27 -29
  127. package/dist/engines/pi/read-image.d.ts +4 -0
  128. package/dist/engines/pi/read-image.js +62 -0
  129. package/dist/engines/pi/search-tools.d.ts +6 -4
  130. package/dist/engines/pi/search-tools.js +3 -1
  131. package/dist/engines/pi/session-builder.d.ts +2 -2
  132. package/dist/engines/pi/session-builder.js +18 -13
  133. package/dist/engines/pi/tool.d.ts +13 -5
  134. package/dist/engines/pi/tool.js +4 -0
  135. package/dist/engines/pi/wake-tool.d.ts +3 -3
  136. package/dist/env.d.ts +16 -4
  137. package/dist/env.js +43 -5
  138. package/dist/host/node.d.ts +4 -2
  139. package/dist/host/node.js +2 -1
  140. package/dist/loader.d.ts +2 -2
  141. package/dist/loader.js +3 -3
  142. package/dist/log.d.ts +1 -1
  143. package/dist/log.js +1 -1
  144. package/dist/paths.d.ts +138 -0
  145. package/dist/paths.js +326 -0
  146. package/dist/pi.d.ts +2 -2
  147. package/dist/pi.js +2 -2
  148. package/dist/runtime.d.ts +7 -5
  149. package/dist/runtime.js +2 -2
  150. package/dist/scaffold/add-channel.d.ts +7 -3
  151. package/dist/scaffold/add-channel.js +55 -29
  152. package/dist/scaffold/init.d.ts +32 -41
  153. package/dist/scaffold/init.js +161 -185
  154. package/dist/scaffold/templates/env.example +15 -6
  155. package/dist/scaffold/templates/fastagent.config.mjs +1 -1
  156. package/dist/scaffold/templates/gitignore +14 -6
  157. package/dist/scaffold/templates/persona.md +4 -2
  158. package/dist/scaffold/templates/secrets.gitignore +5 -0
  159. package/dist/scaffold/templates.d.ts +1 -7
  160. package/dist/scaffold/templates.js +3 -25
  161. package/dist/scaffold/vendor-skill.d.ts +2 -2
  162. package/dist/scaffold/vendor-skill.js +13 -13
  163. package/dist/schedule/discover.js +4 -4
  164. package/dist/schedule/scheduler.d.ts +40 -1
  165. package/dist/schedule/scheduler.js +89 -56
  166. package/dist/schedule/state.js +1 -1
  167. package/dist/schedule/wake-alarm.d.ts +47 -0
  168. package/dist/schedule/wake-alarm.js +136 -0
  169. package/dist/schedule/wakeups.d.ts +1 -0
  170. package/dist/schedule/wakeups.js +18 -0
  171. package/dist/tunnel.d.ts +3 -3
  172. package/dist/tunnel.js +7 -7
  173. package/package.json +7 -4
  174. package/dist/channels/feishu/owned-threads.d.ts +0 -7
  175. package/dist/channels/feishu/owned-threads.js +0 -47
  176. package/dist/channels/slack/owned-threads.d.ts +0 -6
  177. package/dist/channels/slack/owned-threads.js +0 -43
  178. package/dist/scaffold/templates/gitignore.agentdir-root +0 -5
  179. package/dist/scaffold/templates/gitignore.kit +0 -2
  180. package/dist/workspace.d.ts +0 -9
  181. package/dist/workspace.js +0 -45
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@earendil-works/pi-agent-core";
1
+ import type { AgentHarnessTool, ExecutionToolContext, AgentTool } from "@earendil-works/pi-agent-core";
2
2
  import { z } from "zod";
3
3
  import { type ModuleLoadFailure } from "../../loader.ts";
4
4
  import { type ReadonlySessionManager, type ToolActivation } from "./tool-context.ts";
@@ -34,6 +34,14 @@ export interface DefineToolOptions<I extends z.ZodType> {
34
34
  executionMode?: "sequential" | "parallel";
35
35
  execute: (input: z.infer<I>, ctx: ToolContext) => unknown | Promise<unknown>;
36
36
  }
37
+ /**
38
+ * A tool as MOUNTED: what the harness actually runs. Wider than the authored {@link AgentTool} on
39
+ * purpose — pi's default coding tools read the turn's tool context (its ExecutionEnv) as a fifth
40
+ * `execute` parameter, while fastagent's own tools take four and are assignable to it unchanged.
41
+ * Naming the wider type is what lets `defineTool` stay context-free for authors while both families
42
+ * live in one array; every helper that only inspects or reorders tools is typed on THIS.
43
+ */
44
+ export type MountedTool = AgentHarnessTool<ExecutionToolContext>;
37
45
  /** An AgentTool with fastagent's deferral marker — the type for raw tools handed to fastagent
38
46
  * (`config.tools`, L1/L2 `tools`): plain `AgentTool` has no `deferred`, so an object literal with the
39
47
  * marker would fail excess-property checking against upstream's type. `defineTool` produces it. */
@@ -42,10 +50,10 @@ export type FastagentTool = AgentTool & {
42
50
  };
43
51
  /** Read the {@link DefineToolOptions.deferred} marker off a mounted tool (extra property on the
44
52
  * AgentTool object — pi ignores it). */
45
- export declare function isDeferredTool(tool: AgentTool): boolean;
53
+ export declare function isDeferredTool(tool: MountedTool): boolean;
46
54
  /** The same tool without the deferred marker — for a loader that must stay active (a deferred loader
47
55
  * could never be activated and would strand every deferred tool). */
48
- export declare function stripDeferredMarker(tool: AgentTool): AgentTool;
56
+ export declare function stripDeferredMarker(tool: MountedTool): MountedTool;
49
57
  export declare function defineTool<I extends z.ZodType>(options: DefineToolOptions<I>): FastagentTool;
50
58
  /** A discarded same-name tool (within `tools/`, or against an existing tool). Surfaced, never silent. */
51
59
  export interface ToolCollision {
@@ -68,7 +76,7 @@ export declare function loadTools(dir: string): Promise<{
68
76
  * Merge resolved tools (pi defaults + `config.tools`) with discovered `tools/`, deduped by name.
69
77
  * Existing tools win; dropped discovered tools surface as collisions.
70
78
  */
71
- export declare function mergeDiscoveredTools(existing: AgentTool[], discovered: AgentTool[]): {
72
- tools: AgentTool[];
79
+ export declare function mergeDiscoveredTools(existing: MountedTool[], discovered: AgentTool[]): {
80
+ tools: MountedTool[];
73
81
  collisions: ToolCollision[];
74
82
  };
@@ -11,6 +11,7 @@
11
11
  * });
12
12
  */
13
13
  import { join } from "node:path";
14
+ import { assertInsideAgentDir } from "../../paths.js";
14
15
  import { z } from "zod";
15
16
  import { loadModuleDir } from "../../loader.js";
16
17
  import { turnContext } from "./tool-context.js";
@@ -94,6 +95,9 @@ export function defineTool(options) {
94
95
  * `tools/` dir of its OWN scripts, which is exactly this case.
95
96
  */
96
97
  export async function loadTools(dir) {
98
+ // The same containment guard channels/schedules/skills get — and `tools/` is the one that gets
99
+ // IMPORTED AND EXECUTED, so a symlink escaping the agent dir is exactly what it must refuse.
100
+ await assertInsideAgentDir(dir, "tools");
97
101
  const { modules, failures } = await loadModuleDir(join(dir, "tools"));
98
102
  const byName = new Map();
99
103
  const collisions = [];
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@earendil-works/pi-agent-core";
1
+ import { type MountedTool } from "./tool.ts";
2
2
  /**
3
3
  * Parse a delay to milliseconds: a number is SECONDS; a string MUST carry a unit — `"<n><s|m|h|d>"`
4
4
  * ("30m", "2h", "1d"). Undefined for anything else, INCLUDING a bare numeric string like "120": one
@@ -12,6 +12,6 @@ export declare function parseDelayMs(input: string | number): number | undefined
12
12
  * scheduler poller honors a wake-up) and only when the workspace hasn't defined its own `wake` (that
13
13
  * wins, like any tool collision). The single place the mount decision + collision rule run.
14
14
  */
15
- export declare function withWakeTool(tools: AgentTool[], stateRoot: string, enabled: boolean): AgentTool[];
15
+ export declare function withWakeTool(tools: MountedTool[], stateRoot: string, enabled: boolean): MountedTool[];
16
16
  /** Build the `wake` tool bound to `stateRoot` (where wake-ups persist). */
17
- export declare function makeWakeTool(stateRoot: string, now?: () => Date): AgentTool;
17
+ export declare function makeWakeTool(stateRoot: string, now?: () => Date): MountedTool;
package/dist/env.d.ts CHANGED
@@ -16,9 +16,21 @@ export declare function loadEnvFile(file: string): void;
16
16
  * anything else reading/deciding on .env content (e.g. `add`'s secret pre-fill) must use this, never a
17
17
  * private re-implementation: two parsers of one dialect diverge silently. */
18
18
  export declare function parseEnvContent(content: string): Map<string, string>;
19
+ /** The agent's `.env` file: `<resolved secrets dir>/.env` — default `<agentDir>/.secrets/.env`,
20
+ * moved together with auth.json by `FASTAGENT_SECRETS_DIR` ({@link resolveSecretsDir} in the neutral
21
+ * paths.ts). THE path every reader/writer of the agent's .env must use, so "where do secrets
22
+ * live" cannot diverge across commands. The file's OWN location resolves from the REAL environment:
23
+ * commands locate + load `.env` first, so a `FASTAGENT_SECRETS_DIR` set INSIDE it still relocates
24
+ * auth.json but cannot move the file it is read from. */
25
+ export declare function dotEnvPath(agentDir: string, env?: NodeJS.ProcessEnv): string;
26
+ /** The committable template: `<agentDir>/.secrets/.env.example` — deliberately NOT moved by
27
+ * `FASTAGENT_SECRETS_DIR`: it is authored agent surface that travels with the directory (the
28
+ * scaffolded `.secrets/.gitignore` un-ignores exactly it), while the real values follow the override. */
29
+ export declare function envExamplePath(agentDir: string): string;
19
30
  /**
20
- * Load `<dir>/.env` into `process.env` ({@link loadEnvFile}), treating a MISSING file as normal (no .env)
21
- * — the workspace-facing entry every command + the tunnel use. Only ENOENT is swallowed; any other read
22
- * error (a corrupt/unreadable file) propagates, so a real problem surfaces instead of silently skipping.
31
+ * Load the agent's `.env` ({@link dotEnvPath}) into `process.env` ({@link loadEnvFile}), treating a
32
+ * MISSING file as normal (no .env) — the agent-facing entry every command + the tunnel use. `agentDir`
33
+ * is the AGENT DIR (resolvePlacement().agentDir). Only ENOENT is swallowed; any other read error (a
34
+ * corrupt/unreadable file) propagates, so a real problem surfaces instead of silently skipping.
23
35
  */
24
- export declare function loadDotEnv(dir: string): void;
36
+ export declare function loadDotEnv(agentDir: string): void;
package/dist/env.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { log } from "./log.js";
5
+ import { SECRETS_DIRNAME, resolveSecretsDir } from "./paths.js";
3
6
  /**
4
7
  * Load a `.env` file into `process.env`, matching Node's `--env-file` / `process.loadEnvFile` precedence
5
8
  * on BOTH axes (verified against Node): a real env var wins over the file (an already-set key is kept),
@@ -41,17 +44,52 @@ export function parseEnvContent(content) {
41
44
  }
42
45
  return parsed;
43
46
  }
47
+ /** The agent's `.env` file: `<resolved secrets dir>/.env` — default `<agentDir>/.secrets/.env`,
48
+ * moved together with auth.json by `FASTAGENT_SECRETS_DIR` ({@link resolveSecretsDir} in the neutral
49
+ * paths.ts). THE path every reader/writer of the agent's .env must use, so "where do secrets
50
+ * live" cannot diverge across commands. The file's OWN location resolves from the REAL environment:
51
+ * commands locate + load `.env` first, so a `FASTAGENT_SECRETS_DIR` set INSIDE it still relocates
52
+ * auth.json but cannot move the file it is read from. */
53
+ export function dotEnvPath(agentDir, env = process.env) {
54
+ return join(resolveSecretsDir(agentDir, env), ".env");
55
+ }
56
+ /** The committable template: `<agentDir>/.secrets/.env.example` — deliberately NOT moved by
57
+ * `FASTAGENT_SECRETS_DIR`: it is authored agent surface that travels with the directory (the
58
+ * scaffolded `.secrets/.gitignore` un-ignores exactly it), while the real values follow the override. */
59
+ export function envExamplePath(agentDir) {
60
+ return join(agentDir, SECRETS_DIRNAME, ".env.example");
61
+ }
44
62
  /**
45
- * Load `<dir>/.env` into `process.env` ({@link loadEnvFile}), treating a MISSING file as normal (no .env)
46
- * — the workspace-facing entry every command + the tunnel use. Only ENOENT is swallowed; any other read
47
- * error (a corrupt/unreadable file) propagates, so a real problem surfaces instead of silently skipping.
63
+ * Load the agent's `.env` ({@link dotEnvPath}) into `process.env` ({@link loadEnvFile}), treating a
64
+ * MISSING file as normal (no .env) — the agent-facing entry every command + the tunnel use. `agentDir`
65
+ * is the AGENT DIR (resolvePlacement().agentDir). Only ENOENT is swallowed; any other read error (a
66
+ * corrupt/unreadable file) propagates, so a real problem surfaces instead of silently skipping.
48
67
  */
49
- export function loadDotEnv(dir) {
68
+ export function loadDotEnv(agentDir) {
69
+ const path = dotEnvPath(agentDir);
50
70
  try {
51
- loadEnvFile(join(dir, ".env"));
71
+ loadEnvFile(path);
52
72
  }
53
73
  catch (error) {
54
74
  if (error.code !== "ENOENT")
55
75
  throw error;
56
76
  }
77
+ // A `.env` at the agent's root is the file habit puts there, and nothing reads it. Left silent, the
78
+ // symptom is a setting that appears configured and is not.
79
+ //
80
+ // Scoped to keys FASTAGENT itself reads, because that is the only case we can be sure about: an agent
81
+ // directory can be the author's repository too (the shape `--agent-dir .` exists for), where a root
82
+ // `.env` is their APPLICATION's — and "move the values" would break it. Nothing here can tell those
83
+ // apart, so the broader guess (warn whenever the agent has no env of its own) was a warning on every
84
+ // boot, with destructive advice, aimed at the population that never had the problem. Channel
85
+ // credentials are deliberately out of scope: a channel that cannot find its token reports that itself,
86
+ // and it is the one that knows the name.
87
+ const stray = join(agentDir, ".env");
88
+ if (stray === path || !existsSync(stray))
89
+ return;
90
+ const misplaced = [...parseEnvContent(readFileSync(stray, "utf8")).keys()].filter((k) => k.startsWith("FASTAGENT_"));
91
+ if (misplaced.length > 0) {
92
+ log.warn(`[fastagent] ${stray} is NOT read — it sets ${misplaced.join(", ")}, and this agent's env lives at ` +
93
+ `${path}; move those values there`);
94
+ }
57
95
  }
@@ -6,9 +6,9 @@ export type ChannelHandler = (req: Request) => Response | Promise<Response>;
6
6
  export type Routes = Record<string, ChannelHandler>;
7
7
  /**
8
8
  * What the framework hands a channel at mount time: the assembled agent plus the resolved state ROOT
9
- * (absolute; `FASTAGENT_STATE_DIR` > `<dir>/.fastagent`). Channels derive their OWN durable home from
9
+ * (absolute; `FASTAGENT_STATE_DIR` > `<root>/.state`). Channels derive their OWN durable home from
10
10
  * it (`<stateRoot>/channels/<kind>/`) — they never anchor on `process.cwd()`. env is the OPERATOR
11
- * input plane; this context is how the resolved result reaches code (embedders without the workspace
11
+ * input plane; this context is how the resolved result reaches code (embedders without the agent
12
12
  * opener construct it explicitly).
13
13
  */
14
14
  export interface ChannelContext {
@@ -48,9 +48,11 @@ export declare function router(routes: Routes): ChannelHandler;
48
48
  * Serve `handler` on a Node HTTP server. Thin mechanism: bind, report the port, let the caller stop
49
49
  * accepting or force-close active connections — no logging/signals/exit (the CLI owns those).
50
50
  * `listening` resolves with the bound port (useful for port 0) or rejects on a bind error.
51
+ * `host` is the bind address; unset means all interfaces (what containers need).
51
52
  */
52
53
  export declare function serveNode(handler: ChannelHandler, options: {
53
54
  port: number;
55
+ host?: string;
54
56
  }): {
55
57
  listening: Promise<number>;
56
58
  close: () => Promise<void>;
package/dist/host/node.js CHANGED
@@ -35,12 +35,13 @@ export function router(routes) {
35
35
  * Serve `handler` on a Node HTTP server. Thin mechanism: bind, report the port, let the caller stop
36
36
  * accepting or force-close active connections — no logging/signals/exit (the CLI owns those).
37
37
  * `listening` resolves with the bound port (useful for port 0) or rejects on a bind error.
38
+ * `host` is the bind address; unset means all interfaces (what containers need).
38
39
  */
39
40
  export function serveNode(handler, options) {
40
41
  const server = createServer(nodeListener(async (req) => handler(req)));
41
42
  const listening = new Promise((resolve, reject) => {
42
43
  server.once("error", reject); // a bind failure surfaces here, before "listening"
43
- server.listen(options.port, () => {
44
+ server.listen({ port: options.port, host: options.host }, () => {
44
45
  server.off("error", reject);
45
46
  resolve(server.address().port);
46
47
  });
package/dist/loader.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /** Whether `name` is an importable workspace module (a discovery candidate, not a type declaration). */
1
+ /** Whether `name` is an importable agent module (a discovery candidate, not a type declaration). */
2
2
  export declare function isModuleFile(name: string): boolean;
3
3
  export interface DiscoveredModule {
4
4
  /** Basename without extension — the authoritative name for tools/channels. */
@@ -10,7 +10,7 @@ export interface DiscoveredModule {
10
10
  default?: unknown;
11
11
  };
12
12
  }
13
- /** A workspace module that failed to load, surfaced as data so its caller can report the exact file.
13
+ /** An agent module that failed to load, surfaced as data so its caller can report the exact file.
14
14
  * `loadModuleDir` fills it for import failures; domain loaders add validation failures. The caller owns
15
15
  * policy: tools/schedules may skip one bad file, while serving treats a broken declared channel as fatal. */
16
16
  export interface ModuleLoadFailure {
package/dist/loader.js CHANGED
@@ -10,7 +10,7 @@ import { readdir } from "node:fs/promises";
10
10
  import { basename, extname, join } from "node:path";
11
11
  import { pathToFileURL } from "node:url";
12
12
  const MODULE_EXTS = new Set([".ts", ".js", ".mjs"]);
13
- /** Whether `name` is an importable workspace module (a discovery candidate, not a type declaration). */
13
+ /** Whether `name` is an importable agent module (a discovery candidate, not a type declaration). */
14
14
  export function isModuleFile(name) {
15
15
  return MODULE_EXTS.has(extname(name)) && !name.endsWith(".d.ts");
16
16
  }
@@ -60,10 +60,10 @@ export async function loadModuleDir(subDir) {
60
60
  */
61
61
  export function moduleLoadHint(error) {
62
62
  if (error.code === "ERR_MODULE_NOT_FOUND" || /Cannot find (package|module)/.test(error.message)) {
63
- return "\n (a dependency is not installed — run `npm install` in the workspace)";
63
+ return "\n (a dependency is not installed — run `npm install` in the agent dir)";
64
64
  }
65
65
  if (/import statement outside a module|Unexpected token 'export'|ERR_REQUIRE_ESM/.test(error.message)) {
66
- return '\n (this workspace must be ESM — set "type": "module" in package.json)';
66
+ return '\n (the agent dir must be ESM — set "type": "module" in package.json)';
67
67
  }
68
68
  return "";
69
69
  }
package/dist/log.d.ts CHANGED
@@ -15,7 +15,7 @@ export interface Logger {
15
15
  warn(msg: string): void;
16
16
  error(msg: string): void;
17
17
  }
18
- /** A standalone logger over an explicit sink — used in tests to assert level gating without the singleton. */
18
+ /** An embedded logger over an explicit sink — used in tests to assert level gating without the singleton. */
19
19
  export declare function createLogger(opts: {
20
20
  level: LogLevel;
21
21
  sink?: (line: string) => void;
package/dist/log.js CHANGED
@@ -11,7 +11,7 @@
11
11
  const ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
12
12
  const isLevel = (s) => s in ORDER;
13
13
  const format = (level, msg) => `${level.toUpperCase().padEnd(5)} ${msg}`;
14
- /** A standalone logger over an explicit sink — used in tests to assert level gating without the singleton. */
14
+ /** An embedded logger over an explicit sink — used in tests to assert level gating without the singleton. */
15
15
  export function createLogger(opts) {
16
16
  const sink = opts.sink ?? ((line) => console.error(line));
17
17
  const make = (level) => (msg) => {
@@ -0,0 +1,138 @@
1
+ /**
2
+ * The directory name `init` gives a nested agent (`<workspace>/fastagent/`) unless `--agent-dir` names
3
+ * another. A DEFAULT, not a rule: resolution reads the config marker and never a name, so renaming an
4
+ * agent directory changes nothing about how it resolves. Visible on purpose — the agent directory holds
5
+ * the AUTHOR's content (persona, skills, tool code: code, not tool configuration), so it follows the
6
+ * repo convention for code (a plain directory), while fastagent's own machinery inside it (`.secrets/`,
7
+ * `.state/`) keeps the dot prefix.
8
+ */
9
+ export declare const DEFAULT_AGENT_DIRNAME = "fastagent";
10
+ /** The user-global machinery home under `$HOME` — hidden, per the dotfile convention for per-user
11
+ * tool homes (`~/.cargo`, `~/.docker`); unrelated to {@link DEFAULT_AGENT_DIRNAME}, which only names
12
+ * what `init` creates.
13
+ * It carries the same shape inside it as an agent dir does (`~/.fastagent/.secrets/auth.json`), so the
14
+ * resolvers below need no special case: `login` outside any agent simply hands them this directory. */
15
+ export declare const GLOBAL_HOME_DIR = ".fastagent";
16
+ /** The secrets segment inside an agent dir (or the global home): every PATH fastagent resolves —
17
+ * `.env`, `.env.example`, auth.json, the scaffold's write — derives from it, so they cannot drift
18
+ * apart. `FASTAGENT_SECRETS_DIR` relocates the RESOLVED dir ({@link resolveSecretsDir}), never this
19
+ * name. The scaffold's ignore templates are real files the author owns from `init` on, so they spell
20
+ * their rules out as literal text — renaming this constant means editing them too. */
21
+ export declare const SECRETS_DIRNAME = ".secrets";
22
+ /** The state segment inside an agent dir — same rule and same template caveat as {@link SECRETS_DIRNAME}. */
23
+ export declare const STATE_DIRNAME = ".state";
24
+ /** The config filenames, in load precedence. ONE source: the loader (below) and `scaffoldAgent`'s
25
+ * already-an-agent refusal both read this, so "is there a config?" can't diverge between them. */
26
+ export declare const AGENT_CONFIG_NAMES: readonly ["fastagent.config.ts", "fastagent.config.js", "fastagent.config.mjs"];
27
+ export interface ResolvedPlacement {
28
+ /** The AGENT directory — where the definition (persona.md/skills/tools/channels/schedules), the
29
+ * config, and the machinery dirs (`.secrets/`, `.state/`) live. Absolute. */
30
+ agentDir: string;
31
+ /** The WORKSPACE — what the agent works ON: its cwd, and the start of the ② context walk. ALWAYS the
32
+ * directory fastagent was pointed at, which makes it the agent dir's PARENT when the agent was found
33
+ * one level down, and the agent dir ITSELF when you aimed straight at it. Absolute.
34
+ * `agentDir === workspace` is the only discriminant — there is no mode field, because there are no
35
+ * two placements to distinguish: there is one lookup and the directory you gave it. The naming
36
+ * follows git: the repository sits at the root of its working tree, and WORK belongs to the tree. */
37
+ workspace: string;
38
+ }
39
+ /**
40
+ * The agents `dir` resolves over: ITSELF when it holds a config, else the ones directly inside it —
41
+ * never both, because aiming at an agent can only mean that agent. Exported for `init`, which asks the
42
+ * same question this lookup asks: which agents would `dir` resolve over, before and after scaffolding.
43
+ */
44
+ export declare function agentsAt(dir: string): string[];
45
+ /**
46
+ * The one-line hint for "you pointed at the agent, but the project around it is what you meant" — or
47
+ * undefined. The workspace being whatever you aimed at is deliberate (a deployed box may hold nothing
48
+ * but the agent), and the cost is that `cd my-agent && fastagent dev` legitimately narrows the agent's
49
+ * WORKSPACE to its own directory: its cwd, its coding tools' root, and deploy's build context. (②
50
+ * context is not affected — that walk climbs ancestors either way.) Resolution must not guess which you
51
+ * wanted, so this is a HINT — and a hint may use the heuristic ("the parent carries an AGENTS.md or a
52
+ * .git") that a rule may not.
53
+ *
54
+ * It suggests a command only after RUNNING the lookup that command would run, because a hint that dead-ends
55
+ * is worse than none: with several agents beside this one, `..` refuses and names them — sending the
56
+ * reader to a refusal whose own advice points back here.
57
+ */
58
+ export declare function workspaceHint({ agentDir, workspace }: ResolvedPlacement, env?: NodeJS.ProcessEnv): string | undefined;
59
+ /** The agent dir for `dir`, or undefined when there is none — {@link findPlacement} without the pair,
60
+ * and without the throw. `login` is the caller: it is the one command allowed to run outside an agent,
61
+ * so it needs the answer as a value rather than as a refusal. */
62
+ export declare function findAgentDir(dir: string): string | undefined;
63
+ /**
64
+ * The agent whose DEFINITION contains `dir` — scaffolding there would make the new agent part of the
65
+ * outer one's loaded surface rather than an agent of its own. Narrower than {@link enclosingAgentDir} on
66
+ * purpose: an agent owns only what it LOADS ({@link LOADED_SURFACE}); the rest of its directory is the
67
+ * author's tree, where a second agent (a monorepo package, say) is a legitimate thing to create.
68
+ * `enclosingAgentDir` answers a different question ("where do I `cd` to?"), and for that an agent's
69
+ * `src/` genuinely IS inside it.
70
+ */
71
+ export declare function agentDefinitionOwner(dir: string): string | undefined;
72
+ /**
73
+ * Why `dir` is not an agent, when it has its OWN way out — or undefined when it is simply not near one.
74
+ * Two positions qualify: standing INSIDE an agent (its `tools/`, its `src/`), and standing on a
75
+ * directory whose several agents nothing selects between. Both matter because the generic advice ("run
76
+ * `fastagent init`") would not help — the agent already exists, one step away.
77
+ *
78
+ * Exported because `login` is the one command allowed to run outside an agent, and it must tell "truly
79
+ * outside" (→ the global credential) from "a dead end" (→ refuse, like every other command).
80
+ */
81
+ export declare function placementDeadEnd(dir: string, env?: NodeJS.ProcessEnv): string | undefined;
82
+ /**
83
+ * Resolve a directory into its placement — the ONE owner of the rule ({@link findPlacement}): `dir` is
84
+ * the workspace, and the agent is the single `fastagent.config.*` holder at it or one level inside.
85
+ * Placement is never configured and never detected from surroundings; it is that lookup and the
86
+ * directory you pointed at.
87
+ *
88
+ * Anything else throws (fail visibly). Resolution never walks UP — an agent must not be claimed from
89
+ * arbitrarily deep inside it — but the MESSAGE reads the path, so each dead end gets the exit that fits
90
+ * it ({@link placementDeadEnd}).
91
+ */
92
+ export declare function resolvePlacement(dir: string, env?: NodeJS.ProcessEnv): ResolvedPlacement;
93
+ /** How to WRITE a path for someone standing in `cwd`: relative when it is inside `cwd`, absolute when
94
+ * it climbs out (a `../../..` is noise), and undefined when it IS `cwd` (nothing to say). ONE policy,
95
+ * shared by `init`'s `cd` step, `add`'s next-steps paths and `fire`'s "looked in" hint — they all answer
96
+ * the same question, which is a placement-PRESENTATION question, not a scaffolding one. */
97
+ export declare function displayPath(cwd: string, dir: string): string | undefined;
98
+ /** Does a path exist? Plain fs, no placement in it — it lives here because `paths.ts` is where the
99
+ * neutral path helpers are, and the scaffolder is not a utility home for the CLI and deploy. */
100
+ export declare function exists(p: string): Promise<boolean>;
101
+ /**
102
+ * Resolve a user-supplied path override (a CLI flag or an env var) to an absolute path, expanding a
103
+ * leading `~`/`~/` to the home dir FIRST. Path-valued config from `.env` (or any non-shell source)
104
+ * never gets the shell's `~` expansion, so a bare `resolve("~/x")` would silently create a literal `~`
105
+ * directory — a fail-silently footgun for a secret/state path. Expanding here makes `~` mean home
106
+ * everywhere these knobs are read.
107
+ */
108
+ export declare function resolveOverridePath(raw: string | undefined): string | undefined;
109
+ /**
110
+ * The resolved state root — the durable machine-state home (sessions/, channels/<kind>/, schedule/,
111
+ * control.json): `FASTAGENT_STATE_DIR` env > `<agentDir>/.state`. Absolute, so channels and the
112
+ * startup report agree regardless of cwd. Definition: mutable runtime state — single lifecycle
113
+ * (precious, survives redeploy), single process; a container points this at its mounted volume.
114
+ * Secrets are NOT here — they live under {@link resolveSecretsDir} (a different deploy lifecycle:
115
+ * secret store vs volume). The finer knob (`FASTAGENT_SESSIONS_DIR`) still overrides its path on top.
116
+ *
117
+ * `FASTAGENT_STATE_DIR` is an OPERATOR override, so a relative value resolves against `process.cwd()`
118
+ * — the CLI convention its sibling knobs share (`resolveOverridePath`), NOT against `dir`. Only the
119
+ * DEFAULT (`<root>/.state`) is dir-anchored.
120
+ */
121
+ export declare function resolveStateRoot(dir: string, env?: NodeJS.ProcessEnv): string;
122
+ /**
123
+ * The resolved secrets dir — everything fastagent manages that must NEVER leave the machine (the
124
+ * agent's `.env` + auth.json): `FASTAGENT_SECRETS_DIR` env > `<agentDir>/.secrets`. Split from
125
+ * the state root on deploy lifecycle: secrets travel through the host's secret store (env vars / the
126
+ * auth seed), state through a volume. A deployed box sets both env knobs at its volume (e.g.
127
+ * `/data/.secrets`, `/data/.state`) so a seeded-then-ROTATED OAuth credential persists across
128
+ * restarts. The `.env`'s OWN location resolves from the REAL environment — commands locate and load
129
+ * `.env` before anything else, so a `FASTAGENT_SECRETS_DIR` set INSIDE `.env` still relocates
130
+ * auth.json but cannot move the file it is read from (env.ts dotEnvPath).
131
+ */
132
+ export declare function resolveSecretsDir(dir: string, env?: NodeJS.ProcessEnv): string;
133
+ /**
134
+ * Guard that `<agentDir>/<name>` resolves INSIDE the agent dir — a symlink that escapes (or an
135
+ * absolute target) is rejected, so discovery/scaffolding never reaches out of the definition directory.
136
+ * A missing target is fine (nothing to guard yet).
137
+ */
138
+ export declare function assertInsideAgentDir(agentDir: string, name: string): Promise<void>;