@fastagent-sh/fastagent 0.24.2 → 0.24.4
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/dist/agent.d.ts +6 -0
- package/dist/agent.js +6 -0
- package/dist/cli/commands/add.js +1 -1
- package/dist/cli/commands/chat.js +1 -1
- package/dist/cli/commands/deploy.js +2 -2
- package/dist/cli/commands/dev.js +2 -2
- package/dist/cli/commands/info.js +12 -5
- package/dist/cli/commands/invoke.js +2 -3
- package/dist/cli/commands/routine-run.js +4 -5
- package/dist/cli/commands/start.js +2 -2
- package/dist/cli/kernel.js +1 -1
- package/dist/cli/program.js +4 -3
- package/dist/cli/shared.d.ts +28 -2
- package/dist/cli/shared.js +36 -13
- package/dist/core.d.ts +1 -1
- package/dist/core.js +5 -4
- package/dist/deploy/build-context.js +1 -1
- package/dist/deploy/preflight.js +9 -1
- package/dist/dev-supervisor.js +1 -1
- package/dist/engines/pi/agent-session-factory.d.ts +21 -5
- package/dist/engines/pi/agent-session-factory.js +129 -18
- package/dist/engines/pi/config.d.ts +6 -0
- package/dist/engines/pi/config.js +8 -0
- package/dist/engines/pi/create.d.ts +25 -30
- package/dist/engines/pi/create.js +64 -66
- package/dist/engines/pi/definition.d.ts +44 -28
- package/dist/engines/pi/definition.js +123 -30
- package/dist/engines/pi/invoke-session.d.ts +4 -1
- package/dist/engines/pi/invoke-session.js +11 -6
- package/dist/engines/pi/machine.d.ts +1 -2
- package/dist/engines/pi/machine.js +10 -1
- package/dist/engines/pi/open.d.ts +11 -7
- package/dist/engines/pi/open.js +13 -13
- package/dist/engines/pi/report.d.ts +7 -8
- package/dist/engines/pi/report.js +23 -5
- package/dist/engines/pi/session-builder.js +6 -2
- package/dist/engines/pi/session-control.d.ts +3 -2
- package/dist/engines/pi/session-control.js +21 -9
- package/dist/engines/pi/session-markers.d.ts +1 -1
- package/dist/engines/pi/session-markers.js +1 -1
- package/dist/engines/pi/session-settings.d.ts +14 -3
- package/dist/engines/pi/session-settings.js +20 -1
- package/dist/engines/pi/turn-kit.d.ts +20 -0
- package/dist/engines/pi/turn-kit.js +45 -1
- package/dist/paths.d.ts +1 -1
- package/dist/paths.js +12 -1
- package/dist/pi.d.ts +1 -1
- package/dist/scaffold/init.js +2 -2
- package/dist/scaffold/templates/APPEND_SYSTEM.md +12 -0
- package/dist/scaffold/templates/fastagent.config.ts +1 -1
- package/dist/session.d.ts +4 -0
- package/package.json +1 -1
- package/dist/scaffold/templates/persona.md +0 -12
package/dist/agent.d.ts
CHANGED
|
@@ -91,6 +91,12 @@ export declare const SESSION_BUSY_CODE = "session_busy";
|
|
|
91
91
|
* never replayed as a fresh turn on restart.
|
|
92
92
|
*/
|
|
93
93
|
export declare const ABORTED_CODE = "aborted";
|
|
94
|
+
/**
|
|
95
|
+
* The `failed.code` set when a turn has no model to run on: its session records none of its own, and the agent sets
|
|
96
|
+
* no default. Not retryable as is: giving the session a model (session control's `update({ model })`) or the agent a
|
|
97
|
+
* default is what changes the answer.
|
|
98
|
+
*/
|
|
99
|
+
export declare const MISSING_MODEL_CODE = "missing_model";
|
|
94
100
|
/**
|
|
95
101
|
* One turn = one invoke, returning a single async event stream. The stream MUST terminate with exactly one of
|
|
96
102
|
* `completed` / `failed`, or be cancelled by the caller (no terminal event).
|
package/dist/agent.js
CHANGED
|
@@ -13,3 +13,9 @@ export const SESSION_BUSY_CODE = "session_busy";
|
|
|
13
13
|
* never replayed as a fresh turn on restart.
|
|
14
14
|
*/
|
|
15
15
|
export const ABORTED_CODE = "aborted";
|
|
16
|
+
/**
|
|
17
|
+
* The `failed.code` set when a turn has no model to run on: its session records none of its own, and the agent sets
|
|
18
|
+
* no default. Not retryable as is: giving the session a model (session control's `update({ model })`) or the agent a
|
|
19
|
+
* default is what changes the answer.
|
|
20
|
+
*/
|
|
21
|
+
export const MISSING_MODEL_CODE = "missing_model";
|
package/dist/cli/commands/add.js
CHANGED
|
@@ -238,5 +238,5 @@ export async function runAddSkill(source, dirArg, opts) {
|
|
|
238
238
|
if (hasScripts) {
|
|
239
239
|
console.error(` warn: this skill ships scripts/ (executable code that runs in your agent) — review it before deploying`);
|
|
240
240
|
}
|
|
241
|
-
console.error(` next: mention "${name}" in
|
|
241
|
+
console.error(` next: mention "${name}" in APPEND_SYSTEM.md so the model knows when to use it; then \`fastagent dev\``);
|
|
242
242
|
}
|
|
@@ -10,5 +10,5 @@ export async function runChat(dirArg, opts) {
|
|
|
10
10
|
process.chdir(placement.workspace);
|
|
11
11
|
// Lazy-import: chat pulls pi's interactive TUI module graph; headless start/dev never need it.
|
|
12
12
|
const { runPiChat } = await import("../../engines/pi/chat.js");
|
|
13
|
-
await runPiChat(placement.agentDir, { model:
|
|
13
|
+
await runPiChat(placement.agentDir, { model: placement.modelSpec }).catch(failStartup);
|
|
14
14
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { preflightDeploy } from "../../deploy/preflight.js";
|
|
2
2
|
import { loadConfig } from "../../engines/pi/config.js";
|
|
3
3
|
import { failStartup, failUsage } from "../fail.js";
|
|
4
|
-
import {
|
|
4
|
+
import { enterAgentDirectory } from "../shared.js";
|
|
5
5
|
import { agentcoreHost } from "./deploy/agentcore.js";
|
|
6
6
|
import { dockerHost } from "./deploy/docker.js";
|
|
7
7
|
import { flyHost } from "./deploy/fly.js";
|
|
@@ -56,7 +56,7 @@ export async function runDeploy(host, dirArg, opts) {
|
|
|
56
56
|
failUsage(`deploy stopped: --tunnel is supported only by the local Docker target`);
|
|
57
57
|
}
|
|
58
58
|
// The picker's write-back lands the model in fastagent.config.ts.
|
|
59
|
-
const placement = await
|
|
59
|
+
const placement = await enterAgentDirectory(dirArg, opts);
|
|
60
60
|
// ONE deploy semantic: bake the WORKSPACE (WYSIWYG).
|
|
61
61
|
const { agentDir, workspace } = placement;
|
|
62
62
|
const { config } = await loadConfig(agentDir).catch(failStartup);
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -36,11 +36,11 @@ async function serveOnce(placement, opts) {
|
|
|
36
36
|
const tunnel = opts.tunnel ?? false;
|
|
37
37
|
assertTunnelBindable(host, tunnel);
|
|
38
38
|
const a = await createPiAgentFromDir(placement.agentDir, {
|
|
39
|
-
model:
|
|
39
|
+
model: placement.modelSpec,
|
|
40
40
|
serving: true, // long-running serve: the scheduler poller runs (and the wake tool is mounted)
|
|
41
41
|
}).catch(failStartup);
|
|
42
42
|
// The same report `start` prints; `config:` is dev's own extra (see reportAssembly on the asymmetry).
|
|
43
|
-
await reportAssembly(a, { beforeModel: [["config", a.configPath ?? "(none)"]] });
|
|
43
|
+
await reportAssembly({ ...a, modelSpec: placement.modelSpec }, { beforeModel: [["config", a.configPath ?? "(none)"]] });
|
|
44
44
|
// The SAME assembly an embedder gets from `createAgentService` — channels, control plane, schedules, long
|
|
45
45
|
// connections.
|
|
46
46
|
const service = await mountAgentService(withRunOverrides(a, opts), cliMountOptions(logAgentLoop)).catch(failStartup);
|
|
@@ -8,7 +8,7 @@ import { agentModels } from "../../engines/pi/agent-models.js";
|
|
|
8
8
|
import { resolveSessionsDir, resolveStateRoot } from "../../paths.js";
|
|
9
9
|
import { CODING_TOOL_NAMES, resolveAgentTools } from "../../engines/pi/create.js";
|
|
10
10
|
import { loadAgentDefinition } from "../../engines/pi/definition.js";
|
|
11
|
-
import { describeIndirectTools, reportFindingsIfChanged, reportToolCollisions } from "../../engines/pi/report.js";
|
|
11
|
+
import { describeIndirectTools, describePrompt, reportFindingsIfChanged, reportToolCollisions, } from "../../engines/pi/report.js";
|
|
12
12
|
import { allSecrets, describeSecrets, missingSecrets } from "../../declared-secrets.js";
|
|
13
13
|
import { log } from "../../log.js";
|
|
14
14
|
import { reportModuleLoadFailures } from "../../loader.js";
|
|
@@ -25,8 +25,10 @@ export async function runInfo(dirArg, opts) {
|
|
|
25
25
|
// agentDir = where the agent lives (definition + config + machinery); workspace = what it works ON (its cwd, whose
|
|
26
26
|
// AGENTS.md ancestors are ② context).
|
|
27
27
|
const definition = await loadAgentDefinition(agentDir, { cwd: workspace }).catch(failStartup);
|
|
28
|
-
// What this agent HAS: the definition's skills plus the ones its machine lends (machine.ts).
|
|
29
|
-
const
|
|
28
|
+
// What this agent HAS: the definition's skills and prompt templates plus the ones its machine lends (machine.ts).
|
|
29
|
+
const machineResources = await readMachine(workspace);
|
|
30
|
+
const skills = withMachine(definition.skills, machineResources.skills);
|
|
31
|
+
const prompts = withMachine(definition.prompts, machineResources.prompts);
|
|
30
32
|
// A tool that fails to load, for any reason (a missing dep, a top-level throw, or just not being a tool), is
|
|
31
33
|
// isolated the same way everywhere (G2).
|
|
32
34
|
const tools = await resolveAgentTools(config, agentDir, workspace)
|
|
@@ -104,8 +106,10 @@ export async function runInfo(dirArg, opts) {
|
|
|
104
106
|
thinkingLevel: config.thinkingLevel ?? null,
|
|
105
107
|
codingTools: [...CODING_TOOL_NAMES],
|
|
106
108
|
context: definition.contextFiles.map((f) => f.path),
|
|
107
|
-
|
|
109
|
+
systemPrompt: definition.systemPrompt?.path ?? null,
|
|
110
|
+
appendSystemPrompt: definition.appendSystemPrompt?.path ?? null,
|
|
108
111
|
skills: skills.map((skill) => ({ name: skill.name, description: skill.description })),
|
|
112
|
+
prompts: prompts.map((prompt) => ({ name: prompt.name, description: prompt.description ?? null })),
|
|
109
113
|
tools: tools.names,
|
|
110
114
|
indirectTools: tools.indirect,
|
|
111
115
|
toolError: tools.error ?? null,
|
|
@@ -119,6 +123,8 @@ export async function runInfo(dirArg, opts) {
|
|
|
119
123
|
fallbackAuthPath: auth.fallback ?? null,
|
|
120
124
|
diagnostics: definition.diagnostics,
|
|
121
125
|
skillCollisions: definition.collisions,
|
|
126
|
+
shadowed: definition.shadowed,
|
|
127
|
+
ignored: definition.ignored,
|
|
122
128
|
toolCollisions: tools.collisions,
|
|
123
129
|
toolFailures: tools.failures,
|
|
124
130
|
declaredSecrets,
|
|
@@ -142,8 +148,9 @@ export async function runInfo(dirArg, opts) {
|
|
|
142
148
|
line("thinking", config.thinkingLevel);
|
|
143
149
|
line("codingTools", CODING_TOOL_NAMES.join(", "));
|
|
144
150
|
line("context", definition.contextFiles.map((f) => f.path).join(", ") || "(none)");
|
|
145
|
-
line("
|
|
151
|
+
line("prompt", describePrompt(definition));
|
|
146
152
|
line("skills", skills.map((skill) => skill.name).join(", ") || "(none)");
|
|
153
|
+
line("prompts", prompts.map((prompt) => prompt.name).join(", ") || "(none)");
|
|
147
154
|
line("tools", tools.error ? "(could not load — see warning below)" : tools.names.join(", ") || "(none)");
|
|
148
155
|
if (tools.indirect.length > 0)
|
|
149
156
|
line("indirect", describeIndirectTools(tools.indirect));
|
|
@@ -6,9 +6,8 @@ import { failStartup } from "../fail.js";
|
|
|
6
6
|
import { enterAgentCommand, reportAuth } from "../shared.js";
|
|
7
7
|
export async function runInvoke(message, dirArg, opts) {
|
|
8
8
|
const placement = await enterAgentCommand(dirArg, opts);
|
|
9
|
-
const {
|
|
10
|
-
|
|
11
|
-
}).catch(failStartup);
|
|
9
|
+
const { modelSpec } = placement;
|
|
10
|
+
const { agent, models } = await createPiAgentFromDir(placement.agentDir, { model: modelSpec }).catch(failStartup);
|
|
12
11
|
// BOTH directories, like dev/start: which agent ran, and what it worked on.
|
|
13
12
|
console.error(`[fastagent] invoke: ${placement.agentDir} (workspace ${placement.workspace}, ${modelSpec})`);
|
|
14
13
|
await reportAuth(models, modelSpec);
|
|
@@ -10,9 +10,9 @@ import { runInvokeStream } from "../invoke-stream.js";
|
|
|
10
10
|
import { loadRoutines } from "../../schedule/discover.js";
|
|
11
11
|
import { routineSession } from "../../schedule/routine.js";
|
|
12
12
|
import { failStartup, gateSecretsOrExit } from "../fail.js";
|
|
13
|
-
import {
|
|
13
|
+
import { enterAgentDirectory, reportAuth, requireDefaultModel } from "../shared.js";
|
|
14
14
|
export async function runRoutine(name, dirArg, opts) {
|
|
15
|
-
const placement = await
|
|
15
|
+
const placement = await enterAgentDirectory(dirArg, opts);
|
|
16
16
|
// Routines are agent surface — discovered where dev/start/`routine list` do (the agent dir), so this command
|
|
17
17
|
// sees the same set the clock serves.
|
|
18
18
|
const { routines, secrets, failures } = await loadRoutines(placement.agentDir).catch(failStartup);
|
|
@@ -36,9 +36,8 @@ export async function runRoutine(name, dirArg, opts) {
|
|
|
36
36
|
// were printed above: its guarantee must not depend on this call site remembering (a repeated line
|
|
37
37
|
// on the refusal path is the cheaper failure).
|
|
38
38
|
gateSecretsOrExit({ declared: secrets, failures, owner: name });
|
|
39
|
-
const {
|
|
40
|
-
|
|
41
|
-
}).catch(failStartup);
|
|
39
|
+
const { modelSpec } = requireDefaultModel(placement);
|
|
40
|
+
const { agent, models } = await createPiAgentFromDir(placement.agentDir, { model: modelSpec }).catch(failStartup);
|
|
42
41
|
console.error(`[fastagent] routine run: ${name} (${modelSpec})`);
|
|
43
42
|
await reportAuth(models, modelSpec);
|
|
44
43
|
const exitCode = await runInvokeStream(agent.invoke({ session: routineSession(name) }, { text: routine.prompt }), (text) => process.stdout.write(text), (line) => console.error(line));
|
|
@@ -125,11 +125,11 @@ export async function openStartService(dirArg, opts) {
|
|
|
125
125
|
export async function openPreparedStartService(dirArg, opts) {
|
|
126
126
|
const placement = await enterAgentCommand(dirArg, opts);
|
|
127
127
|
const opened = await createPiAgentFromDir(placement.agentDir, {
|
|
128
|
-
model:
|
|
128
|
+
model: placement.modelSpec,
|
|
129
129
|
serving: true,
|
|
130
130
|
});
|
|
131
131
|
const { agent, agentDir, config, stateRoot, sessionsDir } = opened;
|
|
132
|
-
await reportAssembly(opened, {
|
|
132
|
+
await reportAssembly({ ...opened, modelSpec: placement.modelSpec }, {
|
|
133
133
|
afterTools: [
|
|
134
134
|
["state", stateRoot],
|
|
135
135
|
["sessions", sessionsDir],
|
package/dist/cli/kernel.js
CHANGED
|
@@ -9,7 +9,7 @@ const specOf = new WeakMap();
|
|
|
9
9
|
export function buildProgram(specs, options = {}) {
|
|
10
10
|
const exit = options.exit ?? ((code) => process.exit(code));
|
|
11
11
|
const program = new Command("fastagent");
|
|
12
|
-
program.description("Serve a file-defined agent —
|
|
12
|
+
program.description("Serve a file-defined agent — SYSTEM.md, skills/, tools/, channels/ — as a live service.");
|
|
13
13
|
// The exit-code policy: commander throws only for parse-level events — help/version displays carry exitCode 0 (→
|
|
14
14
|
// 0), everything else it rejects is a usage error (→ 2).
|
|
15
15
|
program.exitOverride((err) => exit(err.exitCode === 0 ? 0 : 2));
|
package/dist/cli/program.js
CHANGED
|
@@ -44,7 +44,7 @@ const init = {
|
|
|
44
44
|
description: "Scaffold a runnable agent and run npm install. The agent ALWAYS goes into a subdirectory of dir " +
|
|
45
45
|
"(default .): ./fastagent/, or --agent-dir <name> — the rest of the directory gets zero writes, and " +
|
|
46
46
|
"it is the WORKSPACE the agent works ON when you point fastagent there. Content is a " +
|
|
47
|
-
"self-iterating agent:
|
|
47
|
+
"self-iterating agent: APPEND_SYSTEM.md (its standing instructions), a writing-great-skills " +
|
|
48
48
|
"example skill, a fetch-url code tool, config, package.json, .gitignore. An existing AGENTS.md is " +
|
|
49
49
|
"kept as project context.",
|
|
50
50
|
args: [DIR_ARG],
|
|
@@ -70,7 +70,8 @@ const init = {
|
|
|
70
70
|
const dev = {
|
|
71
71
|
name: "dev",
|
|
72
72
|
summary: "serve the agent locally, restarting on code edits",
|
|
73
|
-
description: "Assemble the agent in dir (default .) and serve a local HTTP channel.
|
|
73
|
+
description: "Assemble the agent in dir (default .) and serve a local HTTP channel. SYSTEM.md/APPEND_SYSTEM.md/" +
|
|
74
|
+
"AGENTS.md/skills/prompts " +
|
|
74
75
|
"are re-read every turn (edits go live next turn); edits to code inputs — tools/, channels/, " +
|
|
75
76
|
"fastagent.config.ts, package.json, .secrets/.env — restart the worker. Files the agent writes as " +
|
|
76
77
|
"work product never trigger a restart.",
|
|
@@ -112,7 +113,7 @@ const chat = {
|
|
|
112
113
|
const info = {
|
|
113
114
|
name: "info",
|
|
114
115
|
summary: "print what the directory assembles into, without serving",
|
|
115
|
-
description: "Print what dir (default .) ASSEMBLES into — model,
|
|
116
|
+
description: "Print what dir (default .) ASSEMBLES into — model, prompt, context files (AGENTS.md), skills, " +
|
|
116
117
|
"tools (+ collisions), channels, schedules, sessions, load diagnostics — WITHOUT serving. " +
|
|
117
118
|
"Read-only (never creates sessions / writes .gitignore); an unset model is reported, not fatal. " +
|
|
118
119
|
"Run it first when something looks off.",
|
package/dist/cli/shared.d.ts
CHANGED
|
@@ -4,11 +4,37 @@ import { type IndirectTool } from "../engines/pi/create.ts";
|
|
|
4
4
|
import type { LoadedDefinition } from "../engines/pi/definition.ts";
|
|
5
5
|
import type { ToolCollision } from "../engines/pi/tool.ts";
|
|
6
6
|
import { type ResolvedPlacement } from "../paths.ts";
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* How a command enters its agent directory, in the one order that works: placement, the agent's environment, then the
|
|
9
|
+
* first-run model picker. Returns the default model that then resolves, if any. `deploy` uses this directly: it gates
|
|
10
|
+
* a missing model itself, by what will ship.
|
|
11
|
+
*/
|
|
12
|
+
export declare function enterAgentDirectory(dirArg: string, opts: {
|
|
13
|
+
model?: string;
|
|
14
|
+
input?: boolean;
|
|
15
|
+
}): Promise<ResolvedPlacement & {
|
|
16
|
+
modelSpec?: string;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* How every command that RUNS the agent on this machine enters it (dev, start, invoke, routine run, chat):
|
|
20
|
+
* {@link enterAgentDirectory}, plus a default model. The agent opens without one, but a process here would then fail
|
|
21
|
+
* every new conversation, so it stops at startup with {@link missingDefaultModel} instead.
|
|
22
|
+
*/
|
|
8
23
|
export declare function enterAgentCommand(dirArg: string, opts: {
|
|
9
24
|
model?: string;
|
|
10
25
|
input?: boolean;
|
|
11
|
-
}): Promise<ResolvedPlacement
|
|
26
|
+
}): Promise<ResolvedPlacement & {
|
|
27
|
+
modelSpec: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* The startup half of {@link enterAgentCommand}, for the one command that checks something of its own in between:
|
|
31
|
+
* `routine run`, whose unknown name or unset secret is the more basic fault and is reported first.
|
|
32
|
+
*/
|
|
33
|
+
export declare function requireDefaultModel(placement: ResolvedPlacement & {
|
|
34
|
+
modelSpec?: string;
|
|
35
|
+
}): ResolvedPlacement & {
|
|
36
|
+
modelSpec: string;
|
|
37
|
+
};
|
|
12
38
|
/** What the startup report reads off an opened directory. */
|
|
13
39
|
export interface ReportableAssembly {
|
|
14
40
|
agentDir: string;
|
package/dist/cli/shared.js
CHANGED
|
@@ -6,26 +6,48 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
6
6
|
import { relative, resolve } from "node:path";
|
|
7
7
|
import { autocomplete, isCancel, log as clackLog, password, select, text as clackText } from "@clack/prompts";
|
|
8
8
|
import { buildModelPickerOptions } from "./models-view.js";
|
|
9
|
-
import { isValidPort, listModels, loadConfig, providerOf, resolveModelSpec, rewriteConfigModel, } from "../engines/pi/config.js";
|
|
9
|
+
import { isValidPort, listModels, loadConfig, missingDefaultModel, providerOf, resolveModelSpec, rewriteConfigModel, } from "../engines/pi/config.js";
|
|
10
10
|
import { LoginCancelled, loginFlow } from "../engines/pi/login.js";
|
|
11
11
|
import { readMachine, withMachine } from "../engines/pi/machine.js";
|
|
12
12
|
import { providerAuthStatuses } from "../engines/pi/models.js";
|
|
13
13
|
import { agentModels } from "../engines/pi/agent-models.js";
|
|
14
14
|
import { formatAuthReport } from "./auth-view.js";
|
|
15
15
|
import { CODING_TOOL_NAMES } from "../engines/pi/create.js";
|
|
16
|
-
import { describeIndirectTools, reportFindingsIfChanged, reportToolCollisions } from "../engines/pi/report.js";
|
|
16
|
+
import { describeIndirectTools, describePrompt, reportFindingsIfChanged, reportToolCollisions, } from "../engines/pi/report.js";
|
|
17
17
|
import { isDeployedWorkspace } from "../paths.js";
|
|
18
18
|
import { log } from "../log.js";
|
|
19
19
|
import { enterAgentEnv } from "../env.js";
|
|
20
20
|
import { openExternalUrl } from "../open-url.js";
|
|
21
21
|
import { bindAddress, isBindAddress } from "../bind.js";
|
|
22
22
|
import { failStartup, failUsage, placementOrExit } from "./fail.js";
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* How a command enters its agent directory, in the one order that works: placement, the agent's environment, then the
|
|
25
|
+
* first-run model picker. Returns the default model that then resolves, if any. `deploy` uses this directly: it gates
|
|
26
|
+
* a missing model itself, by what will ship.
|
|
27
|
+
*/
|
|
28
|
+
export async function enterAgentDirectory(dirArg, opts) {
|
|
25
29
|
const placement = placementOrExit(resolve(dirArg));
|
|
26
30
|
enterAgentEnv(placement.agentDir);
|
|
27
|
-
await resolveFirstRunModel(placement, opts);
|
|
28
|
-
return placement;
|
|
31
|
+
const modelSpec = await resolveFirstRunModel(placement, opts);
|
|
32
|
+
return { ...placement, ...(modelSpec ? { modelSpec } : {}) };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* How every command that RUNS the agent on this machine enters it (dev, start, invoke, routine run, chat):
|
|
36
|
+
* {@link enterAgentDirectory}, plus a default model. The agent opens without one, but a process here would then fail
|
|
37
|
+
* every new conversation, so it stops at startup with {@link missingDefaultModel} instead.
|
|
38
|
+
*/
|
|
39
|
+
export async function enterAgentCommand(dirArg, opts) {
|
|
40
|
+
return requireDefaultModel(await enterAgentDirectory(dirArg, opts));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The startup half of {@link enterAgentCommand}, for the one command that checks something of its own in between:
|
|
44
|
+
* `routine run`, whose unknown name or unset secret is the more basic fault and is reported first.
|
|
45
|
+
*/
|
|
46
|
+
export function requireDefaultModel(placement) {
|
|
47
|
+
const { modelSpec } = placement;
|
|
48
|
+
if (!modelSpec)
|
|
49
|
+
failStartup(missingDefaultModel());
|
|
50
|
+
return { ...placement, modelSpec };
|
|
29
51
|
}
|
|
30
52
|
/** The padded label writer for the STARTUP report (`dev`/`start`, stderr via the log level). */
|
|
31
53
|
function reportLine(label, value) {
|
|
@@ -40,8 +62,7 @@ export async function reportAssembly(a, extras = {}) {
|
|
|
40
62
|
reportLine("model", `${a.modelSpec}${a.config.thinkingLevel ? ` (thinking: ${a.config.thinkingLevel})` : ""}`);
|
|
41
63
|
await reportAuth(a.models, a.modelSpec);
|
|
42
64
|
reportLine("context", a.definition.contextFiles.map((f) => f.path).join(", ") || "(none)");
|
|
43
|
-
|
|
44
|
-
reportLine("persona", "persona.md");
|
|
65
|
+
reportLine("prompt", describePrompt(a.definition));
|
|
45
66
|
// What this agent HAS — the definition's skills and the ones its machine lends (machine.ts).
|
|
46
67
|
const skills = withMachine(a.definition.skills, (await readMachine(a.workspace)).skills);
|
|
47
68
|
reportLine("skills", skills.map((s) => s.name).join(", ") || "(none)");
|
|
@@ -108,21 +129,23 @@ export async function reportAuth(models, modelSpec) {
|
|
|
108
129
|
*/
|
|
109
130
|
async function resolveFirstRunModel({ agentDir, workspace }, options = {}) {
|
|
110
131
|
const { config, path: configPath } = await loadConfig(agentDir).catch(failStartup);
|
|
111
|
-
|
|
112
|
-
|
|
132
|
+
const configured = resolveModelSpec(options.model, config);
|
|
133
|
+
if (configured)
|
|
134
|
+
return configured; // already set (flag > FASTAGENT_MODEL > config)
|
|
113
135
|
if (options.input === false)
|
|
114
|
-
return; // --no-input: never prompt (clig) — the
|
|
136
|
+
return undefined; // --no-input: never prompt (clig) — the caller decides what's missing
|
|
115
137
|
if (!isInteractive())
|
|
116
|
-
return; // CI/deploy: the
|
|
138
|
+
return undefined; // CI/deploy: the caller raises the actionable missing-model error
|
|
117
139
|
const environment = agentModels(agentDir, {}, { cwd: workspace });
|
|
118
140
|
// The picker lists the AGENT's surface: built-ins plus whatever its models.json declares, so a self-hosted endpoint
|
|
119
141
|
// is pickable on first run instead of being invisible until hand-set.
|
|
120
142
|
const models = await environment.runtime().catch(failStartup);
|
|
121
143
|
const chosen = await pickWithCredentials(models, environment.auth.path, agentDir);
|
|
122
144
|
if (chosen === undefined)
|
|
123
|
-
return; // cancelled (or auth probe failed): the caller raises its clear
|
|
145
|
+
return undefined; // cancelled (or auth probe failed): the caller raises its clear error
|
|
124
146
|
process.env.FASTAGENT_MODEL = chosen; // this process + any spawned dev worker inherits it
|
|
125
147
|
await persistModelChoice(agentDir, configPath, chosen);
|
|
148
|
+
return chosen;
|
|
126
149
|
}
|
|
127
150
|
/** The credential-aware pick: full catalog annotated per provider, then the post-pick auth policy. */
|
|
128
151
|
async function pickWithCredentials(models, authPath, agentDir) {
|
package/dist/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { Agent, AgentEvent, ImageRef, Json, Prompt, Scope } from "./agent.ts";
|
|
2
|
-
export { ABORTED_CODE, SESSION_BUSY_CODE } from "./agent.ts";
|
|
2
|
+
export { ABORTED_CODE, MISSING_MODEL_CODE, SESSION_BUSY_CODE } from "./agent.ts";
|
|
3
3
|
export { collect, AgentFailure, type CollectResult } from "./collect.ts";
|
|
4
4
|
export type { ModuleLoadFailure } from "./loader.ts";
|
|
5
5
|
export { createInvokeHandler } from "./channels/http.ts";
|
package/dist/core.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// VALUES, not types: each caller does something DIFFERENT with these
|
|
2
|
-
// occurrence, steer the live run instead of failing
|
|
3
|
-
//
|
|
4
|
-
export
|
|
1
|
+
// VALUES, not types: each caller does something DIFFERENT with these codes (busy: retry with backoff, skip the
|
|
2
|
+
// occurrence, or steer the live run instead of failing; aborted: settle a deliberate stop, never replay it;
|
|
3
|
+
// missing_model: do not retry, give the session a model or the agent a default), which is why they are named at all.
|
|
4
|
+
// Without an export an embedder copies the literal, and the copy is how the first-event rule goes silently wrong.
|
|
5
|
+
export { ABORTED_CODE, MISSING_MODEL_CODE, SESSION_BUSY_CODE } from "./agent.js";
|
|
5
6
|
export { collect, AgentFailure } from "./collect.js";
|
|
6
7
|
export { createInvokeHandler } from "./channels/http.js";
|
|
7
8
|
export { ControlRequestError, connectAgent, connectSessionControl, } from "./session-remote.js";
|
|
@@ -116,7 +116,7 @@ export async function checkKeptIgnoreFiles(ctx, report) {
|
|
|
116
116
|
// Asked as a DIRECTORY (trailing slash), which is what it is.
|
|
117
117
|
if (excluded(`${basename(agentDir)}/`)) {
|
|
118
118
|
const text = `your ${rel} (kept) excludes \`${basename(agentDir)}\` — the build context would ship WITHOUT the ` +
|
|
119
|
-
`agent entirely (the deployed box has no
|
|
119
|
+
`agent entirely (the deployed box has no prompt/config and crash-loops). Remove that rule ` +
|
|
120
120
|
`before deploying.`;
|
|
121
121
|
report.issue(text);
|
|
122
122
|
}
|
package/dist/deploy/preflight.js
CHANGED
|
@@ -13,6 +13,7 @@ import { AGENT_MODEL_CATALOG_FILE, AGENT_MODELS_FILE, exists } from "../paths.js
|
|
|
13
13
|
import { inspectChannels } from "../channels/discover.js";
|
|
14
14
|
import { loadRoutines } from "../schedule/discover.js";
|
|
15
15
|
import { resolveAgentTools } from "../engines/pi/create.js";
|
|
16
|
+
import { loadAgentDefinition } from "../engines/pi/definition.js";
|
|
16
17
|
import { agentModels } from "../engines/pi/agent-models.js";
|
|
17
18
|
import { allSecrets } from "../declared-secrets.js";
|
|
18
19
|
import { createPiModelRuntime, definitionKeyOf, environmentAuthSource, literalKeyProviders, isBuiltinProvider, machineModels, globalCatalogPath, inGlobalCatalog, interactiveLoginKind, loginProviders, } from "../engines/pi/models.js";
|
|
@@ -64,6 +65,13 @@ async function gatherFacts(input, report) {
|
|
|
64
65
|
`makes it an agent, never its name)`);
|
|
65
66
|
}
|
|
66
67
|
const agentPrefix = `${basename(agentDir)}/`;
|
|
68
|
+
// The definition the box will load on every start, loaded here first: a refusal in it (a leftover persona.md, a
|
|
69
|
+
// skill named with a slash) builds a perfectly good image that crash-loops on fly/railway/docker and fails every
|
|
70
|
+
// invocation on AgentCore. This is the boundary that turns that refusal into a deploy finding, so the error is
|
|
71
|
+
// carried whole into the issue rather than rethrown.
|
|
72
|
+
await loadAgentDefinition(agentDir, { cwd: workspace }).catch((error) => {
|
|
73
|
+
report.issue(`the definition does not load, so the deployed agent would not start: ${error instanceof Error ? error.message : String(error)}`);
|
|
74
|
+
});
|
|
67
75
|
// The model this deployment will run on, and where it came from. Resolved HERE so the plan side and the run side
|
|
68
76
|
// cannot disagree about it.
|
|
69
77
|
const valueFile = relative(workspace, dotEnvPath(agentDir));
|
|
@@ -232,7 +240,7 @@ async function gatherFacts(input, report) {
|
|
|
232
240
|
}
|
|
233
241
|
const paths = await buildContextPaths(workspace, agentDir, agentPrefix, authPath);
|
|
234
242
|
await checkKeptIgnoreFiles({ workspace, agentDir, agentPrefix, force, paths }, report);
|
|
235
|
-
// Write-back mechanics are fastagent's (the policy is the
|
|
243
|
+
// Write-back mechanics are fastagent's (the policy is the agent's prompt's).
|
|
236
244
|
const apt = shipsGit ? [...new Set(["git", ...(config.deploy?.apt ?? [])])] : config.deploy?.apt;
|
|
237
245
|
const container = {
|
|
238
246
|
releaseId: randomUUID(),
|
package/dist/dev-supervisor.js
CHANGED
|
@@ -119,7 +119,7 @@ export async function runDevSupervisor(placement, options = {}) {
|
|
|
119
119
|
timer = setTimeout(triggerReload, 200);
|
|
120
120
|
});
|
|
121
121
|
watcher.on("error", (error) => log.warn(`[fastagent] file watching error (${error.message}); some edits may need a manual restart`));
|
|
122
|
-
log.info(`[fastagent] watching ${WATCHED_HINT} — code edits restart the dev worker (--no-watch to disable); AGENTS.md/
|
|
122
|
+
log.info(`[fastagent] watching ${WATCHED_HINT} — code edits restart the dev worker (--no-watch to disable); AGENTS.md/SYSTEM.md/APPEND_SYSTEM.md/skills/prompts edits go live next turn without a restart`);
|
|
123
123
|
// FASTAGENT_SECRETS_DIR can move the `.env` OUT of the agent dir entirely.
|
|
124
124
|
if (!isUnderDir(dotEnvPath(placement.agentDir), placement.agentDir)) {
|
|
125
125
|
log.warn(`[fastagent] .env lives outside the agent dir (FASTAGENT_SECRETS_DIR → ${dotEnvPath(placement.agentDir)}) — it is NOT watched; restart dev after editing it`);
|
|
@@ -5,9 +5,23 @@ import type { PiAgentSessionFactory } from "./invoke-session.ts";
|
|
|
5
5
|
import type { PiSessionRecordStore } from "./session-store.ts";
|
|
6
6
|
import type { MountedTool } from "./tool.ts";
|
|
7
7
|
import { type AnyModel } from "./models.ts";
|
|
8
|
-
|
|
8
|
+
import type { DefinitionPrompt } from "./definition.ts";
|
|
9
|
+
/** What one binding reads from the definition: the prompt pieces, skills and prompt templates pi is handed. */
|
|
10
|
+
export interface PiSessionDefinition {
|
|
11
|
+
/** Replaces pi's default prompt when set (`SYSTEM.md`, L2's `base`, L1's `instructions`); absent → pi's default. */
|
|
9
12
|
systemPrompt?: string;
|
|
13
|
+
/** Added after the prompt, whichever it is (`APPEND_SYSTEM.md`). */
|
|
14
|
+
appendSystemPrompt?: string;
|
|
15
|
+
/** Project instructions (AGENTS.md); pi renders them as its project context. */
|
|
16
|
+
contextFiles?: Array<{
|
|
17
|
+
path: string;
|
|
18
|
+
content: string;
|
|
19
|
+
}>;
|
|
20
|
+
/** FastAgent's own named prompt sections: pi renders them after its own, whoever wrote the rest. */
|
|
21
|
+
sections?: Record<string, string>;
|
|
10
22
|
skills: Skill[];
|
|
23
|
+
/** The definition's own prompt templates; the machine's are added behind them. */
|
|
24
|
+
prompts?: DefinitionPrompt[];
|
|
11
25
|
}
|
|
12
26
|
export interface PiAgentSessionFactoryOptions {
|
|
13
27
|
/** Where conversations live. */
|
|
@@ -16,8 +30,11 @@ export interface PiAgentSessionFactoryOptions {
|
|
|
16
30
|
engine: () => Promise<{
|
|
17
31
|
modelRuntime: ModelRuntime;
|
|
18
32
|
}>;
|
|
19
|
-
/**
|
|
20
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The default model, resolved after extensions register their models. Optional: a session that records a model
|
|
35
|
+
* runs on it, and one that records none is refused with {@link MissingModel} when there is no default either.
|
|
36
|
+
*/
|
|
37
|
+
modelSpec?: string;
|
|
21
38
|
thinkingLevel?: ThinkingLevel;
|
|
22
39
|
tools?: MountedTool[];
|
|
23
40
|
/** Read once per binding; prompt and skills come from the same definition read. */
|
|
@@ -52,8 +69,7 @@ export declare function bindPiSession(options: BindPiSessionOptions): ReturnType
|
|
|
52
69
|
type DefinitionLoaderOptions = NonNullable<CreateAgentSessionServicesOptions["resourceLoaderOptions"]>;
|
|
53
70
|
/** The resource posture a fastagent definition asks pi for — ONE definition of it, for both assemblies. */
|
|
54
71
|
export declare function definitionResourceLoaderOptions(source: {
|
|
55
|
-
|
|
56
|
-
skills: () => Skill[];
|
|
72
|
+
definition: PiSessionDefinition;
|
|
57
73
|
/** {@link readMachine} for this workspace — resolved by the caller, because this function is synchronous. */
|
|
58
74
|
machine: Machine;
|
|
59
75
|
extensionPaths?: readonly string[];
|