@fastagent-sh/fastagent 0.12.1 → 0.14.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 (151) hide show
  1. package/README.md +6 -4
  2. package/dist/channels/feishu/bootstrap-token.d.ts +42 -0
  3. package/dist/channels/feishu/bootstrap-token.js +94 -0
  4. package/dist/channels/feishu/card.d.ts +32 -0
  5. package/dist/channels/feishu/card.js +66 -0
  6. package/dist/channels/feishu/cloud.d.ts +17 -0
  7. package/dist/channels/feishu/cloud.js +19 -0
  8. package/dist/channels/feishu/context-buffer.d.ts +46 -0
  9. package/dist/channels/feishu/context-buffer.js +133 -0
  10. package/dist/channels/feishu/crypto.d.ts +15 -0
  11. package/dist/channels/feishu/crypto.js +43 -0
  12. package/dist/channels/feishu/feishu-api.d.ts +108 -0
  13. package/dist/channels/feishu/feishu-api.js +325 -0
  14. package/dist/channels/feishu/feishu.d.ts +46 -0
  15. package/dist/channels/feishu/feishu.js +472 -0
  16. package/dist/channels/feishu/invoke-turn.d.ts +65 -0
  17. package/dist/channels/feishu/invoke-turn.js +157 -0
  18. package/dist/channels/feishu/model.d.ts +97 -0
  19. package/dist/channels/feishu/model.js +9 -0
  20. package/dist/channels/feishu/normalize.d.ts +22 -0
  21. package/dist/channels/feishu/normalize.js +132 -0
  22. package/dist/channels/feishu/owned-threads.d.ts +11 -0
  23. package/dist/channels/feishu/owned-threads.js +47 -0
  24. package/dist/channels/feishu/parse.d.ts +43 -0
  25. package/dist/channels/feishu/parse.js +65 -0
  26. package/dist/channels/feishu/preview.d.ts +36 -0
  27. package/dist/channels/feishu/preview.js +387 -0
  28. package/dist/channels/feishu/register-app.d.ts +70 -0
  29. package/dist/channels/feishu/register-app.js +141 -0
  30. package/dist/channels/feishu/register-webhook.d.ts +22 -0
  31. package/dist/channels/feishu/register-webhook.js +106 -0
  32. package/dist/channels/feishu/scaffold/channel.ts +41 -0
  33. package/dist/channels/feishu/scaffold/feishu-send.ts +87 -0
  34. package/dist/channels/feishu/seen.d.ts +5 -0
  35. package/dist/channels/feishu/seen.js +47 -0
  36. package/dist/channels/feishu/text.d.ts +13 -0
  37. package/dist/channels/feishu/text.js +63 -0
  38. package/dist/channels/lark/lark.d.ts +15 -0
  39. package/dist/channels/lark/lark.js +10 -0
  40. package/dist/channels/lark/onboard.d.ts +39 -0
  41. package/dist/channels/lark/onboard.js +58 -0
  42. package/dist/channels/lark/scaffold/channel.ts +39 -0
  43. package/dist/channels/lark/scaffold/lark-send.ts +87 -0
  44. package/dist/channels/registration.d.ts +15 -0
  45. package/dist/channels/registration.js +1 -0
  46. package/dist/channels/{telegram/state.js → state.js} +6 -4
  47. package/dist/channels/telegram/context-buffer.js +1 -1
  48. package/dist/channels/telegram/register-webhook.d.ts +4 -1
  49. package/dist/channels/telegram/register-webhook.js +17 -26
  50. package/dist/channels/telegram/telegram.js +2 -2
  51. package/dist/channels/telegram/turn-store.d.ts +8 -21
  52. package/dist/channels/telegram/turn-store.js +11 -130
  53. package/dist/channels/{telegram/turn-queue.js → turn-queue.js} +3 -3
  54. package/dist/channels/turn-store.d.ts +42 -0
  55. package/dist/channels/turn-store.js +139 -0
  56. package/dist/channels/wait-health.d.ts +6 -0
  57. package/dist/channels/wait-health.js +27 -0
  58. package/dist/cli/commands/add.d.ts +9 -0
  59. package/dist/cli/commands/add.js +142 -0
  60. package/dist/cli/commands/chat.d.ts +3 -0
  61. package/dist/cli/commands/chat.js +16 -0
  62. package/dist/cli/commands/deploy.d.ts +13 -0
  63. package/dist/cli/commands/deploy.js +338 -0
  64. package/dist/cli/commands/dev.d.ts +11 -0
  65. package/dist/cli/commands/dev.js +76 -0
  66. package/dist/cli/commands/fire.d.ts +7 -0
  67. package/dist/cli/commands/fire.js +45 -0
  68. package/dist/cli/commands/info.d.ts +7 -0
  69. package/dist/cli/commands/info.js +108 -0
  70. package/dist/cli/commands/init.d.ts +8 -0
  71. package/dist/cli/commands/init.js +81 -0
  72. package/dist/cli/commands/invoke.d.ts +7 -0
  73. package/dist/cli/commands/invoke.js +29 -0
  74. package/dist/cli/commands/login.d.ts +6 -0
  75. package/dist/cli/commands/login.js +63 -0
  76. package/dist/cli/commands/models.d.ts +1 -0
  77. package/dist/cli/commands/models.js +15 -0
  78. package/dist/cli/commands/schedule.d.ts +12 -0
  79. package/dist/cli/commands/schedule.js +89 -0
  80. package/dist/cli/commands/start.d.ts +10 -0
  81. package/dist/cli/commands/start.js +90 -0
  82. package/dist/cli/commands/tool.d.ts +1 -0
  83. package/dist/cli/commands/tool.js +37 -0
  84. package/dist/cli/fail.d.ts +19 -0
  85. package/dist/cli/fail.js +32 -0
  86. package/dist/cli/kernel.d.ts +89 -0
  87. package/dist/cli/kernel.js +190 -0
  88. package/dist/cli/program.d.ts +11 -0
  89. package/dist/cli/program.js +421 -0
  90. package/dist/cli/serve.d.ts +28 -0
  91. package/dist/cli/serve.js +90 -0
  92. package/dist/cli/shared.d.ts +24 -0
  93. package/dist/cli/shared.js +116 -0
  94. package/dist/cli-add-feishu.d.ts +8 -0
  95. package/dist/cli-add-feishu.js +223 -0
  96. package/dist/cli.js +8 -1284
  97. package/dist/deploy/container.js +10 -6
  98. package/dist/deploy/docker/plan.d.ts +45 -0
  99. package/dist/deploy/docker/plan.js +139 -0
  100. package/dist/deploy/docker/run.d.ts +40 -0
  101. package/dist/deploy/docker/run.js +126 -0
  102. package/dist/deploy/fly/plan.d.ts +1 -1
  103. package/dist/deploy/fly/plan.js +15 -4
  104. package/dist/deploy/fly/run.d.ts +7 -4
  105. package/dist/deploy/fly/run.js +26 -5
  106. package/dist/deploy/preflight.js +4 -3
  107. package/dist/deploy/railway/plan.d.ts +1 -1
  108. package/dist/deploy/railway/plan.js +17 -5
  109. package/dist/deploy/railway/run.d.ts +6 -3
  110. package/dist/deploy/railway/run.js +26 -4
  111. package/dist/deploy/registration-gate.d.ts +20 -0
  112. package/dist/deploy/registration-gate.js +20 -0
  113. package/dist/deploy/runner.d.ts +4 -1
  114. package/dist/deploy/runner.js +1 -0
  115. package/dist/deploy/secrets.d.ts +10 -9
  116. package/dist/deploy/secrets.js +15 -14
  117. package/dist/dev-supervisor.js +2 -1
  118. package/dist/engines/pi/auth.js +160 -46
  119. package/dist/engines/pi/chat.js +80 -7
  120. package/dist/engines/pi/config.d.ts +12 -3
  121. package/dist/engines/pi/config.js +16 -1
  122. package/dist/engines/pi/create.d.ts +14 -6
  123. package/dist/engines/pi/create.js +52 -16
  124. package/dist/engines/pi/harness.d.ts +16 -1
  125. package/dist/engines/pi/harness.js +77 -1
  126. package/dist/engines/pi/invoke.d.ts +1 -1
  127. package/dist/engines/pi/invoke.js +37 -2
  128. package/dist/engines/pi/login.js +1 -1
  129. package/dist/engines/pi/search-tools.d.ts +10 -0
  130. package/dist/engines/pi/search-tools.js +138 -0
  131. package/dist/engines/pi/tool-context.d.ts +28 -0
  132. package/dist/engines/pi/tool-context.js +8 -0
  133. package/dist/engines/pi/tool.d.ts +32 -1
  134. package/dist/engines/pi/tool.js +42 -1
  135. package/dist/engines/pi/workspace.d.ts +4 -1
  136. package/dist/engines/pi/workspace.js +3 -1
  137. package/dist/feishu.d.ts +2 -0
  138. package/dist/feishu.js +2 -0
  139. package/dist/lark.d.ts +3 -0
  140. package/dist/lark.js +3 -0
  141. package/dist/open-url.d.ts +2 -0
  142. package/dist/open-url.js +6 -0
  143. package/dist/pi.d.ts +2 -1
  144. package/dist/scaffold/add-channel.d.ts +9 -5
  145. package/dist/scaffold/add-channel.js +75 -7
  146. package/dist/scaffold/templates/fastagent.config.mjs +1 -0
  147. package/dist/tunnel.d.ts +9 -6
  148. package/dist/tunnel.js +48 -31
  149. package/package.json +18 -4
  150. /package/dist/channels/{telegram/state.d.ts → state.d.ts} +0 -0
  151. /package/dist/channels/{telegram/turn-queue.d.ts → turn-queue.d.ts} +0 -0
