@fastagent-sh/fastagent 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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/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.js +50 -6
- 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/cli/serve.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { ServedAgent, ServeOptions, Service } from "../service.ts";
|
|
2
|
-
import type {
|
|
2
|
+
import type { EffectiveConfig } from "../harnesses/pi-durable/config.ts";
|
|
3
3
|
/** Refuse `--tunnel` with a `--bind` that cloudflared cannot reach. */
|
|
4
4
|
export declare function assertTunnelBindable(host: string | undefined, tunnel: boolean): void;
|
|
5
5
|
/**
|
|
6
6
|
* What the CLI adds to the assembly: its shutdown grace, exit on a connection that drops, and the API's keys, none
|
|
7
7
|
* when the definition turns the API off, whatever key the environment holds (a host's from an earlier deploy).
|
|
8
8
|
*/
|
|
9
|
-
export declare function cliMountOptions(config:
|
|
9
|
+
export declare function cliMountOptions(config: EffectiveConfig): ServeOptions;
|
|
10
10
|
/**
|
|
11
11
|
* Bind, report, warn about reach, open the tunnel, and close in order on a signal — the tail dev's worker and start
|
|
12
12
|
* share once the service is assembled.
|
|
@@ -17,11 +17,10 @@ export declare function serveService(service: ServedAgent, bind: {
|
|
|
17
17
|
}, posture: {
|
|
18
18
|
tunnel: boolean;
|
|
19
19
|
agentDir: string;
|
|
20
|
-
|
|
21
|
-
config: AgentConfig;
|
|
20
|
+
config: EffectiveConfig;
|
|
22
21
|
}): void;
|
|
23
22
|
/** The "we are serving" report: the supervisor message `dev`'s watcher waits for, the addresses, and what mounted. */
|
|
24
|
-
export declare function reportServing(service: ServedAgent, host: string | undefined, boundPort: number, config:
|
|
23
|
+
export declare function reportServing(service: ServedAgent, host: string | undefined, boundPort: number, config: EffectiveConfig): void;
|
|
25
24
|
/** The line that names WHERE the serve is — every posture reports it, including AgentCore's own surface. */
|
|
26
25
|
export declare function bindLine(host: string | undefined, boundPort: number): string;
|
|
27
26
|
/** Listen, and report ready — but only once the SERVICE is, which is not the same moment — until a signal closes it. */
|
package/dist/cli/serve.js
CHANGED
|
@@ -30,10 +30,10 @@ export function cliMountOptions(config) {
|
|
|
30
30
|
*/
|
|
31
31
|
export function serveService(service, bind, posture) {
|
|
32
32
|
const { host } = bind;
|
|
33
|
-
const { tunnel, agentDir,
|
|
33
|
+
const { tunnel, agentDir, config } = posture;
|
|
34
34
|
serve(service, bind, (p) => {
|
|
35
35
|
reportServing(service, host, p, config);
|
|
36
|
-
maybeTunnel(agentDir, service.channels.routes, p, tunnel
|
|
36
|
+
maybeTunnel(agentDir, service.channels.routes, p, tunnel);
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
/** The "we are serving" report: the supervisor message `dev`'s watcher waits for, the addresses, and what mounted. */
|
|
@@ -108,16 +108,13 @@ export function serve(service, bind, onListening) {
|
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
/** Start a Cloudflare tunnel for route channels only. */
|
|
111
|
-
function maybeTunnel(agentDir, routeChannels, boundPort, tunnel
|
|
111
|
+
function maybeTunnel(agentDir, routeChannels, boundPort, tunnel) {
|
|
112
112
|
if (!tunnel || process.env.FASTAGENT_DEV_WORKER === "1")
|
|
113
113
|
return;
|
|
114
114
|
void startCloudflareTunnel(boundPort).then((instance) => {
|
|
115
115
|
if (!instance)
|
|
116
116
|
return;
|
|
117
|
-
void announceWebhooks(agentDir, instance.url, declaredChannels(routeChannels), {
|
|
118
|
-
openUrl: openExternalUrl,
|
|
119
|
-
stateRoot,
|
|
120
|
-
});
|
|
117
|
+
void announceWebhooks(agentDir, instance.url, declaredChannels(routeChannels), { openUrl: openExternalUrl });
|
|
121
118
|
const cleanup = () => instance.close();
|
|
122
119
|
process.once("SIGINT", cleanup);
|
|
123
120
|
process.once("SIGTERM", cleanup);
|
package/dist/cli/shared.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { type LoginIO } from "../harnesses/models/login.ts";
|
|
2
2
|
import { type ModelEnvironment } from "../harnesses/models/environment.ts";
|
|
3
3
|
import type { OpenedServing } from "../harnesses/pi-durable/serve.ts";
|
|
4
|
+
import { type Project } from "../harnesses/pi-durable/open.ts";
|
|
5
|
+
import { type InstanceFlags } from "./instance.ts";
|
|
6
|
+
/** The project a command runs, when it is not the development one (`start` runs the build). */
|
|
7
|
+
type LoadProject = (agentDir: string) => Promise<Project>;
|
|
4
8
|
/** An agent directory a command entered, and the default model that then resolves, if any. */
|
|
5
9
|
export interface EnteredAgent {
|
|
6
10
|
agentDir: string;
|
|
@@ -15,7 +19,8 @@ export interface EnteredAgent {
|
|
|
15
19
|
export declare function enterAgentDirectory(dirArg: string, opts: {
|
|
16
20
|
model?: string;
|
|
17
21
|
input?: boolean;
|
|
18
|
-
|
|
22
|
+
instance?: InstanceFlags;
|
|
23
|
+
}, before?: (agentDir: string) => Promise<void>, load?: LoadProject): Promise<EnteredAgent>;
|
|
19
24
|
/**
|
|
20
25
|
* How every command that RUNS the agent on this machine enters it (dev, start, invoke, schedules run):
|
|
21
26
|
* {@link enterAgentDirectory}, plus a default model. The agent opens without one, but a process here would then fail
|
|
@@ -24,7 +29,8 @@ export declare function enterAgentDirectory(dirArg: string, opts: {
|
|
|
24
29
|
export declare function enterAgentCommand(dirArg: string, opts: {
|
|
25
30
|
model?: string;
|
|
26
31
|
input?: boolean;
|
|
27
|
-
|
|
32
|
+
instance?: InstanceFlags;
|
|
33
|
+
}, before?: (agentDir: string) => Promise<void>, load?: LoadProject): Promise<Required<EnteredAgent>>;
|
|
28
34
|
/** What `dev` and `start` say about the agent they just opened, in the order they say it. */
|
|
29
35
|
export declare function reportAssembly(opened: OpenedServing, modelSpec: string, extras?: [label: string, value: string][]): Promise<void>;
|
|
30
36
|
/** Both stdin and stdout are a terminal — the precondition for an interactive prompt. */
|
|
@@ -38,7 +44,10 @@ export declare function parsePort(value: string | undefined, source: string, fro
|
|
|
38
44
|
export declare function parseBind(value: string | undefined): string | undefined;
|
|
39
45
|
/** Report which source provides the model's credentials, surfacing a remediation hint at startup. */
|
|
40
46
|
export declare function reportAuth(models: ModelEnvironment, modelSpec: string, agentDir: string): Promise<void>;
|
|
47
|
+
/** The line of `fastagent.config.ts` that keeps a picked model: what the picker prints, and what it must accept. */
|
|
48
|
+
export declare function pickedModelLine(model: string): string;
|
|
41
49
|
/** Login terminal IO via @clack/prompts: a searchable list once long, a hidden prompt for keys. */
|
|
42
50
|
export declare function terminalLoginIO(): LoginIO;
|
|
43
51
|
/** A copy/paste-safe POSIX shell argument for a command the CLI prints. */
|
|
44
52
|
export declare function shellArg(value: string): string;
|
|
53
|
+
export {};
|
package/dist/cli/shared.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Helpers shared across command modules: interactivity gates, port parsing, the startup auth report, first-run model
|
|
3
3
|
* resolution, and the login terminal IO.
|
|
4
4
|
*/
|
|
5
|
+
import { definedValues } from "../declared-secrets.js";
|
|
5
6
|
import { resolve } from "node:path";
|
|
6
7
|
import { autocomplete, isCancel, log as clackLog, password, select, text as clackText } from "@clack/prompts";
|
|
7
8
|
import { buildModelPickerOptions } from "./models-view.js";
|
|
@@ -9,12 +10,13 @@ import { loginFlow } from "../harnesses/models/login.js";
|
|
|
9
10
|
import { SignInCancelled } from "../sign-in-api.js";
|
|
10
11
|
import { modelEnvironment } from "../harnesses/models/environment.js";
|
|
11
12
|
import { listModels, providerAuthStatuses, providerOf } from "../harnesses/models/models.js";
|
|
12
|
-
import {
|
|
13
|
+
import { resolveConfig } from "../harnesses/pi-durable/config.js";
|
|
14
|
+
import { buildDefinitionShape, definitionProviders, loadDevProject, modelSpecFor, } from "../harnesses/pi-durable/open.js";
|
|
13
15
|
import { formatAuthReport } from "./auth-view.js";
|
|
14
16
|
import { contentLines } from "./content-view.js";
|
|
15
17
|
import { deployedHost, isDeployedWorkspace } from "../paths.js";
|
|
16
18
|
import { log } from "../log.js";
|
|
17
|
-
import {
|
|
19
|
+
import { enterInstance } from "./instance.js";
|
|
18
20
|
import { dotEnvPath } from "../value-file.js";
|
|
19
21
|
import { openExternalUrl } from "../open-url.js";
|
|
20
22
|
import { bindAddress, isBindAddress } from "../bind.js";
|
|
@@ -25,11 +27,11 @@ import { agentDirOrExit, failStartup, failUsage } from "./fail.js";
|
|
|
25
27
|
* gates a missing model itself, by what will ship. `before`: what the command checks of the directory before a model
|
|
26
28
|
* is asked for (`schedules run`: that the schedule it names exists).
|
|
27
29
|
*/
|
|
28
|
-
export async function enterAgentDirectory(dirArg, opts, before) {
|
|
30
|
+
export async function enterAgentDirectory(dirArg, opts, before, load) {
|
|
29
31
|
const agentDir = agentDirOrExit(resolve(dirArg));
|
|
30
|
-
|
|
32
|
+
enterInstance(agentDir, opts.instance);
|
|
31
33
|
await before?.(agentDir);
|
|
32
|
-
const modelSpec = await resolveFirstRunModel(agentDir, opts);
|
|
34
|
+
const modelSpec = await resolveFirstRunModel(agentDir, opts, load);
|
|
33
35
|
return { agentDir, ...(modelSpec ? { modelSpec } : {}) };
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
@@ -37,11 +39,11 @@ export async function enterAgentDirectory(dirArg, opts, before) {
|
|
|
37
39
|
* {@link enterAgentDirectory}, plus a default model. The agent opens without one, but a process here would then fail
|
|
38
40
|
* every new conversation, so it stops at startup instead: exit 2, the choice a terminal's picker would have asked for.
|
|
39
41
|
*/
|
|
40
|
-
export async function enterAgentCommand(dirArg, opts, before) {
|
|
41
|
-
const entered = await enterAgentDirectory(dirArg, opts, before);
|
|
42
|
+
export async function enterAgentCommand(dirArg, opts, before, load) {
|
|
43
|
+
const entered = await enterAgentDirectory(dirArg, opts, before, load);
|
|
42
44
|
const { modelSpec } = entered;
|
|
43
45
|
if (!modelSpec) {
|
|
44
|
-
failUsage(`missing model: set --model, "model" in fastagent.config.ts, or FASTAGENT_MODEL (e.g. "openai/gpt-5.5"; ` +
|
|
46
|
+
failUsage(`missing model: set --model, "defaults.model" in fastagent.config.ts, or FASTAGENT_MODEL (e.g. "openai/gpt-5.5"; ` +
|
|
45
47
|
"`fastagent models` lists them)");
|
|
46
48
|
}
|
|
47
49
|
return { ...entered, modelSpec };
|
|
@@ -52,11 +54,11 @@ function reportLine(label, value) {
|
|
|
52
54
|
}
|
|
53
55
|
/** What `dev` and `start` say about the agent they just opened, in the order they say it. */
|
|
54
56
|
export async function reportAssembly(opened, modelSpec, extras = []) {
|
|
55
|
-
const {
|
|
56
|
-
reportLine("agent", `${
|
|
57
|
+
const { code, models, agentDir, workdir, stateRoot, config } = opened;
|
|
58
|
+
reportLine("agent", `${opened.name} — ${workdir}`);
|
|
57
59
|
for (const [label, value] of contentLines(opened.content))
|
|
58
60
|
reportLine(label, value);
|
|
59
|
-
reportLine("model", `${modelSpec}${
|
|
61
|
+
reportLine("model", `${modelSpec}${config.thinkingLevel ? ` (thinking: ${config.thinkingLevel})` : ""}`);
|
|
60
62
|
await reportAuth(models, modelSpec, agentDir);
|
|
61
63
|
const names = (list) => list.map(({ name }) => name).join(", ") || "(none)";
|
|
62
64
|
reportLine("tools", names(code.tools));
|
|
@@ -126,24 +128,35 @@ export async function reportAuth(models, modelSpec, agentDir) {
|
|
|
126
128
|
* The default model, for every command that runs the agent: `--model` > `FASTAGENT_MODEL` > `fastagent.config.ts`. With
|
|
127
129
|
* none and a terminal, the first-run picker chooses one for this run (and logs in to its provider when needed).
|
|
128
130
|
*/
|
|
129
|
-
async function resolveFirstRunModel(agentDir, options = {}) {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
async function resolveFirstRunModel(agentDir, options = {}, load) {
|
|
132
|
+
const configured = async () => {
|
|
133
|
+
const project = await (load ?? loadDevProject)(agentDir);
|
|
134
|
+
return { project, config: resolveConfig(project.definition, project.instance) };
|
|
135
|
+
};
|
|
136
|
+
// A custom loader's refusal is the caller's to report: AgentCore runs this inside a request, where an exit kills the container.
|
|
137
|
+
const { project, config } = await (load === undefined ? configured().catch(failStartup) : configured());
|
|
138
|
+
const spec = modelSpecFor(config, options.model);
|
|
139
|
+
if (spec)
|
|
140
|
+
return spec;
|
|
134
141
|
if (options.input === false)
|
|
135
142
|
return undefined; // --no-input: never prompt (clig) — the caller decides what's missing
|
|
136
143
|
if (!isInteractive())
|
|
137
144
|
return undefined; // CI/deploy: the caller raises the actionable missing-model error
|
|
138
|
-
const providers =
|
|
139
|
-
|
|
145
|
+
const providers = load === undefined
|
|
146
|
+
? await definitionProviders(agentDir, process.env).catch(failStartup)
|
|
147
|
+
: buildDefinitionShape(project.definition, process.env).providers;
|
|
148
|
+
const environment = await modelEnvironment(agentDir, {}, providers, { environment: definedValues(process.env) });
|
|
140
149
|
const chosen = await pickWithCredentials(environment.models, environment.auth?.path, providers);
|
|
141
150
|
if (chosen === undefined)
|
|
142
151
|
return undefined; // cancelled (or auth probe failed): the caller raises its clear error
|
|
143
152
|
process.env.FASTAGENT_MODEL = chosen; // this process + any spawned dev worker inherits it
|
|
144
|
-
console.error(`[fastagent] picked ${chosen} — set
|
|
153
|
+
console.error(`[fastagent] picked ${chosen} — set \`${pickedModelLine(chosen)}\` in fastagent.config.ts to keep it`);
|
|
145
154
|
return chosen;
|
|
146
155
|
}
|
|
156
|
+
/** The line of `fastagent.config.ts` that keeps a picked model: what the picker prints, and what it must accept. */
|
|
157
|
+
export function pickedModelLine(model) {
|
|
158
|
+
return `defaults: { model: ${JSON.stringify(model)} }`;
|
|
159
|
+
}
|
|
147
160
|
/** The credential-aware pick: full catalog annotated per provider, then the post-pick auth policy. */
|
|
148
161
|
async function pickWithCredentials(models, authPath, providers) {
|
|
149
162
|
let statuses;
|
package/dist/cli/ticket.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* there is something for the person to do. The command's `--check <ticket>` reads where it stands from any process,
|
|
7
7
|
* and hands the step the address a browser ended on when that browser could not reach this machine.
|
|
8
8
|
*
|
|
9
|
-
* A ticket is a file in the machine's own state, ~/.fastagent/.state/tickets, written whole
|
|
10
|
-
* the starting command until the step starts, then the step
|
|
9
|
+
* A ticket is a file in the machine's own state, ~/.fastagent/.state/tickets, written whole (`writeFileAtomic`), but by
|
|
10
|
+
* more than one process at once: the starting command until the step starts, then the step, and whoever finds its
|
|
11
|
+
* process gone or replaces it (every `--check`, a newer step for the subject). One that has
|
|
11
12
|
* ended stays as it ended. A newer step for the same subject stops an older one still waiting on its person, whose
|
|
12
13
|
* process would otherwise hold what it holds (a sign-in's callback port, OpenAI's fixed) until it expires; so does one
|
|
13
14
|
* still getting ready (reaching a box, opening a tunnel), which may hang there. One finishing what its person did (a
|
|
@@ -18,9 +19,10 @@
|
|
|
18
19
|
* stopped with it, by `stop` and when it exits.
|
|
19
20
|
*/
|
|
20
21
|
import { randomBytes } from "node:crypto";
|
|
21
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
22
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, rmSync, statSync, } from "node:fs";
|
|
22
23
|
import { spawn } from "node:child_process";
|
|
23
24
|
import { join } from "node:path";
|
|
25
|
+
import { stripVTControlCharacters } from "node:util";
|
|
24
26
|
import { writeFileAtomic } from "../atomic-write.js";
|
|
25
27
|
import { STATE_DIRNAME, globalHome } from "../paths.js";
|
|
26
28
|
import { recordFailures } from "./fail.js";
|
|
@@ -50,7 +52,12 @@ function readTicket(id) {
|
|
|
50
52
|
throw error;
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
|
-
|
|
55
|
+
/**
|
|
56
|
+
* A ticket is written by several processes: the command that started the step, the step's own process, and every
|
|
57
|
+
* `--check`. Each writes a temp of its own (`sharedWriters`): the fixed name is for one writer, and two of them rename
|
|
58
|
+
* each other's file away or write one temp at once, which a reader sees as `Unexpected non-whitespace character after JSON`.
|
|
59
|
+
*/
|
|
60
|
+
const writeTicket = (ticket) => writeFileAtomic(ticketPath(ticket.ticket), JSON.stringify(ticket), undefined, true);
|
|
54
61
|
const waiting = (ticket) => ticket.status === "starting" || ticket.status === "need_user_action" || ticket.status === "working";
|
|
55
62
|
/**
|
|
56
63
|
* Change the ticket `id` while its step still runs; one that has ended is returned as it ended. A writer that read it
|
|
@@ -246,7 +253,7 @@ export async function checkTicket(id, address) {
|
|
|
246
253
|
if (ticket === undefined)
|
|
247
254
|
return undefined;
|
|
248
255
|
if (address !== undefined && ticket.status === "need_user_action") {
|
|
249
|
-
writeFileAtomic(addressPath(id), address);
|
|
256
|
+
writeFileAtomic(addressPath(id), address, undefined, true);
|
|
250
257
|
for (const deadline = Date.now() + ADDRESS_TIMEOUT_MS; Date.now() < deadline;) {
|
|
251
258
|
ticket = readTicket(id);
|
|
252
259
|
if (ticket.status !== "need_user_action" || !running(ticket.pid))
|
|
@@ -385,6 +392,33 @@ async function awaitAddress(id, signal) {
|
|
|
385
392
|
}
|
|
386
393
|
throw new Error("the step no longer waits for an address");
|
|
387
394
|
}
|
|
395
|
+
const LOG_TAIL_LINES = 12;
|
|
396
|
+
const LOG_TAIL_BYTES = 8192;
|
|
397
|
+
const LOG_LINE_CHARS = 300;
|
|
398
|
+
/**
|
|
399
|
+
* The last lines of a ticket's log, without terminal colors: where a failed step says why. A coding agent reads a
|
|
400
|
+
* report once, and "see the output above" points at a log it has not opened.
|
|
401
|
+
*/
|
|
402
|
+
function logTail(id) {
|
|
403
|
+
const path = ticketLogPath(id);
|
|
404
|
+
if (!existsSync(path))
|
|
405
|
+
return [];
|
|
406
|
+
const size = statSync(path).size;
|
|
407
|
+
const start = Math.max(0, size - LOG_TAIL_BYTES);
|
|
408
|
+
const fd = openSync(path, "r");
|
|
409
|
+
try {
|
|
410
|
+
const buffer = Buffer.alloc(size - start);
|
|
411
|
+
readSync(fd, buffer, 0, buffer.length, start);
|
|
412
|
+
return stripVTControlCharacters(buffer.toString("utf8"))
|
|
413
|
+
.split("\n")
|
|
414
|
+
.map((line) => line.trim().slice(0, LOG_LINE_CHARS))
|
|
415
|
+
.filter((line) => line !== "")
|
|
416
|
+
.slice(-LOG_TAIL_LINES);
|
|
417
|
+
}
|
|
418
|
+
finally {
|
|
419
|
+
closeSync(fd);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
388
422
|
/**
|
|
389
423
|
* A ticket as its command reports it, with what to do next (`agent_next_steps`): one JSON object, or its lines for a
|
|
390
424
|
* person. `done` is what the step's `result` adds to the report, beside a sentence for the agent.
|
|
@@ -412,17 +446,27 @@ export function ticketReport(ticket, when, done = (result) => ({ fields: result,
|
|
|
412
446
|
const message = ticket.status === "expired" ? "it was not finished in time" : ticket.message;
|
|
413
447
|
// What the step reached before it ended is still the person's: where to get the value it stopped for, say.
|
|
414
448
|
const reached = left.length === 0 ? "" : `Tell the user what the steps it reached leave them: ${left.join(" ")} `;
|
|
449
|
+
// An expired step is not a fault: nobody finished it in time, and its log says nothing more.
|
|
450
|
+
const expired = ticket.status === "expired";
|
|
451
|
+
const tail = expired ? [] : logTail(ticket.ticket);
|
|
415
452
|
return {
|
|
416
453
|
json: {
|
|
417
454
|
...base,
|
|
418
455
|
message,
|
|
419
456
|
log,
|
|
457
|
+
...(tail.length === 0 ? {} : { log_tail: tail }),
|
|
420
458
|
...(ticket.afterwards ? { afterwards: ticket.afterwards } : {}),
|
|
421
|
-
agent_next_steps:
|
|
459
|
+
agent_next_steps: expired
|
|
460
|
+
? `${reached}The step was not finished in time, which is not a fault: ask the user to complete it as soon ` +
|
|
461
|
+
`as you run: ${ticket.restart} --json`
|
|
462
|
+
: `${reached}The cause is in log_tail (the whole log is at log): fix what it names, then run: ` +
|
|
463
|
+
`${ticket.restart} --json. The same inputs fail the same way unless the log shows a transient network or ` +
|
|
464
|
+
`builder error.`,
|
|
422
465
|
},
|
|
423
466
|
text: [
|
|
424
467
|
`${ticket.status}: ${message}`,
|
|
425
468
|
`log: ${log}`,
|
|
469
|
+
...tail.map((line) => ` | ${line}`),
|
|
426
470
|
...left.map((line) => `left for the person: ${line}`),
|
|
427
471
|
`to try again: ${ticket.restart}`,
|
|
428
472
|
],
|
|
@@ -1,62 +1,56 @@
|
|
|
1
|
-
/** One content entry as
|
|
2
|
-
export
|
|
1
|
+
/** One content entry as a file holds it, under its name: a repository, or a directory of this machine. */
|
|
2
|
+
export type ContentEntry = {
|
|
3
3
|
/** The repository, `owner/repo`. */
|
|
4
4
|
github: string;
|
|
5
5
|
/** The branch, tag or commit to clone. */
|
|
6
6
|
ref?: string;
|
|
7
7
|
/** One sentence for the agent: what this is, and how to treat it. */
|
|
8
8
|
description?: string;
|
|
9
|
-
}
|
|
9
|
+
} | {
|
|
10
|
+
/** Absolute, `~/`, or relative to the instance configuration. */
|
|
11
|
+
path: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
};
|
|
10
14
|
/** A declared entry, read. */
|
|
11
|
-
export
|
|
15
|
+
export type DeclaredContent = {
|
|
12
16
|
name: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
/** The input that declared it. */
|
|
19
|
+
origin: "program" | "instance";
|
|
20
|
+
} & ({
|
|
21
|
+
kind: "github";
|
|
13
22
|
/** `owner/repo`. */
|
|
14
23
|
repo: string;
|
|
15
24
|
ref?: string;
|
|
16
|
-
description?: string;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* One binding as `context.local.json` holds it, under its name: a directory of this machine. Under the name of an
|
|
20
|
-
* entry `context.json` declares it is this machine's checkout of that repository, and carries only `path`.
|
|
21
|
-
*/
|
|
22
|
-
export interface ContentBinding {
|
|
23
|
-
/** Absolute, `~/`, or relative to the agent directory. */
|
|
24
|
-
path: string;
|
|
25
|
-
description?: string;
|
|
26
|
-
}
|
|
27
|
-
/** A binding, read: `path` absolute. */
|
|
28
|
-
export type BoundContent = {
|
|
29
|
-
name: string;
|
|
30
|
-
path: string;
|
|
31
|
-
} & ({
|
|
32
|
-
/** This machine's checkout of the declared entry of the same name. */
|
|
33
|
-
kind: "checkout";
|
|
34
25
|
} | {
|
|
35
|
-
/** A directory
|
|
26
|
+
/** A directory of this machine, `path` absolute. */
|
|
36
27
|
kind: "local";
|
|
37
|
-
|
|
28
|
+
path: string;
|
|
38
29
|
});
|
|
39
|
-
/**
|
|
40
|
-
export interface
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
/** Where a `content` map was read from, for what a refusal says and how a path is read. */
|
|
31
|
+
export interface ContentSource {
|
|
32
|
+
origin: DeclaredContent["origin"];
|
|
33
|
+
/** The file's name, or what an instance configuration is called. */
|
|
34
|
+
file: string;
|
|
35
|
+
/** What a relative path is relative to: the instance configuration's directory. None for one that is no file. */
|
|
36
|
+
base?: string;
|
|
43
37
|
}
|
|
44
38
|
/** Whether `name` can name a content entry. */
|
|
45
39
|
export declare function isContentName(name: string): boolean;
|
|
46
40
|
/** Whether `repo` names a GitHub repository, `owner/repo`. */
|
|
47
41
|
export declare function isGithubRepo(repo: string): boolean;
|
|
48
|
-
/** `entry` with its keys in
|
|
42
|
+
/** `entry` with its keys in the order they are written. */
|
|
49
43
|
export declare function canonicalEntry(entry: ContentEntry): ContentEntry;
|
|
50
|
-
/** `binding` with its keys in {@link BINDING_KEYS} order: the form written. */
|
|
51
|
-
export declare function canonicalBinding(binding: ContentBinding): ContentBinding;
|
|
52
|
-
/** Read `context.json`'s `content` map (undefined is none). Every refusal names the entry; nothing is defaulted. */
|
|
53
|
-
export declare function declareContent(raw: unknown): DeclaredContent[];
|
|
54
44
|
/**
|
|
55
|
-
* Read
|
|
56
|
-
*
|
|
57
|
-
|
|
45
|
+
* Read a `content` map (undefined is none) from `from`. Every refusal names the entry; nothing is defaulted. The
|
|
46
|
+
* program's declares repositories only, since it is shared; an instance's may name a directory too.
|
|
47
|
+
*/
|
|
48
|
+
export declare function declareContent(raw: unknown, from: ContentSource): DeclaredContent[];
|
|
49
|
+
/**
|
|
50
|
+
* The program's entries and the instance's, as one list. An instance only adds: a name both declare (ignoring case,
|
|
51
|
+
* as names are a directory) is refused, naming both, so nothing is shadowed.
|
|
58
52
|
*/
|
|
59
|
-
export declare function
|
|
53
|
+
export declare function joinContent(program: readonly DeclaredContent[], instance: readonly DeclaredContent[]): DeclaredContent[];
|
|
60
54
|
/**
|
|
61
55
|
* A directory the agent is bound to and the agent directory are kept apart (agent-model.md §2): a definition is
|
|
62
56
|
* released, content is kept, and one directory cannot be both. The ONE statement of the rule; resolve.ts asks
|
package/dist/content/declare.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WHAT AN
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* is
|
|
2
|
+
* WHAT AN AGENT WORKS ON, declared and refused in ONE place (file.ts reads the files through it): the `content` map of
|
|
3
|
+
* the program's `context.json`, which holds only repositories every instance reaches, and the `content` map of an
|
|
4
|
+
* instance's `fastagent.json`, which adds repositories and directories of its own. An instance only adds: a name the
|
|
5
|
+
* program declares is not one it may take. Pure: what is on the disk is resolve.ts's question.
|
|
6
|
+
* docs/design/agent-artifact.md §3.3 and §4.2 are the rule.
|
|
6
7
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { CONTEXT_FILE, LOCAL_CONTEXT_FILE, isUnderDir } from "../paths.js";
|
|
8
|
+
import { isAbsolute } from "node:path";
|
|
9
|
+
import { CONTEXT_FILE, configurationPath, isUnderDir } from "../paths.js";
|
|
10
10
|
/**
|
|
11
|
-
* The keys an entry
|
|
12
|
-
*
|
|
11
|
+
* The keys an entry carries, in the order they are written: where it is, then how it is treated. One list each, so a
|
|
12
|
+
* key the reader accepts is one the writer keeps.
|
|
13
13
|
*/
|
|
14
|
-
const
|
|
15
|
-
const
|
|
14
|
+
const GITHUB_KEYS = ["github", "ref", "description"];
|
|
15
|
+
const PATH_KEYS = ["path", "description"];
|
|
16
16
|
/** A name becomes a directory name, so it is one path segment (the spelling a release's agent name has). */
|
|
17
17
|
const NAME = /^[A-Za-z0-9_-]+$/;
|
|
18
18
|
/** Whether `name` can name a content entry. */
|
|
@@ -24,13 +24,9 @@ const GITHUB_REPO = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
|
24
24
|
export function isGithubRepo(repo) {
|
|
25
25
|
return GITHUB_REPO.test(repo);
|
|
26
26
|
}
|
|
27
|
-
/** `entry` with its keys in
|
|
27
|
+
/** `entry` with its keys in the order they are written. */
|
|
28
28
|
export function canonicalEntry(entry) {
|
|
29
|
-
return pick(entry,
|
|
30
|
-
}
|
|
31
|
-
/** `binding` with its keys in {@link BINDING_KEYS} order: the form written. */
|
|
32
|
-
export function canonicalBinding(binding) {
|
|
33
|
-
return pick(binding, BINDING_KEYS);
|
|
29
|
+
return pick(entry, "github" in entry ? GITHUB_KEYS : PATH_KEYS);
|
|
34
30
|
}
|
|
35
31
|
function pick(value, keys) {
|
|
36
32
|
const out = {};
|
|
@@ -41,42 +37,26 @@ function pick(value, keys) {
|
|
|
41
37
|
}
|
|
42
38
|
return out;
|
|
43
39
|
}
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Read a `content` map (undefined is none) from `from`. Every refusal names the entry; nothing is defaulted. The
|
|
42
|
+
* program's declares repositories only, since it is shared; an instance's may name a directory too.
|
|
43
|
+
*/
|
|
44
|
+
export function declareContent(raw, from) {
|
|
45
|
+
return entriesByName(raw).map(([name, entry]) => declareOne(name, entry, from));
|
|
47
46
|
}
|
|
48
47
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* machine alone.
|
|
48
|
+
* The program's entries and the instance's, as one list. An instance only adds: a name both declare (ignoring case,
|
|
49
|
+
* as names are a directory) is refused, naming both, so nothing is shadowed.
|
|
52
50
|
*/
|
|
53
|
-
export function
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const path = absolutePath(e.path, agentDir);
|
|
60
|
-
const shared = declared.find((entry) => entry.name.toLowerCase() === name.toLowerCase());
|
|
61
|
-
if (shared !== undefined) {
|
|
62
|
-
if (shared.name !== name) {
|
|
63
|
-
throw new Error(`${at} binds the entry ${CONTEXT_FILE} names "${shared.name}": spell it the same`);
|
|
64
|
-
}
|
|
65
|
-
if (e.description !== undefined) {
|
|
66
|
-
throw new Error(`${at} binds this machine's checkout of the entry ${CONTEXT_FILE} declares, which says "description" for ` +
|
|
67
|
-
`every instance: give it only "path"`);
|
|
68
|
-
}
|
|
69
|
-
return { name, path, kind: "checkout" };
|
|
51
|
+
export function joinContent(program, instance) {
|
|
52
|
+
for (const added of instance) {
|
|
53
|
+
const taken = program.find((entry) => entry.name.toLowerCase() === added.name.toLowerCase());
|
|
54
|
+
if (taken !== undefined) {
|
|
55
|
+
throw new Error(`content "${added.name}" is declared by the program (${CONTEXT_FILE} names "${taken.name}") and by the ` +
|
|
56
|
+
`instance: an instance adds to the program's content and does not replace it — name it otherwise`);
|
|
70
57
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
/** `path` as written in `context.local.json`, absolute: `~/` is the home directory, a relative one the agent's. */
|
|
76
|
-
function absolutePath(path, agentDir) {
|
|
77
|
-
if (path === "~" || path.startsWith("~/"))
|
|
78
|
-
return join(homedir(), path.slice(1));
|
|
79
|
-
return isAbsolute(path) ? resolve(path) : resolve(agentDir, path);
|
|
58
|
+
}
|
|
59
|
+
return [...program, ...instance];
|
|
80
60
|
}
|
|
81
61
|
/** The entries of a `content` map, each name checked, unique ignoring case. */
|
|
82
62
|
function entriesByName(raw) {
|
|
@@ -110,24 +90,43 @@ function objectWithKeys(at, value, keys) {
|
|
|
110
90
|
}
|
|
111
91
|
return e;
|
|
112
92
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
93
|
+
/** `path` as written in an instance configuration, absolute: `~/` is the home directory, a relative one `base`'s. */
|
|
94
|
+
function absolutePath(path, from, at) {
|
|
95
|
+
const absolute = configurationPath(path, from.base);
|
|
96
|
+
if (absolute === undefined) {
|
|
97
|
+
throw new Error(`${at}: "path" must be absolute here, since ${from.file} is not a file its directory could anchor`);
|
|
116
98
|
}
|
|
117
|
-
return
|
|
99
|
+
return absolute;
|
|
118
100
|
}
|
|
119
|
-
function declareOne(name, entry) {
|
|
101
|
+
function declareOne(name, entry, from) {
|
|
120
102
|
const at = `content "${name}"`;
|
|
121
|
-
const e = objectWithKeys(at, entry,
|
|
122
|
-
|
|
103
|
+
const e = objectWithKeys(at, entry, [...GITHUB_KEYS, "path"]);
|
|
104
|
+
if (from.origin === "program" && e.path !== undefined) {
|
|
105
|
+
throw new Error(`${at}: ${from.file} is shared, so it declares only the repositories every instance reaches — add a directory ` +
|
|
106
|
+
`to the instance's configuration instead (fastagent content add <dir>)`);
|
|
107
|
+
}
|
|
108
|
+
for (const key of ["github", "ref", "path"]) {
|
|
123
109
|
if (e[key] !== undefined && (typeof e[key] !== "string" || e[key] === "")) {
|
|
124
110
|
throw new Error(`${at}: "${key}" must be a non-empty string`);
|
|
125
111
|
}
|
|
126
112
|
}
|
|
127
|
-
|
|
113
|
+
if (e.description !== undefined && (typeof e.description !== "string" || e.description === "")) {
|
|
114
|
+
throw new Error(`${at}: "description" must be a non-empty string`);
|
|
115
|
+
}
|
|
116
|
+
const base = {
|
|
117
|
+
name,
|
|
118
|
+
origin: from.origin,
|
|
119
|
+
...(e.description !== undefined ? { description: e.description } : {}),
|
|
120
|
+
};
|
|
121
|
+
if (e.github !== undefined && e.path !== undefined)
|
|
122
|
+
throw new Error(`${at} names both "github" and "path": give one`);
|
|
123
|
+
if (e.path !== undefined) {
|
|
124
|
+
if (e.ref !== undefined)
|
|
125
|
+
throw new Error(`${at}: "ref" applies to a repository, and ${at} is a directory`);
|
|
126
|
+
return { ...base, kind: "local", path: absolutePath(e.path, from, at) };
|
|
127
|
+
}
|
|
128
128
|
if (e.github === undefined) {
|
|
129
|
-
throw new Error(`${at} needs "github"
|
|
130
|
-
`directory of this machine in ${LOCAL_CONTEXT_FILE} instead (fastagent content add <dir>)`);
|
|
129
|
+
throw new Error(`${at} needs "github"${from.origin === "instance" ? ' or "path"' : ""}`);
|
|
131
130
|
}
|
|
132
131
|
const repo = e.github;
|
|
133
132
|
if (!isGithubRepo(repo))
|
|
@@ -136,7 +135,7 @@ function declareOne(name, entry) {
|
|
|
136
135
|
// git reads a leading "-" as an option, and no branch, tag or commit name has one.
|
|
137
136
|
if (ref?.startsWith("-"))
|
|
138
137
|
throw new Error(`${at}: "ref" must name a branch, tag or commit, got "${ref}"`);
|
|
139
|
-
return {
|
|
138
|
+
return { ...base, kind: "github", repo, ...(ref !== undefined ? { ref } : {}) };
|
|
140
139
|
}
|
|
141
140
|
/**
|
|
142
141
|
* A directory the agent is bound to and the agent directory are kept apart (agent-model.md §2): a definition is
|