@fastagent-sh/fastagent 0.19.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/atomic-write.d.ts +11 -7
- package/dist/atomic-write.js +16 -11
- package/dist/channels/agentcore-limits.d.ts +9 -0
- package/dist/channels/agentcore-limits.js +9 -0
- package/dist/channels/agentcore-protocol.d.ts +112 -0
- package/dist/channels/agentcore-protocol.js +22 -0
- package/dist/channels/agentcore-service.d.ts +12 -15
- package/dist/channels/agentcore-service.js +15 -24
- package/dist/channels/agentcore-state.d.ts +5 -11
- package/dist/channels/agentcore-state.js +4 -1
- package/dist/channels/agentcore.d.ts +9 -83
- package/dist/channels/agentcore.js +101 -93
- package/dist/channels/control.d.ts +50 -12
- package/dist/channels/control.js +251 -157
- package/dist/channels/discover.d.ts +26 -5
- package/dist/channels/discover.js +13 -26
- 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 +7 -12
- package/dist/channels/feishu/feishu.js +86 -128
- 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/attachment-path.d.ts +12 -0
- package/dist/channels/kit/attachment-path.js +43 -0
- package/dist/channels/kit/context-buffer.d.ts +9 -0
- package/dist/channels/kit/context-buffer.js +11 -0
- package/dist/channels/kit/signature.d.ts +12 -0
- package/dist/channels/kit/signature.js +17 -0
- package/dist/channels/kit/stop-command.js +3 -3
- package/dist/channels/kit/tasks.d.ts +1 -1
- package/dist/channels/kit/tasks.js +10 -4
- package/dist/channels/kit/turn-runner.d.ts +59 -0
- package/dist/channels/kit/turn-runner.js +84 -0
- package/dist/channels/kit/turn-store.d.ts +19 -3
- package/dist/channels/kit/turn-store.js +29 -2
- 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/invoke-turn.d.ts +1 -1
- package/dist/channels/slack/invoke-turn.js +5 -1
- package/dist/channels/slack/manifest.js +5 -1
- package/dist/channels/slack/onboard.d.ts +5 -5
- package/dist/channels/slack/onboard.js +44 -23
- package/dist/channels/slack/onboarding-state.d.ts +7 -3
- package/dist/channels/slack/onboarding-state.js +13 -20
- package/dist/channels/slack/register-webhook.d.ts +3 -2
- package/dist/channels/slack/register-webhook.js +35 -15
- package/dist/channels/slack/scaffold/channel.ts +3 -10
- package/dist/channels/slack/scaffold/slack-send.ts +18 -141
- 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 +100 -73
- package/dist/channels/slack/slack.d.ts +0 -10
- package/dist/channels/slack/slack.js +68 -107
- package/dist/channels/sse.d.ts +4 -0
- package/dist/channels/sse.js +66 -0
- package/dist/channels/telegram/parse.d.ts +21 -1
- package/dist/channels/telegram/parse.js +65 -11
- package/dist/channels/telegram/register-webhook.d.ts +6 -9
- package/dist/channels/telegram/register-webhook.js +44 -42
- package/dist/channels/telegram/scaffold/channel.ts +7 -3
- package/dist/channels/telegram/telegram-api.js +5 -6
- package/dist/channels/telegram/telegram.d.ts +2 -2
- package/dist/channels/telegram/telegram.js +97 -204
- package/dist/channels/wait-health.js +7 -4
- package/dist/cli/add-feishu.js +3 -10
- package/dist/cli/add-slack.js +13 -25
- package/dist/cli/commands/add.d.ts +0 -1
- package/dist/cli/commands/add.js +22 -35
- package/dist/cli/commands/attach.d.ts +2 -4
- package/dist/cli/commands/attach.js +17 -15
- 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 +38 -25
- package/dist/cli/commands/deploy.js +101 -631
- package/dist/cli/commands/dev.js +20 -76
- package/dist/cli/commands/fire.js +6 -12
- package/dist/cli/commands/info.js +1 -1
- package/dist/cli/commands/init.js +1 -2
- package/dist/cli/commands/invoke.js +4 -11
- package/dist/cli/commands/schedule.js +1 -1
- package/dist/cli/commands/start.js +41 -80
- 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 +42 -34
- package/dist/cli/serve.js +107 -28
- package/dist/cli/shared.d.ts +52 -27
- package/dist/cli/shared.js +69 -6
- package/dist/deploy/agentcore/forwarder.js +250 -0
- package/dist/deploy/agentcore/logs.d.ts +2 -2
- package/dist/deploy/agentcore/logs.js +2 -2
- package/dist/deploy/agentcore/plan.d.ts +34 -11
- package/dist/deploy/agentcore/plan.js +52 -259
- package/dist/deploy/agentcore/run.d.ts +9 -24
- package/dist/deploy/agentcore/run.js +35 -43
- package/dist/deploy/channel-ingress.d.ts +73 -0
- package/dist/deploy/channel-ingress.js +101 -0
- package/dist/deploy/docker/plan.d.ts +3 -7
- package/dist/deploy/docker/plan.js +4 -15
- package/dist/deploy/docker/run.d.ts +35 -4
- package/dist/deploy/docker/run.js +40 -8
- package/dist/deploy/fly/plan.d.ts +4 -5
- package/dist/deploy/fly/plan.js +14 -22
- package/dist/deploy/fly/run.d.ts +36 -23
- package/dist/deploy/fly/run.js +129 -83
- package/dist/deploy/hosts.d.ts +5 -0
- package/dist/deploy/hosts.js +4 -0
- package/dist/deploy/preflight.d.ts +6 -7
- package/dist/deploy/preflight.js +27 -23
- package/dist/deploy/railway/plan.d.ts +9 -5
- package/dist/deploy/railway/plan.js +18 -26
- package/dist/deploy/railway/run.d.ts +11 -10
- package/dist/deploy/railway/run.js +44 -43
- package/dist/deploy/runner.js +22 -2
- package/dist/deploy/secrets.d.ts +18 -9
- package/dist/deploy/secrets.js +63 -28
- package/dist/dev-supervisor.js +3 -4
- package/dist/engines/pi/agent-session-factory.d.ts +37 -17
- package/dist/engines/pi/agent-session-factory.js +111 -106
- package/dist/engines/pi/auth.js +43 -43
- package/dist/engines/pi/config.d.ts +2 -2
- package/dist/engines/pi/create.d.ts +22 -27
- package/dist/engines/pi/create.js +59 -74
- package/dist/engines/pi/definition.d.ts +1 -1
- package/dist/engines/pi/definition.js +11 -12
- package/dist/engines/pi/invoke-session.js +13 -30
- package/dist/engines/pi/login.js +32 -16
- package/dist/engines/pi/open.d.ts +3 -4
- package/dist/engines/pi/open.js +32 -42
- package/dist/engines/pi/retry-event.d.ts +6 -0
- package/dist/engines/pi/retry-event.js +15 -0
- package/dist/engines/pi/search-tools.js +1 -1
- package/dist/engines/pi/service.d.ts +1 -1
- package/dist/engines/pi/service.js +8 -0
- package/dist/engines/pi/session-builder.js +26 -140
- package/dist/engines/pi/session-control.d.ts +27 -22
- package/dist/engines/pi/session-control.js +551 -490
- package/dist/engines/pi/session-inheritance.d.ts +8 -22
- package/dist/engines/pi/session-inheritance.js +95 -76
- package/dist/engines/pi/session-markers.d.ts +48 -0
- package/dist/engines/pi/session-markers.js +59 -0
- package/dist/engines/pi/session-settings.d.ts +5 -5
- package/dist/engines/pi/session-settings.js +8 -5
- package/dist/engines/pi/session-store.d.ts +91 -26
- package/dist/engines/pi/session-store.js +413 -82
- package/dist/engines/pi/tool-context.d.ts +24 -11
- package/dist/engines/pi/tool-context.js +29 -4
- package/dist/engines/pi/tool.d.ts +6 -9
- package/dist/engines/pi/tool.js +3 -2
- package/dist/env.js +1 -2
- 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 +51 -7
- package/dist/loader.js +84 -18
- package/dist/log.d.ts +9 -17
- package/dist/log.js +25 -30
- package/dist/paths.d.ts +26 -3
- package/dist/paths.js +43 -5
- package/dist/scaffold/add-channel.d.ts +6 -1
- package/dist/scaffold/add-channel.js +50 -67
- package/dist/scaffold/init.js +7 -2
- package/dist/scaffold/templates/tools/fetch-url.ts +0 -2
- package/dist/schedule/discover.js +3 -15
- package/dist/schedule/wake-alarm.d.ts +14 -19
- package/dist/schedule/wake-alarm.js +89 -48
- package/dist/schedule/wakeups.d.ts +1 -1
- package/dist/schedule/wakeups.js +10 -7
- package/dist/service.d.ts +24 -24
- package/dist/service.js +33 -77
- package/dist/session-remote.d.ts +12 -7
- package/dist/session-remote.js +185 -118
- package/dist/session.d.ts +227 -93
- package/dist/session.js +61 -23
- package/dist/slack.d.ts +2 -0
- package/dist/slack.js +1 -0
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +1 -1
- package/dist/tunnel.d.ts +34 -9
- package/dist/tunnel.js +83 -50
- package/package.json +5 -4
- package/dist/channels/slack/bot-auth.d.ts +0 -15
- package/dist/channels/slack/bot-auth.js +0 -135
|
@@ -124,7 +124,7 @@ export function makeSearchToolsTool() {
|
|
|
124
124
|
.map((t) => t.name)
|
|
125
125
|
.join(", ")}${more > 0 ? ` … and ${more} more` : ""}.${activeNote ? ` ${activeNote}` : ""}`;
|
|
126
126
|
}
|
|
127
|
-
const activated =
|
|
127
|
+
const activated = ctx.tools.activate(inactiveMatches.map((t) => t.name));
|
|
128
128
|
// Report what actually happened, not what was attempted: a parallel sibling call may have
|
|
129
129
|
// activated the same matches first, leaving nothing new here — an empty "Activated:" would lie.
|
|
130
130
|
if (activated.length === 0) {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* pi-specific is opening a DIRECTORY into one, which is why this shortcut lives here and the
|
|
7
7
|
* assembly does not. A second engine ships its own opener and reuses `mountAgentService` unchanged.
|
|
8
8
|
*/
|
|
9
|
-
import type
|
|
9
|
+
import { type AgentService, type MountAgentServiceOptions } from "../../service.ts";
|
|
10
10
|
export interface CreateAgentServiceOptions extends MountAgentServiceOptions {
|
|
11
11
|
model?: string;
|
|
12
12
|
authPath?: string;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createAgentService` — the product as one call, with pi supplying the engine.
|
|
3
|
+
*
|
|
4
|
+
* The ASSEMBLY is engine-neutral and lives in `src/service.ts`: it takes a {@link MountableAgent}
|
|
5
|
+
* (the SPEC contract plus three paths) and knows nothing about how that agent was built. What is
|
|
6
|
+
* pi-specific is opening a DIRECTORY into one, which is why this shortcut lives here and the
|
|
7
|
+
* assembly does not. A second engine ships its own opener and reuses `mountAgentService` unchanged.
|
|
8
|
+
*/
|
|
1
9
|
import { mountAgentService } from "../../service.js";
|
|
2
10
|
import { createPiAgentFromDir } from "./open.js";
|
|
3
11
|
/**
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The shared definition-aware session builder: open a directory's assembled agent as a resident pi
|
|
3
|
-
* `AgentSessionRuntime
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* and deliberately not chat sessions (design §10/§15).
|
|
3
|
+
* `AgentSessionRuntime`, running the SAME agent that `dev`/`start` serve. The TUI (chat.ts) is its
|
|
4
|
+
* one consumer: the session control plane was built on the invoke pipeline instead of this resident
|
|
5
|
+
* runtime, so control-plane observation covers invoke-driven runs and deliberately not chat sessions
|
|
6
|
+
* (docs/design/session-control.md §10, §15).
|
|
8
7
|
*
|
|
9
8
|
* FIDELITY: pi's vanilla discovery (AGENTS.md walk to repo root, machine-global skills/extensions)
|
|
10
9
|
* is suppressed; fastagent's assembly is INJECTED into pi's session:
|
|
@@ -28,18 +27,16 @@
|
|
|
28
27
|
* workspace.
|
|
29
28
|
*/
|
|
30
29
|
import { existsSync, readFileSync } from "node:fs";
|
|
31
|
-
import {
|
|
30
|
+
import { resolve } from "node:path";
|
|
32
31
|
import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node";
|
|
33
|
-
import { SessionManager,
|
|
34
|
-
import { definitionResourceLoaderOptions, reportExtensionErrors } from "./agent-session-factory.js";
|
|
32
|
+
import { SessionManager, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, } from "@earendil-works/pi-coding-agent";
|
|
33
|
+
import { bindPiSession, definitionResourceLoaderOptions, reportExtensionErrors } from "./agent-session-factory.js";
|
|
35
34
|
import { resolveModel } from "./config.js";
|
|
36
35
|
import { assembleSystemPrompt, piBasePrompt } from "./create.js";
|
|
37
36
|
import { canonicalPath, loadAgentDefinition, loadExtensionPaths } from "./definition.js";
|
|
38
|
-
import { createPiModelRuntime
|
|
39
|
-
import {
|
|
40
|
-
import { additiveActivation, agentSessionManager, turnContext, } from "./tool-context.js";
|
|
37
|
+
import { createPiModelRuntime } from "./models.js";
|
|
38
|
+
import { reportModuleLoadFailures } from "../../loader.js";
|
|
41
39
|
import { reportFindingsIfChanged, reportToolCollisions } from "./report.js";
|
|
42
|
-
import { reportModuleLoadFailures } from "../../log.js";
|
|
43
40
|
import { resolveAgentAssembly } from "./open.js";
|
|
44
41
|
/**
|
|
45
42
|
* Build pi's interactive runtime driven by fastagent's assembled agent (model, prompt, tools,
|
|
@@ -49,45 +46,12 @@ import { resolveAgentAssembly } from "./open.js";
|
|
|
49
46
|
export async function buildAgentSessionRuntime(dir, options = {},
|
|
50
47
|
/** Session backend. Defaults to pi's project-scoped store; tests inject SessionManager.inMemory(). */
|
|
51
48
|
sessionManager) {
|
|
52
|
-
/** The turn's {@link ToolActivation} over pi's AgentSession — the counterpart of invoke.ts's
|
|
53
|
-
* serving bridge, so the SAME builtin search_tools serves both paths. Additive; unknown names
|
|
54
|
-
* filtered (`setActiveToolsByName` is authoritative on the session and rebuilds its prompt — our
|
|
55
|
-
* static override keeps the prompt identical to serving). */
|
|
56
|
-
function sessionToolActivation(session) {
|
|
57
|
-
// Same serialization as invoke.ts's bridge (there per turn; here per session — interactive turns
|
|
58
|
-
// make per-session equivalent): the read-modify-write below is only race-free while nothing awaits
|
|
59
|
-
// between read and write, and pi's session setters happening to be synchronous today is not a
|
|
60
|
-
// contract worth betting parallel tool batches on. Built ONCE per session (createRuntime), so
|
|
61
|
-
// parallel calls actually share the chain.
|
|
62
|
-
let chain = Promise.resolve([]);
|
|
63
|
-
return {
|
|
64
|
-
active: () => session.getActiveToolNames(),
|
|
65
|
-
registered: () => session.getAllTools().map((t) => ({ name: t.name, description: t.description ?? "" })),
|
|
66
|
-
activate(names) {
|
|
67
|
-
const run = async () => {
|
|
68
|
-
const current = session.getActiveToolNames();
|
|
69
|
-
const added = additiveActivation(session.getAllTools().map((t) => t.name), current, names);
|
|
70
|
-
if (added.length > 0)
|
|
71
|
-
session.setActiveToolsByName([...current, ...added]);
|
|
72
|
-
return added;
|
|
73
|
-
};
|
|
74
|
-
const result = chain.then(run, run); // run after the predecessor settles, success or failure
|
|
75
|
-
chain = result.catch(() => []); // the caller sees a rejection on `result`; the chain stays usable
|
|
76
|
-
return result;
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
49
|
async function resolveAssembly(cwd) {
|
|
81
50
|
// The shared front half — the SAME placement/config/model-spec/tool/auth resolution the serving
|
|
82
51
|
// opener uses (open.ts); those inputs cannot drift between the two consumption shapes.
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
// (what you iterate is what you serve): the initial active set excludes deferred tools (applied
|
|
87
|
-
// on the session in createRuntime — pi's session starts all-active), and the activation bridge
|
|
88
|
-
// above rides the same turn context, so the SAME search_tools works against pi's AgentSession
|
|
89
|
-
// instead of the served one.
|
|
90
|
-
const { config, modelSpec, agentDir, authPath, stateRoot, tools, deferredToolNames, toolCollisions, toolFailures } = await resolveAgentAssembly(cwd, options);
|
|
52
|
+
// Serving re-reads the definition per invoke; chat keeps a startup snapshot. Both pass identity
|
|
53
|
+
// and project context to Pi, which appends skills/cwd. `tools` already includes search_tools.
|
|
54
|
+
const { config, modelSpec, agentDir, authPath, stateRoot, tools, toolCollisions, toolFailures } = await resolveAgentAssembly(cwd, options);
|
|
91
55
|
reportToolCollisions(toolCollisions);
|
|
92
56
|
reportModuleLoadFailures(toolFailures);
|
|
93
57
|
// ONE hub owns model resolution AND per-request auth; see models.ts.
|
|
@@ -100,33 +64,6 @@ sessionManager) {
|
|
|
100
64
|
// Assembly-time, like serving's: this whole function is memoized, so the scan and its warnings
|
|
101
65
|
// happen once per runtime rather than per session rebuild (/new, /resume, fork).
|
|
102
66
|
const extensionPaths = await loadExtensionPaths(agentDir, { cwd, env });
|
|
103
|
-
// fastagent mounts pi's complete coding set itself; `noTools: "builtin"` below keeps the runtime
|
|
104
|
-
// from adding duplicate copies.
|
|
105
|
-
// Adapt fastagent's AgentTool to pi's ToolDefinition (`parameters` is plain JSON-Schema; pi accepts
|
|
106
|
-
// it). Each execute runs inside the turn context with the CURRENT session's activation bridge — the
|
|
107
|
-
// assembly is memoized across /new//resume/fork rebuilds while the session changes, so the bridge
|
|
108
|
-
// resolves through sessionRef at call time, exactly like the serving path resolves its session.
|
|
109
|
-
const customToolDefs = tools.map((t) => ({
|
|
110
|
-
name: t.name,
|
|
111
|
-
label: t.name,
|
|
112
|
-
description: t.description ?? "",
|
|
113
|
-
parameters: t.parameters,
|
|
114
|
-
// Propagate the execution mode — an activating tool (the builtin loader) declares "sequential"
|
|
115
|
-
// so pi serializes its batch; without this, pi's outer active-set diff double-stamps parallels.
|
|
116
|
-
executionMode: t.executionMode,
|
|
117
|
-
execute: (id, params, signal) => {
|
|
118
|
-
const bound = sessionRef.current;
|
|
119
|
-
// Unreachable by construction (createRuntime sets sessionRef before any turn can run a tool).
|
|
120
|
-
// Throw rather than silently run outside the turn context — that would disguise a broken
|
|
121
|
-
// session-lifecycle invariant as a normal out-of-turn call (fail visibly).
|
|
122
|
-
if (!bound)
|
|
123
|
-
throw new Error("tool executed before its session was built (lifecycle invariant broken)");
|
|
124
|
-
return turnContext.run({ cwd, sessionManager: bound.sessionManager, tools: bound.activation },
|
|
125
|
-
// Lower-level MountedTools may consume the fifth-argument env. Directory coding tools are
|
|
126
|
-
// cwd-bound and ignore it; authored tools read FastAgent's turnContext instead.
|
|
127
|
-
() => t.execute(id, params, signal, undefined, { env }));
|
|
128
|
-
},
|
|
129
|
-
}));
|
|
130
67
|
// base + instructions ONLY — pi appends the skill section and env (cwd) itself (including
|
|
131
68
|
// them here would duplicate them).
|
|
132
69
|
const systemPrompt = assembleSystemPrompt({
|
|
@@ -136,14 +73,11 @@ sessionManager) {
|
|
|
136
73
|
return {
|
|
137
74
|
modelRuntime,
|
|
138
75
|
modelSpec,
|
|
139
|
-
authPath,
|
|
140
76
|
// Serving honors config.thinkingLevel (config → L2); the resident session must too (fidelity).
|
|
141
77
|
thinkingLevel: config.thinkingLevel,
|
|
142
78
|
definition,
|
|
143
79
|
extensionPaths,
|
|
144
|
-
|
|
145
|
-
customToolDefs,
|
|
146
|
-
deferredToolNames,
|
|
80
|
+
tools,
|
|
147
81
|
systemPrompt,
|
|
148
82
|
};
|
|
149
83
|
}
|
|
@@ -153,13 +87,6 @@ sessionManager) {
|
|
|
153
87
|
// load edits. And keep it workspace-scoped — `.env` is process-global, so a switch to another cwd
|
|
154
88
|
// would leak env or require mutating global env at runtime.
|
|
155
89
|
const rootCwd = canonicalPath(dir);
|
|
156
|
-
// The CURRENT pi session + its activation bridge, BOUND TOGETHER — rebuilt on /new//resume/fork
|
|
157
|
-
// while the memoized assembly (and its tool execute closures) stays. The bridge must share the
|
|
158
|
-
// session's lifetime, NOT be rebuilt per tool call (a per-call chain serializes nothing). Note on
|
|
159
|
-
// parallel batches: pi wraps SDK customTools in its own before/after active-set diff, so an
|
|
160
|
-
// activating tool must carry `executionMode: "sequential"` (the builtin loader does) — pi then runs
|
|
161
|
-
// the whole batch serially and the outer diff sees correct snapshots.
|
|
162
|
-
const sessionRef = {};
|
|
163
90
|
let assembly;
|
|
164
91
|
const assemblyFor = (cwd) => {
|
|
165
92
|
// Canonical paths: pi's process.cwd() fallback is a realpath, so a symlinked workspace would
|
|
@@ -171,12 +98,8 @@ sessionManager) {
|
|
|
171
98
|
assembly ??= resolveAssembly(rootCwd);
|
|
172
99
|
return assembly;
|
|
173
100
|
};
|
|
174
|
-
// The credential hint belongs to the RUNTIME, not to each session it builds: model resolution
|
|
175
|
-
// moved into createRuntime (extensions must load first), and repeating this on every /new,
|
|
176
|
-
// /resume and fork would nag about a setting that did not change.
|
|
177
|
-
let credentialHintShown = false;
|
|
178
101
|
const createRuntime = async ({ cwd, sessionManager, sessionStartEvent }) => {
|
|
179
|
-
const { modelRuntime, modelSpec,
|
|
102
|
+
const { modelRuntime, modelSpec, thinkingLevel, definition, extensionPaths, tools, systemPrompt } = await assemblyFor(cwd);
|
|
180
103
|
// Per session, NOT memoized with the assembly: pi replaces the session on /new, /resume and
|
|
181
104
|
// fork, and its extension contract is that the replacement gets freshly loaded extensions
|
|
182
105
|
// rather than the previous session's objects. The expensive halves (model hub, auth) are shared
|
|
@@ -199,62 +122,25 @@ sessionManager) {
|
|
|
199
122
|
// AFTER the services, because an extension may be what defines the model. `registerProvider()`
|
|
200
123
|
// is pi's documented way for one to add providers, and extensions do not execute until the
|
|
201
124
|
// services are built — resolving first fails a definition whose configured model comes from its
|
|
202
|
-
// own extension with a bare "unknown model"
|
|
203
|
-
// that does not exist yet.
|
|
204
|
-
//
|
|
205
|
-
// MIGRATION HINT (deliberate breaking change): chat historically used pi's own `~/.pi` auth; it
|
|
206
|
-
// now reads the agent's credential file like every other command. Probe the RESOLVED model's
|
|
207
|
-
// provider through the normal resolution path (stored credential OR env var — an env-authed
|
|
208
|
-
// user is fine and must not be warned): only when that provider has no usable auth AND pi's old
|
|
209
|
-
// file exists does the bare provider error get its cause named.
|
|
210
|
-
if (!credentialHintShown &&
|
|
211
|
-
(await probeAuthSource(modelRuntime, modelSpec)) === undefined &&
|
|
212
|
-
existsSync(join(getAgentDir(), "auth.json"))) {
|
|
213
|
-
credentialHintShown = true;
|
|
214
|
-
log.warn(`[fastagent] no credentials for ${modelSpec} in ${authPath} — this runtime no longer reads ` +
|
|
215
|
-
`pi's ~/.pi auth; run \`fastagent login\` (or /login in the TUI) to store credentials for this agent`);
|
|
216
|
-
}
|
|
125
|
+
// own extension with a bare "unknown model".
|
|
217
126
|
const model = resolveModel(modelRuntime, modelSpec);
|
|
218
|
-
|
|
127
|
+
// The same bind serving performs, minus the activation record: pi's chat session has nowhere to
|
|
128
|
+
// put one, which is the documented divergence — a resumed chat re-discovers via search_tools.
|
|
129
|
+
// NOT bound to the host here: InteractiveMode.bindCurrentSessionExtensions() calls
|
|
130
|
+
// session.bindExtensions() with the TUI's uiContext, abort handler and command actions — binding
|
|
131
|
+
// here too would emit session_start twice per chat, so an extension opening a resource on start
|
|
132
|
+
// would open two.
|
|
133
|
+
const result = await bindPiSession({
|
|
219
134
|
services,
|
|
220
135
|
sessionManager,
|
|
221
136
|
sessionStartEvent,
|
|
222
137
|
model,
|
|
223
138
|
thinkingLevel,
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// was really there for (the machine's `defaultTools` setting cannot add pi's own copies on top
|
|
229
|
-
// of ours) without freezing anything.
|
|
230
|
-
noTools: "builtin",
|
|
231
|
-
customTools: customToolDefs,
|
|
139
|
+
tools,
|
|
140
|
+
// A tool must see one spelling of the workspace, including when opened through a symlink.
|
|
141
|
+
cwd: rootCwd,
|
|
142
|
+
recordActivations: false,
|
|
232
143
|
});
|
|
233
|
-
sessionRef.current = {
|
|
234
|
-
session: result.session,
|
|
235
|
-
sessionManager: agentSessionManager(result.session, result.session.sessionManager.getSessionId()),
|
|
236
|
-
activation: sessionToolActivation(result.session),
|
|
237
|
-
};
|
|
238
|
-
// NOT bound here: the HOST does it. InteractiveMode.bindCurrentSessionExtensions() calls
|
|
239
|
-
// session.bindExtensions() with the TUI's uiContext, abort handler and command actions — binding
|
|
240
|
-
// here too would emit session_start twice per chat, so an extension opening a resource on start
|
|
241
|
-
// would open two.
|
|
242
|
-
// Deferral emulation: pi starts THIS agent's tools active — every coding tool it mounted, plus
|
|
243
|
-
// every custom and extension tool. So narrow by SUBTRACTING the deferred names from whatever is
|
|
244
|
-
// active, rather than stating a set: an exact-set-equality gate would silently stop narrowing the
|
|
245
|
-
// day pi activates one more.
|
|
246
|
-
//
|
|
247
|
-
// Applied on EVERY build including /resume: pi's chat session does not record activations (its
|
|
248
|
-
// SessionContext has no activeToolNames), so "restore prior activations" is not implementable
|
|
249
|
-
// here — deferral stays consistently ON and a resumed conversation re-discovers via search_tools
|
|
250
|
-
// (a documented divergence from serving, where activations persist in the session). The deferred
|
|
251
|
-
// SET comes from the shared assembly (one definition of "deferred"), never recomputed here.
|
|
252
|
-
if (deferredToolNames.length > 0) {
|
|
253
|
-
const active = result.session.getActiveToolNames();
|
|
254
|
-
if (deferredToolNames.some((n) => active.includes(n))) {
|
|
255
|
-
result.session.setActiveToolsByName(active.filter((n) => !deferredToolNames.includes(n)));
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
144
|
return { ...result, services, diagnostics: services.diagnostics };
|
|
259
145
|
};
|
|
260
146
|
const runtime = await createAgentSessionRuntime(createRuntime, {
|
|
@@ -1,22 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pi implementation of the session control plane. `createPiSessionControl` returns the neutral
|
|
3
|
+
* `SessionControl` plus the {@link SessionObserver} to plug into the invoke pipeline
|
|
4
|
+
* (`createPiAgent({ observer })`).
|
|
5
|
+
*
|
|
6
|
+
* It holds no durable state of its own: live truth comes from the event stream (plus the
|
|
7
|
+
* {@link RunControls} a `run_started` carries), durable truth from {@link PiSessionRecordStore} —
|
|
8
|
+
* which is also what performs every write, so how a record takes a property is not knowledge this
|
|
9
|
+
* file has. What it owns is the vocabulary: capability gating, the lease, error codes, and the
|
|
10
|
+
* events its own writes emit.
|
|
11
|
+
*
|
|
12
|
+
* Writes take the same lease as runs. Without boundary wiring they reject before acceptance with
|
|
13
|
+
* `unsupported_capability` — a client gating on `capabilities()` never sends them.
|
|
14
|
+
*/
|
|
1
15
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import type
|
|
16
|
+
import { type Models } from "@earendil-works/pi-ai";
|
|
3
17
|
import { type AgentCommand, type SessionControl, type SessionEvent } from "../../session.ts";
|
|
4
|
-
import type { SessionObserver } from "./turn-kit.ts";
|
|
5
|
-
import type { Lease } from "./turn-kit.ts";
|
|
18
|
+
import type { SessionObserver, Lease } from "./turn-kit.ts";
|
|
6
19
|
import type { AnyModel } from "./models.ts";
|
|
7
20
|
import type { PiAgentSessionFactory } from "./invoke-session.ts";
|
|
8
21
|
import type { PiSessionRecordStore } from "./session-store.ts";
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
* has its buffer FROZEN at the cap (memory bounded — the actual goal: ≈10k small events ≈ a few
|
|
12
|
-
* MB worst case per stuck connection) and its subscription marked closed. The close is observed
|
|
13
|
-
* via pulls — which a stalled connection by definition does not make — so a consumer that RESUMES
|
|
14
|
-
* pulling first drains the frozen backlog, then gets done (no buffered event dropped), while a
|
|
15
|
-
* permanently stalled one holds the frozen buffer until its TCP connection dies. Recovery either
|
|
16
|
-
* way is the standard reconnect+backfill, semantically lossless. */
|
|
22
|
+
/** Per-subscriber backlog limits: event count and UTF-8 JSON bytes. Overflow freezes the accepted
|
|
23
|
+
* prefix; the reader drains it before seeing done, then reconnects and backfills via entries(). */
|
|
17
24
|
export declare const SUBSCRIBER_BUFFER_CAP = 10000;
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
export declare const SUBSCRIBER_BUFFER_BYTES: number;
|
|
26
|
+
/** What the plane's writes (`update` / `compact` / `fork` / `delete`) need — the SAME instances the
|
|
27
|
+
* agent assembly uses: the lease (a write must not race a run), the model registry (validation +
|
|
20
28
|
* allowedModels), and the session factory (compaction is a model call). Writes go through the
|
|
21
29
|
* record the hub's reader opened — after an existence check, so the control plane never creates
|
|
22
30
|
* a session (that is the data plane's monopoly). */
|
|
@@ -36,15 +44,12 @@ export interface PiBoundaryWiring {
|
|
|
36
44
|
export interface CreatePiSessionControlOptions {
|
|
37
45
|
/** Read-only access to the durable session records (the same root the agent writes). */
|
|
38
46
|
sessions: PiSessionRecordStore;
|
|
39
|
-
/** Boundary-mutation wiring
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* exists before the assembly that can read a definition) — and async because the definition is
|
|
46
|
-
* live: this must re-read it, not close over a boot snapshot, or `commands()` would advertise a
|
|
47
|
-
* list the next turn no longer runs.
|
|
47
|
+
/** Boundary-mutation wiring — the assembly's own parts. Absent → boundary commands are gated off
|
|
48
|
+
* in `capabilities()` and rejected `unsupported_capability`. */
|
|
49
|
+
boundary?: PiBoundaryWiring;
|
|
50
|
+
/** The definition's names, as an async thunk because the definition is live: this must re-read it,
|
|
51
|
+
* not close over a boot snapshot, or `commands()` would advertise a list the next turn no longer
|
|
52
|
+
* runs.
|
|
48
53
|
*
|
|
49
54
|
* OPTIONAL because absence is a TRUE answer for the assembly that omits it: a hub over an L1
|
|
50
55
|
* agent (`createPiAgent({ model, instructions, tools })`) has no definition and therefore no
|