@fastagent-sh/fastagent 0.17.1 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -7
- package/dist/agent.d.ts +12 -1
- package/dist/agent.js +1 -1
- package/dist/atomic-write.d.ts +19 -0
- package/dist/atomic-write.js +48 -0
- package/dist/{host/node.d.ts → channel.d.ts} +24 -28
- package/dist/channel.js +1 -0
- package/dist/channels/agentcore-service.d.ts +54 -0
- package/dist/channels/agentcore-service.js +111 -0
- package/dist/channels/agentcore.d.ts +12 -2
- package/dist/channels/agentcore.js +6 -4
- package/dist/channels/busy.d.ts +1 -1
- package/dist/channels/busy.js +1 -1
- package/dist/channels/control.d.ts +16 -6
- package/dist/channels/control.js +106 -4
- package/dist/{engines/pi/channel.d.ts → channels/discover.d.ts} +2 -2
- package/dist/{engines/pi/channel.js → channels/discover.js} +8 -12
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +1 -1
- package/dist/channels/feishu/context-buffer.js +4 -4
- package/dist/channels/feishu/feishu-api.d.ts +4 -2
- package/dist/channels/feishu/feishu-api.js +1 -1
- package/dist/channels/feishu/feishu.d.ts +1 -1
- package/dist/channels/feishu/feishu.js +45 -15
- package/dist/channels/feishu/invoke-turn.d.ts +9 -3
- package/dist/channels/feishu/invoke-turn.js +152 -34
- package/dist/channels/feishu/parse.js +6 -0
- package/dist/channels/feishu/preview.d.ts +1 -1
- package/dist/channels/feishu/preview.js +7 -3
- package/dist/channels/feishu/ws-ingress.d.ts +1 -1
- package/dist/channels/github/github.d.ts +1 -1
- package/dist/channels/http.d.ts +11 -7
- package/dist/channels/http.js +16 -111
- package/dist/channels/{context-buffer.js → kit/context-buffer.js} +2 -2
- package/dist/channels/{invoke-turn-kit.d.ts → kit/invoke-turn-kit.d.ts} +18 -4
- package/dist/channels/{invoke-turn-kit.js → kit/invoke-turn-kit.js} +23 -8
- package/dist/channels/{preview-kit.d.ts → kit/preview-kit.d.ts} +3 -1
- package/dist/channels/{seen.js → kit/seen.js} +1 -1
- package/dist/channels/kit/state.d.ts +6 -0
- package/dist/channels/{state.js → kit/state.js} +4 -24
- package/dist/channels/{stop-command.d.ts → kit/stop-command.d.ts} +1 -1
- package/dist/channels/{stop-command.js → kit/stop-command.js} +2 -2
- package/dist/channels/{tasks.js → kit/tasks.js} +1 -1
- package/dist/channels/{thread-participants.d.ts → kit/thread-participants.d.ts} +7 -0
- package/dist/channels/{thread-participants.js → kit/thread-participants.js} +4 -1
- package/dist/channels/{turn-queue.js → kit/turn-queue.js} +2 -2
- package/dist/channels/{turn-store.js → kit/turn-store.js} +1 -1
- package/dist/channels/lark/lark.d.ts +1 -1
- package/dist/channels/serve.d.ts +73 -0
- package/dist/channels/serve.js +243 -0
- package/dist/channels/slack/bot-auth.js +3 -14
- package/dist/channels/slack/context-buffer.d.ts +2 -2
- package/dist/channels/slack/context-buffer.js +2 -2
- package/dist/channels/slack/invoke-turn.d.ts +1 -1
- package/dist/channels/slack/invoke-turn.js +9 -6
- package/dist/channels/slack/parse.js +2 -2
- package/dist/channels/slack/preview.d.ts +1 -1
- package/dist/channels/slack/preview.js +11 -3
- package/dist/channels/slack/slack-api.js +1 -1
- package/dist/channels/slack/slack.d.ts +1 -1
- package/dist/channels/slack/slack.js +8 -12
- package/dist/channels/slack/welcomed.js +1 -1
- package/dist/channels/telegram/context-buffer.d.ts +2 -2
- package/dist/channels/telegram/context-buffer.js +2 -2
- package/dist/channels/telegram/invoke-turn.d.ts +1 -1
- package/dist/channels/telegram/invoke-turn.js +2 -2
- package/dist/channels/telegram/parse.js +2 -2
- package/dist/channels/telegram/preview.d.ts +1 -1
- package/dist/channels/telegram/preview.js +6 -2
- package/dist/channels/telegram/telegram.d.ts +1 -1
- package/dist/channels/telegram/telegram.js +3 -3
- package/dist/channels/telegram/turn-store.d.ts +2 -2
- package/dist/channels/telegram/turn-store.js +2 -2
- package/dist/cli/commands/add.js +1 -1
- package/dist/cli/commands/deploy.js +13 -5
- package/dist/cli/commands/dev.js +26 -14
- package/dist/cli/commands/fire.js +2 -2
- package/dist/cli/commands/info.js +28 -5
- package/dist/cli/commands/invoke.js +1 -1
- package/dist/cli/commands/schedule.js +1 -1
- package/dist/cli/commands/start.js +64 -95
- package/dist/cli/commands/tool.js +5 -7
- package/dist/cli/program.js +1 -1
- package/dist/cli/serve.d.ts +18 -57
- package/dist/cli/serve.js +57 -243
- package/dist/cli/shared.d.ts +5 -3
- package/dist/cli/shared.js +14 -7
- package/dist/collect.d.ts +30 -4
- package/dist/collect.js +39 -6
- package/dist/core.d.ts +3 -5
- package/dist/core.js +7 -5
- package/dist/deploy/preflight.d.ts +8 -2
- package/dist/deploy/preflight.js +22 -4
- package/dist/deploy/secrets.d.ts +3 -0
- package/dist/deploy/secrets.js +6 -0
- package/dist/dev-supervisor.js +16 -4
- package/dist/engines/pi/agent-session-factory.d.ts +104 -0
- package/dist/engines/pi/agent-session-factory.js +314 -0
- package/dist/engines/pi/config.d.ts +4 -4
- package/dist/engines/pi/config.js +1 -1
- package/dist/engines/pi/create.d.ts +56 -42
- package/dist/engines/pi/create.js +150 -75
- package/dist/engines/pi/definition.d.ts +41 -7
- package/dist/engines/pi/definition.js +115 -11
- package/dist/engines/pi/invoke-session.d.ts +35 -0
- package/dist/engines/pi/invoke-session.js +357 -0
- package/dist/engines/pi/login.js +7 -4
- package/dist/engines/pi/models.d.ts +59 -11
- package/dist/engines/pi/models.js +64 -9
- package/dist/engines/pi/open.d.ts +9 -6
- package/dist/engines/pi/open.js +20 -10
- package/dist/engines/pi/report.d.ts +0 -4
- package/dist/engines/pi/report.js +0 -7
- package/dist/engines/pi/service.d.ts +23 -0
- package/dist/engines/pi/service.js +19 -0
- package/dist/engines/pi/session-builder.js +88 -90
- package/dist/engines/pi/session-control.d.ts +11 -9
- package/dist/engines/pi/session-control.js +114 -77
- package/dist/engines/pi/session-inheritance.d.ts +44 -0
- package/dist/engines/pi/session-inheritance.js +242 -0
- package/dist/engines/pi/session-settings.d.ts +14 -2
- package/dist/engines/pi/session-settings.js +18 -0
- package/dist/engines/pi/session-store.d.ts +70 -0
- package/dist/engines/pi/session-store.js +313 -0
- package/dist/engines/pi/tool-context.d.ts +18 -8
- package/dist/engines/pi/tool-context.js +23 -1
- package/dist/engines/pi/tool.d.ts +5 -5
- package/dist/engines/pi/tool.js +1 -1
- package/dist/engines/pi/{invoke.d.ts → turn-kit.d.ts} +46 -45
- package/dist/engines/pi/turn-kit.js +188 -0
- package/dist/engines/pi/wake-tool.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/log.d.ts +3 -0
- package/dist/log.js +5 -1
- package/dist/node.d.ts +16 -0
- package/dist/node.js +20 -0
- package/dist/paths.d.ts +13 -0
- package/dist/paths.js +16 -0
- package/dist/pi.d.ts +15 -8
- package/dist/pi.js +7 -6
- package/dist/scaffold/templates/fastagent.config.mjs +2 -0
- package/dist/scaffold/templates/persona.md +2 -2
- package/dist/schedule/state.js +4 -9
- package/dist/schedule/wakeups.d.ts +1 -1
- package/dist/schedule/wakeups.js +1 -1
- package/dist/service.d.ts +158 -0
- package/dist/service.js +354 -0
- package/dist/session-remote.d.ts +1 -1
- package/dist/session-remote.js +10 -2
- package/dist/session.d.ts +4 -2
- package/dist/session.js +1 -1
- package/package.json +14 -5
- package/dist/channels/state.d.ts +0 -16
- package/dist/engines/pi/harness.d.ts +0 -112
- package/dist/engines/pi/harness.js +0 -153
- package/dist/engines/pi/invoke.js +0 -618
- package/dist/engines/pi/read-image.d.ts +0 -4
- package/dist/engines/pi/read-image.js +0 -62
- package/dist/engines/pi/sessions.d.ts +0 -44
- package/dist/engines/pi/sessions.js +0 -182
- package/dist/host/node.js +0 -52
- /package/dist/channels/{context-buffer.d.ts → kit/context-buffer.d.ts} +0 -0
- /package/dist/channels/{preview-kit.js → kit/preview-kit.js} +0 -0
- /package/dist/channels/{seen.d.ts → kit/seen.d.ts} +0 -0
- /package/dist/channels/{tasks.d.ts → kit/tasks.d.ts} +0 -0
- /package/dist/channels/{text.d.ts → kit/text.d.ts} +0 -0
- /package/dist/channels/{text.js → kit/text.js} +0 -0
- /package/dist/channels/{turn-queue.d.ts → kit/turn-queue.d.ts} +0 -0
- /package/dist/channels/{turn-store.d.ts → kit/turn-store.d.ts} +0 -0
|
@@ -1,24 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import type { ExecutionEnv, Skill, ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { 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 { type AnyModel, piHarnessFactory } from "./harness.ts";
|
|
7
|
-
import { type PiSessionStore } from "./sessions.ts";
|
|
8
6
|
import type { ModuleLoadFailure } from "../../loader.ts";
|
|
9
7
|
import { type ToolCollision, type MountedTool } from "./tool.ts";
|
|
10
|
-
import { type
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export declare function resolveTools(config: FastagentConfig): MountedTool[];
|
|
8
|
+
import { type PiAgentSessionFactory } from "./invoke-session.ts";
|
|
9
|
+
import { type AnyModel } from "./models.ts";
|
|
10
|
+
import type { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { type PiSessionRecordStore } from "./session-store.ts";
|
|
12
|
+
import { type Lease, type SessionObserver } from "./turn-kit.ts";
|
|
16
13
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* Every pi coding tool, in canonical order, rooted at the workspace it operates in.
|
|
15
|
+
*
|
|
16
|
+
* pi ships two overlapping groupings and neither is the whole set: `createCodingTools` is the four it
|
|
17
|
+
* ACTIVATES for a terminal (read/bash/edit/write, no searching), `createReadOnlyTools` is
|
|
18
|
+
* read/grep/find/ls. `read` is in both; a directory agent mounts their union.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CODING_TOOL_NAMES: readonly ["read", "grep", "find", "ls", "bash", "edit", "write"];
|
|
21
|
+
export declare function piAllCodingTools(cwd: string): MountedTool[];
|
|
22
|
+
/**
|
|
23
|
+
* The full directory-agent tool set: all pi coding tools + `config.tools` + discovered `tools/`
|
|
24
|
+
* (deduped, existing win), plus the authored names and collisions to report. One source for the
|
|
19
25
|
* dev/start openers AND `fastagent tool`, so they all mount exactly the same set.
|
|
20
26
|
*/
|
|
21
|
-
export declare function resolveAgentTools(config: FastagentConfig, agentDir: string): Promise<{
|
|
27
|
+
export declare function resolveAgentTools(config: FastagentConfig, agentDir: string, cwd: string): Promise<{
|
|
22
28
|
tools: MountedTool[];
|
|
23
29
|
toolNames: string[];
|
|
24
30
|
/** Tools registered but not initially active (defineTool `deferred: true`) — discovered/activated
|
|
@@ -56,20 +62,22 @@ export interface AssembleSystemPromptOptions {
|
|
|
56
62
|
export declare function assembleSystemPrompt(options: AssembleSystemPromptOptions): string;
|
|
57
63
|
/**
|
|
58
64
|
* INTERNAL seam (workspace ↔ assembly): hands the hub-wiring consumer the assembly's live parts —
|
|
59
|
-
* the SAME
|
|
60
|
-
* mutations (session-control.ts) contend on the real lease and validate against the real
|
|
61
|
-
* Called synchronously, exactly once, before the agent is returned. Not
|
|
65
|
+
* the SAME session factory and lease the agent runs with, plus the model registry behind a thunk —
|
|
66
|
+
* so boundary mutations (session-control.ts) contend on the real lease and validate against the real
|
|
67
|
+
* registry. Called synchronously, exactly once, before the agent is returned. Not public surface.
|
|
62
68
|
*/
|
|
63
|
-
type
|
|
64
|
-
models: Models;
|
|
65
|
-
harnessFactory: ReturnType<typeof piHarnessFactory>;
|
|
69
|
+
export type PiAssemblyParts = {
|
|
66
70
|
lease: Lease;
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
sessionFactory: PiAgentSessionFactory;
|
|
72
|
+
/** The registry and configured model, resolved on first use (a credential read is async). */
|
|
73
|
+
engine: () => Promise<{
|
|
74
|
+
modelRuntime: ModelRuntime;
|
|
69
75
|
model: AnyModel;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
}>;
|
|
77
|
+
/** The configured reasoning effort — the other half of the pair a session without overrides runs on. */
|
|
78
|
+
thinkingLevel: ThinkingLevel;
|
|
79
|
+
};
|
|
80
|
+
type OnAssembly = (parts: PiAssemblyParts) => void;
|
|
73
81
|
/** L1 options. Tier 1: model (spec) + instructions + tools. Tier 2: the injectable ports. */
|
|
74
82
|
export interface CreatePiAgentOptions {
|
|
75
83
|
/** Model spec "provider/modelId" (e.g. "openai-codex/gpt-5.5"), resolved against {@link models}. */
|
|
@@ -77,14 +85,18 @@ export interface CreatePiAgentOptions {
|
|
|
77
85
|
/** Reasoning effort (pi's scale). Unset = pi's default; unsupported levels are clamped per model. */
|
|
78
86
|
thinkingLevel?: ThinkingLevel;
|
|
79
87
|
/**
|
|
80
|
-
* The system prompt itself —
|
|
81
|
-
*
|
|
82
|
-
*
|
|
88
|
+
* The system prompt itself — no engine base and no wrapping (unlike the directory path, which
|
|
89
|
+
* assembles the engine base + AGENTS.md as segment ② + persona.md as segment ①). A plain string or
|
|
90
|
+
* a factory re-evaluated per invoke. When {@link skills} are mounted their listing is appended.
|
|
91
|
+
*
|
|
92
|
+
* Not byte-for-byte verbatim: pi appends its own `Current working directory:` line to whatever
|
|
93
|
+
* prompt it is given. What this rung guarantees is that no engine IDENTITY is imposed — a
|
|
94
|
+
* hand-built agent is not told it is a coding assistant.
|
|
83
95
|
*/
|
|
84
96
|
instructions?: string | (() => string);
|
|
85
97
|
/** The tool set to mount. `FastagentTool` (AgentTool plus the optional `deferred` marker, see
|
|
86
98
|
* {@link DefineToolOptions}) widens into {@link MountedTool}, which additionally admits pi's default
|
|
87
|
-
* coding tools —
|
|
99
|
+
* coding tools — those bind their workspace at construction rather than reading a turn context. */
|
|
88
100
|
tools?: MountedTool[];
|
|
89
101
|
skills?: Skill[];
|
|
90
102
|
/**
|
|
@@ -99,12 +111,15 @@ export interface CreatePiAgentOptions {
|
|
|
99
111
|
* consulted when a provider is absent from the file (resolution order is upstream-owned).
|
|
100
112
|
*/
|
|
101
113
|
authPath?: string;
|
|
102
|
-
/** Session persistence. Defaults to in-memory; inject
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
*
|
|
107
|
-
* NOT constrain
|
|
114
|
+
/** Session persistence. Defaults to in-memory; inject piSessionRecordStore for restart-surviving
|
|
115
|
+
* continuity. */
|
|
116
|
+
sessions?: PiSessionRecordStore;
|
|
117
|
+
/** Filesystem/process environment, handed to tools that read one as the turn's context. Defaults to
|
|
118
|
+
* a local NodeExecutionEnv at `process.cwd()`. At THIS rung nothing else consumes it: L1 loads no
|
|
119
|
+
* definition. It does NOT constrain the coding tools (pi's own, rooted at the workspace they were
|
|
120
|
+
* built for) or author-written `tools/`, which are code and can import anything. Not a
|
|
121
|
+
* sandbox — see {@link createPiAgentFromDefinition} for the rung where it also reads the
|
|
122
|
+
* definition. */
|
|
108
123
|
env?: ExecutionEnv;
|
|
109
124
|
/** Single-writer lease. Defaults to in-process fail-fast inProcessLease(). */
|
|
110
125
|
lease?: Lease;
|
|
@@ -126,11 +141,11 @@ export interface CreatePiAgentFromDefinitionOptions {
|
|
|
126
141
|
/** Override the engine base prompt (segment ①). Defaults to piBasePrompt({ tools, persona }) using the
|
|
127
142
|
* live-read persona.md; pass base to fully opt out of persona.md. */
|
|
128
143
|
base?: string;
|
|
129
|
-
/** Override tools. Defaults to {@link
|
|
144
|
+
/** Override tools. Defaults to {@link piAllCodingTools} (lock down with a custom list). An authored
|
|
130
145
|
* `FastagentTool[]` (AgentTool plus the optional `deferred` marker) widens into {@link MountedTool}. */
|
|
131
146
|
tools?: MountedTool[];
|
|
132
147
|
/**
|
|
133
|
-
* The agent's working directory: where the
|
|
148
|
+
* The agent's working directory: where the coding tools operate AND whose ancestors are walked for
|
|
134
149
|
* ② project context (AGENTS.md). Defaults to `dir`. Set it to the enclosing repo so a coding agent
|
|
135
150
|
* whose definition lives in `dir` operates on — and reads the AGENTS.md of — that repo (core.md
|
|
136
151
|
* scenario grid); that is what the CLI's opener does with the workspace.
|
|
@@ -144,12 +159,11 @@ export interface CreatePiAgentFromDefinitionOptions {
|
|
|
144
159
|
* dir) — unlike the dir-less {@link createPiAgent}/{@link createPiModels}, which default global.
|
|
145
160
|
*/
|
|
146
161
|
authPath?: string;
|
|
147
|
-
sessions?:
|
|
148
|
-
/** Filesystem/process environment; see {@link CreatePiAgentOptions.env}. At THIS rung it
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* env narrows the blast radius rather than closing it. */
|
|
162
|
+
sessions?: PiSessionRecordStore;
|
|
163
|
+
/** Filesystem/process environment; see {@link CreatePiAgentOptions.env}. At THIS rung it reads
|
|
164
|
+
* persona.md and skills/. The seven coding tools, ② project context (pi's
|
|
165
|
+
* loadProjectContextFiles uses node fs directly), and author-written `tools/` stay outside it.
|
|
166
|
+
* Injecting an env narrows the blast radius rather than closing it. */
|
|
153
167
|
env?: ExecutionEnv;
|
|
154
168
|
lease?: Lease;
|
|
155
169
|
/** Observation-plane tap; see {@link CreatePiAgentOptions.observer}. */
|
|
@@ -4,72 +4,97 @@
|
|
|
4
4
|
*
|
|
5
5
|
* L2 createPiAgentFromDefinition(dir, options) — load a definition directory, assemble, then L1.
|
|
6
6
|
* L1 createPiAgent(options) — assemble from typed parts (the canonical ctor).
|
|
7
|
-
* L0
|
|
7
|
+
* L0 createPiAgentFromSession({ sessionFactory }) — in invoke-session.ts (the turn mechanism).
|
|
8
8
|
*
|
|
9
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
|
-
import { createBashTool, createEditTool, createReadTool, createWriteTool, } from "@earendil-works/pi-agent-core";
|
|
15
14
|
import { NodeExecutionEnv } from "@earendil-works/pi-agent-core/node";
|
|
16
|
-
import {
|
|
15
|
+
import { createCodingTools, createPowerShellTool, createReadOnlyTools } from "@earendil-works/pi-coding-agent";
|
|
17
16
|
import { defaultAuthPath, resolveModel } from "./config.js";
|
|
18
17
|
import { resolveSecretsDir } from "../../paths.js";
|
|
19
|
-
import { loadAgentDefinition } from "./definition.js";
|
|
20
|
-
import { DEFAULT_THINKING_LEVEL, piHarnessFactory } from "./harness.js";
|
|
21
|
-
import { createPiModels } from "./models.js";
|
|
18
|
+
import { loadAgentDefinition, loadExtensionPaths } from "./definition.js";
|
|
22
19
|
import { reportFindingsIfChanged } from "./report.js";
|
|
23
|
-
import { inMemorySessionStore } from "./sessions.js";
|
|
24
20
|
import { isDeferredTool, loadTools, mergeDiscoveredTools, } from "./tool.js";
|
|
25
21
|
import { withSearchTool } from "./search-tools.js";
|
|
26
|
-
import {
|
|
22
|
+
import { createPiAgentFromSession } from "./invoke-session.js";
|
|
23
|
+
import { piAgentSessionFactory } from "./agent-session-factory.js";
|
|
24
|
+
import { DEFAULT_THINKING_LEVEL, createPiModelRuntime } from "./models.js";
|
|
25
|
+
import { piInMemorySessionRecordStore } from "./session-store.js";
|
|
26
|
+
import { inProcessLease } from "./turn-kit.js";
|
|
27
27
|
// ── §1 tools ─────────────────────────────────────────────────────────────────
|
|
28
28
|
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
29
|
+
// A directory agent gets every pi coding tool. Restricting what those tools can reach belongs at the
|
|
30
|
+
// sandbox boundary, not in an allowlist that leaves authored tools unrestricted. L1/L2 library callers
|
|
31
|
+
// can still replace the coding defaults through `tools`; deferred tools may add `search_tools`.
|
|
32
32
|
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// that governed everything except the tools that actually touch the machine. (It buys no decoupling
|
|
38
|
-
// from pi-coding-agent — definition.ts, models.ts and read-image.ts all import it regardless.)
|
|
33
|
+
// This is not a security boundary and no default here could be one: the built-in POST /invoke has no
|
|
34
|
+
// authentication, author-written `tools/` import whatever they like, and a WebSocket or Socket-Mode
|
|
35
|
+
// channel dials OUT, so no bind address constrains who can message the agent. Whoever can reach an
|
|
36
|
+
// agent can use everything it mounts — put it behind something that decides who may.
|
|
39
37
|
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
38
|
+
// All seven come from pi-coding-agent and reach the machine through `node:fs`, bypassing the per-turn
|
|
39
|
+
// {@link ExecutionEnv}. pi-agent-core ships env-routed look-alikes, but that seam narrows a blast
|
|
40
|
+
// radius rather than closing one (authored `tools/` ignore it), so a real sandbox must wrap the
|
|
41
|
+
// process instead.
|
|
43
42
|
//
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
// Chat receives the same tool objects through pi's `customTools` path; pi's builtin copies stay off.
|
|
44
|
+
/**
|
|
45
|
+
* Every pi coding tool, in canonical order, rooted at the workspace it operates in.
|
|
46
|
+
*
|
|
47
|
+
* pi ships two overlapping groupings and neither is the whole set: `createCodingTools` is the four it
|
|
48
|
+
* ACTIVATES for a terminal (read/bash/edit/write, no searching), `createReadOnlyTools` is
|
|
49
|
+
* read/grep/find/ls. `read` is in both; a directory agent mounts their union.
|
|
50
|
+
*/
|
|
51
|
+
export const CODING_TOOL_NAMES = ["read", "grep", "find", "ls", "bash", "edit", "write"];
|
|
52
|
+
export function piAllCodingTools(cwd) {
|
|
53
|
+
const mutating = createCodingTools(cwd).filter((tool) => tool.name !== "read");
|
|
54
|
+
return [...createReadOnlyTools(cwd), ...mutating];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Every tool an `AgentSession` registers on its own, whether or not a directory agent mounts it.
|
|
58
|
+
*
|
|
59
|
+
* Wider than {@link CODING_TOOL_NAMES} on purpose: pi 0.84.3 added `powershell`, which the session
|
|
60
|
+
* registers regardless of the tools we pass. A name absent from THIS list is a name never excluded,
|
|
61
|
+
* and an excluded name is the only thing that cannot be activated — so leaving it out would let
|
|
62
|
+
* `tools: [read]` keep a reachable shell.
|
|
63
|
+
*/
|
|
64
|
+
function piRegisteredToolNames(cwd) {
|
|
50
65
|
return [
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
createWriteTool(),
|
|
66
|
+
...createReadOnlyTools(cwd).map((tool) => tool.name),
|
|
67
|
+
...createCodingTools(cwd).map((tool) => tool.name),
|
|
68
|
+
createPowerShellTool(cwd).name,
|
|
55
69
|
];
|
|
56
70
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
return
|
|
71
|
+
/** Built-ins omitted by an explicit lower-level tool list. A reused name stays mounted. */
|
|
72
|
+
function omittedBuiltinNames(mounted, cwd) {
|
|
73
|
+
const mountedNames = new Set(mounted.map((tool) => tool.name));
|
|
74
|
+
return [...new Set(piRegisteredToolNames(cwd))].filter((name) => !mountedNames.has(name));
|
|
61
75
|
}
|
|
62
76
|
/**
|
|
63
|
-
* The full tool set
|
|
64
|
-
* existing win), plus the
|
|
77
|
+
* The full directory-agent tool set: all pi coding tools + `config.tools` + discovered `tools/`
|
|
78
|
+
* (deduped, existing win), plus the authored names and collisions to report. One source for the
|
|
65
79
|
* dev/start openers AND `fastagent tool`, so they all mount exactly the same set.
|
|
66
80
|
*/
|
|
67
|
-
export async function resolveAgentTools(config, agentDir) {
|
|
68
|
-
// Discovered `tools/` come from `agentDir` (the agent's own surface); the
|
|
69
|
-
//
|
|
70
|
-
// the
|
|
81
|
+
export async function resolveAgentTools(config, agentDir, cwd) {
|
|
82
|
+
// Discovered `tools/` come from `agentDir` (the agent's own surface); the coding tools are
|
|
83
|
+
// rooted at `cwd`, the WORKSPACE — the project the agent works on, which is where an author expects
|
|
84
|
+
// `read`/`bash` to land, and which is not always the definition directory.
|
|
71
85
|
const discovered = await loadTools(agentDir);
|
|
72
|
-
const
|
|
86
|
+
const configured = piAllCodingTools(cwd);
|
|
87
|
+
const configuredNames = new Set(configured.map((tool) => tool.name));
|
|
88
|
+
const configuredCollisions = [];
|
|
89
|
+
for (const tool of config.tools ?? []) {
|
|
90
|
+
if (configuredNames.has(tool.name)) {
|
|
91
|
+
configuredCollisions.push({ name: tool.name, source: "config.tools" });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
configuredNames.add(tool.name);
|
|
95
|
+
configured.push(tool);
|
|
96
|
+
}
|
|
97
|
+
const merged = mergeDiscoveredTools(configured, discovered.tools);
|
|
73
98
|
// The built-in `search_tools` loader mounts here — the one place the agent's full tool set is
|
|
74
99
|
// computed — so `dev`/`start`/`info`/`fastagent tool` all see the same surface (idempotent; an
|
|
75
100
|
// agent-defined search_tools wins).
|
|
@@ -77,12 +102,12 @@ export async function resolveAgentTools(config, agentDir) {
|
|
|
77
102
|
// Builtin = a search_tools that was ABSENT before withSearchTool (a reference compare would misfire
|
|
78
103
|
// on the deferred-authored-loader case, where withSearchTool returns a new array without adding one).
|
|
79
104
|
const builtinLoaderMounted = !merged.tools.some((t) => t.name === "search_tools") && tools.some((t) => t.name === "search_tools");
|
|
80
|
-
const toolCollisions = [...discovered.collisions, ...merged.collisions];
|
|
81
|
-
// `toolNames` is the AUTHOR's active-by-default surface (config.tools + tools/): exclude pi
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
const defaultNames = new Set(
|
|
105
|
+
const toolCollisions = [...discovered.collisions, ...configuredCollisions, ...merged.collisions];
|
|
106
|
+
// `toolNames` is the AUTHOR's active-by-default surface (config.tools + tools/): exclude pi coding
|
|
107
|
+
// tools, the builtin loader (like wake, a builtin gets its own report line, not an anonymous slot in
|
|
108
|
+
// the author's list — an author-DEFINED search_tools still shows), and deferred tools. Each name
|
|
109
|
+
// lives in exactly ONE report slot, and deferred names live in `deferredToolNames`.
|
|
110
|
+
const defaultNames = new Set(CODING_TOOL_NAMES);
|
|
86
111
|
const toolNames = tools
|
|
87
112
|
.filter((t) => !defaultNames.has(t.name) && !isDeferredTool(t) && !(builtinLoaderMounted && t.name === "search_tools"))
|
|
88
113
|
.map((t) => t.name);
|
|
@@ -122,9 +147,16 @@ export function piBasePrompt(options = {}) {
|
|
|
122
147
|
const deferredCount = mounted.length - tools.length;
|
|
123
148
|
const toolsList = tools.length > 0 ? tools.map((t) => `- ${t.name}: ${(t.description ?? "").split("\n")[0]}`).join("\n") : "(none)";
|
|
124
149
|
// Segment ① identity: an authored persona (persona.md) replaces the default engine identity line
|
|
125
|
-
// (core.md §11), keeping the tools list + guidelines below.
|
|
150
|
+
// (core.md §11), keeping the tools list + guidelines below. Preserve pi's coding identity only for
|
|
151
|
+
// the full coding surface; a partial/empty surface must not claim machine capabilities it lacks.
|
|
152
|
+
// The four this sentence NAMES — reading, executing, editing, writing. Searching is not part of the
|
|
153
|
+
// claim, so requiring it would demote an agent that can do everything the identity says it can.
|
|
154
|
+
const mountedNames = new Set(mounted.map((tool) => tool.name));
|
|
155
|
+
const fullCodingSurface = ["read", "bash", "edit", "write"].every((name) => mountedNames.has(name));
|
|
126
156
|
const identity = options.persona?.trim() ||
|
|
127
|
-
|
|
157
|
+
(fullCodingSurface
|
|
158
|
+
? "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."
|
|
159
|
+
: "You are an AI assistant operating inside pi, an agent harness. Help users using only the tools and context available to you.");
|
|
128
160
|
const deferredNote = deferredCount > 0
|
|
129
161
|
? `\n\n${deferredCount} additional tool(s) are registered but inactive — use search_tools to discover and activate them before concluding a capability is missing.`
|
|
130
162
|
: "";
|
|
@@ -164,30 +196,56 @@ export function assembleSystemPrompt(options) {
|
|
|
164
196
|
* directory) and route through here.
|
|
165
197
|
*/
|
|
166
198
|
function buildPiAgent(opts) {
|
|
167
|
-
const models = createPiModels({ providers: opts.providers, authPath: opts.authPath });
|
|
168
199
|
const env = opts.env ?? new NodeExecutionEnv({ cwd: process.cwd() });
|
|
169
|
-
|
|
170
|
-
//
|
|
200
|
+
const cwd = opts.cwd ?? env.cwd;
|
|
201
|
+
// Materialized here (not defaulted inside the L0) so the exposed parts carry the SAME lease
|
|
202
|
+
// instance the agent runs under — boundary mutations must contend on it.
|
|
171
203
|
const lease = opts.lease ?? inProcessLease();
|
|
172
|
-
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
204
|
+
const sessions = opts.sessions ?? piInMemorySessionRecordStore({ cwd });
|
|
205
|
+
// The model and its runtime resolve on FIRST USE: building a ModelRuntime is async while
|
|
206
|
+
// assembling an agent is not, so the credential read belongs on the first turn rather than in the
|
|
207
|
+
// caller's constructor. Memoized by the factory, and shared with the control plane below so a
|
|
208
|
+
// boundary mutation validates against the registry the runs actually use.
|
|
209
|
+
let engine;
|
|
210
|
+
const resolveEngine = () => {
|
|
211
|
+
engine ??= (async () => {
|
|
212
|
+
// The caller's registry when there is one — the directory rung builds it from the agent's own
|
|
213
|
+
// models.json, so a custom endpoint declared there is the one a turn resolves against.
|
|
214
|
+
const modelRuntime = opts.models ?? (await createPiModelRuntime({ authPath: opts.authPath }));
|
|
215
|
+
// ModelRuntime registers providers by config record, so an injected Provider INSTANCE (a
|
|
216
|
+
// gateway, a self-hosted endpoint, a test fake) goes in through its native seam.
|
|
217
|
+
for (const provider of opts.providers ?? [])
|
|
218
|
+
modelRuntime.registerNativeProvider(provider);
|
|
219
|
+
return { modelRuntime, model: resolveModel(modelRuntime, opts.model) };
|
|
220
|
+
})();
|
|
221
|
+
return engine;
|
|
177
222
|
};
|
|
178
|
-
const
|
|
179
|
-
sessions
|
|
180
|
-
|
|
181
|
-
models,
|
|
182
|
-
model: defaults.model,
|
|
223
|
+
const sessionFactory = piAgentSessionFactory({
|
|
224
|
+
sessions,
|
|
225
|
+
engine: resolveEngine,
|
|
183
226
|
thinkingLevel: opts.thinkingLevel,
|
|
184
|
-
systemPrompt: opts.systemPrompt,
|
|
185
227
|
tools: opts.tools,
|
|
228
|
+
systemPrompt: opts.systemPrompt,
|
|
186
229
|
skills: opts.skills,
|
|
187
230
|
live: opts.live,
|
|
231
|
+
cwd,
|
|
232
|
+
...(opts.agentDir ? { agentDir: opts.agentDir } : {}),
|
|
233
|
+
...(opts.extensionPaths ? { extensionPaths: opts.extensionPaths } : {}),
|
|
234
|
+
// `noTools: "builtin"` leaves pi's built-ins in the registry; a lower-level replacement must also
|
|
235
|
+
// deny every omitted coding name so a loader cannot reactivate one later.
|
|
236
|
+
excludedToolNames: omittedBuiltinNames(opts.tools ?? [], cwd),
|
|
237
|
+
env,
|
|
238
|
+
});
|
|
239
|
+
opts.onAssembly?.({
|
|
240
|
+
lease,
|
|
241
|
+
sessionFactory,
|
|
242
|
+
// The control plane needs the registry and the configured pair as VALUES, and both only exist
|
|
243
|
+
// after the first credential read. Asking for them lazily keeps assembly synchronous without
|
|
244
|
+
// making the hub wait on a runtime it may never need (a control-less deployment never calls it).
|
|
245
|
+
engine: resolveEngine,
|
|
246
|
+
thinkingLevel: opts.thinkingLevel ?? DEFAULT_THINKING_LEVEL,
|
|
188
247
|
});
|
|
189
|
-
|
|
190
|
-
return createPiAgentFromHarness({ lease, observer: opts.observer, cwd: env.cwd, harnessFactory });
|
|
248
|
+
return createPiAgentFromSession({ lease, observer: opts.observer, sessionFactory });
|
|
191
249
|
}
|
|
192
250
|
/**
|
|
193
251
|
* L1 system prompt: `instructions` ARE the prompt (no engine base, no wrapping); the skills listing
|
|
@@ -232,22 +290,32 @@ export async function createPiAgentFromDefinition(dir, options) {
|
|
|
232
290
|
const env = options.env ?? new NodeExecutionEnv({ cwd });
|
|
233
291
|
// Boot-time load: fail-visibly at startup on a broken directory, and give callers the snapshot to
|
|
234
292
|
// report (skills/diagnostics/collisions). Serving does NOT close over it — see `live` below.
|
|
235
|
-
|
|
293
|
+
// `cwd`, not `env.cwd`, for the same reason as the tools below: `cwd` is the run root whose
|
|
294
|
+
// ancestors carry ② project context. Reading it off the env pointed the AGENTS.md walk at the
|
|
295
|
+
// loader's directory whenever a caller supplied both.
|
|
296
|
+
const definition = await loadAgentDefinition(dir, { cwd, env });
|
|
297
|
+
// Deferred tools need their loader on every rung (idempotent — the workspace opener already applied
|
|
298
|
+
// it; a caller's own search_tools wins).
|
|
299
|
+
// `cwd`, not `env.cwd`: the workspace is what this option MEANS, and a caller may hand a custom env
|
|
300
|
+
// for definition loading whose root is a different directory. Taking it off the env would silently
|
|
301
|
+
// point the coding tools at the loader's directory.
|
|
302
|
+
const tools = withSearchTool(options.tools ?? piAllCodingTools(cwd));
|
|
236
303
|
// Boot findings go through the SAME memoized reporter every later reader uses (report.ts, keyed by
|
|
237
304
|
// the resolved dir): announced once here, and re-announced by a turn or by the control plane's
|
|
238
305
|
// command list only when the set CHANGES — a runtime-written bad skill surfaces the moment it
|
|
239
306
|
// appears, a static one does not spam. Log dedup, not session state (stateless invoke holds).
|
|
240
307
|
reportFindingsIfChanged(definition.dir, definition);
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
const
|
|
308
|
+
// Dir-aware default: the same secrets-dir-derived file the opener uses for this dir (the opener
|
|
309
|
+
// passes an explicit authPath, so this only affects direct L2 callers).
|
|
310
|
+
const authPath = options.authPath ?? defaultAuthPath(resolveSecretsDir(dir));
|
|
244
311
|
const agent = buildPiAgent({
|
|
245
312
|
model: options.model,
|
|
246
313
|
thinkingLevel: options.thinkingLevel,
|
|
247
|
-
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
authPath: options.
|
|
314
|
+
// THE directory rung's model surface: built-ins + the agent's own models.json (custom endpoints,
|
|
315
|
+
// which are definition data and travel with the artifact) + any injected Provider instance. This
|
|
316
|
+
// is what makes `dev`/`start`/`invoke` and an embedded L2 caller resolve the same specs.
|
|
317
|
+
models: await createPiModelRuntime({ agentDir: dir, authPath, providers: options.providers }),
|
|
318
|
+
authPath,
|
|
251
319
|
// The directory is the agent, LIVE: re-read the definition on every invoke, so AGENTS.md/skills
|
|
252
320
|
// edits (the author's, or the agent's own self-modification) take effect on the next turn with
|
|
253
321
|
// no process restart — restarts are reserved for code (tools/channels/config, module cache).
|
|
@@ -259,7 +327,7 @@ export async function createPiAgentFromDefinition(dir, options) {
|
|
|
259
327
|
// not silently vanish from the agent, and a static one must not spam every turn's log. The
|
|
260
328
|
// next good edit heals both.
|
|
261
329
|
live: async () => {
|
|
262
|
-
const def = await loadAgentDefinition(dir, { cwd
|
|
330
|
+
const def = await loadAgentDefinition(dir, { cwd, env });
|
|
263
331
|
reportFindingsIfChanged(def.dir, def);
|
|
264
332
|
return {
|
|
265
333
|
systemPrompt: assembleSystemPrompt({
|
|
@@ -269,13 +337,20 @@ export async function createPiAgentFromDefinition(dir, options) {
|
|
|
269
337
|
// ② project context: AGENTS.md files (agentDir + cwd-ancestor walk) via loadProjectContextFiles.
|
|
270
338
|
contextFiles: def.contextFiles,
|
|
271
339
|
skills: def.skills,
|
|
272
|
-
|
|
340
|
+
// The workspace, matching where the tools operate. Telling the model one directory while
|
|
341
|
+
// `read`/`bash` resolve relative paths against another is a lie it cannot detect.
|
|
342
|
+
cwd,
|
|
273
343
|
}),
|
|
274
344
|
skills: def.skills,
|
|
275
345
|
};
|
|
276
346
|
},
|
|
277
347
|
tools,
|
|
278
348
|
sessions: options.sessions,
|
|
349
|
+
// Discovered so the serving assembly can WARN that it does not run them (and so the refusals
|
|
350
|
+
// apply to the artifact either way) — `chat` is where they load. Boot-resolved: the set cannot
|
|
351
|
+
// change without a restart, which is why this sits outside `live` above.
|
|
352
|
+
extensionPaths: await loadExtensionPaths(dir, { cwd, env }),
|
|
353
|
+
cwd,
|
|
279
354
|
env,
|
|
280
355
|
lease: options.lease,
|
|
281
356
|
observer: options.observer,
|
|
@@ -41,6 +41,47 @@ export interface LoadAgentDefinitionOptions {
|
|
|
41
41
|
}
|
|
42
42
|
/** Read an agent definition. persona.md/skills come from `agentDir`; ② context = pi's loadProjectContextFiles({ cwd, agentDir }). */
|
|
43
43
|
export declare function loadAgentDefinition(agentDir: string, options?: LoadAgentDefinitionOptions): Promise<LoadedDefinition>;
|
|
44
|
+
/**
|
|
45
|
+
* Extension entry-point FILES under `<agentDir>/extensions/`, empty when there are none.
|
|
46
|
+
*
|
|
47
|
+
* Paths, not loaded objects — unlike skills (data: content inline, serializable), an extension is
|
|
48
|
+
* CODE that pi loads with jiti and binds to its own eventBus/runtime, so loading it here would
|
|
49
|
+
* reimplement pi's loader. The engine binding hands these to pi as `additionalExtensionPaths`, which
|
|
50
|
+
* survives `noExtensions: true` — that flag suppresses MACHINE-GLOBAL discovery (`~/.pi`), and the
|
|
51
|
+
* definition's own extensions were only ever collateral to it. FILES, not the directory: pi's paths
|
|
52
|
+
* are module specifiers, and a directory fails as `Cannot find module` into a
|
|
53
|
+
* `LoadExtensionsResult.errors` entry nothing reads.
|
|
54
|
+
*
|
|
55
|
+
* SEPARATE from {@link loadAgentDefinition} on purpose. Prompt and skills are re-read every invoke
|
|
56
|
+
* ("the directory is the agent, LIVE"); scanning for extension entry points is not, since the set
|
|
57
|
+
* cannot change without a restart. Called once per assembly, like `tools/`.
|
|
58
|
+
*
|
|
59
|
+
* Discovery runs on BOTH paths; loading does not. `chat` hands these to pi and runs them fully.
|
|
60
|
+
* Serving only announces them — pi's extension runtime is shared across sessions, and serving has
|
|
61
|
+
* concurrent turns for unrelated conversations (see `PiAgentSessionFactoryOptions.extensionPaths`).
|
|
62
|
+
* The refusals below are therefore about what the ARTIFACT may contain, and hold for both.
|
|
63
|
+
*
|
|
64
|
+
* Discovery follows pi's own rules, so an extension that works in pi works here:
|
|
65
|
+
*
|
|
66
|
+
* 1. a direct `*.ts` / `*.js` file;
|
|
67
|
+
* 2. a subdirectory with `index.ts` / `index.js`.
|
|
68
|
+
*
|
|
69
|
+
* pi has a third rule — a subdirectory whose `package.json` declares a `pi` field — which is NOT
|
|
70
|
+
* implemented here. That shape is reported rather than skipped: a definition whose extension silently
|
|
71
|
+
* fails to load is the failure mode this whole path exists to remove.
|
|
72
|
+
*
|
|
73
|
+
* Containment matches skills/tools/channels/schedules (the fifth of five surfaces): a symlinked
|
|
74
|
+
* `extensions/` escaping the agent dir is refused, and — like `loadModuleDir`, whose `entry.isFile()`
|
|
75
|
+
* excludes them — a symlinked ENTRY is not loaded either. pi's own discovery does follow those, but
|
|
76
|
+
* an extension reached through a link out of the definition is code the artifact does not carry: it
|
|
77
|
+
* resolves on the authoring machine and is missing in the container. Refused loudly, never silently.
|
|
78
|
+
*
|
|
79
|
+
* Absent is normal and silent; a FILE at that path is not — that is an author who meant something.
|
|
80
|
+
*/
|
|
81
|
+
export declare function loadExtensionPaths(agentDir: string, options?: {
|
|
82
|
+
cwd?: string;
|
|
83
|
+
env?: ExecutionEnv;
|
|
84
|
+
}): Promise<string[]>;
|
|
44
85
|
/**
|
|
45
86
|
* The definition's skills ALONE, resolved the same way `loadAgentDefinition` resolves them (same
|
|
46
87
|
* loader, same containment guard, same first-wins collision rule) — for readers that need only the
|
|
@@ -56,13 +97,6 @@ export declare function loadAgentSkills(agentDir: string, options?: {
|
|
|
56
97
|
collisions: SkillCollision[];
|
|
57
98
|
dir: string;
|
|
58
99
|
}>;
|
|
59
|
-
/**
|
|
60
|
-
* Whether `targetPath` lives inside `baseDir` (same path counts). Used to ask "did an override move
|
|
61
|
-
* this OUT of the agent?" — the startup report's redeploy notes, `add`'s printed `.env` label, and the
|
|
62
|
-
* dev watcher's "your .env is not watched" warning all turn on that fact. Reporting only: fastagent
|
|
63
|
-
* does not act on where a user's paths point.
|
|
64
|
-
*/
|
|
65
|
-
export declare function isUnderDir(targetPath: string, baseDir: string): boolean;
|
|
66
100
|
/** Resolve to a canonical (symlink-free) absolute path so comparisons match `process.cwd()`'s realpath.
|
|
67
101
|
* A non-existent path can't be realpath'd, so it stays as the plain absolute resolve. */
|
|
68
102
|
export declare function canonicalPath(p: string): string;
|