@fastagent-sh/fastagent 0.14.0 → 0.16.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 +16 -6
- package/dist/agent.d.ts +20 -0
- package/dist/agent.js +9 -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 +258 -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 +35 -0
- package/dist/channels/context-buffer.js +103 -0
- package/dist/channels/control.d.ts +28 -0
- package/dist/channels/control.js +214 -0
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +15 -13
- package/dist/channels/feishu/context-buffer.js +62 -69
- package/dist/channels/feishu/feishu-api.d.ts +11 -3
- package/dist/channels/feishu/feishu-api.js +24 -3
- package/dist/channels/feishu/feishu.d.ts +27 -27
- package/dist/channels/feishu/feishu.js +318 -207
- package/dist/channels/feishu/invoke-turn.d.ts +7 -20
- package/dist/channels/feishu/invoke-turn.js +44 -79
- package/dist/channels/feishu/model.d.ts +2 -2
- package/dist/channels/feishu/normalize.d.ts +2 -1
- package/dist/channels/feishu/normalize.js +0 -1
- package/dist/channels/feishu/parse.d.ts +22 -8
- package/dist/channels/feishu/parse.js +24 -7
- package/dist/channels/feishu/preview.d.ts +4 -7
- package/dist/channels/feishu/preview.js +27 -142
- package/dist/channels/feishu/register-app.d.ts +2 -1
- package/dist/channels/feishu/register-webhook.d.ts +2 -1
- 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 +37 -0
- package/dist/channels/feishu/setup-mode.js +28 -0
- package/dist/channels/feishu/ws-ingress.d.ts +28 -0
- package/dist/channels/feishu/ws-ingress.js +136 -0
- package/dist/channels/github/github.js +16 -7
- package/dist/channels/http.d.ts +14 -0
- package/dist/channels/http.js +36 -3
- package/dist/channels/invoke-turn-kit.d.ts +65 -0
- package/dist/channels/invoke-turn-kit.js +87 -0
- package/dist/channels/lark/lark.d.ts +4 -2
- package/dist/channels/lark/lark.js +4 -1
- package/dist/channels/lark/onboard.d.ts +8 -4
- package/dist/channels/lark/onboard.js +8 -0
- package/dist/channels/lark/scaffold/channel.ts +9 -8
- package/dist/channels/lark/scaffold/lark-send.ts +6 -4
- package/dist/channels/preview-kit.d.ts +115 -0
- package/dist/channels/preview-kit.js +184 -0
- package/dist/channels/seen.d.ts +5 -0
- package/dist/channels/seen.js +35 -0
- package/dist/channels/slack/bot-auth.d.ts +15 -0
- package/dist/channels/slack/bot-auth.js +146 -0
- package/dist/channels/slack/config-api.d.ts +60 -0
- package/dist/channels/slack/config-api.js +149 -0
- package/dist/channels/slack/context-buffer.d.ts +24 -0
- package/dist/channels/slack/context-buffer.js +37 -0
- package/dist/channels/slack/invoke-turn.d.ts +19 -0
- package/dist/channels/slack/invoke-turn.js +63 -0
- package/dist/channels/slack/manifest.d.ts +49 -0
- package/dist/channels/slack/manifest.js +69 -0
- package/dist/channels/slack/model.d.ts +67 -0
- package/dist/channels/slack/model.js +2 -0
- package/dist/channels/slack/onboard.d.ts +41 -0
- package/dist/channels/slack/onboard.js +120 -0
- package/dist/channels/slack/onboarding-state.d.ts +31 -0
- package/dist/channels/slack/onboarding-state.js +69 -0
- package/dist/channels/slack/parse.d.ts +38 -0
- package/dist/channels/slack/parse.js +124 -0
- package/dist/channels/slack/preview.d.ts +23 -0
- package/dist/channels/slack/preview.js +403 -0
- package/dist/channels/slack/reaction.d.ts +24 -0
- package/dist/channels/slack/reaction.js +62 -0
- package/dist/channels/slack/register-webhook.d.ts +10 -0
- package/dist/channels/slack/register-webhook.js +49 -0
- package/dist/channels/slack/scaffold/channel.ts +33 -0
- package/dist/channels/slack/scaffold/slack-send.ts +171 -0
- package/dist/channels/slack/setup-server.d.ts +17 -0
- package/dist/channels/slack/setup-server.js +103 -0
- package/dist/channels/slack/slack-api.d.ts +57 -0
- package/dist/channels/slack/slack-api.js +399 -0
- package/dist/channels/slack/slack.d.ts +51 -0
- package/dist/channels/slack/slack.js +496 -0
- package/dist/channels/slack/welcomed.d.ts +5 -0
- package/dist/channels/slack/welcomed.js +32 -0
- package/dist/channels/state.d.ts +11 -4
- package/dist/channels/state.js +22 -15
- package/dist/channels/stop-command.d.ts +6 -0
- package/dist/channels/stop-command.js +36 -0
- package/dist/channels/tasks.d.ts +7 -0
- package/dist/channels/tasks.js +25 -0
- package/dist/channels/telegram/context-buffer.d.ts +8 -17
- package/dist/channels/telegram/context-buffer.js +6 -85
- package/dist/channels/telegram/invoke-turn.d.ts +5 -22
- package/dist/channels/telegram/invoke-turn.js +11 -58
- package/dist/channels/telegram/parse.d.ts +0 -7
- package/dist/channels/telegram/parse.js +4 -2
- package/dist/channels/telegram/preview.d.ts +4 -7
- package/dist/channels/telegram/preview.js +24 -142
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -3
- package/dist/channels/telegram/telegram.js +24 -10
- package/dist/channels/{feishu/text.d.ts → text.d.ts} +15 -1
- package/dist/channels/{feishu/text.js → text.js} +15 -1
- package/dist/channels/thread-participants.d.ts +21 -0
- package/dist/channels/thread-participants.js +132 -0
- package/dist/channels/turn-queue.js +8 -1
- package/dist/channels/turn-store.d.ts +1 -1
- package/dist/channels/turn-store.js +2 -3
- package/dist/cli/add-feishu.d.ts +30 -0
- package/dist/cli/add-feishu.js +366 -0
- package/dist/cli/add-slack.d.ts +11 -0
- package/dist/cli/add-slack.js +199 -0
- package/dist/{cli-auth.d.ts → cli/auth-view.d.ts} +1 -1
- package/dist/{cli-auth.js → cli/auth-view.js} +1 -1
- package/dist/cli/commands/add.d.ts +4 -0
- package/dist/cli/commands/add.js +191 -60
- package/dist/cli/commands/attach.d.ts +82 -0
- package/dist/cli/commands/attach.js +563 -0
- package/dist/cli/commands/chat.d.ts +1 -0
- package/dist/cli/commands/chat.js +12 -7
- package/dist/cli/commands/deploy.d.ts +3 -1
- package/dist/cli/commands/deploy.js +384 -82
- package/dist/cli/commands/dev.js +34 -26
- package/dist/cli/commands/fire.js +17 -18
- 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 +11 -9
- package/dist/cli/commands/login.js +48 -45
- package/dist/cli/commands/models.js +1 -1
- package/dist/cli/commands/schedule.js +6 -8
- package/dist/cli/commands/start.js +102 -39
- package/dist/cli/commands/tool.js +23 -18
- package/dist/cli/fail.d.ts +17 -2
- package/dist/cli/fail.js +25 -1
- package/dist/{invoke-stream.d.ts → cli/invoke-stream.d.ts} +1 -1
- package/dist/{invoke-stream.js → cli/invoke-stream.js} +4 -0
- package/dist/cli/kernel.d.ts +3 -2
- package/dist/cli/models-view.d.ts +21 -0
- package/dist/cli/models-view.js +66 -0
- package/dist/cli/program.js +135 -56
- package/dist/cli/serve.d.ts +60 -20
- package/dist/cli/serve.js +252 -47
- package/dist/cli/shared.d.ts +40 -7
- package/dist/cli/shared.js +180 -38
- package/dist/collect.d.ts +14 -3
- package/dist/collect.js +24 -0
- package/dist/core.d.ts +3 -1
- package/dist/core.js +2 -0
- package/dist/deploy/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 +3 -3
- package/dist/deploy/docker/plan.js +20 -23
- package/dist/deploy/fly/plan.d.ts +5 -1
- package/dist/deploy/fly/plan.js +39 -26
- package/dist/deploy/fly/run.d.ts +17 -5
- package/dist/deploy/fly/run.js +52 -7
- package/dist/deploy/preflight.d.ts +20 -8
- package/dist/deploy/preflight.js +267 -71
- package/dist/deploy/railway/plan.d.ts +10 -1
- package/dist/deploy/railway/plan.js +53 -20
- package/dist/deploy/railway/run.d.ts +13 -5
- package/dist/deploy/railway/run.js +22 -7
- package/dist/deploy/runner.d.ts +7 -3
- package/dist/deploy/runner.js +9 -3
- package/dist/deploy/secrets.d.ts +2 -1
- package/dist/deploy/secrets.js +23 -3
- package/dist/dev-supervisor.d.ts +11 -10
- package/dist/dev-supervisor.js +56 -50
- package/dist/engines/pi/auth.d.ts +8 -7
- package/dist/engines/pi/auth.js +12 -10
- package/dist/engines/pi/channel.d.ts +22 -16
- package/dist/engines/pi/channel.js +92 -62
- package/dist/engines/pi/chat.d.ts +4 -16
- package/dist/engines/pi/chat.js +8 -261
- package/dist/engines/pi/config.d.ts +20 -55
- package/dist/engines/pi/config.js +49 -121
- package/dist/engines/pi/create.d.ts +30 -10
- package/dist/engines/pi/create.js +33 -26
- package/dist/engines/pi/definition.d.ts +7 -26
- package/dist/engines/pi/definition.js +8 -54
- package/dist/engines/pi/harness.d.ts +49 -0
- package/dist/engines/pi/harness.js +90 -2
- package/dist/engines/pi/invoke.d.ts +55 -2
- package/dist/engines/pi/invoke.js +304 -19
- package/dist/engines/pi/login.d.ts +12 -1
- package/dist/engines/pi/login.js +16 -4
- package/dist/engines/pi/models.d.ts +59 -13
- package/dist/engines/pi/models.js +62 -24
- package/dist/engines/pi/open.d.ts +114 -0
- package/dist/engines/pi/open.js +127 -0
- package/dist/engines/pi/session-builder.d.ts +16 -0
- package/dist/engines/pi/session-builder.js +308 -0
- package/dist/engines/pi/session-control.d.ts +50 -0
- package/dist/engines/pi/session-control.js +604 -0
- package/dist/engines/pi/sessions.d.ts +17 -2
- package/dist/engines/pi/sessions.js +9 -0
- package/dist/engines/pi/tool-context.d.ts +18 -11
- package/dist/engines/pi/tool-context.js +3 -9
- package/dist/engines/pi/tool.d.ts +6 -8
- package/dist/engines/pi/tool.js +10 -1
- package/dist/engines/pi/wake-tool.d.ts +0 -3
- package/dist/engines/pi/wake-tool.js +9 -7
- package/dist/env.d.ts +16 -4
- package/dist/env.js +43 -5
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/host/node.d.ts +25 -8
- package/dist/host/node.js +5 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/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/observe.js +3 -0
- package/dist/paths.d.ts +138 -0
- package/dist/paths.js +326 -0
- package/dist/pi.d.ts +6 -5
- package/dist/pi.js +4 -3
- package/dist/runtime.d.ts +7 -5
- package/dist/runtime.js +2 -2
- package/dist/scaffold/add-channel.d.ts +26 -9
- package/dist/scaffold/add-channel.js +175 -40
- 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 +5 -4
- 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 -3
- package/dist/schedule/wakeups.js +19 -1
- package/dist/session-remote.d.ts +53 -0
- package/dist/session-remote.js +336 -0
- package/dist/session.d.ts +265 -0
- package/dist/session.js +37 -0
- package/dist/slack.d.ts +2 -0
- package/dist/slack.js +2 -0
- package/dist/tunnel.d.ts +7 -4
- package/dist/tunnel.js +23 -12
- package/package.json +22 -7
- package/dist/channels/feishu/owned-threads.d.ts +0 -11
- package/dist/channels/feishu/owned-threads.js +0 -47
- package/dist/channels/feishu/seen.d.ts +0 -5
- package/dist/channels/feishu/seen.js +0 -47
- package/dist/cli-add-feishu.d.ts +0 -8
- package/dist/cli-add-feishu.js +0 -223
- package/dist/cli-models.d.ts +0 -11
- package/dist/cli-models.js +0 -20
- package/dist/engines/pi/workspace.d.ts +0 -56
- package/dist/engines/pi/workspace.js +0 -72
- 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
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import type { FastagentTool } from "./tool.ts";
|
|
3
3
|
import type { Models } from "@earendil-works/pi-ai";
|
|
4
|
-
import type
|
|
5
|
-
/** pi's thinking levels (types.d.ts `ThinkingLevel`), as a runtime list for config validation — pi
|
|
6
|
-
* exports only the type. A level the selected model does not support is clamped by pi per model. */
|
|
7
|
-
export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
4
|
+
import { type AnyModel } from "./harness.ts";
|
|
8
5
|
export interface FastagentConfig {
|
|
9
6
|
/** "provider/modelId". Precedence: CLI --model > FASTAGENT_MODEL > config. */
|
|
10
7
|
model?: string;
|
|
@@ -12,14 +9,6 @@ export interface FastagentConfig {
|
|
|
12
9
|
* "xhigh" | "max"). Unset = pi's default. Authors tune thinking in the pi TUI while vibing — this
|
|
13
10
|
* is the serving-side counterpart (fidelity). Levels a model doesn't support are clamped by pi. */
|
|
14
11
|
thinkingLevel?: ThinkingLevel;
|
|
15
|
-
/**
|
|
16
|
-
* The agent-definition subdirectory (persona.md, skills/, tools/, channels/), relative to the config
|
|
17
|
-
* file's directory. Default: the config directory itself (flat — today's behaviour). Point it at a
|
|
18
|
-
* sibling like `"./agent"` to serve an existing repo as a coding agent: the config dir stays the run
|
|
19
|
-
* root (cwd, whose AGENTS.md the agent reads as ② context), while the agent's own surface lives in the
|
|
20
|
-
* subdir and does not collide with the host's `tools/`/`src/` (core.md scenario grid).
|
|
21
|
-
*/
|
|
22
|
-
agentDir?: string;
|
|
23
12
|
/** Extra custom tools, appended after pi defaults — never replaces them. `FastagentTool` = AgentTool
|
|
24
13
|
* plus the optional `deferred` marker (see defineTool). */
|
|
25
14
|
tools?: FastagentTool[];
|
|
@@ -30,6 +19,15 @@ export interface FastagentConfig {
|
|
|
30
19
|
* Off by default — self-scheduling is an autonomy capability, opt in when you want it. Only takes
|
|
31
20
|
* effect on the serving path (`dev`/`start`, where the scheduler poller honors a wake-up). */
|
|
32
21
|
selfSchedule?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Serve the session control plane over HTTP (`/control/*`: state/entries/events + dispatch —
|
|
24
|
+
* steer/abort/compact/set_model…) for remote consumers: a Web panel, a desktop app, `fastagent
|
|
25
|
+
* attach`. Default off (it is a remote-control surface). When on, `dev`/`start` generate a
|
|
26
|
+
* per-boot bearer token and write `<stateRoot>/control.json` for local discovery. The serve
|
|
27
|
+
* binds all interfaces, so the routes are LAN-reachable with the token as the only protection —
|
|
28
|
+
* firewall the port, or wrap it for real exposure (design §14).
|
|
29
|
+
*/
|
|
30
|
+
sessionControl?: boolean;
|
|
33
31
|
/** Deploy-time declarations for what the agent needs on the box, so real agents don't hand-write a
|
|
34
32
|
* Dockerfile / hand-set variables. */
|
|
35
33
|
deploy?: {
|
|
@@ -47,24 +45,17 @@ export interface FastagentConfig {
|
|
|
47
45
|
export declare function defineConfig(config: FastagentConfig): FastagentConfig;
|
|
48
46
|
export interface LoadedConfig {
|
|
49
47
|
config: FastagentConfig;
|
|
50
|
-
/** Config file path; undefined when
|
|
48
|
+
/** Config file path; undefined when the loader was pointed at a directory holding none. */
|
|
51
49
|
path?: string;
|
|
52
50
|
}
|
|
53
51
|
/** A valid bindable port. */
|
|
54
52
|
export declare function isValidPort(n: number): boolean;
|
|
55
|
-
/**
|
|
56
|
-
* loader (below) and `scaffoldWorkspace`'s already-a-workspace refusal both read this, so "is there a
|
|
57
|
-
* config?" can't diverge between them when the set changes. */
|
|
58
|
-
export declare const WORKSPACE_CONFIG_NAMES: readonly ["fastagent.config.ts", "fastagent.config.js", "fastagent.config.mjs"];
|
|
59
|
-
/** Load `<dir>/fastagent.config.ts|.js|.mjs`. No file = zero-config; a wrong-shape file throws. */
|
|
53
|
+
/** Load `<dir>/fastagent.config.ts|.js|.mjs`. No file = defaults; a wrong-shape file throws. */
|
|
60
54
|
export declare function loadConfig(dir: string): Promise<LoadedConfig>;
|
|
61
|
-
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
|
|
65
|
-
* already validated that agentDir stays under `dir`.
|
|
66
|
-
*/
|
|
67
|
-
export declare function resolveAgentDir(dir: string, config: FastagentConfig): string;
|
|
55
|
+
/** The provider prefix of a "provider/modelId" spec. A spec without "/" returns whole — downstream
|
|
56
|
+
* lookups then miss visibly (an unknown-provider error / a login-required hint), never a mangled id
|
|
57
|
+
* (`slice(0, indexOf("/"))` silently drops the last char when "/" is absent). */
|
|
58
|
+
export declare function providerOf(spec: string): string;
|
|
68
59
|
/** Resolve "provider/modelId" → a pi Model from `models`, so the harness resolves auth from the same collection. */
|
|
69
60
|
export declare function resolveModel(models: Models, spec: string): AnyModel;
|
|
70
61
|
/** All registered "provider/modelId" specs in `models`, sorted — the list behind `fastagent models`. */
|
|
@@ -72,7 +63,7 @@ export declare function listModels(models: Models): string[];
|
|
|
72
63
|
/**
|
|
73
64
|
* Rewrite the `model` in a config file's SOURCE TEXT to `spec`, for the first-run picker's write-back.
|
|
74
65
|
* Handles the scaffold's commented placeholder (`// model: "…"`) and an existing `model:` line; returns
|
|
75
|
-
* null when neither is present (
|
|
66
|
+
* null when neither is present (no config file, or a hand-shaped one) so the caller falls back to a
|
|
76
67
|
* printed hint instead of guessing where to insert. Text-level (not AST) on purpose — it only ever
|
|
77
68
|
* touches a line it recognizes, never reformats the author's file.
|
|
78
69
|
*/
|
|
@@ -85,35 +76,9 @@ export declare function resolveModelSpec(flag: string | undefined, config: Fasta
|
|
|
85
76
|
* Resolved to absolute so the store and the startup report agree regardless of cwd.
|
|
86
77
|
*/
|
|
87
78
|
export declare function resolveSessionsDirOverride(flag: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
|
|
88
|
-
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
* so the store and the startup report agree regardless of cwd. No implicit project↔global fallback (isolation
|
|
92
|
-
* + fail-visibly; see auth.ts); to share one account across projects, point this at the global
|
|
93
|
-
* `~/.fastagent/auth.json` — sharing ONE file is safe under the store's cross-process refresh lock.
|
|
94
|
-
*/
|
|
95
|
-
export declare function resolveAuthPathOverride(flag: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
|
|
96
|
-
/**
|
|
97
|
-
* The IN-TREE default state root, `<dir>/.fastagent` — what {@link resolveStateRoot} falls back to when
|
|
98
|
-
* `FASTAGENT_STATE_DIR` moves state nowhere. THE single definition of that path segment.
|
|
99
|
-
*/
|
|
100
|
-
export declare function projectStateDir(dir: string): string;
|
|
101
|
-
/**
|
|
102
|
-
* The resolved state root — the ONE durable machine-state home everything derives from (auth.json,
|
|
103
|
-
* sessions/, channels/<kind>/): `FASTAGENT_STATE_DIR` env > `<dir>/.fastagent`. Absolute, so channels
|
|
104
|
-
* and the startup report agree regardless of cwd. Definition: single lifecycle (precious, survives
|
|
105
|
-
* redeploy), single process — a container mounts ONE volume here. The finer knobs
|
|
106
|
-
* (`FASTAGENT_SESSIONS_DIR`, `FASTAGENT_AUTH_PATH`) still override their specific path on top.
|
|
107
|
-
*
|
|
108
|
-
* `FASTAGENT_STATE_DIR` is an OPERATOR override, so a relative value resolves against `process.cwd()`
|
|
109
|
-
* — the CLI convention its sibling knobs share (`resolveOverridePath`), NOT against `dir`. Only the
|
|
110
|
-
* DEFAULT (`<dir>/.fastagent`) is dir-anchored. Deployments set an absolute path (a mounted volume);
|
|
111
|
-
* a relative value is in-tree — hence self-ignored — only when run from the definition dir (cwd == dir).
|
|
112
|
-
*/
|
|
113
|
-
export declare function resolveStateRoot(dir: string, env?: NodeJS.ProcessEnv): string;
|
|
114
|
-
/** The default credentials file under a resolved state root ({@link resolveStateRoot}). */
|
|
115
|
-
export declare function defaultAuthPath(stateRoot: string): string;
|
|
116
|
-
/** The effective auth file for a workspace: override if present, else the project-level auth.json. */
|
|
79
|
+
/** The default credentials file under a resolved secrets dir ({@link resolveSecretsDir}). */
|
|
80
|
+
export declare function defaultAuthPath(secretsDir: string): string;
|
|
81
|
+
/** The effective auth file for an agent: override if present, else `<secrets dir>/auth.json`. */
|
|
117
82
|
export declare function resolveAuthPath(dir: string, flag: string | undefined, env?: NodeJS.ProcessEnv): string;
|
|
118
83
|
/** The default sessions dir under a resolved state root ({@link resolveStateRoot}). */
|
|
119
84
|
export declare function defaultSessionsDir(stateRoot: string): string;
|
|
@@ -11,25 +11,18 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
11
11
|
* (resolveModel, resolveModelSpec). One concern: everything about fastagent.config.ts.
|
|
12
12
|
*
|
|
13
13
|
* Red line: config describes deployment/runtime choices, never authored identity or expertise (those
|
|
14
|
-
* live in persona.md + skills, with AGENTS.md as project context).
|
|
15
|
-
*
|
|
14
|
+
* live in persona.md + skills, with AGENTS.md as project context). It has no say in PLACEMENT either —
|
|
15
|
+
* that rule lives in the neutral src/paths.ts — but its FILE is what marks a directory as an agent, so a
|
|
16
|
+
* served agent always has one (its contents may be `export default {}`; a model can still come from
|
|
17
|
+
* --model / FASTAGENT_MODEL). The loader below stays tolerant of a missing file because it is a loader:
|
|
18
|
+
* callers that have not resolved a placement (deploy inspection, tests) may point it anywhere.
|
|
16
19
|
*/
|
|
17
|
-
import { existsSync,
|
|
18
|
-
import {
|
|
19
|
-
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
20
|
+
import { existsSync, statSync } from "node:fs";
|
|
21
|
+
import { basename, join } from "node:path";
|
|
20
22
|
import { pathToFileURL } from "node:url";
|
|
23
|
+
import { THINKING_LEVELS } from "./harness.js";
|
|
21
24
|
import { moduleLoadHint } from "../../loader.js";
|
|
22
|
-
|
|
23
|
-
* exports only the type. A level the selected model does not support is clamped by pi per model. */
|
|
24
|
-
export const THINKING_LEVELS = [
|
|
25
|
-
"off",
|
|
26
|
-
"minimal",
|
|
27
|
-
"low",
|
|
28
|
-
"medium",
|
|
29
|
-
"high",
|
|
30
|
-
"xhigh",
|
|
31
|
-
"max",
|
|
32
|
-
];
|
|
25
|
+
import { AGENT_CONFIG_NAMES, resolveOverridePath, resolveSecretsDir } from "../../paths.js";
|
|
33
26
|
/** Identity function for typing and IDE completion (vite/next-style). */
|
|
34
27
|
export function defineConfig(config) {
|
|
35
28
|
return config;
|
|
@@ -51,13 +44,9 @@ function validateStringList(value, key, shape, desc, path) {
|
|
|
51
44
|
}
|
|
52
45
|
}
|
|
53
46
|
}
|
|
54
|
-
/**
|
|
55
|
-
* loader (below) and `scaffoldWorkspace`'s already-a-workspace refusal both read this, so "is there a
|
|
56
|
-
* config?" can't diverge between them when the set changes. */
|
|
57
|
-
export const WORKSPACE_CONFIG_NAMES = ["fastagent.config.ts", "fastagent.config.js", "fastagent.config.mjs"];
|
|
58
|
-
/** Load `<dir>/fastagent.config.ts|.js|.mjs`. No file = zero-config; a wrong-shape file throws. */
|
|
47
|
+
/** Load `<dir>/fastagent.config.ts|.js|.mjs`. No file = defaults; a wrong-shape file throws. */
|
|
59
48
|
export async function loadConfig(dir) {
|
|
60
|
-
const found =
|
|
49
|
+
const found = AGENT_CONFIG_NAMES.map((name) => join(dir, name)).filter((path) => existsSync(path));
|
|
61
50
|
if (found.length === 0)
|
|
62
51
|
return { config: {} };
|
|
63
52
|
if (found.length > 1) {
|
|
@@ -67,7 +56,14 @@ export async function loadConfig(dir) {
|
|
|
67
56
|
const path = found[0];
|
|
68
57
|
let mod;
|
|
69
58
|
try {
|
|
70
|
-
|
|
59
|
+
// Cache-bust on file change: ESM `import()` caches by URL, so a config REWRITTEN in this process
|
|
60
|
+
// (the first-run picker's write-back) would otherwise read back stale — deploy's model-travel gate
|
|
61
|
+
// then contradicts the "saved model" line it just printed. mtime keeps unchanged files cached;
|
|
62
|
+
// its resolution is the ceiling — a rewrite within the same timestamp tick reads stale (fine for
|
|
63
|
+
// the write-back: sub-tick only on coarse-mtime filesystems, and the next process starts fresh).
|
|
64
|
+
const url = pathToFileURL(path);
|
|
65
|
+
url.searchParams.set("v", String(statSync(path).mtimeMs));
|
|
66
|
+
mod = (await import(__rewriteRelativeImportExtension(url.href)));
|
|
71
67
|
}
|
|
72
68
|
catch (error) {
|
|
73
69
|
throw new Error(`${path}: ${error.message}${moduleLoadHint(error)}`);
|
|
@@ -78,64 +74,26 @@ export async function loadConfig(dir) {
|
|
|
78
74
|
}
|
|
79
75
|
const c = config;
|
|
80
76
|
// Unknown keys throw: defineConfig only type-protects .ts authors; a typo in a .js/.mjs config
|
|
81
|
-
// (`modle:`) must not silently degrade to
|
|
77
|
+
// (`modle:`) must not silently degrade to defaults.
|
|
82
78
|
for (const key of Object.keys(c)) {
|
|
83
79
|
if (key !== "model" &&
|
|
84
80
|
key !== "thinkingLevel" &&
|
|
85
|
-
key !== "agentDir" &&
|
|
86
81
|
key !== "tools" &&
|
|
87
82
|
key !== "http" &&
|
|
88
83
|
key !== "deploy" &&
|
|
89
|
-
key !== "selfSchedule"
|
|
90
|
-
|
|
84
|
+
key !== "selfSchedule" &&
|
|
85
|
+
key !== "sessionControl") {
|
|
86
|
+
throw new Error(`${path}: unknown key "${key}" (valid keys: model, thinkingLevel, tools, http, deploy, selfSchedule, sessionControl)`);
|
|
91
87
|
}
|
|
92
88
|
}
|
|
93
89
|
if (c.model !== undefined && typeof c.model !== "string") {
|
|
94
90
|
throw new Error(`${path}: "model" must be a "provider/modelId" string`);
|
|
95
91
|
}
|
|
96
|
-
if (c.
|
|
97
|
-
throw new Error(`${path}: "
|
|
92
|
+
if (c.sessionControl !== undefined && typeof c.sessionControl !== "boolean") {
|
|
93
|
+
throw new Error(`${path}: "sessionControl" must be a boolean`);
|
|
98
94
|
}
|
|
99
|
-
if (c.
|
|
100
|
-
throw new Error(`${path}: "
|
|
101
|
-
}
|
|
102
|
-
if (typeof c.agentDir === "string") {
|
|
103
|
-
// Enforce the documented "subdirectory of the config dir" contract: an escaping agentDir (e.g.
|
|
104
|
-
// "../shared") would still resolve for tool/channel/persona discovery, but `dev`'s chokidar only
|
|
105
|
-
// watches the config dir subtree — edits outside it would silently never trigger a restart. Reject
|
|
106
|
-
// it here (fail visibly) rather than let hot-reload break without a signal.
|
|
107
|
-
const rel = relative(dir, resolve(dir, c.agentDir));
|
|
108
|
-
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
109
|
-
throw new Error(`${path}: "agentDir" ("${c.agentDir}") must be a subdirectory of the config directory, not escape it`);
|
|
110
|
-
}
|
|
111
|
-
// An explicitly declared agentDir that doesn't exist is a typo until proven otherwise ("./agnet"):
|
|
112
|
-
// without this check every opener would assemble an EMPTY agent (no persona, no skills, no tools)
|
|
113
|
-
// with zero errors — the worst silent failure this config can produce. Deliberately NOT auto-created:
|
|
114
|
-
// config load is read-only (no implicit operations), and a mkdir would turn the typo into a served
|
|
115
|
-
// empty agent plus a junk directory.
|
|
116
|
-
// lstat, not stat: a symlink would pass the literal containment check above while its TARGET lives
|
|
117
|
-
// outside the config dir — exactly what that check exists to prevent (dev's watch would silently
|
|
118
|
-
// never see edits). Same rule as init's parent preflight: reject, don't follow.
|
|
119
|
-
const agentDirAbs = resolve(dir, c.agentDir);
|
|
120
|
-
const st = lstatSync(agentDirAbs, { throwIfNoEntry: false });
|
|
121
|
-
if (!st) {
|
|
122
|
-
throw new Error(`${path}: "agentDir" ("${c.agentDir}") does not exist — create it, or fix the path`);
|
|
123
|
-
}
|
|
124
|
-
if (st.isSymbolicLink()) {
|
|
125
|
-
// Separate message: to its user a symlink LOOKS like a working directory — name the reason and the fix.
|
|
126
|
-
throw new Error(`${path}: "agentDir" ("${c.agentDir}") is a symlink — not allowed (its target can live outside the ` +
|
|
127
|
-
`config directory, where dev's watch would never see edits); use a real directory, or point agentDir at the target's real path`);
|
|
128
|
-
}
|
|
129
|
-
if (!st.isDirectory()) {
|
|
130
|
-
throw new Error(`${path}: "agentDir" ("${c.agentDir}") is not a directory`);
|
|
131
|
-
}
|
|
132
|
-
// The leaf lstat can't see a symlinked INTERMEDIATE segment (agentDir "./a/b" with `a` → outside):
|
|
133
|
-
// realpath equality covers every segment under the config dir in one comparison. dir itself is
|
|
134
|
-
// realpath'd on both sides, so a symlinked config-dir path (macOS /tmp) stays legal.
|
|
135
|
-
if (realpathSync(agentDirAbs) !== resolve(realpathSync(dir), relative(dir, agentDirAbs))) {
|
|
136
|
-
throw new Error(`${path}: "agentDir" ("${c.agentDir}") resolves through a symlink — not allowed (the target can ` +
|
|
137
|
-
`live outside the config directory, where dev's watch would never see edits); use the real path`);
|
|
138
|
-
}
|
|
95
|
+
if (c.thinkingLevel !== undefined && !THINKING_LEVELS.has(c.thinkingLevel)) {
|
|
96
|
+
throw new Error(`${path}: "thinkingLevel" must be one of ${[...THINKING_LEVELS].join(", ")}`);
|
|
139
97
|
}
|
|
140
98
|
if (c.selfSchedule !== undefined && typeof c.selfSchedule !== "boolean") {
|
|
141
99
|
throw new Error(`${path}: "selfSchedule" must be a boolean`);
|
|
@@ -180,14 +138,12 @@ export async function loadConfig(dir) {
|
|
|
180
138
|
validateStringList(c.deploy?.apt, "deploy.apt", /^[a-z0-9][a-z0-9.+-]*$/, "a Debian package name", path);
|
|
181
139
|
return { config: c, path };
|
|
182
140
|
}
|
|
183
|
-
/**
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
export function resolveAgentDir(dir, config) {
|
|
190
|
-
return resolve(dir, config.agentDir ?? ".");
|
|
141
|
+
/** The provider prefix of a "provider/modelId" spec. A spec without "/" returns whole — downstream
|
|
142
|
+
* lookups then miss visibly (an unknown-provider error / a login-required hint), never a mangled id
|
|
143
|
+
* (`slice(0, indexOf("/"))` silently drops the last char when "/" is absent). */
|
|
144
|
+
export function providerOf(spec) {
|
|
145
|
+
const slash = spec.indexOf("/");
|
|
146
|
+
return slash > 0 ? spec.slice(0, slash) : spec;
|
|
191
147
|
}
|
|
192
148
|
/** Resolve "provider/modelId" → a pi Model from `models`, so the harness resolves auth from the same collection. */
|
|
193
149
|
export function resolveModel(models, spec) {
|
|
@@ -215,7 +171,7 @@ export function listModels(models) {
|
|
|
215
171
|
/**
|
|
216
172
|
* Rewrite the `model` in a config file's SOURCE TEXT to `spec`, for the first-run picker's write-back.
|
|
217
173
|
* Handles the scaffold's commented placeholder (`// model: "…"`) and an existing `model:` line; returns
|
|
218
|
-
* null when neither is present (
|
|
174
|
+
* null when neither is present (no config file, or a hand-shaped one) so the caller falls back to a
|
|
219
175
|
* printed hint instead of guessing where to insert. Text-level (not AST) on purpose — it only ever
|
|
220
176
|
* touches a line it recognizes, never reformats the author's file.
|
|
221
177
|
*/
|
|
@@ -227,25 +183,19 @@ export function rewriteConfigModel(src, spec) {
|
|
|
227
183
|
return src.replace(commented, line);
|
|
228
184
|
if (active.test(src))
|
|
229
185
|
return src.replace(active, line);
|
|
186
|
+
// No model line at all — the natural state after "picked once, then hand-deleted the line to reset".
|
|
187
|
+
// Re-INSERT at the top of the default-export object while the config still has the scaffold's block
|
|
188
|
+
// shape; anything else (a wrapper call, a one-liner, a computed export) is hand-shaped — leave it
|
|
189
|
+
// untouched (the caller prints the set-it-yourself hint).
|
|
190
|
+
const opener = /^export default[ \t]*\{[ \t]*$/m;
|
|
191
|
+
if (opener.test(src))
|
|
192
|
+
return src.replace(opener, (open) => `${open}\n${line}`);
|
|
230
193
|
return null;
|
|
231
194
|
}
|
|
232
195
|
/** Model selection precedence: CLI flag > FASTAGENT_MODEL env > config default. */
|
|
233
196
|
export function resolveModelSpec(flag, config, env = process.env) {
|
|
234
197
|
return flag ?? env.FASTAGENT_MODEL ?? config.model;
|
|
235
198
|
}
|
|
236
|
-
/**
|
|
237
|
-
* Resolve a user-supplied path override (a CLI flag or an env var) to an absolute path, expanding a
|
|
238
|
-
* leading `~`/`~/` to the home dir FIRST. Path-valued config from `.env` (or any non-shell source)
|
|
239
|
-
* never gets the shell's `~` expansion, so a bare `resolve("~/x")` would silently create a literal `~`
|
|
240
|
-
* directory — a fail-silently footgun for a secret/state path. Expanding here makes `~` mean home
|
|
241
|
-
* everywhere these knobs are read.
|
|
242
|
-
*/
|
|
243
|
-
function resolveOverridePath(raw) {
|
|
244
|
-
if (!raw)
|
|
245
|
-
return undefined;
|
|
246
|
-
const expanded = raw === "~" ? homedir() : raw.startsWith("~/") ? join(homedir(), raw.slice(2)) : raw;
|
|
247
|
-
return resolve(expanded);
|
|
248
|
-
}
|
|
249
199
|
/**
|
|
250
200
|
* `start`'s sessions-dir override: `--sessions-dir` flag > `FASTAGENT_SESSIONS_DIR` env > undefined
|
|
251
201
|
* (the opener then falls back to {@link defaultSessionsDir} under the {@link resolveStateRoot} root).
|
|
@@ -256,43 +206,21 @@ export function resolveSessionsDirOverride(flag, env = process.env) {
|
|
|
256
206
|
}
|
|
257
207
|
/**
|
|
258
208
|
* The auth-file override: `--auth-path` flag > `FASTAGENT_AUTH_PATH` env > undefined (the opener then
|
|
259
|
-
* falls back to {@link defaultAuthPath} under the {@link
|
|
209
|
+
* falls back to {@link defaultAuthPath} under the {@link resolveSecretsDir} dir). Resolved to absolute
|
|
260
210
|
* so the store and the startup report agree regardless of cwd. No implicit project↔global fallback (isolation
|
|
261
211
|
* + fail-visibly; see auth.ts); to share one account across projects, point this at the global
|
|
262
|
-
* `~/.fastagent/auth.json` — sharing ONE file is safe under the store's cross-process refresh lock.
|
|
212
|
+
* `~/.fastagent/.secrets/auth.json` — sharing ONE file is safe under the store's cross-process refresh lock.
|
|
263
213
|
*/
|
|
264
|
-
|
|
214
|
+
function resolveAuthPathOverride(flag, env = process.env) {
|
|
265
215
|
return resolveOverridePath(flag ?? env.FASTAGENT_AUTH_PATH);
|
|
266
216
|
}
|
|
267
|
-
/**
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
*/
|
|
271
|
-
export function projectStateDir(dir) {
|
|
272
|
-
return join(dir, ".fastagent");
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* The resolved state root — the ONE durable machine-state home everything derives from (auth.json,
|
|
276
|
-
* sessions/, channels/<kind>/): `FASTAGENT_STATE_DIR` env > `<dir>/.fastagent`. Absolute, so channels
|
|
277
|
-
* and the startup report agree regardless of cwd. Definition: single lifecycle (precious, survives
|
|
278
|
-
* redeploy), single process — a container mounts ONE volume here. The finer knobs
|
|
279
|
-
* (`FASTAGENT_SESSIONS_DIR`, `FASTAGENT_AUTH_PATH`) still override their specific path on top.
|
|
280
|
-
*
|
|
281
|
-
* `FASTAGENT_STATE_DIR` is an OPERATOR override, so a relative value resolves against `process.cwd()`
|
|
282
|
-
* — the CLI convention its sibling knobs share (`resolveOverridePath`), NOT against `dir`. Only the
|
|
283
|
-
* DEFAULT (`<dir>/.fastagent`) is dir-anchored. Deployments set an absolute path (a mounted volume);
|
|
284
|
-
* a relative value is in-tree — hence self-ignored — only when run from the definition dir (cwd == dir).
|
|
285
|
-
*/
|
|
286
|
-
export function resolveStateRoot(dir, env = process.env) {
|
|
287
|
-
return resolveOverridePath(env.FASTAGENT_STATE_DIR) ?? resolve(projectStateDir(dir));
|
|
288
|
-
}
|
|
289
|
-
/** The default credentials file under a resolved state root ({@link resolveStateRoot}). */
|
|
290
|
-
export function defaultAuthPath(stateRoot) {
|
|
291
|
-
return join(stateRoot, "auth.json");
|
|
217
|
+
/** The default credentials file under a resolved secrets dir ({@link resolveSecretsDir}). */
|
|
218
|
+
export function defaultAuthPath(secretsDir) {
|
|
219
|
+
return join(secretsDir, "auth.json");
|
|
292
220
|
}
|
|
293
|
-
/** The effective auth file for
|
|
221
|
+
/** The effective auth file for an agent: override if present, else `<secrets dir>/auth.json`. */
|
|
294
222
|
export function resolveAuthPath(dir, flag, env = process.env) {
|
|
295
|
-
return resolveAuthPathOverride(flag, env) ?? defaultAuthPath(
|
|
223
|
+
return resolveAuthPathOverride(flag, env) ?? defaultAuthPath(resolveSecretsDir(dir, env));
|
|
296
224
|
}
|
|
297
225
|
/** The default sessions dir under a resolved state root ({@link resolveStateRoot}). */
|
|
298
226
|
export function defaultSessionsDir(stateRoot) {
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import type { AgentTool, ExecutionEnv, Skill, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import type { Provider } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { Models, Provider } from "@earendil-works/pi-ai";
|
|
3
3
|
import type { Agent } from "../../agent.ts";
|
|
4
4
|
import { type FastagentConfig } from "./config.ts";
|
|
5
5
|
import { type LoadedDefinition } from "./definition.ts";
|
|
6
|
+
import { piHarnessFactory } from "./harness.ts";
|
|
6
7
|
import { type PiSessionStore } from "./sessions.ts";
|
|
7
8
|
import type { ModuleLoadFailure } from "../../loader.ts";
|
|
8
9
|
import { type FastagentTool, type ToolCollision } from "./tool.ts";
|
|
9
|
-
import { type Lease } from "./invoke.ts";
|
|
10
|
+
import { type Lease, type SessionObserver } from "./invoke.ts";
|
|
10
11
|
/** pi's core default toolset (read/bash/edit/write), rooted at cwd. */
|
|
11
12
|
export declare function piDefaultTools(cwd: string): AgentTool[];
|
|
12
13
|
/** `config.tools` semantics: extra tools APPENDED after pi's defaults, never replacing them. */
|
|
13
14
|
export declare function resolveTools(config: FastagentConfig, cwd: string): AgentTool[];
|
|
14
15
|
/**
|
|
15
|
-
* The full tool set
|
|
16
|
+
* The full tool set an agent mounts: pi defaults + `config.tools` + discovered `tools/` (deduped,
|
|
16
17
|
* existing win), plus the non-default tool names and collisions to report. One source for the
|
|
17
18
|
* dev/start openers AND `fastagent tool`, so they all mount exactly the same set.
|
|
18
19
|
*/
|
|
19
|
-
export declare function
|
|
20
|
+
export declare function resolveAgentTools(config: FastagentConfig, agentDir: string, cwd?: string): Promise<{
|
|
20
21
|
tools: AgentTool[];
|
|
21
22
|
toolNames: string[];
|
|
22
23
|
/** Tools registered but not initially active (defineTool `deferred: true`) — discovered/activated
|
|
@@ -52,6 +53,17 @@ export interface AssembleSystemPromptOptions {
|
|
|
52
53
|
cwd?: string;
|
|
53
54
|
}
|
|
54
55
|
export declare function assembleSystemPrompt(options: AssembleSystemPromptOptions): string;
|
|
56
|
+
/**
|
|
57
|
+
* INTERNAL seam (workspace ↔ assembly): hands the hub-wiring consumer the assembly's live parts —
|
|
58
|
+
* the SAME models collection, harness factory, and lease the agent runs with — so boundary
|
|
59
|
+
* mutations (session-control.ts) contend on the real lease and validate against the real registry.
|
|
60
|
+
* Called synchronously, exactly once, before the agent is returned. Not part of the public surface.
|
|
61
|
+
*/
|
|
62
|
+
type OnAssembly = (parts: {
|
|
63
|
+
models: Models;
|
|
64
|
+
harnessFactory: ReturnType<typeof piHarnessFactory>;
|
|
65
|
+
lease: Lease;
|
|
66
|
+
}) => void;
|
|
55
67
|
/** L1 options. Tier 1: model (spec) + instructions + tools. Tier 2: the injectable ports. */
|
|
56
68
|
export interface CreatePiAgentOptions {
|
|
57
69
|
/** Model spec "provider/modelId" (e.g. "openai-codex/gpt-5.5"), resolved against {@link models}. */
|
|
@@ -74,8 +86,8 @@ export interface CreatePiAgentOptions {
|
|
|
74
86
|
*/
|
|
75
87
|
providers?: Provider[];
|
|
76
88
|
/**
|
|
77
|
-
* Credentials file for stored OAuth/API-key auth. Defaults to `~/.fastagent/auth.json`; the
|
|
78
|
-
* directory opener passes the project-level `<
|
|
89
|
+
* Credentials file for stored OAuth/API-key auth. Defaults to `~/.fastagent/.secrets/auth.json`; the
|
|
90
|
+
* directory opener passes the project-level `<root>/.secrets/auth.json` instead. Env vars are still
|
|
79
91
|
* consulted when a provider is absent from the file (resolution order is upstream-owned).
|
|
80
92
|
*/
|
|
81
93
|
authPath?: string;
|
|
@@ -86,6 +98,9 @@ export interface CreatePiAgentOptions {
|
|
|
86
98
|
env?: ExecutionEnv;
|
|
87
99
|
/** Single-writer lease. Defaults to in-process fail-fast inProcessLease(). */
|
|
88
100
|
lease?: Lease;
|
|
101
|
+
/** Observation-plane tap (session control): every rich session event of every run. Wire the one
|
|
102
|
+
* returned by `createPiSessionControl` to serve `state`/`entries`/`events` for this agent. */
|
|
103
|
+
observer?: SessionObserver;
|
|
89
104
|
}
|
|
90
105
|
/** L1: assemble from typed parts. */
|
|
91
106
|
export declare function createPiAgent(options: CreatePiAgentOptions): Agent;
|
|
@@ -106,16 +121,16 @@ export interface CreatePiAgentFromDefinitionOptions {
|
|
|
106
121
|
tools?: FastagentTool[];
|
|
107
122
|
/**
|
|
108
123
|
* The agent's working directory: where the default tools operate AND whose ancestors are walked for
|
|
109
|
-
* ② project context (AGENTS.md). Defaults to `dir
|
|
110
|
-
*
|
|
111
|
-
*
|
|
124
|
+
* ② project context (AGENTS.md). Defaults to `dir`. Set it to the enclosing repo so a coding agent
|
|
125
|
+
* whose definition lives in `dir` operates on — and reads the AGENTS.md of — that repo (core.md
|
|
126
|
+
* scenario grid); that is what the CLI's opener does with the workspace.
|
|
112
127
|
*/
|
|
113
128
|
cwd?: string;
|
|
114
129
|
/** Extra providers registered on top of the built-ins (your own gateway / self-hosted endpoint). */
|
|
115
130
|
providers?: Provider[];
|
|
116
131
|
/**
|
|
117
132
|
* Credentials file (see {@link CreatePiAgentOptions.authPath}). Being dir-aware, this rung defaults
|
|
118
|
-
* to the PROJECT-level `<dir>/.
|
|
133
|
+
* to the PROJECT-level `<dir>/.secrets/auth.json` (matching `fastagent dev`/`start` on the same
|
|
119
134
|
* dir) — unlike the dir-less {@link createPiAgent}/{@link createPiModels}, which default global.
|
|
120
135
|
*/
|
|
121
136
|
authPath?: string;
|
|
@@ -124,6 +139,10 @@ export interface CreatePiAgentFromDefinitionOptions {
|
|
|
124
139
|
* context loader remain local today, so injecting this alone does not sandbox a directory agent. */
|
|
125
140
|
env?: ExecutionEnv;
|
|
126
141
|
lease?: Lease;
|
|
142
|
+
/** Observation-plane tap; see {@link CreatePiAgentOptions.observer}. */
|
|
143
|
+
observer?: SessionObserver;
|
|
144
|
+
/** INTERNAL seam for hub wiring; see {@link OnAssembly}. */
|
|
145
|
+
onAssembly?: OnAssembly;
|
|
127
146
|
}
|
|
128
147
|
/**
|
|
129
148
|
* L2: "point at a directory → agent": load + assemble (base + AGENTS.md + skills + env) + L1 in one
|
|
@@ -133,3 +152,4 @@ export declare function createPiAgentFromDefinition(dir: string, options: Create
|
|
|
133
152
|
agent: Agent;
|
|
134
153
|
definition: LoadedDefinition;
|
|
135
154
|
}>;
|
|
155
|
+
export {};
|
|
@@ -6,14 +6,15 @@
|
|
|
6
6
|
* L1 createPiAgent(options) — assemble from typed parts (the canonical ctor).
|
|
7
7
|
* L0 createPiAgentFromHarness({ harnessFactory }) — in invoke.ts (its body is the turn mechanism).
|
|
8
8
|
*
|
|
9
|
-
* Above L2 sits the
|
|
9
|
+
* Above L2 sits the agent opener createPiAgentFromDir (open.ts), which both `dev` and
|
|
10
10
|
* `start` drive. Each rung calls the one below; options narrow as you go up (L2 owns systemPrompt/skills —
|
|
11
11
|
* they come from the definition; the openers own model/tools — from config resolution).
|
|
12
12
|
*/
|
|
13
13
|
import { formatSkillsForSystemPrompt } from "@earendil-works/pi-agent-core";
|
|
14
14
|
import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node";
|
|
15
15
|
import { createCodingTools } from "@earendil-works/pi-coding-agent";
|
|
16
|
-
import { defaultAuthPath, resolveModel
|
|
16
|
+
import { defaultAuthPath, resolveModel } from "./config.js";
|
|
17
|
+
import { resolveSecretsDir } from "../../paths.js";
|
|
17
18
|
import { loadAgentDefinition } from "./definition.js";
|
|
18
19
|
import { piHarnessFactory } from "./harness.js";
|
|
19
20
|
import { createPiModels } from "./models.js";
|
|
@@ -21,7 +22,7 @@ import { reportDefinitionWarnings } from "./report.js";
|
|
|
21
22
|
import { inMemorySessionStore } from "./sessions.js";
|
|
22
23
|
import { isDeferredTool, loadTools, mergeDiscoveredTools, } from "./tool.js";
|
|
23
24
|
import { withSearchTool } from "./search-tools.js";
|
|
24
|
-
import { createPiAgentFromHarness } from "./invoke.js";
|
|
25
|
+
import { createPiAgentFromHarness, inProcessLease } from "./invoke.js";
|
|
25
26
|
// ── §1 tools ─────────────────────────────────────────────────────────────────
|
|
26
27
|
//
|
|
27
28
|
// The full pi toolset is the default for fidelity: authors vibe in local pi with it, so serving with
|
|
@@ -37,18 +38,18 @@ export function resolveTools(config, cwd) {
|
|
|
37
38
|
return config.tools ? [...defaults, ...config.tools] : defaults;
|
|
38
39
|
}
|
|
39
40
|
/**
|
|
40
|
-
* The full tool set
|
|
41
|
+
* The full tool set an agent mounts: pi defaults + `config.tools` + discovered `tools/` (deduped,
|
|
41
42
|
* existing win), plus the non-default tool names and collisions to report. One source for the
|
|
42
43
|
* dev/start openers AND `fastagent tool`, so they all mount exactly the same set.
|
|
43
44
|
*/
|
|
44
|
-
export async function
|
|
45
|
-
// Default coding tools (read/bash/edit/write) are rooted at `cwd` (the
|
|
46
|
-
// discovered `tools/` come from `agentDir` (the agent's own surface).
|
|
45
|
+
export async function resolveAgentTools(config, agentDir, cwd = agentDir) {
|
|
46
|
+
// Default coding tools (read/bash/edit/write) are rooted at `cwd` (the workspace the agent operates
|
|
47
|
+
// on); discovered `tools/` come from `agentDir` (the agent's own surface).
|
|
47
48
|
const discovered = await loadTools(agentDir);
|
|
48
49
|
const merged = mergeDiscoveredTools(resolveTools(config, cwd), discovered.tools);
|
|
49
|
-
// The built-in `search_tools` loader mounts here — the one place the
|
|
50
|
-
// computed — so `dev`/`start`/`info`/`fastagent tool` all see the same surface (idempotent;
|
|
51
|
-
//
|
|
50
|
+
// The built-in `search_tools` loader mounts here — the one place the agent's full tool set is
|
|
51
|
+
// computed — so `dev`/`start`/`info`/`fastagent tool` all see the same surface (idempotent; an
|
|
52
|
+
// agent-defined search_tools wins).
|
|
52
53
|
const tools = withSearchTool(merged.tools);
|
|
53
54
|
// Builtin = a search_tools that was ABSENT before withSearchTool (a reference compare would misfire
|
|
54
55
|
// on the deferred-authored-loader case, where withSearchTool returns a new array without adding one).
|
|
@@ -98,7 +99,7 @@ export function piBasePrompt(options = {}) {
|
|
|
98
99
|
const deferredCount = mounted.length - tools.length;
|
|
99
100
|
const toolsList = tools.length > 0 ? tools.map((t) => `- ${t.name}: ${(t.description ?? "").split("\n")[0]}`).join("\n") : "(none)";
|
|
100
101
|
// Segment ① identity: an authored persona (persona.md) replaces the default engine identity line
|
|
101
|
-
// (
|
|
102
|
+
// (core.md §11), keeping the tools list + guidelines below.
|
|
102
103
|
const identity = options.persona?.trim() ||
|
|
103
104
|
"You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.";
|
|
104
105
|
const deferredNote = deferredCount > 0
|
|
@@ -141,20 +142,23 @@ export function assembleSystemPrompt(options) {
|
|
|
141
142
|
*/
|
|
142
143
|
function buildPiAgent(opts) {
|
|
143
144
|
const models = createPiModels({ providers: opts.providers, authPath: opts.authPath });
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
const env = opts.env ?? new NodeExecutionEnv({ cwd: process.cwd() });
|
|
146
|
+
// Materialized here (not defaulted inside createPiAgentFromHarness) so the exposed parts carry
|
|
147
|
+
// the SAME lease instance the agent runs under — boundary mutations must contend on it.
|
|
148
|
+
const lease = opts.lease ?? inProcessLease();
|
|
149
|
+
const harnessFactory = piHarnessFactory({
|
|
150
|
+
sessions: opts.sessions ?? inMemorySessionStore(),
|
|
151
|
+
env,
|
|
152
|
+
models,
|
|
153
|
+
model: resolveModel(models, opts.model),
|
|
154
|
+
thinkingLevel: opts.thinkingLevel,
|
|
155
|
+
systemPrompt: opts.systemPrompt,
|
|
156
|
+
tools: opts.tools,
|
|
157
|
+
skills: opts.skills,
|
|
158
|
+
live: opts.live,
|
|
157
159
|
});
|
|
160
|
+
opts.onAssembly?.({ models, harnessFactory, lease });
|
|
161
|
+
return createPiAgentFromHarness({ lease, observer: opts.observer, cwd: env.cwd, harnessFactory });
|
|
158
162
|
}
|
|
159
163
|
/**
|
|
160
164
|
* L1 system prompt: `instructions` ARE the prompt (no engine base, no wrapping); the skills listing
|
|
@@ -185,6 +189,7 @@ export function createPiAgent(options) {
|
|
|
185
189
|
sessions: options.sessions,
|
|
186
190
|
env: options.env,
|
|
187
191
|
lease: options.lease,
|
|
192
|
+
observer: options.observer,
|
|
188
193
|
});
|
|
189
194
|
}
|
|
190
195
|
/** Stable identity of a definition's non-fatal findings, for change-detection in `live` (dedup only). */
|
|
@@ -216,9 +221,9 @@ export async function createPiAgentFromDefinition(dir, options) {
|
|
|
216
221
|
model: options.model,
|
|
217
222
|
thinkingLevel: options.thinkingLevel,
|
|
218
223
|
providers: options.providers,
|
|
219
|
-
// Dir-aware default: the same
|
|
224
|
+
// Dir-aware default: the same secrets-dir-derived file the opener uses for this dir (the opener
|
|
220
225
|
// passes an explicit authPath, so this only affects direct L2 callers).
|
|
221
|
-
authPath: options.authPath ?? defaultAuthPath(
|
|
226
|
+
authPath: options.authPath ?? defaultAuthPath(resolveSecretsDir(dir)),
|
|
222
227
|
// The directory is the agent, LIVE: re-read the definition on every invoke, so AGENTS.md/skills
|
|
223
228
|
// edits (the author's, or the agent's own self-modification) take effect on the next turn with
|
|
224
229
|
// no process restart — restarts are reserved for code (tools/channels/config, module cache).
|
|
@@ -253,6 +258,8 @@ export async function createPiAgentFromDefinition(dir, options) {
|
|
|
253
258
|
sessions: options.sessions,
|
|
254
259
|
env,
|
|
255
260
|
lease: options.lease,
|
|
261
|
+
observer: options.observer,
|
|
262
|
+
onAssembly: options.onAssembly,
|
|
256
263
|
});
|
|
257
264
|
return { agent, definition };
|
|
258
265
|
}
|