@fastagent-sh/fastagent 0.14.0 → 0.15.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 +3 -3
- package/dist/agent.d.ts +20 -0
- package/dist/agent.js +9 -0
- package/dist/channels/context-buffer.d.ts +30 -0
- package/dist/channels/context-buffer.js +98 -0
- package/dist/channels/control.d.ts +28 -0
- package/dist/channels/control.js +214 -0
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +8 -11
- package/dist/channels/feishu/context-buffer.js +6 -67
- package/dist/channels/feishu/feishu-api.d.ts +11 -3
- package/dist/channels/feishu/feishu-api.js +20 -2
- package/dist/channels/feishu/feishu.d.ts +28 -16
- package/dist/channels/feishu/feishu.js +210 -188
- package/dist/channels/feishu/invoke-turn.d.ts +7 -20
- package/dist/channels/feishu/invoke-turn.js +10 -62
- package/dist/channels/feishu/model.d.ts +2 -1
- package/dist/channels/feishu/normalize.d.ts +2 -1
- package/dist/channels/feishu/owned-threads.d.ts +2 -6
- package/dist/channels/feishu/parse.d.ts +1 -1
- package/dist/channels/feishu/preview.d.ts +4 -7
- package/dist/channels/feishu/preview.js +24 -140
- package/dist/channels/feishu/register-app.d.ts +2 -1
- package/dist/channels/feishu/register-webhook.d.ts +2 -1
- package/dist/channels/feishu/setup-mode.d.ts +8 -0
- package/dist/channels/feishu/setup-mode.js +2 -0
- package/dist/channels/feishu/ws-ingress.d.ts +28 -0
- package/dist/channels/feishu/ws-ingress.js +136 -0
- package/dist/channels/github/github.js +8 -6
- package/dist/channels/http.d.ts +14 -0
- package/dist/channels/http.js +35 -2
- package/dist/channels/invoke-turn-kit.d.ts +65 -0
- package/dist/channels/invoke-turn-kit.js +87 -0
- package/dist/channels/lark/lark.d.ts +4 -2
- package/dist/channels/lark/lark.js +4 -1
- package/dist/channels/lark/onboard.d.ts +8 -4
- package/dist/channels/lark/onboard.js +8 -0
- package/dist/channels/preview-kit.d.ts +109 -0
- package/dist/channels/preview-kit.js +183 -0
- package/dist/channels/seen.d.ts +5 -0
- package/dist/channels/seen.js +35 -0
- package/dist/channels/slack/bot-auth.d.ts +15 -0
- package/dist/channels/slack/bot-auth.js +146 -0
- package/dist/channels/slack/config-api.d.ts +60 -0
- package/dist/channels/slack/config-api.js +149 -0
- package/dist/channels/slack/context-buffer.d.ts +24 -0
- package/dist/channels/slack/context-buffer.js +37 -0
- package/dist/channels/slack/invoke-turn.d.ts +19 -0
- package/dist/channels/slack/invoke-turn.js +63 -0
- package/dist/channels/slack/manifest.d.ts +49 -0
- package/dist/channels/slack/manifest.js +69 -0
- package/dist/channels/slack/model.d.ts +67 -0
- package/dist/channels/slack/model.js +2 -0
- package/dist/channels/slack/onboard.d.ts +41 -0
- package/dist/channels/slack/onboard.js +120 -0
- package/dist/channels/slack/onboarding-state.d.ts +31 -0
- package/dist/channels/slack/onboarding-state.js +69 -0
- package/dist/channels/slack/owned-threads.d.ts +6 -0
- package/dist/channels/slack/owned-threads.js +43 -0
- package/dist/channels/slack/parse.d.ts +23 -0
- package/dist/channels/slack/parse.js +81 -0
- package/dist/channels/slack/preview.d.ts +24 -0
- package/dist/channels/slack/preview.js +359 -0
- package/dist/channels/slack/reaction.d.ts +24 -0
- package/dist/channels/slack/reaction.js +62 -0
- package/dist/channels/slack/register-webhook.d.ts +10 -0
- package/dist/channels/slack/register-webhook.js +49 -0
- package/dist/channels/slack/scaffold/channel.ts +33 -0
- package/dist/channels/slack/scaffold/slack-send.ts +171 -0
- package/dist/channels/slack/setup-server.d.ts +17 -0
- package/dist/channels/slack/setup-server.js +103 -0
- package/dist/channels/slack/slack-api.d.ts +77 -0
- package/dist/channels/slack/slack-api.js +415 -0
- package/dist/channels/slack/slack.d.ts +58 -0
- package/dist/channels/slack/slack.js +451 -0
- package/dist/channels/slack/welcomed.d.ts +5 -0
- package/dist/channels/slack/welcomed.js +32 -0
- package/dist/channels/state.js +3 -3
- package/dist/channels/stop-command.d.ts +6 -0
- package/dist/channels/stop-command.js +36 -0
- package/dist/channels/tasks.d.ts +13 -0
- package/dist/channels/tasks.js +10 -0
- package/dist/channels/telegram/context-buffer.d.ts +8 -17
- package/dist/channels/telegram/context-buffer.js +6 -85
- package/dist/channels/telegram/invoke-turn.d.ts +5 -22
- package/dist/channels/telegram/invoke-turn.js +11 -58
- package/dist/channels/telegram/preview.d.ts +4 -7
- package/dist/channels/telegram/preview.js +24 -142
- package/dist/channels/telegram/telegram.js +23 -9
- package/dist/channels/{feishu/text.d.ts → text.d.ts} +1 -1
- package/dist/channels/{feishu/text.js → text.js} +1 -1
- package/dist/channels/turn-queue.js +1 -1
- package/dist/channels/turn-store.d.ts +1 -1
- package/dist/channels/turn-store.js +2 -3
- package/dist/cli/add-feishu.d.ts +27 -0
- package/dist/{cli-add-feishu.js → cli/add-feishu.js} +167 -44
- package/dist/cli/add-slack.d.ts +10 -0
- package/dist/cli/add-slack.js +204 -0
- package/dist/{cli-auth.d.ts → cli/auth-view.d.ts} +1 -1
- package/dist/{cli-auth.js → cli/auth-view.js} +1 -1
- package/dist/cli/commands/add.d.ts +4 -0
- package/dist/cli/commands/add.js +152 -20
- package/dist/cli/commands/attach.d.ts +82 -0
- package/dist/cli/commands/attach.js +559 -0
- package/dist/cli/commands/chat.d.ts +1 -0
- package/dist/cli/commands/chat.js +6 -1
- package/dist/cli/commands/deploy.d.ts +2 -0
- package/dist/cli/commands/deploy.js +78 -22
- package/dist/cli/commands/dev.js +11 -5
- package/dist/cli/commands/fire.js +3 -3
- package/dist/cli/commands/info.js +2 -2
- package/dist/cli/commands/invoke.js +2 -3
- package/dist/cli/commands/login.js +14 -25
- package/dist/cli/commands/models.js +1 -1
- package/dist/cli/commands/start.js +15 -8
- package/dist/cli/commands/tool.js +2 -1
- package/dist/cli/fail.d.ts +0 -2
- package/dist/cli/fail.js +1 -1
- package/dist/{invoke-stream.d.ts → cli/invoke-stream.d.ts} +1 -1
- package/dist/{invoke-stream.js → cli/invoke-stream.js} +4 -0
- package/dist/cli/kernel.d.ts +3 -2
- package/dist/cli/models-view.d.ts +21 -0
- package/dist/cli/models-view.js +66 -0
- package/dist/cli/program.js +79 -21
- package/dist/cli/serve.d.ts +38 -18
- package/dist/cli/serve.js +197 -39
- package/dist/cli/shared.d.ts +26 -6
- package/dist/cli/shared.js +163 -33
- package/dist/collect.d.ts +14 -3
- package/dist/collect.js +24 -0
- package/dist/core.d.ts +3 -1
- package/dist/core.js +2 -0
- package/dist/deploy/docker/plan.d.ts +2 -2
- package/dist/deploy/docker/plan.js +8 -6
- package/dist/deploy/fly/plan.d.ts +3 -1
- package/dist/deploy/fly/plan.js +12 -7
- package/dist/deploy/fly/run.d.ts +5 -4
- package/dist/deploy/fly/run.js +16 -5
- package/dist/deploy/preflight.d.ts +10 -4
- package/dist/deploy/preflight.js +53 -9
- package/dist/deploy/railway/plan.d.ts +3 -1
- package/dist/deploy/railway/plan.js +12 -4
- package/dist/deploy/railway/run.d.ts +5 -4
- package/dist/deploy/railway/run.js +15 -5
- package/dist/deploy/runner.d.ts +2 -1
- package/dist/deploy/secrets.d.ts +2 -1
- package/dist/deploy/secrets.js +23 -3
- package/dist/dev-supervisor.d.ts +0 -2
- package/dist/dev-supervisor.js +7 -3
- package/dist/engines/pi/channel.d.ts +22 -16
- package/dist/engines/pi/channel.js +90 -60
- package/dist/engines/pi/chat.d.ts +4 -16
- package/dist/engines/pi/chat.js +8 -261
- package/dist/engines/pi/config.d.ts +14 -9
- package/dist/engines/pi/config.js +33 -18
- package/dist/engines/pi/create.d.ts +22 -2
- package/dist/engines/pi/create.js +20 -14
- package/dist/engines/pi/harness.d.ts +49 -0
- package/dist/engines/pi/harness.js +90 -2
- package/dist/engines/pi/invoke.d.ts +55 -2
- package/dist/engines/pi/invoke.js +304 -19
- package/dist/engines/pi/login.d.ts +11 -0
- package/dist/engines/pi/login.js +16 -4
- package/dist/engines/pi/models.d.ts +56 -10
- package/dist/engines/pi/models.js +61 -23
- package/dist/engines/pi/session-builder.d.ts +16 -0
- package/dist/engines/pi/session-builder.js +308 -0
- package/dist/engines/pi/session-control.d.ts +50 -0
- package/dist/engines/pi/session-control.js +604 -0
- package/dist/engines/pi/sessions.d.ts +17 -2
- package/dist/engines/pi/sessions.js +9 -0
- package/dist/engines/pi/tool-context.d.ts +18 -11
- package/dist/engines/pi/tool-context.js +3 -9
- package/dist/engines/pi/tool.d.ts +6 -8
- package/dist/engines/pi/tool.js +6 -1
- package/dist/engines/pi/wake-tool.d.ts +0 -3
- package/dist/engines/pi/wake-tool.js +9 -7
- package/dist/engines/pi/workspace.d.ts +52 -0
- package/dist/engines/pi/workspace.js +72 -15
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/host/node.d.ts +23 -6
- package/dist/host/node.js +5 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/observe.js +3 -0
- package/dist/pi.d.ts +5 -4
- package/dist/pi.js +2 -1
- package/dist/scaffold/add-channel.d.ts +19 -6
- package/dist/scaffold/add-channel.js +126 -17
- package/dist/scaffold/templates/fastagent.config.mjs +4 -3
- package/dist/schedule/wakeups.d.ts +0 -3
- package/dist/schedule/wakeups.js +1 -1
- package/dist/session-remote.d.ts +53 -0
- package/dist/session-remote.js +336 -0
- package/dist/session.d.ts +265 -0
- package/dist/session.js +37 -0
- package/dist/slack.d.ts +2 -0
- package/dist/slack.js +2 -0
- package/dist/tunnel.d.ts +7 -4
- package/dist/tunnel.js +21 -10
- package/package.json +19 -7
- package/dist/channels/feishu/seen.d.ts +0 -5
- package/dist/channels/feishu/seen.js +0 -47
- package/dist/cli-add-feishu.d.ts +0 -8
- package/dist/cli-models.d.ts +0 -11
- package/dist/cli-models.js +0 -20
package/dist/engines/pi/chat.js
CHANGED
|
@@ -3,271 +3,18 @@
|
|
|
3
3
|
* engine-neutral channel: it drives pi's full session API (InteractiveMode) for fidelity, so it lives
|
|
4
4
|
* under engines/pi/ and is not re-exported.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - prompt → systemPromptOverride = base + instructions ONLY; pi appends the skill section and env
|
|
10
|
-
* (cwd) itself (including it here would duplicate it).
|
|
11
|
-
* - skills → skillsOverride (fastagent's skills, for the section + invocation).
|
|
12
|
-
* - tools → default coding tools by NAME (pi rebuilds them cwd-bound for rich rendering) +
|
|
13
|
-
* fastagent's custom tools via pi's customTools path (so they survive /new, /resume, fork).
|
|
14
|
-
*
|
|
15
|
-
* Cross-workspace session switches are rejected: `.env` is process-global, so one chat TUI is one
|
|
16
|
-
* workspace.
|
|
17
|
-
*
|
|
18
|
-
* AUTH: chat is the one command that does NOT use fastagent's credential file. It drives pi's own
|
|
19
|
-
* session services (`createAgentSessionServices`, auth from pi's `~/.pi` via `getAgentDir()`), so you
|
|
20
|
-
* log in through pi's TUI, not `fastagent login`. `--auth-path`/`FASTAGENT_AUTH_PATH` therefore do not
|
|
21
|
-
* apply here, and `createPiModels()` below is used only to RESOLVE the model descriptor (never for auth).
|
|
22
|
-
*/
|
|
23
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
-
import { dirname, resolve } from "node:path";
|
|
25
|
-
import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node";
|
|
26
|
-
import { InteractiveMode, SessionManager, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, } from "@earendil-works/pi-coding-agent";
|
|
27
|
-
import { loadConfig, resolveAgentDir, resolveModel, resolveModelSpec } from "./config.js";
|
|
28
|
-
import { assembleSystemPrompt, piBasePrompt, piDefaultTools, resolveTools } from "./create.js";
|
|
29
|
-
import { createPiModels } from "./models.js";
|
|
30
|
-
import { canonicalPath, loadAgentDefinition } from "./definition.js";
|
|
31
|
-
import { isDeferredTool, loadTools, mergeDiscoveredTools } from "./tool.js";
|
|
32
|
-
import { withSearchTool } from "./search-tools.js";
|
|
33
|
-
import { additiveActivation, turnContext } from "./tool-context.js";
|
|
34
|
-
import { reportDefinitionWarnings, reportModuleLoadFailures, reportToolCollisions } from "./report.js";
|
|
35
|
-
/**
|
|
36
|
-
* Build pi's interactive runtime driven by fastagent's assembled agent (model, prompt, tools,
|
|
37
|
-
* skills resolved exactly as the dev opener does). Split from {@link runPiChat} so the assembly —
|
|
38
|
-
* the fidelity-critical part — is inspectable without launching the TUI.
|
|
39
|
-
*/
|
|
40
|
-
export async function buildChatRuntime(dir, options = {},
|
|
41
|
-
/** Session backend. Defaults to pi's project-scoped store; tests inject SessionManager.inMemory(). */
|
|
42
|
-
sessionManager) {
|
|
43
|
-
/** The turn's {@link ToolActivation} over pi's AgentSession — the chat counterpart of invoke.ts's
|
|
44
|
-
* harness bridge, so the SAME builtin search_tools serves both paths. Additive; unknown names
|
|
45
|
-
* filtered (`setActiveToolsByName` is authoritative on the session and rebuilds its prompt — our
|
|
46
|
-
* static override keeps the prompt identical to serving). */
|
|
47
|
-
function chatToolActivation(session) {
|
|
48
|
-
// Same serialization as invoke.ts's bridge (there per turn; here per session — chat turns are
|
|
49
|
-
// interactive, so per-session is equivalent): the read-modify-write below is only race-free while
|
|
50
|
-
// nothing awaits between read and write, and pi's session setters happening to be synchronous today
|
|
51
|
-
// is not a contract worth betting parallel tool batches on. Built ONCE per session (createRuntime),
|
|
52
|
-
// so parallel calls actually share the chain.
|
|
53
|
-
let chain = Promise.resolve([]);
|
|
54
|
-
return {
|
|
55
|
-
active: () => session.getActiveToolNames(),
|
|
56
|
-
registered: () => session.getAllTools().map((t) => ({ name: t.name, description: t.description ?? "" })),
|
|
57
|
-
activate(names) {
|
|
58
|
-
const run = async () => {
|
|
59
|
-
const current = session.getActiveToolNames();
|
|
60
|
-
const added = additiveActivation(session.getAllTools().map((t) => t.name), current, names);
|
|
61
|
-
if (added.length > 0)
|
|
62
|
-
session.setActiveToolsByName([...current, ...added]);
|
|
63
|
-
return added;
|
|
64
|
-
};
|
|
65
|
-
const result = chain.then(run, run); // run after the predecessor settles, success or failure
|
|
66
|
-
chain = result.catch(() => []); // the caller sees a rejection on `result`; the chain stays usable
|
|
67
|
-
return result;
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
async function resolveAssembly(cwd) {
|
|
72
|
-
const { config } = await loadConfig(cwd);
|
|
73
|
-
const modelSpec = resolveModelSpec(options.model, config);
|
|
74
|
-
if (!modelSpec) {
|
|
75
|
-
throw new Error(`missing model: set --model, "model" in fastagent.config.ts, or FASTAGENT_MODEL (e.g. "openai-codex/gpt-5.5")`);
|
|
76
|
-
}
|
|
77
|
-
// Resolution only — the Models' auth is unused here; chat's auth is pi's own (~/.pi via the session
|
|
78
|
-
// services), so authPath is intentionally not threaded in. See the AUTH note in the header.
|
|
79
|
-
const model = resolveModel(createPiModels(), modelSpec);
|
|
80
|
-
const env = new NodeExecutionEnv({ cwd });
|
|
81
|
-
// Same agentDir/cwd split as dev/start: persona/skills/tools from agentDir, ② context walked from cwd.
|
|
82
|
-
const agentDir = resolveAgentDir(cwd, config);
|
|
83
|
-
const definition = await loadAgentDefinition(agentDir, { cwd, env });
|
|
84
|
-
reportDefinitionWarnings(definition.collisions, definition.diagnostics);
|
|
85
|
-
// Same tool resolution as the dev opener, then split: defaults go to pi by NAME (rebuilt cwd-bound
|
|
86
|
-
// for rich rendering); customs go through pi's `customTools` path so they survive /new, /resume, fork.
|
|
87
|
-
const discovered = await loadTools(agentDir);
|
|
88
|
-
const merged = mergeDiscoveredTools(resolveTools(config, cwd), discovered.tools);
|
|
89
|
-
// Chat EMULATES deferral, like serving (what you iterate is what you serve): the builtin loader
|
|
90
|
-
// mounts when a deferred tool exists, the initial active set excludes deferred tools (applied on
|
|
91
|
-
// the session in createRuntime below — pi's TUI session starts all-active), and the activation
|
|
92
|
-
// bridge below rides the same turn context the serving path uses, so the SAME search_tools works
|
|
93
|
-
// against pi's AgentSession instead of fastagent's harness.
|
|
94
|
-
const tools = withSearchTool(merged.tools);
|
|
95
|
-
const crossCollisions = merged.collisions;
|
|
96
|
-
reportToolCollisions([...discovered.collisions, ...crossCollisions]);
|
|
97
|
-
reportModuleLoadFailures(discovered.failures);
|
|
98
|
-
const defaultNames = piDefaultTools(cwd).map((t) => t.name);
|
|
99
|
-
const customTools = tools.filter((t) => !defaultNames.includes(t.name));
|
|
100
|
-
// Adapt fastagent's AgentTool to pi's ToolDefinition (`parameters` is plain JSON-Schema; pi accepts
|
|
101
|
-
// it). Each execute runs inside the turn context with the CURRENT session's activation bridge — the
|
|
102
|
-
// assembly is memoized across /new//resume/fork rebuilds while the session changes, so the bridge
|
|
103
|
-
// resolves through sessionRef at call time, exactly like the serving path resolves its harness.
|
|
104
|
-
const customToolDefs = customTools.map((t) => ({
|
|
105
|
-
name: t.name,
|
|
106
|
-
label: t.name,
|
|
107
|
-
description: t.description ?? "",
|
|
108
|
-
parameters: t.parameters,
|
|
109
|
-
// Propagate the execution mode — an activating tool (the builtin loader) declares "sequential"
|
|
110
|
-
// so pi serializes its batch; without this, pi's outer active-set diff double-stamps parallels.
|
|
111
|
-
executionMode: t.executionMode,
|
|
112
|
-
execute: (id, params, signal) => {
|
|
113
|
-
const bound = sessionRef.current;
|
|
114
|
-
// Unreachable by construction (createRuntime sets sessionRef before any turn can run a tool).
|
|
115
|
-
// Throw rather than silently run outside the turn context — that would disguise a broken
|
|
116
|
-
// session-lifecycle invariant as a normal out-of-turn call (fail visibly).
|
|
117
|
-
if (!bound)
|
|
118
|
-
throw new Error("chat tool executed before its session was built (lifecycle invariant broken)");
|
|
119
|
-
return turnContext.run({ session: bound.session.sessionId, tools: bound.activation }, () => t.execute(id, params, signal));
|
|
120
|
-
},
|
|
121
|
-
}));
|
|
122
|
-
// base + instructions ONLY — pi appends the skill section and env (cwd) itself (including
|
|
123
|
-
// them here would duplicate them).
|
|
124
|
-
const systemPrompt = assembleSystemPrompt({
|
|
125
|
-
base: piBasePrompt({ tools, persona: definition.persona }),
|
|
126
|
-
contextFiles: definition.contextFiles,
|
|
127
|
-
});
|
|
128
|
-
return { model, definition, defaultNames, customTools, customToolDefs, systemPrompt };
|
|
129
|
-
}
|
|
130
|
-
// pi calls the factory again on /new, /resume, switch, and fork. Config/tools dynamic imports are
|
|
131
|
-
// ESM-cached, so treating same-cwd rebuilds as hot reload would yield a half-fresh agent (fresh
|
|
132
|
-
// AGENTS.md/skills, stale config/tools). Keep chat a coherent startup snapshot: restart to load
|
|
133
|
-
// edits. And keep it workspace-scoped — `.env` is process-global, so a switch to another cwd would
|
|
134
|
-
// leak env or require mutating global env at runtime.
|
|
135
|
-
const rootCwd = canonicalPath(dir);
|
|
136
|
-
// The CURRENT pi session + its activation bridge, BOUND TOGETHER — rebuilt on /new//resume/fork
|
|
137
|
-
// while the memoized assembly (and its tool execute closures) stays. The bridge must share the
|
|
138
|
-
// session's lifetime, NOT be rebuilt per tool call (a per-call chain serializes nothing). Note on
|
|
139
|
-
// parallel batches: pi wraps SDK customTools in its own before/after active-set diff, so an
|
|
140
|
-
// activating tool must carry `executionMode: "sequential"` (the builtin loader does) — pi then runs
|
|
141
|
-
// the whole batch serially and the outer diff sees correct snapshots.
|
|
142
|
-
const sessionRef = {};
|
|
143
|
-
let assembly;
|
|
144
|
-
const assemblyFor = (cwd) => {
|
|
145
|
-
// Canonical paths: pi's process.cwd() fallback is a realpath, so a symlinked workspace would
|
|
146
|
-
// otherwise mismatch a non-realpath rootCwd.
|
|
147
|
-
const activeCwd = canonicalPath(cwd);
|
|
148
|
-
if (activeCwd !== rootCwd) {
|
|
149
|
-
throw workspaceScopeError(activeCwd);
|
|
150
|
-
}
|
|
151
|
-
assembly ??= resolveAssembly(rootCwd);
|
|
152
|
-
return assembly;
|
|
153
|
-
};
|
|
154
|
-
const createRuntime = async ({ cwd, sessionManager, sessionStartEvent }) => {
|
|
155
|
-
const { model, definition, defaultNames, customTools, customToolDefs, systemPrompt } = await assemblyFor(cwd);
|
|
156
|
-
const services = await createAgentSessionServices({
|
|
157
|
-
cwd,
|
|
158
|
-
resourceLoaderOptions: {
|
|
159
|
-
// Definition-only, like dev/start: suppress pi's machine-global discovery (the developer's own
|
|
160
|
-
// ~/.pi extensions, slash commands, global AGENTS.md, APPEND_SYSTEM.md) so chat runs the same
|
|
161
|
-
// agent that gets served, not the authoring machine's pi setup on top.
|
|
162
|
-
noExtensions: true,
|
|
163
|
-
noPromptTemplates: true,
|
|
164
|
-
noContextFiles: true,
|
|
165
|
-
systemPromptOverride: () => systemPrompt,
|
|
166
|
-
appendSystemPromptOverride: () => [],
|
|
167
|
-
// Replace pi's discovered skills with fastagent's. fastagent's Skill (content inline) is
|
|
168
|
-
// reshaped to pi-coding-agent's (read from filePath/baseDir at invocation time).
|
|
169
|
-
skillsOverride: (base) => ({
|
|
170
|
-
skills: definition.skills.map((s) => ({
|
|
171
|
-
name: s.name,
|
|
172
|
-
description: s.description,
|
|
173
|
-
filePath: s.filePath,
|
|
174
|
-
baseDir: dirname(s.filePath),
|
|
175
|
-
sourceInfo: {
|
|
176
|
-
path: s.filePath,
|
|
177
|
-
source: "fastagent",
|
|
178
|
-
scope: "project",
|
|
179
|
-
origin: "top-level",
|
|
180
|
-
baseDir: dirname(s.filePath),
|
|
181
|
-
},
|
|
182
|
-
disableModelInvocation: s.disableModelInvocation ?? false,
|
|
183
|
-
})),
|
|
184
|
-
diagnostics: base.diagnostics,
|
|
185
|
-
}),
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
const result = await createAgentSessionFromServices({
|
|
189
|
-
services,
|
|
190
|
-
sessionManager,
|
|
191
|
-
sessionStartEvent,
|
|
192
|
-
model,
|
|
193
|
-
tools: [...defaultNames, ...customTools.map((t) => t.name)],
|
|
194
|
-
customTools: customToolDefs,
|
|
195
|
-
});
|
|
196
|
-
sessionRef.current = { session: result.session, activation: chatToolActivation(result.session) };
|
|
197
|
-
// Deferral emulation: pi's TUI session starts with everything active — narrow it by SUBTRACTING
|
|
198
|
-
// the deferred names from whatever is active (robust to pi mounting tools of its own; an
|
|
199
|
-
// exact-set-equality gate would silently stop narrowing the day pi adds one). Applied on EVERY
|
|
200
|
-
// build including /resume: pi's chat session does not record activations (its SessionContext has
|
|
201
|
-
// no activeToolNames), so "restore prior activations" is not implementable here — deferral stays
|
|
202
|
-
// consistently ON and a resumed conversation re-discovers via search_tools (documented divergence
|
|
203
|
-
// from serving, where activations persist in the session).
|
|
204
|
-
const deferredNames = customTools.filter(isDeferredTool).map((t) => t.name);
|
|
205
|
-
if (deferredNames.length > 0) {
|
|
206
|
-
const active = result.session.getActiveToolNames();
|
|
207
|
-
if (deferredNames.some((n) => active.includes(n))) {
|
|
208
|
-
result.session.setActiveToolsByName(active.filter((n) => !deferredNames.includes(n)));
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return { ...result, services, diagnostics: services.diagnostics };
|
|
212
|
-
};
|
|
213
|
-
const runtime = await createAgentSessionRuntime(createRuntime, {
|
|
214
|
-
cwd: rootCwd,
|
|
215
|
-
agentDir: getAgentDir(),
|
|
216
|
-
sessionManager: sessionManager ?? SessionManager.create(rootCwd),
|
|
217
|
-
});
|
|
218
|
-
enforceWorkspaceScopedSessionSwitches(runtime, rootCwd);
|
|
219
|
-
return runtime;
|
|
220
|
-
}
|
|
221
|
-
function workspaceScopeError(targetCwd) {
|
|
222
|
-
return new Error(`fastagent chat is workspace-scoped: cannot switch to ${targetCwd}; run \`fastagent chat ${targetCwd}\` instead`);
|
|
223
|
-
}
|
|
224
|
-
function readSessionHeaderCwd(sessionPath) {
|
|
225
|
-
const resolvedPath = resolve(sessionPath);
|
|
226
|
-
if (!existsSync(resolvedPath))
|
|
227
|
-
return undefined;
|
|
228
|
-
for (const line of readFileSync(resolvedPath, "utf8").split(/\r?\n/)) {
|
|
229
|
-
if (!line.trim())
|
|
230
|
-
continue;
|
|
231
|
-
try {
|
|
232
|
-
const entry = JSON.parse(line);
|
|
233
|
-
if (entry.type === "session")
|
|
234
|
-
return typeof entry.cwd === "string" ? canonicalPath(entry.cwd) : undefined;
|
|
235
|
-
}
|
|
236
|
-
catch {
|
|
237
|
-
// Ignore malformed lines the same way pi's session loader does; no header cwd → caller pins root.
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return undefined;
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Keep resume/import inside the chat's single workspace, deciding BEFORE delegating to pi. The chat
|
|
244
|
-
* process is chdir'd into rootCwd, so a session with no cwd header already lands on rootCwd. The gap
|
|
245
|
-
* is a session that EXPLICITLY records a different cwd: pi would bind it and the factory would reject
|
|
246
|
-
* it — but only AFTER tearing the live session down. Reject such a switch up front.
|
|
6
|
+
* The TUI is ONE consumer of the shared definition-aware builder (session-builder.ts) — the same
|
|
7
|
+
* assembly, model surface, and fastagent auth that serving uses. Log in with `fastagent login` (or
|
|
8
|
+
* pi's TUI `/login`, which writes through the same credential store into the workspace auth file).
|
|
247
9
|
*/
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const target = cwdOverride !== undefined ? canonicalPath(cwdOverride) : readSessionHeaderCwd(sessionPath);
|
|
251
|
-
if (target !== undefined && target !== rootCwd)
|
|
252
|
-
throw workspaceScopeError(target);
|
|
253
|
-
};
|
|
254
|
-
const switchSession = runtime.switchSession.bind(runtime);
|
|
255
|
-
runtime.switchSession = async (...args) => {
|
|
256
|
-
rejectForeignTarget(args[0], args[1]?.cwdOverride);
|
|
257
|
-
return switchSession(...args);
|
|
258
|
-
};
|
|
259
|
-
const importFromJsonl = runtime.importFromJsonl.bind(runtime);
|
|
260
|
-
runtime.importFromJsonl = async (...args) => {
|
|
261
|
-
rejectForeignTarget(args[0], args[1]);
|
|
262
|
-
return importFromJsonl(...args);
|
|
263
|
-
};
|
|
264
|
-
}
|
|
10
|
+
import { InteractiveMode } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { buildWorkspaceSessionRuntime } from "./session-builder.js";
|
|
265
12
|
/**
|
|
266
13
|
* Open the workspace's agent in pi's interactive TUI and run until the user exits. The agent is
|
|
267
|
-
* fastagent's assembled agent (same model/tools/skills/prompt as dev/start serve); pi's TUI
|
|
268
|
-
* login, rendering, and same-workspace sessions natively.
|
|
14
|
+
* fastagent's assembled agent (same model/tools/skills/prompt/auth as dev/start serve); pi's TUI
|
|
15
|
+
* handles login, rendering, and same-workspace sessions natively.
|
|
269
16
|
*/
|
|
270
17
|
export async function runPiChat(dir, options = {}) {
|
|
271
|
-
const runtime = await
|
|
18
|
+
const runtime = await buildWorkspaceSessionRuntime(dir, options);
|
|
272
19
|
await new InteractiveMode(runtime, {}).run();
|
|
273
20
|
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import type { FastagentTool } from "./tool.ts";
|
|
3
3
|
import type { Models } from "@earendil-works/pi-ai";
|
|
4
|
-
import type
|
|
5
|
-
/** pi's thinking levels (types.d.ts `ThinkingLevel`), as a runtime list for config validation — pi
|
|
6
|
-
* exports only the type. A level the selected model does not support is clamped by pi per model. */
|
|
7
|
-
export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
4
|
+
import { type AnyModel } from "./harness.ts";
|
|
8
5
|
export interface FastagentConfig {
|
|
9
6
|
/** "provider/modelId". Precedence: CLI --model > FASTAGENT_MODEL > config. */
|
|
10
7
|
model?: string;
|
|
@@ -30,6 +27,15 @@ export interface FastagentConfig {
|
|
|
30
27
|
* Off by default — self-scheduling is an autonomy capability, opt in when you want it. Only takes
|
|
31
28
|
* effect on the serving path (`dev`/`start`, where the scheduler poller honors a wake-up). */
|
|
32
29
|
selfSchedule?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Serve the session control plane over HTTP (`/control/*`: state/entries/events + dispatch —
|
|
32
|
+
* steer/abort/compact/set_model…) for remote consumers: a Web panel, a desktop app, `fastagent
|
|
33
|
+
* attach`. Default off (it is a remote-control surface). When on, `dev`/`start` generate a
|
|
34
|
+
* per-boot bearer token and write `<stateRoot>/control.json` for local discovery. The serve
|
|
35
|
+
* binds all interfaces, so the routes are LAN-reachable with the token as the only protection —
|
|
36
|
+
* firewall the port, or wrap it for real exposure (design §14).
|
|
37
|
+
*/
|
|
38
|
+
sessionControl?: boolean;
|
|
33
39
|
/** Deploy-time declarations for what the agent needs on the box, so real agents don't hand-write a
|
|
34
40
|
* Dockerfile / hand-set variables. */
|
|
35
41
|
deploy?: {
|
|
@@ -65,6 +71,10 @@ export declare function loadConfig(dir: string): Promise<LoadedConfig>;
|
|
|
65
71
|
* already validated that agentDir stays under `dir`.
|
|
66
72
|
*/
|
|
67
73
|
export declare function resolveAgentDir(dir: string, config: FastagentConfig): string;
|
|
74
|
+
/** The provider prefix of a "provider/modelId" spec. A spec without "/" returns whole — downstream
|
|
75
|
+
* lookups then miss visibly (an unknown-provider error / a login-required hint), never a mangled id
|
|
76
|
+
* (`slice(0, indexOf("/"))` silently drops the last char when "/" is absent). */
|
|
77
|
+
export declare function providerOf(spec: string): string;
|
|
68
78
|
/** Resolve "provider/modelId" → a pi Model from `models`, so the harness resolves auth from the same collection. */
|
|
69
79
|
export declare function resolveModel(models: Models, spec: string): AnyModel;
|
|
70
80
|
/** All registered "provider/modelId" specs in `models`, sorted — the list behind `fastagent models`. */
|
|
@@ -93,11 +103,6 @@ export declare function resolveSessionsDirOverride(flag: string | undefined, env
|
|
|
93
103
|
* `~/.fastagent/auth.json` — sharing ONE file is safe under the store's cross-process refresh lock.
|
|
94
104
|
*/
|
|
95
105
|
export declare function resolveAuthPathOverride(flag: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
|
|
96
|
-
/**
|
|
97
|
-
* The IN-TREE default state root, `<dir>/.fastagent` — what {@link resolveStateRoot} falls back to when
|
|
98
|
-
* `FASTAGENT_STATE_DIR` moves state nowhere. THE single definition of that path segment.
|
|
99
|
-
*/
|
|
100
|
-
export declare function projectStateDir(dir: string): string;
|
|
101
106
|
/**
|
|
102
107
|
* The resolved state root — the ONE durable machine-state home everything derives from (auth.json,
|
|
103
108
|
* sessions/, channels/<kind>/): `FASTAGENT_STATE_DIR` env > `<dir>/.fastagent`. Absolute, so channels
|
|
@@ -14,22 +14,12 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
14
14
|
* live in persona.md + skills, with AGENTS.md as project context). Deleting the config still leaves a
|
|
15
15
|
* zero-config agent runnable with a model supplied by --model / FASTAGENT_MODEL.
|
|
16
16
|
*/
|
|
17
|
-
import { existsSync, lstatSync, realpathSync } from "node:fs";
|
|
17
|
+
import { existsSync, lstatSync, realpathSync, statSync } from "node:fs";
|
|
18
18
|
import { homedir } from "node:os";
|
|
19
19
|
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
20
20
|
import { pathToFileURL } from "node:url";
|
|
21
|
+
import { THINKING_LEVELS } from "./harness.js";
|
|
21
22
|
import { moduleLoadHint } from "../../loader.js";
|
|
22
|
-
/** pi's thinking levels (types.d.ts `ThinkingLevel`), as a runtime list for config validation — pi
|
|
23
|
-
* exports only the type. A level the selected model does not support is clamped by pi per model. */
|
|
24
|
-
export const THINKING_LEVELS = [
|
|
25
|
-
"off",
|
|
26
|
-
"minimal",
|
|
27
|
-
"low",
|
|
28
|
-
"medium",
|
|
29
|
-
"high",
|
|
30
|
-
"xhigh",
|
|
31
|
-
"max",
|
|
32
|
-
];
|
|
33
23
|
/** Identity function for typing and IDE completion (vite/next-style). */
|
|
34
24
|
export function defineConfig(config) {
|
|
35
25
|
return config;
|
|
@@ -67,7 +57,14 @@ export async function loadConfig(dir) {
|
|
|
67
57
|
const path = found[0];
|
|
68
58
|
let mod;
|
|
69
59
|
try {
|
|
70
|
-
|
|
60
|
+
// Cache-bust on file change: ESM `import()` caches by URL, so a config REWRITTEN in this process
|
|
61
|
+
// (the first-run picker's write-back) would otherwise read back stale — deploy's model-travel gate
|
|
62
|
+
// then contradicts the "saved model" line it just printed. mtime keeps unchanged files cached;
|
|
63
|
+
// its resolution is the ceiling — a rewrite within the same timestamp tick reads stale (fine for
|
|
64
|
+
// the write-back: sub-tick only on coarse-mtime filesystems, and the next process starts fresh).
|
|
65
|
+
const url = pathToFileURL(path);
|
|
66
|
+
url.searchParams.set("v", String(statSync(path).mtimeMs));
|
|
67
|
+
mod = (await import(__rewriteRelativeImportExtension(url.href)));
|
|
71
68
|
}
|
|
72
69
|
catch (error) {
|
|
73
70
|
throw new Error(`${path}: ${error.message}${moduleLoadHint(error)}`);
|
|
@@ -86,15 +83,19 @@ export async function loadConfig(dir) {
|
|
|
86
83
|
key !== "tools" &&
|
|
87
84
|
key !== "http" &&
|
|
88
85
|
key !== "deploy" &&
|
|
89
|
-
key !== "selfSchedule"
|
|
90
|
-
|
|
86
|
+
key !== "selfSchedule" &&
|
|
87
|
+
key !== "sessionControl") {
|
|
88
|
+
throw new Error(`${path}: unknown key "${key}" (valid keys: model, thinkingLevel, agentDir, tools, http, deploy, selfSchedule, sessionControl)`);
|
|
91
89
|
}
|
|
92
90
|
}
|
|
93
91
|
if (c.model !== undefined && typeof c.model !== "string") {
|
|
94
92
|
throw new Error(`${path}: "model" must be a "provider/modelId" string`);
|
|
95
93
|
}
|
|
96
|
-
if (c.
|
|
97
|
-
throw new Error(`${path}: "
|
|
94
|
+
if (c.sessionControl !== undefined && typeof c.sessionControl !== "boolean") {
|
|
95
|
+
throw new Error(`${path}: "sessionControl" must be a boolean`);
|
|
96
|
+
}
|
|
97
|
+
if (c.thinkingLevel !== undefined && !THINKING_LEVELS.has(c.thinkingLevel)) {
|
|
98
|
+
throw new Error(`${path}: "thinkingLevel" must be one of ${[...THINKING_LEVELS].join(", ")}`);
|
|
98
99
|
}
|
|
99
100
|
if (c.agentDir !== undefined && typeof c.agentDir !== "string") {
|
|
100
101
|
throw new Error(`${path}: "agentDir" must be a string (a subdirectory relative to the config file)`);
|
|
@@ -189,6 +190,13 @@ export async function loadConfig(dir) {
|
|
|
189
190
|
export function resolveAgentDir(dir, config) {
|
|
190
191
|
return resolve(dir, config.agentDir ?? ".");
|
|
191
192
|
}
|
|
193
|
+
/** The provider prefix of a "provider/modelId" spec. A spec without "/" returns whole — downstream
|
|
194
|
+
* lookups then miss visibly (an unknown-provider error / a login-required hint), never a mangled id
|
|
195
|
+
* (`slice(0, indexOf("/"))` silently drops the last char when "/" is absent). */
|
|
196
|
+
export function providerOf(spec) {
|
|
197
|
+
const slash = spec.indexOf("/");
|
|
198
|
+
return slash > 0 ? spec.slice(0, slash) : spec;
|
|
199
|
+
}
|
|
192
200
|
/** Resolve "provider/modelId" → a pi Model from `models`, so the harness resolves auth from the same collection. */
|
|
193
201
|
export function resolveModel(models, spec) {
|
|
194
202
|
const slash = spec.indexOf("/");
|
|
@@ -227,6 +235,13 @@ export function rewriteConfigModel(src, spec) {
|
|
|
227
235
|
return src.replace(commented, line);
|
|
228
236
|
if (active.test(src))
|
|
229
237
|
return src.replace(active, line);
|
|
238
|
+
// No model line at all — the natural state after "picked once, then hand-deleted the line to reset".
|
|
239
|
+
// Re-INSERT at the top of the default-export object while the config still has the scaffold's block
|
|
240
|
+
// shape; anything else (a wrapper call, a one-liner, a computed export) is hand-shaped — leave it
|
|
241
|
+
// untouched (the caller prints the set-it-yourself hint).
|
|
242
|
+
const opener = /^export default[ \t]*\{[ \t]*$/m;
|
|
243
|
+
if (opener.test(src))
|
|
244
|
+
return src.replace(opener, (open) => `${open}\n${line}`);
|
|
230
245
|
return null;
|
|
231
246
|
}
|
|
232
247
|
/** Model selection precedence: CLI flag > FASTAGENT_MODEL env > config default. */
|
|
@@ -268,7 +283,7 @@ export function resolveAuthPathOverride(flag, env = process.env) {
|
|
|
268
283
|
* The IN-TREE default state root, `<dir>/.fastagent` — what {@link resolveStateRoot} falls back to when
|
|
269
284
|
* `FASTAGENT_STATE_DIR` moves state nowhere. THE single definition of that path segment.
|
|
270
285
|
*/
|
|
271
|
-
|
|
286
|
+
function projectStateDir(dir) {
|
|
272
287
|
return join(dir, ".fastagent");
|
|
273
288
|
}
|
|
274
289
|
/**
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { AgentTool, ExecutionEnv, Skill, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import type { Provider } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { Models, Provider } from "@earendil-works/pi-ai";
|
|
3
3
|
import type { Agent } from "../../agent.ts";
|
|
4
4
|
import { type FastagentConfig } from "./config.ts";
|
|
5
5
|
import { type LoadedDefinition } from "./definition.ts";
|
|
6
|
+
import { piHarnessFactory } from "./harness.ts";
|
|
6
7
|
import { type PiSessionStore } from "./sessions.ts";
|
|
7
8
|
import type { ModuleLoadFailure } from "../../loader.ts";
|
|
8
9
|
import { type FastagentTool, type ToolCollision } from "./tool.ts";
|
|
9
|
-
import { type Lease } from "./invoke.ts";
|
|
10
|
+
import { type Lease, type SessionObserver } from "./invoke.ts";
|
|
10
11
|
/** pi's core default toolset (read/bash/edit/write), rooted at cwd. */
|
|
11
12
|
export declare function piDefaultTools(cwd: string): AgentTool[];
|
|
12
13
|
/** `config.tools` semantics: extra tools APPENDED after pi's defaults, never replacing them. */
|
|
@@ -52,6 +53,17 @@ export interface AssembleSystemPromptOptions {
|
|
|
52
53
|
cwd?: string;
|
|
53
54
|
}
|
|
54
55
|
export declare function assembleSystemPrompt(options: AssembleSystemPromptOptions): string;
|
|
56
|
+
/**
|
|
57
|
+
* INTERNAL seam (workspace ↔ assembly): hands the hub-wiring consumer the assembly's live parts —
|
|
58
|
+
* the SAME models collection, harness factory, and lease the agent runs with — so boundary
|
|
59
|
+
* mutations (session-control.ts) contend on the real lease and validate against the real registry.
|
|
60
|
+
* Called synchronously, exactly once, before the agent is returned. Not part of the public surface.
|
|
61
|
+
*/
|
|
62
|
+
type OnAssembly = (parts: {
|
|
63
|
+
models: Models;
|
|
64
|
+
harnessFactory: ReturnType<typeof piHarnessFactory>;
|
|
65
|
+
lease: Lease;
|
|
66
|
+
}) => void;
|
|
55
67
|
/** L1 options. Tier 1: model (spec) + instructions + tools. Tier 2: the injectable ports. */
|
|
56
68
|
export interface CreatePiAgentOptions {
|
|
57
69
|
/** Model spec "provider/modelId" (e.g. "openai-codex/gpt-5.5"), resolved against {@link models}. */
|
|
@@ -86,6 +98,9 @@ export interface CreatePiAgentOptions {
|
|
|
86
98
|
env?: ExecutionEnv;
|
|
87
99
|
/** Single-writer lease. Defaults to in-process fail-fast inProcessLease(). */
|
|
88
100
|
lease?: Lease;
|
|
101
|
+
/** Observation-plane tap (session control): every rich session event of every run. Wire the one
|
|
102
|
+
* returned by `createPiSessionControl` to serve `state`/`entries`/`events` for this agent. */
|
|
103
|
+
observer?: SessionObserver;
|
|
89
104
|
}
|
|
90
105
|
/** L1: assemble from typed parts. */
|
|
91
106
|
export declare function createPiAgent(options: CreatePiAgentOptions): Agent;
|
|
@@ -124,6 +139,10 @@ export interface CreatePiAgentFromDefinitionOptions {
|
|
|
124
139
|
* context loader remain local today, so injecting this alone does not sandbox a directory agent. */
|
|
125
140
|
env?: ExecutionEnv;
|
|
126
141
|
lease?: Lease;
|
|
142
|
+
/** Observation-plane tap; see {@link CreatePiAgentOptions.observer}. */
|
|
143
|
+
observer?: SessionObserver;
|
|
144
|
+
/** INTERNAL seam for hub wiring; see {@link OnAssembly}. */
|
|
145
|
+
onAssembly?: OnAssembly;
|
|
127
146
|
}
|
|
128
147
|
/**
|
|
129
148
|
* L2: "point at a directory → agent": load + assemble (base + AGENTS.md + skills + env) + L1 in one
|
|
@@ -133,3 +152,4 @@ export declare function createPiAgentFromDefinition(dir: string, options: Create
|
|
|
133
152
|
agent: Agent;
|
|
134
153
|
definition: LoadedDefinition;
|
|
135
154
|
}>;
|
|
155
|
+
export {};
|
|
@@ -21,7 +21,7 @@ import { reportDefinitionWarnings } from "./report.js";
|
|
|
21
21
|
import { inMemorySessionStore } from "./sessions.js";
|
|
22
22
|
import { isDeferredTool, loadTools, mergeDiscoveredTools, } from "./tool.js";
|
|
23
23
|
import { withSearchTool } from "./search-tools.js";
|
|
24
|
-
import { createPiAgentFromHarness } from "./invoke.js";
|
|
24
|
+
import { createPiAgentFromHarness, inProcessLease } from "./invoke.js";
|
|
25
25
|
// ── §1 tools ─────────────────────────────────────────────────────────────────
|
|
26
26
|
//
|
|
27
27
|
// The full pi toolset is the default for fidelity: authors vibe in local pi with it, so serving with
|
|
@@ -141,20 +141,23 @@ export function assembleSystemPrompt(options) {
|
|
|
141
141
|
*/
|
|
142
142
|
function buildPiAgent(opts) {
|
|
143
143
|
const models = createPiModels({ providers: opts.providers, authPath: opts.authPath });
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
144
|
+
const env = opts.env ?? new NodeExecutionEnv({ cwd: process.cwd() });
|
|
145
|
+
// Materialized here (not defaulted inside createPiAgentFromHarness) so the exposed parts carry
|
|
146
|
+
// the SAME lease instance the agent runs under — boundary mutations must contend on it.
|
|
147
|
+
const lease = opts.lease ?? inProcessLease();
|
|
148
|
+
const harnessFactory = piHarnessFactory({
|
|
149
|
+
sessions: opts.sessions ?? inMemorySessionStore(),
|
|
150
|
+
env,
|
|
151
|
+
models,
|
|
152
|
+
model: resolveModel(models, opts.model),
|
|
153
|
+
thinkingLevel: opts.thinkingLevel,
|
|
154
|
+
systemPrompt: opts.systemPrompt,
|
|
155
|
+
tools: opts.tools,
|
|
156
|
+
skills: opts.skills,
|
|
157
|
+
live: opts.live,
|
|
157
158
|
});
|
|
159
|
+
opts.onAssembly?.({ models, harnessFactory, lease });
|
|
160
|
+
return createPiAgentFromHarness({ lease, observer: opts.observer, cwd: env.cwd, harnessFactory });
|
|
158
161
|
}
|
|
159
162
|
/**
|
|
160
163
|
* L1 system prompt: `instructions` ARE the prompt (no engine base, no wrapping); the skills listing
|
|
@@ -185,6 +188,7 @@ export function createPiAgent(options) {
|
|
|
185
188
|
sessions: options.sessions,
|
|
186
189
|
env: options.env,
|
|
187
190
|
lease: options.lease,
|
|
191
|
+
observer: options.observer,
|
|
188
192
|
});
|
|
189
193
|
}
|
|
190
194
|
/** Stable identity of a definition's non-fatal findings, for change-detection in `live` (dedup only). */
|
|
@@ -253,6 +257,8 @@ export async function createPiAgentFromDefinition(dir, options) {
|
|
|
253
257
|
sessions: options.sessions,
|
|
254
258
|
env,
|
|
255
259
|
lease: options.lease,
|
|
260
|
+
observer: options.observer,
|
|
261
|
+
onAssembly: options.onAssembly,
|
|
256
262
|
});
|
|
257
263
|
return { agent, definition };
|
|
258
264
|
}
|
|
@@ -60,6 +60,55 @@ export interface PiHarnessFactoryOptions {
|
|
|
60
60
|
skills?: Skill[];
|
|
61
61
|
}>;
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Retry policy for generated compaction/branch-summary model calls (pi ≥0.81.1, #6901). OPT-IN
|
|
65
|
+
* upstream — an undefined policy means no retries — so both compaction paths pass it explicitly:
|
|
66
|
+
* the harness config (auto-compaction inside a run) and the manual `compact()` dispatch in
|
|
67
|
+
* session-control. Values mirror pi's own app defaults (maxRetries 3, base 2s exponential).
|
|
68
|
+
*/
|
|
69
|
+
export declare const SUMMARIZATION_RETRY_POLICY: {
|
|
70
|
+
readonly enabled: true;
|
|
71
|
+
readonly maxRetries: 3;
|
|
72
|
+
readonly baseDelayMs: 2000;
|
|
73
|
+
};
|
|
74
|
+
export declare const THINKING_LEVELS: ReadonlySet<ThinkingLevel>;
|
|
75
|
+
/** The shape both override consumers walk — a session entry, structurally. */
|
|
76
|
+
export interface OverrideEntryLike {
|
|
77
|
+
type: string;
|
|
78
|
+
provider?: string;
|
|
79
|
+
modelId?: string;
|
|
80
|
+
thinkingLevel?: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The session's durable override FACTS — the ONE walk both surfaces consume (`state()` reports the
|
|
84
|
+
* recorded truth; `resolveHarnessOverrides` below applies registry/scale fallbacks on top). The
|
|
85
|
+
* LAST entry of each kind wins, and a malformed record reads as ABSENT for that kind — never
|
|
86
|
+
* skipped over to an earlier record: the reporting surface and the execution surface must agree on
|
|
87
|
+
* which record is "the" override.
|
|
88
|
+
*/
|
|
89
|
+
export declare function lastOverrideEntries(entries: OverrideEntryLike[]): {
|
|
90
|
+
model?: {
|
|
91
|
+
provider: string;
|
|
92
|
+
modelId: string;
|
|
93
|
+
};
|
|
94
|
+
thinkingLevel?: string;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Resolve the session's model/thinking OVERRIDES for a fresh harness — same shape as the
|
|
98
|
+
* active-tools resolve above: pi writes `model_change`/`thinking_level_change` entries on explicit
|
|
99
|
+
* setModel/setThinkingLevel (the control plane's `set_model`/`set_thinking` append them directly)
|
|
100
|
+
* but a fresh harness never reads them back. Override facts come from {@link lastOverrideEntries};
|
|
101
|
+
* this adds the EXECUTION fallbacks: a recorded model no longer in this deployment's registry falls
|
|
102
|
+
* back to the default with a deduped warn (fail visibly without bricking the session — the
|
|
103
|
+
* conversation must survive a registry change across deploys); an unknown thinking level likewise.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveHarnessOverrides(entries: OverrideEntryLike[], models: Models, defaults: {
|
|
106
|
+
model: AnyModel;
|
|
107
|
+
thinkingLevel: ThinkingLevel;
|
|
108
|
+
}, sessionId: string): {
|
|
109
|
+
model: AnyModel;
|
|
110
|
+
thinkingLevel: ThinkingLevel;
|
|
111
|
+
};
|
|
63
112
|
export declare function resolveHarnessActiveToolNames(recorded: string[] | null, tools: AgentTool[], sessionId: string): string[] | undefined;
|
|
64
113
|
/** Open-or-create the session per invoke: existing → open (history via buildContext); missing → create. */
|
|
65
114
|
export declare function piHarnessFactory(options: PiHarnessFactoryOptions): PiHarnessFactory;
|