@fastagent-sh/fastagent 0.22.1 → 0.24.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.
Files changed (128) hide show
  1. package/dist/agent.d.ts +10 -0
  2. package/dist/channels/agentcore-protocol.d.ts +2 -1
  3. package/dist/channels/agentcore-service.js +10 -18
  4. package/dist/channels/agentcore.js +6 -3
  5. package/dist/channels/control.js +23 -0
  6. package/dist/channels/kit/preview-kit.d.ts +6 -1
  7. package/dist/channels/kit/preview-kit.js +21 -4
  8. package/dist/channels/slack/config-api.d.ts +2 -2
  9. package/dist/channels/slack/config-api.js +2 -2
  10. package/dist/channels/slack/onboard.js +2 -2
  11. package/dist/channels/slack/preview.js +2 -1
  12. package/dist/channels/slack/register-webhook.js +2 -2
  13. package/dist/channels/slack/slack-api.js +4 -4
  14. package/dist/cli/add-feishu.js +4 -2
  15. package/dist/cli/auth-view.d.ts +15 -6
  16. package/dist/cli/auth-view.js +24 -8
  17. package/dist/cli/box-login.d.ts +33 -0
  18. package/dist/cli/box-login.js +162 -0
  19. package/dist/cli/commands/deploy/agentcore.js +48 -11
  20. package/dist/cli/commands/deploy/docker.js +24 -13
  21. package/dist/cli/commands/deploy/fly.js +30 -7
  22. package/dist/cli/commands/deploy/railway.js +21 -7
  23. package/dist/cli/commands/deploy/shared.d.ts +18 -17
  24. package/dist/cli/commands/deploy/shared.js +20 -21
  25. package/dist/cli/commands/destroy.js +2 -2
  26. package/dist/cli/commands/info.js +15 -12
  27. package/dist/cli/commands/invoke.js +2 -2
  28. package/dist/cli/commands/login.d.ts +7 -1
  29. package/dist/cli/commands/login.js +123 -10
  30. package/dist/cli/commands/logs.js +2 -2
  31. package/dist/cli/commands/models.d.ts +4 -1
  32. package/dist/cli/commands/models.js +37 -4
  33. package/dist/cli/commands/routine-run.js +2 -2
  34. package/dist/cli/commands/start.d.ts +0 -5
  35. package/dist/cli/commands/start.js +3 -13
  36. package/dist/cli/invoke-stream.js +14 -0
  37. package/dist/cli/kernel.d.ts +2 -0
  38. package/dist/cli/kernel.js +1 -1
  39. package/dist/cli/login-relay.d.ts +38 -0
  40. package/dist/cli/login-relay.js +121 -0
  41. package/dist/cli/program.js +46 -8
  42. package/dist/cli/shared.d.ts +6 -5
  43. package/dist/cli/shared.js +29 -36
  44. package/dist/deploy/agentcore/plan.d.ts +10 -11
  45. package/dist/deploy/agentcore/plan.js +8 -14
  46. package/dist/deploy/agentcore/run.d.ts +8 -1
  47. package/dist/deploy/agentcore/run.js +20 -14
  48. package/dist/deploy/agentcore/shell.d.ts +25 -0
  49. package/dist/deploy/agentcore/shell.js +202 -0
  50. package/dist/deploy/box-shell.d.ts +43 -0
  51. package/dist/deploy/box-shell.js +28 -0
  52. package/dist/deploy/build-context.js +16 -7
  53. package/dist/deploy/channel-ingress.d.ts +20 -1
  54. package/dist/deploy/channel-ingress.js +34 -7
  55. package/dist/deploy/container.d.ts +14 -0
  56. package/dist/deploy/container.js +45 -1
  57. package/dist/deploy/docker/plan.d.ts +7 -3
  58. package/dist/deploy/docker/plan.js +9 -14
  59. package/dist/deploy/docker/run.d.ts +14 -7
  60. package/dist/deploy/docker/run.js +58 -35
  61. package/dist/deploy/fly/plan.d.ts +6 -3
  62. package/dist/deploy/fly/plan.js +6 -8
  63. package/dist/deploy/fly/run.d.ts +8 -2
  64. package/dist/deploy/fly/run.js +15 -25
  65. package/dist/deploy/preflight.d.ts +9 -7
  66. package/dist/deploy/preflight.js +92 -20
  67. package/dist/deploy/railway/plan.d.ts +6 -3
  68. package/dist/deploy/railway/plan.js +7 -9
  69. package/dist/deploy/railway/run.d.ts +10 -6
  70. package/dist/deploy/railway/run.js +29 -13
  71. package/dist/deploy/runner.d.ts +16 -0
  72. package/dist/deploy/runner.js +31 -0
  73. package/dist/deploy/secrets.d.ts +4 -13
  74. package/dist/deploy/secrets.js +8 -27
  75. package/dist/dev-supervisor.js +7 -4
  76. package/dist/engines/pi/agent-models.d.ts +83 -0
  77. package/dist/engines/pi/agent-models.js +120 -0
  78. package/dist/engines/pi/agent-session-factory.d.ts +13 -13
  79. package/dist/engines/pi/agent-session-factory.js +132 -159
  80. package/dist/engines/pi/auth.d.ts +56 -3
  81. package/dist/engines/pi/auth.js +68 -109
  82. package/dist/engines/pi/config.d.ts +0 -25
  83. package/dist/engines/pi/config.js +3 -37
  84. package/dist/engines/pi/create.d.ts +34 -21
  85. package/dist/engines/pi/create.js +90 -57
  86. package/dist/engines/pi/entry-images.d.ts +14 -0
  87. package/dist/engines/pi/entry-images.js +23 -0
  88. package/dist/engines/pi/invoke-session.js +88 -20
  89. package/dist/engines/pi/locked-file.d.ts +9 -0
  90. package/dist/engines/pi/locked-file.js +102 -0
  91. package/dist/engines/pi/login.d.ts +27 -14
  92. package/dist/engines/pi/login.js +41 -18
  93. package/dist/engines/pi/machine.d.ts +23 -0
  94. package/dist/engines/pi/machine.js +24 -2
  95. package/dist/engines/pi/models.d.ts +120 -36
  96. package/dist/engines/pi/models.js +316 -66
  97. package/dist/engines/pi/open.d.ts +65 -20
  98. package/dist/engines/pi/open.js +88 -34
  99. package/dist/engines/pi/report.d.ts +3 -0
  100. package/dist/engines/pi/report.js +11 -0
  101. package/dist/engines/pi/service.d.ts +2 -2
  102. package/dist/engines/pi/service.js +1 -0
  103. package/dist/engines/pi/session-builder.d.ts +3 -2
  104. package/dist/engines/pi/session-builder.js +30 -76
  105. package/dist/engines/pi/session-control.d.ts +4 -4
  106. package/dist/engines/pi/session-control.js +86 -43
  107. package/dist/engines/pi/session-settings.d.ts +9 -0
  108. package/dist/engines/pi/session-settings.js +17 -0
  109. package/dist/engines/pi/session-store.d.ts +2 -2
  110. package/dist/engines/pi/tool-context.d.ts +3 -5
  111. package/dist/engines/pi/tool-context.js +4 -16
  112. package/dist/engines/pi/tool.d.ts +32 -21
  113. package/dist/engines/pi/tool.js +21 -25
  114. package/dist/engines/pi/turn-kit.d.ts +18 -9
  115. package/dist/engines/pi/turn-kit.js +115 -32
  116. package/dist/open-url.d.ts +15 -1
  117. package/dist/open-url.js +35 -3
  118. package/dist/paths.d.ts +4 -2
  119. package/dist/paths.js +6 -2
  120. package/dist/pi.d.ts +5 -3
  121. package/dist/pi.js +3 -2
  122. package/dist/service.d.ts +5 -8
  123. package/dist/session-remote.js +21 -0
  124. package/dist/session.d.ts +88 -15
  125. package/dist/session.js +5 -1
  126. package/package.json +6 -5
  127. package/dist/engines/pi/search-tools.d.ts +0 -4
  128. package/dist/engines/pi/search-tools.js +0 -109
