@fastagent-sh/fastagent 0.18.0 → 0.20.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 +25 -7
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/atomic-write.d.ts +23 -0
- package/dist/atomic-write.js +53 -0
- package/dist/{host/node.d.ts → channel.d.ts} +24 -28
- package/dist/channel.js +1 -0
- package/dist/channels/agentcore-limits.d.ts +9 -0
- package/dist/channels/agentcore-limits.js +9 -0
- package/dist/channels/agentcore-service.d.ts +55 -0
- package/dist/channels/agentcore-service.js +107 -0
- package/dist/channels/agentcore.d.ts +22 -20
- package/dist/channels/agentcore.js +20 -11
- package/dist/channels/busy.d.ts +1 -1
- package/dist/channels/busy.js +1 -1
- package/dist/channels/control.d.ts +55 -7
- package/dist/channels/control.js +312 -67
- package/dist/channels/discover.d.ts +59 -0
- package/dist/{engines/pi/channel.js → channels/discover.js} +19 -34
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +1 -1
- package/dist/channels/feishu/context-buffer.js +4 -4
- package/dist/channels/feishu/feishu-api.js +6 -9
- package/dist/channels/feishu/feishu.d.ts +1 -1
- package/dist/channels/feishu/feishu.js +41 -24
- package/dist/channels/feishu/invoke-turn.d.ts +1 -1
- package/dist/channels/feishu/invoke-turn.js +3 -4
- package/dist/channels/feishu/preview.d.ts +1 -1
- package/dist/channels/feishu/preview.js +7 -3
- package/dist/channels/feishu/ws-ingress.d.ts +1 -1
- package/dist/channels/github/github.d.ts +1 -1
- package/dist/channels/http.d.ts +11 -7
- package/dist/channels/http.js +1 -109
- package/dist/channels/kit/attachment-path.d.ts +12 -0
- package/dist/channels/kit/attachment-path.js +43 -0
- package/dist/channels/{context-buffer.d.ts → kit/context-buffer.d.ts} +9 -0
- package/dist/channels/{context-buffer.js → kit/context-buffer.js} +13 -2
- package/dist/channels/{invoke-turn-kit.d.ts → kit/invoke-turn-kit.d.ts} +14 -3
- package/dist/channels/{invoke-turn-kit.js → kit/invoke-turn-kit.js} +17 -6
- package/dist/channels/{preview-kit.d.ts → kit/preview-kit.d.ts} +3 -1
- package/dist/channels/{seen.js → kit/seen.js} +1 -1
- package/dist/channels/kit/signature.d.ts +12 -0
- package/dist/channels/kit/signature.js +17 -0
- package/dist/channels/{state.js → kit/state.js} +4 -7
- package/dist/channels/{stop-command.d.ts → kit/stop-command.d.ts} +1 -1
- package/dist/channels/{stop-command.js → kit/stop-command.js} +5 -5
- package/dist/channels/{tasks.d.ts → kit/tasks.d.ts} +1 -1
- package/dist/channels/{tasks.js → kit/tasks.js} +11 -5
- package/dist/channels/{thread-participants.js → kit/thread-participants.js} +1 -1
- package/dist/channels/{turn-queue.js → kit/turn-queue.js} +2 -2
- package/dist/channels/{turn-store.d.ts → kit/turn-store.d.ts} +19 -3
- package/dist/channels/{turn-store.js → kit/turn-store.js} +30 -3
- package/dist/channels/lark/lark.d.ts +1 -1
- package/dist/channels/serve.d.ts +73 -0
- package/dist/channels/serve.js +243 -0
- package/dist/channels/slack/bot-auth.js +3 -14
- package/dist/channels/slack/context-buffer.d.ts +2 -2
- package/dist/channels/slack/context-buffer.js +2 -2
- package/dist/channels/slack/invoke-turn.d.ts +2 -2
- package/dist/channels/slack/invoke-turn.js +13 -6
- package/dist/channels/slack/onboard.js +5 -5
- package/dist/channels/slack/onboarding-state.d.ts +7 -3
- package/dist/channels/slack/onboarding-state.js +13 -20
- package/dist/channels/slack/parse.js +2 -2
- package/dist/channels/slack/preview.d.ts +1 -1
- package/dist/channels/slack/preview.js +11 -3
- package/dist/channels/slack/register-webhook.js +1 -1
- package/dist/channels/slack/scaffold/slack-send.ts +2 -17
- package/dist/channels/slack/slack-api.js +23 -55
- package/dist/channels/slack/slack.d.ts +1 -1
- package/dist/channels/slack/slack.js +20 -20
- package/dist/channels/slack/welcomed.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +2 -2
- package/dist/channels/telegram/context-buffer.js +2 -2
- package/dist/channels/telegram/invoke-turn.d.ts +1 -1
- package/dist/channels/telegram/invoke-turn.js +1 -1
- package/dist/channels/telegram/parse.d.ts +21 -1
- package/dist/channels/telegram/parse.js +67 -13
- package/dist/channels/telegram/preview.d.ts +1 -1
- package/dist/channels/telegram/preview.js +6 -2
- 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 +3 -3
- package/dist/channels/telegram/telegram.js +60 -72
- package/dist/channels/telegram/turn-store.d.ts +2 -2
- package/dist/channels/telegram/turn-store.js +2 -2
- package/dist/cli/add-slack.js +6 -5
- package/dist/cli/commands/add.js +1 -5
- package/dist/cli/commands/attach.d.ts +2 -4
- package/dist/cli/commands/attach.js +17 -15
- package/dist/cli/commands/deploy.d.ts +50 -5
- package/dist/cli/commands/deploy.js +183 -134
- package/dist/cli/commands/dev.js +25 -40
- package/dist/cli/commands/fire.js +1 -1
- package/dist/cli/commands/info.js +7 -5
- package/dist/cli/commands/init.js +1 -2
- package/dist/cli/commands/schedule.js +1 -1
- package/dist/cli/commands/start.js +80 -121
- package/dist/cli/commands/tool.js +5 -7
- package/dist/cli/program.js +1 -1
- package/dist/cli/serve.d.ts +22 -70
- package/dist/cli/serve.js +66 -257
- package/dist/cli/shared.d.ts +40 -10
- package/dist/cli/shared.js +53 -6
- package/dist/core.d.ts +3 -5
- package/dist/core.js +7 -5
- package/dist/deploy/agentcore/logs.d.ts +2 -2
- package/dist/deploy/agentcore/logs.js +2 -2
- package/dist/deploy/agentcore/plan.d.ts +12 -10
- package/dist/deploy/agentcore/plan.js +39 -31
- package/dist/deploy/agentcore/run.d.ts +5 -4
- package/dist/deploy/agentcore/run.js +13 -36
- 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 +18 -1
- package/dist/deploy/docker/run.js +10 -0
- package/dist/deploy/fly/plan.d.ts +4 -5
- package/dist/deploy/fly/plan.js +7 -22
- package/dist/deploy/fly/run.d.ts +9 -23
- package/dist/deploy/fly/run.js +32 -68
- package/dist/deploy/preflight.d.ts +6 -7
- package/dist/deploy/preflight.js +27 -23
- package/dist/deploy/railway/plan.d.ts +4 -5
- package/dist/deploy/railway/plan.js +11 -26
- package/dist/deploy/railway/run.d.ts +9 -9
- package/dist/deploy/railway/run.js +40 -40
- package/dist/deploy/runner.js +22 -2
- package/dist/deploy/secrets.d.ts +18 -9
- package/dist/deploy/secrets.js +64 -12
- package/dist/dev-supervisor.js +11 -6
- package/dist/engines/pi/agent-session-factory.d.ts +104 -0
- package/dist/engines/pi/agent-session-factory.js +295 -0
- package/dist/engines/pi/auth.js +43 -43
- package/dist/engines/pi/config.d.ts +5 -5
- package/dist/engines/pi/config.js +1 -1
- package/dist/engines/pi/create.d.ts +67 -42
- package/dist/engines/pi/create.js +142 -72
- package/dist/engines/pi/definition.d.ts +42 -8
- package/dist/engines/pi/definition.js +114 -11
- package/dist/engines/pi/invoke-session.d.ts +22 -19
- package/dist/engines/pi/invoke-session.js +286 -100
- package/dist/engines/pi/login.js +39 -20
- package/dist/engines/pi/models.d.ts +14 -0
- package/dist/engines/pi/models.js +9 -1
- package/dist/engines/pi/open.d.ts +10 -8
- package/dist/engines/pi/open.js +21 -11
- package/dist/engines/pi/report.d.ts +0 -4
- package/dist/engines/pi/report.js +0 -7
- package/dist/engines/pi/search-tools.js +1 -1
- package/dist/engines/pi/service.d.ts +23 -0
- package/dist/engines/pi/service.js +27 -0
- package/dist/engines/pi/session-builder.js +87 -118
- package/dist/engines/pi/session-control.d.ts +28 -14
- package/dist/engines/pi/session-control.js +570 -438
- package/dist/engines/pi/session-inheritance.d.ts +30 -0
- package/dist/engines/pi/session-inheritance.js +261 -0
- 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 +15 -3
- package/dist/engines/pi/session-settings.js +22 -1
- package/dist/engines/pi/session-store.d.ts +140 -0
- package/dist/engines/pi/session-store.js +666 -0
- package/dist/engines/pi/tool-context.d.ts +36 -13
- package/dist/engines/pi/tool-context.js +51 -4
- package/dist/engines/pi/tool.d.ts +5 -5
- package/dist/engines/pi/tool.js +3 -3
- package/dist/engines/pi/turn-kit.d.ts +32 -6
- package/dist/engines/pi/turn-kit.js +28 -1
- package/dist/engines/pi/wake-tool.js +1 -1
- package/dist/env.js +1 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/loader.d.ts +49 -7
- package/dist/loader.js +79 -18
- package/dist/log.d.ts +3 -0
- package/dist/log.js +5 -1
- package/dist/node.d.ts +16 -0
- package/dist/node.js +20 -0
- package/dist/paths.d.ts +26 -0
- package/dist/paths.js +37 -2
- package/dist/pi.d.ts +14 -8
- package/dist/pi.js +6 -5
- package/dist/scaffold/add-channel.js +2 -2
- package/dist/scaffold/init.js +7 -2
- package/dist/scaffold/templates/persona.md +2 -2
- package/dist/scaffold/templates/tools/fetch-url.ts +0 -2
- package/dist/schedule/discover.js +3 -15
- package/dist/schedule/state.js +4 -9
- package/dist/schedule/wake-alarm.d.ts +13 -7
- package/dist/schedule/wake-alarm.js +88 -46
- package/dist/schedule/wakeups.d.ts +2 -2
- package/dist/schedule/wakeups.js +11 -8
- package/dist/service.d.ts +157 -0
- package/dist/service.js +376 -0
- package/dist/session-remote.d.ts +8 -3
- package/dist/session-remote.js +162 -92
- package/dist/session.d.ts +229 -93
- package/dist/session.js +61 -23
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +1 -1
- package/dist/tunnel.d.ts +18 -3
- package/dist/tunnel.js +32 -40
- package/package.json +14 -5
- package/dist/engines/pi/channel.d.ts +0 -37
- package/dist/engines/pi/harness.d.ts +0 -115
- package/dist/engines/pi/harness.js +0 -154
- package/dist/engines/pi/invoke.d.ts +0 -60
- package/dist/engines/pi/invoke.js +0 -459
- package/dist/engines/pi/read-image.d.ts +0 -4
- package/dist/engines/pi/read-image.js +0 -62
- package/dist/engines/pi/sessions.d.ts +0 -60
- package/dist/engines/pi/sessions.js +0 -464
- package/dist/host/node.js +0 -52
- /package/dist/channels/{preview-kit.js → kit/preview-kit.js} +0 -0
- /package/dist/channels/{seen.d.ts → kit/seen.d.ts} +0 -0
- /package/dist/channels/{state.d.ts → kit/state.d.ts} +0 -0
- /package/dist/channels/{text.d.ts → kit/text.d.ts} +0 -0
- /package/dist/channels/{text.js → kit/text.js} +0 -0
- /package/dist/channels/{thread-participants.d.ts → kit/thread-participants.d.ts} +0 -0
- /package/dist/channels/{turn-queue.d.ts → kit/turn-queue.d.ts} +0 -0
package/dist/engines/pi/open.js
CHANGED
|
@@ -16,7 +16,7 @@ import { createPiSessionControl } from "./session-control.js";
|
|
|
16
16
|
import { withWakeTool } from "./wake-tool.js";
|
|
17
17
|
import { loadAgentSkills } from "./definition.js";
|
|
18
18
|
import { reportFindingsIfChanged } from "./report.js";
|
|
19
|
-
import {
|
|
19
|
+
import { piSessionRecordStore } from "./session-store.js";
|
|
20
20
|
export async function resolveAgentAssembly(dir, options = {}) {
|
|
21
21
|
// Placement is structural (resolvePlacement): the AGENT DIR carries definition + config + machinery;
|
|
22
22
|
// its parent — the WORKSPACE — is what the agent works on: its cwd and the start of the ②-context
|
|
@@ -27,7 +27,7 @@ export async function resolveAgentAssembly(dir, options = {}) {
|
|
|
27
27
|
if (!modelSpec) {
|
|
28
28
|
throw new Error(`missing model: set --model, "model" in fastagent.config.ts, or FASTAGENT_MODEL (e.g. "openai-codex/gpt-5.5")`);
|
|
29
29
|
}
|
|
30
|
-
const { tools, toolNames, deferredToolNames, toolCollisions, toolFailures } = await resolveAgentTools(config, agentDir);
|
|
30
|
+
const { tools, toolNames, deferredToolNames, toolCollisions, toolFailures } = await resolveAgentTools(config, agentDir, workspace);
|
|
31
31
|
// The state root: sessions/channel state/schedule state derive from it (FASTAGENT_STATE_DIR moves it
|
|
32
32
|
// in one knob — a container points it at its volume); the finer overrides below still win.
|
|
33
33
|
const stateRoot = resolveStateRoot(agentDir);
|
|
@@ -62,13 +62,14 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
62
62
|
const mountedTools = withWakeTool(tools, stateRoot, !!options.serving && !!config.selfSchedule);
|
|
63
63
|
const sessionsDir = options.sessionsDir ?? defaultSessionsDir(stateRoot);
|
|
64
64
|
await mkdir(sessionsDir, { recursive: true });
|
|
65
|
-
const sessions =
|
|
65
|
+
const sessions = piSessionRecordStore({ dir: sessionsDir, cwd: workspace });
|
|
66
66
|
// The hub is wired HERE because the store is created here: chicken-and-egg otherwise (the hub
|
|
67
|
-
// needs the store; the agent needs the hub's observer). Boundary parts (
|
|
67
|
+
// needs the store; the agent needs the hub's observer). Boundary parts (factory/lease/registry)
|
|
68
68
|
// only exist after the assembly below — the hub takes them as a lazy thunk, filled by the
|
|
69
69
|
// assembly's onAssembly callback (assembly completes before this function returns, so every
|
|
70
70
|
// dispatch sees them). An extra caller observer composes after the hub's (TRUSTED seam).
|
|
71
71
|
let boundaryParts;
|
|
72
|
+
let assembled;
|
|
72
73
|
const caller = options.observer;
|
|
73
74
|
const wantControl = options.sessionControl ?? (config.sessionControl === true && options.serving === true);
|
|
74
75
|
const hub = wantControl
|
|
@@ -94,7 +95,7 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
94
95
|
},
|
|
95
96
|
// The caller tap's boundary-event half: state_changed/compaction_* originate in the hub
|
|
96
97
|
// and never cross the data plane's observer seam — without this, an audit tap wired here
|
|
97
|
-
// would miss exactly the mutations it most needs to see (
|
|
98
|
+
// would miss exactly the mutations it most needs to see (`update({ model })`).
|
|
98
99
|
tap: caller ? (session, event) => caller(session, event) : undefined,
|
|
99
100
|
})
|
|
100
101
|
: undefined;
|
|
@@ -117,20 +118,29 @@ export async function createPiAgentFromDir(dir, options = {}) {
|
|
|
117
118
|
observer,
|
|
118
119
|
onAssembly: hub
|
|
119
120
|
? (parts) => {
|
|
120
|
-
|
|
121
|
-
lease: parts.lease,
|
|
122
|
-
models: parts.models,
|
|
123
|
-
harnessFactory: parts.harnessFactory,
|
|
124
|
-
defaults: parts.defaults,
|
|
125
|
-
};
|
|
121
|
+
assembled = parts;
|
|
126
122
|
}
|
|
127
123
|
: undefined,
|
|
128
124
|
});
|
|
125
|
+
if (hub && assembled) {
|
|
126
|
+
// The hub's surface is synchronous (`capabilities()` lists the allowed models), while building
|
|
127
|
+
// the registry reads credentials and is not. Resolve it ONCE here — the opener is async anyway,
|
|
128
|
+
// and the first turn would have paid the same read — so every boundary mutation validates
|
|
129
|
+
// against the same registry the runs use.
|
|
130
|
+
const { modelRuntime, model } = await assembled.engine();
|
|
131
|
+
boundaryParts = {
|
|
132
|
+
lease: assembled.lease,
|
|
133
|
+
models: modelRuntime,
|
|
134
|
+
sessionFactory: assembled.sessionFactory,
|
|
135
|
+
defaults: { model, thinkingLevel: assembled.thinkingLevel },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
129
138
|
return {
|
|
130
139
|
agent,
|
|
131
140
|
definition,
|
|
132
141
|
sessions,
|
|
133
142
|
sessionControl: hub?.control,
|
|
143
|
+
selfSchedule: config.selfSchedule ?? false,
|
|
134
144
|
agentDir,
|
|
135
145
|
workspace,
|
|
136
146
|
config,
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { SkillDiagnostic } from "@earendil-works/pi-agent-core";
|
|
6
6
|
import type { SkillCollision } from "./definition.ts";
|
|
7
|
-
import type { ModuleLoadFailure } from "../../loader.ts";
|
|
8
7
|
import type { ToolCollision } from "./tool.ts";
|
|
9
8
|
type Findings = {
|
|
10
9
|
collisions: SkillCollision[];
|
|
@@ -23,7 +22,4 @@ type Findings = {
|
|
|
23
22
|
export declare function reportFindingsIfChanged(dir: string, def: Findings): void;
|
|
24
23
|
export declare function reportDefinitionWarnings(collisions: SkillCollision[], diagnostics: SkillDiagnostic[]): void;
|
|
25
24
|
export declare function reportToolCollisions(collisions: ToolCollision[]): void;
|
|
26
|
-
/** Report per-file module failures. The caller decides whether they are degradations (tools/schedules)
|
|
27
|
-
* or fatal (declared channels on the serving path). */
|
|
28
|
-
export declare function reportModuleLoadFailures(failures: ModuleLoadFailure[]): void;
|
|
29
25
|
export {};
|
|
@@ -42,10 +42,3 @@ export function reportToolCollisions(collisions) {
|
|
|
42
42
|
log.warn(`[fastagent] tool "${c.name}" (${c.source}) dropped — a default/config tool already uses that name`);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
/** Report per-file module failures. The caller decides whether they are degradations (tools/schedules)
|
|
46
|
-
* or fatal (declared channels on the serving path). */
|
|
47
|
-
export function reportModuleLoadFailures(failures) {
|
|
48
|
-
for (const f of failures) {
|
|
49
|
-
log.warn(`[fastagent] ${f.label} failed to load, skipping it — ${f.message}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -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) {
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
*/
|
|
9
|
+
import { type AgentService, type MountAgentServiceOptions } from "../../service.ts";
|
|
10
|
+
export interface CreateAgentServiceOptions extends MountAgentServiceOptions {
|
|
11
|
+
model?: string;
|
|
12
|
+
authPath?: string;
|
|
13
|
+
sessionsDir?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Open an agent directory as a live service: one handler, mounted wherever you serve.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const service = await createAgentService("./my-agent");
|
|
20
|
+
* app.use("/agent", nodeListener(service.handler));
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function createAgentService(dir: string, options?: CreateAgentServiceOptions): Promise<AgentService>;
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
*/
|
|
9
|
+
import { mountAgentService } from "../../service.js";
|
|
10
|
+
import { createPiAgentFromDir } from "./open.js";
|
|
11
|
+
/**
|
|
12
|
+
* Open an agent directory as a live service: one handler, mounted wherever you serve.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* const service = await createAgentService("./my-agent");
|
|
16
|
+
* app.use("/agent", nodeListener(service.handler));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export async function createAgentService(dir, options = {}) {
|
|
20
|
+
const opened = await createPiAgentFromDir(dir, {
|
|
21
|
+
...(options.model !== undefined ? { model: options.model } : {}),
|
|
22
|
+
...(options.authPath !== undefined ? { authPath: options.authPath } : {}),
|
|
23
|
+
...(options.sessionsDir !== undefined ? { sessionsDir: options.sessionsDir } : {}),
|
|
24
|
+
serving: true, // a mounted service is long-running: the scheduler poller runs
|
|
25
|
+
});
|
|
26
|
+
return mountAgentService(opened, options);
|
|
27
|
+
}
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* - prompt → systemPromptOverride = base + instructions ONLY; pi appends the skill section and env
|
|
12
12
|
* (cwd) itself (including it here would duplicate it).
|
|
13
13
|
* - skills → skillsOverride (fastagent's skills, for the section + invocation).
|
|
14
|
-
* - tools →
|
|
15
|
-
*
|
|
14
|
+
* - tools → all seven coding tools plus authored tools through pi's customTools path; pi's builtin
|
|
15
|
+
* copies are suppressed, and the injected set survives /new, /resume, and fork.
|
|
16
16
|
* - models → a ModelRuntime with builtins only (`modelsPath: null`, no availability network), so
|
|
17
17
|
* the model surface equals serving's `createPiModels()` — pi's machine-global
|
|
18
18
|
* models.json does not leak in.
|
|
@@ -28,32 +28,18 @@
|
|
|
28
28
|
* workspace.
|
|
29
29
|
*/
|
|
30
30
|
import { existsSync, readFileSync } from "node:fs";
|
|
31
|
-
import {
|
|
31
|
+
import { join, resolve } from "node:path";
|
|
32
32
|
import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node";
|
|
33
33
|
import { SessionManager, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, } from "@earendil-works/pi-coding-agent";
|
|
34
|
+
import { definitionResourceLoaderOptions, reportExtensionErrors } from "./agent-session-factory.js";
|
|
34
35
|
import { resolveModel } from "./config.js";
|
|
35
|
-
import { assembleSystemPrompt, piBasePrompt
|
|
36
|
-
import { canonicalPath, loadAgentDefinition } from "./definition.js";
|
|
36
|
+
import { assembleSystemPrompt, piBasePrompt } from "./create.js";
|
|
37
|
+
import { canonicalPath, loadAgentDefinition, loadExtensionPaths } from "./definition.js";
|
|
37
38
|
import { createPiModelRuntime, probeAuthSource } from "./models.js";
|
|
38
|
-
import { log } from "../../log.js";
|
|
39
|
-
import {
|
|
40
|
-
import { reportFindingsIfChanged,
|
|
39
|
+
import { log, reportModuleLoadFailures } from "../../log.js";
|
|
40
|
+
import { agentSessionManager, sessionToolActivation, turnContext, } from "./tool-context.js";
|
|
41
|
+
import { reportFindingsIfChanged, reportToolCollisions } from "./report.js";
|
|
41
42
|
import { resolveAgentAssembly } from "./open.js";
|
|
42
|
-
/** Adapt coding-agent's resident SessionManager to FastAgent's shared tool-runtime manager port. */
|
|
43
|
-
function toolChatSessionManager(session) {
|
|
44
|
-
return {
|
|
45
|
-
getSessionId: () => session.sessionManager.getSessionId(),
|
|
46
|
-
async getHeader() {
|
|
47
|
-
const header = session.sessionManager.getHeader();
|
|
48
|
-
if (!header)
|
|
49
|
-
throw new Error("chat session has no metadata header");
|
|
50
|
-
return { id: header.id, timestamp: header.timestamp };
|
|
51
|
-
},
|
|
52
|
-
async getBranch() {
|
|
53
|
-
return session.sessionManager.getBranch();
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
43
|
/**
|
|
58
44
|
* Build pi's interactive runtime driven by fastagent's assembled agent (model, prompt, tools,
|
|
59
45
|
* skills, and auth resolved exactly as the serving opener does). Split from the TUI launcher so the
|
|
@@ -62,34 +48,6 @@ function toolChatSessionManager(session) {
|
|
|
62
48
|
export async function buildAgentSessionRuntime(dir, options = {},
|
|
63
49
|
/** Session backend. Defaults to pi's project-scoped store; tests inject SessionManager.inMemory(). */
|
|
64
50
|
sessionManager) {
|
|
65
|
-
/** The turn's {@link ToolActivation} over pi's AgentSession — the counterpart of invoke.ts's
|
|
66
|
-
* harness bridge, so the SAME builtin search_tools serves both paths. Additive; unknown names
|
|
67
|
-
* filtered (`setActiveToolsByName` is authoritative on the session and rebuilds its prompt — our
|
|
68
|
-
* static override keeps the prompt identical to serving). */
|
|
69
|
-
function sessionToolActivation(session) {
|
|
70
|
-
// Same serialization as invoke.ts's bridge (there per turn; here per session — interactive turns
|
|
71
|
-
// make per-session equivalent): the read-modify-write below is only race-free while nothing awaits
|
|
72
|
-
// between read and write, and pi's session setters happening to be synchronous today is not a
|
|
73
|
-
// contract worth betting parallel tool batches on. Built ONCE per session (createRuntime), so
|
|
74
|
-
// parallel calls actually share the chain.
|
|
75
|
-
let chain = Promise.resolve([]);
|
|
76
|
-
return {
|
|
77
|
-
active: () => session.getActiveToolNames(),
|
|
78
|
-
registered: () => session.getAllTools().map((t) => ({ name: t.name, description: t.description ?? "" })),
|
|
79
|
-
activate(names) {
|
|
80
|
-
const run = async () => {
|
|
81
|
-
const current = session.getActiveToolNames();
|
|
82
|
-
const added = additiveActivation(session.getAllTools().map((t) => t.name), current, names);
|
|
83
|
-
if (added.length > 0)
|
|
84
|
-
session.setActiveToolsByName([...current, ...added]);
|
|
85
|
-
return added;
|
|
86
|
-
};
|
|
87
|
-
const result = chain.then(run, run); // run after the predecessor settles, success or failure
|
|
88
|
-
chain = result.catch(() => []); // the caller sees a rejection on `result`; the chain stays usable
|
|
89
|
-
return result;
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
51
|
async function resolveAssembly(cwd) {
|
|
94
52
|
// The shared front half — the SAME placement/config/model-spec/tool/auth resolution the serving
|
|
95
53
|
// opener uses (open.ts); those inputs cannot drift between the two consumption shapes.
|
|
@@ -99,36 +57,27 @@ sessionManager) {
|
|
|
99
57
|
// (what you iterate is what you serve): the initial active set excludes deferred tools (applied
|
|
100
58
|
// on the session in createRuntime — pi's session starts all-active), and the activation bridge
|
|
101
59
|
// above rides the same turn context, so the SAME search_tools works against pi's AgentSession
|
|
102
|
-
// instead of
|
|
60
|
+
// instead of the served one.
|
|
103
61
|
const { config, modelSpec, agentDir, authPath, stateRoot, tools, deferredToolNames, toolCollisions, toolFailures } = await resolveAgentAssembly(cwd, options);
|
|
104
62
|
reportToolCollisions(toolCollisions);
|
|
105
63
|
reportModuleLoadFailures(toolFailures);
|
|
106
|
-
// ONE hub owns model resolution AND per-request auth
|
|
107
|
-
// serving's createPiModels; see models.ts.
|
|
64
|
+
// ONE hub owns model resolution AND per-request auth; see models.ts.
|
|
108
65
|
// agentDir carries the agent's own models.json (custom endpoints); stateRoot keeps pi's generated
|
|
109
66
|
// catalog cache out of the definition dir. See createPiModelRuntime.
|
|
110
67
|
const modelRuntime = await createPiModelRuntime({ authPath, agentDir, stateRoot });
|
|
111
|
-
// MIGRATION HINT (deliberate breaking change): chat historically used pi's own `~/.pi` auth;
|
|
112
|
-
// it now reads the agent's credential file like every other command. Probe the RESOLVED
|
|
113
|
-
// model's provider through the normal resolution path (stored credential OR env var — an
|
|
114
|
-
// env-authed user is fine and must not be warned): only when that provider has no usable auth
|
|
115
|
-
// AND pi's old file exists does the bare provider error get its cause named.
|
|
116
|
-
if ((await probeAuthSource(modelRuntime, modelSpec)) === undefined &&
|
|
117
|
-
existsSync(join(getAgentDir(), "auth.json"))) {
|
|
118
|
-
log.warn(`[fastagent] no credentials for ${modelSpec} in ${authPath} — this runtime no longer reads ` +
|
|
119
|
-
`pi's ~/.pi auth; run \`fastagent login\` (or /login in the TUI) to store credentials for this agent`);
|
|
120
|
-
}
|
|
121
|
-
const model = resolveModel(modelRuntime, modelSpec);
|
|
122
68
|
const env = new NodeExecutionEnv({ cwd });
|
|
123
69
|
const definition = await loadAgentDefinition(agentDir, { cwd, env });
|
|
124
70
|
reportFindingsIfChanged(definition.dir, definition);
|
|
125
|
-
|
|
126
|
-
|
|
71
|
+
// Assembly-time, like serving's: this whole function is memoized, so the scan and its warnings
|
|
72
|
+
// happen once per runtime rather than per session rebuild (/new, /resume, fork).
|
|
73
|
+
const extensionPaths = await loadExtensionPaths(agentDir, { cwd, env });
|
|
74
|
+
// fastagent mounts pi's complete coding set itself; `noTools: "builtin"` below keeps the runtime
|
|
75
|
+
// from adding duplicate copies.
|
|
127
76
|
// Adapt fastagent's AgentTool to pi's ToolDefinition (`parameters` is plain JSON-Schema; pi accepts
|
|
128
77
|
// it). Each execute runs inside the turn context with the CURRENT session's activation bridge — the
|
|
129
78
|
// assembly is memoized across /new//resume/fork rebuilds while the session changes, so the bridge
|
|
130
|
-
// resolves through sessionRef at call time, exactly like the serving path resolves its
|
|
131
|
-
const customToolDefs =
|
|
79
|
+
// resolves through sessionRef at call time, exactly like the serving path resolves its session.
|
|
80
|
+
const customToolDefs = tools.map((t) => ({
|
|
132
81
|
name: t.name,
|
|
133
82
|
label: t.name,
|
|
134
83
|
description: t.description ?? "",
|
|
@@ -144,10 +93,8 @@ sessionManager) {
|
|
|
144
93
|
if (!bound)
|
|
145
94
|
throw new Error("tool executed before its session was built (lifecycle invariant broken)");
|
|
146
95
|
return turnContext.run({ cwd, sessionManager: bound.sessionManager, tools: bound.activation },
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
// `turnContext` (AsyncLocalStorage). So this env exists to satisfy the shape, and it is the
|
|
150
|
-
// chat cwd's — the same root a default tool would have got, had one reached here.
|
|
96
|
+
// Lower-level MountedTools may consume the fifth-argument env. Directory coding tools are
|
|
97
|
+
// cwd-bound and ignore it; authored tools read FastAgent's turnContext instead.
|
|
151
98
|
() => t.execute(id, params, signal, undefined, { env }));
|
|
152
99
|
},
|
|
153
100
|
}));
|
|
@@ -158,13 +105,14 @@ sessionManager) {
|
|
|
158
105
|
contextFiles: definition.contextFiles,
|
|
159
106
|
});
|
|
160
107
|
return {
|
|
161
|
-
model,
|
|
162
108
|
modelRuntime,
|
|
109
|
+
modelSpec,
|
|
110
|
+
authPath,
|
|
163
111
|
// Serving honors config.thinkingLevel (config → L2); the resident session must too (fidelity).
|
|
164
112
|
thinkingLevel: config.thinkingLevel,
|
|
165
113
|
definition,
|
|
166
|
-
|
|
167
|
-
customTools,
|
|
114
|
+
extensionPaths,
|
|
115
|
+
customTools: tools,
|
|
168
116
|
customToolDefs,
|
|
169
117
|
deferredToolNames,
|
|
170
118
|
systemPrompt,
|
|
@@ -177,11 +125,13 @@ sessionManager) {
|
|
|
177
125
|
// would leak env or require mutating global env at runtime.
|
|
178
126
|
const rootCwd = canonicalPath(dir);
|
|
179
127
|
// The CURRENT pi session + its activation bridge, BOUND TOGETHER — rebuilt on /new//resume/fork
|
|
180
|
-
// while the memoized assembly (and its tool execute closures) stays. The bridge
|
|
181
|
-
// session's lifetime
|
|
128
|
+
// while the memoized assembly (and its tool execute closures) stays. The bridge shares the
|
|
129
|
+
// session's lifetime because a tool call has to see what the previous one activated. Note on
|
|
182
130
|
// parallel batches: pi wraps SDK customTools in its own before/after active-set diff, so an
|
|
183
131
|
// activating tool must carry `executionMode: "sequential"` (the builtin loader does) — pi then runs
|
|
184
132
|
// the whole batch serially and the outer diff sees correct snapshots.
|
|
133
|
+
// NO activation record here: pi's chat session has nowhere to put one (see sessionToolActivation's
|
|
134
|
+
// `onActivated`), which is the documented divergence from serving — a resumed chat re-discovers.
|
|
185
135
|
const sessionRef = {};
|
|
186
136
|
let assembly;
|
|
187
137
|
const assemblyFor = (cwd) => {
|
|
@@ -194,65 +144,84 @@ sessionManager) {
|
|
|
194
144
|
assembly ??= resolveAssembly(rootCwd);
|
|
195
145
|
return assembly;
|
|
196
146
|
};
|
|
147
|
+
// The credential hint belongs to the RUNTIME, not to each session it builds: model resolution
|
|
148
|
+
// moved into createRuntime (extensions must load first), and repeating this on every /new,
|
|
149
|
+
// /resume and fork would nag about a setting that did not change.
|
|
150
|
+
let credentialHintShown = false;
|
|
197
151
|
const createRuntime = async ({ cwd, sessionManager, sessionStartEvent }) => {
|
|
198
|
-
const {
|
|
152
|
+
const { modelRuntime, modelSpec, authPath, thinkingLevel, definition, extensionPaths, customToolDefs, deferredToolNames, systemPrompt, } = await assemblyFor(cwd);
|
|
153
|
+
// Per session, NOT memoized with the assembly: pi replaces the session on /new, /resume and
|
|
154
|
+
// fork, and its extension contract is that the replacement gets freshly loaded extensions
|
|
155
|
+
// rather than the previous session's objects. The expensive halves (model hub, auth) are shared
|
|
156
|
+
// through the assembly; only the resource loader is rebuilt.
|
|
199
157
|
const services = await createAgentSessionServices({
|
|
200
158
|
cwd,
|
|
201
159
|
// fastagent's models + auth hub replaces pi's default (~/.pi-backed) one — the auth
|
|
202
160
|
// unification point; see the header.
|
|
203
161
|
modelRuntime,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
appendSystemPromptOverride: () => [],
|
|
213
|
-
// Replace pi's discovered skills with fastagent's. fastagent's Skill (content inline) is
|
|
214
|
-
// reshaped to pi-coding-agent's (read from filePath/baseDir at invocation time).
|
|
215
|
-
skillsOverride: (base) => ({
|
|
216
|
-
skills: definition.skills.map((s) => ({
|
|
217
|
-
name: s.name,
|
|
218
|
-
description: s.description,
|
|
219
|
-
filePath: s.filePath,
|
|
220
|
-
baseDir: dirname(s.filePath),
|
|
221
|
-
sourceInfo: {
|
|
222
|
-
path: s.filePath,
|
|
223
|
-
source: "fastagent",
|
|
224
|
-
scope: "project",
|
|
225
|
-
origin: "top-level",
|
|
226
|
-
baseDir: dirname(s.filePath),
|
|
227
|
-
},
|
|
228
|
-
disableModelInvocation: s.disableModelInvocation ?? false,
|
|
229
|
-
})),
|
|
230
|
-
diagnostics: base.diagnostics,
|
|
231
|
-
}),
|
|
232
|
-
},
|
|
162
|
+
// Chat's assembly is fixed for the life of the runtime (a rebuild makes a new one), so these
|
|
163
|
+
// read constants — where serving passes accessors that change per turn — and it DOES pass its
|
|
164
|
+
// extension paths: chat runs them.
|
|
165
|
+
resourceLoaderOptions: definitionResourceLoaderOptions({
|
|
166
|
+
systemPrompt: () => systemPrompt,
|
|
167
|
+
skills: () => definition.skills,
|
|
168
|
+
extensionPaths,
|
|
169
|
+
}),
|
|
233
170
|
});
|
|
171
|
+
reportExtensionErrors(services);
|
|
172
|
+
// AFTER the services, because an extension may be what defines the model. `registerProvider()`
|
|
173
|
+
// is pi's documented way for one to add providers, and extensions do not execute until the
|
|
174
|
+
// services are built — resolving first fails a definition whose configured model comes from its
|
|
175
|
+
// own extension with a bare "unknown model", after warning about credentials for a provider
|
|
176
|
+
// that does not exist yet.
|
|
177
|
+
//
|
|
178
|
+
// MIGRATION HINT (deliberate breaking change): chat historically used pi's own `~/.pi` auth; it
|
|
179
|
+
// now reads the agent's credential file like every other command. Probe the RESOLVED model's
|
|
180
|
+
// provider through the normal resolution path (stored credential OR env var — an env-authed
|
|
181
|
+
// user is fine and must not be warned): only when that provider has no usable auth AND pi's old
|
|
182
|
+
// file exists does the bare provider error get its cause named.
|
|
183
|
+
if (!credentialHintShown &&
|
|
184
|
+
(await probeAuthSource(modelRuntime, modelSpec)) === undefined &&
|
|
185
|
+
existsSync(join(getAgentDir(), "auth.json"))) {
|
|
186
|
+
credentialHintShown = true;
|
|
187
|
+
log.warn(`[fastagent] no credentials for ${modelSpec} in ${authPath} — this runtime no longer reads ` +
|
|
188
|
+
`pi's ~/.pi auth; run \`fastagent login\` (or /login in the TUI) to store credentials for this agent`);
|
|
189
|
+
}
|
|
190
|
+
const model = resolveModel(modelRuntime, modelSpec);
|
|
234
191
|
const result = await createAgentSessionFromServices({
|
|
235
192
|
services,
|
|
236
193
|
sessionManager,
|
|
237
194
|
sessionStartEvent,
|
|
238
195
|
model,
|
|
239
196
|
thinkingLevel,
|
|
240
|
-
tools
|
|
197
|
+
// NO `tools` allowlist. It would freeze the tool set at build time, and pi lets an extension
|
|
198
|
+
// register from `session_start`, a command, or any handler — those names would not be in a
|
|
199
|
+
// startup snapshot, so `refreshTools()` would filter them straight back out and the extension
|
|
200
|
+
// would look like it did nothing. `noTools: "builtin"` gets the same guarantee the allowlist
|
|
201
|
+
// was really there for (the machine's `defaultTools` setting cannot add pi's own copies on top
|
|
202
|
+
// of ours) without freezing anything.
|
|
203
|
+
noTools: "builtin",
|
|
241
204
|
customTools: customToolDefs,
|
|
242
205
|
});
|
|
243
206
|
sessionRef.current = {
|
|
244
207
|
session: result.session,
|
|
245
|
-
sessionManager:
|
|
208
|
+
sessionManager: agentSessionManager(result.session, result.session.sessionManager.getSessionId()),
|
|
246
209
|
activation: sessionToolActivation(result.session),
|
|
247
210
|
};
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
211
|
+
// NOT bound here: the HOST does it. InteractiveMode.bindCurrentSessionExtensions() calls
|
|
212
|
+
// session.bindExtensions() with the TUI's uiContext, abort handler and command actions — binding
|
|
213
|
+
// here too would emit session_start twice per chat, so an extension opening a resource on start
|
|
214
|
+
// would open two.
|
|
215
|
+
// Deferral emulation: pi starts THIS agent's tools active — every coding tool it mounted, plus
|
|
216
|
+
// every custom and extension tool. So narrow by SUBTRACTING the deferred names from whatever is
|
|
217
|
+
// active, rather than stating a set: an exact-set-equality gate would silently stop narrowing the
|
|
218
|
+
// day pi activates one more.
|
|
219
|
+
//
|
|
220
|
+
// Applied on EVERY build including /resume: pi's chat session does not record activations (its
|
|
221
|
+
// SessionContext has no activeToolNames), so "restore prior activations" is not implementable
|
|
222
|
+
// here — deferral stays consistently ON and a resumed conversation re-discovers via search_tools
|
|
223
|
+
// (a documented divergence from serving, where activations persist in the session). The deferred
|
|
224
|
+
// SET comes from the shared assembly (one definition of "deferred"), never recomputed here.
|
|
256
225
|
if (deferredToolNames.length > 0) {
|
|
257
226
|
const active = result.session.getActiveToolNames();
|
|
258
227
|
if (deferredToolNames.some((n) => active.includes(n))) {
|
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
+
*/
|
|
15
|
+
import { type ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
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 "./
|
|
5
|
-
import type {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
18
|
+
import type { SessionObserver, Lease } from "./turn-kit.ts";
|
|
19
|
+
import type { AnyModel } from "./models.ts";
|
|
20
|
+
import type { PiAgentSessionFactory } from "./invoke-session.ts";
|
|
21
|
+
import type { PiSessionRecordStore } from "./session-store.ts";
|
|
8
22
|
/** Ceiling for one subscriber's unconsumed backlog. A consumer this far behind (a stalled remote
|
|
9
23
|
* connection — the wire's ReadableStream backpressure stops pulling while invokes keep pushing)
|
|
10
24
|
* has its buffer FROZEN at the cap (memory bounded — the actual goal: ≈10k small events ≈ a few
|
|
@@ -14,27 +28,27 @@ import { type PiSessionReader } from "./sessions.ts";
|
|
|
14
28
|
* permanently stalled one holds the frozen buffer until its TCP connection dies. Recovery either
|
|
15
29
|
* way is the standard reconnect+backfill, semantically lossless. */
|
|
16
30
|
export declare const SUBSCRIBER_BUFFER_CAP = 10000;
|
|
17
|
-
/** What
|
|
18
|
-
* agent assembly uses: the lease (
|
|
19
|
-
* allowedModels), and the
|
|
20
|
-
*
|
|
31
|
+
/** What the plane's writes (`update` / `compact` / `fork` / `delete`) need — the SAME instances the
|
|
32
|
+
* agent assembly uses: the lease (a write must not race a run), the model registry (validation +
|
|
33
|
+
* allowedModels), and the session factory (compaction is a model call). Writes go through the
|
|
34
|
+
* record the hub's reader opened — after an existence check, so the control plane never creates
|
|
21
35
|
* a session (that is the data plane's monopoly). */
|
|
22
36
|
export interface PiBoundaryWiring {
|
|
23
37
|
lease: Lease;
|
|
24
38
|
models: Models;
|
|
25
|
-
|
|
39
|
+
sessionFactory: PiAgentSessionFactory;
|
|
26
40
|
/** The assembly's configured PAIR — what a session with no overrides runs on. One field because
|
|
27
41
|
* model and thinking level are one setting: which levels exist is a property of the model, so a
|
|
28
42
|
* wiring that could carry them apart could carry a pair no run uses. Must be what
|
|
29
|
-
* {@link
|
|
43
|
+
* {@link sessionFactory} was built with. */
|
|
30
44
|
defaults: {
|
|
31
45
|
model: AnyModel;
|
|
32
46
|
thinkingLevel: ThinkingLevel;
|
|
33
47
|
};
|
|
34
48
|
}
|
|
35
49
|
export interface CreatePiSessionControlOptions {
|
|
36
|
-
/** Read-only access to the durable session
|
|
37
|
-
sessions:
|
|
50
|
+
/** Read-only access to the durable session records (the same root the agent writes). */
|
|
51
|
+
sessions: PiSessionRecordStore;
|
|
38
52
|
/** Boundary-mutation wiring, as a LAZY thunk: the hub's observer must exist before the agent
|
|
39
53
|
* assembly that produces these parts, so the hub asks for them at dispatch time instead
|
|
40
54
|
* (assembly completes before any dispatch can arrive). Absent / undefined → boundary commands
|