package/dist/tunnel.js CHANGED
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * `--tunnel`: expose the local dev server on a public HTTPS URL via a Cloudflare quick tunnel, then
3
- * auto-register the first-party webhook channels against it (telegram setWebhook; github prints the
4
- * URL to paste into repo settings). This closes the "local dev → public URL" gap webhooks need.
3
+ * auto-register the first-party webhook channels against it (Telegram setWebhook; Feishu/Lark
4
+ * application-config PATCH; GitHub prints the URL to paste into repo settings). This closes the
5
+ * "local dev → public URL" gap webhooks need.
5
6
  *
6
7
  * Process orchestration, not assembly — lives outside the engine, beside dev-supervisor.ts.
7
8
  */
8
9
  import { spawn } from "node:child_process";
9
10
  import { readdirSync } from "node:fs";
10
11
  import { join } from "node:path";
11
- import { setTimeout as sleep } from "node:timers/promises";
12
+ import { registerFeishuWebhook } from "./channels/feishu/register-webhook.js";
12
13
  import { registerTelegramWebhook } from "./channels/telegram/register-webhook.js";
13
14
  import { loadDotEnv } from "./env.js";
14
15
  import { log } from "./log.js";
@@ -21,8 +22,12 @@ const TUNNEL_URL_RE = /https:\/\/(?!api\.)[a-z0-9-]+\.trycloudflare\.com/i;
21
22
  export function parseTunnelUrl(chunk) {
22
23
  return chunk.match(TUNNEL_URL_RE)?.[0];
23
24
  }
