@fastagent-sh/fastagent 0.29.0 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/atomic-write.d.ts +4 -5
- package/dist/atomic-write.js +4 -5
- package/dist/authoring.d.ts +32 -15
- package/dist/authoring.js +147 -127
- package/dist/channels/agentcore-protocol.d.ts +8 -3
- package/dist/channels/agentcore-protocol.js +3 -1
- package/dist/channels/agentcore-service.d.ts +1 -0
- package/dist/channels/agentcore-service.js +14 -4
- package/dist/channels/agentcore.d.ts +9 -0
- package/dist/channels/agentcore.js +29 -4
- package/dist/channels/context.d.ts +2 -0
- package/dist/channels/context.js +5 -1
- package/dist/channels/feishu/feishu-api.d.ts +2 -1
- package/dist/channels/feishu/feishu.d.ts +0 -3
- package/dist/channels/feishu/feishu.js +10 -10
- package/dist/channels/feishu/register-app.js +3 -5
- package/dist/channels/feishu/scaffold/channel.ts +32 -34
- package/dist/channels/feishu/scaffold/feishu-send.ts +5 -5
- package/dist/channels/feishu/scaffold/feishu-threads.ts +3 -3
- package/dist/channels/feishu/setup-mode.d.ts +2 -12
- package/dist/channels/feishu/setup-mode.js +2 -22
- package/dist/channels/feishu/setup.d.ts +12 -0
- package/dist/channels/feishu/setup.js +197 -0
- package/dist/channels/kit/abortable.d.ts +8 -0
- package/dist/channels/kit/abortable.js +22 -0
- package/dist/channels/lark/lark.d.ts +0 -3
- package/dist/channels/lark/lark.js +0 -5
- package/dist/channels/lark/scaffold/channel.ts +33 -35
- package/dist/channels/lark/scaffold/lark-send.ts +5 -5
- package/dist/channels/lark/scaffold/lark-threads.ts +3 -3
- package/dist/channels/lark/setup.d.ts +9 -0
- package/dist/channels/lark/setup.js +57 -0
- package/dist/channels/setup.d.ts +112 -0
- package/dist/channels/setup.js +31 -0
- package/dist/channels/slack/history.js +1 -1
- package/dist/channels/slack/manifest.d.ts +17 -7
- package/dist/channels/slack/manifest.js +12 -16
- package/dist/channels/slack/scaffold/channel.ts +44 -30
- package/dist/channels/slack/scaffold/slack-send.ts +8 -8
- package/dist/channels/slack/scaffold/slack-threads.ts +4 -4
- package/dist/channels/slack/setup.d.ts +7 -0
- package/dist/channels/slack/setup.js +119 -0
- package/dist/channels/slack/slack-api.d.ts +2 -0
- package/dist/channels/slack/slack-api.js +4 -0
- package/dist/channels/slack/slack.d.ts +21 -2
- package/dist/channels/slack/slack.js +398 -338
- package/dist/channels/slack/socket.d.ts +57 -0
- package/dist/channels/slack/socket.js +305 -0
- package/dist/channels/telegram/polling.d.ts +27 -0
- package/dist/channels/telegram/polling.js +105 -0
- package/dist/channels/telegram/scaffold/channel.ts +52 -32
- package/dist/channels/telegram/scaffold/telegram-send.ts +9 -9
- package/dist/channels/telegram/setup.d.ts +2 -0
- package/dist/channels/telegram/setup.js +42 -0
- package/dist/channels/telegram/telegram-api.d.ts +27 -1
- package/dist/channels/telegram/telegram-api.js +7 -6
- package/dist/channels/telegram/telegram.d.ts +11 -3
- package/dist/channels/telegram/telegram.js +184 -157
- package/dist/channels/webhook-evidence.d.ts +11 -0
- package/dist/channels/webhook-evidence.js +71 -0
- package/dist/channels/webhooks.d.ts +37 -0
- package/dist/channels/webhooks.js +70 -0
- package/dist/child-env.d.ts +16 -0
- package/dist/child-env.js +30 -0
- package/dist/cli/box-login.js +4 -1
- package/dist/cli/channel-app.d.ts +3 -0
- package/dist/cli/channel-app.js +35 -0
- package/dist/cli/channel-setup.d.ts +38 -0
- package/dist/cli/channel-setup.js +195 -0
- package/dist/cli/commands/add.d.ts +6 -14
- package/dist/cli/commands/add.js +45 -303
- package/dist/cli/commands/auth.js +8 -2
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +16 -0
- package/dist/cli/commands/channels.d.ts +19 -0
- package/dist/cli/commands/channels.js +230 -0
- package/dist/cli/commands/content.d.ts +7 -3
- package/dist/cli/commands/content.js +16 -10
- package/dist/cli/commands/deploy/agentcore.js +55 -23
- package/dist/cli/commands/deploy/channel-apps.d.ts +17 -0
- package/dist/cli/commands/deploy/channel-apps.js +30 -0
- package/dist/cli/commands/deploy/docker.js +32 -65
- package/dist/cli/commands/deploy/fly.js +28 -12
- package/dist/cli/commands/deploy/railway.js +38 -13
- package/dist/cli/commands/deploy/secrets-step.d.ts +19 -0
- package/dist/cli/commands/deploy/secrets-step.js +66 -0
- package/dist/cli/commands/deploy/shared.d.ts +16 -5
- package/dist/cli/commands/deploy/shared.js +25 -8
- package/dist/cli/commands/deploy.js +53 -60
- package/dist/cli/commands/dev.d.ts +2 -0
- package/dist/cli/commands/dev.js +15 -3
- package/dist/cli/commands/env.js +1 -1
- package/dist/cli/commands/info.d.ts +2 -0
- package/dist/cli/commands/info.js +60 -25
- package/dist/cli/commands/init.js +3 -1
- package/dist/cli/commands/invoke.d.ts +2 -0
- package/dist/cli/commands/invoke.js +20 -7
- package/dist/cli/commands/login.d.ts +0 -7
- package/dist/cli/commands/login.js +22 -10
- package/dist/cli/commands/logout.js +1 -1
- package/dist/cli/commands/mcp.js +14 -6
- package/dist/cli/commands/models.js +3 -2
- package/dist/cli/commands/schedules.d.ts +4 -2
- package/dist/cli/commands/schedules.js +14 -10
- package/dist/cli/commands/secrets.d.ts +14 -0
- package/dist/cli/commands/secrets.js +213 -0
- package/dist/cli/commands/start.d.ts +14 -14
- package/dist/cli/commands/start.js +46 -60
- package/dist/cli/content-view.d.ts +4 -6
- package/dist/cli/content-view.js +5 -11
- package/dist/cli/first-run-channels.d.ts +3 -0
- package/dist/cli/first-run-channels.js +63 -0
- package/dist/cli/instance.d.ts +41 -0
- package/dist/cli/instance.js +155 -0
- package/dist/cli/invoke-stream.d.ts +5 -2
- package/dist/cli/invoke-stream.js +7 -2
- package/dist/cli/kernel.js +3 -0
- package/dist/cli/program.js +282 -110
- package/dist/cli/serve.d.ts +4 -5
- package/dist/cli/serve.js +4 -7
- package/dist/cli/shared.d.ts +11 -2
- package/dist/cli/shared.js +32 -19
- package/dist/cli/ticket.d.ts +7 -0
- package/dist/cli/ticket.js +106 -30
- package/dist/content/declare.d.ts +31 -37
- package/dist/content/declare.js +59 -60
- package/dist/content/file.d.ts +23 -12
- package/dist/content/file.js +48 -35
- package/dist/content/git.d.ts +0 -10
- package/dist/content/git.js +5 -39
- package/dist/content/resolve.d.ts +18 -22
- package/dist/content/resolve.js +32 -66
- package/dist/content/source.d.ts +7 -8
- package/dist/content/source.js +10 -28
- package/dist/credentials-page.d.ts +26 -0
- package/dist/credentials-page.js +185 -0
- package/dist/declared-secrets.d.ts +10 -3
- package/dist/declared-secrets.js +8 -4
- package/dist/deploy/agentcore/destroy.d.ts +4 -1
- package/dist/deploy/agentcore/destroy.js +25 -1
- package/dist/deploy/agentcore/forwarder.js +1 -1
- package/dist/deploy/agentcore/plan.d.ts +7 -21
- package/dist/deploy/agentcore/plan.js +47 -49
- package/dist/deploy/agentcore/run.d.ts +18 -10
- package/dist/deploy/agentcore/run.js +93 -70
- package/dist/deploy/agentcore/runtime-secrets.d.ts +7 -0
- package/dist/deploy/agentcore/runtime-secrets.js +108 -0
- package/dist/deploy/agentcore/shell.js +12 -19
- package/dist/deploy/agentcore/sigv4.d.ts +7 -0
- package/dist/deploy/agentcore/sigv4.js +15 -0
- package/dist/deploy/agentcore/slr.d.ts +18 -0
- package/dist/deploy/agentcore/slr.js +27 -0
- package/dist/deploy/agentcore/store.d.ts +7 -0
- package/dist/deploy/agentcore/store.js +115 -0
- package/dist/deploy/api.d.ts +14 -14
- package/dist/deploy/api.js +26 -24
- package/dist/deploy/box-shell.d.ts +3 -1
- package/dist/deploy/build-context.d.ts +4 -4
- package/dist/deploy/build-context.js +11 -15
- package/dist/deploy/channel-ingress.d.ts +35 -9
- package/dist/deploy/channel-ingress.js +119 -22
- package/dist/deploy/container.d.ts +35 -10
- package/dist/deploy/container.js +79 -37
- package/dist/deploy/delivered-text.d.ts +13 -0
- package/dist/deploy/delivered-text.js +86 -0
- package/dist/deploy/docker/plan.d.ts +7 -8
- package/dist/deploy/docker/plan.js +31 -52
- package/dist/deploy/docker/run.d.ts +5 -43
- package/dist/deploy/docker/run.js +9 -84
- package/dist/deploy/file-store.d.ts +2 -0
- package/dist/deploy/file-store.js +17 -0
- package/dist/deploy/fly/plan.d.ts +3 -2
- package/dist/deploy/fly/plan.js +20 -9
- package/dist/deploy/fly/run.d.ts +18 -8
- package/dist/deploy/fly/run.js +24 -42
- package/dist/deploy/fly/store.d.ts +12 -0
- package/dist/deploy/fly/store.js +83 -0
- package/dist/deploy/hosts.d.ts +1 -2
- package/dist/deploy/hosts.js +1 -2
- package/dist/deploy/preflight.d.ts +18 -5
- package/dist/deploy/preflight.js +135 -63
- package/dist/deploy/railway/ignore.d.ts +8 -0
- package/dist/deploy/railway/ignore.js +57 -0
- package/dist/deploy/railway/plan.d.ts +1 -2
- package/dist/deploy/railway/plan.js +8 -6
- package/dist/deploy/railway/run.d.ts +21 -15
- package/dist/deploy/railway/run.js +32 -78
- package/dist/deploy/railway/store.d.ts +29 -0
- package/dist/deploy/railway/store.js +143 -0
- package/dist/deploy/runner.d.ts +5 -2
- package/dist/deploy/runner.js +4 -3
- package/dist/deploy/secret-store.d.ts +63 -0
- package/dist/deploy/secret-store.js +98 -0
- package/dist/deploy/secrets.d.ts +29 -46
- package/dist/deploy/secrets.js +49 -122
- package/dist/deploy/workspace.d.ts +6 -12
- package/dist/deploy/workspace.js +18 -94
- package/dist/dev-supervisor.js +6 -8
- package/dist/environment/declare.js +3 -2
- package/dist/environment/mise.d.ts +3 -1
- package/dist/environment/mise.js +4 -2
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/harnesses/models/catalog.d.ts +1 -0
- package/dist/harnesses/models/catalog.js +1 -2
- package/dist/harnesses/models/environment.d.ts +7 -2
- package/dist/harnesses/models/environment.js +9 -5
- package/dist/harnesses/models/host.d.ts +0 -7
- package/dist/harnesses/models/host.js +10 -1
- package/dist/harnesses/models/login.d.ts +0 -8
- package/dist/harnesses/models/login.js +5 -4
- package/dist/harnesses/models/models.d.ts +7 -2
- package/dist/harnesses/models/models.js +28 -4
- package/dist/harnesses/pi-durable/agent.js +59 -38
- package/dist/harnesses/pi-durable/build.d.ts +12 -0
- package/dist/harnesses/pi-durable/build.js +270 -0
- package/dist/harnesses/pi-durable/built.d.ts +24 -0
- package/dist/harnesses/pi-durable/built.js +136 -0
- package/dist/harnesses/pi-durable/config.d.ts +81 -0
- package/dist/harnesses/pi-durable/config.js +266 -0
- package/dist/harnesses/pi-durable/define.d.ts +63 -13
- package/dist/harnesses/pi-durable/define.js +23 -2
- package/dist/harnesses/pi-durable/definition.d.ts +72 -0
- package/dist/harnesses/pi-durable/definition.js +143 -0
- package/dist/harnesses/pi-durable/events.d.ts +4 -3
- package/dist/harnesses/pi-durable/events.js +179 -136
- package/dist/harnesses/pi-durable/execution-env.d.ts +17 -0
- package/dist/harnesses/pi-durable/execution-env.js +11 -0
- package/dist/harnesses/pi-durable/mcp.js +1 -3
- package/dist/harnesses/pi-durable/open.d.ts +35 -37
- package/dist/harnesses/pi-durable/open.js +91 -153
- package/dist/harnesses/pi-durable/reads.d.ts +0 -1
- package/dist/harnesses/pi-durable/reads.js +1 -3
- package/dist/harnesses/pi-durable/registry.d.ts +4 -6
- package/dist/harnesses/pi-durable/registry.js +29 -26
- package/dist/harnesses/pi-durable/release.d.ts +2 -2
- package/dist/harnesses/pi-durable/release.js +10 -7
- package/dist/harnesses/pi-durable/serve.d.ts +91 -15
- package/dist/harnesses/pi-durable/serve.js +109 -47
- package/dist/harnesses/pi-durable/sessions.js +15 -12
- package/dist/harnesses/pi-durable/text-sources.d.ts +30 -0
- package/dist/harnesses/pi-durable/text-sources.js +55 -0
- package/dist/harnesses/pi-durable/text.d.ts +16 -4
- package/dist/harnesses/pi-durable/text.js +115 -59
- package/dist/harnesses/pi-durable/wake.js +8 -8
- package/dist/http/client.js +9 -3
- package/dist/http/contract.d.ts +3 -3
- package/dist/http/server.d.ts +1 -1
- package/dist/http/server.js +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +4 -1
- package/dist/instance-name.d.ts +7 -0
- package/dist/instance-name.js +26 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/mcp/client.js +11 -2
- package/dist/mcp/config.d.ts +70 -18
- package/dist/mcp/config.js +136 -53
- package/dist/mcp/host.d.ts +5 -2
- package/dist/mcp/host.js +14 -6
- package/dist/mcp/sign-in.js +2 -2
- package/dist/mcp-api.d.ts +14 -0
- package/dist/paths.d.ts +22 -8
- package/dist/paths.js +44 -11
- package/dist/runtime-websocket.d.ts +14 -0
- package/dist/runtime-websocket.js +39 -0
- package/dist/scaffold/add-channel.d.ts +9 -16
- package/dist/scaffold/add-channel.js +13 -83
- package/dist/scaffold/templates/env.example +4 -5
- package/dist/scaffold/templates/fastagent.config.ts +20 -14
- package/dist/scaffold/templates/gitignore +9 -6
- package/dist/scaffold/templates.d.ts +6 -2
- package/dist/scaffold/templates.js +7 -2
- package/dist/scaffold/vendor-skill.js +4 -2
- package/dist/service.d.ts +44 -6
- package/dist/service.js +110 -10
- package/dist/serving-command.d.ts +10 -0
- package/dist/serving-command.js +1 -0
- package/dist/slack.d.ts +1 -1
- package/dist/slack.js +1 -1
- package/dist/strip-edges.d.ts +10 -0
- package/dist/strip-edges.js +22 -0
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +1 -1
- package/dist/tunnel.d.ts +0 -1
- package/dist/tunnel.js +2 -8
- package/dist/value-file.d.ts +11 -6
- package/dist/value-file.js +21 -9
- package/dist/version.d.ts +7 -1
- package/dist/version.js +9 -3
- package/package.json +6 -5
- package/dist/channels/lark/credentials-page.d.ts +0 -25
- package/dist/channels/lark/credentials-page.js +0 -206
- package/dist/channels/lark/onboard.d.ts +0 -52
- package/dist/channels/lark/onboard.js +0 -86
- package/dist/channels/slack/config-api.d.ts +0 -69
- package/dist/channels/slack/config-api.js +0 -162
- package/dist/channels/slack/onboard.d.ts +0 -40
- package/dist/channels/slack/onboard.js +0 -136
- package/dist/channels/slack/onboarding-state.d.ts +0 -31
- package/dist/channels/slack/onboarding-state.js +0 -64
- package/dist/channels/slack/register-webhook.d.ts +0 -12
- package/dist/channels/slack/register-webhook.js +0 -67
- package/dist/channels/slack/setup-server.d.ts +0 -32
- package/dist/channels/slack/setup-server.js +0 -246
- package/dist/cli/add-feishu.d.ts +0 -41
- package/dist/cli/add-feishu.js +0 -391
- package/dist/cli/add-slack.d.ts +0 -18
- package/dist/cli/add-slack.js +0 -216
- package/dist/harnesses/pi-durable/parked.d.ts +0 -18
- package/dist/harnesses/pi-durable/parked.js +0 -27
package/dist/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.d.ts
CHANGED
|
@@ -41,6 +41,13 @@ export interface Ticket {
|
|
|
41
41
|
}
|
|
42
42
|
/** What the step's process says while it runs: the place to look when it fails. */
|
|
43
43
|
export declare const ticketLogPath: (id: string) => string;
|
|
44
|
+
/**
|
|
45
|
+
* End the ticket `id` with `end`, unless something ended it first: the end is a file created once, by a hard link,
|
|
46
|
+
* which the kernel lets only one writer make, so two that both read the ticket as running (the step finishing as a
|
|
47
|
+
* person stops it) end it once. Answers the ticket as whoever ended it ended it. Not public: `update` ends a ticket,
|
|
48
|
+
* and a test seam.
|
|
49
|
+
*/
|
|
50
|
+
export declare function endTicket(id: string, end: Partial<Ticket>): Ticket;
|
|
44
51
|
export interface TicketRequest {
|
|
45
52
|
subject: string;
|
|
46
53
|
action: string;
|
package/dist/cli/ticket.js
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
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
|
|
10
|
-
* the starting command until the step starts, then the step
|
|
11
|
-
*
|
|
9
|
+
* A ticket is two files in the machine's own state, ~/.fastagent/.state/tickets. One is the step as its process keeps
|
|
10
|
+
* it, written by one process at a time: the starting command until the step starts, then the step. The other is its
|
|
11
|
+
* end, written once, by whoever ends it first: the step itself, or a process that stops it, replaces it or finds it
|
|
12
|
+
* gone. An end is decided before what it decides is done, so a stop is the end of the step it stops even when the step
|
|
13
|
+
* hears the consequences of being stopped first, and nothing the step writes after reopens it. A newer step for the same subject stops an older one still waiting on its person, whose
|
|
12
14
|
* process would otherwise hold what it holds (a sign-in's callback port, OpenAI's fixed) until it expires; so does one
|
|
13
15
|
* still getting ready (reaching a box, opening a tunnel), which may hang there. One finishing what its person did (a
|
|
14
16
|
* code exchanged, an app created from a pasted token) is never stopped halfway, and the newer attempt is answered
|
|
@@ -18,9 +20,10 @@
|
|
|
18
20
|
* stopped with it, by `stop` and when it exits.
|
|
19
21
|
*/
|
|
20
22
|
import { randomBytes } from "node:crypto";
|
|
21
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
23
|
+
import { closeSync, existsSync, linkSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
22
24
|
import { spawn } from "node:child_process";
|
|
23
25
|
import { join } from "node:path";
|
|
26
|
+
import { stripVTControlCharacters } from "node:util";
|
|
24
27
|
import { writeFileAtomic } from "../atomic-write.js";
|
|
25
28
|
import { STATE_DIRNAME, globalHome } from "../paths.js";
|
|
26
29
|
import { recordFailures } from "./fail.js";
|
|
@@ -31,6 +34,7 @@ const ADDRESS_TIMEOUT_MS = 15_000;
|
|
|
31
34
|
/** How long a stopped step's process has to let go of what it holds before it is killed. */
|
|
32
35
|
const STOP_TIMEOUT_MS = 5_000;
|
|
33
36
|
const POLL_MS = 200;
|
|
37
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
34
38
|
/** Ticket files are random hex: anything else names no ticket, and no file outside the directory. */
|
|
35
39
|
const TICKET_ID = /^[0-9a-f]{16}$/;
|
|
36
40
|
const ticketsDir = () => join(globalHome(), STATE_DIRNAME, "tickets");
|
|
@@ -38,11 +42,10 @@ const ticketPath = (id) => join(ticketsDir(), `${id}.json`);
|
|
|
38
42
|
const addressPath = (id) => join(ticketsDir(), `${id}.address`);
|
|
39
43
|
/** What the step's process says while it runs: the place to look when it fails. */
|
|
40
44
|
export const ticketLogPath = (id) => join(ticketsDir(), `${id}.log`);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return undefined;
|
|
45
|
+
const endPath = (id) => join(ticketsDir(), `${id}.end.json`);
|
|
46
|
+
function readJson(path) {
|
|
44
47
|
try {
|
|
45
|
-
return JSON.parse(readFileSync(
|
|
48
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
46
49
|
}
|
|
47
50
|
catch (error) {
|
|
48
51
|
if (error.code === "ENOENT")
|
|
@@ -50,11 +53,45 @@ function readTicket(id) {
|
|
|
50
53
|
throw error;
|
|
51
54
|
}
|
|
52
55
|
}
|
|
56
|
+
/** The ticket `id`: the step as its process keeps it, with its end over it once it has one. */
|
|
57
|
+
function readTicket(id) {
|
|
58
|
+
if (!TICKET_ID.test(id))
|
|
59
|
+
return undefined;
|
|
60
|
+
const kept = readJson(ticketPath(id));
|
|
61
|
+
if (kept === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
const end = readJson(endPath(id));
|
|
64
|
+
return end === undefined ? kept : { ...kept, ...end };
|
|
65
|
+
}
|
|
66
|
+
/** The step as its process keeps it: one writer at a time, the starting command and then the step. */
|
|
53
67
|
const writeTicket = (ticket) => writeFileAtomic(ticketPath(ticket.ticket), JSON.stringify(ticket));
|
|
54
68
|
const waiting = (ticket) => ticket.status === "starting" || ticket.status === "need_user_action" || ticket.status === "working";
|
|
69
|
+
const ENDS = ["done", "failed", "expired"];
|
|
55
70
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
71
|
+
* End the ticket `id` with `end`, unless something ended it first: the end is a file created once, by a hard link,
|
|
72
|
+
* which the kernel lets only one writer make, so two that both read the ticket as running (the step finishing as a
|
|
73
|
+
* person stops it) end it once. Answers the ticket as whoever ended it ended it. Not public: `update` ends a ticket,
|
|
74
|
+
* and a test seam.
|
|
75
|
+
*/
|
|
76
|
+
export function endTicket(id, end) {
|
|
77
|
+
const draft = `${endPath(id)}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
78
|
+
writeFileSync(draft, JSON.stringify(end));
|
|
79
|
+
try {
|
|
80
|
+
linkSync(draft, endPath(id));
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (error.code !== "EEXIST")
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
rmSync(draft, { force: true });
|
|
88
|
+
}
|
|
89
|
+
return readTicket(id);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Change the ticket `id` while its step still runs; one that has ended is returned as it ended. An end is
|
|
93
|
+
* `endTicket`'s, so the first one written is the one that stays; anything else is the step's own record, which its
|
|
94
|
+
* process alone writes.
|
|
58
95
|
*/
|
|
59
96
|
function update(id, change) {
|
|
60
97
|
const current = readTicket(id);
|
|
@@ -62,6 +99,8 @@ function update(id, change) {
|
|
|
62
99
|
throw new Error(`ticket ${id} is gone from ${ticketsDir()}`);
|
|
63
100
|
if (!waiting(current))
|
|
64
101
|
return current;
|
|
102
|
+
if (change.status !== undefined && ENDS.includes(change.status))
|
|
103
|
+
return endTicket(id, change);
|
|
65
104
|
const next = { ...current, ...change };
|
|
66
105
|
writeTicket(next);
|
|
67
106
|
return next;
|
|
@@ -141,24 +180,23 @@ async function supersede(subject, now) {
|
|
|
141
180
|
if (finishing !== undefined)
|
|
142
181
|
return finishing;
|
|
143
182
|
for (const ticket of runs) {
|
|
183
|
+
update(ticket.ticket, { status: "failed", message: `replaced by a newer attempt to ${ticket.action}` });
|
|
144
184
|
if (ticket.pid !== undefined)
|
|
145
185
|
await stop(ticket.pid);
|
|
146
|
-
update(ticket.ticket, { status: "failed", message: `replaced by a newer attempt to ${ticket.action}` });
|
|
147
186
|
}
|
|
148
187
|
return undefined;
|
|
149
188
|
}
|
|
150
|
-
/** Tickets a day past their end: each one
|
|
189
|
+
/** Tickets a day past their end: every file of each one goes. */
|
|
151
190
|
function prune(now) {
|
|
152
191
|
if (!existsSync(ticketsDir()))
|
|
153
192
|
return;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
193
|
+
const files = readdirSync(ticketsDir());
|
|
194
|
+
const past = new Set(files
|
|
195
|
+
.map((file) => file.slice(0, 16))
|
|
196
|
+
.filter((id) => TICKET_ID.test(id) && (readTicket(id)?.expiresAt ?? Number.POSITIVE_INFINITY) < now - DAY_MS));
|
|
197
|
+
for (const file of files)
|
|
198
|
+
if (past.has(file.slice(0, 16)))
|
|
199
|
+
rmSync(join(ticketsDir(), file), { force: true });
|
|
162
200
|
}
|
|
163
201
|
/** A new ticket, its step not started yet. Not public: what `startTicket` starts, and a test seam. */
|
|
164
202
|
export function createTicket(request) {
|
|
@@ -222,13 +260,10 @@ export async function startTicket(input) {
|
|
|
222
260
|
// Claimed and at work: the step writes the ticket now, so this command reports it as it is.
|
|
223
261
|
if (!exited && !input.waitsFirst && ticket.status === "working")
|
|
224
262
|
return ticket;
|
|
225
|
-
// Given up on, unclaimed or giving the person nothing:
|
|
226
|
-
// the ticket after this.
|
|
263
|
+
// Given up on, unclaimed or giving the person nothing: the end first, then its process, which would hold its port.
|
|
227
264
|
const stoppedItself = exited;
|
|
228
|
-
if (!stoppedItself && child.pid !== undefined)
|
|
229
|
-
await stop(child.pid);
|
|
230
265
|
const within = `${startTimeoutMs / 1000}s: ${ticketLogPath(id)} says why`;
|
|
231
|
-
|
|
266
|
+
const ended = update(id, {
|
|
232
267
|
status: "failed",
|
|
233
268
|
message: stoppedItself
|
|
234
269
|
? `it stopped before it started: ${ticketLogPath(id)} says why`
|
|
@@ -236,6 +271,9 @@ export async function startTicket(input) {
|
|
|
236
271
|
? `it did not start within ${within}`
|
|
237
272
|
: `it gave the person nothing to do within ${within}`,
|
|
238
273
|
});
|
|
274
|
+
if (!stoppedItself && child.pid !== undefined)
|
|
275
|
+
await stop(child.pid);
|
|
276
|
+
return ended;
|
|
239
277
|
}
|
|
240
278
|
/**
|
|
241
279
|
* Where the step `id` stands. `address`: what the person's browser ended on (or the code its page showed), for a step
|
|
@@ -246,7 +284,7 @@ export async function checkTicket(id, address) {
|
|
|
246
284
|
if (ticket === undefined)
|
|
247
285
|
return undefined;
|
|
248
286
|
if (address !== undefined && ticket.status === "need_user_action") {
|
|
249
|
-
writeFileAtomic(addressPath(id), address);
|
|
287
|
+
writeFileAtomic(addressPath(id), address, undefined, true);
|
|
250
288
|
for (const deadline = Date.now() + ADDRESS_TIMEOUT_MS; Date.now() < deadline;) {
|
|
251
289
|
ticket = readTicket(id);
|
|
252
290
|
if (ticket.status !== "need_user_action" || !running(ticket.pid))
|
|
@@ -271,12 +309,13 @@ export async function stopTicket(id) {
|
|
|
271
309
|
throw new Error(`ticket ${id} is gone from ${ticketsDir()}`);
|
|
272
310
|
if (!waiting(ticket))
|
|
273
311
|
return ticket;
|
|
274
|
-
|
|
275
|
-
await stop(ticket.pid);
|
|
276
|
-
return update(id, {
|
|
312
|
+
const ended = update(id, {
|
|
277
313
|
status: "failed",
|
|
278
314
|
message: `stopped on request; what it was doing may be left half-done (${ticketLogPath(id)} says where it was)`,
|
|
279
315
|
});
|
|
316
|
+
if (ticket.pid !== undefined)
|
|
317
|
+
await stop(ticket.pid);
|
|
318
|
+
return ended;
|
|
280
319
|
}
|
|
281
320
|
/**
|
|
282
321
|
* Why the command whose `--check` is `check` cannot read ticket `id`: there is none on this machine, or it is another
|
|
@@ -385,6 +424,33 @@ async function awaitAddress(id, signal) {
|
|
|
385
424
|
}
|
|
386
425
|
throw new Error("the step no longer waits for an address");
|
|
387
426
|
}
|
|
427
|
+
const LOG_TAIL_LINES = 12;
|
|
428
|
+
const LOG_TAIL_BYTES = 8192;
|
|
429
|
+
const LOG_LINE_CHARS = 300;
|
|
430
|
+
/**
|
|
431
|
+
* The last lines of a ticket's log, without terminal colors: where a failed step says why. A coding agent reads a
|
|
432
|
+
* report once, and "see the output above" points at a log it has not opened.
|
|
433
|
+
*/
|
|
434
|
+
function logTail(id) {
|
|
435
|
+
const path = ticketLogPath(id);
|
|
436
|
+
if (!existsSync(path))
|
|
437
|
+
return [];
|
|
438
|
+
const size = statSync(path).size;
|
|
439
|
+
const start = Math.max(0, size - LOG_TAIL_BYTES);
|
|
440
|
+
const fd = openSync(path, "r");
|
|
441
|
+
try {
|
|
442
|
+
const buffer = Buffer.alloc(size - start);
|
|
443
|
+
readSync(fd, buffer, 0, buffer.length, start);
|
|
444
|
+
return stripVTControlCharacters(buffer.toString("utf8"))
|
|
445
|
+
.split("\n")
|
|
446
|
+
.map((line) => line.trim().slice(0, LOG_LINE_CHARS))
|
|
447
|
+
.filter((line) => line !== "")
|
|
448
|
+
.slice(-LOG_TAIL_LINES);
|
|
449
|
+
}
|
|
450
|
+
finally {
|
|
451
|
+
closeSync(fd);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
388
454
|
/**
|
|
389
455
|
* A ticket as its command reports it, with what to do next (`agent_next_steps`): one JSON object, or its lines for a
|
|
390
456
|
* person. `done` is what the step's `result` adds to the report, beside a sentence for the agent.
|
|
@@ -412,17 +478,27 @@ export function ticketReport(ticket, when, done = (result) => ({ fields: result,
|
|
|
412
478
|
const message = ticket.status === "expired" ? "it was not finished in time" : ticket.message;
|
|
413
479
|
// What the step reached before it ended is still the person's: where to get the value it stopped for, say.
|
|
414
480
|
const reached = left.length === 0 ? "" : `Tell the user what the steps it reached leave them: ${left.join(" ")} `;
|
|
481
|
+
// An expired step is not a fault: nobody finished it in time, and its log says nothing more.
|
|
482
|
+
const expired = ticket.status === "expired";
|
|
483
|
+
const tail = expired ? [] : logTail(ticket.ticket);
|
|
415
484
|
return {
|
|
416
485
|
json: {
|
|
417
486
|
...base,
|
|
418
487
|
message,
|
|
419
488
|
log,
|
|
489
|
+
...(tail.length === 0 ? {} : { log_tail: tail }),
|
|
420
490
|
...(ticket.afterwards ? { afterwards: ticket.afterwards } : {}),
|
|
421
|
-
agent_next_steps:
|
|
491
|
+
agent_next_steps: expired
|
|
492
|
+
? `${reached}The step was not finished in time, which is not a fault: ask the user to complete it as soon ` +
|
|
493
|
+
`as you run: ${ticket.restart} --json`
|
|
494
|
+
: `${reached}The cause is in log_tail (the whole log is at log): fix what it names, then run: ` +
|
|
495
|
+
`${ticket.restart} --json. The same inputs fail the same way unless the log shows a transient network or ` +
|
|
496
|
+
`builder error.`,
|
|
422
497
|
},
|
|
423
498
|
text: [
|
|
424
499
|
`${ticket.status}: ${message}`,
|
|
425
500
|
`log: ${log}`,
|
|
501
|
+
...tail.map((line) => ` | ${line}`),
|
|
426
502
|
...left.map((line) => `left for the person: ${line}`),
|
|
427
503
|
`to try again: ${ticket.restart}`,
|
|
428
504
|
],
|
|
@@ -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
|