@fastagent-sh/fastagent 0.29.0 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/atomic-write.d.ts +4 -5
- package/dist/atomic-write.js +4 -5
- package/dist/authoring.d.ts +32 -15
- package/dist/authoring.js +147 -127
- package/dist/channels/agentcore-protocol.d.ts +8 -3
- package/dist/channels/agentcore-protocol.js +3 -1
- package/dist/channels/agentcore-service.d.ts +1 -0
- package/dist/channels/agentcore-service.js +14 -4
- package/dist/channels/agentcore.d.ts +9 -0
- package/dist/channels/agentcore.js +29 -4
- package/dist/channels/context.d.ts +2 -0
- package/dist/channels/context.js +5 -1
- package/dist/channels/feishu/feishu-api.d.ts +2 -1
- package/dist/channels/feishu/feishu.d.ts +0 -3
- package/dist/channels/feishu/feishu.js +10 -10
- package/dist/channels/feishu/register-app.js +3 -5
- package/dist/channels/feishu/scaffold/channel.ts +32 -34
- package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
- package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
- package/dist/channels/feishu/setup-mode.d.ts +2 -12
- package/dist/channels/feishu/setup-mode.js +2 -22
- package/dist/channels/feishu/setup.d.ts +12 -0
- package/dist/channels/feishu/setup.js +197 -0
- package/dist/channels/kit/abortable.d.ts +8 -0
- package/dist/channels/kit/abortable.js +22 -0
- package/dist/channels/lark/lark.d.ts +0 -3
- package/dist/channels/lark/lark.js +0 -5
- package/dist/channels/lark/scaffold/channel.ts +33 -35
- package/dist/channels/lark/scaffold/lark-send.ts +5 -5
- package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
- package/dist/channels/lark/setup.d.ts +9 -0
- package/dist/channels/lark/setup.js +57 -0
- package/dist/channels/setup.d.ts +112 -0
- package/dist/channels/setup.js +31 -0
- package/dist/channels/slack/history.js +1 -1
- package/dist/channels/slack/manifest.d.ts +17 -7
- package/dist/channels/slack/manifest.js +12 -16
- package/dist/channels/slack/scaffold/channel.ts +44 -30
- package/dist/channels/slack/scaffold/slack-send.ts +8 -8
- package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
- package/dist/channels/slack/setup.d.ts +7 -0
- package/dist/channels/slack/setup.js +119 -0
- package/dist/channels/slack/slack-api.d.ts +2 -0
- package/dist/channels/slack/slack-api.js +4 -0
- package/dist/channels/slack/slack.d.ts +21 -2
- package/dist/channels/slack/slack.js +398 -338
- package/dist/channels/slack/socket.d.ts +57 -0
- package/dist/channels/slack/socket.js +305 -0
- package/dist/channels/telegram/polling.d.ts +27 -0
- package/dist/channels/telegram/polling.js +105 -0
- package/dist/channels/telegram/scaffold/channel.ts +52 -32
- package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
- package/dist/channels/telegram/setup.d.ts +2 -0
- package/dist/channels/telegram/setup.js +42 -0
- package/dist/channels/telegram/telegram-api.d.ts +27 -1
- package/dist/channels/telegram/telegram-api.js +7 -6
- package/dist/channels/telegram/telegram.d.ts +11 -3
- package/dist/channels/telegram/telegram.js +184 -157
- package/dist/channels/webhook-evidence.d.ts +11 -0
- package/dist/channels/webhook-evidence.js +71 -0
- package/dist/channels/webhooks.d.ts +37 -0
- package/dist/channels/webhooks.js +70 -0
- package/dist/child-env.d.ts +16 -0
- package/dist/child-env.js +30 -0
- package/dist/cli/box-login.js +4 -1
- package/dist/cli/channel-app.d.ts +3 -0
- package/dist/cli/channel-app.js +35 -0
- package/dist/cli/channel-setup.d.ts +38 -0
- package/dist/cli/channel-setup.js +195 -0
- package/dist/cli/commands/add.d.ts +6 -14
- package/dist/cli/commands/add.js +45 -303
- package/dist/cli/commands/auth.js +8 -2
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +16 -0
- package/dist/cli/commands/channels.d.ts +19 -0
- package/dist/cli/commands/channels.js +230 -0
- package/dist/cli/commands/content.d.ts +7 -3
- package/dist/cli/commands/content.js +16 -10
- package/dist/cli/commands/deploy/agentcore.js +55 -23
- package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
- package/dist/cli/commands/deploy/channel-apps.js +30 -0
- package/dist/cli/commands/deploy/docker.js +32 -65
- package/dist/cli/commands/deploy/fly.js +28 -12
- package/dist/cli/commands/deploy/railway.js +38 -13
- package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
- package/dist/cli/commands/deploy/secrets-step.js +66 -0
- package/dist/cli/commands/deploy/shared.d.ts +16 -5
- package/dist/cli/commands/deploy/shared.js +25 -8
- package/dist/cli/commands/deploy.js +53 -60
- package/dist/cli/commands/dev.d.ts +2 -0
- package/dist/cli/commands/dev.js +15 -3
- package/dist/cli/commands/env.js +1 -1
- package/dist/cli/commands/info.d.ts +2 -0
- package/dist/cli/commands/info.js +60 -25
- package/dist/cli/commands/init.js +3 -1
- package/dist/cli/commands/invoke.d.ts +2 -0
- package/dist/cli/commands/invoke.js +20 -7
- package/dist/cli/commands/login.d.ts +0 -7
- package/dist/cli/commands/login.js +22 -10
- package/dist/cli/commands/logout.js +1 -1
- package/dist/cli/commands/mcp.js +14 -6
- package/dist/cli/commands/models.js +3 -2
- package/dist/cli/commands/schedules.d.ts +4 -2
- package/dist/cli/commands/schedules.js +14 -10
- package/dist/cli/commands/secrets.d.ts +14 -0
- package/dist/cli/commands/secrets.js +213 -0
- package/dist/cli/commands/start.d.ts +14 -14
- package/dist/cli/commands/start.js +46 -60
- package/dist/cli/content-view.d.ts +4 -6
- package/dist/cli/content-view.js +5 -11
- package/dist/cli/first-run-channels.d.ts +3 -0
- package/dist/cli/first-run-channels.js +63 -0
- package/dist/cli/instance.d.ts +41 -0
- package/dist/cli/instance.js +155 -0
- package/dist/cli/invoke-stream.d.ts +5 -2
- package/dist/cli/invoke-stream.js +7 -2
- package/dist/cli/kernel.js +3 -0
- package/dist/cli/program.js +282 -110
- package/dist/cli/serve.d.ts +4 -5
- package/dist/cli/serve.js +4 -7
- package/dist/cli/shared.d.ts +11 -2
- package/dist/cli/shared.js +32 -19
- package/dist/cli/ticket.d.ts +7 -0
- package/dist/cli/ticket.js +106 -30
- package/dist/content/declare.d.ts +31 -37
- package/dist/content/declare.js +59 -60
- package/dist/content/file.d.ts +23 -12
- package/dist/content/file.js +48 -35
- package/dist/content/git.d.ts +0 -10
- package/dist/content/git.js +5 -39
- package/dist/content/resolve.d.ts +18 -22
- package/dist/content/resolve.js +32 -66
- package/dist/content/source.d.ts +7 -8
- package/dist/content/source.js +10 -28
- package/dist/credentials-page.d.ts +26 -0
- package/dist/credentials-page.js +185 -0
- package/dist/declared-secrets.d.ts +10 -3
- package/dist/declared-secrets.js +8 -4
- package/dist/deploy/agentcore/destroy.d.ts +4 -1
- package/dist/deploy/agentcore/destroy.js +25 -1
- package/dist/deploy/agentcore/forwarder.js +1 -1
- package/dist/deploy/agentcore/plan.d.ts +7 -21
- package/dist/deploy/agentcore/plan.js +47 -49
- package/dist/deploy/agentcore/run.d.ts +18 -10
- package/dist/deploy/agentcore/run.js +93 -70
- package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
- package/dist/deploy/agentcore/runtime-secrets.js +108 -0
- package/dist/deploy/agentcore/shell.js +12 -19
- package/dist/deploy/agentcore/sigv4.d.ts +7 -0
- package/dist/deploy/agentcore/sigv4.js +15 -0
- package/dist/deploy/agentcore/slr.d.ts +18 -0
- package/dist/deploy/agentcore/slr.js +27 -0
- package/dist/deploy/agentcore/store.d.ts +7 -0
- package/dist/deploy/agentcore/store.js +115 -0
- package/dist/deploy/api.d.ts +14 -14
- package/dist/deploy/api.js +26 -24
- package/dist/deploy/box-shell.d.ts +3 -1
- package/dist/deploy/build-context.d.ts +4 -4
- package/dist/deploy/build-context.js +11 -15
- package/dist/deploy/channel-ingress.d.ts +35 -9
- package/dist/deploy/channel-ingress.js +119 -22
- package/dist/deploy/container.d.ts +35 -10
- package/dist/deploy/container.js +79 -37
- package/dist/deploy/delivered-text.d.ts +13 -0
- package/dist/deploy/delivered-text.js +86 -0
- package/dist/deploy/docker/plan.d.ts +7 -8
- package/dist/deploy/docker/plan.js +31 -52
- package/dist/deploy/docker/run.d.ts +5 -43
- package/dist/deploy/docker/run.js +9 -84
- package/dist/deploy/file-store.d.ts +2 -0
- package/dist/deploy/file-store.js +17 -0
- package/dist/deploy/fly/plan.d.ts +3 -2
- package/dist/deploy/fly/plan.js +20 -9
- package/dist/deploy/fly/run.d.ts +18 -8
- package/dist/deploy/fly/run.js +24 -42
- package/dist/deploy/fly/store.d.ts +12 -0
- package/dist/deploy/fly/store.js +83 -0
- package/dist/deploy/hosts.d.ts +1 -2
- package/dist/deploy/hosts.js +1 -2
- package/dist/deploy/preflight.d.ts +18 -5
- package/dist/deploy/preflight.js +135 -63
- package/dist/deploy/railway/ignore.d.ts +8 -0
- package/dist/deploy/railway/ignore.js +57 -0
- package/dist/deploy/railway/plan.d.ts +1 -2
- package/dist/deploy/railway/plan.js +8 -6
- package/dist/deploy/railway/run.d.ts +21 -15
- package/dist/deploy/railway/run.js +32 -78
- package/dist/deploy/railway/store.d.ts +29 -0
- package/dist/deploy/railway/store.js +143 -0
- package/dist/deploy/runner.d.ts +5 -2
- package/dist/deploy/runner.js +4 -3
- package/dist/deploy/secret-store.d.ts +63 -0
- package/dist/deploy/secret-store.js +98 -0
- package/dist/deploy/secrets.d.ts +29 -46
- package/dist/deploy/secrets.js +49 -122
- package/dist/deploy/workspace.d.ts +6 -12
- package/dist/deploy/workspace.js +18 -94
- package/dist/dev-supervisor.js +6 -8
- package/dist/environment/declare.js +3 -2
- package/dist/environment/mise.d.ts +3 -1
- package/dist/environment/mise.js +4 -2
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/harnesses/models/catalog.d.ts +1 -0
- package/dist/harnesses/models/catalog.js +1 -2
- package/dist/harnesses/models/environment.d.ts +7 -2
- package/dist/harnesses/models/environment.js +9 -5
- package/dist/harnesses/models/host.d.ts +0 -7
- package/dist/harnesses/models/host.js +10 -1
- package/dist/harnesses/models/login.d.ts +0 -8
- package/dist/harnesses/models/login.js +5 -4
- package/dist/harnesses/models/models.d.ts +7 -2
- package/dist/harnesses/models/models.js +28 -4
- package/dist/harnesses/pi-durable/agent.js +59 -38
- package/dist/harnesses/pi-durable/build.d.ts +12 -0
- package/dist/harnesses/pi-durable/build.js +270 -0
- package/dist/harnesses/pi-durable/built.d.ts +24 -0
- package/dist/harnesses/pi-durable/built.js +136 -0
- package/dist/harnesses/pi-durable/config.d.ts +81 -0
- package/dist/harnesses/pi-durable/config.js +266 -0
- package/dist/harnesses/pi-durable/define.d.ts +63 -13
- package/dist/harnesses/pi-durable/define.js +23 -2
- package/dist/harnesses/pi-durable/definition.d.ts +72 -0
- package/dist/harnesses/pi-durable/definition.js +143 -0
- package/dist/harnesses/pi-durable/events.d.ts +4 -3
- package/dist/harnesses/pi-durable/events.js +179 -136
- package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
- package/dist/harnesses/pi-durable/execution-env.js +11 -0
- package/dist/harnesses/pi-durable/mcp.js +1 -3
- package/dist/harnesses/pi-durable/open.d.ts +35 -37
- package/dist/harnesses/pi-durable/open.js +91 -153
- package/dist/harnesses/pi-durable/reads.d.ts +0 -1
- package/dist/harnesses/pi-durable/reads.js +1 -3
- package/dist/harnesses/pi-durable/registry.d.ts +4 -6
- package/dist/harnesses/pi-durable/registry.js +29 -26
- package/dist/harnesses/pi-durable/release.d.ts +2 -2
- package/dist/harnesses/pi-durable/release.js +10 -7
- package/dist/harnesses/pi-durable/serve.d.ts +91 -15
- package/dist/harnesses/pi-durable/serve.js +109 -47
- package/dist/harnesses/pi-durable/sessions.js +15 -12
- package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
- package/dist/harnesses/pi-durable/text-sources.js +55 -0
- package/dist/harnesses/pi-durable/text.d.ts +16 -4
- package/dist/harnesses/pi-durable/text.js +115 -59
- package/dist/harnesses/pi-durable/wake.js +8 -8
- package/dist/http/client.js +9 -3
- package/dist/http/contract.d.ts +3 -3
- package/dist/http/server.d.ts +1 -1
- package/dist/http/server.js +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +4 -1
- package/dist/instance-name.d.ts +7 -0
- package/dist/instance-name.js +26 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/mcp/client.js +11 -2
- package/dist/mcp/config.d.ts +70 -18
- package/dist/mcp/config.js +136 -53
- package/dist/mcp/host.d.ts +5 -2
- package/dist/mcp/host.js +14 -6
- package/dist/mcp/sign-in.js +2 -2
- package/dist/mcp-api.d.ts +14 -0
- package/dist/paths.d.ts +22 -8
- package/dist/paths.js +44 -11
- package/dist/runtime-websocket.d.ts +14 -0
- package/dist/runtime-websocket.js +39 -0
- package/dist/scaffold/add-channel.d.ts +9 -16
- package/dist/scaffold/add-channel.js +13 -83
- package/dist/scaffold/templates/env.example +4 -5
- package/dist/scaffold/templates/fastagent.config.ts +20 -14
- package/dist/scaffold/templates/gitignore +9 -6
- package/dist/scaffold/templates.d.ts +6 -2
- package/dist/scaffold/templates.js +7 -2
- package/dist/scaffold/vendor-skill.js +4 -2
- package/dist/service.d.ts +44 -6
- package/dist/service.js +110 -10
- package/dist/serving-command.d.ts +10 -0
- package/dist/serving-command.js +1 -0
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +1 -1
- package/dist/strip-edges.d.ts +10 -0
- package/dist/strip-edges.js +22 -0
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +1 -1
- package/dist/tunnel.d.ts +0 -1
- package/dist/tunnel.js +2 -8
- package/dist/value-file.d.ts +11 -6
- package/dist/value-file.js +21 -9
- package/dist/version.d.ts +7 -1
- package/dist/version.js +9 -3
- package/package.json +6 -5
- package/dist/channels/lark/credentials-page.d.ts +0 -25
- package/dist/channels/lark/credentials-page.js +0 -206
- package/dist/channels/lark/onboard.d.ts +0 -52
- package/dist/channels/lark/onboard.js +0 -86
- package/dist/channels/slack/config-api.d.ts +0 -69
- package/dist/channels/slack/config-api.js +0 -162
- package/dist/channels/slack/onboard.d.ts +0 -40
- package/dist/channels/slack/onboard.js +0 -136
- package/dist/channels/slack/onboarding-state.d.ts +0 -31
- package/dist/channels/slack/onboarding-state.js +0 -64
- package/dist/channels/slack/register-webhook.d.ts +0 -12
- package/dist/channels/slack/register-webhook.js +0 -67
- package/dist/channels/slack/setup-server.d.ts +0 -32
- package/dist/channels/slack/setup-server.js +0 -246
- package/dist/cli/add-feishu.d.ts +0 -41
- package/dist/cli/add-feishu.js +0 -391
- package/dist/cli/add-slack.d.ts +0 -18
- package/dist/cli/add-slack.js +0 -216
- package/dist/harnesses/pi-durable/parked.d.ts +0 -18
- package/dist/harnesses/pi-durable/parked.js +0 -27
package/dist/dev-supervisor.js
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { relative, sep } from "node:path";
|
|
7
7
|
import { watch as watchTree } from "chokidar";
|
|
8
|
-
import { AGENT_CONFIG_FILE, CONTEXT_FILE,
|
|
8
|
+
import { AGENT_CONFIG_FILE, CONTEXT_FILE, INSTANCE_CONFIG_FILE, isUnderDir } from "./paths.js";
|
|
9
9
|
import { dotEnvPath } from "./value-file.js";
|
|
10
|
-
import { MCP_FILE } from "./mcp/config.js";
|
|
11
10
|
import { log } from "./log.js";
|
|
12
11
|
import { openExternalUrl } from "./open-url.js";
|
|
13
12
|
import { declaredChannels } from "./channels/discover.js";
|
|
@@ -15,7 +14,7 @@ import { activeWork } from "./channels/busy.js";
|
|
|
15
14
|
import { announceWebhooks, startCloudflareTunnel } from "./tunnel.js";
|
|
16
15
|
/** The agent's code (its tools, extensions, channels and schedules), loaded ONCE per worker: a restart is its only re-read. */
|
|
17
16
|
const CODE_DIR = "src";
|
|
18
|
-
const WATCHED_HINT = `${CODE_DIR}/, package.json, fastagent.config.ts, context.json,
|
|
17
|
+
const WATCHED_HINT = `${CODE_DIR}/, package.json, fastagent.config.ts, context.json, fastagent.json, .secrets/.env`;
|
|
19
18
|
/** chokidar `ignored` matcher for the narrow watch scope (true = ignore), rooted at the AGENT DIR. */
|
|
20
19
|
export function devWatchIgnored(root, envFile) {
|
|
21
20
|
// The `.env` is allow-listed by its RESOLVED path, not by the `.secrets` name.
|
|
@@ -26,9 +25,9 @@ export function devWatchIgnored(root, envFile) {
|
|
|
26
25
|
const rel = relative(root, path);
|
|
27
26
|
// Code inputs at the agent dir root: config, package.json, and the code loaded once per worker (a restart is
|
|
28
27
|
// their only re-read).
|
|
29
|
-
if (rel === AGENT_CONFIG_FILE || rel === CONTEXT_FILE
|
|
28
|
+
if (rel === AGENT_CONFIG_FILE || rel === CONTEXT_FILE)
|
|
30
29
|
return false;
|
|
31
|
-
if (rel === "package.json" || rel ===
|
|
30
|
+
if (rel === "package.json" || rel === INSTANCE_CONFIG_FILE)
|
|
32
31
|
return false;
|
|
33
32
|
const segments = rel.split(sep);
|
|
34
33
|
if (segments[0] === CODE_DIR)
|
|
@@ -96,7 +95,6 @@ export async function runDevSupervisor(agentDir, options = {}) {
|
|
|
96
95
|
tunnel = t;
|
|
97
96
|
void announceWebhooks(agentDir, t.url, declaredChannels(m.routeChannels ?? []), {
|
|
98
97
|
openUrl: openExternalUrl,
|
|
99
|
-
stateRoot: resolveStateRoot(agentDir),
|
|
100
98
|
});
|
|
101
99
|
}
|
|
102
100
|
});
|
|
@@ -146,9 +144,9 @@ export async function runDevSupervisor(agentDir, options = {}) {
|
|
|
146
144
|
});
|
|
147
145
|
watcher.on("error", (error) => log.warn(`[fastagent] file watching error (${error.message}); some edits may need a manual restart`));
|
|
148
146
|
log.info(`[fastagent] watching ${WATCHED_HINT} — code edits restart the dev worker (--no-watch to disable); SYSTEM.md, AGENTS.md, skills/ and prompts/ go live without a restart`);
|
|
149
|
-
// FASTAGENT_SECRETS_DIR can move the `.env` OUT of the agent dir entirely.
|
|
147
|
+
// FASTAGENT_SECRETS_DIR or --env-file can move the `.env` OUT of the agent dir entirely.
|
|
150
148
|
if (!isUnderDir(dotEnvPath(agentDir), agentDir)) {
|
|
151
|
-
log.warn(`[fastagent] .env lives outside the agent dir (FASTAGENT_SECRETS_DIR → ${dotEnvPath(agentDir)}) — it is NOT watched; restart dev after editing it`);
|
|
149
|
+
log.warn(`[fastagent] .env lives outside the agent dir (FASTAGENT_SECRETS_DIR or --env-file → ${dotEnvPath(agentDir)}) — it is NOT watched; restart dev after editing it`);
|
|
152
150
|
}
|
|
153
151
|
const shutdown = () => {
|
|
154
152
|
worker?.kill("SIGTERM");
|
|
@@ -61,7 +61,7 @@ function declareVariables(raw) {
|
|
|
61
61
|
if (!isEnvName(name))
|
|
62
62
|
throw new Error(`${at} is not an environment variable name`);
|
|
63
63
|
if (isDeployOwned(name)) {
|
|
64
|
-
throw new Error(`${at} is set by the deployment itself, so it cannot be declared
|
|
64
|
+
throw new Error(`${at} is set by the deployment itself, so it cannot be declared`);
|
|
65
65
|
}
|
|
66
66
|
if (!variable || typeof variable !== "object" || Array.isArray(variable)) {
|
|
67
67
|
throw new Error(`${at} must be { "description": "what it is for" }: its value goes in the stage's value file ` +
|
|
@@ -87,8 +87,9 @@ function declareVariables(raw) {
|
|
|
87
87
|
export function declaredVariables(environment) {
|
|
88
88
|
return Object.entries(environment?.variables ?? {}).map(([name, { description }]) => ({
|
|
89
89
|
name,
|
|
90
|
-
|
|
90
|
+
declaredIn: CONTEXT_FILE,
|
|
91
91
|
description,
|
|
92
|
+
optional: false,
|
|
92
93
|
}));
|
|
93
94
|
}
|
|
94
95
|
function declarePackages(raw) {
|
|
@@ -48,4 +48,6 @@ export declare function installPackages(agentDir: string, bin: string, packages:
|
|
|
48
48
|
* mise itself is not put on the PATH. An agent without packages borrows the machine's commands (no variables). Apt
|
|
49
49
|
* packages are the image's to install; one missing here is said, with how.
|
|
50
50
|
*/
|
|
51
|
-
export declare function prepareEnvironment(agentDir: string, env: NodeJS.ProcessEnv
|
|
51
|
+
export declare function prepareEnvironment(agentDir: string, env: NodeJS.ProcessEnv,
|
|
52
|
+
/** What the packages' `PATH` is computed over: the agent's own, which is not the installer's. */
|
|
53
|
+
commandsEnv?: NodeJS.ProcessEnv): Promise<Record<string, string>>;
|
package/dist/environment/mise.js
CHANGED
|
@@ -256,7 +256,9 @@ function missingMise(agentDir) {
|
|
|
256
256
|
* mise itself is not put on the PATH. An agent without packages borrows the machine's commands (no variables). Apt
|
|
257
257
|
* packages are the image's to install; one missing here is said, with how.
|
|
258
258
|
*/
|
|
259
|
-
export async function prepareEnvironment(agentDir, env
|
|
259
|
+
export async function prepareEnvironment(agentDir, env,
|
|
260
|
+
/** What the packages' `PATH` is computed over: the agent's own, which is not the installer's. */
|
|
261
|
+
commandsEnv = env) {
|
|
260
262
|
const declared = loadEnvironment(agentDir);
|
|
261
263
|
const missingApt = missingAptPackages(declared?.aptPackages ?? []);
|
|
262
264
|
if (missingApt && missingApt.length > 0)
|
|
@@ -268,5 +270,5 @@ export async function prepareEnvironment(agentDir, env) {
|
|
|
268
270
|
if (!bin)
|
|
269
271
|
throw missingMise(agentDir);
|
|
270
272
|
await installPackages(agentDir, bin, packages, [], env);
|
|
271
|
-
return packagesEnvironment(agentDir, bin, packageSpecs(packages),
|
|
273
|
+
return packagesEnvironment(agentDir, bin, packageSpecs(packages), commandsEnv);
|
|
272
274
|
}
|
package/dist/feishu.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** `@fastagent-sh/fastagent/feishu` — the canonical Feishu (open.feishu.cn) bot-channel surface. */
|
|
2
|
-
export { feishuChannel,
|
|
2
|
+
export { feishuChannel, feishuWebSocketChannel, defaultFeishuRoute, feishuEnvelope, type FeishuChannelOptions, type FeishuWebSocketChannelOptions, type FeishuMessageEvent, type FeishuMessage, type FeishuRoute, type FeishuFailure, } from "./channels/feishu/feishu.ts";
|
|
3
3
|
export { feishuThreads, feishuTransport, type FeishuTransport } from "./channels/feishu/shared-api.ts";
|
package/dist/feishu.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** `@fastagent-sh/fastagent/feishu` — the canonical Feishu (open.feishu.cn) bot-channel surface. */
|
|
2
|
-
export { feishuChannel,
|
|
2
|
+
export { feishuChannel, feishuWebSocketChannel, defaultFeishuRoute, feishuEnvelope, } from "./channels/feishu/feishu.js";
|
|
3
3
|
export { feishuThreads, feishuTransport } from "./channels/feishu/shared-api.js";
|
|
@@ -11,6 +11,7 @@ export interface Catalogs {
|
|
|
11
11
|
*/
|
|
12
12
|
refresh(models: Models, options?: {
|
|
13
13
|
force?: boolean;
|
|
14
|
+
offline?: boolean;
|
|
14
15
|
}): Promise<string | undefined>;
|
|
15
16
|
}
|
|
16
17
|
/** The catalogs kept in `dir`, or, without one, for this process alone. `url` and `timeoutMs` are for tests. */
|
|
@@ -110,8 +110,7 @@ export function catalogsIn(dir, options = {}) {
|
|
|
110
110
|
};
|
|
111
111
|
},
|
|
112
112
|
fetched,
|
|
113
|
-
async refresh(models, { force = false } = {}) {
|
|
114
|
-
const offline = process.env[OFFLINE_VARIABLE] === "1";
|
|
113
|
+
async refresh(models, { force = false, offline = process.env[OFFLINE_VARIABLE] === "1" } = {}) {
|
|
115
114
|
const signal = AbortSignal.timeout(timeoutMs);
|
|
116
115
|
const why = (error) => signal.aborted ? `no answer within ${timeoutMs / 1000}s` : messageOf(error);
|
|
117
116
|
const [published, own] = await Promise.all([
|
|
@@ -35,8 +35,9 @@ export interface ModelEnvironment {
|
|
|
35
35
|
* The model environment of the agent in `agentDir` (or, without one, of a caller with no directory: the named or global
|
|
36
36
|
* credentials file). A supplied `credentialStore` is used as given; otherwise the files: the directory's own layers
|
|
37
37
|
* ({@link resolveAuthLayers}), or the named file, or the global one. `providers` are the definition's. `environment` is
|
|
38
|
-
*
|
|
39
|
-
* (`FASTAGENT_AUTH_PATH`, `FASTAGENT_STATE_DIR`)
|
|
38
|
+
* the one source of the values a provider authenticates with and of the requests' settings, which this process's
|
|
39
|
+
* environment never adds to; where its files are (`FASTAGENT_AUTH_PATH`, `FASTAGENT_STATE_DIR`) is still read from
|
|
40
|
+
* this process's first. The model lists are refreshed before it
|
|
40
41
|
* resolves, kept in the instance's state root (`stateRoot`, else the directory's); `force` asks the network even when
|
|
41
42
|
* they are fresh.
|
|
42
43
|
*/
|
|
@@ -44,5 +45,9 @@ export declare function modelEnvironment(agentDir: string | undefined, source?:
|
|
|
44
45
|
force?: boolean;
|
|
45
46
|
stateRoot?: string;
|
|
46
47
|
environment?: Readonly<Record<string, string>>;
|
|
48
|
+
/** The environment `FASTAGENT_AUTH_PATH` and `FASTAGENT_STATE_DIR` are read from. Default: this process's. */
|
|
49
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
50
|
+
/** Refresh the lists without the network. Default: `FASTAGENT_OFFLINE` is `1` in this process's environment. */
|
|
51
|
+
offline?: boolean;
|
|
47
52
|
}): Promise<ModelEnvironment>;
|
|
48
53
|
export {};
|
|
@@ -15,14 +15,15 @@ import { environmentAuth, modelsOver, probeAuthSource } from "./models.js";
|
|
|
15
15
|
* The model environment of the agent in `agentDir` (or, without one, of a caller with no directory: the named or global
|
|
16
16
|
* credentials file). A supplied `credentialStore` is used as given; otherwise the files: the directory's own layers
|
|
17
17
|
* ({@link resolveAuthLayers}), or the named file, or the global one. `providers` are the definition's. `environment` is
|
|
18
|
-
*
|
|
19
|
-
* (`FASTAGENT_AUTH_PATH`, `FASTAGENT_STATE_DIR`)
|
|
18
|
+
* the one source of the values a provider authenticates with and of the requests' settings, which this process's
|
|
19
|
+
* environment never adds to; where its files are (`FASTAGENT_AUTH_PATH`, `FASTAGENT_STATE_DIR`) is still read from
|
|
20
|
+
* this process's first. The model lists are refreshed before it
|
|
20
21
|
* resolves, kept in the instance's state root (`stateRoot`, else the directory's); `force` asks the network even when
|
|
21
22
|
* they are fresh.
|
|
22
23
|
*/
|
|
23
24
|
export async function modelEnvironment(agentDir, source = {}, providers = [], options = {}) {
|
|
24
25
|
const environment = options.environment ?? {};
|
|
25
|
-
const env = { ...process.env, ...environment };
|
|
26
|
+
const env = { ...(options.env ?? process.env), ...environment };
|
|
26
27
|
assertOneCredentialSource(source);
|
|
27
28
|
const files = source.credentialStore
|
|
28
29
|
? undefined
|
|
@@ -34,7 +35,10 @@ export async function modelEnvironment(agentDir, source = {}, providers = [], op
|
|
|
34
35
|
const stateRoot = options.stateRoot ?? (agentDir === undefined ? undefined : resolveStateRoot(agentDir, env));
|
|
35
36
|
const catalogs = catalogsIn(stateRoot === undefined ? undefined : join(stateRoot, "models"));
|
|
36
37
|
const models = modelsOver(credentials, providers, catalogs, environment);
|
|
37
|
-
const catalogProblem = await catalogs.refresh(models, {
|
|
38
|
+
const catalogProblem = await catalogs.refresh(models, {
|
|
39
|
+
force: options.force === true,
|
|
40
|
+
...(options.offline === undefined ? {} : { offline: options.offline }),
|
|
41
|
+
});
|
|
38
42
|
if (catalogProblem !== undefined)
|
|
39
43
|
log.warn(`[fastagent] ${catalogProblem}`);
|
|
40
44
|
return {
|
|
@@ -58,7 +62,7 @@ export async function modelEnvironment(agentDir, source = {}, providers = [], op
|
|
|
58
62
|
}
|
|
59
63
|
// `read` never refreshes: this is the kind on file, whatever became of it.
|
|
60
64
|
const stored = (await credentials.read(provider))?.type;
|
|
61
|
-
const fromEnvironment = stored === undefined ? undefined : (await environmentAuth(provider,
|
|
65
|
+
const fromEnvironment = stored === undefined ? undefined : (await environmentAuth(provider, environment, undefined, providers))?.source;
|
|
62
66
|
const shadowed = resolved.source !== undefined && fromEnvironment !== resolved.source ? fromEnvironment : undefined;
|
|
63
67
|
return {
|
|
64
68
|
...(store ? { path: await store.layerOf(provider) } : {}),
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What an embedding host calls for model credentials (sign-in-api.ts has the types): a store kept where the host
|
|
3
|
-
* chooses, a sign-in in its own UI, and what authenticates a provider. Thin over what `fastagent login`, `logout` and
|
|
4
|
-
* `auth check` run, so each rule keeps one implementation. The one translation between FastAgent's types and pi-ai's
|
|
5
|
-
* is here, written without casts and with every function checked as a function, so it stops compiling when the two
|
|
6
|
-
* drift apart.
|
|
7
|
-
*/
|
|
8
1
|
import type { CredentialStore as PiCredentialStore } from "@earendil-works/pi-ai";
|
|
9
2
|
import type { CredentialStore, ProviderAuth, SignInOption, SignInRequest, SignInResult } from "../../sign-in-api.ts";
|
|
10
3
|
/**
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an embedding host calls for model credentials (sign-in-api.ts has the types): a store kept where the host
|
|
3
|
+
* chooses, a sign-in in its own UI, and what authenticates a provider. Thin over what `fastagent login`, `logout` and
|
|
4
|
+
* `auth check` run, so each rule keeps one implementation. The one translation between FastAgent's types and pi-ai's
|
|
5
|
+
* is here, written without casts and with every function checked as a function, so it stops compiling when the two
|
|
6
|
+
* drift apart.
|
|
7
|
+
*/
|
|
8
|
+
import { definedValues } from "../../declared-secrets.js";
|
|
1
9
|
import { resolveOverridePath } from "../../paths.js";
|
|
2
10
|
import { fastagentCredentialStore } from "./credentials.js";
|
|
3
11
|
import { loginOptionsOver, loginOver } from "./login.js";
|
|
@@ -74,7 +82,8 @@ export function signIn(request) {
|
|
|
74
82
|
* does; rejects when resolving fails, such as a refresh the provider refused.
|
|
75
83
|
*/
|
|
76
84
|
export async function providerAuth(credentials, provider) {
|
|
77
|
-
|
|
85
|
+
// The host's own environment: the answer is what a request this process sends authenticates with.
|
|
86
|
+
const models = modelsOver(piCredentials(credentials), [], undefined, definedValues(process.env));
|
|
78
87
|
if (models.getProvider(provider) === undefined)
|
|
79
88
|
throw new Error(`unknown provider "${provider}"`);
|
|
80
89
|
const checked = await models.checkAuth(provider);
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Signing in to a MODEL PROVIDER, for any front end: {@link loginOver} runs one provider's flow over pi-ai's own
|
|
3
|
-
* `AuthInteraction` and persists the credential through the same {@link fastagentCredentialStore} the runtime reads
|
|
4
|
-
* (one writer, one lock/corruption semantics); {@link loginOptionsOver} is the list a picker offers. An embedding host
|
|
5
|
-
* reaches both through host.ts (`signIn`, `signInOptions`); `fastagent login` is a front end of its own
|
|
6
|
-
* ({@link loginFlow}: terminal menus, then {@link loginOver}); `login --deployment` drives the same flow on a deployed
|
|
7
|
-
* box over a relay (cli/login-relay.ts).
|
|
8
|
-
*/
|
|
9
1
|
import { type AuthInteraction, type CredentialStore, type Provider } from "@earendil-works/pi-ai";
|
|
10
2
|
import { type SignInMethod, type SignInOption, type SignInResult } from "../../sign-in-api.ts";
|
|
11
3
|
/** A sign-in that cannot start as asked: an unknown provider, or a choice a caller without menus did not make. */
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* ({@link loginFlow}: terminal menus, then {@link loginOver}); `login --deployment` drives the same flow on a deployed
|
|
7
7
|
* box over a relay (cli/login-relay.ts).
|
|
8
8
|
*/
|
|
9
|
+
import { definedValues } from "../../declared-secrets.js";
|
|
9
10
|
import { InMemoryCredentialStore, } from "@earendil-works/pi-ai";
|
|
10
11
|
import { randomUUID } from "node:crypto";
|
|
11
12
|
import { mkdir, readFile } from "node:fs/promises";
|
|
@@ -36,8 +37,8 @@ export async function loginOptionsOver(credentials, providers) {
|
|
|
36
37
|
// ONE read of the store: `list` is metadata only, and reading per provider would repeat a corrupt file's warning for
|
|
37
38
|
// every provider.
|
|
38
39
|
const held = new Map((await storeOf(credentials).list()).map((info) => [info.providerId, info.type]));
|
|
39
|
-
// The environment alone, over no store: what authenticates a provider nothing is stored for.
|
|
40
|
-
const environment = modelsOver(new InMemoryCredentialStore(), providers);
|
|
40
|
+
// The environment alone, over no store: what authenticates a provider nothing is stored for, here the host's own.
|
|
41
|
+
const environment = modelsOver(new InMemoryCredentialStore(), providers, undefined, definedValues(process.env));
|
|
41
42
|
const offered = [];
|
|
42
43
|
for (const provider of loginProviders(providers)) {
|
|
43
44
|
const methods = ["oauth", "api_key"].filter((method) => provider.auth[method === "oauth" ? "oauth" : "apiKey"]);
|
|
@@ -129,7 +130,7 @@ export async function loginOver(request, internals = {}) {
|
|
|
129
130
|
// The key under test lives here, not in the file, until it passes.
|
|
130
131
|
const trial = new InMemoryCredentialStore();
|
|
131
132
|
// Only a key needs the registry: it is checked with one request before it is kept.
|
|
132
|
-
const models = method === "api_key" ? modelsOver(trial, providers) : undefined;
|
|
133
|
+
const models = method === "api_key" ? modelsOver(trial, providers, undefined, definedValues(process.env)) : undefined;
|
|
133
134
|
const store = request.credentialStore ?? fastagentCredentialStore(request.authPath);
|
|
134
135
|
await store.modify(provider.id, async () => undefined);
|
|
135
136
|
const signal = interaction.signal ?? new AbortController().signal;
|
|
@@ -235,7 +236,7 @@ export const PI_DEFAULT_MODELS = {
|
|
|
235
236
|
"amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
|
|
236
237
|
"ant-ling": "Ring-2.6-1T",
|
|
237
238
|
anthropic: "claude-opus-4-8",
|
|
238
|
-
openai: "gpt-
|
|
239
|
+
openai: "gpt-6.1-sol",
|
|
239
240
|
azure: "gpt-5.4",
|
|
240
241
|
"openai-codex": "gpt-6.1-sol",
|
|
241
242
|
radius: "balanced",
|
|
@@ -11,8 +11,13 @@ export declare function providerOf(spec: string): string;
|
|
|
11
11
|
/** Every chat model `models` offers, as `"provider/modelId"`, sorted. */
|
|
12
12
|
export declare function listModels(models: Models): string[];
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
* the
|
|
14
|
+
* What to say when `spec` names no model: the models of its provider that read most like it, or the providers there are
|
|
15
|
+
* when the provider is the unknown part, and the command that lists them.
|
|
16
|
+
*/
|
|
17
|
+
export declare function modelHint(specs: readonly string[], spec: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* The built-ins plus `providers` (a same id replaces a built-in), over any credential store, in `environment`: the
|
|
20
|
+
* values a provider's auth reads its keys from, and each request is given. With
|
|
16
21
|
* `catalogs`, the lists its refresh brings up to date; without, the ones this pi-ai was built with.
|
|
17
22
|
*/
|
|
18
23
|
export declare function modelsOver(credentials: CredentialStore, providers?: readonly Provider[], catalogs?: Catalogs, environment?: Readonly<Record<string, string>>): Models;
|
|
@@ -20,6 +20,30 @@ export function listModels(models) {
|
|
|
20
20
|
}
|
|
21
21
|
return specs.sort();
|
|
22
22
|
}
|
|
23
|
+
const tokensOf = (text) => text
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.split(/[^a-z0-9]+/)
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
/**
|
|
28
|
+
* What to say when `spec` names no model: the models of its provider that read most like it, or the providers there are
|
|
29
|
+
* when the provider is the unknown part, and the command that lists them.
|
|
30
|
+
*/
|
|
31
|
+
export function modelHint(specs, spec) {
|
|
32
|
+
const provider = providerOf(spec);
|
|
33
|
+
const own = specs.filter((candidate) => candidate.startsWith(`${provider}/`));
|
|
34
|
+
if (own.length === 0) {
|
|
35
|
+
const providers = [...new Set(specs.map(providerOf))];
|
|
36
|
+
return `there is no provider "${provider}"; the providers are ${providers.join(", ")} (\`fastagent models\` lists their models)`;
|
|
37
|
+
}
|
|
38
|
+
const wanted = new Set(tokensOf(spec.slice(provider.length + 1)));
|
|
39
|
+
const overlap = (candidate) => tokensOf(candidate.slice(provider.length + 1)).filter((t) => wanted.has(t)).length;
|
|
40
|
+
const closest = own
|
|
41
|
+
.map((candidate) => ({ candidate, score: overlap(candidate) }))
|
|
42
|
+
.sort((a, b) => b.score - a.score || a.candidate.localeCompare(b.candidate))
|
|
43
|
+
.slice(0, 5)
|
|
44
|
+
.map(({ candidate }) => candidate);
|
|
45
|
+
return `${provider}'s models include ${closest.join(", ")} (\`fastagent models ${provider}\` lists all ${own.length})`;
|
|
46
|
+
}
|
|
23
47
|
/**
|
|
24
48
|
* pi's own reading of an environment (a blank variable is unset) through `read` instead of this process's: an agent's
|
|
25
49
|
* environment is its own when the process runs several. Files a source points at are this machine's unless
|
|
@@ -61,15 +85,15 @@ function inEnvironment(provider, environment) {
|
|
|
61
85
|
};
|
|
62
86
|
}
|
|
63
87
|
/**
|
|
64
|
-
* The built-ins plus `providers` (a same id replaces a built-in), over any credential store, in `environment`:
|
|
65
|
-
*
|
|
88
|
+
* The built-ins plus `providers` (a same id replaces a built-in), over any credential store, in `environment`: the
|
|
89
|
+
* values a provider's auth reads its keys from, and each request is given. With
|
|
66
90
|
* `catalogs`, the lists its refresh brings up to date; without, the ones this pi-ai was built with.
|
|
67
91
|
*/
|
|
68
92
|
export function modelsOver(credentials, providers = [], catalogs, environment = {}) {
|
|
69
93
|
const models = createModels({
|
|
70
94
|
credentials,
|
|
71
|
-
//
|
|
72
|
-
authContext: authContextIn((name) => environment[name]
|
|
95
|
+
// The values given, and none of this process's: an instance's keys are its own, whoever else runs here.
|
|
96
|
+
authContext: authContextIn((name) => environment[name]),
|
|
73
97
|
...(catalogs ? { modelsStore: catalogs.fetched } : {}),
|
|
74
98
|
});
|
|
75
99
|
for (const builtIn of builtinProviders()) {
|
|
@@ -23,7 +23,6 @@ import { admitSettings } from "./defaults.js";
|
|
|
23
23
|
import { USER_ENTRY, imageAt, isPublished, parseImageRef } from "./entries.js";
|
|
24
24
|
import { sessionEvents } from "./events.js";
|
|
25
25
|
import { storageIds } from "./ids.js";
|
|
26
|
-
import { giveUpOnParked } from "./parked.js";
|
|
27
26
|
import { isInput, queuedInputs, requestIdFor, usageOfState } from "./messages.js";
|
|
28
27
|
import { durableReads } from "./reads.js";
|
|
29
28
|
import { releaseWaitingQueues } from "./release.js";
|
|
@@ -38,13 +37,12 @@ export async function openDurableAgent(options) {
|
|
|
38
37
|
registry: options.registry,
|
|
39
38
|
...(options.settings === undefined ? {} : { settings: options.settings }),
|
|
40
39
|
...(options.env === undefined ? {} : { env: options.env }),
|
|
41
|
-
onReport: (
|
|
40
|
+
onReport: (report) => log.warn(`[fastagent] pi-durable reported ${report.kind}: ${String(report.error)}`),
|
|
42
41
|
}, context);
|
|
43
42
|
const lifetime = Scope.makeUnsafe();
|
|
44
43
|
try {
|
|
45
44
|
const ids = await storageIds(harness, context);
|
|
46
45
|
const sessions = await Effect.runPromise(Scope.provide(trackSessions(harness, options.storage, context), lifetime));
|
|
47
|
-
await giveUpOnParked(harness, context);
|
|
48
46
|
// Before `resume()`, which runs an earlier process's work. An abort enables the scheduler too, but commits first, so
|
|
49
47
|
// the aborted work does not run (measured).
|
|
50
48
|
const unsettled = (await harness.inspect(context)).submissions.filter(isInput);
|
|
@@ -215,58 +213,81 @@ function durableAgent(harness, ids, sessions, lifetime, options) {
|
|
|
215
213
|
sessionId: ids.encode(conversation.id),
|
|
216
214
|
...(messageId === undefined ? {} : { messageId: ids.encode(messageId) }),
|
|
217
215
|
});
|
|
218
|
-
/**
|
|
216
|
+
/**
|
|
217
|
+
* Admission, one at a time per process: one commit that looks the key up, then checks the process's limit and the
|
|
218
|
+
* queue, takes the defaults and submits, so it stores all of that or none of it.
|
|
219
|
+
*/
|
|
219
220
|
const admit = (request) => Semaphore.withPermits(admission, 1)(Effect.gen(function* () {
|
|
220
221
|
const requestId = requestIdFor(request.idempotencyKey);
|
|
221
222
|
// Before anything is written: reading or expanding a command reads the definition's files, and may throw.
|
|
222
223
|
const { content, sent } = expandCommand(request.content, commands());
|
|
223
224
|
const conversation = yield* conversationOf(request.sessionId);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return { message: yield* reads.message(seen), replayed: true };
|
|
228
|
-
}
|
|
229
|
-
if ((yield* inFlight) >= inFlightLimit) {
|
|
230
|
-
return yield* agentError("busy", `This agent already holds ${inFlightLimit} messages`, {
|
|
231
|
-
reason: "capacity",
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
if (request.whenBusy !== "reject") {
|
|
235
|
-
const inbox = yield* reads.inbox(conversation);
|
|
236
|
-
if (queuedInputs(inbox).length >= LIMITS.queuedPerSession) {
|
|
237
|
-
return yield* agentError("busy", `The session already has ${LIMITS.queuedPerSession} queued messages`, {
|
|
238
|
-
reason: "queue_full",
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
const refused = yield* port(() => conversation.commit((tx) => admitSettings(tx, conversation.id, defaults, has), context));
|
|
243
|
-
if (refused !== undefined)
|
|
244
|
-
return yield* unanswerable(refused);
|
|
225
|
+
// Read before the commit, which cannot read the harness, and applied after the key: a key the session has
|
|
226
|
+
// seen answers with its message whatever the process holds (SPEC.md §5.2).
|
|
227
|
+
const atCapacity = (yield* inFlight) >= inFlightLimit;
|
|
245
228
|
const data = { requestId, ...(sent === undefined ? {} : { sent }) };
|
|
246
|
-
let
|
|
247
|
-
|
|
248
|
-
|
|
229
|
+
let submitting = false;
|
|
230
|
+
const admitted = yield* port(() => conversation.commit(async (tx) => {
|
|
231
|
+
if (request.idempotencyKey !== undefined) {
|
|
232
|
+
const seen = await tx.submissionByRequest(conversation.id, requestId);
|
|
233
|
+
if (isInput(seen))
|
|
234
|
+
return { kind: "seen", seen };
|
|
235
|
+
}
|
|
236
|
+
if (atCapacity)
|
|
237
|
+
return { kind: "capacity" };
|
|
238
|
+
if (request.whenBusy !== "reject") {
|
|
239
|
+
const inbox = await tx.doc(InboxDoc, conversation.id);
|
|
240
|
+
if (queuedInputs(inbox).length >= LIMITS.queuedPerSession)
|
|
241
|
+
return { kind: "full" };
|
|
242
|
+
}
|
|
243
|
+
const refused = await admitSettings(tx, conversation.id, defaults, has);
|
|
244
|
+
if (refused !== undefined)
|
|
245
|
+
return { kind: "refused", refused };
|
|
246
|
+
submitting = true;
|
|
247
|
+
const id = await conversation.submit(tx, {
|
|
249
248
|
type: "input",
|
|
250
249
|
content,
|
|
251
250
|
requestId,
|
|
252
251
|
whenBusy: request.whenBusy === "steer" ? "steer" : request.whenBusy === "reject" ? "reject" : "followUp",
|
|
253
252
|
entry: { kind: USER_ENTRY, data },
|
|
254
|
-
}
|
|
255
|
-
|
|
253
|
+
});
|
|
254
|
+
return { kind: "admitted", id };
|
|
255
|
+
}, context)).pipe(
|
|
256
|
+
// Once the submission is in the commit, only pi-durable's explicit refusal proves nothing was recorded.
|
|
257
|
+
Effect.catchCause((cause) => {
|
|
258
|
+
const error = portError(cause);
|
|
259
|
+
if (error instanceof ConversationBusy) {
|
|
260
|
+
return Effect.fail(agentError("busy", "The session is answering another message", { reason: "running" }));
|
|
261
|
+
}
|
|
262
|
+
return submitting ? Effect.fail(unconfirmed(conversation, undefined, error)) : Effect.failCause(cause);
|
|
263
|
+
}));
|
|
264
|
+
if (admitted.kind === "seen")
|
|
265
|
+
return { message: yield* reads.message(admitted.seen), replayed: true };
|
|
266
|
+
if (admitted.kind === "capacity") {
|
|
267
|
+
return yield* agentError("busy", `This agent already holds ${inFlightLimit} messages`, {
|
|
268
|
+
reason: "capacity",
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (admitted.kind === "full") {
|
|
272
|
+
return yield* agentError("busy", `The session already has ${LIMITS.queuedPerSession} queued messages`, {
|
|
273
|
+
reason: "queue_full",
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (admitted.kind === "refused")
|
|
277
|
+
return yield* unanswerable(admitted.refused);
|
|
278
|
+
const messageId = admitted.id;
|
|
279
|
+
return yield* Effect.gen(function* () {
|
|
280
|
+
const submission = yield* port(() => harness.submission(messageId, context));
|
|
281
|
+
if (submission === undefined) {
|
|
282
|
+
throw new Error(`submission ${messageId} is not in the storage it was admitted to`);
|
|
283
|
+
}
|
|
256
284
|
// Counted once it is read back, whether or not that read succeeds: the wait must not be what reads the
|
|
257
285
|
// storage first, or a failing read would fail it instead of answering this call.
|
|
258
286
|
const record = yield* port(() => submission.status(context)).pipe(Effect.ensuring(Effect.sync(() => countUntilSettled(submission))));
|
|
259
287
|
if (!isInput(record))
|
|
260
288
|
throw new Error(`submission ${record.id} was admitted as a write`);
|
|
261
289
|
return { message: yield* reads.message(record), replayed: false };
|
|
262
|
-
}).pipe(
|
|
263
|
-
// Once submission starts, only pi-durable's explicit refusal proves nothing was recorded.
|
|
264
|
-
Effect.catchCause((cause) => {
|
|
265
|
-
const error = portError(cause);
|
|
266
|
-
return Effect.fail(messageId === undefined && error instanceof ConversationBusy
|
|
267
|
-
? agentError("busy", "The session is answering another message", { reason: "running" })
|
|
268
|
-
: unconfirmed(conversation, messageId, error));
|
|
269
|
-
}));
|
|
290
|
+
}).pipe(Effect.catchCause((cause) => Effect.fail(unconfirmed(conversation, messageId, portError(cause)))));
|
|
270
291
|
}));
|
|
271
292
|
/** The message as it is now. */
|
|
272
293
|
const current = (message) => reads.submission(message.id).pipe(Effect.flatMap((record) => {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AgentDefinition } from "./definition.ts";
|
|
2
|
+
export interface BuildOptions {
|
|
3
|
+
/** Type-check the project (default). Without it, `tsc --noCheck` only emits. */
|
|
4
|
+
check?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface BuildResult {
|
|
7
|
+
dist: string;
|
|
8
|
+
definition: AgentDefinition;
|
|
9
|
+
/** What the build would publish that the author should look at: a literal credential in `mcpServers`, say. */
|
|
10
|
+
warnings: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function buildProject(directory: string, options?: BuildOptions): Promise<BuildResult>;
|