@@ -1,112 +1,29 @@
1
1
  /**
2
2
  * Auth for the pi engine: a read-WRITE {@link CredentialStore} over a fastagent credentials file, consumed by the
3
- * `Models` collection (models.ts). The write path refuses to overwrite a corrupt file, so a torn read never clobbers
4
- * the other providers' credentials.
3
+ * `Models` collection (models.ts), and which of those files an agent reads. The write path refuses to overwrite a
4
+ * corrupt file, so a torn read never clobbers the other providers' credentials.
5
5
  */
6
- import { existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, realpathSync, writeFileSync } from "node:fs";
7
- import { homedir } from "node:os";
8
- import { dirname, join, resolve } from "node:path";
9
- import { GLOBAL_HOME_DIR, SECRETS_DIRNAME, SECRET_FILE_MODE } from "../../paths.js";
10
- import { writeFileAtomic } from "../../atomic-write.js";
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { globalHome, SECRETS_DIRNAME, resolveOverridePath, resolveSecretsDir } from "../../paths.js";
11
9
  import { log } from "../../log.js";
12
- import lockfile from "proper-lockfile";
10
+ import { withLockedFile } from "./locked-file.js";
13
11
  /**
14
12
  * The GLOBAL fastagent credentials file (distinct from pi's `~/.pi`), under the user-global machinery home
15
13
  * `~/.fastagent/`.
16
14
  */
