@fastagent-sh/fastagent 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/agent.d.ts +20 -0
- package/dist/agent.js +9 -0
- package/dist/channels/context-buffer.d.ts +30 -0
- package/dist/channels/context-buffer.js +98 -0
- package/dist/channels/control.d.ts +28 -0
- package/dist/channels/control.js +214 -0
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +8 -11
- package/dist/channels/feishu/context-buffer.js +6 -67
- package/dist/channels/feishu/feishu-api.d.ts +11 -3
- package/dist/channels/feishu/feishu-api.js +20 -2
- package/dist/channels/feishu/feishu.d.ts +28 -16
- package/dist/channels/feishu/feishu.js +210 -188
- package/dist/channels/feishu/invoke-turn.d.ts +7 -20
- package/dist/channels/feishu/invoke-turn.js +10 -62
- package/dist/channels/feishu/model.d.ts +2 -1
- package/dist/channels/feishu/normalize.d.ts +2 -1
- package/dist/channels/feishu/owned-threads.d.ts +2 -6
- package/dist/channels/feishu/parse.d.ts +1 -1
- package/dist/channels/feishu/preview.d.ts +4 -7
- package/dist/channels/feishu/preview.js +24 -140
- package/dist/channels/feishu/register-app.d.ts +2 -1
- package/dist/channels/feishu/register-webhook.d.ts +2 -1
- package/dist/channels/feishu/setup-mode.d.ts +8 -0
- package/dist/channels/feishu/setup-mode.js +2 -0
- package/dist/channels/feishu/ws-ingress.d.ts +28 -0
- package/dist/channels/feishu/ws-ingress.js +136 -0
- package/dist/channels/github/github.js +8 -6
- package/dist/channels/http.d.ts +14 -0
- package/dist/channels/http.js +35 -2
- package/dist/channels/invoke-turn-kit.d.ts +65 -0
- package/dist/channels/invoke-turn-kit.js +87 -0
- package/dist/channels/lark/lark.d.ts +4 -2
- package/dist/channels/lark/lark.js +4 -1
- package/dist/channels/lark/onboard.d.ts +8 -4
- package/dist/channels/lark/onboard.js +8 -0
- package/dist/channels/preview-kit.d.ts +109 -0
- package/dist/channels/preview-kit.js +183 -0
- package/dist/channels/seen.d.ts +5 -0
- package/dist/channels/seen.js +35 -0
- package/dist/channels/slack/bot-auth.d.ts +15 -0
- package/dist/channels/slack/bot-auth.js +146 -0
- package/dist/channels/slack/config-api.d.ts +60 -0
- package/dist/channels/slack/config-api.js +149 -0
- package/dist/channels/slack/context-buffer.d.ts +24 -0
- package/dist/channels/slack/context-buffer.js +37 -0
- package/dist/channels/slack/invoke-turn.d.ts +19 -0
- package/dist/channels/slack/invoke-turn.js +63 -0
- package/dist/channels/slack/manifest.d.ts +49 -0
- package/dist/channels/slack/manifest.js +69 -0
- package/dist/channels/slack/model.d.ts +67 -0
- package/dist/channels/slack/model.js +2 -0
- package/dist/channels/slack/onboard.d.ts +41 -0
- package/dist/channels/slack/onboard.js +120 -0
- package/dist/channels/slack/onboarding-state.d.ts +31 -0
- package/dist/channels/slack/onboarding-state.js +69 -0
- package/dist/channels/slack/owned-threads.d.ts +6 -0
- package/dist/channels/slack/owned-threads.js +43 -0
- package/dist/channels/slack/parse.d.ts +23 -0
- package/dist/channels/slack/parse.js +81 -0
- package/dist/channels/slack/preview.d.ts +24 -0
- package/dist/channels/slack/preview.js +359 -0
- package/dist/channels/slack/reaction.d.ts +24 -0
- package/dist/channels/slack/reaction.js +62 -0
- package/dist/channels/slack/register-webhook.d.ts +10 -0
- package/dist/channels/slack/register-webhook.js +49 -0
- package/dist/channels/slack/scaffold/channel.ts +33 -0
- package/dist/channels/slack/scaffold/slack-send.ts +171 -0
- package/dist/channels/slack/setup-server.d.ts +17 -0
- package/dist/channels/slack/setup-server.js +103 -0
- package/dist/channels/slack/slack-api.d.ts +77 -0
- package/dist/channels/slack/slack-api.js +415 -0
- package/dist/channels/slack/slack.d.ts +58 -0
- package/dist/channels/slack/slack.js +451 -0
- package/dist/channels/slack/welcomed.d.ts +5 -0
- package/dist/channels/slack/welcomed.js +32 -0
- package/dist/channels/state.js +3 -3
- package/dist/channels/stop-command.d.ts +6 -0
- package/dist/channels/stop-command.js +36 -0
- package/dist/channels/tasks.d.ts +13 -0
- package/dist/channels/tasks.js +10 -0
- package/dist/channels/telegram/context-buffer.d.ts +8 -17
- package/dist/channels/telegram/context-buffer.js +6 -85
- package/dist/channels/telegram/invoke-turn.d.ts +5 -22
- package/dist/channels/telegram/invoke-turn.js +11 -58
- package/dist/channels/telegram/preview.d.ts +4 -7
- package/dist/channels/telegram/preview.js +24 -142
- package/dist/channels/telegram/telegram.js +23 -9
- package/dist/channels/{feishu/text.d.ts → text.d.ts} +1 -1
- package/dist/channels/{feishu/text.js → text.js} +1 -1
- package/dist/channels/turn-queue.js +1 -1
- package/dist/channels/turn-store.d.ts +1 -1
- package/dist/channels/turn-store.js +2 -3
- package/dist/cli/add-feishu.d.ts +27 -0
- package/dist/{cli-add-feishu.js → cli/add-feishu.js} +167 -44
- package/dist/cli/add-slack.d.ts +10 -0
- package/dist/cli/add-slack.js +204 -0
- package/dist/{cli-auth.d.ts → cli/auth-view.d.ts} +1 -1
- package/dist/{cli-auth.js → cli/auth-view.js} +1 -1
- package/dist/cli/commands/add.d.ts +4 -0
- package/dist/cli/commands/add.js +152 -20
- package/dist/cli/commands/attach.d.ts +82 -0
- package/dist/cli/commands/attach.js +559 -0
- package/dist/cli/commands/chat.d.ts +1 -0
- package/dist/cli/commands/chat.js +6 -1
- package/dist/cli/commands/deploy.d.ts +2 -0
- package/dist/cli/commands/deploy.js +78 -22
- package/dist/cli/commands/dev.js +11 -5
- package/dist/cli/commands/fire.js +3 -3
- package/dist/cli/commands/info.js +2 -2
- package/dist/cli/commands/invoke.js +2 -3
- package/dist/cli/commands/login.js +14 -25
- package/dist/cli/commands/models.js +1 -1
- package/dist/cli/commands/start.js +15 -8
- package/dist/cli/commands/tool.js +2 -1
- package/dist/cli/fail.d.ts +0 -2
- package/dist/cli/fail.js +1 -1
- package/dist/{invoke-stream.d.ts → cli/invoke-stream.d.ts} +1 -1
- package/dist/{invoke-stream.js → cli/invoke-stream.js} +4 -0
- package/dist/cli/kernel.d.ts +3 -2
- package/dist/cli/models-view.d.ts +21 -0
- package/dist/cli/models-view.js +66 -0
- package/dist/cli/program.js +79 -21
- package/dist/cli/serve.d.ts +38 -18
- package/dist/cli/serve.js +197 -39
- package/dist/cli/shared.d.ts +26 -6
- package/dist/cli/shared.js +163 -33
- package/dist/collect.d.ts +14 -3
- package/dist/collect.js +24 -0
- package/dist/core.d.ts +3 -1
- package/dist/core.js +2 -0
- package/dist/deploy/docker/plan.d.ts +2 -2
- package/dist/deploy/docker/plan.js +8 -6
- package/dist/deploy/fly/plan.d.ts +3 -1
- package/dist/deploy/fly/plan.js +12 -7
- package/dist/deploy/fly/run.d.ts +5 -4
- package/dist/deploy/fly/run.js +16 -5
- package/dist/deploy/preflight.d.ts +10 -4
- package/dist/deploy/preflight.js +53 -9
- package/dist/deploy/railway/plan.d.ts +3 -1
- package/dist/deploy/railway/plan.js +12 -4
- package/dist/deploy/railway/run.d.ts +5 -4
- package/dist/deploy/railway/run.js +15 -5
- package/dist/deploy/runner.d.ts +2 -1
- package/dist/deploy/secrets.d.ts +2 -1
- package/dist/deploy/secrets.js +23 -3
- package/dist/dev-supervisor.d.ts +0 -2
- package/dist/dev-supervisor.js +7 -3
- package/dist/engines/pi/channel.d.ts +22 -16
- package/dist/engines/pi/channel.js +90 -60
- package/dist/engines/pi/chat.d.ts +4 -16
- package/dist/engines/pi/chat.js +8 -261
- package/dist/engines/pi/config.d.ts +14 -9
- package/dist/engines/pi/config.js +33 -18
- package/dist/engines/pi/create.d.ts +22 -2
- package/dist/engines/pi/create.js +20 -14
- package/dist/engines/pi/harness.d.ts +49 -0
- package/dist/engines/pi/harness.js +90 -2
- package/dist/engines/pi/invoke.d.ts +55 -2
- package/dist/engines/pi/invoke.js +304 -19
- package/dist/engines/pi/login.d.ts +11 -0
- package/dist/engines/pi/login.js +16 -4
- package/dist/engines/pi/models.d.ts +56 -10
- package/dist/engines/pi/models.js +61 -23
- package/dist/engines/pi/session-builder.d.ts +16 -0
- package/dist/engines/pi/session-builder.js +308 -0
- package/dist/engines/pi/session-control.d.ts +50 -0
- package/dist/engines/pi/session-control.js +604 -0
- package/dist/engines/pi/sessions.d.ts +17 -2
- package/dist/engines/pi/sessions.js +9 -0
- package/dist/engines/pi/tool-context.d.ts +18 -11
- package/dist/engines/pi/tool-context.js +3 -9
- package/dist/engines/pi/tool.d.ts +6 -8
- package/dist/engines/pi/tool.js +6 -1
- package/dist/engines/pi/wake-tool.d.ts +0 -3
- package/dist/engines/pi/wake-tool.js +9 -7
- package/dist/engines/pi/workspace.d.ts +52 -0
- package/dist/engines/pi/workspace.js +72 -15
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/host/node.d.ts +23 -6
- package/dist/host/node.js +5 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/observe.js +3 -0
- package/dist/pi.d.ts +5 -4
- package/dist/pi.js +2 -1
- package/dist/scaffold/add-channel.d.ts +19 -6
- package/dist/scaffold/add-channel.js +126 -17
- package/dist/scaffold/templates/fastagent.config.mjs +4 -3
- package/dist/schedule/wakeups.d.ts +0 -3
- package/dist/schedule/wakeups.js +1 -1
- package/dist/session-remote.d.ts +53 -0
- package/dist/session-remote.js +336 -0
- package/dist/session.d.ts +265 -0
- package/dist/session.js +37 -0
- package/dist/slack.d.ts +2 -0
- package/dist/slack.js +2 -0
- package/dist/tunnel.d.ts +7 -4
- package/dist/tunnel.js +21 -10
- package/package.json +19 -7
- package/dist/channels/feishu/seen.d.ts +0 -5
- package/dist/channels/feishu/seen.js +0 -47
- package/dist/cli-add-feishu.d.ts +0 -8
- package/dist/cli-models.d.ts +0 -11
- package/dist/cli-models.js +0 -20
|
@@ -43,11 +43,39 @@ const CHANNEL_SCAFFOLDS = {
|
|
|
43
43
|
"the agent can send messages or files back by calling the scaffolded {tools}/telegram-send.ts tool",
|
|
44
44
|
],
|
|
45
45
|
},
|
|
46
|
+
slack: {
|
|
47
|
+
env: [
|
|
48
|
+
{ name: "SLACK_BOT_TOKEN", hint: "Slack app → rotating Bot User OAuth access token", required: true },
|
|
49
|
+
{
|
|
50
|
+
name: "SLACK_BOT_REFRESH_TOKEN",
|
|
51
|
+
hint: "Slack OAuth bot refresh token (required when token rotation is enabled)",
|
|
52
|
+
required: false,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "SLACK_BOT_TOKEN_EXPIRES_AT",
|
|
56
|
+
hint: "Slack rotating bot access-token expiry (epoch milliseconds)",
|
|
57
|
+
required: false,
|
|
58
|
+
},
|
|
59
|
+
{ name: "SLACK_CLIENT_ID", hint: "Slack app OAuth client ID (for bot-token rotation)", required: false },
|
|
60
|
+
{
|
|
61
|
+
name: "SLACK_CLIENT_SECRET",
|
|
62
|
+
hint: "Slack app OAuth client secret (for bot-token rotation)",
|
|
63
|
+
required: false,
|
|
64
|
+
},
|
|
65
|
+
{ name: "SLACK_SIGNING_SECRET", hint: "Slack app → Basic Information → App Credentials", required: true },
|
|
66
|
+
],
|
|
67
|
+
steps: [
|
|
68
|
+
"Slack Bot Token Scopes: app_mentions:read, assistant:write, chat:write, im:history, files:read, files:write, channels:history, groups:history, mpim:history",
|
|
69
|
+
"enable Agents (agent_view) and token rotation; subscribe app_home_opened, app_context_changed, app_mention, message.im, message.channels, message.groups, message.mpim; set Request URL to <public-url>/slack",
|
|
70
|
+
"reinstall the app after changing scopes, then invite it to each channel it should read",
|
|
71
|
+
"the agent can send messages or files by calling the scaffolded {tools}/slack-send.ts tool",
|
|
72
|
+
],
|
|
73
|
+
},
|
|
46
74
|
// Feishu is the canonical engine/cloud; Lark international reuses its protocol through a degraded
|
|
47
75
|
// compatibility profile. Each remains its own channel KIND: route, env, state, console, onboarding.
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
76
|
+
// This table is the webhook setup; continuous mode below selects only App ID/Secret. No `generate`
|
|
77
|
+
// in either: values come FROM the platform. `add feishu` scan-creates the app; Lark lacks that
|
|
78
|
+
// control-plane capability, so `add lark` guides console credential collection.
|
|
51
79
|
feishu: {
|
|
52
80
|
env: [
|
|
53
81
|
{
|
|
@@ -72,8 +100,8 @@ const CHANNEL_SCAFFOLDS = {
|
|
|
72
100
|
},
|
|
73
101
|
],
|
|
74
102
|
steps: [
|
|
75
|
-
"
|
|
76
|
-
"PUBLISH the app version
|
|
103
|
+
"before publishing: approve the sensitive im:message.group_msg permission for context-aware groups (the CLI adds it to the app draft when supported); it delivers all group messages so bare managed-thread replies can invoke and other unsummoned discussion can buffer",
|
|
104
|
+
"PUBLISH the app version in the developer console after permission approval — the switch to webhook mode takes effect on publish (one click, once ever; no API for it)",
|
|
77
105
|
"edit {channel} — routing policy (the header walks through the console setup, for hand-made apps)",
|
|
78
106
|
"the event Request URL is auto-registered by `dev --tunnel` / `deploy --run`",
|
|
79
107
|
"the agent can push messages from scheduled turns via the scaffolded {tools}/feishu-send.ts tool",
|
|
@@ -96,7 +124,7 @@ const CHANNEL_SCAFFOLDS = {
|
|
|
96
124
|
],
|
|
97
125
|
steps: [
|
|
98
126
|
"finish the console setup: enable Bot and add the required permissions + im.message.receive_v1 event listed in {channel} (do not publish yet)",
|
|
99
|
-
"
|
|
127
|
+
"before publishing: approve the sensitive im:message.group_msg permission for context-aware groups (add it manually if Lark's config API fallback was used); it delivers all group messages so bare managed-thread replies can invoke and other unsummoned discussion can buffer",
|
|
100
128
|
"run `fastagent dev --tunnel` and keep it running; if auto-registration reports a config-API 404, manually switch Subscription mode to webhook, set its printed https://…/lark Request URL, save, then create + publish a version",
|
|
101
129
|
"the agent can push messages from scheduled turns via the scaffolded {tools}/lark-send.ts tool",
|
|
102
130
|
],
|
|
@@ -104,17 +132,61 @@ const CHANNEL_SCAFFOLDS = {
|
|
|
104
132
|
};
|
|
105
133
|
/** The channel kinds `fastagent add <kind>` can scaffold. */
|
|
106
134
|
export const CHANNEL_KINDS = Object.keys(CHANNEL_SCAFFOLDS);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
135
|
+
const WEBSOCKET_SETUPS = {
|
|
136
|
+
feishu: {
|
|
137
|
+
env: CHANNEL_SCAFFOLDS.feishu.env.filter((entry) => ["FEISHU_APP_ID", "FEISHU_APP_SECRET"].includes(entry.name)),
|
|
138
|
+
steps: [
|
|
139
|
+
"before publishing: approve the sensitive im:message.group_msg permission for context-aware groups (the CLI adds it to the app draft when supported); it delivers all group messages so bare managed-thread replies can invoke and other unsummoned discussion can buffer",
|
|
140
|
+
"PUBLISH the app version in the developer console after permission approval — long-connection event subscriptions become active with the published version",
|
|
141
|
+
"edit {channel} — routing policy (the scaffold is already set to WebSocket ingress)",
|
|
142
|
+
"run `fastagent dev` without --tunnel; deployments must keep one process running (no scale-to-zero)",
|
|
143
|
+
"the agent can push messages from scheduled turns via the scaffolded {tools}/feishu-send.ts tool",
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
lark: {
|
|
147
|
+
env: CHANNEL_SCAFFOLDS.lark.env.filter((entry) => ["LARK_APP_ID", "LARK_APP_SECRET"].includes(entry.name)),
|
|
148
|
+
steps: [
|
|
149
|
+
"before publishing: approve the sensitive im:message.group_msg permission for context-aware groups (add it manually if Lark's config API fallback was used); it delivers all group messages so bare managed-thread replies can invoke and other unsummoned discussion can buffer",
|
|
150
|
+
"in Events & Callbacks choose long connection, subscribe im.message.receive_v1, then create + publish a version",
|
|
151
|
+
"edit {channel} — routing policy (the scaffold is already set to WebSocket ingress)",
|
|
152
|
+
"run `fastagent dev` without --tunnel; deployments must keep one process running (no scale-to-zero)",
|
|
153
|
+
"the agent can push messages from scheduled turns via the scaffolded {tools}/lark-send.ts tool",
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
/** The mode-specific env vars + next-step lines a scaffolded channel needs. */
|
|
158
|
+
export function channelSetup(kind, ingress = "webhook", groupBehavior) {
|
|
159
|
+
const behavior = groupBehavior ?? "context";
|
|
160
|
+
const setup = ingress === "websocket" && (kind === "feishu" || kind === "lark")
|
|
161
|
+
? WEBSOCKET_SETUPS[kind]
|
|
162
|
+
: CHANNEL_SCAFFOLDS[kind];
|
|
163
|
+
if ((kind === "feishu" || kind === "lark") && behavior === "mentions") {
|
|
164
|
+
return {
|
|
165
|
+
env: setup.env,
|
|
166
|
+
steps: setup.steps.map((step) => step.includes("im:message.group_msg")
|
|
167
|
+
? "group behavior: mention-only — do not grant im:message.group_msg; bare managed-thread replies and group context buffering remain disabled"
|
|
168
|
+
: step),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (kind === "slack" && behavior === "mentions") {
|
|
172
|
+
return {
|
|
173
|
+
env: setup.env,
|
|
174
|
+
steps: [
|
|
175
|
+
"Slack Bot Token Scopes: app_mentions:read, assistant:write, chat:write, im:history, files:read, files:write (no channel/group/mpim history scopes)",
|
|
176
|
+
"enable Agents (agent_view) and token rotation; subscribe app_home_opened, app_context_changed, app_mention, and message.im; set Request URL to <public-url>/slack",
|
|
177
|
+
"group behavior: mention-only — bare managed-thread replies and unsummoned group context remain disabled",
|
|
178
|
+
...setup.steps.slice(2),
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return { env: setup.env, steps: setup.steps };
|
|
111
183
|
}
|
|
112
184
|
/**
|
|
113
185
|
* Append a channel's env vars (commented placeholders + hints) to `.env.example`, so a developer who
|
|
114
186
|
* copies it to `.env` finds the vars already there. No-op when there is no `.env.example` or the block
|
|
115
187
|
* is already present. Placeholders only — no real secret lands in the committable template.
|
|
116
188
|
*/
|
|
117
|
-
export async function appendChannelEnv(dir, kind) {
|
|
189
|
+
export async function appendChannelEnv(dir, kind, ingress = "webhook") {
|
|
118
190
|
const file = join(dir, ".env.example");
|
|
119
191
|
let current;
|
|
120
192
|
try {
|
|
@@ -131,7 +203,9 @@ export async function appendChannelEnv(dir, kind) {
|
|
|
131
203
|
// Hint on its OWN line above the placeholder (like the base env.example template) — never inline
|
|
132
204
|
// after `=`: loadEnvFile does not strip trailing comments, so an uncommented `KEY= # hint` (or a
|
|
133
205
|
// value pasted before the `#`) would carry the hint text into the parsed value.
|
|
134
|
-
const block = `\n${marker}\n${
|
|
206
|
+
const block = `\n${marker}\n${channelSetup(kind, ingress)
|
|
207
|
+
.env.map((e) => `# ${e.hint}\n# ${e.name}=`)
|
|
208
|
+
.join("\n")}\n`;
|
|
135
209
|
await appendFile(file, block);
|
|
136
210
|
return true;
|
|
137
211
|
}
|
|
@@ -153,7 +227,7 @@ function mentionsEnvName(content, name) {
|
|
|
153
227
|
* (e.g. TELEGRAM_BOT_TOKEN from BotFather) are added only as commented placeholders, so the file is
|
|
154
228
|
* ready to edit while no fake secret is committed to the user's mental model.
|
|
155
229
|
*/
|
|
156
|
-
export async function appendChannelDotEnv(dir, kind, generated, overwrite = []) {
|
|
230
|
+
export async function appendChannelDotEnv(dir, kind, generated, overwrite = [], ingress = "webhook") {
|
|
157
231
|
const file = join(dir, ".env");
|
|
158
232
|
let current = "";
|
|
159
233
|
try {
|
|
@@ -163,14 +237,15 @@ export async function appendChannelDotEnv(dir, kind, generated, overwrite = [])
|
|
|
163
237
|
if (e.code !== "ENOENT")
|
|
164
238
|
throw e;
|
|
165
239
|
}
|
|
166
|
-
const
|
|
240
|
+
const env = channelSetup(kind, ingress).env;
|
|
241
|
+
const alreadySet = env
|
|
167
242
|
.filter((e) => !overwrite.includes(e.name) && hasActiveEnvValue(current, e.name))
|
|
168
243
|
.map((e) => e.name);
|
|
169
244
|
const lines = [];
|
|
170
245
|
const written = [];
|
|
171
246
|
const contentLines = current.split("\n");
|
|
172
247
|
let replacedInPlace = false;
|
|
173
|
-
for (const e of
|
|
248
|
+
for (const e of env) {
|
|
174
249
|
if (alreadySet.includes(e.name))
|
|
175
250
|
continue;
|
|
176
251
|
const value = generated[e.name];
|
|
@@ -230,7 +305,7 @@ export async function channelExists(dir, kind) {
|
|
|
230
305
|
* Scaffold `channels/<kind>.ts` into {@link dir}. Never clobbers an existing file (the glue is
|
|
231
306
|
* authored content). The wx write is the TOCTOU safety net behind {@link channelExists}.
|
|
232
307
|
*/
|
|
233
|
-
export async function scaffoldChannel(dir, kind) {
|
|
308
|
+
export async function scaffoldChannel(dir, kind, options = {}) {
|
|
234
309
|
const channelsDir = join(dir, "channels");
|
|
235
310
|
// Don't write through a channels/ symlink that escapes the workspace; an in-workspace one is fine.
|
|
236
311
|
await assertInsideWorkspace(dir, "channels");
|
|
@@ -242,8 +317,42 @@ export async function scaffoldChannel(dir, kind) {
|
|
|
242
317
|
// `channel.ts` is THE adapter (→ channels/<kind>.ts); any other .ts in the bundle is a companion tool
|
|
243
318
|
// (→ tools/<name>, never clobbering an authored one).
|
|
244
319
|
for (const name of channelBundleFiles(kind)) {
|
|
245
|
-
|
|
320
|
+
let content = channelTemplate(kind, name);
|
|
246
321
|
if (name === "channel.ts") {
|
|
322
|
+
if ((kind === "feishu" || kind === "lark") && options.ingress === "websocket") {
|
|
323
|
+
const factory = `${kind}Channel`;
|
|
324
|
+
const wsFactory = `${kind}WebSocketChannel`;
|
|
325
|
+
let configured = content
|
|
326
|
+
.replace(`import { ${factory} }`, `import { ${wsFactory} }`)
|
|
327
|
+
.replace(`export default ${factory}({`, `export default ${wsFactory}({`);
|
|
328
|
+
if (configured === content)
|
|
329
|
+
throw new Error(`${kind} channel template has no factory anchors`);
|
|
330
|
+
const prefix = kind === "feishu" ? "FEISHU" : "LARK";
|
|
331
|
+
const exportAt = configured.indexOf("export default");
|
|
332
|
+
const importEnd = configured.indexOf("\n\n");
|
|
333
|
+
if (exportAt < 0 || importEnd < 0)
|
|
334
|
+
throw new Error(`${kind} channel template header anchors are missing`);
|
|
335
|
+
const brand = kind === "feishu" ? "Feishu" : "Lark";
|
|
336
|
+
configured =
|
|
337
|
+
`${configured.slice(0, importEnd)}\n\n` +
|
|
338
|
+
`// ${brand} WebSocket long connection: the process connects OUT to the platform, so no public URL,\n` +
|
|
339
|
+
`// Verification Token, Encrypt Key, or --tunnel is needed. In Events & Callbacks choose long\n` +
|
|
340
|
+
`// connection, subscribe im.message.receive_v1, then publish the app version. Keep one process\n` +
|
|
341
|
+
`// running in production: scale-to-zero/App Sleeping would disconnect ingress.\n` +
|
|
342
|
+
configured.slice(exportAt);
|
|
343
|
+
configured = configured
|
|
344
|
+
.split("\n")
|
|
345
|
+
.filter((line) => !line.includes(`verificationToken: process.env.${prefix}_VERIFICATION_TOKEN`) &&
|
|
346
|
+
!line.includes(`encryptKey: process.env.${prefix}_ENCRYPT_KEY`))
|
|
347
|
+
.join("\n");
|
|
348
|
+
content = configured;
|
|
349
|
+
}
|
|
350
|
+
if (kind === "slack" && options.groupBehavior === "mentions") {
|
|
351
|
+
const configured = content.replace('groupBehavior: "context"', 'groupBehavior: "mentions"');
|
|
352
|
+
if (configured === content)
|
|
353
|
+
throw new Error("slack channel template has no groupBehavior anchor");
|
|
354
|
+
content = configured;
|
|
355
|
+
}
|
|
247
356
|
await writeFile(file, content, { flag: "wx" });
|
|
248
357
|
continue;
|
|
249
358
|
}
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
// Your agent's identity lives in persona.md; its capabilities in skills/ + tools/ — never here.
|
|
3
3
|
// An AGENTS.md at the workspace root (yours or the host repo's) is read as project context.
|
|
4
4
|
// Model precedence: `--model` flag > FASTAGENT_MODEL env > this default.
|
|
5
|
-
// No model is preset: `fastagent dev`
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// No model is preset: `fastagent dev` shows the full model catalog (models you already have
|
|
6
|
+
// credentials for come first; picking one that needs auth logs you in inline) and writes your choice
|
|
7
|
+
// below. Or set it by hand to a "provider/modelId" (`fastagent models` lists them).
|
|
8
8
|
export default {
|
|
9
9
|
// model: "openai-codex/gpt-5.5",
|
|
10
10
|
// thinkingLevel: "high", // reasoning effort (off|minimal|low|medium|high|xhigh|max); default "medium" (pi TUI parity)
|
|
11
11
|
http: { port: 8787 },
|
|
12
12
|
// selfSchedule: true, // mount the built-in `wake` tool: the agent schedules its own follow-up turns
|
|
13
13
|
// // ("check the deploy in 10 min"). Cron jobs need no opt-in — drop a schedules/<name>.ts.
|
|
14
|
+
// sessionControl: true, // serve /control/* for remote observation + steering (fastagent attach / Web panel)
|
|
14
15
|
// deploy: what the agent needs on the box (so `fastagent deploy` doesn't need a hand-written Dockerfile
|
|
15
16
|
// or hand-set host variables). Uncomment as needed:
|
|
16
17
|
// deploy: {
|
|
@@ -26,9 +26,6 @@ export declare const MAX_PENDING_WAKEUPS = 20;
|
|
|
26
26
|
* wake into an active conversation fires in the first gap between the user's turns; this generous
|
|
27
27
|
* ceiling (~1h) only gives up on a pathologically stuck session (then logs, operator-visible). */
|
|
28
28
|
export declare const MAX_WAKE_ATTEMPTS = 120;
|
|
29
|
-
/** The minimum gap between two consecutive fires of a RECURRING wake — stricter than the one-shot floor:
|
|
30
|
-
* a recurring runs forever, so a tight cron is a permanent token burner, not a one-time mistake. */
|
|
31
|
-
export declare const MIN_RECURRING_GAP_MS: number;
|
|
32
29
|
/** The current pending wake-ups (`fastagent schedule list` uses this). */
|
|
33
30
|
export declare function listWakeups(stateRoot: string): Wakeup[];
|
|
34
31
|
export type AddWakeupResult = {
|
package/dist/schedule/wakeups.js
CHANGED
|
@@ -26,7 +26,7 @@ export const MAX_PENDING_WAKEUPS = 20;
|
|
|
26
26
|
export const MAX_WAKE_ATTEMPTS = 120;
|
|
27
27
|
/** The minimum gap between two consecutive fires of a RECURRING wake — stricter than the one-shot floor:
|
|
28
28
|
* a recurring runs forever, so a tight cron is a permanent token burner, not a one-time mistake. */
|
|
29
|
-
|
|
29
|
+
const MIN_RECURRING_GAP_MS = 10 * 60_000; // 10 minutes
|
|
30
30
|
/** A stored entry is a real Wakeup: the fields are present and `fireAt` is a parseable date. A malformed
|
|
31
31
|
* one (bad/missing fireAt) would compare NaN <= now = false forever — never due, never cleared, but still
|
|
32
32
|
* eating the pending quota. So validate at this IO boundary and drop it (warn), like a corrupt file. */
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The remote `SessionControl` — the client half of the Phase 3 transport (design §13). Engine- and
|
|
3
|
+
* server-neutral: speaks only the wire protocol `controlRoutes` serves (HTTP JSON + SSE with the
|
|
4
|
+
* {sessionId, epoch, seq, event} envelope) and re-exposes the SAME `SessionControl` interface, so
|
|
5
|
+
* local and remote consumers are isomorphic — client code does not change when the agent moves out
|
|
6
|
+
* of process.
|
|
7
|
+
*
|
|
8
|
+
* Envelope consumption is internal: a seq gap (loss in transit on this connection) — and any
|
|
9
|
+
* mid-stream transport failure, a server restart included (its connections drop) — THROWS from
|
|
10
|
+
* the events iterator, so the consumer's failure handling and budget own it; only the consumer's
|
|
11
|
+
* own detach reads as a clean end. Recovery is the standard reconnect steps (`entries({ since })`
|
|
12
|
+
* → `state()` → resubscribe), exactly as after any disconnect. The envelope's `epoch` is
|
|
13
|
+
* informational for consumers that correlate ACROSS connections — within one connection it cannot
|
|
14
|
+
* change, so this client does not compare it. Nothing here retries silently: a broken stream is
|
|
15
|
+
* visible as a thrown iteration error, a failed request as a rejected promise.
|
|
16
|
+
*/
|
|
17
|
+
import type { Agent } from "./agent.ts";
|
|
18
|
+
import type { SessionControl } from "./session.ts";
|
|
19
|
+
/** A control request the server answered with a non-2xx status. Carries the STRUCTURED status so a
|
|
20
|
+
* consumer distinguishing auth failure (401 — stale token, unrecoverable) from transient transport
|
|
21
|
+
* trouble branches on `status`, never on message prose. */
|
|
22
|
+
export declare class ControlRequestError extends Error {
|
|
23
|
+
readonly status: number;
|
|
24
|
+
constructor(status: number, body: string);
|
|
25
|
+
}
|
|
26
|
+
/** Connection parameters shared by BOTH remote planes (`connectSessionControl` and
|
|
27
|
+
* `connectAgent`) — plane-neutral on purpose: one endpoint, one token, two contracts. */
|
|
28
|
+
export interface RemoteEndpointOptions {
|
|
29
|
+
/** Base URL of the serving process (e.g. `http://127.0.0.1:8787`); `/control/*` is appended. */
|
|
30
|
+
url: string;
|
|
31
|
+
/** The shared bearer secret (`<stateRoot>/control.json` on the serving machine). */
|
|
32
|
+
token: string;
|
|
33
|
+
/** Injectable for tests. Defaults to global fetch. */
|
|
34
|
+
fetchFn?: typeof fetch;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Connect and return a remote `SessionControl`. Async because `capabilities()` is synchronous in
|
|
38
|
+
* the contract: the static declaration is fetched ONCE here and served from memory — which also
|
|
39
|
+
* makes a wrong URL/token fail at connect time, not on first use.
|
|
40
|
+
*/
|
|
41
|
+
export declare function connectSessionControl(options: RemoteEndpointOptions): Promise<SessionControl>;
|
|
42
|
+
/**
|
|
43
|
+
* The remote DATA plane: an `Agent` whose `invoke` drives `POST /control/invoke` on a serving
|
|
44
|
+
* process — paired with {@link connectSessionControl}, a client holds a full remote fastagent
|
|
45
|
+
* instance through the same two contracts local code uses. A REAL Agent, failure discipline
|
|
46
|
+
* included: SPEC MUST 2 forbids iteration throws, so every failure — transport (401/refused/
|
|
47
|
+
* dropped mid-stream), protocol, and the images precheck — becomes a terminal `failed` event
|
|
48
|
+
* (`retryable` from the HTTP status where one exists; network trouble is retryable). Breaking out
|
|
49
|
+
* of iteration disconnects the request, which cancels the run (SPEC cancellation semantics travel
|
|
50
|
+
* the wire). The invoke wire is text-only for now: a prompt with images fails visibly instead of
|
|
51
|
+
* silently dropping them (steer/follow_up on the control plane carry full Prompts).
|
|
52
|
+
*/
|
|
53
|
+
export declare function connectAgent(options: RemoteEndpointOptions): Agent;
|