@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.
Files changed (277) hide show
  1. package/README.md +16 -6
  2. package/dist/agent.d.ts +20 -0
  3. package/dist/agent.js +9 -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 +258 -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 +35 -0
  13. package/dist/channels/context-buffer.js +103 -0
  14. package/dist/channels/control.d.ts +28 -0
  15. package/dist/channels/control.js +214 -0
  16. package/dist/channels/feishu/card.js +1 -1
  17. package/dist/channels/feishu/context-buffer.d.ts +15 -13
  18. package/dist/channels/feishu/context-buffer.js +62 -69
  19. package/dist/channels/feishu/feishu-api.d.ts +11 -3
  20. package/dist/channels/feishu/feishu-api.js +24 -3
  21. package/dist/channels/feishu/feishu.d.ts +27 -27
  22. package/dist/channels/feishu/feishu.js +318 -207
  23. package/dist/channels/feishu/invoke-turn.d.ts +7 -20
  24. package/dist/channels/feishu/invoke-turn.js +44 -79
  25. package/dist/channels/feishu/model.d.ts +2 -2
  26. package/dist/channels/feishu/normalize.d.ts +2 -1
  27. package/dist/channels/feishu/normalize.js +0 -1
  28. package/dist/channels/feishu/parse.d.ts +22 -8
  29. package/dist/channels/feishu/parse.js +24 -7
  30. package/dist/channels/feishu/preview.d.ts +4 -7
  31. package/dist/channels/feishu/preview.js +27 -142
  32. package/dist/channels/feishu/register-app.d.ts +2 -1
  33. package/dist/channels/feishu/register-webhook.d.ts +2 -1
  34. package/dist/channels/feishu/scaffold/channel.ts +9 -8
  35. package/dist/channels/feishu/scaffold/feishu-send.ts +6 -4
  36. package/dist/channels/feishu/setup-mode.d.ts +37 -0
  37. package/dist/channels/feishu/setup-mode.js +28 -0
  38. package/dist/channels/feishu/ws-ingress.d.ts +28 -0
  39. package/dist/channels/feishu/ws-ingress.js +136 -0
  40. package/dist/channels/github/github.js +16 -7
  41. package/dist/channels/http.d.ts +14 -0
  42. package/dist/channels/http.js +36 -3
  43. package/dist/channels/invoke-turn-kit.d.ts +65 -0
  44. package/dist/channels/invoke-turn-kit.js +87 -0
  45. package/dist/channels/lark/lark.d.ts +4 -2
  46. package/dist/channels/lark/lark.js +4 -1
  47. package/dist/channels/lark/onboard.d.ts +8 -4
  48. package/dist/channels/lark/onboard.js +8 -0
  49. package/dist/channels/lark/scaffold/channel.ts +9 -8
  50. package/dist/channels/lark/scaffold/lark-send.ts +6 -4
  51. package/dist/channels/preview-kit.d.ts +115 -0
  52. package/dist/channels/preview-kit.js +184 -0
  53. package/dist/channels/seen.d.ts +5 -0
  54. package/dist/channels/seen.js +35 -0
  55. package/dist/channels/slack/bot-auth.d.ts +15 -0
  56. package/dist/channels/slack/bot-auth.js +146 -0
  57. package/dist/channels/slack/config-api.d.ts +60 -0
  58. package/dist/channels/slack/config-api.js +149 -0
  59. package/dist/channels/slack/context-buffer.d.ts +24 -0
  60. package/dist/channels/slack/context-buffer.js +37 -0
  61. package/dist/channels/slack/invoke-turn.d.ts +19 -0
  62. package/dist/channels/slack/invoke-turn.js +63 -0
  63. package/dist/channels/slack/manifest.d.ts +49 -0
  64. package/dist/channels/slack/manifest.js +69 -0
  65. package/dist/channels/slack/model.d.ts +67 -0
  66. package/dist/channels/slack/model.js +2 -0
  67. package/dist/channels/slack/onboard.d.ts +41 -0
  68. package/dist/channels/slack/onboard.js +120 -0
  69. package/dist/channels/slack/onboarding-state.d.ts +31 -0
  70. package/dist/channels/slack/onboarding-state.js +69 -0
  71. package/dist/channels/slack/parse.d.ts +38 -0
  72. package/dist/channels/slack/parse.js +124 -0
  73. package/dist/channels/slack/preview.d.ts +23 -0
  74. package/dist/channels/slack/preview.js +403 -0
  75. package/dist/channels/slack/reaction.d.ts +24 -0
  76. package/dist/channels/slack/reaction.js +62 -0
  77. package/dist/channels/slack/register-webhook.d.ts +10 -0
  78. package/dist/channels/slack/register-webhook.js +49 -0
  79. package/dist/channels/slack/scaffold/channel.ts +33 -0
  80. package/dist/channels/slack/scaffold/slack-send.ts +171 -0
  81. package/dist/channels/slack/setup-server.d.ts +17 -0
  82. package/dist/channels/slack/setup-server.js +103 -0
  83. package/dist/channels/slack/slack-api.d.ts +57 -0
  84. package/dist/channels/slack/slack-api.js +399 -0
  85. package/dist/channels/slack/slack.d.ts +51 -0
  86. package/dist/channels/slack/slack.js +496 -0
  87. package/dist/channels/slack/welcomed.d.ts +5 -0
  88. package/dist/channels/slack/welcomed.js +32 -0
  89. package/dist/channels/state.d.ts +11 -4
  90. package/dist/channels/state.js +22 -15
  91. package/dist/channels/stop-command.d.ts +6 -0
  92. package/dist/channels/stop-command.js +36 -0
  93. package/dist/channels/tasks.d.ts +7 -0
  94. package/dist/channels/tasks.js +25 -0
  95. package/dist/channels/telegram/context-buffer.d.ts +8 -17
  96. package/dist/channels/telegram/context-buffer.js +6 -85
  97. package/dist/channels/telegram/invoke-turn.d.ts +5 -22
  98. package/dist/channels/telegram/invoke-turn.js +11 -58
  99. package/dist/channels/telegram/parse.d.ts +0 -7
  100. package/dist/channels/telegram/parse.js +4 -2
  101. package/dist/channels/telegram/preview.d.ts +4 -7
  102. package/dist/channels/telegram/preview.js +24 -142
  103. package/dist/channels/telegram/scaffold/telegram-send.ts +6 -3
  104. package/dist/channels/telegram/telegram.js +24 -10
  105. package/dist/channels/{feishu/text.d.ts → text.d.ts} +15 -1
  106. package/dist/channels/{feishu/text.js → text.js} +15 -1
  107. package/dist/channels/thread-participants.d.ts +21 -0
  108. package/dist/channels/thread-participants.js +132 -0
  109. package/dist/channels/turn-queue.js +8 -1
  110. package/dist/channels/turn-store.d.ts +1 -1
  111. package/dist/channels/turn-store.js +2 -3
  112. package/dist/cli/add-feishu.d.ts +30 -0
  113. package/dist/cli/add-feishu.js +366 -0
  114. package/dist/cli/add-slack.d.ts +11 -0
  115. package/dist/cli/add-slack.js +199 -0
  116. package/dist/{cli-auth.d.ts → cli/auth-view.d.ts} +1 -1
  117. package/dist/{cli-auth.js → cli/auth-view.js} +1 -1
  118. package/dist/cli/commands/add.d.ts +4 -0
  119. package/dist/cli/commands/add.js +191 -60
  120. package/dist/cli/commands/attach.d.ts +82 -0
  121. package/dist/cli/commands/attach.js +563 -0
  122. package/dist/cli/commands/chat.d.ts +1 -0
  123. package/dist/cli/commands/chat.js +12 -7
  124. package/dist/cli/commands/deploy.d.ts +3 -1
  125. package/dist/cli/commands/deploy.js +384 -82
  126. package/dist/cli/commands/dev.js +34 -26
  127. package/dist/cli/commands/fire.js +17 -18
  128. package/dist/cli/commands/info.js +36 -29
  129. package/dist/cli/commands/init.d.ts +1 -1
  130. package/dist/cli/commands/init.js +65 -53
  131. package/dist/cli/commands/invoke.js +11 -9
  132. package/dist/cli/commands/login.js +48 -45
  133. package/dist/cli/commands/models.js +1 -1
  134. package/dist/cli/commands/schedule.js +6 -8
  135. package/dist/cli/commands/start.js +102 -39
  136. package/dist/cli/commands/tool.js +23 -18
  137. package/dist/cli/fail.d.ts +17 -2
  138. package/dist/cli/fail.js +25 -1
  139. package/dist/{invoke-stream.d.ts → cli/invoke-stream.d.ts} +1 -1
  140. package/dist/{invoke-stream.js → cli/invoke-stream.js} +4 -0
  141. package/dist/cli/kernel.d.ts +3 -2
  142. package/dist/cli/models-view.d.ts +21 -0
  143. package/dist/cli/models-view.js +66 -0
  144. package/dist/cli/program.js +135 -56
  145. package/dist/cli/serve.d.ts +60 -20
  146. package/dist/cli/serve.js +252 -47
  147. package/dist/cli/shared.d.ts +40 -7
  148. package/dist/cli/shared.js +180 -38
  149. package/dist/collect.d.ts +14 -3
  150. package/dist/collect.js +24 -0
  151. package/dist/core.d.ts +3 -1
  152. package/dist/core.js +2 -0
  153. package/dist/deploy/agentcore/plan.d.ts +117 -0
  154. package/dist/deploy/agentcore/plan.js +721 -0
  155. package/dist/deploy/agentcore/run.d.ts +73 -0
  156. package/dist/deploy/agentcore/run.js +412 -0
  157. package/dist/deploy/agentcore/zip.d.ts +17 -0
  158. package/dist/deploy/agentcore/zip.js +68 -0
  159. package/dist/deploy/container.d.ts +26 -25
  160. package/dist/deploy/container.js +93 -89
  161. package/dist/deploy/docker/plan.d.ts +3 -3
  162. package/dist/deploy/docker/plan.js +20 -23
  163. package/dist/deploy/fly/plan.d.ts +5 -1
  164. package/dist/deploy/fly/plan.js +39 -26
  165. package/dist/deploy/fly/run.d.ts +17 -5
  166. package/dist/deploy/fly/run.js +52 -7
  167. package/dist/deploy/preflight.d.ts +20 -8
  168. package/dist/deploy/preflight.js +267 -71
  169. package/dist/deploy/railway/plan.d.ts +10 -1
  170. package/dist/deploy/railway/plan.js +53 -20
  171. package/dist/deploy/railway/run.d.ts +13 -5
  172. package/dist/deploy/railway/run.js +22 -7
  173. package/dist/deploy/runner.d.ts +7 -3
  174. package/dist/deploy/runner.js +9 -3
  175. package/dist/deploy/secrets.d.ts +2 -1
  176. package/dist/deploy/secrets.js +23 -3
  177. package/dist/dev-supervisor.d.ts +11 -10
  178. package/dist/dev-supervisor.js +56 -50
  179. package/dist/engines/pi/auth.d.ts +8 -7
  180. package/dist/engines/pi/auth.js +12 -10
  181. package/dist/engines/pi/channel.d.ts +22 -16
  182. package/dist/engines/pi/channel.js +92 -62
  183. package/dist/engines/pi/chat.d.ts +4 -16
  184. package/dist/engines/pi/chat.js +8 -261
  185. package/dist/engines/pi/config.d.ts +20 -55
  186. package/dist/engines/pi/config.js +49 -121
  187. package/dist/engines/pi/create.d.ts +30 -10
  188. package/dist/engines/pi/create.js +33 -26
  189. package/dist/engines/pi/definition.d.ts +7 -26
  190. package/dist/engines/pi/definition.js +8 -54
  191. package/dist/engines/pi/harness.d.ts +49 -0
  192. package/dist/engines/pi/harness.js +90 -2
  193. package/dist/engines/pi/invoke.d.ts +55 -2
  194. package/dist/engines/pi/invoke.js +304 -19
  195. package/dist/engines/pi/login.d.ts +12 -1
  196. package/dist/engines/pi/login.js +16 -4
  197. package/dist/engines/pi/models.d.ts +59 -13
  198. package/dist/engines/pi/models.js +62 -24
  199. package/dist/engines/pi/open.d.ts +114 -0
  200. package/dist/engines/pi/open.js +127 -0
  201. package/dist/engines/pi/session-builder.d.ts +16 -0
  202. package/dist/engines/pi/session-builder.js +308 -0
  203. package/dist/engines/pi/session-control.d.ts +50 -0
  204. package/dist/engines/pi/session-control.js +604 -0
  205. package/dist/engines/pi/sessions.d.ts +17 -2
  206. package/dist/engines/pi/sessions.js +9 -0
  207. package/dist/engines/pi/tool-context.d.ts +18 -11
  208. package/dist/engines/pi/tool-context.js +3 -9
  209. package/dist/engines/pi/tool.d.ts +6 -8
  210. package/dist/engines/pi/tool.js +10 -1
  211. package/dist/engines/pi/wake-tool.d.ts +0 -3
  212. package/dist/engines/pi/wake-tool.js +9 -7
  213. package/dist/env.d.ts +16 -4
  214. package/dist/env.js +43 -5
  215. package/dist/feishu.d.ts +1 -1
  216. package/dist/feishu.js +1 -1
  217. package/dist/host/node.d.ts +25 -8
  218. package/dist/host/node.js +5 -4
  219. package/dist/index.d.ts +1 -0
  220. package/dist/index.js +1 -0
  221. package/dist/lark.d.ts +1 -1
  222. package/dist/lark.js +1 -1
  223. package/dist/loader.d.ts +2 -2
  224. package/dist/loader.js +3 -3
  225. package/dist/log.d.ts +1 -1
  226. package/dist/log.js +1 -1
  227. package/dist/observe.js +3 -0
  228. package/dist/paths.d.ts +138 -0
  229. package/dist/paths.js +326 -0
  230. package/dist/pi.d.ts +6 -5
  231. package/dist/pi.js +4 -3
  232. package/dist/runtime.d.ts +7 -5
  233. package/dist/runtime.js +2 -2
  234. package/dist/scaffold/add-channel.d.ts +26 -9
  235. package/dist/scaffold/add-channel.js +175 -40
  236. package/dist/scaffold/init.d.ts +32 -41
  237. package/dist/scaffold/init.js +161 -185
  238. package/dist/scaffold/templates/env.example +15 -6
  239. package/dist/scaffold/templates/fastagent.config.mjs +5 -4
  240. package/dist/scaffold/templates/gitignore +14 -6
  241. package/dist/scaffold/templates/persona.md +4 -2
  242. package/dist/scaffold/templates/secrets.gitignore +5 -0
  243. package/dist/scaffold/templates.d.ts +1 -7
  244. package/dist/scaffold/templates.js +3 -25
  245. package/dist/scaffold/vendor-skill.d.ts +2 -2
  246. package/dist/scaffold/vendor-skill.js +13 -13
  247. package/dist/schedule/discover.js +4 -4
  248. package/dist/schedule/scheduler.d.ts +40 -1
  249. package/dist/schedule/scheduler.js +89 -56
  250. package/dist/schedule/state.js +1 -1
  251. package/dist/schedule/wake-alarm.d.ts +47 -0
  252. package/dist/schedule/wake-alarm.js +136 -0
  253. package/dist/schedule/wakeups.d.ts +1 -3
  254. package/dist/schedule/wakeups.js +19 -1
  255. package/dist/session-remote.d.ts +53 -0
  256. package/dist/session-remote.js +336 -0
  257. package/dist/session.d.ts +265 -0
  258. package/dist/session.js +37 -0
  259. package/dist/slack.d.ts +2 -0
  260. package/dist/slack.js +2 -0
  261. package/dist/tunnel.d.ts +7 -4
  262. package/dist/tunnel.js +23 -12
  263. package/package.json +22 -7
  264. package/dist/channels/feishu/owned-threads.d.ts +0 -11
  265. package/dist/channels/feishu/owned-threads.js +0 -47
  266. package/dist/channels/feishu/seen.d.ts +0 -5
  267. package/dist/channels/feishu/seen.js +0 -47
  268. package/dist/cli-add-feishu.d.ts +0 -8
  269. package/dist/cli-add-feishu.js +0 -223
  270. package/dist/cli-models.d.ts +0 -11
  271. package/dist/cli-models.js +0 -20
  272. package/dist/engines/pi/workspace.d.ts +0 -56
  273. package/dist/engines/pi/workspace.js +0 -72
  274. package/dist/scaffold/templates/gitignore.agentdir-root +0 -5
  275. package/dist/scaffold/templates/gitignore.kit +0 -2
  276. package/dist/workspace.d.ts +0 -9
  277. package/dist/workspace.js +0 -45