17
- export const GLOBAL_AUTH_PATH = join(homedir(), GLOBAL_HOME_DIR, SECRETS_DIRNAME, "auth.json");
15
+ export const GLOBAL_AUTH_PATH = join(globalHome(), SECRETS_DIRNAME, "auth.json");
16
+ /** The one refusal for a caller that names both sources: silently preferring one would hide the other's mistake. */
17
+ export function assertOneCredentialSource(options) {
18
+ if (options.authPath !== undefined && options.credentialStore !== undefined) {
19
+ throw new Error("pass authPath or credentialStore, not both: a supplied store replaces the credentials file");
20
+ }
21
+ }
18
22
  /** A valid stored credential, or undefined — a foreign/old entry reads as not-configured, not a crash. */
19
23
  function pick(creds, providerId) {
20
24
  const cred = creds[providerId];
21
25
  return cred && (cred.type === "oauth" || cred.type === "api_key") ? cred : undefined;
22
26
  }
23
- const AUTH_FILE_WRITE_OPTIONS = { encoding: "utf8", mode: SECRET_FILE_MODE };
24
- /**
25
- * The file a write must land IN, which is not always the path it is addressed BY. pi writes the auth file in place,
26
- * so it follows a symlink to the target; an atomic rename would replace the link with a regular file instead, and
27
- * from then on the two tools would be editing different files — the same split the shared lock exists to prevent,
28
- * arriving one write later. A dangling link (a dotfile manager that has not checked the target out yet) still names
29
- * where the credentials belong, so it is followed too rather than overwritten.
30
- */
31
- function writeTarget(authPath) {
32
- if (existsSync(authPath))
33
- return realpathSync(authPath);
34
- const link = lstatSync(authPath, { throwIfNoEntry: false });
35
- // ponytail: one hop. A chain of dangling links would have its second link replaced; resolve iteratively if that
36
- // ever shows up in a real layout.
37
- return link?.isSymbolicLink() ? resolve(dirname(authPath), readlinkSync(authPath)) : authPath;
38
- }
39
- /** Serialized cross-process read-modify-write of the credentials file. */
40
- async function withLockedAuthFile(authPath, fn) {
41
- mkdirSync(dirname(authPath), { recursive: true });
42
- if (!existsSync(authPath)) {
43
- try {
44
- // `wx` makes this an exclusive CREATE, which is the one case `writeFileSync` honours `mode` in — so no chmod
45
- // after it (unlike writeFileAtomic, whose temp may already exist from a crashed writer).
46
- writeFileSync(authPath, "{}", { ...AUTH_FILE_WRITE_OPTIONS, flag: "wx" });
47
- }
48
- catch (error) {
49
- // EEXIST: the path IS taken, by something `existsSync` does not see through — either another process created
50
- // the file just now (its credentials must not be clobbered) or the path is a dangling symlink. Both are left
51
- // alone: the write below resolves where they belong.
52
- if (error.code !== "EEXIST")
53
- throw error;
54
- }
55
- }
56
- let compromised;
57
- const throwIfCompromised = () => {
58
- if (compromised)
59
- throw compromised;
60
- };
61
- const release = await lockfile.lock(authPath, {
62
- // MUST match pi-coding-agent's `core/auth-storage.js`, which locks with `realpath: false`. When the auth file
63
- // itself is a symlink (dotfile managers do this), the two settings name DIFFERENT lock files and both locks can
64
- // be held at once — so `pi` and `fastagent` would refresh the same provider concurrently, and a rotated refresh
65
- // token logs one of them out. Resolving the link would be the stronger rule, but only if BOTH sides did it, and
66
- // we do not own the other side.
67
- realpath: false,
68
- retries: { retries: 10, factor: 2, minTimeout: 100, maxTimeout: 10_000, randomize: true },
69
- stale: 30_000,
70
- onCompromised: (error) => {
71
- compromised = error;
72
- },
73
- });
74
- let result;
75
- try {
76
- throwIfCompromised();
77
- const current = existsSync(authPath) ? readFileSync(authPath, "utf8") : undefined;
78
- const out = await fn(current);
79
- throwIfCompromised();
80
- // Rename, not an in-place rewrite: it is what lets `read` stay unlocked, and it is the only spelling that applies
81
- // the mode before the content is reachable.
82
- if (out.next !== undefined) {
83
- writeFileAtomic(writeTarget(authPath), out.next, SECRET_FILE_MODE);
84
- }
85
- throwIfCompromised();
86
- result = out.result;
87
- }
88
- catch (error) {
89
- // The primary failure stays the signal; unlock noise must not mask it.
90
- try {
91
- await release();
92
- }
93
- catch {
94
- // Secondary: a compromised or stale-reclaimed lock often cannot release cleanly.
95
- }
96
- throw error;
97
- }
98
- // Success path: a failed release is a real cleanup failure (the leftover auth.json.lock stalls the next writer for
99
- // the staleness window with zero diagnostics), so it surfaces instead of resolving a silently degraded operation.
100
- try {
101
- await release();
102
- }
103
- catch (releaseError) {
104
- if (compromised === undefined)
105
- throw releaseError;
106
- }
107
- throwIfCompromised();
108
- return result;
109
- }
110
27
  /** Decode the credentials JSON, shared by the read and write paths. */
