@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
|
@@ -47,10 +47,21 @@ export interface SessionSettings {
|
|
|
47
47
|
};
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* `defaults` is the assembly's configured pair — what a session with no overrides runs on. An agent may set no default
|
|
52
|
+
* model, so a session runs on the model it records, else the default, else none: undefined then, and the session
|
|
53
|
+
* cannot run until it is given one ({@link MissingModel}).
|
|
54
|
+
*/
|
|
51
55
|
export declare function resolveSessionSettings(entries: OverrideEntryLike[], models: Models, defaults: {
|
|
52
|
-
model
|
|
56
|
+
model?: AnyModel;
|
|
53
57
|
thinkingLevel: ThinkingLevel;
|
|
54
|
-
}): SessionSettings;
|
|
58
|
+
}): SessionSettings | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* A turn has no model to run on: its session records none this registry knows, and the agent sets no default. Its own
|
|
61
|
+
* class so the invoke answers it with `missing_model` instead of a prose failure.
|
|
62
|
+
*/
|
|
63
|
+
export declare class MissingModel extends Error {
|
|
64
|
+
constructor(session: string, entries: OverrideEntryLike[]);
|
|
65
|
+
}
|
|
55
66
|
/** The entries on the session's ACTIVE path, root→leaf — what every last-wins settings read walks. */
|
|
56
67
|
export declare function activePath(record: SessionManager, from?: string): OverrideEntryLike[];
|
|
@@ -51,7 +51,11 @@ export function lastOverrideEntries(entries) {
|
|
|
51
51
|
}
|
|
52
52
|
return { model, thinkingLevel };
|
|
53
53
|
}
|
|
54
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* `defaults` is the assembly's configured pair — what a session with no overrides runs on. An agent may set no default
|
|
56
|
+
* model, so a session runs on the model it records, else the default, else none: undefined then, and the session
|
|
57
|
+
* cannot run until it is given one ({@link MissingModel}).
|
|
58
|
+
*/
|
|
55
59
|
export function resolveSessionSettings(entries, models, defaults) {
|
|
56
60
|
const recorded = lastOverrideEntries(entries);
|
|
57
61
|
const dropped = {};
|
|
@@ -64,6 +68,8 @@ export function resolveSessionSettings(entries, models, defaults) {
|
|
|
64
68
|
else
|
|
65
69
|
dropped.model = `${recorded.model.provider}/${recorded.model.modelId}`;
|
|
66
70
|
}
|
|
71
|
+
if (!model)
|
|
72
|
+
return undefined;
|
|
67
73
|
const availableThinkingLevels = getSupportedThinkingLevels(model);
|
|
68
74
|
let thinkingLevel = clampThinkingLevel(model, defaults.thinkingLevel);
|
|
69
75
|
if (recorded.thinkingLevel !== undefined) {
|
|
@@ -87,6 +93,19 @@ export function resolveSessionSettings(entries, models, defaults) {
|
|
|
87
93
|
...(dropped.model || dropped.thinkingLevel ? { dropped } : {}),
|
|
88
94
|
};
|
|
89
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* A turn has no model to run on: its session records none this registry knows, and the agent sets no default. Its own
|
|
98
|
+
* class so the invoke answers it with `missing_model` instead of a prose failure.
|
|
99
|
+
*/
|
|
100
|
+
export class MissingModel extends Error {
|
|
101
|
+
constructor(session, entries) {
|
|
102
|
+
const recorded = lastOverrideEntries(entries).model;
|
|
103
|
+
super(`session "${session}" has no model: ${recorded
|
|
104
|
+
? `the model it records, ${recorded.provider}/${recorded.modelId}, is not one this agent knows,`
|
|
105
|
+
: "it records none,"} and the agent sets no default — give the session one (session control's update({ model })), or the agent ` +
|
|
106
|
+
`a default ("model" in fastagent.config.ts, or FASTAGENT_MODEL)`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
90
109
|
/** The entries on the session's ACTIVE path, root→leaf — what every last-wins settings read walks. */
|
|
91
110
|
export function activePath(record, from) {
|
|
92
111
|
// `from` asks a different question: the path a leaf move is ABOUT to make active, which a caller validating a patch
|
|
@@ -18,6 +18,26 @@ export declare function classifyRetryable(details: string, signal: {
|
|
|
18
18
|
* the recorded one cannot disagree.
|
|
19
19
|
*/
|
|
20
20
|
export declare function answerOutcome(message: AssistantMessage): AnswerOutcome | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* An answer that failed while its run's abort was in effect, as what it was: aborted. Undefined when that is not the
|
|
23
|
+
* case (no abort, or not a failure). pi's own reading is `signal.aborted ? "aborted" : "error"` (`handleRunFailure`,
|
|
24
|
+
* the providers), but its request-setup path (`lazyStream`: auth, request preparation) writes "error" whatever the
|
|
25
|
+
* signal (pi 0.99.2). A run stopped during a tool lands there: the loop asks the model once more, and the setup throws
|
|
26
|
+
* the abort. Recorded this way, the answer's {@link answerOutcome} is the `aborted` the run settled with, live and read
|
|
27
|
+
* back. A failure recorded BEFORE the abort (a provider error whose retry was then stopped) stays as recorded.
|
|
28
|
+
*/
|
|
29
|
+
export declare function abortedAnswer(message: AssistantMessage, runSignal: AbortSignal | undefined): AssistantMessage | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* An answer's recorded thinking: its published thinking blocks, in order, joined with nothing between them, exactly as
|
|
32
|
+
* their live deltas add up (the stream marks no block boundary, and the answer's `text` is joined the same way).
|
|
33
|
+
* Undefined when it has none.
|
|
34
|
+
*/
|
|
35
|
+
export declare function answerThinking(message: AssistantMessage): string | undefined;
|
|
36
|
+
/** Whether a live thinking delta is streamed: the block it extends is one {@link answerThinking} will publish. */
|
|
37
|
+
export declare function streamsThinkingDelta(event: {
|
|
38
|
+
contentIndex: number;
|
|
39
|
+
partial: AssistantMessage;
|
|
40
|
+
}): boolean;
|
|
21
41
|
/**
|
|
22
42
|
* Whether a finished tool call asked to end its run (pi's `terminate`: returned by the tool, or set by a `tool_call`
|
|
23
43
|
* hook that blocks it; pi 0.99.2's `tool_result` hook cannot set it). After a batch whose EVERY result asks it, pi
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** The turn mechanism's ENGINE-agnostic half: the parts that describe a turn rather than pi. */
|
|
2
2
|
import { stripVTControlCharacters } from "node:util";
|
|
3
|
-
import { contentText } from "@earendil-works/pi-ai";
|
|
3
|
+
import { contentText, } from "@earendil-works/pi-ai";
|
|
4
4
|
import { ABORTED_CODE } from "../../agent.js";
|
|
5
5
|
import { log } from "../../log.js";
|
|
6
6
|
export function inProcessLease() {
|
|
@@ -91,6 +91,50 @@ export function answerOutcome(message) {
|
|
|
91
91
|
return undefined;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* An answer that failed while its run's abort was in effect, as what it was: aborted. Undefined when that is not the
|
|
96
|
+
* case (no abort, or not a failure). pi's own reading is `signal.aborted ? "aborted" : "error"` (`handleRunFailure`,
|
|
97
|
+
* the providers), but its request-setup path (`lazyStream`: auth, request preparation) writes "error" whatever the
|
|
98
|
+
* signal (pi 0.99.2). A run stopped during a tool lands there: the loop asks the model once more, and the setup throws
|
|
99
|
+
* the abort. Recorded this way, the answer's {@link answerOutcome} is the `aborted` the run settled with, live and read
|
|
100
|
+
* back. A failure recorded BEFORE the abort (a provider error whose retry was then stopped) stays as recorded.
|
|
101
|
+
*/
|
|
102
|
+
export function abortedAnswer(message, runSignal) {
|
|
103
|
+
return message.stopReason === "error" && runSignal?.aborted === true
|
|
104
|
+
? { ...message, stopReason: "aborted" }
|
|
105
|
+
: undefined;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Whether a thinking block is PUBLISHED: readable reasoning. A redacted one holds a provider's opaque payload and a
|
|
109
|
+
* placeholder (pi writes "[Reasoning redacted]" for it), not reasoning anyone can read, so it is neither streamed nor
|
|
110
|
+
* read back. The ONE rule: the live `message_delta { channel: "thinking" }` ({@link streamsThinkingDelta}) and the
|
|
111
|
+
* `assistant` entry's `thinking` ({@link answerThinking}) both apply it, so a client reads the same text either way.
|
|
112
|
+
*
|
|
113
|
+
* One exception, by when the flag is read: live reads it as each delta arrives, the record as the answer ended. pi's
|
|
114
|
+
* Bedrock path flags a block redacted when its first encrypted chunk arrives, and its readable text and its encrypted
|
|
115
|
+
* chunks are separate fields, so a block that streamed readable text BEFORE turning redacted had that text streamed
|
|
116
|
+
* and is then not read back. Not separable after the fact: the record keeps one flag and one string per block, with
|
|
117
|
+
* pi's placeholder appended. Whether a Bedrock model sends both in one block is unverified.
|
|
118
|
+
*/
|
|
119
|
+
function isPublishedThinking(block) {
|
|
120
|
+
return block?.type === "thinking" && block.redacted !== true;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* An answer's recorded thinking: its published thinking blocks, in order, joined with nothing between them, exactly as
|
|
124
|
+
* their live deltas add up (the stream marks no block boundary, and the answer's `text` is joined the same way).
|
|
125
|
+
* Undefined when it has none.
|
|
126
|
+
*/
|
|
127
|
+
export function answerThinking(message) {
|
|
128
|
+
const text = message.content
|
|
129
|
+
.filter(isPublishedThinking)
|
|
130
|
+
.map((block) => block.thinking)
|
|
131
|
+
.join("");
|
|
132
|
+
return text === "" ? undefined : text;
|
|
133
|
+
}
|
|
134
|
+
/** Whether a live thinking delta is streamed: the block it extends is one {@link answerThinking} will publish. */
|
|
135
|
+
export function streamsThinkingDelta(event) {
|
|
136
|
+
return isPublishedThinking(event.partial.content[event.contentIndex]);
|
|
137
|
+
}
|
|
94
138
|
/**
|
|
95
139
|
* Whether a finished tool call asked to end its run (pi's `terminate`: returned by the tool, or set by a `tool_call`
|
|
96
140
|
* hook that blocks it; pi 0.99.2's `tool_result` hook cannot set it). After a batch whose EVERY result asks it, pi
|
package/dist/paths.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare const AGENT_MODELS_FILE = "models.json";
|
|
|
23
23
|
export declare const AGENT_MODEL_CATALOG_FILE = "models-store.json";
|
|
24
24
|
export interface ResolvedPlacement {
|
|
25
25
|
/**
|
|
26
|
-
* The AGENT directory — where the definition (
|
|
26
|
+
* The AGENT directory — where the definition (SYSTEM.md/skills/tools/channels/routines), the config, and the
|
|
27
27
|
* machinery dirs (`.secrets/`, `.state/`) live.
|
|
28
28
|
*/
|
|
29
29
|
agentDir: string;
|
package/dist/paths.js
CHANGED
|
@@ -35,7 +35,18 @@ export const AGENT_MODEL_CATALOG_FILE = "models-store.json";
|
|
|
35
35
|
* The definition paths an agent LOADS content from — the surface a second agent must not be scaffolded inside
|
|
36
36
|
* ({@link agentDefinitionOwner}), because the outer agent would read it as its own skills/tools.
|
|
37
37
|
*/
|
|
38
|
-
const LOADED_SURFACE = [
|
|
38
|
+
const LOADED_SURFACE = [
|
|
39
|
+
"SYSTEM.md",
|
|
40
|
+
"APPEND_SYSTEM.md",
|
|
41
|
+
"skills",
|
|
42
|
+
"prompts",
|
|
43
|
+
"tools",
|
|
44
|
+
"channels",
|
|
45
|
+
"routines",
|
|
46
|
+
// pi's and the standard spellings the definition also reads: `.pi/skills`, `.pi/prompts`, `.agents/skills`.
|
|
47
|
+
".pi",
|
|
48
|
+
".agents",
|
|
49
|
+
];
|
|
39
50
|
/**
|
|
40
51
|
* "Not there" and "could not look" are different answers, and only the first may read as an absence: an agent
|
|
41
52
|
* directory the caller cannot enter (EACCES) reported as no-agent-here ends in `run \`fastagent init\` to
|
package/dist/pi.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type { AgentTool, ExecutionEnv, Skill, SkillDiagnostic } from "@earendil-
|
|
|
6
6
|
/** A conversation record, as the tool runtime and the control plane hold it. */
|
|
7
7
|
export type { SessionManager, SessionEntry as PiSessionEntry } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
export { availableModelsFromDir, createPiAgentFromDir, type CreatePiAgentFromDirOptions, refreshMachineModelCatalog, refreshModelCatalog, } from "./engines/pi/open.ts";
|
|
9
|
-
export type { LoadedDefinition, SkillCollision } from "./engines/pi/definition.ts";
|
|
9
|
+
export type { DefinitionDiagnostic, DefinitionFile, DefinitionPrompt, DefinitionShadow, LoadedDefinition, SkillCollision, } from "./engines/pi/definition.ts";
|
|
10
10
|
export { defineConfig, listModels, resolveModel, type FastagentConfig, } from "./engines/pi/config.ts";
|
|
11
11
|
export type { SessionObserver } from "./engines/pi/turn-kit.ts";
|
|
12
12
|
export { inProcessLease, type Lease, type Release } from "./engines/pi/turn-kit.ts";
|
package/dist/scaffold/init.js
CHANGED
|
@@ -35,8 +35,8 @@ export async function scaffoldAgent(dir, options = {}) {
|
|
|
35
35
|
content: baseTemplate(`skills/writing-great-skills/${name}`),
|
|
36
36
|
});
|
|
37
37
|
const files = [
|
|
38
|
-
//
|
|
39
|
-
{ rel: join(root, "
|
|
38
|
+
// Standing instructions, added to pi's default prompt (SYSTEM.md would replace it).
|
|
39
|
+
{ rel: join(root, "APPEND_SYSTEM.md"), content: baseTemplate("APPEND_SYSTEM.md") },
|
|
40
40
|
// The example skill: how to author skills well — the core of self-iteration.
|
|
41
41
|
skill("SKILL.md"),
|
|
42
42
|
skill("GLOSSARY.md"),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Standing instructions
|
|
2
|
+
|
|
3
|
+
These instructions are added to your system prompt on every turn. This file, `APPEND_SYSTEM.md`, is re-read every turn along with the rest of your definition (`skills/` — capabilities you load when a task calls for them; `prompts/` — prompt templates; `tools/` — code tools your author added, in the same directory as this file). An edit to any of them takes effect on your next message, no restart.
|
|
4
|
+
|
|
5
|
+
Your definition is this directory: `APPEND_SYSTEM.md`, `skills/`, `tools/`, and the config beside them. Your WORKSPACE is the directory you were started in — the project you work on. It may be this same directory, or the one containing it; `fastagent info` prints both. Use only the tools actually listed in your system prompt. If the workspace has an `AGENTS.md`, it is project context — follow it without assuming a file tool is available.
|
|
6
|
+
|
|
7
|
+
When your mounted tools allow it, you can improve yourself. When a task reveals something durable — a repeatable process, a standing preference, a hard-won fact — write it into your definition instead of losing it:
|
|
8
|
+
|
|
9
|
+
- A repeatable process or capability → a new skill beside this file: `skills/<name>/SKILL.md`. Read `skills/writing-great-skills/SKILL.md` first; it is the guide to authoring skills well.
|
|
10
|
+
- A standing instruction or fact → edit this file.
|
|
11
|
+
|
|
12
|
+
Keep both lean: include only what changes your behavior, and delete what no longer earns its place.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// fastagent.config.ts — deployment choices only (model / http; code tools auto-discover from tools/).
|
|
2
|
-
// Your agent's
|
|
2
|
+
// Your agent's prompt lives in SYSTEM.md / APPEND_SYSTEM.md; its capabilities in skills/ + tools/ — never here.
|
|
3
3
|
// An AGENTS.md in the WORKSPACE (the directory the agent is started in) is read as project context.
|
|
4
4
|
// Model precedence: `--model` flag > FASTAGENT_MODEL env > this default.
|
|
5
5
|
// No model is preset: `fastagent dev` shows the full model catalog (models you already have
|
package/dist/session.d.ts
CHANGED
|
@@ -347,6 +347,10 @@ export type UserMessageEvent = SessionEvent<"user_message", {
|
|
|
347
347
|
}> & {
|
|
348
348
|
runId: string;
|
|
349
349
|
};
|
|
350
|
+
/**
|
|
351
|
+
* A piece of the answer being written. The `thinking` deltas of one answer add up to its `assistant` entry's
|
|
352
|
+
* `thinking`, as its `text` deltas add up to its `text`; redacted reasoning is streamed in neither.
|
|
353
|
+
*/
|
|
350
354
|
export type MessageDeltaEvent = SessionEvent<"message_delta", {
|
|
351
355
|
channel: "text" | "thinking";
|
|
352
356
|
delta: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastagent-sh/fastagent",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.4",
|
|
4
4
|
"description": "Vibe first. Then FastAgent: turn a local agent directory into a live service in your app, on Telegram, Slack, Feishu, or behind any channel.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Persona
|
|
2
|
-
|
|
3
|
-
You are this workspace's agent. This file is your identity — it overrides the engine's default identity line, and it is re-read every turn along with the rest of your definition (`skills/` — capabilities you load when a task calls for them; `tools/` — code tools your author added, in the same directory as this file). An edit to any of them takes effect on your next message, no restart.
|
|
4
|
-
|
|
5
|
-
Your definition is this directory: `persona.md`, `skills/`, `tools/`, and the config beside them. Your WORKSPACE is the directory you were started in — the project you work on. It may be this same directory, or the one containing it; `fastagent info` prints both. Use only the tools actually listed in your system prompt. If the workspace has an `AGENTS.md`, it is project context — follow it without assuming a file tool is available.
|
|
6
|
-
|
|
7
|
-
When your mounted tools allow it, you can improve yourself. When a task reveals something durable — a repeatable process, a standing preference, a hard-won fact — write it into your definition instead of losing it:
|
|
8
|
-
|
|
9
|
-
- A repeatable process or capability → a new skill beside this file: `skills/<name>/SKILL.md`. Only the `skills/` next to this file is scanned. Read `skills/writing-great-skills/SKILL.md` first; it is the guide to authoring skills well.
|
|
10
|
-
- A standing instruction or fact → edit this file.
|
|
11
|
-
|
|
12
|
-
Keep both lean: include only what changes your behavior, and delete what no longer earns its place.
|