@@ -5,20 +5,20 @@
5
5
  */
6
6
  import { resolve } from "node:path";
7
7
  import { loadDotEnv } from "../../env.js";
8
- import { loadConfig, resolveAgentDir, resolveStateRoot } from "../../engines/pi/config.js";
8
+ import { resolveStateRoot } from "../../paths.js";
9
9
  import { reportModuleLoadFailures } from "../../engines/pi/report.js";
10
10
  import { readRuns } from "../../schedule/audit.js";
11
11
  import { nextRun } from "../../schedule/cron.js";
12
12
  import { loadSchedules } from "../../schedule/discover.js";
13
13
  import { listWakeups, removeWakeup } from "../../schedule/wakeups.js";
14
- import { failStartup } from "../fail.js";
14
+ import { failStartup, placementOrExit } from "../fail.js";
15
15
  /**
16
16
  * `fastagent schedule history <name> [dir]`: print the run audit for one schedule (or "wake") — fired
17
17
  * time, outcome, duration, reply/error. Read-only (reads `<stateRoot>/schedule/runs.jsonl`); the answer
18
18
  * to "did last night's run silently fail?". Text mode previews the reply/error; --json is the full record.
19
19
  */
20
20
  export function runScheduleHistory(name, dirArg, json) {
21
- const target = resolve(dirArg);
21
+ const { agentDir: target } = placementOrExit(resolve(dirArg));
22
22
  loadDotEnv(target); // FASTAGENT_STATE_DIR may live in .env — read the SAME state root the scheduler wrote
23
23
  const runs = readRuns(resolveStateRoot(target), name);
24
24
  if (json) {
@@ -45,11 +45,9 @@ export function runScheduleHistory(name, dirArg, json) {
45
45
  /** `fastagent schedule list [dir]`: everything that will fire — BOTH producers: the static `schedules/`
46
46
  * files (with their next instant) and the agent's pending self-scheduled wake-ups. Read-only. */
47
47
  export async function runScheduleList(dirArg, json) {
48
- const target = resolve(dirArg);
48
+ const { agentDir: target } = placementOrExit(resolve(dirArg));
49
49
  loadDotEnv(target);
50
- const { config } = await loadConfig(target).catch(failStartup);
51
- const agentDir = resolveAgentDir(target, config);
52
- const { schedules, failures } = await loadSchedules(agentDir).catch(failStartup);
50
+ const { schedules, failures } = await loadSchedules(target).catch(failStartup);
53
51
  reportModuleLoadFailures(failures);
54
52
  const wakeups = listWakeups(resolveStateRoot(target));
55
53
  if (json) {
@@ -75,7 +73,7 @@ export async function runScheduleList(dirArg, json) {
75
73
  /** `fastagent schedule cancel <id> [dir]`: remove a pending wake-up — the operator's kill switch (the
76
74
  * agent's own is the `unwake` tool). Unlike unwake it is NOT session-scoped: the operator owns the box. */
77
75
  export function runScheduleCancel(id, dirArg) {
78
- const target = resolve(dirArg);
76
+ const { agentDir: target } = placementOrExit(resolve(dirArg));
79
77
  loadDotEnv(target);
80
78
  if (removeWakeup(resolveStateRoot(target), id)) {
81
79
  // ponytail: the store's load→save is lock-free — a serving scheduler's claim-advance can race this
@@ -4,84 +4,147 @@
4
4
  */
5
5
  import { mkdir, writeFile } from "node:fs/promises";
6
6
  import { dirname, resolve } from "node:path";
7
- import { authSeedBytes } from "../../deploy/fly/run.js";
7
+ import { authSeedBytes, collectAuthSeed } from "../../deploy/fly/run.js";
8
8
  import { loadDotEnv } from "../../env.js";
9
- import { defaultAuthPath, resolveAuthPathOverride, resolveSessionsDirOverride, resolveStateRoot, } from "../../engines/pi/config.js";
9
+ import { resolveAuthPath, resolveSessionsDirOverride } from "../../engines/pi/config.js";
10
+ import { resolveSecretsDir, workspaceHint } from "../../paths.js";
10
11
  import { isUnderDir } from "../../engines/pi/definition.js";
11
12
  import { reportDefinitionWarnings, reportModuleLoadFailures, reportToolCollisions } from "../../engines/pi/report.js";
12
- import { createPiAgentFromWorkspace } from "../../engines/pi/workspace.js";
13
+ import { createPiAgentFromDir } from "../../engines/pi/open.js";
13
14
  import { log, setLogLevel } from "../../log.js";
15
+ import { createWakeAlarmSink, reconcileWakeAlarms } from "../../schedule/wake-alarm.js";
16
+ import { setWakeupsSink } from "../../schedule/wakeups.js";
14
17
  import { logAgentLoop } from "../../observe.js";
15
18
  import { installProxyFetch } from "../../proxy.js";
16
- import { exists } from "../../scaffold/init.js";
17
- import { failStartup } from "../fail.js";
18
- import { maybeTunnel, routesFor, serve, startSchedules } from "../serve.js";
19
- import { parsePort, reportAuth, resolveFirstRunModel } from "../shared.js";
19
+ import { exists } from "../../paths.js";
20
+ import { failStartup, placementOrExit } from "../fail.js";
21
+ import { maybeTunnel, mountAgentcore, mountSessionControl, routesFor, serve, startSchedules } from "../serve.js";
22
+ import { parsePort, reportAuth, reportLine, resolveFirstRunModel, reportWorkspaceHint } from "../shared.js";
20
23
  export async function runStart(dirArg, opts) {
21
24
  const dir = resolve(dirArg);
22
- setLogLevel("info"); // production posture: info+, the debug turn trace (and its end-user content) gated out
25
+ // Flag validation first: a bad --port is a USAGE error (exit 2), and reporting it must not depend on
26
+ // the directory being an agent (which is a runtime/environment failure, exit 1).
23
27
  const portFlag = parsePort(opts.port, "--port", "flag");
24
- loadDotEnv(dir);
28
+ const placement = placementOrExit(dir);
29
+ setLogLevel("info"); // production posture: info+, the debug turn trace (and its end-user content) gated out
30
+ loadDotEnv(placement.agentDir);
25
31
  installProxyFetch();
26
- await resolveFirstRunModel(dir, opts);
32
+ await resolveFirstRunModel(placement.agentDir, opts);
27
33
  // A `deploy --run` may carry the operator's local credential as FASTAGENT_AUTH_SEED —
28
- // materialize it onto the writable state root BEFORE the opener resolves auth (once, absent-only).
29
- const authPathOverride = resolveAuthPathOverride(opts.authPath);
30
- await maybeSeedAuth(authPathOverride ?? defaultAuthPath(resolveStateRoot(dir)));
34
+ // materialize it into the writable secrets dir BEFORE the opener resolves auth (once, absent-only).
35
+ // Same resolveAuthPath the opener uses — ONE owner of the flag > env > default chain.
36
+ await maybeSeedAuth(resolveAuthPath(placement.agentDir, opts.authPath));
31
37
  // The same opener dev uses (single assembly source), just no watch.
32
38
  const sessionsDirOverride = resolveSessionsDirOverride(opts.sessionsDir);
33
- const { agent, definition, agentDir, config, modelSpec, stateRoot, sessionsDir, authPath, toolNames, deferredToolNames, toolCollisions, toolFailures, } = await createPiAgentFromWorkspace(dir, {
39
+ const { agent, definition, agentDir, workspace, config, modelSpec, stateRoot, sessionsDir, authPath, toolNames, deferredToolNames, toolCollisions, toolFailures, sessionControl, } = await createPiAgentFromDir(dir, {
34
40
  model: opts.model,
35
41
  sessionsDir: sessionsDirOverride,
36
- authPath: authPathOverride,
42
+ authPath: opts.authPath,
37
43
  serving: true, // long-running serve: the scheduler poller runs (wake mounts iff config.selfSchedule)
38
44
  }).catch(failStartup);
39
- log.info(`[fastagent] start: ${dir}`);
40
- if (agentDir !== dir)
41
- log.info(`[fastagent] agent: ${agentDir}`);
42
- log.info(`[fastagent] model: ${modelSpec}${config.thinkingLevel ? ` (thinking: ${config.thinkingLevel})` : ""}`);
45
+ reportLine("agent", agentDir);
46
+ reportLine("workspace", workspace);
47
+ reportWorkspaceHint(workspaceHint({ agentDir, workspace }));
48
+ reportLine("model", `${modelSpec}${config.thinkingLevel ? ` (thinking: ${config.thinkingLevel})` : ""}`);
43
49
  await reportAuth(modelSpec, authPath);
44
- log.info(`[fastagent] context: ${definition.contextFiles.map((f) => f.path).join(", ") || "(none)"}`);
50
+ reportLine("context", definition.contextFiles.map((f) => f.path).join(", ") || "(none)");
45
51
  if (definition.persona)
46
- log.info(`[fastagent] persona: persona.md`);
47
- log.info(`[fastagent] skills: ${definition.skills.map((s) => s.name).join(", ") || "(none)"}`);
52
+ reportLine("persona", "persona.md");
53
+ reportLine("skills", definition.skills.map((s) => s.name).join(", ") || "(none)");
48
54
  if (toolNames.length > 0)
49
- log.info(`[fastagent] tools: ${toolNames.join(", ")}`);
55
+ reportLine("tools", toolNames.join(", "));
50
56
  if (deferredToolNames.length > 0) {
51
- log.info(`[fastagent] deferred: ${deferredToolNames.join(", ")} (activated via search_tools)`);
57
+ reportLine("deferred", `${deferredToolNames.join(", ")} (activated via search_tools)`);
52
58
  }
53
59
  reportToolCollisions(toolCollisions);
54
60
  reportModuleLoadFailures(toolFailures);
55
- log.info(`[fastagent] state: ${stateRoot}`);
56
- log.info(`[fastagent] sessions: ${sessionsDir}`);
57
- // State defaults under the definition dir, which a redeploy may replace wholesale. Gate on where the
58
- // root ACTUALLY resolved (in-tree?), not on the raw env var: an empty `FASTAGENT_STATE_DIR=""` reads
59
- // as unset (resolveStateRoot) and still lands in-tree, so a raw `=== undefined` check would wrongly
60
- // silence the warning. A sessions/auth override to a volume does not help — channel state (the
61
- // telegram turn/context files replay depends on) is still in-tree.
62
- if (isUnderDir(stateRoot, dir)) {
63
- log.info(`[fastagent] note: state (auth, sessions, channel state) lives under the definition dir; point ` +
61
+ reportLine("state", stateRoot);
62
+ reportLine("sessions", sessionsDir);
63
+ // State defaults under the agent dir, which a redeploy may replace wholesale. Gate on where the
64
+ // state root ACTUALLY resolved (inside the agent dir?), not on the raw env var: an empty
65
+ // `FASTAGENT_STATE_DIR=""` reads as unset (resolveStateRoot) and still lands in-agent, so a raw
66
+ // `=== undefined` check would wrongly silence the warning. A sessions override to a volume does not
67
+ // help — channel state (the telegram turn/context files replay depends on) is still in-agent.
68
+ // (auth.json is NOT under the state root — it lives in the secrets dir, resolveSecretsDir.)
69
+ if (isUnderDir(stateRoot, agentDir)) {
70
+ log.info(`[fastagent] note: state (sessions, channel state) lives under the definition dir; point ` +
64
71
  `FASTAGENT_STATE_DIR at a persistent volume so a redeploy that replaces the dir does not wipe it.`);
65
72
  }
73
+ // The secrets dir is the SAME trap on a different lifecycle: auth.json is MACHINE-WRITTEN (OAuth
74
+ // rotation), so the copy under the definition dir is the only valid one — a redeploy that replaces
75
+ // the dir loses a credential no re-seed can restore. An independent check on purpose: moving ONE
76
+ // of the two to a volume must not silence the note about the other.
77
+ if (isUnderDir(resolveSecretsDir(agentDir), agentDir)) {
78
+ log.info(`[fastagent] note: secrets (.env, rotated auth.json) live under the definition dir; point ` +
79
+ `FASTAGENT_SECRETS_DIR at a persistent volume so a redeploy that replaces the dir does not wipe them.`);
80
+ }
66
81
  reportDefinitionWarnings(definition.collisions, definition.diagnostics);
82
+ // AgentCore Runtime posture (FASTAGENT_AGENTCORE=1, set by the generated deploy artifacts): the
83
+ // adapter (POST /invocations + GET /ping) is the container's only reachable surface, and cron
84
+ // slots arrive from the external clock through it — so no resident cron timers. In particular,
85
+ // do NOT mount the ordinary unauthenticated POST /invoke fallback: a selfSchedule-only topology
86
+ // needs a public Function URL for wake callbacks, and its forwarder can relay arbitrary paths.
87
+ const agentcore = process.env.FASTAGENT_AGENTCORE === "1";
67
88
  // Same debug turn trace as dev; gated out here by the info level (see dev.ts serveOnce).
68
89
  const traced = logAgentLoop(agent);
69
- const routes = await routesFor(agentDir, traced, stateRoot).catch(failStartup);
70
- await startSchedules(agentDir, traced, stateRoot, config.selfSchedule ?? false);
71
- serve(routes, portFlag ?? parsePort(process.env.PORT, "PORT env", "env") ?? config.http?.port ?? 8787, (p) => maybeTunnel(agentDir, p, opts.tunnel ?? false));
90
+ const routed = await routesFor(agentDir, traced, stateRoot, sessionControl, { builtinInvoke: !agentcore }).catch(failStartup);
91
+ const withControl = mountSessionControl(routed.routes, sessionControl, stateRoot, {
92
+ tunnel: opts.tunnel ?? false,
93
+ agent: traced,
94
+ });
95
+ // AgentCore + selfSchedule: register the wake-ALARM sink BEFORE the scheduler starts — the boot
96
+ // wake pump may advance a recurring entry (a store save) and that save must already re-arm its
97
+ // alarm. The secret arrives via the stack (FASTAGENT_WAKE_SECRET); without it the deployment
98
+ // degrades to awake-only wakes — warned, never silent.
99
+ let onStateReady;
100
+ if (agentcore && config.selfSchedule) {
101
+ const wakeSecret = process.env.FASTAGENT_WAKE_SECRET;
102
+ if (wakeSecret) {
103
+ const sink = createWakeAlarmSink({ secret: wakeSecret });
104
+ setWakeupsSink(sink);
105
+ // NOT here: at boot the state mount is whatever the platform just provisioned — after a runtime
106
+ // version update that is EMPTY, so a reconcile now would see no pending wake-ups and conclude
107
+ // there is nothing to re-arm. It runs once the snapshot has been restored (mountAgentcore).
108
+ onStateReady = () => reconcileWakeAlarms(stateRoot, sink);
109
+ log.info(`[fastagent] wake alarms: EventBridge-backed via the forwarder`);
110
+ }
111
+ else {
112
+ log.warn(`[fastagent] FASTAGENT_WAKE_SECRET is not set — wake-ups fire only while a session is awake ` +
113
+ `(redeploy with the current template to fix)`);
114
+ }
115
+ }
116
+ const schedules = await startSchedules(agentDir, traced, stateRoot, config.selfSchedule ?? false, {
117
+ externalClock: agentcore,
118
+ });
119
+ let routes = withControl.routes;
120
+ if (agentcore) {
121
+ try {
122
+ routes = mountAgentcore(routes, { agent: traced, stateRoot, schedules, onStateReady });
123
+ }
124
+ catch (e) {
125
+ failStartup(e);
126
+ }
127
+ log.info(`[fastagent] agentcore: serving POST /invocations + GET /ping (FASTAGENT_AGENTCORE=1)`);
128
+ }
129
+ serve({ ...routed, routes }, portFlag ?? parsePort(process.env.PORT, "PORT env", "env") ?? config.http?.port ?? 8787, (p) => {
130
+ withControl.announce(p);
131
+ maybeTunnel(agentDir, routed.routeChannels, p, opts.tunnel ?? false, stateRoot);
132
+ });
72
133
  // No graceful drain: webhook turns run fire-and-forget; SIGTERM just exits mid-turn. Whether an
73
134
  // in-flight turn is LOST depends on the channel: the Telegram channel persists turn intent pre-ACK
74
135
  // and replays it next start (turn-store.ts, L1 durable execution, at-least-once); HTTP and other
75
136
  // channels have no such layer, so their in-flight turns are still lost (the asker re-invokes).
76
137
  }
77
138
  /**
78
- * Materialize `FASTAGENT_AUTH_SEED` (base64 of an auth.json, set by `deploy --run`) onto the
79
- * writable state root ONCE — only when the seed is set AND the auth file is absent, so a refreshed
139
+ * Materialize `FASTAGENT_AUTH_SEED` (base64 of an auth.json, set by `deploy --run`) into the
140
+ * writable secrets dir ONCE — only when the seed is set AND the auth file is absent, so a refreshed
80
141
  * volume copy is never clobbered by the stale seed. Lets a deploy carry the operator's local
81
142
  * OAuth/API credential so the box runs on the SAME subscription. No-op locally (the seed is unset).
82
143
  */
83
144
  async function maybeSeedAuth(authPath) {
84
- const bytes = authSeedBytes(process.env.FASTAGENT_AUTH_SEED, await exists(authPath));
145
+ // collectAuthSeed: the seed may arrive CHUNKED (FASTAGENT_AUTH_SEED + _2…) on hosts with a small
146
+ // env-value max length (AgentCore); single-var hosts are unchanged.
147
+ const bytes = authSeedBytes(collectAuthSeed(process.env), await exists(authPath));
85
148
  if (!bytes)
86
149
  return;
87
150
  await mkdir(dirname(authPath), { recursive: true });
@@ -1,19 +1,22 @@
1
1
  /** `fastagent tool <name> '<json>' [dir]`: run one tool's body directly with JSON args — no model. */
2
2
  import { resolve } from "node:path";
3
3
  import { loadDotEnv } from "../../env.js";
4
- import { loadConfig, resolveAgentDir } from "../../engines/pi/config.js";
5
- import { resolveWorkspaceTools } from "../../engines/pi/create.js";
4
+ import { loadConfig } from "../../engines/pi/config.js";
5
+ import { resolveAgentTools } from "../../engines/pi/create.js";
6
6
  import { reportModuleLoadFailures } from "../../engines/pi/report.js";
7
- import { failStartup, failUsage } from "../fail.js";
7
+ import { turnContext } from "../../engines/pi/tool-context.js";
8
+ import { failStartup, failUsage, placementOrExit } from "../fail.js";
8
9
  export async function runTool(name, argsJson, dirArg) {
9
- const toolDir = resolve(dirArg);
10
- loadDotEnv(toolDir); // a tool may read a key from .env
11
- const { config } = await loadConfig(toolDir).catch(failStartup);
10
+ // Argument shape first: malformed JSON is a USAGE error (exit 2), independent of whether the
11
+ // directory is an agent (a runtime failure, exit 1).
12
+ const args = parseToolArgs(argsJson);
13
+ const { agentDir, workspace } = placementOrExit(resolve(dirArg));
14
+ loadDotEnv(agentDir); // a tool may read a key from .env
15
+ const { config } = await loadConfig(agentDir).catch(failStartup);
12
16
  // The same tool set dev/start mount (defaults + config.tools + discovered, deduped), so the runner
13
- // exercises exactly what gets served — a shadowed tool is surfaced, not silently run. Resolve agentDir
14
- // like the openers so `fastagent tool` finds the SAME tools/ as dev/start when config.agentDir is set.
15
- const agentDir = resolveAgentDir(toolDir, config);
16
- const { tools, toolCollisions, toolFailures } = await resolveWorkspaceTools(config, agentDir, toolDir).catch(failStartup);
17
+ // exercises exactly what gets served — a shadowed tool is surfaced, not silently run. Resolve the
18
+ // placement like the openers, so `fastagent tool` finds the SAME tools/ as dev/start.
19
+ const { tools, toolCollisions, toolFailures } = await resolveAgentTools(config, agentDir, workspace).catch(failStartup);
17
20
  for (const c of toolCollisions) {
18
21
  console.error(`[fastagent] warn: tool "${c.name}" (${c.source}) is shadowed by a default/config tool — not mounted`);
19
22
  }
@@ -22,16 +25,18 @@ export async function runTool(name, argsJson, dirArg) {
22
25
  if (!tool) {
23
26
  failStartup(new Error(`unknown tool "${name}". available: ${tools.map((t) => t.name).join(", ") || "(none)"}`));
24
27
  }
25
- let args;
26
- try {
27
- args = JSON.parse(argsJson);
28
- }
29
- catch {
30
- failUsage(`invalid JSON args: ${argsJson}`); // malformed input syntax = usage error, exit 2
31
- }
32
- const result = await tool.execute(`cli-${name}`, args).catch(failStartup);
28
+ const result = await turnContext.run({ cwd: workspace }, () => tool.execute(`cli-${name}`, args)).catch(failStartup);
33
29
  const out = result?.details !== undefined
34
30
  ? result.details
35
31
  : (result?.content ?? []).map((c) => ("text" in c ? c.text : "")).join("");
36
32
  console.log(typeof out === "string" ? out : JSON.stringify(out, null, 2));
37
33
  }
34
+ /** Parse the CLI's JSON args blob; malformed input syntax is a usage error (exit 2). */
35
+ function parseToolArgs(argsJson) {
36
+ try {
37
+ return JSON.parse(argsJson);
38
+ }
39
+ catch {
40
+ failUsage(`invalid JSON args: ${argsJson}`);
41
+ }
42
+ }
@@ -1,5 +1,4 @@
1
- /** stderr renders color: a color TTY, with Node's `hasColors()` carrying the NO_COLOR/TERM=dumb veto. */
2
- export declare function stderrHasColors(): boolean;
1
+ import { type ResolvedPlacement } from "../paths.ts";
3
2
  /**
4
3
  * The ONE error prefix every error message carries — bold red when stderr renders color, plain
5
4
  * otherwise. Errors are the only place the CLI uses color at all.
@@ -11,6 +10,22 @@ export declare function errorPrefix(colors?: boolean): string;
11
10
  * Shared by the kernel and the command modules; exit 1 (runtime failure).
12
11
  */
13
12
  export declare function failStartup(error: unknown): never;
13
+ /**
14
+ * THE placement entry point for commands: resolve `dir`, or exit 1 with the one-line refusal. Every
15
+ * command that needs an agent goes through this — the try/catch was hand-repeated at sixteen call
16
+ * sites, which is sixteen chances to forget it (`dev`'s supervisor did, and surfaced a raw stack).
17
+ *
18
+ * The catch is what makes it a function at all: placement resolves SYNCHRONOUSLY, before any promise
19
+ * exists, so it cannot ride the `.catch(failStartup)` every async startup chain carries — and its
20
+ * refusals ("not a fastagent agent", plus the two dead ends with their own exits) are user-fixable,
21
+ * which means a one-line `Error:` and exit 1, never a stack.
22
+ *
23
+ * Not every command calls it — and the exceptions are the useful part of this note. `init` CREATES the
24
+ * agent (it must run where there is none). `models` is directory-independent. `login` needs one only for
25
+ * the project-level credential and falls back to the global one, and `attach --url/--token` reads
26
+ * nothing local. Everything else needs an agent, so it comes through here.
27
+ */
28
+ export declare function placementOrExit(dir: string): ResolvedPlacement;
14
29
  /**
15
30
  * A usage error the parser could not catch (a bad value shape, an invalid flag/argument combination
16
31
  * discovered in a command body): print the message and exit 2 — the same class as a parse error.
package/dist/cli/fail.js CHANGED
@@ -1,5 +1,6 @@
1
+ import { resolvePlacement } from "../paths.js";
1
2
  /** stderr renders color: a color TTY, with Node's `hasColors()` carrying the NO_COLOR/TERM=dumb veto. */
2
- export function stderrHasColors() {
3
+ function stderrHasColors() {
3
4
  return process.stderr.isTTY === true && (process.stderr.hasColors?.() ?? false);
4
5
  }
5
6
  /**
@@ -21,6 +22,29 @@ export function failStartup(error) {
21
22
  console.error(errorPrefix(), error);
22
23
  process.exit(1);
23
24
  }
25
+ /**
26
+ * THE placement entry point for commands: resolve `dir`, or exit 1 with the one-line refusal. Every
27
+ * command that needs an agent goes through this — the try/catch was hand-repeated at sixteen call
28
+ * sites, which is sixteen chances to forget it (`dev`'s supervisor did, and surfaced a raw stack).
29
+ *
30
+ * The catch is what makes it a function at all: placement resolves SYNCHRONOUSLY, before any promise
31
+ * exists, so it cannot ride the `.catch(failStartup)` every async startup chain carries — and its
32
+ * refusals ("not a fastagent agent", plus the two dead ends with their own exits) are user-fixable,
33
+ * which means a one-line `Error:` and exit 1, never a stack.
34
+ *
35
+ * Not every command calls it — and the exceptions are the useful part of this note. `init` CREATES the
36
+ * agent (it must run where there is none). `models` is directory-independent. `login` needs one only for
37
+ * the project-level credential and falls back to the global one, and `attach --url/--token` reads
38
+ * nothing local. Everything else needs an agent, so it comes through here.
39
+ */
40
+ export function placementOrExit(dir) {
41
+ try {
42
+ return resolvePlacement(dir);
43
+ }
44
+ catch (error) {
45
+ failStartup(error);
46
+ }
47
+ }
24
48
  /**
25
49
  * A usage error the parser could not catch (a bad value shape, an invalid flag/argument combination
26
50
  * discovered in a command body): print the message and exit 2 — the same class as a parse error.
@@ -6,5 +6,5 @@
6
6
  * An errored tool inside an otherwise-completed turn still surfaces (the diagnostic the operator
7
7
  * needs); tool_ended carries no name, so it is remembered from the matching tool_started.
8
8
  */
9
- import type { AgentEvent } from "./agent.ts";
9
+ import type { AgentEvent } from "../agent.ts";
10
10
  export declare function runInvokeStream(events: AsyncIterable<AgentEvent>, out: (text: string) => void, err: (line: string) => void): Promise<number>;
@@ -14,6 +14,10 @@ export async function runInvokeStream(events, out, err) {
14
14
  if (event.isError)
15
15
  err(`[tool] ${toolName.get(event.id) ?? event.id} failed`);
16
16
  break;
17
+ case "retrying":
18
+ // Operator-facing: include the reason (channels show a neutral customer line instead).
19
+ err(`[fastagent] transient failure — retrying (${event.attempt}/${event.maxAttempts} in ${event.delayMs}ms): ${event.reason}`);
20
+ break;
17
21
  case "failed":
18
22
  err(`[fastagent] failed: ${event.details}${event.retryable ? " (retryable)" : ""}`);
19
23
  exitCode = 1;
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { Command } from "commander";
15
15
  /** One positional argument, in commander syntax: `<name>` required, `[dir]` optional. */
16
- export interface ArgSpec {
16
+ interface ArgSpec {
17
17
  name: string;
18
18
  description: string;
19
19
  default?: string;
@@ -29,7 +29,7 @@ export interface FlagSpec {
29
29
  /** Mutually exclusive with these {@link optionKey} values — validated at build time. */
30
30
  conflicts?: string[];
31
31
  }
32
- export interface ExampleSpec {
32
+ interface ExampleSpec {
33
33
  cmd: string;
34
34
  note?: string;
35
35
  }
@@ -87,3 +87,4 @@ export interface ProgramOptions {
87
87
  export declare function optionKey(flags: string): string;
88
88
  /** Build the commander program for `specs`. The CLI entry parses with it; tests inject the IO seams. */
89
89
  export declare function buildProgram(specs: readonly CommandSpec[], options?: ProgramOptions): Command;
90
+ export {};
@@ -0,0 +1,21 @@
1
+ import type { ProviderAuthStatus } from "../engines/pi/models.ts";
2
+ /** One first-run picker entry (@clack/prompts option shape). */
3
+ export interface ModelPickerOption {
4
+ value: string;
5
+ label: string;
6
+ hint: string;
7
+ }
8
+ /**
9
+ * The first-run picker menu: the FULL model catalog, each spec annotated with its provider's auth
10
+ * status — ready first (usable now, with the credential source so "which account pays" is visible at
11
+ * the decision point), then the rest with their remedy. Order within each group preserves `specs`
12
+ * (sorted by the caller). A broken provider (expired/corrupt credential) is annotated, not dropped —
13
+ * fail visibly.
14
+ */
15
+ export declare function buildModelPickerOptions(specs: string[], statuses: Map<string, ProviderAuthStatus>): ModelPickerOption[];
16
+ /** The output of `fastagent models [search]`: the spec `lines` to print to stdout (a case-insensitive
17
+ * substring filter; no search → all), and an stderr `error` diagnostic when a search matches nothing. */
18
+ export declare function formatModelsCommand(specs: string[], search?: string): {
19
+ lines: string[];
20
+ error?: string;
21
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * CLI presenter for the model-facing commands: `fastagent models [search]` output and the first-run
3
+ * picker's option list. Output DECISIONS live here, out of the engine config layer (config.ts owns
4
+ * model resolution / listModels), so this process-boundary behavior is unit-testable without
5
+ * spawning the CLI.
6
+ */
7
+ import { providerOf } from "../engines/pi/config.js";
8
+ /** The remedy hint for a non-ready provider, by what picking it actually does: an OAuth flow →
9
+ * "login required"; an interactive key prompt → "API key required"; no flow at all → the env var. */
10
+ function remedy(login) {
11
+ if (login === "oauth")
12
+ return "login required";
13
+ return login === "api_key" ? "API key required" : "API key required — set the provider's env var";
14
+ }
15
+ /**
16
+ * The first-run picker menu: the FULL model catalog, each spec annotated with its provider's auth
17
+ * status — ready first (usable now, with the credential source so "which account pays" is visible at
18
+ * the decision point), then the rest with their remedy. Order within each group preserves `specs`
19
+ * (sorted by the caller). A broken provider (expired/corrupt credential) is annotated, not dropped —
20
+ * fail visibly.
21
+ */
22
+ export function buildModelPickerOptions(specs, statuses) {
23
+ const ready = [];
24
+ const rest = [];
25
+ for (const spec of specs) {
26
+ const status = statuses.get(providerOf(spec));
27
+ if (status?.state === "ready") {
28
+ ready.push({ value: spec, label: spec, hint: status.source ? `ready — ${status.source}` : "ready" });
29
+ }
30
+ else if (status?.state === "broken") {
31
+ // A broken stored credential OWNS the provider (env is consulted only when nothing is stored),
32
+ // so with no login flow the remedy is fixing the store — not the env var (which can't win here).
33
+ rest.push({
34
+ value: spec,
35
+ label: spec,
36
+ hint: status.login === "none"
37
+ ? `stored auth unusable: ${status.message} — fix or remove the stored credential`
38
+ : `${remedy(status.login)} — stored auth unusable: ${status.message}`,
39
+ });
40
+ }
41
+ else if (status) {
42
+ rest.push({ value: spec, label: spec, hint: remedy(status.login) });
43
+ }
44
+ else {
45
+ // Unreachable when `specs` and `statuses` come from the same Models (every listed provider is
46
+ // probed); if a caller ever mixes sources, promise nothing — neutral wording, no login claim.
47
+ rest.push({ value: spec, label: spec, hint: "auth required" });
48
+ }
49
+ }
50
+ return [...ready, ...rest];
51
+ }
52
+ /** The output of `fastagent models [search]`: the spec `lines` to print to stdout (a case-insensitive
53
+ * substring filter; no search → all), and an stderr `error` diagnostic when a search matches nothing. */
54
+ export function formatModelsCommand(specs, search) {
55
+ if (!search)
56
+ return { lines: specs };
57
+ const q = search.toLowerCase();
58
+ const matches = specs.filter((spec) => spec.toLowerCase().includes(q));
59
+ if (matches.length === 0)
60
+ return { lines: matches, error: `no model matches "${search}"` };
61
+ // Rank a PROVIDER-name match (query in the part before "/") above an incidental model-id match, so
62
+ // `models anthropic` leads with anthropic/* rather than burying it under amazon-bedrock/anthropic.*
63
+ // and google-vertex/…-anthropic-… (which only match in the model id). Order within each group is kept.
64
+ const providerMatch = (spec) => providerOf(spec).toLowerCase().includes(q);
65
+ return { lines: [...matches.filter(providerMatch), ...matches.filter((s) => !providerMatch(s))] };
66
+ }