@fastagent-sh/fastagent 0.22.1 → 0.24.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/dist/agent.d.ts +10 -0
- package/dist/channels/agentcore-protocol.d.ts +2 -1
- package/dist/channels/agentcore-service.js +10 -18
- package/dist/channels/agentcore.js +6 -3
- package/dist/channels/control.js +23 -0
- package/dist/channels/kit/preview-kit.d.ts +6 -1
- package/dist/channels/kit/preview-kit.js +21 -4
- package/dist/channels/slack/config-api.d.ts +2 -2
- package/dist/channels/slack/config-api.js +2 -2
- package/dist/channels/slack/onboard.js +2 -2
- package/dist/channels/slack/preview.js +2 -1
- package/dist/channels/slack/register-webhook.js +2 -2
- package/dist/channels/slack/slack-api.js +4 -4
- package/dist/cli/add-feishu.js +4 -2
- package/dist/cli/auth-view.d.ts +15 -6
- package/dist/cli/auth-view.js +24 -8
- package/dist/cli/box-login.d.ts +33 -0
- package/dist/cli/box-login.js +162 -0
- package/dist/cli/commands/deploy/agentcore.js +48 -11
- package/dist/cli/commands/deploy/docker.js +24 -13
- package/dist/cli/commands/deploy/fly.js +30 -7
- package/dist/cli/commands/deploy/railway.js +21 -7
- package/dist/cli/commands/deploy/shared.d.ts +18 -17
- package/dist/cli/commands/deploy/shared.js +20 -21
- package/dist/cli/commands/destroy.js +2 -2
- package/dist/cli/commands/info.js +15 -12
- package/dist/cli/commands/invoke.js +2 -2
- package/dist/cli/commands/login.d.ts +7 -1
- package/dist/cli/commands/login.js +123 -10
- package/dist/cli/commands/logs.js +2 -2
- package/dist/cli/commands/models.d.ts +4 -1
- package/dist/cli/commands/models.js +37 -4
- package/dist/cli/commands/routine-run.js +2 -2
- package/dist/cli/commands/start.d.ts +0 -5
- package/dist/cli/commands/start.js +3 -13
- package/dist/cli/invoke-stream.js +14 -0
- package/dist/cli/kernel.d.ts +2 -0
- package/dist/cli/kernel.js +1 -1
- package/dist/cli/login-relay.d.ts +38 -0
- package/dist/cli/login-relay.js +121 -0
- package/dist/cli/program.js +46 -8
- package/dist/cli/shared.d.ts +6 -5
- package/dist/cli/shared.js +29 -36
- package/dist/deploy/agentcore/plan.d.ts +10 -11
- package/dist/deploy/agentcore/plan.js +8 -14
- package/dist/deploy/agentcore/run.d.ts +8 -1
- package/dist/deploy/agentcore/run.js +20 -14
- package/dist/deploy/agentcore/shell.d.ts +25 -0
- package/dist/deploy/agentcore/shell.js +202 -0
- package/dist/deploy/box-shell.d.ts +43 -0
- package/dist/deploy/box-shell.js +28 -0
- package/dist/deploy/build-context.js +16 -7
- package/dist/deploy/channel-ingress.d.ts +20 -1
- package/dist/deploy/channel-ingress.js +34 -7
- package/dist/deploy/container.d.ts +14 -0
- package/dist/deploy/container.js +45 -1
- package/dist/deploy/docker/plan.d.ts +7 -3
- package/dist/deploy/docker/plan.js +9 -14
- package/dist/deploy/docker/run.d.ts +14 -7
- package/dist/deploy/docker/run.js +58 -35
- package/dist/deploy/fly/plan.d.ts +6 -3
- package/dist/deploy/fly/plan.js +6 -8
- package/dist/deploy/fly/run.d.ts +8 -2
- package/dist/deploy/fly/run.js +15 -25
- package/dist/deploy/preflight.d.ts +9 -7
- package/dist/deploy/preflight.js +92 -20
- package/dist/deploy/railway/plan.d.ts +6 -3
- package/dist/deploy/railway/plan.js +7 -9
- package/dist/deploy/railway/run.d.ts +10 -6
- package/dist/deploy/railway/run.js +29 -13
- package/dist/deploy/runner.d.ts +16 -0
- package/dist/deploy/runner.js +31 -0
- package/dist/deploy/secrets.d.ts +4 -13
- package/dist/deploy/secrets.js +8 -27
- package/dist/dev-supervisor.js +7 -4
- package/dist/engines/pi/agent-models.d.ts +83 -0
- package/dist/engines/pi/agent-models.js +120 -0
- package/dist/engines/pi/agent-session-factory.d.ts +13 -13
- package/dist/engines/pi/agent-session-factory.js +132 -159
- package/dist/engines/pi/auth.d.ts +56 -3
- package/dist/engines/pi/auth.js +68 -109
- package/dist/engines/pi/config.d.ts +0 -25
- package/dist/engines/pi/config.js +3 -37
- package/dist/engines/pi/create.d.ts +34 -21
- package/dist/engines/pi/create.js +90 -57
- package/dist/engines/pi/entry-images.d.ts +14 -0
- package/dist/engines/pi/entry-images.js +23 -0
- package/dist/engines/pi/invoke-session.js +88 -20
- package/dist/engines/pi/locked-file.d.ts +9 -0
- package/dist/engines/pi/locked-file.js +102 -0
- package/dist/engines/pi/login.d.ts +27 -14
- package/dist/engines/pi/login.js +41 -18
- package/dist/engines/pi/machine.d.ts +23 -0
- package/dist/engines/pi/machine.js +24 -2
- package/dist/engines/pi/models.d.ts +120 -36
- package/dist/engines/pi/models.js +316 -66
- package/dist/engines/pi/open.d.ts +65 -20
- package/dist/engines/pi/open.js +88 -34
- package/dist/engines/pi/report.d.ts +3 -0
- package/dist/engines/pi/report.js +11 -0
- package/dist/engines/pi/service.d.ts +2 -2
- package/dist/engines/pi/service.js +1 -0
- package/dist/engines/pi/session-builder.d.ts +3 -2
- package/dist/engines/pi/session-builder.js +30 -76
- package/dist/engines/pi/session-control.d.ts +4 -4
- package/dist/engines/pi/session-control.js +86 -43
- package/dist/engines/pi/session-settings.d.ts +9 -0
- package/dist/engines/pi/session-settings.js +17 -0
- package/dist/engines/pi/session-store.d.ts +2 -2
- package/dist/engines/pi/tool-context.d.ts +3 -5
- package/dist/engines/pi/tool-context.js +4 -16
- package/dist/engines/pi/tool.d.ts +32 -21
- package/dist/engines/pi/tool.js +21 -25
- package/dist/engines/pi/turn-kit.d.ts +18 -9
- package/dist/engines/pi/turn-kit.js +115 -32
- package/dist/open-url.d.ts +15 -1
- package/dist/open-url.js +35 -3
- package/dist/paths.d.ts +4 -2
- package/dist/paths.js +6 -2
- package/dist/pi.d.ts +5 -3
- package/dist/pi.js +3 -2
- package/dist/service.d.ts +5 -8
- package/dist/session-remote.js +21 -0
- package/dist/session.d.ts +88 -15
- package/dist/session.js +5 -1
- package/package.json +6 -5
- package/dist/engines/pi/search-tools.d.ts +0 -4
- package/dist/engines/pi/search-tools.js +0 -109
package/dist/engines/pi/open.js
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* drive.
|
|
4
4
|
*/
|
|
5
5
|
import { mkdir } from "node:fs/promises";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { loadConfig, resolveModelSpec } from "./config.js";
|
|
8
|
+
import { AGENT_MODEL_CATALOG_FILE, resolveSessionsDir, resolveStateRoot, resolvePlacement } from "../../paths.js";
|
|
9
|
+
import { describeModels } from "./session-settings.js";
|
|
10
|
+
import { agentOf, assemblePiFromDefinition, resolveAgentTools } from "./create.js";
|
|
9
11
|
import { createPiSessionControl } from "./session-control.js";
|
|
10
12
|
import { withWakeTool } from "./wake-tool.js";
|
|
11
13
|
import { refuseBrokenDeclarations } from "../../loader.js";
|
|
@@ -13,6 +15,8 @@ import { loadAgentSkills } from "./definition.js";
|
|
|
13
15
|
import { servedExtensionCommands } from "./agent-session-factory.js";
|
|
14
16
|
import { reportFindingsIfChanged } from "./report.js";
|
|
15
17
|
import { readMachine, withMachine } from "./machine.js";
|
|
18
|
+
import { createPiModelRuntime, globalCatalogPath, machineModelRuntime, refreshCatalog } from "./models.js";
|
|
19
|
+
import { agentModels } from "./agent-models.js";
|
|
16
20
|
import { piSessionRecordStore } from "./session-store.js";
|
|
17
21
|
import { gateSecrets } from "../../secrets-gate.js";
|
|
18
22
|
/**
|
|
@@ -33,9 +37,11 @@ export async function agentCommands(agentDir, workspace, served) {
|
|
|
33
37
|
reportFindingsIfChanged(own.dir, own);
|
|
34
38
|
const machine = await readMachine(workspace);
|
|
35
39
|
const { extensionPaths } = served;
|
|
36
|
-
const extensionCommands =
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
const extensionCommands = await servedExtensionCommands({
|
|
41
|
+
cwd: workspace,
|
|
42
|
+
modelRuntime: await served.modelRuntime(),
|
|
43
|
+
extensionPaths,
|
|
44
|
+
});
|
|
39
45
|
// pi dispatches an extension command before it expands a template, so a template it shadows never runs.
|
|
40
46
|
const shadowed = new Set(extensionCommands.map((command) => command.invocationName));
|
|
41
47
|
return [
|
|
@@ -66,7 +72,7 @@ export async function resolveAgentAssembly(dir, options = {}) {
|
|
|
66
72
|
if (!modelSpec) {
|
|
67
73
|
throw new Error(`missing model: set --model, "model" in fastagent.config.ts, or FASTAGENT_MODEL (e.g. "openai-codex/gpt-5.5")`);
|
|
68
74
|
}
|
|
69
|
-
const { tools, toolNames,
|
|
75
|
+
const { tools, toolNames, indirectTools, toolCollisions, toolFailures, toolSecrets } = await resolveAgentTools(config, agentDir, workspace);
|
|
70
76
|
// THE serving-path gate for tool declarations, in the order that costs the fewest round trips. A file that could
|
|
71
77
|
// not be imported declares nothing, so its `secrets:` are missing from `toolSecrets` — gating secrets first would
|
|
72
78
|
// report an incomplete set, and fixing the file could then reveal more missing values. Refuse the broken file
|
|
@@ -76,15 +82,13 @@ export async function resolveAgentAssembly(dir, options = {}) {
|
|
|
76
82
|
// definition and must survive both faults; every path through this function is about to run ALL of the tools, so
|
|
77
83
|
// no `owner`.
|
|
78
84
|
//
|
|
79
|
-
// DEFERRED tools gate too, deliberately:
|
|
85
|
+
// DEFERRED tools gate too, deliberately: native discovery can activate one mid-turn, so "registered"
|
|
80
86
|
// means "may run in this process" — letting it start would put the empty-credential failure back
|
|
81
87
|
// inside a turn. An author who does not want that opts out per tool by not declaring.
|
|
82
88
|
gateSecrets({ declared: toolSecrets, failures: [] });
|
|
83
89
|
// The state root: sessions/channel state/schedule state derive from it (FASTAGENT_STATE_DIR moves it in one knob —
|
|
84
90
|
// a container points it at its volume).
|
|
85
91
|
const stateRoot = resolveStateRoot(agentDir);
|
|
86
|
-
// The credentials file: project-level by default (under `<agentDir>/.secrets`).
|
|
87
|
-
const auth = resolveAuthLayers(agentDir, options.authPath);
|
|
88
92
|
return {
|
|
89
93
|
config,
|
|
90
94
|
configPath,
|
|
@@ -92,37 +96,94 @@ export async function resolveAgentAssembly(dir, options = {}) {
|
|
|
92
96
|
agentDir,
|
|
93
97
|
workspace,
|
|
94
98
|
stateRoot,
|
|
95
|
-
|
|
99
|
+
// Project-level by default (under `<agentDir>/.secrets`), with the global file behind it per provider.
|
|
100
|
+
models: agentModels(agentDir, options, { cwd: workspace }),
|
|
96
101
|
tools,
|
|
97
102
|
toolNames,
|
|
98
|
-
|
|
103
|
+
indirectTools,
|
|
99
104
|
toolCollisions,
|
|
100
105
|
toolSecrets,
|
|
101
106
|
};
|
|
102
107
|
}
|
|
103
108
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
|
|
109
|
+
* L2 over a resolved front half: the ONE mapping from what the directory resolved to what the assembly runs on, for
|
|
110
|
+
* both session shapes (the served agent and `chat`'s resident one). Two copies of it drifted once (#566, chat ran at
|
|
111
|
+
* the wrong reasoning effort). `models` is the front's own, so the assembly runs on what the report describes.
|
|
112
|
+
*/
|
|
113
|
+
export function assembleFront(front, extra = {}) {
|
|
114
|
+
return assemblePiFromDefinition(front.agentDir, {
|
|
115
|
+
model: front.modelSpec,
|
|
116
|
+
thinkingLevel: front.config.thinkingLevel,
|
|
117
|
+
cwd: front.workspace,
|
|
118
|
+
tools: extra.tools ?? front.tools,
|
|
119
|
+
models: front.models,
|
|
120
|
+
...(extra.sessions ? { sessions: extra.sessions } : {}),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The models `createPiAgentFromDir(dir, { authPath })` could run now, as a picker shows them (spec, name, the thinking
|
|
125
|
+
* levels a session on it accepts, context window), sorted by spec: the agent's registry (pi's built-ins, its
|
|
126
|
+
* `models.json`, and what its `extensions/` declare) filtered to the providers whose credentials are configured. The
|
|
127
|
+
* directory needs no model set, and the same placement and credential layers as the opener apply, so a listed spec
|
|
128
|
+
* authenticates there. Configuration is checked, not validity: no OAuth token is refreshed and no provider is called.
|
|
108
129
|
*
|
|
109
130
|
* `warn` reaches the credential store. An unreadable or corrupt credentials file otherwise reads as "nothing
|
|
110
131
|
* configured", so a client that must not show that as an empty list passes a sink that throws.
|
|
111
132
|
*/
|
|
112
133
|
export async function availableModelsFromDir(dir, options = {}) {
|
|
134
|
+
const { agentDir, workspace } = resolvePlacement(dir);
|
|
135
|
+
const models = await agentModels(agentDir, options, { cwd: workspace }).runtime();
|
|
136
|
+
return describeModels(await models.getAvailable());
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Refresh the agent's model catalog (`<agent dir>/models-store.json`) with the credentials
|
|
140
|
+
* `createPiAgentFromDir(dir, { authPath })` would use, so a model released after the installed pi appears in
|
|
141
|
+
* {@link availableModelsFromDir} and runs. Only providers those credentials authenticate are fetched (pi asks pi.dev
|
|
142
|
+
* for no other). Nothing refreshes it on its own. The file is part of the definition: commit it, and it ships with a
|
|
143
|
+
* deploy, so the deployed agent knows the same models without a network call.
|
|
144
|
+
*
|
|
145
|
+
* Rejects, naming each provider that failed, when the refresh fails, outlasts 15 seconds or `PI_OFFLINE` is set, and
|
|
146
|
+
* when the credentials authenticate no provider at all.
|
|
147
|
+
*/
|
|
148
|
+
export function refreshModelCatalog(dir, options = {}) {
|
|
149
|
+
return refreshModelCatalogOver(dir, options);
|
|
150
|
+
}
|
|
151
|
+
/** {@link refreshModelCatalog} against another catalog server. Not public: a test seam. */
|
|
152
|
+
export async function refreshModelCatalogOver(dir, options, catalogBaseUrl) {
|
|
113
153
|
const { agentDir } = resolvePlacement(dir);
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
154
|
+
const { credentials } = agentModels(agentDir, options);
|
|
155
|
+
await refreshCatalog(join(agentDir, AGENT_MODEL_CATALOG_FILE), (catalogFile) => createPiModelRuntime({
|
|
156
|
+
agentDir,
|
|
157
|
+
credentials,
|
|
158
|
+
catalogFile,
|
|
159
|
+
...(catalogBaseUrl ? { catalogBaseUrl } : {}),
|
|
160
|
+
}), options.signal ? { signal: options.signal } : {});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Refresh the MACHINE's model catalog (`~/.fastagent/models-store.json`), which every agent here reads under its own,
|
|
164
|
+
* so one refresh serves them all and no file is written into any agent. Credentials resolve as for
|
|
165
|
+
* {@link refreshModelCatalog} without a directory: `credentialStore`, else `authPath`, else the global credentials file;
|
|
166
|
+
* the environment applies either way. Rejects for the same reasons. The file never ships with a deploy.
|
|
167
|
+
*/
|
|
168
|
+
export function refreshMachineModelCatalog(options = {}) {
|
|
169
|
+
return refreshMachineModelCatalogOver(options);
|
|
170
|
+
}
|
|
171
|
+
/** {@link refreshMachineModelCatalog} against another catalog server. Not public: a test seam. */
|
|
172
|
+
export async function refreshMachineModelCatalogOver(options, catalogBaseUrl) {
|
|
173
|
+
const { credentials } = agentModels(undefined, options);
|
|
174
|
+
await refreshCatalog(globalCatalogPath(), (catalogFile) => machineModelRuntime({
|
|
175
|
+
credentials,
|
|
176
|
+
catalogFile,
|
|
177
|
+
...(catalogBaseUrl ? { catalogBaseUrl } : {}),
|
|
178
|
+
}), options.signal ? { signal: options.signal } : {});
|
|
119
179
|
}
|
|
120
180
|
/**
|
|
121
181
|
* "Point at a directory → agent": resolve the placement (`dir` may be either end — the workspace or the agent dir
|
|
122
182
|
* itself), load the config, resolve model and tools, then L2.
|
|
123
183
|
*/
|
|
124
184
|
export async function createPiAgentFromDir(dir, options = {}) {
|
|
125
|
-
const
|
|
185
|
+
const front = await resolveAgentAssembly(dir, options);
|
|
186
|
+
const { config, configPath, modelSpec, agentDir, workspace, stateRoot, models, tools, toolNames, indirectTools, toolCollisions, } = front;
|
|
126
187
|
// Every serve mounts the built-in `wake` tool: the agent's own follow-up work is a default capability, and a serve
|
|
127
188
|
// is where the poller that honors it runs. A one-shot `invoke` has no poller, so nothing there would fire it.
|
|
128
189
|
const mountedTools = withWakeTool(tools, stateRoot, !!options.serving);
|
|
@@ -131,15 +192,8 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
131
192
|
const sessionsDir = options.sessionsDir ?? resolveSessionsDir(agentDir);
|
|
132
193
|
await mkdir(sessionsDir, { recursive: true });
|
|
133
194
|
const sessions = piSessionRecordStore({ dir: sessionsDir, cwd: workspace });
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
thinkingLevel: config.thinkingLevel,
|
|
137
|
-
cwd: workspace,
|
|
138
|
-
tools: mountedTools,
|
|
139
|
-
auth,
|
|
140
|
-
// Skills are definition-only (the agent is its directory), so dev mirrors deployment exactly.
|
|
141
|
-
sessions,
|
|
142
|
-
});
|
|
195
|
+
// Skills are definition-only (the agent is its directory), so dev mirrors deployment exactly.
|
|
196
|
+
const { assembly, definition } = await assembleFront(front, { tools: mountedTools, sessions });
|
|
143
197
|
// The hub is wired HERE because the store is created here (an external `createPiSessionControl` cannot exist before
|
|
144
198
|
// the store does).
|
|
145
199
|
const caller = options.observer;
|
|
@@ -169,7 +223,7 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
169
223
|
// the prompt would not expand — a second reading of "which skills exist" is how those two come to disagree.
|
|
170
224
|
commands: () => agentCommands(agentDir, workspace, {
|
|
171
225
|
extensionPaths: assembly.extensionPaths,
|
|
172
|
-
modelRuntime:
|
|
226
|
+
modelRuntime: assembly.createModelRuntime,
|
|
173
227
|
}),
|
|
174
228
|
// The caller tap's boundary-event half: state_changed/compaction_* originate in the hub and never cross the
|
|
175
229
|
// data plane's observer seam.
|
|
@@ -199,9 +253,9 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
199
253
|
modelSpec,
|
|
200
254
|
stateRoot,
|
|
201
255
|
sessionsDir,
|
|
202
|
-
|
|
256
|
+
models,
|
|
203
257
|
toolNames,
|
|
204
|
-
|
|
258
|
+
indirectTools,
|
|
205
259
|
toolCollisions,
|
|
206
260
|
};
|
|
207
261
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { SkillDiagnostic } from "@earendil-works/pi-agent-core";
|
|
6
6
|
import type { SkillCollision } from "./definition.ts";
|
|
7
7
|
import type { ToolCollision } from "./tool.ts";
|
|
8
|
+
import type { IndirectTool } from "./create.ts";
|
|
8
9
|
type Findings = {
|
|
9
10
|
collisions: SkillCollision[];
|
|
10
11
|
diagnostics: SkillDiagnostic[];
|
|
@@ -12,5 +13,7 @@ type Findings = {
|
|
|
12
13
|
/** THE door for definition findings: warns only when this dir's set CHANGED since the last report. */
|
|
13
14
|
export declare function reportFindingsIfChanged(dir: string, def: Findings): void;
|
|
14
15
|
export declare function reportDefinitionWarnings(collisions: SkillCollision[], diagnostics: SkillDiagnostic[]): void;
|
|
16
|
+
/** The report line for the mounted tools the model is not given up front: each name with its way in. */
|
|
17
|
+
export declare function describeIndirectTools(tools: readonly IndirectTool[]): string;
|
|
15
18
|
export declare function reportToolCollisions(collisions: ToolCollision[]): void;
|
|
16
19
|
export {};
|
|
@@ -23,6 +23,17 @@ export function reportDefinitionWarnings(collisions, diagnostics) {
|
|
|
23
23
|
log.warn(`[fastagent] ${d.code}: ${d.message} (${d.path})`);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
+
const REACH = {
|
|
27
|
+
tool_search: "loaded via tool_search",
|
|
28
|
+
codemode: "codemode scripts only",
|
|
29
|
+
hidden: "hidden",
|
|
30
|
+
inactive: "inactive until an authored loader activates it",
|
|
31
|
+
unreachable: "unreachable: pi's settings disable the built-in extension it needs",
|
|
32
|
+
};
|
|
33
|
+
/** The report line for the mounted tools the model is not given up front: each name with its way in. */
|
|
34
|
+
export function describeIndirectTools(tools) {
|
|
35
|
+
return tools.map((tool) => `${tool.name} (${REACH[tool.reach]})`).join(", ");
|
|
36
|
+
}
|
|
26
37
|
export function reportToolCollisions(collisions) {
|
|
27
38
|
for (const c of collisions) {
|
|
28
39
|
log.warn(`[fastagent] tool "${c.name}" (${c.source}) dropped — a default/config tool already uses that name`);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** `createAgentService` — the product as one call, with pi supplying the engine. */
|
|
2
2
|
import { type AgentService, type MountAgentServiceOptions } from "../../service.ts";
|
|
3
|
-
|
|
3
|
+
import type { CredentialSourceOptions } from "./auth.ts";
|
|
4
|
+
export interface CreateAgentServiceOptions extends MountAgentServiceOptions, CredentialSourceOptions {
|
|
4
5
|
model?: string;
|
|
5
|
-
authPath?: string;
|
|
6
6
|
sessionsDir?: string;
|
|
7
7
|
}
|
|
8
8
|
/** Open an agent directory as a live service: one handler, mounted wherever you serve. */
|
|
@@ -6,6 +6,7 @@ export async function createAgentService(dir, options = {}) {
|
|
|
6
6
|
const opened = await createPiAgentFromDir(dir, {
|
|
7
7
|
...(options.model !== undefined ? { model: options.model } : {}),
|
|
8
8
|
...(options.authPath !== undefined ? { authPath: options.authPath } : {}),
|
|
9
|
+
...(options.credentialStore !== undefined ? { credentialStore: options.credentialStore } : {}),
|
|
9
10
|
...(options.sessionsDir !== undefined ? { sessionsDir: options.sessionsDir } : {}),
|
|
10
11
|
serving: true, // a mounted service is long-running: the scheduler poller runs
|
|
11
12
|
});
|
|
@@ -6,7 +6,8 @@ export interface BuildSessionRuntimeOptions {
|
|
|
6
6
|
authPath?: string;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* Build pi's interactive runtime
|
|
10
|
-
*
|
|
9
|
+
* Build pi's interactive runtime over the SAME assembly `dev`/`start` serve ({@link assembleFront}): model
|
|
10
|
+
* registry, prompt, tools, skills, reasoning effort and credentials all come from it, so what differs is only the
|
|
11
|
+
* session's shape (one resident runtime rather than one session per invoke).
|
|
11
12
|
*/
|
|
12
13
|
export declare function buildAgentSessionRuntime(dir: string, options?: BuildSessionRuntimeOptions, sessionManager?: SessionManager): Promise<AgentSessionRuntime>;
|
|
@@ -4,109 +4,63 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { bindPiSession, definitionResourceLoaderOptions, reportExtensionErrors } from "./agent-session-factory.js";
|
|
10
|
-
import { readMachine } from "./machine.js";
|
|
7
|
+
import { SessionManager, SettingsManager, createAgentSessionRuntime, getAgentDir, } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { bindPiSession, definitionServices } from "./agent-session-factory.js";
|
|
11
9
|
import { resolveModel } from "./config.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { resolveAgentAssembly } from "./open.js";
|
|
10
|
+
import { canonicalPath } from "./definition.js";
|
|
11
|
+
import { reportToolCollisions } from "./report.js";
|
|
12
|
+
import { assembleFront, resolveAgentAssembly } from "./open.js";
|
|
13
|
+
import { resolvePlacement } from "../../paths.js";
|
|
17
14
|
/**
|
|
18
|
-
* Build pi's interactive runtime
|
|
19
|
-
*
|
|
15
|
+
* Build pi's interactive runtime over the SAME assembly `dev`/`start` serve ({@link assembleFront}): model
|
|
16
|
+
* registry, prompt, tools, skills, reasoning effort and credentials all come from it, so what differs is only the
|
|
17
|
+
* session's shape (one resident runtime rather than one session per invoke).
|
|
20
18
|
*/
|
|
21
19
|
export async function buildAgentSessionRuntime(dir, options = {}, sessionManager) {
|
|
22
|
-
async function resolveAssembly(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const {
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
// divergence the layer exists to remove.
|
|
30
|
-
const modelRuntime = await createPiModelRuntime({ auth, agentDir, stateRoot });
|
|
31
|
-
const env = new NodeExecutionEnv({ cwd });
|
|
32
|
-
const definition = await loadAgentDefinition(agentDir, { cwd, env });
|
|
33
|
-
reportFindingsIfChanged(definition.dir, definition);
|
|
34
|
-
// Assembly-time, like serving's: this whole function is memoized, so the scan and its warnings happen once per
|
|
35
|
-
// runtime rather than per session rebuild (/new, /resume, fork).
|
|
36
|
-
const extensionPaths = await loadExtensionPaths(agentDir, { cwd, env });
|
|
37
|
-
// base + instructions ONLY — pi appends the skill section and env (cwd) itself (including them here would
|
|
38
|
-
// duplicate them).
|
|
39
|
-
const systemPrompt = assembleSystemPrompt({
|
|
40
|
-
base: piBasePrompt({ tools, persona: definition.persona }),
|
|
41
|
-
contextFiles: definition.contextFiles,
|
|
42
|
-
});
|
|
43
|
-
return {
|
|
44
|
-
modelRuntime,
|
|
45
|
-
modelSpec,
|
|
46
|
-
// Serving honors config.thinkingLevel (config → L2); the resident session must too (fidelity).
|
|
47
|
-
thinkingLevel: config.thinkingLevel,
|
|
48
|
-
definition,
|
|
49
|
-
extensionPaths,
|
|
50
|
-
tools,
|
|
51
|
-
systemPrompt,
|
|
52
|
-
};
|
|
20
|
+
async function resolveAssembly() {
|
|
21
|
+
const front = await resolveAgentAssembly(dir, options);
|
|
22
|
+
reportToolCollisions(front.toolCollisions);
|
|
23
|
+
const { assembly } = await assembleFront(front);
|
|
24
|
+
// Read ONCE per runtime: a rebuild (/new, fork) keeps the startup snapshot, because config and tools stay in the
|
|
25
|
+
// import cache and a half-refreshed agent is worse than a stale one. Restart chat to pick up edits.
|
|
26
|
+
return { modelSpec: front.modelSpec, assembly, definition: await assembly.readDefinition() };
|
|
53
27
|
}
|
|
54
|
-
//
|
|
55
|
-
|
|
28
|
+
// The workspace, like serving's: where tools run and what session records are keyed to. Canonical, because pi's
|
|
29
|
+
// process.cwd() fallback is a realpath and a symlinked workspace would otherwise mismatch it.
|
|
30
|
+
const rootCwd = canonicalPath(resolvePlacement(dir).workspace);
|
|
31
|
+
// pi calls the factory again on /new, /resume, switch, and fork; the assembly is built once.
|
|
56
32
|
let assembly;
|
|
57
33
|
const assemblyFor = (cwd) => {
|
|
58
|
-
// Canonical paths: pi's process.cwd() fallback is a realpath, so a symlinked workspace would otherwise mismatch a
|
|
59
|
-
// non-realpath rootCwd.
|
|
60
34
|
const activeCwd = canonicalPath(cwd);
|
|
61
35
|
if (activeCwd !== rootCwd) {
|
|
62
36
|
throw workspaceScopeError(activeCwd);
|
|
63
37
|
}
|
|
64
|
-
assembly ??= resolveAssembly(
|
|
38
|
+
assembly ??= resolveAssembly();
|
|
65
39
|
return assembly;
|
|
66
40
|
};
|
|
67
41
|
const createRuntime = async ({ cwd, sessionManager, sessionStartEvent }) => {
|
|
68
|
-
const {
|
|
42
|
+
const { modelSpec, assembly, definition } = await assemblyFor(cwd);
|
|
69
43
|
// Per session, NOT memoized with the assembly.
|
|
70
|
-
const
|
|
71
|
-
const loaded = await
|
|
72
|
-
cwd,
|
|
73
|
-
// fastagent's models + auth hub replaces pi's default (~/.pi-backed) one — the auth unification point; see the
|
|
74
|
-
// header.
|
|
75
|
-
modelRuntime,
|
|
76
|
-
// PACKAGELESS, for the loader: fastagent never installs a pi package (machine.ts). Handed pi's own settings, the
|
|
77
|
-
// loader resolves `packages` itself — installing a missing one, and failing chat's start when that fails.
|
|
78
|
-
settingsManager: machine.settingsManager(),
|
|
79
|
-
// Chat's assembly is fixed for the life of the runtime (a rebuild makes a new one), so these read constants.
|
|
80
|
-
resourceLoaderOptions: definitionResourceLoaderOptions({
|
|
81
|
-
systemPrompt: () => systemPrompt,
|
|
82
|
-
skills: () => definition.skills,
|
|
83
|
-
machine,
|
|
84
|
-
extensionPaths,
|
|
85
|
-
}),
|
|
86
|
-
});
|
|
44
|
+
const modelRuntime = await assembly.createModelRuntime();
|
|
45
|
+
const loaded = await definitionServices({ cwd, modelRuntime, definition, extensionPaths: assembly.extensionPaths });
|
|
87
46
|
// ...while the SESSION keeps pi's own file-backed settings, so `/settings` in the TUI still saves. pi persists by
|
|
88
47
|
// re-reading the file under its lock and writing only the fields that changed, so `packages` there is untouched.
|
|
89
48
|
const services = { ...loaded, settingsManager: SettingsManager.create(cwd, loaded.agentDir) };
|
|
90
|
-
reportExtensionErrors(services);
|
|
91
49
|
// AFTER the services, because an extension may be what defines the model.
|
|
92
50
|
const model = resolveModel(modelRuntime, modelSpec);
|
|
93
|
-
//
|
|
94
|
-
// the documented divergence.
|
|
51
|
+
// Serving and chat restore the same native transcript declarations.
|
|
95
52
|
const result = await bindPiSession({
|
|
96
53
|
services,
|
|
97
54
|
sessionManager,
|
|
98
55
|
sessionStartEvent,
|
|
99
56
|
model,
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
thinkingLevel: thinkingLevel ?? DEFAULT_THINKING_LEVEL,
|
|
106
|
-
tools,
|
|
57
|
+
// Always set, never left for pi to fill: pi resolves an ABSENT level from the machine's settings, and the effort
|
|
58
|
+
// is the definition's (`thinkingLevel` in fastagent.config.ts), like serving's.
|
|
59
|
+
thinkingLevel: assembly.thinkingLevel,
|
|
60
|
+
tools: assembly.tools,
|
|
61
|
+
excludedToolNames: assembly.excludedToolNames,
|
|
107
62
|
// A tool must see one spelling of the workspace, including when opened through a symlink.
|
|
108
63
|
cwd: rootCwd,
|
|
109
|
-
recordActivations: false,
|
|
110
64
|
});
|
|
111
65
|
return { ...result, services, diagnostics: services.diagnostics };
|
|
112
66
|
};
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
16
16
|
import { type Models } from "@earendil-works/pi-ai";
|
|
17
17
|
import { type AgentCommand, type SessionControl, type SessionEvent } from "../../session.ts";
|
|
18
|
-
import type
|
|
18
|
+
import { type SessionObserver, type Lease } from "./turn-kit.ts";
|
|
19
19
|
import type { AnyModel } from "./models.ts";
|
|
20
20
|
import type { PiAgentSessionFactory } from "./invoke-session.ts";
|
|
21
21
|
import type { PiSessionRecordStore } from "./session-store.ts";
|
|
@@ -25,9 +25,9 @@ export declare const SUBSCRIBER_BUFFER_CAP = 10000;
|
|
|
25
25
|
export declare const SUBSCRIBER_BUFFER_BYTES: number;
|
|
26
26
|
/** What the plane's writes (`update` / `compact` / `fork` / `delete`) need — the SAME instances the
|
|
27
27
|
* agent assembly uses: the lease (a write must not race a run), the model registry (validation +
|
|
28
|
-
* allowedModels), and the session factory (compaction is a model call).
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* allowedModels), and the session factory (compaction is a model call). Two writes create a record:
|
|
29
|
+
* fork's `into`, and `update()` on an id that has none (through the store's `openOrCreate`, like an
|
|
30
|
+
* invoke). Every other write needs a record that exists. */
|
|
31
31
|
export interface PiBoundaryWiring {
|
|
32
32
|
lease: Lease;
|
|
33
33
|
models: Models;
|