@fastagent-sh/fastagent 0.15.0 → 0.16.1
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 +14 -4
- package/dist/bind.d.ts +34 -0
- package/dist/bind.js +74 -0
- package/dist/channels/agentcore-limits.d.ts +7 -0
- package/dist/channels/agentcore-limits.js +9 -0
- package/dist/channels/agentcore-state.d.ts +83 -0
- package/dist/channels/agentcore-state.js +266 -0
- package/dist/channels/agentcore.d.ts +98 -0
- package/dist/channels/agentcore.js +260 -0
- package/dist/channels/busy.d.ts +23 -0
- package/dist/channels/busy.js +53 -0
- package/dist/channels/context-buffer.d.ts +5 -0
- package/dist/channels/context-buffer.js +5 -0
- package/dist/channels/feishu/context-buffer.d.ts +13 -8
- package/dist/channels/feishu/context-buffer.js +60 -6
- package/dist/channels/feishu/feishu-api.js +4 -1
- package/dist/channels/feishu/feishu.d.ts +0 -12
- package/dist/channels/feishu/feishu.js +143 -54
- package/dist/channels/feishu/invoke-turn.js +35 -18
- package/dist/channels/feishu/model.d.ts +0 -1
- package/dist/channels/feishu/normalize.js +0 -1
- package/dist/channels/feishu/parse.d.ts +21 -7
- package/dist/channels/feishu/parse.js +24 -7
- package/dist/channels/feishu/preview.js +3 -2
- package/dist/channels/feishu/scaffold/channel.ts +9 -8
- package/dist/channels/feishu/scaffold/feishu-send.ts +6 -4
- package/dist/channels/feishu/setup-mode.d.ts +30 -1
- package/dist/channels/feishu/setup-mode.js +27 -1
- package/dist/channels/github/github.js +8 -1
- package/dist/channels/http.js +1 -1
- package/dist/channels/lark/scaffold/channel.ts +9 -8
- package/dist/channels/lark/scaffold/lark-send.ts +6 -4
- package/dist/channels/preview-kit.d.ts +7 -1
- package/dist/channels/preview-kit.js +3 -2
- package/dist/channels/slack/parse.d.ts +16 -1
- package/dist/channels/slack/parse.js +46 -3
- package/dist/channels/slack/preview.d.ts +1 -2
- package/dist/channels/slack/preview.js +68 -24
- package/dist/channels/slack/scaffold/channel.ts +5 -5
- package/dist/channels/slack/slack-api.d.ts +3 -23
- package/dist/channels/slack/slack-api.js +6 -22
- package/dist/channels/slack/slack.d.ts +13 -20
- package/dist/channels/slack/slack.js +95 -50
- package/dist/channels/state.d.ts +11 -4
- package/dist/channels/state.js +19 -12
- package/dist/channels/tasks.d.ts +0 -6
- package/dist/channels/tasks.js +16 -1
- package/dist/channels/telegram/parse.d.ts +0 -7
- package/dist/channels/telegram/parse.js +4 -2
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -3
- package/dist/channels/telegram/telegram.js +1 -1
- package/dist/channels/text.d.ts +14 -0
- package/dist/channels/text.js +14 -0
- package/dist/channels/thread-participants.d.ts +21 -0
- package/dist/channels/thread-participants.js +132 -0
- package/dist/channels/turn-queue.js +7 -0
- package/dist/cli/add-feishu.d.ts +7 -4
- package/dist/cli/add-feishu.js +57 -37
- package/dist/cli/add-slack.d.ts +2 -1
- package/dist/cli/add-slack.js +6 -11
- package/dist/cli/commands/add.js +50 -51
- package/dist/cli/commands/attach.js +8 -4
- package/dist/cli/commands/chat.js +8 -8
- package/dist/cli/commands/deploy.d.ts +14 -1
- package/dist/cli/commands/deploy.js +330 -78
- package/dist/cli/commands/dev.d.ts +1 -0
- package/dist/cli/commands/dev.js +38 -25
- package/dist/cli/commands/fire.js +15 -16
- package/dist/cli/commands/info.js +36 -29
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.js +65 -53
- package/dist/cli/commands/invoke.js +9 -6
- package/dist/cli/commands/login.js +35 -21
- package/dist/cli/commands/schedule.js +6 -8
- package/dist/cli/commands/start.d.ts +1 -0
- package/dist/cli/commands/start.js +101 -37
- package/dist/cli/commands/tool.js +30 -18
- package/dist/cli/fail.d.ts +17 -0
- package/dist/cli/fail.js +24 -0
- package/dist/cli/program.js +66 -36
- package/dist/cli/serve.d.ts +52 -8
- package/dist/cli/serve.js +133 -32
- package/dist/cli/shared.d.ts +21 -2
- package/dist/cli/shared.js +44 -18
- package/dist/deploy/agentcore/plan.d.ts +117 -0
- package/dist/deploy/agentcore/plan.js +721 -0
- package/dist/deploy/agentcore/run.d.ts +73 -0
- package/dist/deploy/agentcore/run.js +412 -0
- package/dist/deploy/agentcore/zip.d.ts +17 -0
- package/dist/deploy/agentcore/zip.js +68 -0
- package/dist/deploy/container.d.ts +26 -25
- package/dist/deploy/container.js +93 -89
- package/dist/deploy/docker/plan.d.ts +1 -1
- package/dist/deploy/docker/plan.js +12 -17
- package/dist/deploy/fly/plan.d.ts +2 -0
- package/dist/deploy/fly/plan.js +27 -19
- package/dist/deploy/fly/run.d.ts +12 -1
- package/dist/deploy/fly/run.js +36 -2
- package/dist/deploy/preflight.d.ts +11 -5
- package/dist/deploy/preflight.js +235 -65
- package/dist/deploy/railway/plan.d.ts +7 -0
- package/dist/deploy/railway/plan.js +41 -16
- package/dist/deploy/railway/run.d.ts +8 -1
- package/dist/deploy/railway/run.js +7 -2
- package/dist/deploy/runner.d.ts +5 -2
- package/dist/deploy/runner.js +9 -3
- package/dist/dev-supervisor.d.ts +11 -8
- package/dist/dev-supervisor.js +53 -51
- package/dist/engines/pi/auth.d.ts +8 -7
- package/dist/engines/pi/auth.js +12 -10
- package/dist/engines/pi/channel.d.ts +1 -1
- package/dist/engines/pi/channel.js +5 -5
- package/dist/engines/pi/chat.js +2 -2
- package/dist/engines/pi/config.d.ts +12 -48
- package/dist/engines/pi/config.js +29 -110
- package/dist/engines/pi/create.d.ts +32 -24
- package/dist/engines/pi/create.js +47 -23
- package/dist/engines/pi/definition.d.ts +7 -26
- package/dist/engines/pi/definition.js +8 -54
- package/dist/engines/pi/harness.d.ts +19 -5
- package/dist/engines/pi/harness.js +3 -5
- package/dist/engines/pi/login.d.ts +1 -1
- package/dist/engines/pi/models.d.ts +3 -3
- package/dist/engines/pi/models.js +1 -1
- package/dist/engines/pi/{workspace.d.ts → open.d.ts} +31 -25
- package/dist/engines/pi/{workspace.js → open.js} +27 -29
- package/dist/engines/pi/read-image.d.ts +4 -0
- package/dist/engines/pi/read-image.js +62 -0
- package/dist/engines/pi/search-tools.d.ts +6 -4
- package/dist/engines/pi/search-tools.js +3 -1
- package/dist/engines/pi/session-builder.d.ts +2 -2
- package/dist/engines/pi/session-builder.js +18 -13
- package/dist/engines/pi/tool.d.ts +13 -5
- package/dist/engines/pi/tool.js +4 -0
- package/dist/engines/pi/wake-tool.d.ts +3 -3
- package/dist/env.d.ts +16 -4
- package/dist/env.js +43 -5
- package/dist/host/node.d.ts +4 -2
- package/dist/host/node.js +2 -1
- package/dist/loader.d.ts +2 -2
- package/dist/loader.js +3 -3
- package/dist/log.d.ts +1 -1
- package/dist/log.js +1 -1
- package/dist/paths.d.ts +138 -0
- package/dist/paths.js +326 -0
- package/dist/pi.d.ts +2 -2
- package/dist/pi.js +2 -2
- package/dist/runtime.d.ts +7 -5
- package/dist/runtime.js +2 -2
- package/dist/scaffold/add-channel.d.ts +7 -3
- package/dist/scaffold/add-channel.js +55 -29
- package/dist/scaffold/init.d.ts +32 -41
- package/dist/scaffold/init.js +161 -185
- package/dist/scaffold/templates/env.example +15 -6
- package/dist/scaffold/templates/fastagent.config.mjs +1 -1
- package/dist/scaffold/templates/gitignore +14 -6
- package/dist/scaffold/templates/persona.md +4 -2
- package/dist/scaffold/templates/secrets.gitignore +5 -0
- package/dist/scaffold/templates.d.ts +1 -7
- package/dist/scaffold/templates.js +3 -25
- package/dist/scaffold/vendor-skill.d.ts +2 -2
- package/dist/scaffold/vendor-skill.js +13 -13
- package/dist/schedule/discover.js +4 -4
- package/dist/schedule/scheduler.d.ts +40 -1
- package/dist/schedule/scheduler.js +89 -56
- package/dist/schedule/state.js +1 -1
- package/dist/schedule/wake-alarm.d.ts +47 -0
- package/dist/schedule/wake-alarm.js +136 -0
- package/dist/schedule/wakeups.d.ts +1 -0
- package/dist/schedule/wakeups.js +18 -0
- package/dist/tunnel.d.ts +3 -3
- package/dist/tunnel.js +7 -7
- package/package.json +7 -4
- package/dist/channels/feishu/owned-threads.d.ts +0 -7
- package/dist/channels/feishu/owned-threads.js +0 -47
- package/dist/channels/slack/owned-threads.d.ts +0 -6
- package/dist/channels/slack/owned-threads.js +0 -43
- package/dist/scaffold/templates/gitignore.agentdir-root +0 -5
- package/dist/scaffold/templates/gitignore.kit +0 -2
- package/dist/workspace.d.ts +0 -9
- package/dist/workspace.js +0 -45
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* historical entries back into context via buildContext().
|
|
8
8
|
*/
|
|
9
9
|
import { AgentHarness } from "@earendil-works/pi-agent-core";
|
|
10
|
-
import type {
|
|
10
|
+
import type { ExecutionEnv, ExecutionToolContext, Skill, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
11
11
|
import type { Model, Models } from "@earendil-works/pi-ai";
|
|
12
12
|
import type { PiSessionStore } from "./sessions.ts";
|
|
13
|
+
import { type MountedTool } from "./tool.ts";
|
|
13
14
|
/**
|
|
14
15
|
* The session custom-entry type recording ONE activation delta: `{ names }` — exactly the deferred
|
|
15
16
|
* tools a loader activated in that call. The DEDICATED record the resolve below reads: pi's own
|
|
@@ -19,18 +20,30 @@ import type { PiSessionStore } from "./sessions.ts";
|
|
|
19
20
|
* carry only what was actually discovered.
|
|
20
21
|
*/
|
|
21
22
|
export declare const TOOL_ACTIVATION_ENTRY = "fastagent:tool-activation";
|
|
23
|
+
/** The session a factory-built harness is bound to — the seam the activation bridge (invoke.ts) uses
|
|
24
|
+
* to write {@link TOOL_ACTIVATION_ENTRY} deltas (pi's harness keeps its session private). Absent for
|
|
25
|
+
* a harness built outside {@link piHarnessFactory}: activation still works in-turn there, but is not
|
|
26
|
+
* recorded — the factory owns persistence. */
|
|
27
|
+
type AnyHarness = AgentHarness<any>;
|
|
22
28
|
export type PiSession = Awaited<ReturnType<PiSessionStore["openOrCreate"]>>;
|
|
23
|
-
export declare function harnessSession(harness:
|
|
29
|
+
export declare function harnessSession(harness: AnyHarness): PiSession | undefined;
|
|
24
30
|
/**
|
|
25
31
|
* pi's Model with the API-shape generic erased — fastagent only passes models through to the
|
|
26
32
|
* harness, so the generic carries no information. One alias keeps the `any` auditable.
|
|
27
33
|
*/
|
|
28
34
|
export type AnyModel = Model<any>;
|
|
29
35
|
/** Builds a pi harness bound to the given session — called once per invoke. */
|
|
30
|
-
|
|
36
|
+
/** The harness fastagent builds: context-typed on {@link ExecutionToolContext}, because that is what
|
|
37
|
+
* pi's env-backed default tools read (pi 0.83). Custom tools are context-FREE and stay assignable — a
|
|
38
|
+
* four-parameter `execute` satisfies the five-parameter one, so `defineTool` is untouched by this. */
|
|
39
|
+
type PiHarness = AgentHarness<ExecutionToolContext>;
|
|
40
|
+
export type PiHarnessFactory = (session: string) => PiHarness | Promise<PiHarness>;
|
|
31
41
|
export interface PiHarnessFactoryOptions {
|
|
32
42
|
/** Session persistence. Continuity = same backing store + same session id. */
|
|
33
43
|
sessions: PiSessionStore;
|
|
44
|
+
/** Filesystem/process environment for the default coding tools. Handed to the harness as the TURN's
|
|
45
|
+
* tool context (pi 0.83), which is how read/bash/edit/write reach the machine at all — so this is the
|
|
46
|
+
* ONE seam a sandbox adapter implements, not a knob beside the tools that ignore it. */
|
|
34
47
|
env: ExecutionEnv;
|
|
35
48
|
/** Provider collection for all model requests; {@link model} must belong to it (same provider id). */
|
|
36
49
|
models: Models;
|
|
@@ -38,7 +51,7 @@ export interface PiHarnessFactoryOptions {
|
|
|
38
51
|
/** Reasoning effort for the model (pi's scale). Unset = fastagent's pinned default ("medium", pi
|
|
39
52
|
* TUI parity — see {@link DEFAULT_THINKING_LEVEL}); unsupported levels are clamped by pi per model. */
|
|
40
53
|
thinkingLevel?: ThinkingLevel;
|
|
41
|
-
tools?:
|
|
54
|
+
tools?: MountedTool[];
|
|
42
55
|
/**
|
|
43
56
|
* Final assembled prompt, or a SYNC factory re-evaluated per invoke (how L1 serves dynamic
|
|
44
57
|
* `instructions` + the skills listing). Distinct from {@link live}, which is the directory rung's
|
|
@@ -109,6 +122,7 @@ export declare function resolveHarnessOverrides(entries: OverrideEntryLike[], mo
|
|
|
109
122
|
model: AnyModel;
|
|
110
123
|
thinkingLevel: ThinkingLevel;
|
|
111
124
|
};
|
|
112
|
-
export declare function resolveHarnessActiveToolNames(recorded: string[] | null, tools:
|
|
125
|
+
export declare function resolveHarnessActiveToolNames(recorded: string[] | null, tools: MountedTool[], sessionId: string): string[] | undefined;
|
|
113
126
|
/** Open-or-create the session per invoke: existing → open (history via buildContext); missing → create. */
|
|
114
127
|
export declare function piHarnessFactory(options: PiHarnessFactoryOptions): PiHarnessFactory;
|
|
128
|
+
export {};
|
|
@@ -18,10 +18,6 @@ import { isDeferredTool } from "./tool.js";
|
|
|
18
18
|
* carry only what was actually discovered.
|
|
19
19
|
*/
|
|
20
20
|
export const TOOL_ACTIVATION_ENTRY = "fastagent:tool-activation";
|
|
21
|
-
/** The session a factory-built harness is bound to — the seam the activation bridge (invoke.ts) uses
|
|
22
|
-
* to write {@link TOOL_ACTIVATION_ENTRY} deltas (pi's harness keeps its session private). Absent for
|
|
23
|
-
* a harness built outside {@link piHarnessFactory}: activation still works in-turn there, but is not
|
|
24
|
-
* recorded — the factory owns persistence. */
|
|
25
21
|
const harnessSessions = new WeakMap();
|
|
26
22
|
export function harnessSession(harness) {
|
|
27
23
|
return harnessSessions.get(harness);
|
|
@@ -187,7 +183,9 @@ export function piHarnessFactory(options) {
|
|
|
187
183
|
// Session overrides (set_model / set_thinking) win over the assembly defaults — same entry walk.
|
|
188
184
|
const overrides = resolveHarnessOverrides(entries, options.models, { model: options.model, thinkingLevel: options.thinkingLevel ?? DEFAULT_THINKING_LEVEL }, sessionId);
|
|
189
185
|
const harness = new AgentHarness({
|
|
190
|
-
|
|
186
|
+
// Static, not a per-turn provider: the env is fixed for the agent's lifetime, and resolving a
|
|
187
|
+
// constant per turn would only add a promise to the turn's critical path.
|
|
188
|
+
toolContext: { env: options.env },
|
|
191
189
|
session,
|
|
192
190
|
models: options.models,
|
|
193
191
|
model: overrides.model,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `fastagent login`: authenticate a MODEL PROVIDER into the resolved auth file (project-level
|
|
3
|
-
* `<
|
|
3
|
+
* `<root>/.secrets/auth.json` by default, or `--auth-path`/`FASTAGENT_AUTH_PATH`) via the same
|
|
4
4
|
* {@link fastagentCredentialStore} the runtime uses (one writer, one lock/corruption semantics).
|
|
5
5
|
*
|
|
6
6
|
* Flow (pi-ai's unified `ProviderAuth` API): pick a method (OAuth or API key), then a provider that
|
|
@@ -9,8 +9,8 @@ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
|
9
9
|
import { type FastagentAuthOptions } from "./auth.ts";
|
|
10
10
|
import { type InteractiveLoginKind } from "./login.ts";
|
|
11
11
|
export interface CreatePiModelsOptions extends FastagentAuthOptions {
|
|
12
|
-
/** Credentials file path. Defaults to the global `~/.fastagent/auth.json`; the directory opener passes
|
|
13
|
-
* the project-level `<
|
|
12
|
+
/** Credentials file path. Defaults to the global `~/.fastagent/.secrets/auth.json`; the directory opener passes
|
|
13
|
+
* the project-level `<root>/.secrets/auth.json`. */
|
|
14
14
|
authPath?: string;
|
|
15
15
|
/** Extra providers registered on top of the built-ins (same id overrides a built-in). */
|
|
16
16
|
providers?: Provider[];
|
|
@@ -18,7 +18,7 @@ export interface CreatePiModelsOptions extends FastagentAuthOptions {
|
|
|
18
18
|
/**
|
|
19
19
|
* A `Models` with every built-in pi provider, wired to fastagent's auth: stored credentials from the
|
|
20
20
|
* {@link CreatePiModelsOptions.authPath} file (via {@link fastagentCredentialStore}; the global
|
|
21
|
-
* `~/.fastagent/auth.json` unless the opener passes a project-level path), then ambient env vars. A
|
|
21
|
+
* `~/.fastagent/.secrets/auth.json` unless the opener passes a project-level path), then ambient env vars. A
|
|
22
22
|
* stored credential owns the provider; env is consulted only when nothing is stored (resolution order
|
|
23
23
|
* is upstream-owned).
|
|
24
24
|
*/
|
|
@@ -12,7 +12,7 @@ import { interactiveLoginKind } from "./login.js";
|
|
|
12
12
|
/**
|
|
13
13
|
* A `Models` with every built-in pi provider, wired to fastagent's auth: stored credentials from the
|
|
14
14
|
* {@link CreatePiModelsOptions.authPath} file (via {@link fastagentCredentialStore}; the global
|
|
15
|
-
* `~/.fastagent/auth.json` unless the opener passes a project-level path), then ambient env vars. A
|
|
15
|
+
* `~/.fastagent/.secrets/auth.json` unless the opener passes a project-level path), then ambient env vars. A
|
|
16
16
|
* stored credential owns the provider; env is consulted only when nothing is stored (resolution order
|
|
17
17
|
* is upstream-owned).
|
|
18
18
|
*/
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
2
1
|
import type { Agent } from "../../agent.ts";
|
|
3
2
|
import { type FastagentConfig } from "./config.ts";
|
|
4
3
|
import type { SessionControl } from "../../session.ts";
|
|
5
4
|
import type { SessionObserver } from "./invoke.ts";
|
|
6
5
|
import type { PiSessionReader, PiSessionStore } from "./sessions.ts";
|
|
7
6
|
import type { ModuleLoadFailure } from "../../loader.ts";
|
|
8
|
-
import {
|
|
7
|
+
import type { LoadedDefinition } from "./definition.ts";
|
|
9
8
|
import type { ToolCollision } from "./tool.ts";
|
|
10
|
-
|
|
9
|
+
import type { MountedTool } from "./tool.ts";
|
|
10
|
+
export interface CreatePiAgentFromDirOptions {
|
|
11
11
|
/** Model spec override (e.g. the CLI --model flag). Precedence: this > FASTAGENT_MODEL > config.model. */
|
|
12
12
|
model?: string;
|
|
13
13
|
/**
|
|
14
|
-
* Session store directory. Default `<
|
|
14
|
+
* Session store directory. Default `<agentDir>/.state/sessions` (machine state). `start`
|
|
15
15
|
* overrides it (--sessions-dir / FASTAGENT_SESSIONS_DIR / a mounted volume) so production continuity
|
|
16
16
|
* survives redeploys.
|
|
17
17
|
*/
|
|
18
18
|
sessionsDir?: string;
|
|
19
19
|
/**
|
|
20
|
-
* Credentials file override. Default `<
|
|
21
|
-
*
|
|
22
|
-
* `~/.fastagent/auth.json` to share one credential across projects.
|
|
20
|
+
* Credentials file override. Default `<agentDir>/.secrets/auth.json` (project-level, under the
|
|
21
|
+
* `.secrets/`). Override via --auth-path / FASTAGENT_AUTH_PATH; point it at
|
|
22
|
+
* the global `~/.fastagent/.secrets/auth.json` to share one credential across projects.
|
|
23
23
|
*/
|
|
24
24
|
authPath?: string;
|
|
25
25
|
/**
|
|
@@ -28,7 +28,7 @@ export interface CreatePiAgentFromWorkspaceOptions {
|
|
|
28
28
|
* and never poll). The built-in `wake` tool mounts only when this is set AND `config.selfSchedule` is on.
|
|
29
29
|
*/
|
|
30
30
|
serving?: boolean;
|
|
31
|
-
/** Assemble the session control plane over this
|
|
31
|
+
/** Assemble the session control plane over this agent's session store and return it as
|
|
32
32
|
* {@link sessionControl} — the store is created inside this opener, so the hub must be wired
|
|
33
33
|
* here too (an external `createPiSessionControl` cannot exist before the store does).
|
|
34
34
|
* Default: `config.sessionControl` AND {@link serving} — the config key means "serve the control
|
|
@@ -43,51 +43,57 @@ export interface CreatePiAgentFromWorkspaceOptions {
|
|
|
43
43
|
observer?: SessionObserver;
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* The
|
|
46
|
+
* The agent assembly FRONT HALF — everything that is independent of how pi consumes the
|
|
47
47
|
* definition (transient harness for serving vs resident AgentSession for chat / session control):
|
|
48
|
-
* config → model spec →
|
|
49
|
-
* place it is computed) → state root → auth path. Both {@link
|
|
48
|
+
* placement resolution → config → model spec → the full tool surface ({@link resolveAgentTools} — the
|
|
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
|
|
51
51
|
* consumption shapes. (Definition loading and prompt assembly stay per-consumer: serving re-reads
|
|
52
52
|
* them live per invoke, the session builder snapshots at startup and lets pi append skills/env.)
|
|
53
53
|
*/
|
|
54
|
-
export interface
|
|
54
|
+
export interface AgentAssembly {
|
|
55
55
|
config: FastagentConfig;
|
|
56
56
|
configPath?: string;
|
|
57
57
|
/** The resolved "provider/modelId" spec in use. */
|
|
58
58
|
modelSpec: string;
|
|
59
|
-
/** Absolute agent
|
|
59
|
+
/** Absolute agent dir — definition + config + machinery live here (resolvePlacement().agentDir). */
|
|
60
60
|
agentDir: string;
|
|
61
|
-
/** Absolute
|
|
61
|
+
/** Absolute workspace — the agent's cwd and the start of the ②-context walk: the agent dir's parent
|
|
62
|
+
* when the agent sits inside it, the agent dir ITSELF when you point at the agent. */
|
|
63
|
+
workspace: string;
|
|
64
|
+
/** Absolute state root (FASTAGENT_STATE_DIR > <agentDir>/.state). */
|
|
62
65
|
stateRoot: string;
|
|
63
|
-
/** Absolute credentials file (--auth-path/authPath option > FASTAGENT_AUTH_PATH > <
|
|
66
|
+
/** Absolute credentials file (--auth-path/authPath option > FASTAGENT_AUTH_PATH > <agentDir>/.secrets/auth.json). */
|
|
64
67
|
authPath: string;
|
|
65
68
|
/** The full mounted tool surface (config.tools + discovered tools/, search_tools applied). */
|
|
66
|
-
tools:
|
|
69
|
+
tools: MountedTool[];
|
|
67
70
|
toolNames: string[];
|
|
68
71
|
deferredToolNames: string[];
|
|
69
72
|
toolCollisions: ToolCollision[];
|
|
70
73
|
toolFailures: ModuleLoadFailure[];
|
|
71
74
|
}
|
|
72
|
-
export declare function
|
|
75
|
+
export declare function resolveAgentAssembly(dir: string, options?: {
|
|
73
76
|
model?: string;
|
|
74
77
|
authPath?: string;
|
|
75
|
-
}): Promise<
|
|
78
|
+
}): Promise<AgentAssembly>;
|
|
76
79
|
/**
|
|
77
|
-
* "Point at a
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
+
* "Point at a directory → agent": resolve the placement (`dir` may be either end — the workspace or
|
|
81
|
+
* the agent dir itself), load the config, resolve model and tools, then L2. Throws a clear error when
|
|
82
|
+
* no model source is set (fail visibly at startup). Returns everything an entry point needs to report
|
|
83
|
+
* what it assembled.
|
|
80
84
|
*/
|
|
81
|
-
export declare function
|
|
85
|
+
export declare function createPiAgentFromDir(dir: string, options?: CreatePiAgentFromDirOptions): Promise<{
|
|
82
86
|
agent: Agent;
|
|
83
87
|
definition: LoadedDefinition;
|
|
84
88
|
config: FastagentConfig;
|
|
85
89
|
configPath?: string;
|
|
86
90
|
/** The resolved "provider/modelId" spec actually in use. */
|
|
87
91
|
modelSpec: string;
|
|
88
|
-
/** Absolute agent
|
|
92
|
+
/** Absolute agent dir in use — channels/tools/persona come from here. */
|
|
89
93
|
agentDir: string;
|
|
90
|
-
/** Absolute
|
|
94
|
+
/** Absolute workspace in use — the agent's cwd: ALWAYS the directory that was pointed at. */
|
|
95
|
+
workspace: string;
|
|
96
|
+
/** Absolute state root in use (FASTAGENT_STATE_DIR > <agentDir>/.state) — the ChannelContext's stateRoot. */
|
|
91
97
|
stateRoot: string;
|
|
92
98
|
/** Absolute session store directory in use (for the startup report). */
|
|
93
99
|
sessionsDir: string;
|
|
@@ -95,7 +101,7 @@ export declare function createPiAgentFromWorkspace(dir: string, options?: Create
|
|
|
95
101
|
authPath: string;
|
|
96
102
|
/** The session store in use — also a {@link PiSessionReader}. */
|
|
97
103
|
sessions: PiSessionStore & PiSessionReader;
|
|
98
|
-
/** The observation plane over this
|
|
104
|
+
/** The observation plane over this agent's sessions; present iff `options.sessionControl`. */
|
|
99
105
|
sessionControl?: SessionControl;
|
|
100
106
|
/** Non-default, active-by-default tool names in effect: config.tools + discovered tools/. Each name
|
|
101
107
|
* lives in exactly one report slot — deferred names are in {@link deferredToolNames} instead. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Open a definition directory into an agent — the single
|
|
2
|
+
* Open a definition directory into an agent — the single agent opener BOTH `fastagent dev` and
|
|
3
3
|
* `fastagent start` drive.
|
|
4
4
|
*
|
|
5
5
|
* A thin command-posture composition over L2 `createPiAgentFromDefinition`: open the directory →
|
|
@@ -9,39 +9,35 @@
|
|
|
9
9
|
* watch and can point sessions at a mounted volume.
|
|
10
10
|
*/
|
|
11
11
|
import { mkdir } from "node:fs/promises";
|
|
12
|
-
import { defaultSessionsDir, loadConfig,
|
|
13
|
-
import {
|
|
12
|
+
import { defaultSessionsDir, loadConfig, resolveAuthPath, resolveModelSpec, } from "./config.js";
|
|
13
|
+
import { resolveStateRoot, resolvePlacement } from "../../paths.js";
|
|
14
|
+
import { createPiAgentFromDefinition, resolveAgentTools } from "./create.js";
|
|
14
15
|
import { createPiSessionControl } from "./session-control.js";
|
|
15
16
|
import { withWakeTool } from "./wake-tool.js";
|
|
16
|
-
import { ensureStateRootSelfIgnored } from "./definition.js";
|
|
17
17
|
import { jsonlSessionStore } from "./sessions.js";
|
|
18
|
-
export async function
|
|
19
|
-
|
|
18
|
+
export async function resolveAgentAssembly(dir, options = {}) {
|
|
19
|
+
// Placement is structural (resolvePlacement): the AGENT DIR carries definition + config + machinery;
|
|
20
|
+
// its parent — the WORKSPACE — is what the agent works on: its cwd and the start of the ②-context
|
|
21
|
+
// walk (that is where it reads the project's AGENTS.md from).
|
|
22
|
+
const { agentDir, workspace } = resolvePlacement(dir);
|
|
23
|
+
const { config, path: configPath } = await loadConfig(agentDir);
|
|
20
24
|
const modelSpec = resolveModelSpec(options.model, config);
|
|
21
25
|
if (!modelSpec) {
|
|
22
26
|
throw new Error(`missing model: set --model, "model" in fastagent.config.ts, or FASTAGENT_MODEL (e.g. "openai-codex/gpt-5.5")`);
|
|
23
27
|
}
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
// The
|
|
29
|
-
// whole machine-state home in one knob (a container mounts one volume); the finer overrides below
|
|
30
|
-
// still win for their specific path.
|
|
31
|
-
const stateRoot = resolveStateRoot(dir);
|
|
32
|
-
// The credentials file: project-level by default (under the state root); only resolved here, never
|
|
28
|
+
const { tools, toolNames, deferredToolNames, toolCollisions, toolFailures } = await resolveAgentTools(config, agentDir);
|
|
29
|
+
// The state root: sessions/channel state/schedule state derive from it (FASTAGENT_STATE_DIR moves it
|
|
30
|
+
// in one knob — a container points it at its volume); the finer overrides below still win.
|
|
31
|
+
const stateRoot = resolveStateRoot(agentDir);
|
|
32
|
+
// The credentials file: project-level by default (under `<agentDir>/.secrets`); only resolved here, never
|
|
33
33
|
// created (a missing file reads as not-configured — `fastagent login` creates it).
|
|
34
|
-
const authPath = resolveAuthPath(
|
|
35
|
-
// Self-ignore the state root iff it lands in-tree — which covers everything under it (sessions, auth,
|
|
36
|
-
// every channel's `channels/<kind>` home). HERE, not in the serving opener: every consumer of this
|
|
37
|
-
// assembly can WRITE under the state root (serving: sessions/channels; the session builder: pi's
|
|
38
|
-
// `/login` writing auth.json), so resolving a workspace's state root must make it leak-safe.
|
|
39
|
-
await ensureStateRootSelfIgnored(dir, stateRoot);
|
|
34
|
+
const authPath = resolveAuthPath(agentDir, options.authPath);
|
|
40
35
|
return {
|
|
41
36
|
config,
|
|
42
37
|
configPath,
|
|
43
38
|
modelSpec,
|
|
44
39
|
agentDir,
|
|
40
|
+
workspace,
|
|
45
41
|
stateRoot,
|
|
46
42
|
authPath,
|
|
47
43
|
tools,
|
|
@@ -52,18 +48,19 @@ export async function resolveWorkspaceAssembly(dir, options = {}) {
|
|
|
52
48
|
};
|
|
53
49
|
}
|
|
54
50
|
/**
|
|
55
|
-
* "Point at a
|
|
56
|
-
*
|
|
57
|
-
*
|
|
51
|
+
* "Point at a directory → agent": resolve the placement (`dir` may be either end — the workspace or
|
|
52
|
+
* the agent dir itself), load the config, resolve model and tools, then L2. Throws a clear error when
|
|
53
|
+
* no model source is set (fail visibly at startup). Returns everything an entry point needs to report
|
|
54
|
+
* what it assembled.
|
|
58
55
|
*/
|
|
59
|
-
export async function
|
|
60
|
-
const { config, configPath, modelSpec, agentDir, stateRoot, authPath, tools, toolNames, deferredToolNames, toolCollisions, toolFailures, } = await
|
|
56
|
+
export async function createPiAgentFromDir(dir, options = {}) {
|
|
57
|
+
const { config, configPath, modelSpec, agentDir, workspace, stateRoot, authPath, tools, toolNames, deferredToolNames, toolCollisions, toolFailures, } = await resolveAgentAssembly(dir, options);
|
|
61
58
|
// Mount the built-in `wake` tool only when BOTH: this is a long-running serve (the poller honors it) AND
|
|
62
|
-
// the author opted into self-scheduling (config.selfSchedule). The
|
|
59
|
+
// the author opted into self-scheduling (config.selfSchedule). The agent's own `wake` wins if defined.
|
|
63
60
|
const mountedTools = withWakeTool(tools, stateRoot, !!options.serving && !!config.selfSchedule);
|
|
64
61
|
const sessionsDir = options.sessionsDir ?? defaultSessionsDir(stateRoot);
|
|
65
62
|
await mkdir(sessionsDir, { recursive: true });
|
|
66
|
-
const sessions = jsonlSessionStore({ dir: sessionsDir, cwd:
|
|
63
|
+
const sessions = jsonlSessionStore({ dir: sessionsDir, cwd: workspace });
|
|
67
64
|
// The hub is wired HERE because the store is created here: chicken-and-egg otherwise (the hub
|
|
68
65
|
// needs the store; the agent needs the hub's observer). Boundary parts (models/factory/lease)
|
|
69
66
|
// only exist after the assembly below — the hub takes them as a lazy thunk, filled by the
|
|
@@ -93,7 +90,7 @@ export async function createPiAgentFromWorkspace(dir, options = {}) {
|
|
|
93
90
|
const { agent, definition } = await createPiAgentFromDefinition(agentDir, {
|
|
94
91
|
model: modelSpec,
|
|
95
92
|
thinkingLevel: config.thinkingLevel,
|
|
96
|
-
cwd:
|
|
93
|
+
cwd: workspace,
|
|
97
94
|
tools: mountedTools,
|
|
98
95
|
authPath,
|
|
99
96
|
// Skills are definition-only (the agent is its directory), so dev mirrors deployment exactly.
|
|
@@ -115,6 +112,7 @@ export async function createPiAgentFromWorkspace(dir, options = {}) {
|
|
|
115
112
|
sessions,
|
|
116
113
|
sessionControl: hub?.control,
|
|
117
114
|
agentDir,
|
|
115
|
+
workspace,
|
|
118
116
|
config,
|
|
119
117
|
configPath,
|
|
120
118
|
modelSpec,
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ReadImageProcessor } from "@earendil-works/pi-agent-core";
|
|
2
|
+
/** The `read` tool's image processor. Matches pi-coding-agent's messages verbatim: they reach the model
|
|
3
|
+
* as tool output, so a reworded one is a different prompt, not a different implementation detail. */
|
|
4
|
+
export declare const readImageProcessor: ReadImageProcessor;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The image pipeline pi's `read` tool needs: normalize an unsupported format to PNG, resize below the
|
|
3
|
+
* inline limit, and hand back the hints that tell the model what it is looking at.
|
|
4
|
+
*
|
|
5
|
+
* pi-agent-core's `createReadTool` takes this as an INJECTED processor and does nothing without one —
|
|
6
|
+
* unlike pi-coding-agent's, which wires its private `processImage` internally. That function is not
|
|
7
|
+
* exported (nor reachable: the package's `exports` map has no deep paths), so this rebuilds it from the
|
|
8
|
+
* two halves that ARE public, `convertToPng` and `resizeImage`/`formatDimensionNote`.
|
|
9
|
+
*
|
|
10
|
+
* It is upstream logic restated, which is a real cost — without it `read` on a screenshot sends the raw
|
|
11
|
+
* bytes (measured: 7.48 MB of base64 where pi-coding-agent sends 3.48 MB, and no dimension note for the
|
|
12
|
+
* model's coordinate math), and a bmp is dropped entirely while the tool's own description still
|
|
13
|
+
* advertises it. test/tools-parity.test.ts compares this against pi-coding-agent's real `read` on both
|
|
14
|
+
* paths, so upstream changing the pipeline surfaces as a failing test rather than as drift.
|
|
15
|
+
*/
|
|
16
|
+
import { convertToPng, formatDimensionNote, resizeImage } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
/** Formats a provider takes inline as-is; everything else has to become a PNG first. */
|
|
18
|
+
const INLINE_MIME = {
|
|
19
|
+
"image/png": "image/png",
|
|
20
|
+
"image/jpeg": "image/jpeg",
|
|
21
|
+
"image/jpg": "image/jpeg",
|
|
22
|
+
"image/gif": "image/gif",
|
|
23
|
+
"image/webp": "image/webp",
|
|
24
|
+
};
|
|
25
|
+
/** The `read` tool's image processor. Matches pi-coding-agent's messages verbatim: they reach the model
|
|
26
|
+
* as tool output, so a reworded one is a different prompt, not a different implementation detail. */
|
|
27
|
+
export const readImageProcessor = async (bytes, mimeType, options) => {
|
|
28
|
+
const base = mimeType.split(";")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();
|
|
29
|
+
const inline = INLINE_MIME[base];
|
|
30
|
+
let normalized;
|
|
31
|
+
if (inline) {
|
|
32
|
+
normalized = { bytes, mimeType: inline };
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const png = await convertToPng(Buffer.from(bytes).toString("base64"), base);
|
|
36
|
+
if (!png)
|
|
37
|
+
return { ok: false, message: "[Image omitted: could not be converted to a supported inline image format.]" };
|
|
38
|
+
normalized = { bytes: Buffer.from(png.data, "base64"), mimeType: png.mimeType, convertedFrom: base };
|
|
39
|
+
}
|
|
40
|
+
const hints = [];
|
|
41
|
+
const converted = (to) => normalized.convertedFrom && normalized.convertedFrom !== to
|
|
42
|
+
? `[Image converted from ${normalized.convertedFrom} to ${to}.]`
|
|
43
|
+
: undefined;
|
|
44
|
+
if (!options.autoResizeImages) {
|
|
45
|
+
const hint = converted(normalized.mimeType);
|
|
46
|
+
if (hint)
|
|
47
|
+
hints.push(hint);
|
|
48
|
+
return { ok: true, data: Buffer.from(normalized.bytes).toString("base64"), mimeType: normalized.mimeType, hints };
|
|
49
|
+
}
|
|
50
|
+
const resized = await resizeImage(normalized.bytes, normalized.mimeType);
|
|
51
|
+
if (!resized)
|
|
52
|
+
return { ok: false, message: "[Image omitted: could not be resized below the inline image size limit.]" };
|
|
53
|
+
const hint = converted(resized.mimeType);
|
|
54
|
+
if (hint)
|
|
55
|
+
hints.push(hint);
|
|
56
|
+
// The scale factor the model needs to map coordinates back onto the original — dropping it is what
|
|
57
|
+
// makes a resized screenshot unusable for anything positional.
|
|
58
|
+
const note = formatDimensionNote(resized);
|
|
59
|
+
if (note)
|
|
60
|
+
hints.push(note);
|
|
61
|
+
return { ok: true, data: resized.data, mimeType: resized.mimeType, hints };
|
|
62
|
+
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type
|
|
2
|
-
/** Mount the built-in loader iff any mounted tool is deferred and the author didn't define their own.
|
|
3
|
-
|
|
1
|
+
import { type MountedTool } from "./tool.ts";
|
|
2
|
+
/** Mount the built-in loader iff any mounted tool is deferred and the author didn't define their own.
|
|
3
|
+
* Typed on the MOUNTED tool, not the authored one: it inspects names and deferral and never executes,
|
|
4
|
+
* so narrowing here would reject the very set it is handed (pi's defaults take the turn's context). */
|
|
5
|
+
export declare function withSearchTool(tools: MountedTool[]): MountedTool[];
|
|
4
6
|
/** Build the `search_tools` loader. Keyword search over the inactive tools' name+description.
|
|
5
7
|
*
|
|
6
8
|
* `executionMode: "sequential"` — pi turns any batch containing a sequential tool serial. Required for
|
|
7
9
|
* correct load-point attribution everywhere an OUTER active-set diff exists: pi wraps SDK customTools
|
|
8
10
|
* (the chat path) in a before/after diff, and two parallel loader calls would both snapshot the
|
|
9
11
|
* pre-activation set and get stamped with the same activation. Custom loader authors must set it too. */
|
|
10
|
-
export declare function makeSearchToolsTool():
|
|
12
|
+
export declare function makeSearchToolsTool(): MountedTool;
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { log } from "../../log.js";
|
|
10
10
|
import { defineTool, isDeferredTool, stripDeferredMarker } from "./tool.js";
|
|
11
|
-
/** Mount the built-in loader iff any mounted tool is deferred and the author didn't define their own.
|
|
11
|
+
/** Mount the built-in loader iff any mounted tool is deferred and the author didn't define their own.
|
|
12
|
+
* Typed on the MOUNTED tool, not the authored one: it inspects names and deferral and never executes,
|
|
13
|
+
* so narrowing here would reject the very set it is handed (pi's defaults take the turn's context). */
|
|
12
14
|
export function withSearchTool(tools) {
|
|
13
15
|
if (!tools.some(isDeferredTool))
|
|
14
16
|
return tools;
|
|
@@ -3,7 +3,7 @@ export interface BuildSessionRuntimeOptions {
|
|
|
3
3
|
/** Model spec override (the CLI --model flag). Precedence: this > FASTAGENT_MODEL > config.model. */
|
|
4
4
|
model?: string;
|
|
5
5
|
/** Credentials file override (the CLI --auth-path flag). Precedence: this > FASTAGENT_AUTH_PATH >
|
|
6
|
-
* the
|
|
6
|
+
* the agent's default `<agentDir>/.secrets/auth.json`. */
|
|
7
7
|
authPath?: string;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
@@ -11,6 +11,6 @@ export interface BuildSessionRuntimeOptions {
|
|
|
11
11
|
* skills, and auth resolved exactly as the serving opener does). Split from the TUI launcher so the
|
|
12
12
|
* assembly — the fidelity-critical part — is inspectable and reusable without launching a TUI.
|
|
13
13
|
*/
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function buildAgentSessionRuntime(dir: string, options?: BuildSessionRuntimeOptions,
|
|
15
15
|
/** Session backend. Defaults to pi's project-scoped store; tests inject SessionManager.inMemory(). */
|
|
16
16
|
sessionManager?: SessionManager): Promise<AgentSessionRuntime>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The shared definition-aware session builder: open a
|
|
2
|
+
* The shared definition-aware session builder: open a directory's assembled agent as a resident pi
|
|
3
3
|
* `AgentSessionRuntime`. Extracted from chat.ts (session-control Phase 0) as the proof of the
|
|
4
4
|
* assembly seam — independently instantiable, running the SAME agent that `dev`/`start` serve. The
|
|
5
5
|
* TUI (chat.ts) is its one consumer: the session control plane (Phases 1–3) was built on the invoke
|
|
@@ -16,8 +16,8 @@
|
|
|
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.
|
|
19
|
-
* - auth → fastagent's credential store at the
|
|
20
|
-
* serving opener: `--auth-path`/`FASTAGENT_AUTH_PATH`, else `<
|
|
19
|
+
* - auth → fastagent's credential store at the AGENT's auth path (same resolution as the
|
|
20
|
+
* serving opener: `--auth-path`/`FASTAGENT_AUTH_PATH`, else `<root>/.secrets/auth.json`).
|
|
21
21
|
* pi's TUI `/login` writes through the injected store into the SAME file, so `fastagent
|
|
22
22
|
* login` and chat share one credential lifecycle. pi's `~/.pi` auth is not consulted.
|
|
23
23
|
*
|
|
@@ -38,7 +38,7 @@ import { createPiModelRuntime, probeAuthSource } from "./models.js";
|
|
|
38
38
|
import { log } from "../../log.js";
|
|
39
39
|
import { additiveActivation, turnContext } from "./tool-context.js";
|
|
40
40
|
import { reportDefinitionWarnings, reportModuleLoadFailures, reportToolCollisions } from "./report.js";
|
|
41
|
-
import {
|
|
41
|
+
import { resolveAgentAssembly } from "./open.js";
|
|
42
42
|
/** Adapt coding-agent's resident SessionManager to FastAgent's shared tool-runtime manager port. */
|
|
43
43
|
function toolChatSessionManager(session) {
|
|
44
44
|
return {
|
|
@@ -59,7 +59,7 @@ function toolChatSessionManager(session) {
|
|
|
59
59
|
* skills, and auth resolved exactly as the serving opener does). Split from the TUI launcher so the
|
|
60
60
|
* assembly — the fidelity-critical part — is inspectable and reusable without launching a TUI.
|
|
61
61
|
*/
|
|
62
|
-
export async function
|
|
62
|
+
export async function buildAgentSessionRuntime(dir, options = {},
|
|
63
63
|
/** Session backend. Defaults to pi's project-scoped store; tests inject SessionManager.inMemory(). */
|
|
64
64
|
sessionManager) {
|
|
65
65
|
/** The turn's {@link ToolActivation} over pi's AgentSession — the counterpart of invoke.ts's
|
|
@@ -91,8 +91,8 @@ sessionManager) {
|
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
async function resolveAssembly(cwd) {
|
|
94
|
-
// The shared front half — the SAME config/model-spec/
|
|
95
|
-
// opener uses (
|
|
94
|
+
// The shared front half — the SAME placement/config/model-spec/tool/auth resolution the serving
|
|
95
|
+
// opener uses (open.ts); those inputs cannot drift between the two consumption shapes.
|
|
96
96
|
// (Definition→prompt assembly is NOT shared: serving re-reads live per invoke, this runtime is a
|
|
97
97
|
// startup snapshot and pi appends skills/env itself — see the header.) `tools` arrives with
|
|
98
98
|
// search_tools applied; deferral is EMULATED below like serving
|
|
@@ -100,27 +100,27 @@ sessionManager) {
|
|
|
100
100
|
// on the session in createRuntime — pi's session starts all-active), and the activation bridge
|
|
101
101
|
// above rides the same turn context, so the SAME search_tools works against pi's AgentSession
|
|
102
102
|
// instead of fastagent's harness.
|
|
103
|
-
const { config, modelSpec, agentDir, authPath, tools, deferredToolNames, toolCollisions, toolFailures } = await
|
|
103
|
+
const { config, modelSpec, agentDir, authPath, tools, deferredToolNames, toolCollisions, toolFailures } = await resolveAgentAssembly(cwd, options);
|
|
104
104
|
reportToolCollisions(toolCollisions);
|
|
105
105
|
reportModuleLoadFailures(toolFailures);
|
|
106
106
|
// ONE hub owns model resolution AND per-request auth — the ModelRuntime-shaped sibling of
|
|
107
107
|
// serving's createPiModels; see models.ts.
|
|
108
108
|
const modelRuntime = await createPiModelRuntime({ authPath });
|
|
109
109
|
// MIGRATION HINT (deliberate breaking change): chat historically used pi's own `~/.pi` auth;
|
|
110
|
-
// it now reads the
|
|
110
|
+
// it now reads the agent's credential file like every other command. Probe the RESOLVED
|
|
111
111
|
// model's provider through the normal resolution path (stored credential OR env var — an
|
|
112
112
|
// env-authed user is fine and must not be warned): only when that provider has no usable auth
|
|
113
113
|
// AND pi's old file exists does the bare provider error get its cause named.
|
|
114
114
|
if ((await probeAuthSource(modelRuntime, modelSpec)) === undefined &&
|
|
115
115
|
existsSync(join(getAgentDir(), "auth.json"))) {
|
|
116
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
|
|
117
|
+
`pi's ~/.pi auth; run \`fastagent login\` (or /login in the TUI) to store credentials for this agent`);
|
|
118
118
|
}
|
|
119
119
|
const model = resolveModel(modelRuntime, modelSpec);
|
|
120
120
|
const env = new NodeExecutionEnv({ cwd });
|
|
121
121
|
const definition = await loadAgentDefinition(agentDir, { cwd, env });
|
|
122
122
|
reportDefinitionWarnings(definition.collisions, definition.diagnostics);
|
|
123
|
-
const defaultNames = piDefaultTools(
|
|
123
|
+
const defaultNames = piDefaultTools().map((t) => t.name);
|
|
124
124
|
const customTools = tools.filter((t) => !defaultNames.includes(t.name));
|
|
125
125
|
// Adapt fastagent's AgentTool to pi's ToolDefinition (`parameters` is plain JSON-Schema; pi accepts
|
|
126
126
|
// it). Each execute runs inside the turn context with the CURRENT session's activation bridge — the
|
|
@@ -141,7 +141,12 @@ sessionManager) {
|
|
|
141
141
|
// session-lifecycle invariant as a normal out-of-turn call (fail visibly).
|
|
142
142
|
if (!bound)
|
|
143
143
|
throw new Error("tool executed before its session was built (lifecycle invariant broken)");
|
|
144
|
-
return turnContext.run({ cwd, sessionManager: bound.sessionManager, tools: bound.activation },
|
|
144
|
+
return turnContext.run({ cwd, sessionManager: bound.sessionManager, tools: bound.activation },
|
|
145
|
+
// pi's per-turn TOOL context (5th parameter) is read only by its default coding tools, and
|
|
146
|
+
// those are filtered out of `customTools` above; fastagent's own take theirs from
|
|
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.
|
|
149
|
+
() => t.execute(id, params, signal, undefined, { env }));
|
|
145
150
|
},
|
|
146
151
|
}));
|
|
147
152
|
// base + instructions ONLY — pi appends the skill section and env (cwd) itself (including
|
|
@@ -153,7 +158,7 @@ sessionManager) {
|
|
|
153
158
|
return {
|
|
154
159
|
model,
|
|
155
160
|
modelRuntime,
|
|
156
|
-
// Serving honors config.thinkingLevel (
|
|
161
|
+
// Serving honors config.thinkingLevel (config → L2); the resident session must too (fidelity).
|
|
157
162
|
thinkingLevel: config.thinkingLevel,
|
|
158
163
|
definition,
|
|
159
164
|
defaultNames,
|