111
28
  function decodeCreds(raw) {
112
29
  let parsed;
@@ -153,16 +70,25 @@ function parseForWrite(raw, where) {
153
70
  }
154
71
  /**
155
72
  * A read-write `CredentialStore` over the given credentials file (default {@link GLOBAL_AUTH_PATH}; the directory
156
- * opener passes the project-level `<root>/.secrets/auth.json`), optionally falling back to a second file.
73
+ * opener passes the project-level `<root>/.secrets/auth.json`), optionally falling back to a second file. A path is
74
+ * read like `FASTAGENT_AUTH_PATH` (`~` is the home directory, a relative one resolves against the cwd), so every
75
+ * caller that names a file names the same one.
157
76
  *
158
77
  * The fallback is PER PROVIDER, not per file: logging one provider into a project must not hide the others a person
159
78
  * already has globally. And the layer a credential was READ from is the layer its refresh is written back to —
160
79
  * anything else would conjure a second holder of the same OAuth grant, which is the failure this whole area exists
161
80
  * to avoid. A provider present in neither layer is new, and new credentials belong to the primary.
81
+ *
82
+ * The fallback also yields to `projectAuthenticates`: a provider the project authenticates some other way (an env key,
83
+ * its own models.json) never reaches it — for reading, listing or writing back.
162
84
  */
163
- export function fastagentCredentialStore(authPath = GLOBAL_AUTH_PATH, options = {}) {
85
+ export function fastagentCredentialStore(path, options = {}) {
164
86
  const warn = options.warn ?? ((message) => log.warn(message));
165
- const fallback = options.fallbackPath !== undefined && options.fallbackPath !== authPath ? options.fallbackPath : undefined;
87
+ const authPath = resolveOverridePath(path) ?? GLOBAL_AUTH_PATH;
88
+ const fallbackPath = resolveOverridePath(options.fallbackPath);
89
+ const fallback = fallbackPath !== undefined && fallbackPath !== authPath ? fallbackPath : undefined;
90
+ /** The fallback, when it may serve this provider at all. */
91
+ const fallbackFor = async (providerId) => fallback === undefined || (await options.projectAuthenticates?.(providerId)) ? undefined : fallback;
166
92
  /**
167
93
  * The file that owns this provider: where it already is, else the primary.
168
94
  *
@@ -171,18 +97,21 @@ export function fastagentCredentialStore(authPath = GLOBAL_AUTH_PATH, options =
171
97
  * "one grant, one copy" rule can be lost. Re-checking under the lock means locking both files in a fixed order;
172
98
  * worth it only if concurrent logins stop being a rounding error.
173
99
  */
174
- const owner = (providerId) => {
175
- if (fallback === undefined)
100
+ const owner = async (providerId) => {
101
+ const second = await fallbackFor(providerId);
102
+ if (second === undefined)
176
103
  return authPath;
177
104
  const primary = readCreds(authPath, warn);
178
105
  if (primary && pick(primary, providerId))
179
106
  return authPath;
180
- const secondary = readCreds(fallback, warn);
181
- return secondary && pick(secondary, providerId) ? fallback : authPath;
107
+ const secondary = readCreds(second, warn);
108
+ return secondary && pick(secondary, providerId) ? second : authPath;
182
109
  };
183
110
  return {
111
+ layerOf: owner,
184
112
  async read(providerId) {
185
- for (const path of fallback === undefined ? [authPath] : [authPath, fallback]) {
113
+ const second = await fallbackFor(providerId);
114
+ for (const path of second === undefined ? [authPath] : [authPath, second]) {
186
115
  const creds = readCreds(path, warn);
187
116
  const found = creds && pick(creds, providerId);
188
117
  if (found)
@@ -196,15 +125,17 @@ export function fastagentCredentialStore(authPath = GLOBAL_AUTH_PATH, options =
196
125
  const infos = new Map();
197
126
  for (const path of fallback === undefined ? [authPath] : [fallback, authPath]) {
198
127
  for (const [providerId, cred] of Object.entries(readCreds(path, warn) ?? {})) {
128
+ if (path === fallback && (await fallbackFor(providerId)) === undefined)
129
+ continue;
199
130
  if (cred && (cred.type === "oauth" || cred.type === "api_key"))
200
131
  infos.set(providerId, { providerId, type: cred.type });
201
132
  }
202
133
  }
203
134
  return [...infos.values()];
204
135
  },
205
- modify(providerId, fn) {
206
- const path = owner(providerId);
207
- return withLockedAuthFile(path, async (current) => {
136
+ async modify(providerId, fn) {
137
+ const path = await owner(providerId);
138
+ return withLockedFile(path, async (current) => {
208
139
  const creds = parseForWrite(current, path); // corrupt → throw → no clobber
209
140
  const next = await fn(pick(creds, providerId));
210
141
  if (next === undefined)
@@ -214,12 +145,12 @@ export function fastagentCredentialStore(authPath = GLOBAL_AUTH_PATH, options =
214
145
  });
215
146
  },
216
147
  async delete(providerId) {
217
- const path = owner(providerId);
148
+ const path = await owner(providerId);
218
149
  // No-op when nothing is stored: do NOT take the lock (which would create the file) on a machine that never
219
150
  // stored this provider.
220
151
  if (!existsSync(path))
221
152
  return;
222
- await withLockedAuthFile(path, async (current) => {
153
+ await withLockedFile(path, async (current) => {
223
154
  const creds = parseForWrite(current, path);
224
155
  if (!(providerId in creds))
225
156
  return { result: undefined }; // absent: no write
@@ -229,3 +160,31 @@ export function fastagentCredentialStore(authPath = GLOBAL_AUTH_PATH, options =
229
160
  },
230
161
  };
231
162
  }
163
+ // ── Which files an agent reads ───────────────────────────────────────────────
164
+ /**
165
+ * The auth-file override: the SDK's `authPath` option > `FASTAGENT_AUTH_PATH` env > undefined (then
166
+ * `<secrets dir>/auth.json`). The CLI has no flag for it: a second spelling of one environment variable buys nothing,
167
+ * and the variable is what a deployed container reads anyway.
168
+ */
169
+ function resolveAuthPathOverride(flag, env) {
170
+ return resolveOverridePath(flag ?? env.FASTAGENT_AUTH_PATH);
171
+ }
172
+ /** The effective auth file for an agent: override if present, else `<secrets dir>/auth.json`. */
173
+ export function resolveAuthPath(dir, flag, env = process.env) {
174
+ return resolveAuthPathOverride(flag, env) ?? join(resolveSecretsDir(dir, env), "auth.json");
175
+ }
176
+ /**
177
+ * The {@link AuthLayers} an agent directory reads: `authPath` option > `FASTAGENT_AUTH_PATH` > its own file, and
178
+ * behind it the user-global store, because a login is a PERSON on a machine and not a project — one `login -g` then
179
+ * serves every agent here.
180
+ *
181
+ * No fallback when a path was named: "use this file" is an instruction, not a preference. BOTH knobs
182
+ * {@link resolveAuthPath} reads count as that instruction, `FASTAGENT_SECRETS_DIR` included — it is what the
183
+ * Fly/Railway/AgentCore artifacts set, and a deployed container must read its mounted credentials and nothing else
184
+ * (the artifact is the truth).
185
+ */
186
+ export function resolveAuthLayers(agentDir, authPath, env = process.env) {
187
+ const path = resolveAuthPath(agentDir, authPath, env);
188
+ const named = resolveAuthPathOverride(authPath, env) ?? resolveOverridePath(env.FASTAGENT_SECRETS_DIR);
189
+ return named === undefined ? { path, fallback: GLOBAL_AUTH_PATH } : { path };
190
+ }
@@ -63,28 +63,3 @@ export declare function rewriteConfigModel(src: string, spec: string): string |
63
63
  * would refuse to start an agent whose config names a model.
64
64
  */
65
65
  export declare function resolveModelSpec(flag: string | undefined, config: FastagentConfig, env?: NodeJS.ProcessEnv): string | undefined;
66
- /** The default credentials file under a resolved secrets dir ({@link resolveSecretsDir}). */
67
- export declare function defaultAuthPath(secretsDir: string): string;
68
- /** The effective auth file for an agent: override if present, else `<secrets dir>/auth.json`. */
69
- export declare function resolveAuthPath(dir: string, flag?: string, env?: NodeJS.ProcessEnv): string;
70
- /**
71
- * Which credentials files an agent reads: `path` first, then, per provider, `fallback` — present only when nothing
72
- * named the path ({@link resolveAuthFallback}). One value because it is one decision: the opener, the model list, the
73
- * startup report and the first-run picker all read through it.
74
- */
75
- export interface AuthLayers {
76
- path: string;
77
- fallback?: string;
78
- }
79
- /** The {@link AuthLayers} an agent directory reads: `authPath` option > `FASTAGENT_AUTH_PATH` > its own file. */
80
- export declare function resolveAuthLayers(agentDir: string, authPath?: string, env?: NodeJS.ProcessEnv): AuthLayers;
81
- /**
82
- * Where a credential the project does not have is read from instead: the user-global store, because a login is a
83
- * PERSON on a machine and not a project — one `login -g` then serves every agent here. Undefined when an explicit
84
- * path was named: "use this file" is an instruction, not a preference, so it gets no second layer.
85
- *
86
- * BOTH knobs {@link resolveAuthPath} reads count as that instruction, `FASTAGENT_SECRETS_DIR` included — it is what
87
- * the Fly/Railway/AgentCore artifacts set, and a deployed container must read its mounted credentials and nothing
88
- * else (the artifact is the truth).
89
- */
90
- export declare function resolveAuthFallback(flag?: string, env?: NodeJS.ProcessEnv): string | undefined;
@@ -14,11 +14,10 @@ import { existsSync, statSync } from "node:fs";
14
14
  import { join } from "node:path";
15
15
  import { pathToFileURL } from "node:url";
16
16
  import { THINKING_LEVELS } from "./session-settings.js";
17
- import { GLOBAL_AUTH_PATH } from "./auth.js";
18
17
  import { readSecretDeclaration } from "../../declared-secrets.js";
19
18
  import { assertCorsOrigins } from "../../channels/serve.js";
20
19
  import { moduleLoadHint } from "../../loader.js";
21
- import { AGENT_CONFIG_FILE, resolveOverridePath, resolveSecretsDir } from "../../paths.js";
20
+ import { AGENT_CONFIG_FILE } from "../../paths.js";
22
21
  /** Identity function for typing and IDE completion (vite/next-style). */
23
22
  export function defineConfig(config) {
24
23
  return config;
@@ -152,7 +151,8 @@ export function resolveModel(models, spec) {
152
151
  const modelId = spec.slice(slash + 1);
153
152
  const model = models.getModel(provider, modelId);
154
153
  if (!model) {
155
- throw new Error(`unknown model "${spec}" (provider "${provider}" / id "${modelId}" not in registry); run \`fastagent models\` to list available specs`);
154
+ throw new Error(`unknown model "${spec}" (provider "${provider}" / id "${modelId}" not in registry); run \`fastagent models\` to list ` +
155
+ "available specs, or `fastagent models --refresh` in the agent to fetch models newer than the installed pi");
156
156
  }
157
157
  return model;
158
158
  }
@@ -189,37 +189,3 @@ export function rewriteConfigModel(src, spec) {
189
189
  export function resolveModelSpec(flag, config, env = process.env) {
190
190
  return flag ?? (env.FASTAGENT_MODEL || config.model);
191
191
  }
192
- /**
193
- * The auth-file override: the SDK's `authPath` option > `FASTAGENT_AUTH_PATH` env > undefined (the opener then falls
194
- * back to {@link defaultAuthPath} under the {@link resolveSecretsDir} dir). The CLI has no flag for it: a second
195
- * spelling of one environment variable buys nothing, and the variable is what a deployed container reads anyway.
196
- */
197
- function resolveAuthPathOverride(flag, env = process.env) {
198
- return resolveOverridePath(flag ?? env.FASTAGENT_AUTH_PATH);
199
- }
200
- /** The default credentials file under a resolved secrets dir ({@link resolveSecretsDir}). */
201
- export function defaultAuthPath(secretsDir) {
202
- return join(secretsDir, "auth.json");
203
- }
204
- /** The effective auth file for an agent: override if present, else `<secrets dir>/auth.json`. */
205
- export function resolveAuthPath(dir, flag, env = process.env) {
206
- return resolveAuthPathOverride(flag, env) ?? defaultAuthPath(resolveSecretsDir(dir, env));
207
- }
208
- /** The {@link AuthLayers} an agent directory reads: `authPath` option > `FASTAGENT_AUTH_PATH` > its own file. */
209
- export function resolveAuthLayers(agentDir, authPath, env = process.env) {
210
- const fallback = resolveAuthFallback(authPath, env);
211
- return { path: resolveAuthPath(agentDir, authPath, env), ...(fallback !== undefined ? { fallback } : {}) };
212
- }
213
- /**
214
- * Where a credential the project does not have is read from instead: the user-global store, because a login is a
215
- * PERSON on a machine and not a project — one `login -g` then serves every agent here. Undefined when an explicit
216
- * path was named: "use this file" is an instruction, not a preference, so it gets no second layer.
217
- *
218
- * BOTH knobs {@link resolveAuthPath} reads count as that instruction, `FASTAGENT_SECRETS_DIR` included — it is what
219
- * the Fly/Railway/AgentCore artifacts set, and a deployed container must read its mounted credentials and nothing
220
- * else (the artifact is the truth).
221
- */
222
- export function resolveAuthFallback(flag, env = process.env) {
223
- const explicit = resolveAuthPathOverride(flag, env) ?? resolveOverridePath(env.FASTAGENT_SECRETS_DIR);
224
- return explicit === undefined ? GLOBAL_AUTH_PATH : undefined;
225
- }
@@ -1,15 +1,16 @@
1
1
  import type { ExecutionEnv, Skill, ThinkingLevel } from "@earendil-works/pi-agent-core";
2
2
  import { type ModelRuntime } from "@earendil-works/pi-coding-agent";
3
- import type { Provider } from "@earendil-works/pi-ai";
3
+ import type { CredentialStore, Provider } from "@earendil-works/pi-ai";
4
4
  import type { Agent } from "../../agent.ts";
5
- import { type AuthLayers, type FastagentConfig } from "./config.ts";
6
- import type { FastagentAuthOptions } from "./auth.ts";
5
+ import { type FastagentConfig } from "./config.ts";
7
6
  import { type LoadedDefinition } from "./definition.ts";
8
7
  import type { ModuleLoadFailure } from "../../loader.ts";
9
8
  import { type ToolCollision, type MountedTool } from "./tool.ts";
10
9
  import { type DeclaredSecret } from "../../declared-secrets.ts";
11
10
  import { type PiAgentSessionFactory } from "./invoke-session.ts";
11
+ import { type PiAgentSessionFactoryOptions } from "./agent-session-factory.ts";
12
12
  import { type AnyModel } from "./models.ts";
13
+ import { type AgentModels } from "./agent-models.ts";
13
14
  import { type PiSessionRecordStore } from "./session-store.ts";
14
15
  import { type Lease, type SessionObserver } from "./turn-kit.ts";
15
16
  /**
@@ -18,6 +19,13 @@ import { type Lease, type SessionObserver } from "./turn-kit.ts";
18
19
  */
19
20
  export declare const CODING_TOOL_NAMES: readonly ["read", "grep", "find", "ls", "bash", "edit", "write"];
20
21
  export declare function piAllCodingTools(cwd: string): MountedTool[];
22
+ /** How the model reaches a mounted tool it is not given up front. */
23
+ export type ToolReach = "tool_search" | "codemode" | "hidden" | "inactive" | "unreachable";
24
+ /** A mounted authored tool outside the default-active surface, and its way in. */
25
+ export interface IndirectTool {
26
+ name: string;
27
+ reach: ToolReach;
28
+ }
21
29
  /**
22
30
  * The full directory-agent tool set: all pi coding tools + `config.tools` + discovered `tools/` (deduped, existing
23
31
  * win), plus the authored names and collisions to report.
@@ -25,11 +33,8 @@ export declare function piAllCodingTools(cwd: string): MountedTool[];
25
33
  export declare function resolveAgentTools(config: FastagentConfig, agentDir: string, cwd: string): Promise<{
26
34
  tools: MountedTool[];
27
35
  toolNames: string[];
28
- /**
29
- * Tools registered but not initially active (defineTool `deferred: true`) — discovered/activated via the built-in
30
- * `search_tools` loader.
31
- */
32
- deferredToolNames: string[];
36
+ /** Mounted authored tools the model is not given up front, with how each is reached. */
37
+ indirectTools: IndirectTool[];
33
38
  toolCollisions: ToolCollision[];
34
39
  toolFailures: ModuleLoadFailure[];
35
40
  /** Env vars the MOUNTED tools declared they need, BY TOOL NAME — a discovered tool shadowed by a
@@ -44,6 +49,8 @@ export declare function resolveAgentTools(config: FastagentConfig, agentDir: str
44
49
  export declare function piBasePrompt(options?: {
45
50
  tools?: MountedTool[];
46
51
  persona?: string;
52
+ /** The {@link BUILTIN_EXTENSIONS} this machine leaves enabled (`Machine.builtinExtensions`); all by default. */
53
+ builtinExtensions?: readonly string[];
47
54
  }): string;
48
55
  export interface AssembleSystemPromptOptions {
49
56
  /**
@@ -65,6 +72,10 @@ export declare function assembleSystemPrompt(options: AssembleSystemPromptOption
65
72
  export interface PiAssembly {
66
73
  lease: Lease;
67
74
  sessionFactory: PiAgentSessionFactory;
75
+ /** Extension-aware catalog for startup reporting and control-plane validation; never bound to a session. */
76
+ modelRuntime: () => Promise<ModelRuntime>;
77
+ /** A fresh runtime for every session, before loading its extensions. */
78
+ createModelRuntime: () => Promise<ModelRuntime>;
68
79
  /** The registry and configured model, resolved on first use (a credential read is async). */
69
80
  engine: () => Promise<{
70
81
  modelRuntime: ModelRuntime;
@@ -72,6 +83,12 @@ export interface PiAssembly {
72
83
  }>;
73
84
  /** The configured reasoning effort — the other half of the pair a session without overrides runs on. */
74
85
  thinkingLevel: ThinkingLevel;
86
+ /** The tools every session mounts. */
87
+ tools: MountedTool[];
88
+ /** Pi built-ins the mounted tools leave out, denied in every session so discovery cannot bring them back. */
89
+ excludedToolNames: readonly string[];
90
+ /** The prompt and skills a session runs on, read when a session is bound. */
91
+ readDefinition: PiAgentSessionFactoryOptions["readDefinition"];
75
92
  /** The extension entry points every session loads, discovered once with the assembly. */
76
93
  extensionPaths: readonly string[];
77
94
  }
@@ -98,6 +115,8 @@ export interface CreatePiAgentOptions {
98
115
  providers?: Provider[];
99
116
  /** Credentials file for stored OAuth/API-key auth (default `GLOBAL_AUTH_PATH`). */
100
117
  authPath?: string;
118
+ /** The caller's own credential store, in place of any file ({@link CredentialSourceOptions}). */
119
+ credentialStore?: CredentialStore;
101
120
  sessions?: PiSessionRecordStore;
102
121
  /** Supplies the working directory at L1 (default: process.cwd()), which loads no definition. */
103
122
  env?: ExecutionEnv;
@@ -129,6 +148,8 @@ export interface CreatePiAgentFromDefinitionOptions {
129
148
  * gets no second layer.
130
149
  */
131
150
  authPath?: string;
151
+ /** The caller's own credential store, in place of any file ({@link CredentialSourceOptions}). */
152
+ credentialStore?: CredentialStore;
132
153
  sessions?: PiSessionRecordStore;
133
154
  /** Filesystem/process environment; see {@link CreatePiAgentOptions.env}. */
134
155
  env?: ExecutionEnv;
@@ -137,20 +158,12 @@ export interface CreatePiAgentFromDefinitionOptions {
137
158
  observer?: SessionObserver;
138
159
  }
139
160
  /**
140
- * THE model registry an agent directory runs on: pi's built-ins, the directory's own `models.json` (custom endpoints
141
- * are definition data and travel with the artifact) layered over the machine's `~/.fastagent/models.json` (environment,
142
- * which does not travel), and any injected Provider instance, over the given credentials.
143
- * The directory rung assembles from it and `availableModelsFromDir` lists from it, so a layer added here reaches both.
161
+ * L2, as the value: load the directory (base + AGENTS.md + skills + env) and assemble. `models` is the directory's
162
+ * model environment when the caller already built it (the opener, whose report must describe what runs); otherwise it
163
+ * is built here from the credential options.
144
164
  */
145
- export declare function definitionModelRuntime(dir: string, options: FastagentAuthOptions & {
146
- auth: AuthLayers;
147
- providers?: Provider[];
148
- }): Promise<ModelRuntime>;
149
- /** L2, as the value: load the directory (base + AGENTS.md + skills + env) and assemble. */
150
- export declare function assemblePiFromDefinition(dir: string,
151
- /** `auth` is the opener's already-resolved layers, which take the place of `authPath`. */
152
- options: Omit<CreatePiAgentFromDefinitionOptions, "observer"> & {
153
- auth?: AuthLayers;
165
+ export declare function assemblePiFromDefinition(dir: string, options: Omit<CreatePiAgentFromDefinitionOptions, "observer"> & {
166
+ models?: AgentModels;
154
167
  }): Promise<{
155
168
  assembly: PiAssembly;
156
169
  definition: LoadedDefinition;