@fastagent-sh/fastagent 0.17.1 → 0.19.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 +12 -1
- package/dist/agent.js +1 -1
- package/dist/atomic-write.d.ts +19 -0
- package/dist/atomic-write.js +48 -0
- package/dist/{host/node.d.ts → channel.d.ts} +24 -28
- package/dist/channel.js +1 -0
- package/dist/channels/agentcore-service.d.ts +54 -0
- package/dist/channels/agentcore-service.js +111 -0
- package/dist/channels/agentcore.d.ts +12 -2
- package/dist/channels/agentcore.js +6 -4
- package/dist/channels/busy.d.ts +1 -1
- package/dist/channels/busy.js +1 -1
- package/dist/channels/control.d.ts +16 -6
- package/dist/channels/control.js +106 -4
- package/dist/{engines/pi/channel.d.ts → channels/discover.d.ts} +2 -2
- package/dist/{engines/pi/channel.js → channels/discover.js} +8 -12
- 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.d.ts +4 -2
- package/dist/channels/feishu/feishu-api.js +1 -1
- package/dist/channels/feishu/feishu.d.ts +1 -1
- package/dist/channels/feishu/feishu.js +45 -15
- package/dist/channels/feishu/invoke-turn.d.ts +9 -3
- package/dist/channels/feishu/invoke-turn.js +152 -34
- package/dist/channels/feishu/parse.js +6 -0
- 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 +16 -111
- package/dist/channels/{context-buffer.js → kit/context-buffer.js} +2 -2
- package/dist/channels/{invoke-turn-kit.d.ts → kit/invoke-turn-kit.d.ts} +18 -4
- package/dist/channels/{invoke-turn-kit.js → kit/invoke-turn-kit.js} +23 -8
- 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/state.d.ts +6 -0
- package/dist/channels/{state.js → kit/state.js} +4 -24
- package/dist/channels/{stop-command.d.ts → kit/stop-command.d.ts} +1 -1
- package/dist/channels/{stop-command.js → kit/stop-command.js} +2 -2
- package/dist/channels/{tasks.js → kit/tasks.js} +1 -1
- package/dist/channels/{thread-participants.d.ts → kit/thread-participants.d.ts} +7 -0
- package/dist/channels/{thread-participants.js → kit/thread-participants.js} +4 -1
- package/dist/channels/{turn-queue.js → kit/turn-queue.js} +2 -2
- package/dist/channels/{turn-store.js → kit/turn-store.js} +1 -1
- 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 +1 -1
- package/dist/channels/slack/invoke-turn.js +9 -6
- 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/slack-api.js +1 -1
- package/dist/channels/slack/slack.d.ts +1 -1
- package/dist/channels/slack/slack.js +8 -12
- 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 +2 -2
- package/dist/channels/telegram/parse.js +2 -2
- package/dist/channels/telegram/preview.d.ts +1 -1
- package/dist/channels/telegram/preview.js +6 -2
- package/dist/channels/telegram/telegram.d.ts +1 -1
- package/dist/channels/telegram/telegram.js +3 -3
- package/dist/channels/telegram/turn-store.d.ts +2 -2
- package/dist/channels/telegram/turn-store.js +2 -2
- package/dist/cli/commands/add.js +1 -1
- package/dist/cli/commands/deploy.js +13 -5
- package/dist/cli/commands/dev.js +26 -14
- package/dist/cli/commands/fire.js +2 -2
- package/dist/cli/commands/info.js +28 -5
- package/dist/cli/commands/invoke.js +1 -1
- package/dist/cli/commands/schedule.js +1 -1
- package/dist/cli/commands/start.js +64 -95
- package/dist/cli/commands/tool.js +5 -7
- package/dist/cli/program.js +1 -1
- package/dist/cli/serve.d.ts +18 -57
- package/dist/cli/serve.js +57 -243
- package/dist/cli/shared.d.ts +5 -3
- package/dist/cli/shared.js +14 -7
- package/dist/collect.d.ts +30 -4
- package/dist/collect.js +39 -6
- package/dist/core.d.ts +3 -5
- package/dist/core.js +7 -5
- package/dist/deploy/preflight.d.ts +8 -2
- package/dist/deploy/preflight.js +22 -4
- package/dist/deploy/secrets.d.ts +3 -0
- package/dist/deploy/secrets.js +6 -0
- package/dist/dev-supervisor.js +16 -4
- package/dist/engines/pi/agent-session-factory.d.ts +104 -0
- package/dist/engines/pi/agent-session-factory.js +314 -0
- package/dist/engines/pi/config.d.ts +4 -4
- package/dist/engines/pi/config.js +1 -1
- package/dist/engines/pi/create.d.ts +56 -42
- package/dist/engines/pi/create.js +150 -75
- package/dist/engines/pi/definition.d.ts +41 -7
- package/dist/engines/pi/definition.js +115 -11
- package/dist/engines/pi/invoke-session.d.ts +35 -0
- package/dist/engines/pi/invoke-session.js +357 -0
- package/dist/engines/pi/login.js +7 -4
- package/dist/engines/pi/models.d.ts +59 -11
- package/dist/engines/pi/models.js +64 -9
- package/dist/engines/pi/open.d.ts +9 -6
- package/dist/engines/pi/open.js +20 -10
- package/dist/engines/pi/report.d.ts +0 -4
- package/dist/engines/pi/report.js +0 -7
- package/dist/engines/pi/service.d.ts +23 -0
- package/dist/engines/pi/service.js +19 -0
- package/dist/engines/pi/session-builder.js +88 -90
- package/dist/engines/pi/session-control.d.ts +11 -9
- package/dist/engines/pi/session-control.js +114 -77
- package/dist/engines/pi/session-inheritance.d.ts +44 -0
- package/dist/engines/pi/session-inheritance.js +242 -0
- package/dist/engines/pi/session-settings.d.ts +14 -2
- package/dist/engines/pi/session-settings.js +18 -0
- package/dist/engines/pi/session-store.d.ts +70 -0
- package/dist/engines/pi/session-store.js +313 -0
- package/dist/engines/pi/tool-context.d.ts +18 -8
- package/dist/engines/pi/tool-context.js +23 -1
- package/dist/engines/pi/tool.d.ts +5 -5
- package/dist/engines/pi/tool.js +1 -1
- package/dist/engines/pi/{invoke.d.ts → turn-kit.d.ts} +46 -45
- package/dist/engines/pi/turn-kit.js +188 -0
- package/dist/engines/pi/wake-tool.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- 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 +13 -0
- package/dist/paths.js +16 -0
- package/dist/pi.d.ts +15 -8
- package/dist/pi.js +7 -6
- package/dist/scaffold/templates/fastagent.config.mjs +2 -0
- package/dist/scaffold/templates/persona.md +2 -2
- package/dist/schedule/state.js +4 -9
- package/dist/schedule/wakeups.d.ts +1 -1
- package/dist/schedule/wakeups.js +1 -1
- package/dist/service.d.ts +158 -0
- package/dist/service.js +354 -0
- package/dist/session-remote.d.ts +1 -1
- package/dist/session-remote.js +10 -2
- package/dist/session.d.ts +4 -2
- package/dist/session.js +1 -1
- package/package.json +14 -5
- package/dist/channels/state.d.ts +0 -16
- package/dist/engines/pi/harness.d.ts +0 -112
- package/dist/engines/pi/harness.js +0 -153
- package/dist/engines/pi/invoke.js +0 -618
- 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 -44
- package/dist/engines/pi/sessions.js +0 -182
- package/dist/host/node.js +0 -52
- /package/dist/channels/{context-buffer.d.ts → kit/context-buffer.d.ts} +0 -0
- /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/{tasks.d.ts → kit/tasks.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/{turn-queue.d.ts → kit/turn-queue.d.ts} +0 -0
- /package/dist/channels/{turn-store.d.ts → kit/turn-store.d.ts} +0 -0
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The pi `Models` collection — the single hub that owns BOTH model resolution (provider/modelId
|
|
3
3
|
* lookup) AND auth (per-request credential resolution). fastagent builds one per opener and threads
|
|
4
|
-
* it into the
|
|
4
|
+
* it into the engine alongside the selected `model`; the two must come from the same collection so
|
|
5
5
|
* the model's provider auth is in scope.
|
|
6
6
|
*/
|
|
7
|
+
import { join } from "node:path";
|
|
7
8
|
import { defaultProviderAuthContext } from "@earendil-works/pi-ai";
|
|
8
9
|
import { builtinModels } from "@earendil-works/pi-ai/providers/all";
|
|
9
10
|
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
10
11
|
import { fastagentCredentialStore } from "./auth.js";
|
|
12
|
+
import { providerOf } from "./config.js";
|
|
11
13
|
import { interactiveLoginKind } from "./login.js";
|
|
14
|
+
import { AGENT_MODELS_FILE, resolveStateRoot } from "../../paths.js";
|
|
12
15
|
/**
|
|
13
16
|
* A `Models` with every built-in pi provider, wired to fastagent's auth: stored credentials from the
|
|
14
17
|
* {@link CreatePiModelsOptions.authPath} file (via {@link fastagentCredentialStore}; the global
|
|
@@ -25,21 +28,73 @@ export function createPiModels(options = {}) {
|
|
|
25
28
|
models.setProvider(provider);
|
|
26
29
|
return models;
|
|
27
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The serving default for reasoning effort, pinned to what pi's TUI defaults to (its own
|
|
33
|
+
* DEFAULT_THINKING_LEVEL) — NOT inherited from the engine, whose fallback is "off": an author vibes
|
|
34
|
+
* at "medium" in pi and must get "medium" when served (fidelity), and pinning the value here means
|
|
35
|
+
* an upstream default change in either place cannot silently alter deployments. Models that do not
|
|
36
|
+
* support a level are clamped by pi per model.
|
|
37
|
+
*/
|
|
38
|
+
export const DEFAULT_THINKING_LEVEL = "medium";
|
|
28
39
|
/**
|
|
29
40
|
* The `ModelRuntime`-shaped sibling of {@link createPiModels} — the SAME hub semantics (built-in
|
|
30
41
|
* providers + fastagent's credential store at `authPath`) in the type pi's session services require
|
|
31
|
-
* (`createAgentSessionServices({ modelRuntime })`).
|
|
32
|
-
*
|
|
33
|
-
* surface equals serving's.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
42
|
+
* (`createAgentSessionServices({ modelRuntime })`). Built-ins PLUS the agent's own
|
|
43
|
+
* {@link AGENT_MODELS_FILE} when `agentDir` is given (a dir-less caller gets built-ins only), and no
|
|
44
|
+
* availability network, so the model surface equals serving's.
|
|
45
|
+
*
|
|
46
|
+
* `ModelRuntime` also takes `Provider` INSTANCES via `registerNativeProvider` (pi 0.83); the
|
|
47
|
+
* declarative file is what this rung wires because it is data that travels with the definition.
|
|
36
48
|
*/
|
|
37
|
-
export function createPiModelRuntime(options = {}) {
|
|
38
|
-
|
|
49
|
+
export async function createPiModelRuntime(options = {}) {
|
|
50
|
+
const { agentDir } = options;
|
|
51
|
+
const runtime = await ModelRuntime.create({
|
|
39
52
|
credentials: fastagentCredentialStore(options.authPath, { warn: options.warn }),
|
|
40
|
-
modelsPath: null,
|
|
53
|
+
modelsPath: agentDir ? join(agentDir, AGENT_MODELS_FILE) : null,
|
|
54
|
+
// MUST be set whenever modelsPath is: pi defaults this to `<dirname(modelsPath)>/models-store.json`,
|
|
55
|
+
// which would write a generated cache INTO the author's agent dir — and `deploy` bakes the whole
|
|
56
|
+
// tree, so it would travel into the image as stale state. Machinery belongs under the state root.
|
|
57
|
+
...(agentDir
|
|
58
|
+
? { modelsStorePath: join(options.stateRoot ?? resolveStateRoot(agentDir), "models-store.json") }
|
|
59
|
+
: {}),
|
|
41
60
|
allowModelNetwork: false,
|
|
42
61
|
});
|
|
62
|
+
// A malformed models.json does NOT throw upstream — `create` resolves with the built-ins and parks the
|
|
63
|
+
// reason in getError(). Left unread, a typo'd endpoint would silently degrade to "provider not in
|
|
64
|
+
// registry" at model-resolution time, i.e. the silent fallback this codebase forbids. The upstream
|
|
65
|
+
// message already names both the reason and the file, so it is surfaced verbatim.
|
|
66
|
+
const error = runtime.getError();
|
|
67
|
+
if (error)
|
|
68
|
+
throw new Error(error);
|
|
69
|
+
for (const provider of options.providers ?? [])
|
|
70
|
+
runtime.registerNativeProvider(provider);
|
|
71
|
+
return runtime;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* How a model's credential will REACH a deployed agent — the question `deploy` asks, which
|
|
75
|
+
* {@link probeAuthSource} cannot answer: it flattens every models.json endpoint to the display label
|
|
76
|
+
* "configured API key", so a self-hosted endpoint looks credential-less to the deploy gate even when
|
|
77
|
+
* its key is sitting in an env var.
|
|
78
|
+
*
|
|
79
|
+
* - `envVar`: an environment variable backs it, BY NAME — the shape `deploy` already understands, so
|
|
80
|
+
* the value carries as a host secret with no extra declaration from the author.
|
|
81
|
+
* - `inDefinition`: the definition itself carries it (a literal `apiKey`, or a `!command` run on the
|
|
82
|
+
* host). Nothing for `deploy` to carry — and nothing to gate on either, which is the point: the
|
|
83
|
+
* `fastagent login` remedy is meaningless for a provider login cannot serve.
|
|
84
|
+
*
|
|
85
|
+
* Neither set = a stored credential or nothing at all; the existing auth.json / gate paths decide.
|
|
86
|
+
*/
|
|
87
|
+
export function modelCredentialCarry(runtime, spec) {
|
|
88
|
+
const status = runtime.getProviderAuthStatus(providerOf(spec));
|
|
89
|
+
if (!status.configured)
|
|
90
|
+
return { inDefinition: false };
|
|
91
|
+
// An env-var name is only useful downstream if it IS one: `"${A}_${B}"` interpolation resolves from
|
|
92
|
+
// the environment but has no single name to carry, so it falls through to the definition-carried
|
|
93
|
+
// branch, where the author's `deploy.secrets` is the mechanism.
|
|
94
|
+
if (status.source === "environment" && status.label && /^[A-Z][A-Z0-9_]*$/.test(status.label)) {
|
|
95
|
+
return { envVar: status.label, inDefinition: false };
|
|
96
|
+
}
|
|
97
|
+
return { inDefinition: status.source !== "stored" };
|
|
43
98
|
}
|
|
44
99
|
/**
|
|
45
100
|
* Probe every provider's auth once (auth is provider-scoped, so any of its models works as the probe)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Agent } from "../../agent.ts";
|
|
2
2
|
import { type FastagentConfig } from "./config.ts";
|
|
3
3
|
import type { SessionControl } from "../../session.ts";
|
|
4
|
-
import type { SessionObserver } from "./
|
|
5
|
-
import type { PiSessionReader, PiSessionStore } from "./sessions.ts";
|
|
4
|
+
import type { SessionObserver } from "./turn-kit.ts";
|
|
6
5
|
import type { ModuleLoadFailure } from "../../loader.ts";
|
|
7
6
|
import { type LoadedDefinition } from "./definition.ts";
|
|
7
|
+
import { type PiSessionRecordStore } from "./session-store.ts";
|
|
8
8
|
import type { ToolCollision } from "./tool.ts";
|
|
9
9
|
import type { MountedTool } from "./tool.ts";
|
|
10
10
|
export interface CreatePiAgentFromDirOptions {
|
|
@@ -44,7 +44,7 @@ export interface CreatePiAgentFromDirOptions {
|
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* The agent assembly FRONT HALF — everything that is independent of how pi consumes the
|
|
47
|
-
* definition (
|
|
47
|
+
* definition (a per-invoke session for serving vs a resident one for chat):
|
|
48
48
|
* placement resolution → config → model spec → the full tool surface ({@link resolveAgentTools} — the
|
|
49
49
|
* ONE place it is computed) → state root → auth path. Both {@link createPiAgentFromDir} and the
|
|
50
50
|
* session builder (session-builder.ts) consume this, so THESE inputs cannot drift between the two
|
|
@@ -65,7 +65,7 @@ export interface AgentAssembly {
|
|
|
65
65
|
stateRoot: string;
|
|
66
66
|
/** Absolute credentials file (--auth-path/authPath option > FASTAGENT_AUTH_PATH > <agentDir>/.secrets/auth.json). */
|
|
67
67
|
authPath: string;
|
|
68
|
-
/** The full mounted tool surface (config.tools + discovered tools/, search_tools applied). */
|
|
68
|
+
/** The full mounted tool surface (all coding tools + config.tools + discovered tools/, search_tools applied). */
|
|
69
69
|
tools: MountedTool[];
|
|
70
70
|
toolNames: string[];
|
|
71
71
|
deferredToolNames: string[];
|
|
@@ -99,10 +99,13 @@ export declare function createPiAgentFromDir(dir: string, options?: CreatePiAgen
|
|
|
99
99
|
sessionsDir: string;
|
|
100
100
|
/** Absolute credentials file in use (for the startup report). */
|
|
101
101
|
authPath: string;
|
|
102
|
-
/** The session store in use
|
|
103
|
-
sessions:
|
|
102
|
+
/** The session store in use. */
|
|
103
|
+
sessions: PiSessionRecordStore;
|
|
104
104
|
/** The observation plane over this agent's sessions; present iff `options.sessionControl`. */
|
|
105
105
|
sessionControl?: SessionControl;
|
|
106
|
+
/** Whether the agent schedules its own follow-up turns — read from the config, so a caller
|
|
107
|
+
* assembling a service does not have to reach back into it (MountableAgent). */
|
|
108
|
+
selfSchedule: boolean;
|
|
106
109
|
/** Non-default, active-by-default tool names in effect: config.tools + discovered tools/. Each name
|
|
107
110
|
* lives in exactly one report slot — deferred names are in {@link deferredToolNames} instead. */
|
|
108
111
|
toolNames: string[];
|
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
|
|
@@ -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
|
-
}
|
|
@@ -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, 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,19 @@
|
|
|
1
|
+
import { mountAgentService } from "../../service.js";
|
|
2
|
+
import { createPiAgentFromDir } from "./open.js";
|
|
3
|
+
/**
|
|
4
|
+
* Open an agent directory as a live service: one handler, mounted wherever you serve.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const service = await createAgentService("./my-agent");
|
|
8
|
+
* app.use("/agent", nodeListener(service.handler));
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export async function createAgentService(dir, options = {}) {
|
|
12
|
+
const opened = await createPiAgentFromDir(dir, {
|
|
13
|
+
...(options.model !== undefined ? { model: options.model } : {}),
|
|
14
|
+
...(options.authPath !== undefined ? { authPath: options.authPath } : {}),
|
|
15
|
+
...(options.sessionsDir !== undefined ? { sessionsDir: options.sessionsDir } : {}),
|
|
16
|
+
serving: true, // a mounted service is long-running: the scheduler poller runs
|
|
17
|
+
});
|
|
18
|
+
return mountAgentService(opened, options);
|
|
19
|
+
}
|
|
@@ -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,19 @@
|
|
|
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
39
|
import { log } from "../../log.js";
|
|
39
|
-
import { additiveActivation, turnContext } from "./tool-context.js";
|
|
40
|
-
import { reportFindingsIfChanged,
|
|
40
|
+
import { additiveActivation, agentSessionManager, turnContext, } from "./tool-context.js";
|
|
41
|
+
import { reportFindingsIfChanged, reportToolCollisions } from "./report.js";
|
|
42
|
+
import { reportModuleLoadFailures } from "../../log.js";
|
|
41
43
|
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
44
|
/**
|
|
58
45
|
* Build pi's interactive runtime driven by fastagent's assembled agent (model, prompt, tools,
|
|
59
46
|
* skills, and auth resolved exactly as the serving opener does). Split from the TUI launcher so the
|
|
@@ -63,7 +50,7 @@ export async function buildAgentSessionRuntime(dir, options = {},
|
|
|
63
50
|
/** Session backend. Defaults to pi's project-scoped store; tests inject SessionManager.inMemory(). */
|
|
64
51
|
sessionManager) {
|
|
65
52
|
/** The turn's {@link ToolActivation} over pi's AgentSession — the counterpart of invoke.ts's
|
|
66
|
-
*
|
|
53
|
+
* serving bridge, so the SAME builtin search_tools serves both paths. Additive; unknown names
|
|
67
54
|
* filtered (`setActiveToolsByName` is authoritative on the session and rebuilds its prompt — our
|
|
68
55
|
* static override keeps the prompt identical to serving). */
|
|
69
56
|
function sessionToolActivation(session) {
|
|
@@ -99,34 +86,27 @@ sessionManager) {
|
|
|
99
86
|
// (what you iterate is what you serve): the initial active set excludes deferred tools (applied
|
|
100
87
|
// on the session in createRuntime — pi's session starts all-active), and the activation bridge
|
|
101
88
|
// above rides the same turn context, so the SAME search_tools works against pi's AgentSession
|
|
102
|
-
// instead of
|
|
103
|
-
const { config, modelSpec, agentDir, authPath, tools, deferredToolNames, toolCollisions, toolFailures } = await resolveAgentAssembly(cwd, options);
|
|
89
|
+
// instead of the served one.
|
|
90
|
+
const { config, modelSpec, agentDir, authPath, stateRoot, tools, deferredToolNames, toolCollisions, toolFailures } = await resolveAgentAssembly(cwd, options);
|
|
104
91
|
reportToolCollisions(toolCollisions);
|
|
105
92
|
reportModuleLoadFailures(toolFailures);
|
|
106
|
-
// ONE hub owns model resolution AND per-request auth
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// it now reads the agent's credential file like every other command. Probe the RESOLVED
|
|
111
|
-
// model's provider through the normal resolution path (stored credential OR env var — an
|
|
112
|
-
// env-authed user is fine and must not be warned): only when that provider has no usable auth
|
|
113
|
-
// AND pi's old file exists does the bare provider error get its cause named.
|
|
114
|
-
if ((await probeAuthSource(modelRuntime, modelSpec)) === undefined &&
|
|
115
|
-
existsSync(join(getAgentDir(), "auth.json"))) {
|
|
116
|
-
log.warn(`[fastagent] no credentials for ${modelSpec} in ${authPath} — this runtime no longer reads ` +
|
|
117
|
-
`pi's ~/.pi auth; run \`fastagent login\` (or /login in the TUI) to store credentials for this agent`);
|
|
118
|
-
}
|
|
119
|
-
const model = resolveModel(modelRuntime, modelSpec);
|
|
93
|
+
// ONE hub owns model resolution AND per-request auth; see models.ts.
|
|
94
|
+
// agentDir carries the agent's own models.json (custom endpoints); stateRoot keeps pi's generated
|
|
95
|
+
// catalog cache out of the definition dir. See createPiModelRuntime.
|
|
96
|
+
const modelRuntime = await createPiModelRuntime({ authPath, agentDir, stateRoot });
|
|
120
97
|
const env = new NodeExecutionEnv({ cwd });
|
|
121
98
|
const definition = await loadAgentDefinition(agentDir, { cwd, env });
|
|
122
99
|
reportFindingsIfChanged(definition.dir, definition);
|
|
123
|
-
|
|
124
|
-
|
|
100
|
+
// Assembly-time, like serving's: this whole function is memoized, so the scan and its warnings
|
|
101
|
+
// happen once per runtime rather than per session rebuild (/new, /resume, fork).
|
|
102
|
+
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.
|
|
125
105
|
// Adapt fastagent's AgentTool to pi's ToolDefinition (`parameters` is plain JSON-Schema; pi accepts
|
|
126
106
|
// it). Each execute runs inside the turn context with the CURRENT session's activation bridge — the
|
|
127
107
|
// assembly is memoized across /new//resume/fork rebuilds while the session changes, so the bridge
|
|
128
|
-
// resolves through sessionRef at call time, exactly like the serving path resolves its
|
|
129
|
-
const customToolDefs =
|
|
108
|
+
// resolves through sessionRef at call time, exactly like the serving path resolves its session.
|
|
109
|
+
const customToolDefs = tools.map((t) => ({
|
|
130
110
|
name: t.name,
|
|
131
111
|
label: t.name,
|
|
132
112
|
description: t.description ?? "",
|
|
@@ -142,10 +122,8 @@ sessionManager) {
|
|
|
142
122
|
if (!bound)
|
|
143
123
|
throw new Error("tool executed before its session was built (lifecycle invariant broken)");
|
|
144
124
|
return turnContext.run({ cwd, sessionManager: bound.sessionManager, tools: bound.activation },
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
// `turnContext` (AsyncLocalStorage). So this env exists to satisfy the shape, and it is the
|
|
148
|
-
// chat cwd's — the same root a default tool would have got, had one reached here.
|
|
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.
|
|
149
127
|
() => t.execute(id, params, signal, undefined, { env }));
|
|
150
128
|
},
|
|
151
129
|
}));
|
|
@@ -156,13 +134,14 @@ sessionManager) {
|
|
|
156
134
|
contextFiles: definition.contextFiles,
|
|
157
135
|
});
|
|
158
136
|
return {
|
|
159
|
-
model,
|
|
160
137
|
modelRuntime,
|
|
138
|
+
modelSpec,
|
|
139
|
+
authPath,
|
|
161
140
|
// Serving honors config.thinkingLevel (config → L2); the resident session must too (fidelity).
|
|
162
141
|
thinkingLevel: config.thinkingLevel,
|
|
163
142
|
definition,
|
|
164
|
-
|
|
165
|
-
customTools,
|
|
143
|
+
extensionPaths,
|
|
144
|
+
customTools: tools,
|
|
166
145
|
customToolDefs,
|
|
167
146
|
deferredToolNames,
|
|
168
147
|
systemPrompt,
|
|
@@ -192,65 +171,84 @@ sessionManager) {
|
|
|
192
171
|
assembly ??= resolveAssembly(rootCwd);
|
|
193
172
|
return assembly;
|
|
194
173
|
};
|
|
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;
|
|
195
178
|
const createRuntime = async ({ cwd, sessionManager, sessionStartEvent }) => {
|
|
196
|
-
const {
|
|
179
|
+
const { modelRuntime, modelSpec, authPath, thinkingLevel, definition, extensionPaths, customToolDefs, deferredToolNames, systemPrompt, } = await assemblyFor(cwd);
|
|
180
|
+
// Per session, NOT memoized with the assembly: pi replaces the session on /new, /resume and
|
|
181
|
+
// fork, and its extension contract is that the replacement gets freshly loaded extensions
|
|
182
|
+
// rather than the previous session's objects. The expensive halves (model hub, auth) are shared
|
|
183
|
+
// through the assembly; only the resource loader is rebuilt.
|
|
197
184
|
const services = await createAgentSessionServices({
|
|
198
185
|
cwd,
|
|
199
186
|
// fastagent's models + auth hub replaces pi's default (~/.pi-backed) one — the auth
|
|
200
187
|
// unification point; see the header.
|
|
201
188
|
modelRuntime,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
appendSystemPromptOverride: () => [],
|
|
211
|
-
// Replace pi's discovered skills with fastagent's. fastagent's Skill (content inline) is
|
|
212
|
-
// reshaped to pi-coding-agent's (read from filePath/baseDir at invocation time).
|
|
213
|
-
skillsOverride: (base) => ({
|
|
214
|
-
skills: definition.skills.map((s) => ({
|
|
215
|
-
name: s.name,
|
|
216
|
-
description: s.description,
|
|
217
|
-
filePath: s.filePath,
|
|
218
|
-
baseDir: dirname(s.filePath),
|
|
219
|
-
sourceInfo: {
|
|
220
|
-
path: s.filePath,
|
|
221
|
-
source: "fastagent",
|
|
222
|
-
scope: "project",
|
|
223
|
-
origin: "top-level",
|
|
224
|
-
baseDir: dirname(s.filePath),
|
|
225
|
-
},
|
|
226
|
-
disableModelInvocation: s.disableModelInvocation ?? false,
|
|
227
|
-
})),
|
|
228
|
-
diagnostics: base.diagnostics,
|
|
229
|
-
}),
|
|
230
|
-
},
|
|
189
|
+
// Chat's assembly is fixed for the life of the runtime (a rebuild makes a new one), so these
|
|
190
|
+
// read constants — where serving passes accessors that change per turn — and it DOES pass its
|
|
191
|
+
// extension paths: chat runs them.
|
|
192
|
+
resourceLoaderOptions: definitionResourceLoaderOptions({
|
|
193
|
+
systemPrompt: () => systemPrompt,
|
|
194
|
+
skills: () => definition.skills,
|
|
195
|
+
extensionPaths,
|
|
196
|
+
}),
|
|
231
197
|
});
|
|
198
|
+
reportExtensionErrors(services);
|
|
199
|
+
// AFTER the services, because an extension may be what defines the model. `registerProvider()`
|
|
200
|
+
// is pi's documented way for one to add providers, and extensions do not execute until the
|
|
201
|
+
// services are built — resolving first fails a definition whose configured model comes from its
|
|
202
|
+
// own extension with a bare "unknown model", after warning about credentials for a provider
|
|
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
|
+
}
|
|
217
|
+
const model = resolveModel(modelRuntime, modelSpec);
|
|
232
218
|
const result = await createAgentSessionFromServices({
|
|
233
219
|
services,
|
|
234
220
|
sessionManager,
|
|
235
221
|
sessionStartEvent,
|
|
236
222
|
model,
|
|
237
223
|
thinkingLevel,
|
|
238
|
-
tools
|
|
224
|
+
// NO `tools` allowlist. It would freeze the tool set at build time, and pi lets an extension
|
|
225
|
+
// register from `session_start`, a command, or any handler — those names would not be in a
|
|
226
|
+
// startup snapshot, so `refreshTools()` would filter them straight back out and the extension
|
|
227
|
+
// would look like it did nothing. `noTools: "builtin"` gets the same guarantee the allowlist
|
|
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",
|
|
239
231
|
customTools: customToolDefs,
|
|
240
232
|
});
|
|
241
233
|
sessionRef.current = {
|
|
242
234
|
session: result.session,
|
|
243
|
-
sessionManager:
|
|
235
|
+
sessionManager: agentSessionManager(result.session, result.session.sessionManager.getSessionId()),
|
|
244
236
|
activation: sessionToolActivation(result.session),
|
|
245
237
|
};
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
//
|
|
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.
|
|
254
252
|
if (deferredToolNames.length > 0) {
|
|
255
253
|
const active = result.session.getActiveToolNames();
|
|
256
254
|
if (deferredToolNames.some((n) => active.includes(n))) {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import type { Models } from "@earendil-works/pi-ai";
|
|
3
3
|
import { type AgentCommand, type SessionControl, type SessionEvent } from "../../session.ts";
|
|
4
|
-
import type {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
4
|
+
import type { SessionObserver } from "./turn-kit.ts";
|
|
5
|
+
import type { Lease } from "./turn-kit.ts";
|
|
6
|
+
import type { AnyModel } from "./models.ts";
|
|
7
|
+
import type { PiAgentSessionFactory } from "./invoke-session.ts";
|
|
8
|
+
import type { PiSessionRecordStore } from "./session-store.ts";
|
|
7
9
|
/** Ceiling for one subscriber's unconsumed backlog. A consumer this far behind (a stalled remote
|
|
8
10
|
* connection — the wire's ReadableStream backpressure stops pulling while invokes keep pushing)
|
|
9
11
|
* has its buffer FROZEN at the cap (memory bounded — the actual goal: ≈10k small events ≈ a few
|
|
@@ -15,25 +17,25 @@ import { type PiSessionReader } from "./sessions.ts";
|
|
|
15
17
|
export declare const SUBSCRIBER_BUFFER_CAP = 10000;
|
|
16
18
|
/** What boundary mutations (compact / set_model / set_thinking / navigate) need — the SAME instances the
|
|
17
19
|
* agent assembly uses: the lease (mutations must not race a run), the model registry (validation +
|
|
18
|
-
* allowedModels), and the
|
|
19
|
-
*
|
|
20
|
+
* allowedModels), and the session factory (compaction is a model call). Writes go through the
|
|
21
|
+
* record the hub's reader opened — after an existence check, so the control plane never creates
|
|
20
22
|
* a session (that is the data plane's monopoly). */
|
|
21
23
|
export interface PiBoundaryWiring {
|
|
22
24
|
lease: Lease;
|
|
23
25
|
models: Models;
|
|
24
|
-
|
|
26
|
+
sessionFactory: PiAgentSessionFactory;
|
|
25
27
|
/** The assembly's configured PAIR — what a session with no overrides runs on. One field because
|
|
26
28
|
* model and thinking level are one setting: which levels exist is a property of the model, so a
|
|
27
29
|
* wiring that could carry them apart could carry a pair no run uses. Must be what
|
|
28
|
-
* {@link
|
|
30
|
+
* {@link sessionFactory} was built with. */
|
|
29
31
|
defaults: {
|
|
30
32
|
model: AnyModel;
|
|
31
33
|
thinkingLevel: ThinkingLevel;
|
|
32
34
|
};
|
|
33
35
|
}
|
|
34
36
|
export interface CreatePiSessionControlOptions {
|
|
35
|
-
/** Read-only access to the durable session
|
|
36
|
-
sessions:
|
|
37
|
+
/** Read-only access to the durable session records (the same root the agent writes). */
|
|
38
|
+
sessions: PiSessionRecordStore;
|
|
37
39
|
/** Boundary-mutation wiring, as a LAZY thunk: the hub's observer must exist before the agent
|
|
38
40
|
* assembly that produces these parts, so the hub asks for them at dispatch time instead
|
|
39
41
|
* (assembly completes before any dispatch can arrive). Absent / undefined → boundary commands
|