25
+ /** Global timer (rather than timers/promises) so timeout/retry behavior is deterministic under fake timers. */
26
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
24
27
  const TUNNEL_ATTEMPTS = 3;
25
28
  const TUNNEL_RETRY_MS = 2000;
29
+ /** cloudflared can stay alive without ever receiving/printing an assigned quick-tunnel URL. */
30
+ const TUNNEL_START_TIMEOUT_MS = 30_000;
26
31
  const spawnCloudflared = (port) => spawn("cloudflared", ["tunnel", "--url", `http://localhost:${port}`], { stdio: ["ignore", "pipe", "pipe"] });
27
32
  /**
28
33
  * Start a Cloudflare quick tunnel to localhost:`port`, resolving once its public URL appears.
@@ -32,9 +37,9 @@ const spawnCloudflared = (port) => spawn("cloudflared", ["tunnel", "--url", `htt
32
37
  * (Edge warmup AFTER the URL appears is handled downstream: the telegram registrar polls /health before
33
38
  * it calls setWebhook, so a not-yet-routable tunnel just delays registration rather than failing it.)
34
39
  */
35
- export async function startCloudflareTunnel(port, spawnFn = spawnCloudflared) {
40
+ export async function startCloudflareTunnel(port, spawnFn = spawnCloudflared, attemptTimeoutMs = TUNNEL_START_TIMEOUT_MS) {
36
41
  for (let attempt = 1; attempt <= TUNNEL_ATTEMPTS; attempt++) {
37
- const r = await spawnTunnelOnce(port, spawnFn);
42
+ const r = await spawnTunnelOnce(port, spawnFn, attemptTimeoutMs);
38
43
  if (r.tunnel)
39
44
  return r.tunnel;
40
45
  if (r.fatal) {
@@ -50,43 +55,45 @@ export async function startCloudflareTunnel(port, spawnFn = spawnCloudflared) {
50
55
  return undefined;
51
56
  }
52
57
  /** One cloudflared launch: a Tunnel on the first URL, or a failure (missing binary / exit before a URL). */
53
- function spawnTunnelOnce(port, spawnFn) {
58
+ function spawnTunnelOnce(port, spawnFn, timeoutMs) {
54
59
  return new Promise((resolve) => {
55
60
  const child = spawnFn(port);
56
61
  let settled = false;
62
+ let timer;
57
63
  let tail = ""; // recent output, surfaced as the failure reason
58
- const onChunk = (buf) => {
59
- tail = (tail + String(buf)).slice(-600);
64
+ const finish = (result) => {
60
65
  if (settled)
61
66
  return;
67
+ settled = true;
68
+ if (timer)
69
+ clearTimeout(timer);
70
+ resolve(result);
71
+ };
72
+ const onChunk = (buf) => {
73
+ tail = (tail + String(buf)).slice(-600);
62
74
  const url = parseTunnelUrl(String(buf));
63
- if (url) {
64
- settled = true;
65
- resolve({ tunnel: { url, close: () => child.kill("SIGTERM") } });
66
- }
75
+ if (url)
76
+ finish({ tunnel: { url, close: () => child.kill("SIGTERM") } });
67
77
  };
68
78
  child.stdout?.on("data", onChunk);
69
79
  child.stderr?.on("data", onChunk); // cloudflared prints the URL (and its errors) on stderr
70
80
  child.on("error", (e) => {
71
- if (settled)
72
- return;
73
- settled = true;
74
81
  if (e.code === "ENOENT") {
75
- resolve({
82
+ finish({
76
83
  fatal: true,
77
84
  message: "[fastagent] --tunnel needs cloudflared — install it (e.g. `brew install cloudflared`), then re-run. Serving without a tunnel.",
78
85
  });
79
86
  }
80
87
  else {
81
- resolve({ fatal: false, detail: e.message });
88
+ finish({ fatal: false, detail: e.message });
82
89
  }
83
90
  });
84
- child.on("exit", () => {
85
- if (settled)
86
- return;
87
- settled = true;
88
- resolve({ fatal: false, detail: lastErrorLine(tail) });
89
- });
91
+ child.on("exit", () => finish({ fatal: false, detail: lastErrorLine(tail) }));
92
+ timer = setTimeout(() => {
93
+ child.kill("SIGTERM");
94
+ finish({ fatal: false, detail: `timed out after ${Math.round(timeoutMs / 1000)}s waiting for a public URL` });
95
+ }, timeoutMs);
96
+ timer.unref();
90
97
  });
91
98
  }
92
99
  /** The most informative line of cloudflared's output tail (prefer an error line) for a failure log. */
@@ -109,31 +116,41 @@ function channelBasenames(dir) {
109
116
  }
110
117
  }
111
118
  /**
112
- * Print the public URL and wire up the first-party webhook channels found under `dir`: telegram is
113
- * auto-registered via setWebhook (using .env tokens); github prints the URL to add in repo settings.
119
+ * Print the public URL and wire up the first-party webhook channels found under `dir`: Telegram and
120
+ * Feishu/Lark auto-register using credentials from .env; GitHub prints the URL to add in repo settings.
114
121
  */
115
- export async function announceWebhooks(dir, baseUrl) {
122
+ export async function announceWebhooks(dir, baseUrl, opts = {}) {
116
123
  log.info(`[fastagent] public URL: ${baseUrl}`);
117
124
  try {
118
- loadDotEnv(dir); // telegram registration reads tokens from .env
125
+ loadDotEnv(dir); // webhook registrars read channel credentials from .env
119
126
  }
120
127
  catch (error) {
121
128
  // best-effort boundary: a MISSING .env is already tolerated by loadDotEnv; an unreadable one (EACCES,
122
129
  // or .env is a directory) must NOT crash the long-running dev/start server — announceWebhooks is
123
130
  // void-called with no unhandledRejection handler, so a throw here would terminate the process. Warn
124
- // (surface it, rule 8) and continue best-effort; telegram registration then degrades to its manual
125
- // instruction if the token is absent. loadDotEnv keeps throwing for the synchronous command callers.
131
+ // (surface it, rule 8) and continue best-effort; each registrar then surfaces its own
132
+ // missing-credential guidance. loadDotEnv keeps throwing for the synchronous command callers.
126
133
  log.warn(`[fastagent] could not read ${join(dir, ".env")}: ${error.message} — continuing without it`);
127
134
  }
128
135
  const channels = channelBasenames(dir);
129
136
  if (channels.length === 0)
130
137
  return;
131
138
  // Readiness is the registrar's job now: a fresh quick tunnel returns Cloudflare 530 for ~20-30s before
132
- // its origin connects, and registerTelegramWebhook polls /health until it serves before setWebhook (the
133
- // same wait the deploy runners rely on). github needs no wait — the operator adds that webhook by hand.
139
+ // its origin connects, and the automatic registrars poll /health before configuring the platform (the
140
+ // same wait the deploy runners rely on). GitHub needs no wait — the operator adds that webhook by hand.
134
141
  if (channels.includes("telegram"))
135
142
  await registerTelegramWebhook(baseUrl);
136
143
  if (channels.includes("github")) {
137
144
  log.info(`[fastagent] github: add a webhook in your repo (Settings → Webhooks): Payload URL = ${baseUrl}/webhook, content type application/json, secret = GITHUB_WEBHOOK_SECRET`);
138
145
  }
146
+ // feishu/lark register programmatically too (application-v7 config PATCH — telegram-setWebhook
147
+ // parity), once per mounted kind (each kind is its own app with its own credentials); the registrar
148
+ // owns its own health wait and degrades to the manual console instruction.
149
+ const feishuOptions = {
150
+ onManualRegistration: ({ consoleUrl }) => opts.openUrl?.(consoleUrl),
151
+ };
152
+ if (channels.includes("feishu"))
153
+ await registerFeishuWebhook(baseUrl, "feishu", feishuOptions);
154
+ if (channels.includes("lark"))
155
+ await registerFeishuWebhook(baseUrl, "lark", feishuOptions);
139
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastagent-sh/fastagent",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "description": "Vibe first. Then FastAgent: turn a local agent directory into a live service in your app, on GitHub, in Telegram, or behind any channel.",
5
5
  "keywords": [
6
6
  "agent",
@@ -9,6 +9,9 @@
9
9
  "agents.md",
10
10
  "agent-skills",
11
11
  "agent-serving",
12
+ "telegram",
13
+ "github",
14
+ "webhook",
12
15
  "sse",
13
16
  "pi",
14
17
  "fastagent"
@@ -51,6 +54,14 @@
51
54
  "types": "./dist/telegram.d.ts",
52
55
  "default": "./dist/telegram.js"
53
56
  },
57
+ "./feishu": {
58
+ "types": "./dist/feishu.d.ts",
59
+ "default": "./dist/feishu.js"
60
+ },
61
+ "./lark": {
62
+ "types": "./dist/lark.d.ts",
63
+ "default": "./dist/lark.js"
64
+ },
54
65
  "./package.json": "./package.json"
55
66
  },
56
67
  "bin": {
@@ -77,20 +88,23 @@
77
88
  },
78
89
  "dependencies": {
79
90
  "@clack/prompts": "^1.6.0",
80
- "@earendil-works/pi-agent-core": "^0.80.3",
81
- "@earendil-works/pi-ai": "^0.80.3",
82
- "@earendil-works/pi-coding-agent": "^0.80.3",
91
+ "@earendil-works/pi-agent-core": "^0.80.10",
92
+ "@earendil-works/pi-ai": "^0.80.10",
93
+ "@earendil-works/pi-coding-agent": "^0.80.10",
83
94
  "@octokit/webhooks-methods": "^6.0.0",
84
95
  "@octokit/webhooks-types": "^7.6.1",
85
96
  "chokidar": "^5.0.0",
97
+ "commander": "^15.0.0",
86
98
  "croner": "^10.0.1",
87
99
  "giget": "^3.3.0",
88
100
  "ignore": "^7.0.5",
101
+ "proper-lockfile": "^4.1.2",
89
102
  "undici": "^8.6.0",
90
103
  "zod": "^4.4.3"
91
104
  },
92
105
  "devDependencies": {
93
106
  "@biomejs/biome": "^2.5.2",
107
+ "@types/proper-lockfile": "^4.1.4",
94
108
  "typescript": "^7.0.2",
95
109
  "vitest": "^4.1.9"
96
110
  }