@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
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
* terminal, so the command switches it to raw, no echo (answers are not echoed back, and a long line is not cut by
|
|
14
14
|
* the terminal's line editor) before handing it to `exec`.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
16
|
+
import { randomUUID } from "node:crypto";
|
|
17
17
|
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
18
18
|
import { tmpdir } from "node:os";
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import { PassThrough, Writable } from "node:stream";
|
|
21
21
|
import { SignatureV4 } from "@smithy/signature-v4";
|
|
22
|
-
import {
|
|
22
|
+
import { RuntimeWebSocket, pinger } from "../../runtime-websocket.js";
|
|
23
23
|
import { awsCli, awsJson } from "./aws-cli.js";
|
|
24
|
+
import { Sha256 } from "./sigv4.js";
|
|
24
25
|
import { MOUNT, PROBE_PAYLOAD } from "./plan.js";
|
|
25
26
|
const STDIN = 0x00;
|
|
26
27
|
const STDOUT = 0x01;
|
|
@@ -29,6 +30,7 @@ const STATUS = 0x03;
|
|
|
29
30
|
const HEARTBEAT = 0x05;
|
|
30
31
|
/** The service closes a connection whose frame is larger (code 1009). */
|
|
31
32
|
const MAX_PAYLOAD = 64 * 1024 - 1;
|
|
33
|
+
const WS_OPEN = 1;
|
|
32
34
|
/** The SDK's interval; a connection with no ping for ~60s is dropped by the service. */
|
|
33
35
|
const KEEPALIVE_MS = 30_000;
|
|
34
36
|
/** The longest a presigned shell URL may live (the SDK refuses more). */
|
|
@@ -49,20 +51,6 @@ async function awsCliCredentials(aws) {
|
|
|
49
51
|
return read.ok;
|
|
50
52
|
throw new Error(`could not read AWS credentials from the AWS CLI (${"absent" in read ? "none configured" : read.unreadable})`);
|
|
51
53
|
}
|
|
52
|
-
/** SHA-256 / HMAC-SHA-256 over node:crypto, in the shape SigV4's signer takes. */
|
|
53
|
-
class Sha256 {
|
|
54
|
-
hash;
|
|
55
|
-
constructor(secret) {
|
|
56
|
-
const key = secret instanceof ArrayBuffer ? Buffer.from(secret) : secret;
|
|
57
|
-
this.hash = key === undefined ? createHash("sha256") : createHmac("sha256", key);
|
|
58
|
-
}
|
|
59
|
-
update(data) {
|
|
60
|
-
this.hash.update(data instanceof ArrayBuffer ? Buffer.from(data) : data);
|
|
61
|
-
}
|
|
62
|
-
async digest() {
|
|
63
|
-
return new Uint8Array(this.hash.digest());
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
54
|
/** The runtime's region, from its ARN (`arn:<partition>:bedrock-agentcore:<region>:<account>:runtime/<id>`). */
|
|
67
55
|
function regionOf(runtimeArn) {
|
|
68
56
|
const region = runtimeArn.split(":")[3];
|
|
@@ -105,7 +93,12 @@ export function shellExit(status) {
|
|
|
105
93
|
/** Open `command` in a new shell on `url`: stdin/stdout as streams, the other channels folded in. */
|
|
106
94
|
export function openShellChannel(url, command) {
|
|
107
95
|
const output = new PassThrough();
|
|
108
|
-
const ws = new
|
|
96
|
+
const ws = new RuntimeWebSocket(url);
|
|
97
|
+
const ping = pinger(ws);
|
|
98
|
+
if (ping === undefined) {
|
|
99
|
+
ws.close();
|
|
100
|
+
throw new Error("this runtime cannot send WebSocket pings, and the AgentCore service drops a shell connection that has none for about a minute");
|
|
101
|
+
}
|
|
109
102
|
ws.binaryType = "arraybuffer";
|
|
110
103
|
const frame = (channel, payload = Buffer.alloc(0)) => ws.send(Buffer.concat([Buffer.from([channel]), payload]));
|
|
111
104
|
const stdin = (bytes) => {
|
|
@@ -122,7 +115,7 @@ export function openShellChannel(url, command) {
|
|
|
122
115
|
ws.addEventListener("open", () => {
|
|
123
116
|
stdin(Buffer.from(`stty raw -echo 2>/dev/null; exec sh -c '${command}'\n`));
|
|
124
117
|
keepalive = setInterval(() => {
|
|
125
|
-
ping(
|
|
118
|
+
ping();
|
|
126
119
|
frame(HEARTBEAT);
|
|
127
120
|
}, KEEPALIVE_MS);
|
|
128
121
|
});
|
|
@@ -157,7 +150,7 @@ export function openShellChannel(url, command) {
|
|
|
157
150
|
const input = new Writable({
|
|
158
151
|
write(chunk, _encoding, done) {
|
|
159
152
|
void opened.then((open) => {
|
|
160
|
-
if (open && ws.readyState ===
|
|
153
|
+
if (open && ws.readyState === WS_OPEN)
|
|
161
154
|
stdin(chunk);
|
|
162
155
|
done();
|
|
163
156
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** SHA-256 / HMAC-SHA-256 over node:crypto, in the shape SigV4's signer takes. */
|
|
2
|
+
export declare class Sha256 {
|
|
3
|
+
private readonly hash;
|
|
4
|
+
constructor(secret?: string | ArrayBuffer | ArrayBufferView);
|
|
5
|
+
update(data: string | ArrayBuffer | ArrayBufferView): void;
|
|
6
|
+
digest(): Promise<Uint8Array>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createHash, createHmac } from "node:crypto";
|
|
2
|
+
/** SHA-256 / HMAC-SHA-256 over node:crypto, in the shape SigV4's signer takes. */
|
|
3
|
+
export class Sha256 {
|
|
4
|
+
hash;
|
|
5
|
+
constructor(secret) {
|
|
6
|
+
const key = secret instanceof ArrayBuffer ? Buffer.from(secret) : secret;
|
|
7
|
+
this.hash = key === undefined ? createHash("sha256") : createHmac("sha256", key);
|
|
8
|
+
}
|
|
9
|
+
update(data) {
|
|
10
|
+
this.hash.update(data instanceof ArrayBuffer ? Buffer.from(data) : data);
|
|
11
|
+
}
|
|
12
|
+
async digest() {
|
|
13
|
+
return new Uint8Array(this.hash.digest());
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The service-linked role AgentCore's runtime needs in the account, checked before a deploy builds anything. Without it
|
|
3
|
+
* CloudFormation fails at the runtime, minutes after the image push, with a bare `AccessDenied` that names nothing.
|
|
4
|
+
*
|
|
5
|
+
* Read first, create only when it is absent: IAM authorizes `CreateServiceLinkedRole` before it says a role already
|
|
6
|
+
* exists, so creating unconditionally would refuse a deployer who lacks that permission in every account where the role
|
|
7
|
+
* is long since there. A read that cannot be answered warns and goes on, as the region probe does.
|
|
8
|
+
*/
|
|
9
|
+
import type { AwsCli } from "./aws-cli.ts";
|
|
10
|
+
export interface ServiceLinkedRole {
|
|
11
|
+
/** The service principal the role is created for. */
|
|
12
|
+
service: string;
|
|
13
|
+
/** The role's name, which is what `iam get-role` takes. */
|
|
14
|
+
name: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const RUNTIME_IDENTITY_ROLE: ServiceLinkedRole;
|
|
17
|
+
/** A gate line when the role is absent and cannot be made, else undefined. */
|
|
18
|
+
export declare function ensureServiceLinkedRole(cli: AwsCli, role: ServiceLinkedRole, log: (message: string) => void): Promise<string | undefined>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const RUNTIME_IDENTITY_ROLE = {
|
|
2
|
+
service: "runtime-identity.bedrock-agentcore.amazonaws.com",
|
|
3
|
+
name: "AWSServiceRoleForBedrockAgentCoreRuntimeIdentity",
|
|
4
|
+
};
|
|
5
|
+
/** A gate line when the role is absent and cannot be made, else undefined. */
|
|
6
|
+
export async function ensureServiceLinkedRole(cli, role, log) {
|
|
7
|
+
const found = await cli.present(["iam", "get-role", "--role-name", role.name]);
|
|
8
|
+
if ("ok" in found)
|
|
9
|
+
return undefined;
|
|
10
|
+
if ("unreadable" in found) {
|
|
11
|
+
log(`warn: could not check for the service-linked role ${role.name} (${found.unreadable.split("\n")[0]}) — if the ` +
|
|
12
|
+
`account has never used AgentCore and this principal may not create it, cloudformation deploy fails at the ` +
|
|
13
|
+
`runtime, after the image build`);
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
log(`creating the service-linked role ${role.name}…`);
|
|
17
|
+
const created = await cli.write(["iam", "create-service-linked-role", "--aws-service-name", role.service]);
|
|
18
|
+
if ("done" in created)
|
|
19
|
+
return undefined;
|
|
20
|
+
// Made by another deploy between the read and the write: it is there, which is all that is wanted.
|
|
21
|
+
if ("refused" in created && /has been taken/i.test(created.refused))
|
|
22
|
+
return undefined;
|
|
23
|
+
const why = "refused" in created ? created.refused : "it was not found";
|
|
24
|
+
return (`the service-linked role ${role.name} that AgentCore's runtime needs does not exist in this account, and it could ` +
|
|
25
|
+
`not be created (${why}) — have someone with iam:CreateServiceLinkedRole run ` +
|
|
26
|
+
`\`aws iam create-service-linked-role --aws-service-name ${role.service}\`, then re-run`);
|
|
27
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CliRunner } from "../runner.ts";
|
|
2
|
+
import type { SecretStore } from "../secret-store.ts";
|
|
3
|
+
export interface AgentcoreSecretStore extends SecretStore {
|
|
4
|
+
/** The secret's ARN, which the stack takes as its `SecretArn` parameter. */
|
|
5
|
+
arn(): Promise<string>;
|
|
6
|
+
}
|
|
7
|
+
export declare function agentcoreSecretStore(aws: CliRunner, secretName: string, log: (message: string) => void): AgentcoreSecretStore;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentCore's values as a deployment's store: one Secrets Manager secret per deployment, a JSON object of NAME → value
|
|
3
|
+
* that the container reads on its first invocation (runtime-secrets.ts). It lives outside the stack, as the ECR
|
|
4
|
+
* repository and the artifact bucket do: its values must exist before the first deploy, and a stack update must not be
|
|
5
|
+
* what rewrites them.
|
|
6
|
+
*
|
|
7
|
+
* Reading goes around `awsCli().read`, whose unreadable answer quotes stdout: here stdout is every value.
|
|
8
|
+
*/
|
|
9
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { SECRET_FILE_MODE } from "../../paths.js";
|
|
13
|
+
import { awsCli, awsJson } from "./aws-cli.js";
|
|
14
|
+
const describe = (secretName, ...query) => [
|
|
15
|
+
"secretsmanager",
|
|
16
|
+
"describe-secret",
|
|
17
|
+
"--secret-id",
|
|
18
|
+
secretName,
|
|
19
|
+
...query,
|
|
20
|
+
];
|
|
21
|
+
/** The values the secret holds now. A secret that is not a JSON object of strings is refused, naming nothing in it. */
|
|
22
|
+
function parseValues(secretString) {
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = JSON.parse(secretString);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new Error("its value is not JSON");
|
|
29
|
+
}
|
|
30
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
31
|
+
throw new Error("its value is not a JSON object of NAME → value");
|
|
32
|
+
}
|
|
33
|
+
for (const [name, value] of Object.entries(parsed)) {
|
|
34
|
+
if (typeof value !== "string")
|
|
35
|
+
throw new Error(`its value for ${name} is not a string`);
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
export function agentcoreSecretStore(aws, secretName, log) {
|
|
40
|
+
const cli = awsCli(aws);
|
|
41
|
+
const current = async () => {
|
|
42
|
+
const { code, stdout, stderr } = await aws(["secretsmanager", "get-secret-value", "--secret-id", secretName, "--query", "SecretString", "--output", "text"], { capture: true, captureStderr: true });
|
|
43
|
+
if (code !== 0) {
|
|
44
|
+
throw new Error(`\`aws secretsmanager get-secret-value\` failed: ${(stderr ?? "").trim().slice(0, 300)}`);
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
return parseValues(stdout.trim());
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new Error(`the secret ${secretName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const store = {
|
|
54
|
+
async exists() {
|
|
55
|
+
const read = await cli.read(describe(secretName, "--output", "json"), awsJson((parsed) => parsed ?? undefined));
|
|
56
|
+
if ("unreadable" in read)
|
|
57
|
+
throw new Error(`could not read secret ${secretName}: ${read.unreadable}`);
|
|
58
|
+
if ("absent" in read)
|
|
59
|
+
return false;
|
|
60
|
+
if (read.ok.DeletedDate !== undefined) {
|
|
61
|
+
throw new Error(`secret ${secretName} is scheduled for deletion: \`aws secretsmanager restore-secret --secret-id ${secretName}\` ` +
|
|
62
|
+
`brings it back, or wait for the deletion and re-run`);
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
},
|
|
66
|
+
async ensure() {
|
|
67
|
+
if (await store.exists())
|
|
68
|
+
return;
|
|
69
|
+
log(`creating secret ${secretName}…`);
|
|
70
|
+
const created = await cli.write([
|
|
71
|
+
"secretsmanager",
|
|
72
|
+
"create-secret",
|
|
73
|
+
"--name",
|
|
74
|
+
secretName,
|
|
75
|
+
"--description",
|
|
76
|
+
"FastAgent deployment values",
|
|
77
|
+
"--secret-string",
|
|
78
|
+
"{}",
|
|
79
|
+
]);
|
|
80
|
+
if (!("done" in created)) {
|
|
81
|
+
throw new Error(`\`aws secretsmanager create-secret\` failed: ${"refused" in created ? created.refused : ""}`);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
async names() {
|
|
85
|
+
return new Set(Object.keys(await current()));
|
|
86
|
+
},
|
|
87
|
+
async write(values) {
|
|
88
|
+
const names = Object.keys(values);
|
|
89
|
+
log(`setting ${names.length} value(s) in ${secretName}: ${names.join(", ")}`);
|
|
90
|
+
// Read, merge, write: one operator at a time, as every host's store assumes.
|
|
91
|
+
const merged = { ...(await current()), ...values };
|
|
92
|
+
// Over a file only this process can read, never argv.
|
|
93
|
+
const dir = await mkdtemp(join(tmpdir(), "fastagent-secret-"));
|
|
94
|
+
try {
|
|
95
|
+
const file = join(dir, "secret.json");
|
|
96
|
+
await writeFile(file, JSON.stringify(merged), { mode: SECRET_FILE_MODE });
|
|
97
|
+
const { code, stderr } = await aws(["secretsmanager", "put-secret-value", "--secret-id", secretName, "--secret-string", `file://${file}`], { capture: true, captureStderr: true });
|
|
98
|
+
if (code !== 0) {
|
|
99
|
+
throw new Error(`\`aws secretsmanager put-secret-value\` failed: ${(stderr ?? "").trim().slice(0, 300)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
await rm(dir, { recursive: true, force: true });
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
async arn() {
|
|
107
|
+
const read = await cli.read(describe(secretName, "--query", "ARN", "--output", "text"), (stdout) => stdout.trim() || undefined);
|
|
108
|
+
if (!("ok" in read)) {
|
|
109
|
+
throw new Error(`could not read the ARN of secret ${secretName}: ${"unreadable" in read ? read.unreadable : "absent"}`);
|
|
110
|
+
}
|
|
111
|
+
return read.ok;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
return store;
|
|
115
|
+
}
|
package/dist/deploy/api.d.ts
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/** Whether the deployment serves the API
|
|
1
|
+
import type { EffectiveConfig } from "../harnesses/pi-durable/config.ts";
|
|
2
|
+
/** Whether the deployment serves the API, and why not when it does not. */
|
|
3
3
|
export type DeploymentApi = {
|
|
4
4
|
served: true;
|
|
5
|
-
generatesKey: boolean;
|
|
6
5
|
} | {
|
|
7
6
|
served: false;
|
|
8
7
|
why: string;
|
|
9
8
|
};
|
|
10
9
|
/**
|
|
11
|
-
* Decide the API for one deploy, writing nothing
|
|
12
|
-
*
|
|
13
|
-
* same setting.
|
|
10
|
+
* Decide the API for one deploy, writing nothing. Turned off, it is off on the deployment too, a key the target still
|
|
11
|
+
* holds included: the serve reads the same setting.
|
|
14
12
|
*/
|
|
15
13
|
export declare function deploymentApi(input: {
|
|
16
|
-
config:
|
|
14
|
+
config: EffectiveConfig;
|
|
17
15
|
/** The host serves the API at all: not AgentCore, reached only through `/invocations`. */
|
|
18
16
|
hostServes: boolean;
|
|
19
|
-
values: ReadonlyMap<string, string>;
|
|
20
17
|
}): DeploymentApi;
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export declare function writeApiKey(agentDir: string): Promise<string | undefined>;
|
|
18
|
+
/** A key for the Agent API, made here: FastAgent mints it, so no person holds it before the deploy. */
|
|
19
|
+
export declare function mintApiKey(): {
|
|
20
|
+
name: string;
|
|
21
|
+
make(): string;
|
|
22
|
+
};
|
|
27
23
|
/**
|
|
28
24
|
* What a deploy says of the API, once the host has said where the deployment is reached (when it knows). The check
|
|
29
25
|
* names the agent's directory when the command runs elsewhere, since that is where `invoke --url` finds the key.
|
|
26
|
+
* `keyInFile`: this machine's production value file holds a key, which `invoke --url` then reads. `run`: the deploy
|
|
27
|
+
* has set the key (or found one) on the target; without it, nothing was set.
|
|
30
28
|
*/
|
|
31
29
|
export declare function apiReport(api: DeploymentApi, where: {
|
|
32
30
|
valueFile: string;
|
|
33
31
|
agentArg: string | undefined;
|
|
32
|
+
keyInFile: boolean;
|
|
33
|
+
run: boolean;
|
|
34
34
|
}, reachedAt: string | undefined): string[];
|
package/dist/deploy/api.js
CHANGED
|
@@ -1,50 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A deployment's Agent API (SPEC.md §9.5, §9.6): served under `/v1` on every host but AgentCore, unless the
|
|
3
|
-
* definition turns it off (`http.api: false`), behind a key
|
|
4
|
-
*
|
|
3
|
+
* definition turns it off (`http.api: false`), behind a key the deploy mints and sets on the target the first time
|
|
4
|
+
* neither the target nor the production value file has one. And what a deploy reports of it.
|
|
5
5
|
*/
|
|
6
6
|
import { randomBytes } from "node:crypto";
|
|
7
7
|
import { API_PREFIX } from "../http/contract.js";
|
|
8
|
-
import { API_KEY_VARIABLE, API_TURNED_OFF,
|
|
9
|
-
import { writeValues } from "../value-file.js";
|
|
8
|
+
import { API_KEY_VARIABLE, API_TURNED_OFF, apiTurnedOff } from "../http/server.js";
|
|
10
9
|
/**
|
|
11
|
-
* Decide the API for one deploy, writing nothing
|
|
12
|
-
*
|
|
13
|
-
* same setting.
|
|
10
|
+
* Decide the API for one deploy, writing nothing. Turned off, it is off on the deployment too, a key the target still
|
|
11
|
+
* holds included: the serve reads the same setting.
|
|
14
12
|
*/
|
|
15
13
|
export function deploymentApi(input) {
|
|
16
|
-
const { config, hostServes
|
|
14
|
+
const { config, hostServes } = input;
|
|
17
15
|
if (!hostServes)
|
|
18
16
|
return { served: false, why: "this host reaches the agent only through its own invocations" };
|
|
19
17
|
if (apiTurnedOff(config))
|
|
20
18
|
return { served: false, why: API_TURNED_OFF };
|
|
21
|
-
return { served: true
|
|
19
|
+
return { served: true };
|
|
22
20
|
}
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* wrote into that git may track.
|
|
27
|
-
*/
|
|
28
|
-
export async function writeApiKey(agentDir) {
|
|
29
|
-
const key = randomBytes(32).toString("base64url");
|
|
30
|
-
// Named in `overwrite`: a line that holds no key (empty, or only commas) is replaced, not kept as set.
|
|
31
|
-
const { unprotectedSecretsDir } = await writeValues(agentDir, "Agent API", [{ name: API_KEY_VARIABLE, value: key }], [API_KEY_VARIABLE]);
|
|
32
|
-
return unprotectedSecretsDir;
|
|
21
|
+
/** A key for the Agent API, made here: FastAgent mints it, so no person holds it before the deploy. */
|
|
22
|
+
export function mintApiKey() {
|
|
23
|
+
return { name: API_KEY_VARIABLE, make: () => randomBytes(32).toString("base64url") };
|
|
33
24
|
}
|
|
34
25
|
/**
|
|
35
26
|
* What a deploy says of the API, once the host has said where the deployment is reached (when it knows). The check
|
|
36
27
|
* names the agent's directory when the command runs elsewhere, since that is where `invoke --url` finds the key.
|
|
28
|
+
* `keyInFile`: this machine's production value file holds a key, which `invoke --url` then reads. `run`: the deploy
|
|
29
|
+
* has set the key (or found one) on the target; without it, nothing was set.
|
|
37
30
|
*/
|
|
38
31
|
export function apiReport(api, where, reachedAt) {
|
|
39
32
|
if (!api.served)
|
|
40
33
|
return [`Agent API: not served — ${api.why}`];
|
|
41
|
-
const { valueFile, agentArg } = where;
|
|
34
|
+
const { valueFile, agentArg, keyInFile, run } = where;
|
|
42
35
|
const base = reachedAt ?? "<the deployment's URL>";
|
|
36
|
+
if (!keyInFile && !run) {
|
|
37
|
+
return [
|
|
38
|
+
`Agent API: ${base}${API_PREFIX}, behind ${API_KEY_VARIABLE}, which --run makes and sets on the target when ` +
|
|
39
|
+
`neither it nor ${valueFile} has one`,
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
if (!keyInFile) {
|
|
43
|
+
return [
|
|
44
|
+
`Agent API: ${base}${API_PREFIX}, behind ${API_KEY_VARIABLE} on the target; ${valueFile} holds none, so pass the key ` +
|
|
45
|
+
`to \`fastagent invoke --url\` with --key`,
|
|
46
|
+
];
|
|
47
|
+
}
|
|
43
48
|
return [
|
|
44
|
-
|
|
45
|
-
? [`Agent API: generated ${API_KEY_VARIABLE} in ${valueFile}; it travels with the other values`]
|
|
46
|
-
: []),
|
|
47
|
-
`Agent API: ${base}${API_PREFIX}, behind ${API_KEY_VARIABLE} in ${valueFile}`,
|
|
49
|
+
`Agent API: ${base}${API_PREFIX}, behind ${API_KEY_VARIABLE} (in ${valueFile})`,
|
|
48
50
|
`check it: fastagent invoke --url ${base} "hello"${agentArg === undefined ? "" : ` ${agentArg}`}`,
|
|
49
51
|
];
|
|
50
52
|
}
|
|
@@ -21,9 +21,11 @@ export interface BoxShell {
|
|
|
21
21
|
wake?(): Promise<void>;
|
|
22
22
|
/**
|
|
23
23
|
* The box's storage root, for a shell that does not carry the server's environment (AgentCore's). Unset: the
|
|
24
|
-
* shell sees `
|
|
24
|
+
* shell sees `FASTAGENT_ROOT` as the server does, or the image's default.
|
|
25
25
|
*/
|
|
26
26
|
storage?: string;
|
|
27
|
+
/** Where the box's image has the agent. Unset: where a generated image has it. */
|
|
28
|
+
program?: string;
|
|
27
29
|
}
|
|
28
30
|
/** A host CLI that runs a command on the box itself: `bin ...args(command)`, run from `cwd`. */
|
|
29
31
|
export declare function processShell(bin: string, args: (command: string) => string[], cwd: string): BoxShell;
|
|
@@ -5,12 +5,12 @@ export interface BuildContextPaths {
|
|
|
5
5
|
machineryPaths: string[];
|
|
6
6
|
/** Credential files that exist right now — a kept ignore file that lets one through bakes it. */
|
|
7
7
|
leakCandidates: string[];
|
|
8
|
-
/**
|
|
8
|
+
/** A state root of the agent's own choosing inside the context, when it exists. */
|
|
9
9
|
stateShips?: string;
|
|
10
|
-
/**
|
|
10
|
+
/** `.fastagent/`, when it exists: the local instances' state and clones, and a build's staging. */
|
|
11
|
+
instancesShip?: string;
|
|
12
|
+
/** `content/`, when it exists: clones in the root, which a host refuses in a release. */
|
|
11
13
|
contentShips?: string;
|
|
12
|
-
/** `context.local.json`, when it exists: the directories of this machine the agent is bound to. */
|
|
13
|
-
bindingsShip?: string;
|
|
14
14
|
/** The agent's node_modules, when it exists. */
|
|
15
15
|
depDirs: string[];
|
|
16
16
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { readdir } from "node:fs/promises";
|
|
2
2
|
import { isAbsolute, relative, join, sep } from "node:path";
|
|
3
3
|
import ignore from "ignore";
|
|
4
|
-
import { AGENT_CONFIG_FILE, CONTENT_DIRNAME,
|
|
5
|
-
import { isGeneratedDockerignore } from "./container.js";
|
|
6
|
-
import { MCP_FILE } from "../mcp/config.js";
|
|
4
|
+
import { AGENT_CONFIG_FILE, CONTENT_DIRNAME, INSTANCES_DIRNAME, SECRETS_DIRNAME, exists, readTextIfExists, resolveSecretsDir, resolveStateRoot, } from "../paths.js";
|
|
5
|
+
import { DELIVERED_CONFIG_DIR, isGeneratedDockerignore } from "./container.js";
|
|
7
6
|
/**
|
|
8
7
|
* "Would docker's packer drop this path?" — built from a `.dockerignore`'s text via the `ignore` matcher (the same
|
|
9
8
|
* library the scaffolded ignore files use), so `!` negation and last-match-wins are the library's problem, not ours.
|
|
@@ -65,10 +64,11 @@ export async function buildContextPaths(agentDir, authPath) {
|
|
|
65
64
|
// State gets the same treatment (a custom in-tree FASTAGENT_STATE_DIR is invisible to the name-based `**/.state`),
|
|
66
65
|
// at warn level.
|
|
67
66
|
const stateRel = inContext(resolveStateRoot(agentDir));
|
|
67
|
+
const inInstances = stateRel !== undefined && (stateRel === INSTANCES_DIRNAME || stateRel.startsWith(`${INSTANCES_DIRNAME}/`));
|
|
68
68
|
// Existence gates the WARNING, never the generated exclude (same split as secretPaths vs leakCandidates).
|
|
69
|
-
const stateShips = stateRel !== undefined && (await exists(join(agentDir, stateRel))) ? stateRel : undefined;
|
|
69
|
+
const stateShips = stateRel !== undefined && !inInstances && (await exists(join(agentDir, stateRel))) ? stateRel : undefined;
|
|
70
|
+
const instancesShip = (await exists(join(agentDir, INSTANCES_DIRNAME))) ? INSTANCES_DIRNAME : undefined;
|
|
70
71
|
const contentShips = (await exists(join(agentDir, CONTENT_DIRNAME))) ? CONTENT_DIRNAME : undefined;
|
|
71
|
-
const bindingsShip = (await exists(join(agentDir, LOCAL_CONTEXT_FILE))) ? LOCAL_CONTEXT_FILE : undefined;
|
|
72
72
|
// The `.env` family at the level fastagent is RESPONSIBLE for: the agent dir's root.
|
|
73
73
|
const envFiles = (await entriesIfExists(agentDir))
|
|
74
74
|
.map((entry) => entry.name)
|
|
@@ -95,13 +95,13 @@ export async function buildContextPaths(agentDir, authPath) {
|
|
|
95
95
|
];
|
|
96
96
|
// Same existence rule: a node_modules that is not there cannot be uploaded.
|
|
97
97
|
const depDirs = await present(["node_modules"]);
|
|
98
|
-
const machineryPaths = [...secretPaths, ...(stateRel ? [stateRel] : [])];
|
|
99
|
-
return { machineryPaths, leakCandidates, stateShips,
|
|
98
|
+
const machineryPaths = [...secretPaths, ...(stateRel && !inInstances ? [stateRel] : [])];
|
|
99
|
+
return { machineryPaths, leakCandidates, stateShips, instancesShip, contentShips, depDirs };
|
|
100
100
|
}
|
|
101
101
|
/** Interrogate BOTH ignore files deploy emits, when the author kept them. */
|
|
102
102
|
export async function checkKeptIgnoreFiles(ctx, report) {
|
|
103
103
|
const { agentDir, force } = ctx;
|
|
104
|
-
const { leakCandidates, stateShips,
|
|
104
|
+
const { leakCandidates, stateShips, instancesShip, contentShips, depDirs } = ctx.paths;
|
|
105
105
|
for (const rel of [".dockerignore", "Dockerfile.dockerignore"]) {
|
|
106
106
|
const kept = await readTextIfExists(join(agentDir, rel));
|
|
107
107
|
if (kept === undefined)
|
|
@@ -121,9 +121,6 @@ export async function checkKeptIgnoreFiles(ctx, report) {
|
|
|
121
121
|
`config (the deployed box has no agent to open and crash-loops). Remove that rule before deploying.`;
|
|
122
122
|
report.issue(text);
|
|
123
123
|
}
|
|
124
|
-
if (excluded(MCP_FILE) && (await exists(join(agentDir, MCP_FILE)))) {
|
|
125
|
-
report.issue(`your ${rel} excludes ${MCP_FILE}; the deployment would lose its MCP servers. Remove that rule before deploying.`);
|
|
126
|
-
}
|
|
127
124
|
// Resolved paths, not spellings: dockerignore patterns are root-anchored (unlike .gitignore).
|
|
128
125
|
const leaks = leakCandidates.filter((p) => !excluded(p));
|
|
129
126
|
if (leaks.length > 0) {
|
|
@@ -134,14 +131,13 @@ export async function checkKeptIgnoreFiles(ctx, report) {
|
|
|
134
131
|
if (stateShips && !excluded(`${stateShips}/sessions`)) {
|
|
135
132
|
report.warn(`your ${rel} (kept) does not exclude \`${stateShips}\` — the build machine's sessions/channel state would ship in the image. ${remedy([`/${stateShips}`])}`);
|
|
136
133
|
}
|
|
134
|
+
if (instancesShip && !excluded(`${instancesShip}/dev/content/x`)) {
|
|
135
|
+
report.warn(`your ${rel} (kept) does not exclude \`${instancesShip}\` — the build machine's sessions, channel state and clones of the agent's content would ship in the image. ${remedy([`/${instancesShip}`, `!/${DELIVERED_CONFIG_DIR}`])}`);
|
|
136
|
+
}
|
|
137
137
|
// An issue, not a warning: a release holding content/ is refused on the host, which links its own there.
|
|
138
138
|
if (contentShips && !excluded(`${contentShips}/x`)) {
|
|
139
139
|
report.issue(`your ${rel} (kept) does not exclude \`${contentShips}\` — this machine's clones of the agent's content would ship in the image. ${remedy([`/${contentShips}`])}`);
|
|
140
140
|
}
|
|
141
|
-
// A warning: the host reads it, and the paths it names (this machine's, the user's name in them) are not there.
|
|
142
|
-
if (bindingsShip && !excluded(bindingsShip)) {
|
|
143
|
-
report.warn(`your ${rel} (kept) does not exclude \`${bindingsShip}\` — the directories of this machine the agent is bound to would ship in the image, and the host would look for them at every start. ${remedy([`/${bindingsShip}`])}`);
|
|
144
|
-
}
|
|
145
141
|
const unexcludedDeps = depDirs.filter((p) => !excluded(`${p}/.package-lock.json`));
|
|
146
142
|
if (unexcludedDeps.length > 0) {
|
|
147
143
|
report.warn(`your ${rel} does not exclude ${unexcludedDeps.map((p) => `\`${p}\``).join(" or ")} — the ` +
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/** HOW A RUNNING CHANNEL IS REACHED — the URL-neutral half of "point this channel at the agent". */
|
|
2
2
|
import type { DeclaredChannel } from "../channels/discover.ts";
|
|
3
3
|
import type { RegistrationOutcome } from "../channels/registration.ts";
|
|
4
|
-
import type
|
|
4
|
+
import { type WebhookKind, type WebhookRegistration } from "../channels/webhooks.ts";
|
|
5
5
|
export interface Registrars {
|
|
6
6
|
telegram: (baseUrl: string) => Promise<RegistrationOutcome>;
|
|
7
|
-
slack?: (baseUrl: string) => Promise<RegistrationOutcome>;
|
|
8
7
|
feishu?: (baseUrl: string, kind: "feishu" | "lark") => Promise<RegistrationOutcome>;
|
|
9
8
|
/**
|
|
10
9
|
* Where a channel's manual step goes beside the log, for a person who reads no log: a coding agent's user, through
|
|
@@ -12,11 +11,6 @@ export interface Registrars {
|
|
|
12
11
|
*/
|
|
13
12
|
leave?: (step: string) => void;
|
|
14
13
|
}
|
|
15
|
-
/**
|
|
16
|
-
* The first-party channels this deployment must point at a URL: webhook ingress, and a kind this tool knows how to
|
|
17
|
-
* instruct.
|
|
18
|
-
*/
|
|
19
|
-
export declare function webhookKinds(channels: readonly DeclaredChannel[]): ChannelKind[];
|
|
20
14
|
/** Default route keys for {@link webhookKinds}, in the same order (the docker plan's ingress note). */
|
|
21
15
|
export declare function webhookPaths(channels: readonly DeclaredChannel[]): string[];
|
|
22
16
|
/**
|
|
@@ -31,7 +25,7 @@ export declare function pointChannelsAt(input: {
|
|
|
31
25
|
registrars: Registrars;
|
|
32
26
|
log: (msg: string) => void;
|
|
33
27
|
}): Promise<{
|
|
34
|
-
kind:
|
|
28
|
+
kind: WebhookKind;
|
|
35
29
|
outcome: RegistrationOutcome;
|
|
36
30
|
}[]>;
|
|
37
31
|
/** Poll a public `/health` until it answers 200; false on timeout. Injected by tests. */
|
|
@@ -70,7 +64,7 @@ export declare function loginGate(input: {
|
|
|
70
64
|
log: (msg: string) => void;
|
|
71
65
|
/** What THIS host does about its webhooks once the box is logged in — the only per-host words in the gate. */
|
|
72
66
|
afterLogin: string;
|
|
73
|
-
/** Where the channels point; unset when the host has
|
|
67
|
+
/** Where the channels point, for the lines that point them by hand; unset when the host has none to print. */
|
|
74
68
|
baseUrl?: string;
|
|
75
69
|
}): string;
|
|
76
70
|
/** {@link pointChannelsAt} plus the shared gate policy, for a command that EXITS. */
|
|
@@ -82,3 +76,35 @@ export declare function registerWebhooks(input: {
|
|
|
82
76
|
/** How THIS host retries — the only per-host words in the gate. */
|
|
83
77
|
retryHint: string;
|
|
84
78
|
}): Promise<string | undefined>;
|
|
79
|
+
/** A box's `/health` webhooks, or undefined while it cannot be read. Injected by tests. */
|
|
80
|
+
export type BoxWebhooksProbe = (healthUrl: string) => Promise<readonly {
|
|
81
|
+
channel: string;
|
|
82
|
+
outcome: WebhookRegistration["outcome"];
|
|
83
|
+
}[] | undefined>;
|
|
84
|
+
/**
|
|
85
|
+
* What a box that points its own channels made of them (channel-setup.md §5): read from its `/health` until no channel
|
|
86
|
+
* is `pending`, then gated as {@link registerWebhooks} gates. Slack's Request URL no credential on the box can set, so
|
|
87
|
+
* `registrars.slack` sets it from this machine, as before.
|
|
88
|
+
*/
|
|
89
|
+
export declare function confirmWebhooks(input: {
|
|
90
|
+
/** Where this machine reaches the box's `/health`: the host's own URL. */
|
|
91
|
+
baseUrl: string;
|
|
92
|
+
/** Where the platforms reach it, the delivered `http.publicUrl` (a custom domain, say): every step names this one. */
|
|
93
|
+
publicUrl: string;
|
|
94
|
+
channels: readonly DeclaredChannel[];
|
|
95
|
+
registrars: Pick<Registrars, "leave">;
|
|
96
|
+
log: (msg: string) => void;
|
|
97
|
+
retryHint: string;
|
|
98
|
+
probe?: BoxWebhooksProbe;
|
|
99
|
+
timeoutMs?: number;
|
|
100
|
+
intervalMs?: number;
|
|
101
|
+
/** At a terminal with a `--config` file: record each URL a person set once the box confirms it. */
|
|
102
|
+
record?: UrlRecorder;
|
|
103
|
+
}): Promise<string | undefined>;
|
|
104
|
+
/** Where a deploy at a terminal records the URL a person set once the box confirms it (`channels.<name>.app.url`). */
|
|
105
|
+
export interface UrlRecorder {
|
|
106
|
+
/** How long to wait for the person to save the URL on the app's page. */
|
|
107
|
+
waitMs: number;
|
|
108
|
+
/** Write `url` into the binding of `kind` in the configuration `--config` names; resolves where it wrote. */
|
|
109
|
+
write(kind: WebhookKind, url: string): Promise<string>;
|
|
110
|
+
}
|