@fastagent-sh/fastagent 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/channels/agentcore-protocol.d.ts +112 -0
- package/dist/channels/agentcore-protocol.js +22 -0
- package/dist/channels/agentcore-service.d.ts +0 -4
- package/dist/channels/agentcore-service.js +1 -6
- package/dist/channels/agentcore-state.d.ts +5 -11
- package/dist/channels/agentcore-state.js +4 -1
- package/dist/channels/agentcore.d.ts +1 -67
- package/dist/channels/agentcore.js +90 -89
- package/dist/channels/control.d.ts +1 -1
- package/dist/channels/control.js +13 -62
- package/dist/channels/discover.d.ts +0 -1
- package/dist/channels/discover.js +1 -3
- package/dist/channels/feishu/context-buffer.d.ts +6 -0
- package/dist/channels/feishu/context-buffer.js +0 -38
- package/dist/channels/feishu/crypto.d.ts +0 -2
- package/dist/channels/feishu/crypto.js +3 -8
- package/dist/channels/feishu/feishu-api.js +2 -4
- package/dist/channels/feishu/feishu.js +56 -115
- package/dist/channels/feishu/parse.d.ts +4 -3
- package/dist/channels/feishu/parse.js +5 -4
- package/dist/channels/feishu/register-webhook.d.ts +5 -5
- package/dist/channels/feishu/register-webhook.js +43 -57
- package/dist/channels/feishu/scaffold/feishu-send.ts +12 -67
- package/dist/channels/feishu/setup-mode.d.ts +30 -0
- package/dist/channels/feishu/setup-mode.js +26 -0
- package/dist/channels/feishu/shared-api.d.ts +10 -0
- package/dist/channels/feishu/shared-api.js +38 -0
- package/dist/channels/http.d.ts +0 -8
- package/dist/channels/http.js +4 -56
- package/dist/channels/kit/turn-runner.d.ts +59 -0
- package/dist/channels/kit/turn-runner.js +84 -0
- package/dist/channels/lark/scaffold/lark-send.ts +12 -67
- package/dist/channels/registration.d.ts +36 -1
- package/dist/channels/registration.js +57 -1
- package/dist/channels/secret.d.ts +1 -0
- package/dist/channels/secret.js +16 -0
- package/dist/channels/slack/config-api.d.ts +35 -3
- package/dist/channels/slack/config-api.js +51 -9
- package/dist/channels/slack/manifest.js +5 -1
- package/dist/channels/slack/onboard.d.ts +5 -5
- package/dist/channels/slack/onboard.js +41 -20
- package/dist/channels/slack/register-webhook.d.ts +3 -2
- package/dist/channels/slack/register-webhook.js +34 -14
- package/dist/channels/slack/scaffold/channel.ts +3 -10
- package/dist/channels/slack/scaffold/slack-send.ts +18 -126
- package/dist/channels/slack/shared-api.d.ts +10 -0
- package/dist/channels/slack/shared-api.js +34 -0
- package/dist/channels/slack/slack-api.d.ts +20 -2
- package/dist/channels/slack/slack-api.js +81 -22
- package/dist/channels/slack/slack.d.ts +0 -10
- package/dist/channels/slack/slack.js +60 -99
- package/dist/channels/sse.d.ts +4 -0
- package/dist/channels/sse.js +66 -0
- package/dist/channels/telegram/register-webhook.d.ts +6 -9
- package/dist/channels/telegram/register-webhook.js +44 -42
- package/dist/channels/telegram/telegram.js +47 -142
- package/dist/channels/wait-health.js +7 -4
- package/dist/cli/add-feishu.js +3 -10
- package/dist/cli/add-slack.js +7 -20
- package/dist/cli/commands/add.d.ts +0 -1
- package/dist/cli/commands/add.js +22 -31
- package/dist/cli/commands/chat.js +6 -12
- package/dist/cli/commands/deploy/agentcore.d.ts +2 -0
- package/dist/cli/commands/deploy/agentcore.js +178 -0
- package/dist/cli/commands/deploy/docker.d.ts +2 -0
- package/dist/cli/commands/deploy/docker.js +119 -0
- package/dist/cli/commands/deploy/fly.d.ts +2 -0
- package/dist/cli/commands/deploy/fly.js +131 -0
- package/dist/cli/commands/deploy/railway.d.ts +2 -0
- package/dist/cli/commands/deploy/railway.js +71 -0
- package/dist/cli/commands/deploy/shared.d.ts +114 -0
- package/dist/cli/commands/deploy/shared.js +124 -0
- package/dist/cli/commands/deploy.d.ts +17 -49
- package/dist/cli/commands/deploy.js +39 -618
- package/dist/cli/commands/dev.js +17 -46
- package/dist/cli/commands/fire.js +6 -12
- package/dist/cli/commands/info.js +2 -1
- package/dist/cli/commands/invoke.js +4 -11
- package/dist/cli/commands/schedule.js +1 -1
- package/dist/cli/commands/start.js +17 -46
- package/dist/cli/commands/tool.js +3 -8
- package/dist/cli/kernel.d.ts +0 -2
- package/dist/cli/kernel.js +0 -2
- package/dist/cli/program.js +7 -12
- package/dist/cli/serve.d.ts +36 -19
- package/dist/cli/serve.js +91 -7
- package/dist/cli/shared.d.ts +13 -18
- package/dist/cli/shared.js +20 -4
- package/dist/deploy/agentcore/forwarder.js +250 -0
- package/dist/deploy/agentcore/plan.d.ts +23 -2
- package/dist/deploy/agentcore/plan.js +29 -244
- package/dist/deploy/agentcore/run.d.ts +4 -20
- package/dist/deploy/agentcore/run.js +22 -7
- package/dist/deploy/channel-ingress.js +1 -1
- package/dist/deploy/docker/run.d.ts +17 -3
- package/dist/deploy/docker/run.js +30 -8
- package/dist/deploy/fly/plan.js +7 -0
- package/dist/deploy/fly/run.d.ts +27 -0
- package/dist/deploy/fly/run.js +102 -20
- package/dist/deploy/hosts.d.ts +5 -0
- package/dist/deploy/hosts.js +4 -0
- package/dist/deploy/preflight.js +3 -3
- package/dist/deploy/railway/plan.d.ts +5 -0
- package/dist/deploy/railway/plan.js +7 -0
- package/dist/deploy/railway/run.d.ts +2 -1
- package/dist/deploy/railway/run.js +5 -4
- package/dist/deploy/secrets.js +0 -17
- package/dist/engines/pi/agent-session-factory.d.ts +37 -17
- package/dist/engines/pi/agent-session-factory.js +109 -85
- package/dist/engines/pi/config.d.ts +1 -1
- package/dist/engines/pi/create.d.ts +22 -39
- package/dist/engines/pi/create.js +58 -73
- package/dist/engines/pi/definition.js +8 -8
- package/dist/engines/pi/invoke-session.js +13 -30
- package/dist/engines/pi/open.d.ts +2 -2
- package/dist/engines/pi/open.js +31 -41
- package/dist/engines/pi/retry-event.d.ts +6 -0
- package/dist/engines/pi/retry-event.js +15 -0
- package/dist/engines/pi/session-builder.js +26 -113
- package/dist/engines/pi/session-control.d.ts +10 -18
- package/dist/engines/pi/session-control.js +47 -81
- package/dist/engines/pi/session-settings.d.ts +1 -1
- package/dist/engines/pi/session-settings.js +1 -1
- package/dist/engines/pi/session-store.d.ts +12 -17
- package/dist/engines/pi/session-store.js +18 -40
- package/dist/engines/pi/tool-context.d.ts +3 -3
- package/dist/engines/pi/tool.d.ts +6 -9
- package/dist/engines/pi/tool.js +1 -0
- package/dist/feishu.d.ts +1 -0
- package/dist/feishu.js +1 -0
- package/dist/lark.d.ts +1 -0
- package/dist/lark.js +1 -0
- package/dist/loader.d.ts +2 -0
- package/dist/loader.js +5 -0
- package/dist/log.d.ts +9 -17
- package/dist/log.js +25 -30
- package/dist/paths.d.ts +7 -3
- package/dist/paths.js +17 -4
- package/dist/scaffold/add-channel.d.ts +6 -1
- package/dist/scaffold/add-channel.js +48 -65
- package/dist/schedule/wake-alarm.d.ts +1 -12
- package/dist/schedule/wake-alarm.js +2 -3
- package/dist/service.d.ts +23 -22
- package/dist/service.js +7 -73
- package/dist/session-remote.d.ts +5 -5
- package/dist/session-remote.js +25 -28
- package/dist/session.d.ts +1 -1
- package/dist/slack.d.ts +2 -0
- package/dist/slack.js +1 -0
- package/dist/tunnel.d.ts +16 -6
- package/dist/tunnel.js +53 -12
- package/package.json +5 -4
- package/dist/channels/slack/bot-auth.d.ts +0 -15
- package/dist/channels/slack/bot-auth.js +0 -135
|
@@ -29,7 +29,7 @@ export interface FastagentConfig {
|
|
|
29
29
|
* per-boot bearer token and write `<stateRoot>/control.json` for local discovery. The serve
|
|
30
30
|
* binds all interfaces by default, so the routes are LAN-reachable with the token as the only
|
|
31
31
|
* protection — bind loopback (`--bind 127.0.0.1`; not `http.host`, which travels into a deployed
|
|
32
|
-
* image), firewall the port, or wrap it for real exposure (design §14).
|
|
32
|
+
* image), firewall the port, or wrap it for real exposure (docs/design/session-control.md §14).
|
|
33
33
|
*/
|
|
34
34
|
sessionControl?: boolean;
|
|
35
35
|
/** Deploy-time declarations for what the agent needs on the box, so real agents don't hand-write a
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Agent assembly (configuration-time): the engine assets (tools, prompt) plus the reusable ladder
|
|
3
|
-
* that puts a pi agent together.
|
|
4
|
-
*
|
|
5
|
-
* L2 createPiAgentFromDefinition(dir, options) — load a definition directory, assemble, then L1.
|
|
6
|
-
* L1 createPiAgent(options) — assemble from typed parts (the canonical ctor).
|
|
7
|
-
* L0 createPiAgentFromSession({ sessionFactory }) — in invoke-session.ts (the turn mechanism).
|
|
8
|
-
*
|
|
9
|
-
* Above L2 sits the agent opener createPiAgentFromDir (open.ts), which both `dev` and
|
|
10
|
-
* `start` drive. Each rung calls the one below; options narrow as you go up (L2 owns systemPrompt/skills —
|
|
11
|
-
* they come from the definition; the openers own model/tools — from config resolution).
|
|
12
|
-
*/
|
|
13
|
-
import { type ExecutionEnv, type Skill, type ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
1
|
+
import type { ExecutionEnv, Skill, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
14
2
|
import { type ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
15
3
|
import type { Provider } from "@earendil-works/pi-ai";
|
|
16
4
|
import type { Agent } from "../../agent.ts";
|
|
@@ -66,19 +54,14 @@ export interface AssembleSystemPromptOptions {
|
|
|
66
54
|
path: string;
|
|
67
55
|
content: string;
|
|
68
56
|
}>;
|
|
69
|
-
/** ③ Skills for the <available_skills> listing. */
|
|
70
|
-
skills?: Skill[];
|
|
71
|
-
/** ④ Env context, caller-provided (keeps this function pure). Omitted = segment omitted. */
|
|
72
|
-
cwd?: string;
|
|
73
57
|
}
|
|
74
58
|
export declare function assembleSystemPrompt(options: AssembleSystemPromptOptions): string;
|
|
75
59
|
/**
|
|
76
|
-
*
|
|
77
|
-
* the
|
|
78
|
-
*
|
|
79
|
-
* registry. Called synchronously, exactly once, before the agent is returned. Not public surface.
|
|
60
|
+
* The assembly, as a value: what every rung builds and what the agent runs on. The opener hands it to
|
|
61
|
+
* the control plane too — boundary mutations contend on the SAME lease and validate against the SAME
|
|
62
|
+
* registry the runs use, which is only true if there is one of each to hand over.
|
|
80
63
|
*/
|
|
81
|
-
export
|
|
64
|
+
export interface PiAssembly {
|
|
82
65
|
lease: Lease;
|
|
83
66
|
sessionFactory: PiAgentSessionFactory;
|
|
84
67
|
/** The registry and configured model, resolved on first use (a credential read is async). */
|
|
@@ -88,8 +71,9 @@ export type PiAssemblyParts = {
|
|
|
88
71
|
}>;
|
|
89
72
|
/** The configured reasoning effort — the other half of the pair a session without overrides runs on. */
|
|
90
73
|
thinkingLevel: ThinkingLevel;
|
|
91
|
-
}
|
|
92
|
-
|
|
74
|
+
}
|
|
75
|
+
/** The agent an assembly runs as: the L0 over its lease and session factory. */
|
|
76
|
+
export declare function agentOf(assembly: PiAssembly, observer?: SessionObserver): Agent;
|
|
93
77
|
/** L1 options. Tier 1: model (spec) + instructions + tools. Tier 2: the injectable ports. */
|
|
94
78
|
export interface CreatePiAgentOptions {
|
|
95
79
|
/** Model spec "provider/modelId" (e.g. "openai-codex/gpt-5.5"), resolved against {@link models}. */
|
|
@@ -99,16 +83,15 @@ export interface CreatePiAgentOptions {
|
|
|
99
83
|
/**
|
|
100
84
|
* The system prompt itself — no engine base and no wrapping (unlike the directory path, which
|
|
101
85
|
* assembles the engine base + AGENTS.md as segment ② + persona.md as segment ①). A plain string or
|
|
102
|
-
* a factory
|
|
86
|
+
* a factory evaluated once per invoke, never during construction. Pi appends the skills listing
|
|
87
|
+
* when read is active.
|
|
103
88
|
*
|
|
104
89
|
* Not byte-for-byte verbatim: pi appends its own `Current working directory:` line to whatever
|
|
105
90
|
* prompt it is given. What this rung guarantees is that no engine IDENTITY is imposed — a
|
|
106
91
|
* hand-built agent is not told it is a coding assistant.
|
|
107
92
|
*/
|
|
108
93
|
instructions?: string | (() => string);
|
|
109
|
-
/** The tool set to mount
|
|
110
|
-
* {@link DefineToolOptions}) widens into {@link MountedTool}, which additionally admits pi's default
|
|
111
|
-
* coding tools — those bind their workspace at construction rather than reading a turn context. */
|
|
94
|
+
/** The tool set to mount: authored tools or pi's cwd-bound coding tools, both AgentTool. */
|
|
112
95
|
tools?: MountedTool[];
|
|
113
96
|
skills?: Skill[];
|
|
114
97
|
/**
|
|
@@ -126,12 +109,9 @@ export interface CreatePiAgentOptions {
|
|
|
126
109
|
/** Session persistence. Defaults to in-memory; inject piSessionRecordStore for restart-surviving
|
|
127
110
|
* continuity. */
|
|
128
111
|
sessions?: PiSessionRecordStore;
|
|
129
|
-
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* built for) or author-written `tools/`, which are code and can import anything. Not a
|
|
133
|
-
* sandbox — see {@link createPiAgentFromDefinition} for the rung where it also reads the
|
|
134
|
-
* definition. */
|
|
112
|
+
/** Supplies the working directory at L1 (default: process.cwd()), which loads no definition.
|
|
113
|
+
* At L2 it also reads persona.md and skills/.
|
|
114
|
+
* Tools and project-context discovery use the local process directly; this is not a sandbox. */
|
|
135
115
|
env?: ExecutionEnv;
|
|
136
116
|
/** Single-writer lease. Defaults to in-process fail-fast inProcessLease(). */
|
|
137
117
|
lease?: Lease;
|
|
@@ -180,15 +160,18 @@ export interface CreatePiAgentFromDefinitionOptions {
|
|
|
180
160
|
lease?: Lease;
|
|
181
161
|
/** Observation-plane tap; see {@link CreatePiAgentOptions.observer}. */
|
|
182
162
|
observer?: SessionObserver;
|
|
183
|
-
/** INTERNAL seam for hub wiring; see {@link OnAssembly}. */
|
|
184
|
-
onAssembly?: OnAssembly;
|
|
185
163
|
}
|
|
186
164
|
/**
|
|
187
|
-
* L2
|
|
188
|
-
*
|
|
165
|
+
* L2, as the value: load the directory (base + AGENTS.md + skills + env) and assemble. Returns the
|
|
166
|
+
* definition so callers can surface diagnostics/collisions. The opener consumes this form because
|
|
167
|
+
* the control plane needs the parts; {@link createPiAgentFromDefinition} is it plus the L0.
|
|
189
168
|
*/
|
|
169
|
+
export declare function assemblePiFromDefinition(dir: string, options: Omit<CreatePiAgentFromDefinitionOptions, "observer">): Promise<{
|
|
170
|
+
assembly: PiAssembly;
|
|
171
|
+
definition: LoadedDefinition;
|
|
172
|
+
}>;
|
|
173
|
+
/** L2: "point at a directory → agent": {@link assemblePiFromDefinition} under the L0. */
|
|
190
174
|
export declare function createPiAgentFromDefinition(dir: string, options: CreatePiAgentFromDefinitionOptions): Promise<{
|
|
191
175
|
agent: Agent;
|
|
192
176
|
definition: LoadedDefinition;
|
|
193
177
|
}>;
|
|
194
|
-
export {};
|
|
@@ -9,15 +9,20 @@
|
|
|
9
9
|
* Above L2 sits the agent opener createPiAgentFromDir (open.ts), which both `dev` and
|
|
10
10
|
* `start` drive. Each rung calls the one below; options narrow as you go up (L2 owns systemPrompt/skills —
|
|
11
11
|
* they come from the definition; the openers own model/tools — from config resolution).
|
|
12
|
+
*
|
|
13
|
+
* Every rung assembles the same VALUE first — a {@link PiAssembly}: the lease, the session factory
|
|
14
|
+
* and the engine thunk — and the agent is that value under the L0. The opener needs the value
|
|
15
|
+
* itself (the control plane contends on the same lease and validates against the same registry), so
|
|
16
|
+
* `assemblePiFromDefinition` hands it out and `createPiAgentFromDefinition` is it plus the L0.
|
|
12
17
|
*/
|
|
13
|
-
import {
|
|
18
|
+
import { toUSVString } from "node:util";
|
|
14
19
|
import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node";
|
|
15
20
|
import { createCodingTools, createPowerShellTool, createReadOnlyTools, } from "@earendil-works/pi-coding-agent";
|
|
16
21
|
import { defaultAuthPath, resolveModel } from "./config.js";
|
|
17
22
|
import { resolveSecretsDir } from "../../paths.js";
|
|
18
23
|
import { loadAgentDefinition, loadExtensionPaths } from "./definition.js";
|
|
19
24
|
import { reportFindingsIfChanged } from "./report.js";
|
|
20
|
-
import { isDeferredTool, loadTools, mergeDiscoveredTools
|
|
25
|
+
import { isDeferredTool, loadTools, mergeDiscoveredTools } from "./tool.js";
|
|
21
26
|
import { withSearchTool } from "./search-tools.js";
|
|
22
27
|
import { createPiAgentFromSession } from "./invoke-session.js";
|
|
23
28
|
import { piAgentSessionFactory } from "./agent-session-factory.js";
|
|
@@ -50,7 +55,21 @@ import { inProcessLease } from "./turn-kit.js";
|
|
|
50
55
|
*/
|
|
51
56
|
export const CODING_TOOL_NAMES = ["read", "grep", "find", "ls", "bash", "edit", "write"];
|
|
52
57
|
export function piAllCodingTools(cwd) {
|
|
53
|
-
const mutating = createCodingTools(cwd
|
|
58
|
+
const mutating = createCodingTools(cwd, {
|
|
59
|
+
bash: {
|
|
60
|
+
spawnHook(context) {
|
|
61
|
+
const { env } = context;
|
|
62
|
+
const id = env.PI_SESSION_ID;
|
|
63
|
+
delete env.PI_SESSION_ID_ENCODING;
|
|
64
|
+
// OS environment strings cannot preserve NUL or unpaired UTF-16 surrogates.
|
|
65
|
+
if (id !== undefined && (id.includes("\u0000") || toUSVString(id) !== id)) {
|
|
66
|
+
env.PI_SESSION_ID = JSON.stringify(id);
|
|
67
|
+
env.PI_SESSION_ID_ENCODING = "json";
|
|
68
|
+
}
|
|
69
|
+
return context;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
}).filter((tool) => tool.name !== "read");
|
|
54
73
|
return [...createReadOnlyTools(cwd), ...mutating];
|
|
55
74
|
}
|
|
56
75
|
/**
|
|
@@ -119,18 +138,7 @@ export async function resolveAgentTools(config, agentDir, cwd) {
|
|
|
119
138
|
toolFailures: discovered.failures,
|
|
120
139
|
};
|
|
121
140
|
}
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// systemPrompt = ① base (engine asset; a persona.md persona overrides its identity line)
|
|
125
|
-
// + ② project context (AGENTS.md files via pi's loadProjectContextFiles, <project_context>-wrapped)
|
|
126
|
-
// + ③ skills listing + ④ env context (cwd)
|
|
127
|
-
//
|
|
128
|
-
// AGENTS.md ≠ system prompt. Pure functions: segment ④ input (cwd) is caller-provided, so the
|
|
129
|
-
// same inputs always produce the same prompt (testable, reproducible). No date: a date line would
|
|
130
|
-
// invalidate the provider prompt cache (a prefix cache) for every session at each day boundary —
|
|
131
|
-
// channel sessions routinely live for weeks (pi ≥0.80.7 dropped it from its default prompt for the
|
|
132
|
-
// same reason). The model gets the date when it needs it: `bash date`, and the wake tool takes
|
|
133
|
-
// relative delays ("30m") / cron — never an absolute now-derived instant.
|
|
141
|
+
// Fastagent owns identity and project context; Pi appends skills and cwd for both serving and chat.
|
|
134
142
|
/**
|
|
135
143
|
* The pi engine's base prompt (segment ①), mirroring pi-coding-agent's default path with two
|
|
136
144
|
* deviations: the pi-TUI docs section is dropped (those paths don't exist in deployments), and the
|
|
@@ -147,7 +155,7 @@ export function piBasePrompt(options = {}) {
|
|
|
147
155
|
const deferredCount = mounted.length - tools.length;
|
|
148
156
|
const toolsList = tools.length > 0 ? tools.map((t) => `- ${t.name}: ${(t.description ?? "").split("\n")[0]}`).join("\n") : "(none)";
|
|
149
157
|
// Segment ① identity: an authored persona (persona.md) replaces the default engine identity line
|
|
150
|
-
// (core.md §
|
|
158
|
+
// (core.md §2), keeping the tools list + guidelines below. Preserve pi's coding identity only for
|
|
151
159
|
// the full coding surface; a partial/empty surface must not claim machine capabilities it lacks.
|
|
152
160
|
// The four this sentence NAMES — reading, executing, editing, writing. Searching is not part of the
|
|
153
161
|
// claim, so requiring it would demote an agent that can do everything the identity says it can.
|
|
@@ -182,30 +190,23 @@ export function assembleSystemPrompt(options) {
|
|
|
182
190
|
}
|
|
183
191
|
prompt += `</project_context>\n`;
|
|
184
192
|
}
|
|
185
|
-
if (options.skills && options.skills.length > 0) {
|
|
186
|
-
prompt += `\n${formatSkillsForSystemPrompt(options.skills)}\n`;
|
|
187
|
-
}
|
|
188
|
-
if (options.cwd)
|
|
189
|
-
prompt += `\nCurrent working directory: ${options.cwd}`;
|
|
190
193
|
return prompt;
|
|
191
194
|
}
|
|
192
|
-
// ── §3 the reusable assembly ladder: L1 / L2 ────────────────────────────────
|
|
193
195
|
/**
|
|
194
196
|
* Shared low-level wiring: resolve the model spec against the collection, default the K ports, build
|
|
195
|
-
* the
|
|
197
|
+
* the parts. Internal — the public rungs decide the systemPrompt (L1 from instructions, L2 from the
|
|
196
198
|
* directory) and route through here.
|
|
197
199
|
*/
|
|
198
|
-
function
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
//
|
|
202
|
-
// instance the agent runs under — boundary mutations must contend on it.
|
|
200
|
+
function assemblePi(opts) {
|
|
201
|
+
const cwd = opts.cwd ?? opts.env?.cwd ?? process.cwd();
|
|
202
|
+
// Materialized here (not defaulted inside the L0) so the value carries the SAME lease instance the
|
|
203
|
+
// agent runs under — boundary mutations must contend on it.
|
|
203
204
|
const lease = opts.lease ?? inProcessLease();
|
|
204
205
|
const sessions = opts.sessions ?? piInMemorySessionRecordStore({ cwd });
|
|
205
206
|
// The model and its runtime resolve on FIRST USE: building a ModelRuntime is async while
|
|
206
207
|
// assembling an agent is not, so the credential read belongs on the first turn rather than in the
|
|
207
|
-
// caller's constructor. Memoized
|
|
208
|
-
//
|
|
208
|
+
// caller's constructor. Memoized, and shared with the control plane so a boundary mutation
|
|
209
|
+
// validates against the registry the runs actually use.
|
|
209
210
|
let engine;
|
|
210
211
|
const resolveEngine = () => {
|
|
211
212
|
engine ??= (async () => {
|
|
@@ -225,71 +226,56 @@ function buildPiAgent(opts) {
|
|
|
225
226
|
engine: resolveEngine,
|
|
226
227
|
thinkingLevel: opts.thinkingLevel,
|
|
227
228
|
tools: opts.tools,
|
|
228
|
-
|
|
229
|
-
skills: opts.skills,
|
|
230
|
-
live: opts.live,
|
|
229
|
+
readDefinition: opts.readDefinition,
|
|
231
230
|
cwd,
|
|
232
231
|
...(opts.agentDir ? { agentDir: opts.agentDir } : {}),
|
|
233
232
|
...(opts.extensionPaths ? { extensionPaths: opts.extensionPaths } : {}),
|
|
234
233
|
// `noTools: "builtin"` leaves pi's built-ins in the registry; a lower-level replacement must also
|
|
235
234
|
// deny every omitted coding name so a loader cannot reactivate one later.
|
|
236
235
|
excludedToolNames: omittedBuiltinNames(opts.tools ?? [], cwd),
|
|
237
|
-
env,
|
|
238
236
|
});
|
|
239
|
-
|
|
237
|
+
return {
|
|
240
238
|
lease,
|
|
241
239
|
sessionFactory,
|
|
242
|
-
// The control plane needs the registry and the configured pair as VALUES, and both only exist
|
|
243
|
-
// after the first credential read. Asking for them lazily keeps assembly synchronous without
|
|
244
|
-
// making the hub wait on a runtime it may never need (a control-less deployment never calls it).
|
|
245
240
|
engine: resolveEngine,
|
|
246
241
|
thinkingLevel: opts.thinkingLevel ?? DEFAULT_THINKING_LEVEL,
|
|
247
|
-
});
|
|
248
|
-
return createPiAgentFromSession({ lease, observer: opts.observer, sessionFactory });
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* L1 system prompt: `instructions` ARE the prompt (no engine base, no wrapping); the skills listing
|
|
252
|
-
* is appended only when skills are mounted (the model must know what it can invoke). A factory so a
|
|
253
|
-
* dynamic `instructions` and per-invoke freshness both work; undefined when there is nothing to send.
|
|
254
|
-
*/
|
|
255
|
-
function instructionsPrompt(instructions, skills) {
|
|
256
|
-
const hasSkills = skills !== undefined && skills.length > 0;
|
|
257
|
-
if (instructions === undefined && !hasSkills)
|
|
258
|
-
return undefined;
|
|
259
|
-
return () => {
|
|
260
|
-
const prose = typeof instructions === "function" ? instructions() : (instructions ?? "");
|
|
261
|
-
const listing = hasSkills ? formatSkillsForSystemPrompt(skills) : "";
|
|
262
|
-
return [prose, listing].filter((s) => s !== "").join("\n");
|
|
263
242
|
};
|
|
264
243
|
}
|
|
244
|
+
/** The agent an assembly runs as: the L0 over its lease and session factory. */
|
|
245
|
+
export function agentOf(assembly, observer) {
|
|
246
|
+
return createPiAgentFromSession({ lease: assembly.lease, observer, sessionFactory: assembly.sessionFactory });
|
|
247
|
+
}
|
|
265
248
|
/** L1: assemble from typed parts. */
|
|
266
249
|
export function createPiAgent(options) {
|
|
267
|
-
|
|
250
|
+
const { instructions, skills = [] } = options;
|
|
251
|
+
return agentOf(assemblePi({
|
|
268
252
|
model: options.model,
|
|
269
253
|
thinkingLevel: options.thinkingLevel,
|
|
270
254
|
providers: options.providers,
|
|
271
255
|
authPath: options.authPath,
|
|
272
|
-
|
|
256
|
+
readDefinition: () => ({
|
|
257
|
+
systemPrompt: typeof instructions === "function" ? instructions() : instructions,
|
|
258
|
+
skills,
|
|
259
|
+
}),
|
|
273
260
|
// Deferred tools need their loader on every rung (idempotent; the caller's own search_tools wins).
|
|
274
261
|
tools: options.tools ? withSearchTool(options.tools) : options.tools,
|
|
275
|
-
skills: options.skills,
|
|
276
262
|
sessions: options.sessions,
|
|
277
263
|
env: options.env,
|
|
278
264
|
lease: options.lease,
|
|
279
|
-
|
|
280
|
-
});
|
|
265
|
+
}), options.observer);
|
|
281
266
|
}
|
|
282
267
|
/**
|
|
283
|
-
* L2
|
|
284
|
-
*
|
|
268
|
+
* L2, as the value: load the directory (base + AGENTS.md + skills + env) and assemble. Returns the
|
|
269
|
+
* definition so callers can surface diagnostics/collisions. The opener consumes this form because
|
|
270
|
+
* the control plane needs the parts; {@link createPiAgentFromDefinition} is it plus the L0.
|
|
285
271
|
*/
|
|
286
|
-
export async function
|
|
272
|
+
export async function assemblePiFromDefinition(dir, options) {
|
|
287
273
|
// `dir` = the agent-definition dir (persona.md/skills/); `cwd` (default = dir) is the run root where
|
|
288
274
|
// tools operate and whose ancestors are walked for ② context.
|
|
289
275
|
const cwd = options.cwd ?? dir;
|
|
290
276
|
const env = options.env ?? new NodeExecutionEnv({ cwd });
|
|
291
277
|
// Boot-time load: fail-visibly at startup on a broken directory, and give callers the snapshot to
|
|
292
|
-
// report (skills/diagnostics/collisions). Serving
|
|
278
|
+
// report (skills/diagnostics/collisions). Serving re-reads it through readDefinition below.
|
|
293
279
|
// `cwd`, not `env.cwd`, for the same reason as the tools below: `cwd` is the run root whose
|
|
294
280
|
// ancestors carry ② project context. Reading it off the env pointed the AGENTS.md walk at the
|
|
295
281
|
// loader's directory whenever a caller supplied both.
|
|
@@ -308,7 +294,7 @@ export async function createPiAgentFromDefinition(dir, options) {
|
|
|
308
294
|
// Dir-aware default: the same secrets-dir-derived file the opener uses for this dir (the opener
|
|
309
295
|
// passes an explicit authPath, so this only affects direct L2 callers).
|
|
310
296
|
const authPath = options.authPath ?? defaultAuthPath(resolveSecretsDir(dir));
|
|
311
|
-
const
|
|
297
|
+
const assembly = assemblePi({
|
|
312
298
|
model: options.model,
|
|
313
299
|
thinkingLevel: options.thinkingLevel,
|
|
314
300
|
// THE directory rung's model surface: built-ins + the agent's own models.json (custom endpoints,
|
|
@@ -326,7 +312,7 @@ export async function createPiAgentFromDefinition(dir, options) {
|
|
|
326
312
|
// the finding set changes (boot findings are the baseline) — a runtime-written bad skill must
|
|
327
313
|
// not silently vanish from the agent, and a static one must not spam every turn's log. The
|
|
328
314
|
// next good edit heals both.
|
|
329
|
-
|
|
315
|
+
readDefinition: async () => {
|
|
330
316
|
const def = await loadAgentDefinition(dir, { cwd, env });
|
|
331
317
|
reportFindingsIfChanged(def.dir, def);
|
|
332
318
|
return {
|
|
@@ -336,10 +322,6 @@ export async function createPiAgentFromDefinition(dir, options) {
|
|
|
336
322
|
base: options.base ?? piBasePrompt({ tools, persona: def.persona }),
|
|
337
323
|
// ② project context: AGENTS.md files (agentDir + cwd-ancestor walk) via loadProjectContextFiles.
|
|
338
324
|
contextFiles: def.contextFiles,
|
|
339
|
-
skills: def.skills,
|
|
340
|
-
// The workspace, matching where the tools operate. Telling the model one directory while
|
|
341
|
-
// `read`/`bash` resolve relative paths against another is a lie it cannot detect.
|
|
342
|
-
cwd,
|
|
343
325
|
}),
|
|
344
326
|
skills: def.skills,
|
|
345
327
|
};
|
|
@@ -348,13 +330,16 @@ export async function createPiAgentFromDefinition(dir, options) {
|
|
|
348
330
|
sessions: options.sessions,
|
|
349
331
|
// Discovered so the serving assembly can WARN that it does not run them (and so the refusals
|
|
350
332
|
// apply to the artifact either way) — `chat` is where they load. Boot-resolved: the set cannot
|
|
351
|
-
// change without a restart, which is why this sits outside
|
|
333
|
+
// change without a restart, which is why this sits outside readDefinition above.
|
|
352
334
|
extensionPaths: await loadExtensionPaths(dir, { cwd, env }),
|
|
353
335
|
cwd,
|
|
354
336
|
env,
|
|
355
337
|
lease: options.lease,
|
|
356
|
-
observer: options.observer,
|
|
357
|
-
onAssembly: options.onAssembly,
|
|
358
338
|
});
|
|
359
|
-
return {
|
|
339
|
+
return { assembly, definition };
|
|
340
|
+
}
|
|
341
|
+
/** L2: "point at a directory → agent": {@link assemblePiFromDefinition} under the L0. */
|
|
342
|
+
export async function createPiAgentFromDefinition(dir, options) {
|
|
343
|
+
const { assembly, definition } = await assemblePiFromDefinition(dir, options);
|
|
344
|
+
return { agent: agentOf(assembly, options.observer), definition };
|
|
360
345
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { realpathSync } from "node:fs";
|
|
17
17
|
import { join, resolve } from "node:path";
|
|
18
|
-
import { loadSkills } from "@earendil-works/pi-agent-core";
|
|
18
|
+
import { BACKGROUND_CONTEXT, loadSkills, } from "@earendil-works/pi-agent-core";
|
|
19
19
|
import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node";
|
|
20
20
|
import { loadProjectContextFiles } from "@earendil-works/pi-coding-agent";
|
|
21
21
|
import { log } from "../../log.js";
|
|
@@ -26,7 +26,7 @@ export async function loadAgentDefinition(agentDir, options = {}) {
|
|
|
26
26
|
// never diverge if a caller passes a relative agentDir.
|
|
27
27
|
const cwd = options.cwd ?? agentDir;
|
|
28
28
|
const e = options.env ?? new NodeExecutionEnv({ cwd });
|
|
29
|
-
const rootResult = await e.absolutePath(agentDir);
|
|
29
|
+
const rootResult = await e.absolutePath(agentDir, BACKGROUND_CONTEXT);
|
|
30
30
|
if (!rootResult.ok) {
|
|
31
31
|
throw new Error(`cannot resolve agent dir "${agentDir}": ${rootResult.error.message}`);
|
|
32
32
|
}
|
|
@@ -38,7 +38,7 @@ export async function loadAgentDefinition(agentDir, options = {}) {
|
|
|
38
38
|
// persona.md → segment ① persona (overrides the identity line). Same error policy as AGENTS.md:
|
|
39
39
|
// only not_found means "absent"; any other read error surfaces rather than silently dropping the persona.
|
|
40
40
|
const personaPath = join(root, "persona.md");
|
|
41
|
-
const personaRead = await e.readTextFile(personaPath);
|
|
41
|
+
const personaRead = await e.readTextFile(personaPath, BACKGROUND_CONTEXT);
|
|
42
42
|
if (!personaRead.ok && personaRead.error.code !== "not_found") {
|
|
43
43
|
throw new Error(`cannot read ${personaPath}: ${personaRead.error.message}`);
|
|
44
44
|
}
|
|
@@ -86,13 +86,13 @@ export async function loadAgentDefinition(agentDir, options = {}) {
|
|
|
86
86
|
export async function loadExtensionPaths(agentDir, options = {}) {
|
|
87
87
|
const cwd = options.cwd ?? agentDir;
|
|
88
88
|
const e = options.env ?? new NodeExecutionEnv({ cwd });
|
|
89
|
-
const rootResult = await e.absolutePath(agentDir);
|
|
89
|
+
const rootResult = await e.absolutePath(agentDir, BACKGROUND_CONTEXT);
|
|
90
90
|
if (!rootResult.ok)
|
|
91
91
|
throw new Error(`cannot resolve agent dir "${agentDir}": ${rootResult.error.message}`);
|
|
92
92
|
const root = rootResult.value;
|
|
93
93
|
await assertInsideAgentDir(root, "extensions");
|
|
94
94
|
const dir = join(root, "extensions");
|
|
95
|
-
const listed = await e.listDir(dir);
|
|
95
|
+
const listed = await e.listDir(dir, BACKGROUND_CONTEXT);
|
|
96
96
|
if (!listed.ok) {
|
|
97
97
|
if (listed.error.code === "not_found")
|
|
98
98
|
return [];
|
|
@@ -138,7 +138,7 @@ export async function loadExtensionPaths(agentDir, options = {}) {
|
|
|
138
138
|
async function firstRealFile(e, candidates) {
|
|
139
139
|
let refused = false;
|
|
140
140
|
for (const candidate of candidates) {
|
|
141
|
-
const info = await e.fileInfo(candidate);
|
|
141
|
+
const info = await e.fileInfo(candidate, BACKGROUND_CONTEXT);
|
|
142
142
|
if (!info.ok) {
|
|
143
143
|
if (info.error.code === "not_found")
|
|
144
144
|
continue;
|
|
@@ -164,7 +164,7 @@ async function readSkills(e, root) {
|
|
|
164
164
|
// definition loads the same skills on every machine — and, like tools/channels/schedules, a symlink
|
|
165
165
|
// that escapes the agent dir is refused rather than followed (the fourth of four surfaces).
|
|
166
166
|
await assertInsideAgentDir(root, "skills");
|
|
167
|
-
const { skills: raw, diagnostics } = await loadSkills(e, [join(root, "skills")]);
|
|
167
|
+
const { skills: raw, diagnostics } = await loadSkills(e, [join(root, "skills")], BACKGROUND_CONTEXT);
|
|
168
168
|
const byName = new Map();
|
|
169
169
|
const collisions = [];
|
|
170
170
|
for (const skill of raw) {
|
|
@@ -187,7 +187,7 @@ async function readSkills(e, root) {
|
|
|
187
187
|
export async function loadAgentSkills(agentDir, options = {}) {
|
|
188
188
|
const cwd = options.cwd ?? agentDir;
|
|
189
189
|
const e = options.env ?? new NodeExecutionEnv({ cwd });
|
|
190
|
-
const rootResult = await e.absolutePath(agentDir);
|
|
190
|
+
const rootResult = await e.absolutePath(agentDir, BACKGROUND_CONTEXT);
|
|
191
191
|
if (!rootResult.ok)
|
|
192
192
|
throw new Error(`cannot resolve agent dir "${agentDir}": ${rootResult.error.message}`);
|
|
193
193
|
// `dir` is the RESOLVED root, like {@link LoadedDefinition.dir}: readers key per-definition state
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ABORTED_CODE, SESSION_BUSY_CODE, } from "../../agent.js";
|
|
2
|
+
import { toRetryScheduledEvent } from "./retry-event.js";
|
|
2
3
|
import { cancellableStream } from "../../collect.js";
|
|
3
4
|
import { log } from "../../log.js";
|
|
4
5
|
import { EventQueue, errorToTerminal, inProcessLease, projectAgentEvent, toPiPromptOptions, toTerminal, } from "./turn-kit.js";
|
|
@@ -65,36 +66,9 @@ function toSessionEvent(event, runId) {
|
|
|
65
66
|
runId,
|
|
66
67
|
data: { steering: event.steering.length, followUp: event.followUp.length },
|
|
67
68
|
};
|
|
68
|
-
case "auto_retry_start":
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
timestamp: at,
|
|
72
|
-
runId,
|
|
73
|
-
data: {
|
|
74
|
-
operation: "assistant",
|
|
75
|
-
attempt: event.attempt,
|
|
76
|
-
maxAttempts: event.maxAttempts,
|
|
77
|
-
delayMs: event.delayMs,
|
|
78
|
-
error: event.errorMessage,
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
return retry;
|
|
82
|
-
}
|
|
83
|
-
case "summarization_retry_scheduled": {
|
|
84
|
-
const retry = {
|
|
85
|
-
type: "retry_scheduled",
|
|
86
|
-
timestamp: at,
|
|
87
|
-
runId,
|
|
88
|
-
data: {
|
|
89
|
-
operation: "compaction",
|
|
90
|
-
attempt: event.attempt,
|
|
91
|
-
maxAttempts: event.maxAttempts,
|
|
92
|
-
delayMs: event.delayMs,
|
|
93
|
-
error: event.errorMessage,
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
return retry;
|
|
97
|
-
}
|
|
69
|
+
case "auto_retry_start":
|
|
70
|
+
case "summarization_retry_scheduled":
|
|
71
|
+
return toRetryScheduledEvent(event, runId);
|
|
98
72
|
default:
|
|
99
73
|
return null;
|
|
100
74
|
}
|
|
@@ -240,6 +214,15 @@ export function createPiAgentFromSession(options) {
|
|
|
240
214
|
return; // decided; the retry's output is not ours
|
|
241
215
|
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
242
216
|
finalAssistant = event.message;
|
|
217
|
+
// Error messages and details can contain provider payloads; log only diagnostic metadata.
|
|
218
|
+
for (const diagnostic of finalAssistant.diagnostics ?? []) {
|
|
219
|
+
log.warn(`[fastagent] provider diagnostic ${diagnostic.type} (${finalAssistant.provider}/${finalAssistant.model}, session ${scope.session}, run ${runId})`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (event.type === "compaction_end" && event.reason !== "manual") {
|
|
223
|
+
const status = event.aborted ? "aborted" : event.errorMessage ? "failed" : "completed";
|
|
224
|
+
const emit = event.errorMessage && !event.aborted ? log.warn : log.debug;
|
|
225
|
+
emit(`[fastagent] automatic compaction ${event.reason} (session ${scope.session}, run ${runId}): ${status}`);
|
|
243
226
|
}
|
|
244
227
|
// pi retries a failed assistant request by DISCARDING that attempt's assistant message and
|
|
245
228
|
// asking again. Everything the turn achieved before it survives — executed tools keep
|
|
@@ -36,8 +36,8 @@ export interface CreatePiAgentFromDirOptions {
|
|
|
36
36
|
sessionControl?: boolean;
|
|
37
37
|
/** Additional raw tap with the FULL vocabulary: run events composed after the
|
|
38
38
|
* {@link sessionControl} hub's observer, plus the hub's own boundary-mutation events
|
|
39
|
-
* (`state_changed`/`compaction_*`) via the hub's tap. TRUSTED seam:
|
|
40
|
-
*
|
|
39
|
+
* (`state_changed`/`compaction_*`) via the hub's tap. TRUSTED seam: an observer receives each
|
|
40
|
+
* run's live modulation handles (see `SessionObserver`) — for read-only consumers
|
|
41
41
|
* use the hub's `events()` stream instead. */
|
|
42
42
|
observer?: SessionObserver;
|
|
43
43
|
}
|
package/dist/engines/pi/open.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { mkdir } from "node:fs/promises";
|
|
12
12
|
import { defaultSessionsDir, loadConfig, resolveAuthPath, resolveModelSpec, } from "./config.js";
|
|
13
13
|
import { resolveStateRoot, resolvePlacement } from "../../paths.js";
|
|
14
|
-
import {
|
|
14
|
+
import { agentOf, assemblePiFromDefinition, resolveAgentTools } from "./create.js";
|
|
15
15
|
import { createPiSessionControl } from "./session-control.js";
|
|
16
16
|
import { withWakeTool } from "./wake-tool.js";
|
|
17
17
|
import { loadAgentSkills } from "./definition.js";
|
|
@@ -63,19 +63,36 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
63
63
|
const sessionsDir = options.sessionsDir ?? defaultSessionsDir(stateRoot);
|
|
64
64
|
await mkdir(sessionsDir, { recursive: true });
|
|
65
65
|
const sessions = piSessionRecordStore({ dir: sessionsDir, cwd: workspace });
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
const { assembly, definition } = await assemblePiFromDefinition(agentDir, {
|
|
67
|
+
model: modelSpec,
|
|
68
|
+
thinkingLevel: config.thinkingLevel,
|
|
69
|
+
cwd: workspace,
|
|
70
|
+
tools: mountedTools,
|
|
71
|
+
authPath,
|
|
72
|
+
// Skills are definition-only (the agent is its directory), so dev mirrors deployment exactly.
|
|
73
|
+
sessions,
|
|
74
|
+
});
|
|
75
|
+
// The hub is wired HERE because the store is created here (an external `createPiSessionControl`
|
|
76
|
+
// cannot exist before the store does). Its writes contend on the assembly's own lease and validate
|
|
77
|
+
// against its own registry — the value above is what makes "same" true. An extra caller observer
|
|
78
|
+
// composes after the hub's (TRUSTED seam).
|
|
73
79
|
const caller = options.observer;
|
|
74
80
|
const wantControl = options.sessionControl ?? (config.sessionControl === true && options.serving === true);
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
let hub;
|
|
82
|
+
if (wantControl) {
|
|
83
|
+
// The hub's surface is synchronous (`capabilities()` lists the allowed models), while building
|
|
84
|
+
// the registry reads credentials and is not. Resolve it ONCE here — the opener is async anyway,
|
|
85
|
+
// and the first turn would have paid the same read — so every boundary mutation validates
|
|
86
|
+
// against the same registry the runs use.
|
|
87
|
+
const { modelRuntime, model } = await assembly.engine();
|
|
88
|
+
hub = createPiSessionControl({
|
|
77
89
|
sessions,
|
|
78
|
-
boundary:
|
|
90
|
+
boundary: {
|
|
91
|
+
lease: assembly.lease,
|
|
92
|
+
models: modelRuntime,
|
|
93
|
+
sessionFactory: assembly.sessionFactory,
|
|
94
|
+
defaults: { model, thinkingLevel: assembly.thinkingLevel },
|
|
95
|
+
},
|
|
79
96
|
// Skills ARE the names a client offers — the resolved set, after collisions were decided
|
|
80
97
|
// first-wins, which a client cannot reconstruct from the directory. Read LIVE (the directory
|
|
81
98
|
// is the agent: a skill added while serving is in play on the next turn, so it must be
|
|
@@ -97,8 +114,8 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
97
114
|
// and never cross the data plane's observer seam — without this, an audit tap wired here
|
|
98
115
|
// would miss exactly the mutations it most needs to see (`update({ model })`).
|
|
99
116
|
tap: caller ? (session, event) => caller(session, event) : undefined,
|
|
100
|
-
})
|
|
101
|
-
|
|
117
|
+
});
|
|
118
|
+
}
|
|
102
119
|
const observer = hub
|
|
103
120
|
? caller
|
|
104
121
|
? (session, event, run) => {
|
|
@@ -107,34 +124,7 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
107
124
|
}
|
|
108
125
|
: hub.observer
|
|
109
126
|
: caller;
|
|
110
|
-
const
|
|
111
|
-
model: modelSpec,
|
|
112
|
-
thinkingLevel: config.thinkingLevel,
|
|
113
|
-
cwd: workspace,
|
|
114
|
-
tools: mountedTools,
|
|
115
|
-
authPath,
|
|
116
|
-
// Skills are definition-only (the agent is its directory), so dev mirrors deployment exactly.
|
|
117
|
-
sessions,
|
|
118
|
-
observer,
|
|
119
|
-
onAssembly: hub
|
|
120
|
-
? (parts) => {
|
|
121
|
-
assembled = parts;
|
|
122
|
-
}
|
|
123
|
-
: undefined,
|
|
124
|
-
});
|
|
125
|
-
if (hub && assembled) {
|
|
126
|
-
// The hub's surface is synchronous (`capabilities()` lists the allowed models), while building
|
|
127
|
-
// the registry reads credentials and is not. Resolve it ONCE here — the opener is async anyway,
|
|
128
|
-
// and the first turn would have paid the same read — so every boundary mutation validates
|
|
129
|
-
// against the same registry the runs use.
|
|
130
|
-
const { modelRuntime, model } = await assembled.engine();
|
|
131
|
-
boundaryParts = {
|
|
132
|
-
lease: assembled.lease,
|
|
133
|
-
models: modelRuntime,
|
|
134
|
-
sessionFactory: assembled.sessionFactory,
|
|
135
|
-
defaults: { model, thinkingLevel: assembled.thinkingLevel },
|
|
136
|
-
};
|
|
137
|
-
}
|
|
127
|
+
const agent = agentOf(assembly, observer);
|
|
138
128
|
return {
|
|
139
129
|
agent,
|
|
140
130
|
definition,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { RetryScheduledEvent } from "../../session.ts";
|
|
3
|
+
/** Manual compaction retries are session-scoped; retries inside an invoke carry its runId. */
|
|
4
|
+
export declare function toRetryScheduledEvent(event: Extract<AgentSessionEvent, {
|
|
5
|
+
type: "auto_retry_start" | "summarization_retry_scheduled";
|
|
6
|
+
}>, runId?: string): RetryScheduledEvent;
|