@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
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { ABORTED_CODE } from "../../agent.js";
|
|
2
|
+
export function inProcessLease() {
|
|
3
|
+
const busy = new Set();
|
|
4
|
+
return {
|
|
5
|
+
tryAcquire(session) {
|
|
6
|
+
if (busy.has(session))
|
|
7
|
+
return null;
|
|
8
|
+
busy.add(session);
|
|
9
|
+
let released = false;
|
|
10
|
+
return () => {
|
|
11
|
+
if (released)
|
|
12
|
+
return;
|
|
13
|
+
released = true;
|
|
14
|
+
busy.delete(session);
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Clearly-transient network error codes (Node/undici), decisive on their own. */
|
|
20
|
+
const RETRYABLE_CODES = new Set([
|
|
21
|
+
"ECONNRESET",
|
|
22
|
+
"ETIMEDOUT",
|
|
23
|
+
"ENETUNREACH",
|
|
24
|
+
"ENETDOWN",
|
|
25
|
+
"EAI_AGAIN",
|
|
26
|
+
"EPIPE",
|
|
27
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
28
|
+
"UND_ERR_SOCKET",
|
|
29
|
+
]);
|
|
30
|
+
/** 429 (rate limit) and 5xx (server) are worth retrying; other statuses are decisive NON-retryable. */
|
|
31
|
+
const statusIsRetryable = (status) => status === 429 || (status >= 500 && status < 600);
|
|
32
|
+
/** Last-resort prose match, used only when no structured status/code is available. */
|
|
33
|
+
const RETRYABLE_MESSAGE = /\b(429|5\d\d|timeout|timed out|rate.?limit|overloaded|ECONNRESET|ETIMEDOUT|ENETUNREACH|EAI_AGAIN|socket hang up)\b/i;
|
|
34
|
+
/** A structured status/code decision, or `null` when the signal is absent/undecisive → fall to prose. */
|
|
35
|
+
function retryableFromSignal(signal) {
|
|
36
|
+
if (typeof signal.status === "number")
|
|
37
|
+
return statusIsRetryable(signal.status);
|
|
38
|
+
const { code } = signal;
|
|
39
|
+
if (typeof code === "number")
|
|
40
|
+
return statusIsRetryable(code);
|
|
41
|
+
if (typeof code === "string") {
|
|
42
|
+
if (RETRYABLE_CODES.has(code))
|
|
43
|
+
return true;
|
|
44
|
+
if (/^\d{3}$/.test(code))
|
|
45
|
+
return statusIsRetryable(Number(code)); // a status carried as a string
|
|
46
|
+
}
|
|
47
|
+
return null; // no code, or an unknown one — not decisive on its own
|
|
48
|
+
}
|
|
49
|
+
/** Classify `retryable`: structured status/code first, message prose only as the last-resort ceiling. */
|
|
50
|
+
export function classifyRetryable(details, signal) {
|
|
51
|
+
return retryableFromSignal(signal) ?? RETRYABLE_MESSAGE.test(details);
|
|
52
|
+
}
|
|
53
|
+
/** Pull a structured status/code off a thrown error (HTTP status or a network code, incl. its cause). */
|
|
54
|
+
function errorSignal(error) {
|
|
55
|
+
if (!error || typeof error !== "object")
|
|
56
|
+
return {};
|
|
57
|
+
const e = error;
|
|
58
|
+
const status = typeof e.status === "number" ? e.status : typeof e.statusCode === "number" ? e.statusCode : undefined;
|
|
59
|
+
const causeCode = e.cause && typeof e.cause === "object" ? e.cause.code : undefined;
|
|
60
|
+
return { status, code: e.code ?? causeCode };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Pull the structured error `code` pi records on a failed message's diagnostics. `diagnostics`
|
|
64
|
+
* accumulates across attempts (`appendAssistantMessageDiagnostic`), so the terminal cause is the LAST
|
|
65
|
+
* code-bearing entry — `findLast`, not `find`: an earlier attempt's transient 503 must not classify a
|
|
66
|
+
* terminal 400/auth failure as retryable. (Reverse scan rather than `findLast` — the tsconfig lib is
|
|
67
|
+
* ES2022.)
|
|
68
|
+
*/
|
|
69
|
+
function messageSignal(message) {
|
|
70
|
+
const diagnostics = message.diagnostics ?? [];
|
|
71
|
+
for (let i = diagnostics.length - 1; i >= 0; i--) {
|
|
72
|
+
const code = diagnostics[i]?.error?.code;
|
|
73
|
+
if (code !== undefined)
|
|
74
|
+
return { code };
|
|
75
|
+
}
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Terminal mapping, decided by the resolved message's stopReason: pi's prompt() resolves a message
|
|
80
|
+
* with stopReason "error"/"aborted" rather than throwing, so relying on catch alone would miss this
|
|
81
|
+
* entire failure class (violating SPEC MUST 1).
|
|
82
|
+
*/
|
|
83
|
+
export function toTerminal(message) {
|
|
84
|
+
if (message.stopReason === "aborted") {
|
|
85
|
+
// A deliberate stop (a control-plane or consumer abort), not an error — see {@link ABORTED_CODE}
|
|
86
|
+
// for the consumer contract (design §6).
|
|
87
|
+
const details = message.errorMessage ?? "run aborted";
|
|
88
|
+
return { type: "failed", details, retryable: false, code: ABORTED_CODE };
|
|
89
|
+
}
|
|
90
|
+
if (message.stopReason === "error") {
|
|
91
|
+
const details = message.errorMessage ?? `model stopped: ${message.stopReason}`;
|
|
92
|
+
return { type: "failed", details, retryable: classifyRetryable(details, messageSignal(message)) };
|
|
93
|
+
}
|
|
94
|
+
return { type: "completed" };
|
|
95
|
+
}
|
|
96
|
+
export function errorToTerminal(error) {
|
|
97
|
+
const details = error instanceof Error ? error.message : String(error);
|
|
98
|
+
return { type: "failed", details, retryable: classifyRetryable(details, errorSignal(error)) };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Map prompt images to pi ImageContent, resizing each to model-friendly dimensions/size with pi's
|
|
102
|
+
* Photon resizer (reused from pi-coding-agent, lazy-imported so the common no-image headless path never
|
|
103
|
+
* loads the TUI module graph). A null resize (unresizable / Photon unavailable) keeps the original
|
|
104
|
+
* bytes — the provider then applies its own limit.
|
|
105
|
+
*/
|
|
106
|
+
export async function toPiPromptOptions(prompt) {
|
|
107
|
+
if (!prompt.images || prompt.images.length === 0)
|
|
108
|
+
return undefined;
|
|
109
|
+
const { resizeImage } = await import("@earendil-works/pi-coding-agent");
|
|
110
|
+
const images = await Promise.all(prompt.images.map(async (img) => {
|
|
111
|
+
const resized = await resizeImage(Buffer.from(img.data, "base64"), img.mimeType, {
|
|
112
|
+
maxWidth: 1568,
|
|
113
|
+
maxHeight: 1568,
|
|
114
|
+
maxBytes: 5 * 1024 * 1024,
|
|
115
|
+
}).catch(() => null);
|
|
116
|
+
return resized
|
|
117
|
+
? { type: "image", data: resized.data, mimeType: resized.mimeType }
|
|
118
|
+
: { type: "image", data: img.data, mimeType: img.mimeType };
|
|
119
|
+
}));
|
|
120
|
+
return { images };
|
|
121
|
+
}
|
|
122
|
+
/** The SPEC projection of the rich stream. Events with no `AgentEvent` counterpart (progress,
|
|
123
|
+
* message boundaries, run boundaries) project to null — the invoke terminal is produced from the
|
|
124
|
+
* resolved message ({@link toTerminal}), not from `run_settled`. */
|
|
125
|
+
export function projectAgentEvent(se) {
|
|
126
|
+
switch (se.type) {
|
|
127
|
+
case "message_delta": {
|
|
128
|
+
const d = se.data;
|
|
129
|
+
return d.channel === "text" ? { type: "text", delta: d.delta } : { type: "thinking", delta: d.delta };
|
|
130
|
+
}
|
|
131
|
+
case "tool_started": {
|
|
132
|
+
const d = se.data;
|
|
133
|
+
return { type: "tool_started", id: d.id, name: d.name, args: d.args };
|
|
134
|
+
}
|
|
135
|
+
case "tool_finished": {
|
|
136
|
+
const d = se.data;
|
|
137
|
+
return { type: "tool_ended", id: d.id, isError: d.isError, content: d.content };
|
|
138
|
+
}
|
|
139
|
+
case "retry_scheduled": {
|
|
140
|
+
// `operation` (compaction | branch_summary) stays session-plane vocabulary — a turn renderer
|
|
141
|
+
// only needs "transient failure, retrying"; the engine detail lives in the control plane.
|
|
142
|
+
const d = se.data;
|
|
143
|
+
return { type: "retrying", attempt: d.attempt, maxAttempts: d.maxAttempts, delayMs: d.delayMs, reason: d.error };
|
|
144
|
+
}
|
|
145
|
+
default:
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// ── EventQueue: push→pull plumbing for a two-port engine ────────────────────
|
|
150
|
+
//
|
|
151
|
+
// Single-consumer async queue; single-threaded JS means no await interleaves between push and
|
|
152
|
+
// drain, so no locking. Engines that are natively async-iterable would not need it.
|
|
153
|
+
export class EventQueue {
|
|
154
|
+
buffer = [];
|
|
155
|
+
wake;
|
|
156
|
+
push(item) {
|
|
157
|
+
this.buffer.push(item);
|
|
158
|
+
const wake = this.wake;
|
|
159
|
+
this.wake = undefined;
|
|
160
|
+
wake?.();
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Yield pushed events in order until `done` settles AND the buffer is drained. The terminal is
|
|
164
|
+
* produced separately (toTerminal); rejections of `done` are swallowed here (the caller awaits
|
|
165
|
+
* `run` itself) to avoid unhandled rejections.
|
|
166
|
+
*/
|
|
167
|
+
async *drainUntil(done) {
|
|
168
|
+
let settled = false;
|
|
169
|
+
const onSettle = () => {
|
|
170
|
+
settled = true;
|
|
171
|
+
const wake = this.wake;
|
|
172
|
+
this.wake = undefined;
|
|
173
|
+
wake?.();
|
|
174
|
+
};
|
|
175
|
+
const finished = done.then(onSettle, onSettle);
|
|
176
|
+
while (true) {
|
|
177
|
+
while (this.buffer.length > 0) {
|
|
178
|
+
yield this.buffer.shift();
|
|
179
|
+
}
|
|
180
|
+
if (settled)
|
|
181
|
+
break;
|
|
182
|
+
await new Promise((resolve) => {
|
|
183
|
+
this.wake = resolve;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
await finished;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The built-in `wake` tool: the agent's self-scheduling surface. Calling it records a one-shot wake-up
|
|
3
3
|
* (wakeups.ts); the scheduler fires it back into the SAME session, so the agent resumes THIS
|
|
4
4
|
* conversation after a delay ("check the deploy in 10 minutes"). The session comes from the turn
|
|
5
|
-
* context (ToolContext.sessionManager, set around the
|
|
5
|
+
* context (ToolContext.sessionManager, set around the turn); the state root is closed over at build
|
|
6
6
|
* time (where it is known — the workspace opener), never read from the turn.
|
|
7
7
|
*
|
|
8
8
|
* Mounted by the opener ONLY when `config.selfSchedule` is on AND on the serving path (`dev`/`start`, where
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
// Contract/channel-only consumers should prefer `@fastagent-sh/fastagent/core`; pi-specific consumers
|
|
3
3
|
// may use `@fastagent-sh/fastagent/pi`. The root remains the supported all-in-one surface.
|
|
4
4
|
export * from "./core.js";
|
|
5
|
+
export * from "./node.js";
|
|
5
6
|
export * from "./session.js";
|
|
6
7
|
export * from "./pi.js";
|
package/dist/log.d.ts
CHANGED
|
@@ -23,4 +23,7 @@ export declare function createLogger(opts: {
|
|
|
23
23
|
/** Set the posture default. A valid `FASTAGENT_LOG_LEVEL` override, if present, wins and is not changed. */
|
|
24
24
|
export declare function setLogLevel(level: LogLevel): void;
|
|
25
25
|
/** The process logger. Runtime code imports this and calls `log.info(...)` etc. */
|
|
26
|
+
import type { ModuleLoadFailure } from "./loader.ts";
|
|
26
27
|
export declare const log: Logger;
|
|
28
|
+
/** A module the loader skipped, said once, the same way for tools, channels and schedules. */
|
|
29
|
+
export declare function reportModuleLoadFailures(failures: readonly ModuleLoadFailure[]): void;
|
package/dist/log.js
CHANGED
|
@@ -47,5 +47,9 @@ const emit = (level) => (msg) => {
|
|
|
47
47
|
if (ORDER[level] >= ORDER[currentLevel])
|
|
48
48
|
console.error(format(level, msg));
|
|
49
49
|
};
|
|
50
|
-
/** The process logger. Runtime code imports this and calls `log.info(...)` etc. */
|
|
51
50
|
export const log = { debug: emit("debug"), info: emit("info"), warn: emit("warn"), error: emit("error") };
|
|
51
|
+
/** A module the loader skipped, said once, the same way for tools, channels and schedules. */
|
|
52
|
+
export function reportModuleLoadFailures(failures) {
|
|
53
|
+
for (const f of failures)
|
|
54
|
+
log.warn(`[fastagent] ${f.label} failed to load, skipping it — ${f.message}`);
|
|
55
|
+
}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binding a Fetch handler to a Node HTTP server.
|
|
3
|
+
*
|
|
4
|
+
* Its own entry point because it is the one piece of the neutral surface that is RUNTIME-specific:
|
|
5
|
+
* `@hono/node-server` bridges `node:http` ↔ Fetch, and that package is the only third-party weight
|
|
6
|
+
* anywhere behind `/core`. Keeping it here lets a channel package or another engine import the
|
|
7
|
+
* contract without pulling a Node HTTP bridge it will never call — and lets a non-Node runtime
|
|
8
|
+
* (Workers, Deno, Bun's own server) consume `/core` unchanged.
|
|
9
|
+
*
|
|
10
|
+
* Engine-neutral is not the same as runtime-neutral, and the two properties give the surface its
|
|
11
|
+
* three layers: `/core` is both (contract, fetch-shaped kit), `/node` is engine-neutral only (this
|
|
12
|
+
* file: the assembly and the HTTP binding, which need a filesystem, a clock and an environment),
|
|
13
|
+
* `/pi` is neither (it names an engine).
|
|
14
|
+
*/
|
|
15
|
+
export { nodeListener, serveNode } from "./channels/serve.ts";
|
|
16
|
+
export { mountAgentService, type AgentService, type MountableAgent, type MountAgentServiceOptions, } from "./service.ts";
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binding a Fetch handler to a Node HTTP server.
|
|
3
|
+
*
|
|
4
|
+
* Its own entry point because it is the one piece of the neutral surface that is RUNTIME-specific:
|
|
5
|
+
* `@hono/node-server` bridges `node:http` ↔ Fetch, and that package is the only third-party weight
|
|
6
|
+
* anywhere behind `/core`. Keeping it here lets a channel package or another engine import the
|
|
7
|
+
* contract without pulling a Node HTTP bridge it will never call — and lets a non-Node runtime
|
|
8
|
+
* (Workers, Deno, Bun's own server) consume `/core` unchanged.
|
|
9
|
+
*
|
|
10
|
+
* Engine-neutral is not the same as runtime-neutral, and the two properties give the surface its
|
|
11
|
+
* three layers: `/core` is both (contract, fetch-shaped kit), `/node` is engine-neutral only (this
|
|
12
|
+
* file: the assembly and the HTTP binding, which need a filesystem, a clock and an environment),
|
|
13
|
+
* `/pi` is neither (it names an engine).
|
|
14
|
+
*/
|
|
15
|
+
export { nodeListener, serveNode } from "./channels/serve.js";
|
|
16
|
+
// The assembly: a MountableAgent becomes a mounted service. Engine-neutral — it reads the SPEC
|
|
17
|
+
// contract plus three paths, so a second engine reuses it with its own opener — but NOT runtime
|
|
18
|
+
// neutral: it reads a directory, a cron and an environment. That is what makes this its entry
|
|
19
|
+
// rather than `/core`.
|
|
20
|
+
export { mountAgentService, } from "./service.js";
|
package/dist/paths.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export declare const STATE_DIRNAME = ".state";
|
|
|
24
24
|
/** The config filenames, in load precedence. ONE source: the loader (below) and `scaffoldAgent`'s
|
|
25
25
|
* already-an-agent refusal both read this, so "is there a config?" can't diverge between them. */
|
|
26
26
|
export declare const AGENT_CONFIG_NAMES: readonly ["fastagent.config.ts", "fastagent.config.js", "fastagent.config.mjs"];
|
|
27
|
+
/** The optional custom-model-endpoint file inside an agent dir (pi's models.json schema). Definition
|
|
28
|
+
* data, not machinery: it declares WHICH endpoint the agent talks to, so it belongs beside the config
|
|
29
|
+
* and travels into the deployed image. The name lives HERE, with the other placement facts, because
|
|
30
|
+
* two neutral readers need it — the loader in engines/pi/models.ts and `dev`'s watcher, whose restart
|
|
31
|
+
* scope must not silently drift from what the worker actually loads. */
|
|
32
|
+
export declare const AGENT_MODELS_FILE = "models.json";
|
|
27
33
|
export interface ResolvedPlacement {
|
|
28
34
|
/** The AGENT directory — where the definition (persona.md/skills/tools/channels/schedules), the
|
|
29
35
|
* config, and the machinery dirs (`.secrets/`, `.state/`) live. Absolute. */
|
|
@@ -136,3 +142,10 @@ export declare function resolveSecretsDir(dir: string, env?: NodeJS.ProcessEnv):
|
|
|
136
142
|
* A missing target is fine (nothing to guard yet).
|
|
137
143
|
*/
|
|
138
144
|
export declare function assertInsideAgentDir(agentDir: string, name: string): Promise<void>;
|
|
145
|
+
/**
|
|
146
|
+
* Whether `targetPath` lives inside `baseDir` (same path counts). Used to ask "did an override move
|
|
147
|
+
* this OUT of the agent?" — the startup report's redeploy notes, `add`'s printed `.env` label, and the
|
|
148
|
+
* dev watcher's "your .env is not watched" warning all turn on that fact. Reporting only: fastagent
|
|
149
|
+
* does not act on where a user's paths point.
|
|
150
|
+
*/
|
|
151
|
+
export declare function isUnderDir(targetPath: string, baseDir: string): boolean;
|
package/dist/paths.js
CHANGED
|
@@ -46,6 +46,12 @@ export const STATE_DIRNAME = ".state";
|
|
|
46
46
|
/** The config filenames, in load precedence. ONE source: the loader (below) and `scaffoldAgent`'s
|
|
47
47
|
* already-an-agent refusal both read this, so "is there a config?" can't diverge between them. */
|
|
48
48
|
export const AGENT_CONFIG_NAMES = ["fastagent.config.ts", "fastagent.config.js", "fastagent.config.mjs"];
|
|
49
|
+
/** The optional custom-model-endpoint file inside an agent dir (pi's models.json schema). Definition
|
|
50
|
+
* data, not machinery: it declares WHICH endpoint the agent talks to, so it belongs beside the config
|
|
51
|
+
* and travels into the deployed image. The name lives HERE, with the other placement facts, because
|
|
52
|
+
* two neutral readers need it — the loader in engines/pi/models.ts and `dev`'s watcher, whose restart
|
|
53
|
+
* scope must not silently drift from what the worker actually loads. */
|
|
54
|
+
export const AGENT_MODELS_FILE = "models.json";
|
|
49
55
|
/** The definition paths an agent LOADS content from — the surface a second agent must not be scaffolded
|
|
50
56
|
* inside ({@link agentDefinitionOwner}), because the outer agent would read it as its own skills/tools.
|
|
51
57
|
* NOT evidence of an agent: `tools/` and `skills/` are ordinary names half the world's repositories
|
|
@@ -324,3 +330,13 @@ export async function assertInsideAgentDir(agentDir, name) {
|
|
|
324
330
|
`use a real directory or a symlink that stays within it`);
|
|
325
331
|
}
|
|
326
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Whether `targetPath` lives inside `baseDir` (same path counts). Used to ask "did an override move
|
|
335
|
+
* this OUT of the agent?" — the startup report's redeploy notes, `add`'s printed `.env` label, and the
|
|
336
|
+
* dev watcher's "your .env is not watched" warning all turn on that fact. Reporting only: fastagent
|
|
337
|
+
* does not act on where a user's paths point.
|
|
338
|
+
*/
|
|
339
|
+
export function isUnderDir(targetPath, baseDir) {
|
|
340
|
+
const rel = relative(baseDir, targetPath);
|
|
341
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
342
|
+
}
|
package/dist/pi.d.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
export { createPiAgent, createPiAgentFromDefinition, type CreatePiAgentFromDefinitionOptions, type CreatePiAgentOptions, } from "./engines/pi/create.ts";
|
|
2
|
-
export { defineTool,
|
|
2
|
+
export { defineTool, type DefineToolOptions, type FastagentTool, type MountedTool, type ToolCollision, type ToolContext, } from "./engines/pi/tool.ts";
|
|
3
3
|
export type { ReadonlySessionManager, ToolActivation } from "./engines/pi/tool-context.ts";
|
|
4
4
|
export { z } from "zod";
|
|
5
|
-
export type { AgentTool, ExecutionEnv,
|
|
6
|
-
|
|
5
|
+
export type { AgentTool, ExecutionEnv, Skill, SkillDiagnostic } from "@earendil-works/pi-agent-core";
|
|
6
|
+
/**
|
|
7
|
+
* A conversation record, as the tool runtime and the control plane hold it. Its entries are pi's own
|
|
8
|
+
* — exported under a qualified name because `SessionEntry` in this package is the NEUTRAL one the
|
|
9
|
+
* control plane publishes (session.ts), and the two are different shapes.
|
|
10
|
+
*/
|
|
11
|
+
export type { SessionManager, SessionEntry as PiSessionEntry } from "@earendil-works/pi-coding-agent";
|
|
7
12
|
export { createPiAgentFromDir, type CreatePiAgentFromDirOptions, } from "./engines/pi/open.ts";
|
|
8
13
|
export type { LoadedDefinition, SkillCollision } from "./engines/pi/definition.ts";
|
|
9
|
-
export { defineConfig, listModels, resolveModel, type FastagentConfig } from "./engines/pi/config.ts";
|
|
10
|
-
export
|
|
14
|
+
export { defineConfig, listModels, resolveModel, type FastagentConfig, } from "./engines/pi/config.ts";
|
|
15
|
+
export type { SessionObserver } from "./engines/pi/turn-kit.ts";
|
|
16
|
+
export { inProcessLease, type Lease, type Release } from "./engines/pi/turn-kit.ts";
|
|
11
17
|
export { createPiSessionControl, type CreatePiSessionControlOptions, } from "./engines/pi/session-control.ts";
|
|
12
|
-
export type
|
|
13
|
-
export
|
|
18
|
+
export { piInMemorySessionRecordStore, piSessionRecordStore, type PiSessionRecordStore, } from "./engines/pi/session-store.ts";
|
|
19
|
+
export type { SessionInheritance } from "./engines/pi/session-inheritance.ts";
|
|
14
20
|
export { GLOBAL_AUTH_PATH, fastagentCredentialStore, type FastagentAuthOptions } from "./engines/pi/auth.ts";
|
|
15
21
|
export { createPiModels, probeAuthSource, type CreatePiModelsOptions } from "./engines/pi/models.ts";
|
|
16
22
|
export type { Models } from "@earendil-works/pi-ai";
|
|
17
|
-
export {
|
|
23
|
+
export type { Provider, ProviderAuth } from "@earendil-works/pi-ai";
|
|
18
24
|
export type { Model } from "@earendil-works/pi-ai";
|
|
25
|
+
export { createAgentService, type CreateAgentServiceOptions } from "./engines/pi/service.ts";
|
package/dist/pi.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// The pi reference implementation: assembly, agent discovery, tools, config, models, auth, and state ports.
|
|
2
2
|
export { createPiAgent, createPiAgentFromDefinition, } from "./engines/pi/create.js";
|
|
3
|
-
export { defineTool,
|
|
3
|
+
export { defineTool, } from "./engines/pi/tool.js";
|
|
4
4
|
export { z } from "zod";
|
|
5
|
-
export { loadChannels } from "./engines/pi/channel.js";
|
|
6
5
|
export { createPiAgentFromDir, } from "./engines/pi/open.js";
|
|
7
|
-
export { defineConfig, listModels, resolveModel } from "./engines/pi/config.js";
|
|
8
|
-
export { inProcessLease } from "./engines/pi/
|
|
6
|
+
export { defineConfig, listModels, resolveModel, } from "./engines/pi/config.js";
|
|
7
|
+
export { inProcessLease } from "./engines/pi/turn-kit.js";
|
|
9
8
|
export { createPiSessionControl, } from "./engines/pi/session-control.js";
|
|
10
|
-
export {
|
|
9
|
+
export { piInMemorySessionRecordStore, piSessionRecordStore, } from "./engines/pi/session-store.js";
|
|
11
10
|
export { GLOBAL_AUTH_PATH, fastagentCredentialStore } from "./engines/pi/auth.js";
|
|
12
11
|
export { createPiModels, probeAuthSource } from "./engines/pi/models.js";
|
|
13
|
-
|
|
12
|
+
// The product's one-call assembly: a directory becomes a live service. On the pi surface because
|
|
13
|
+
// it opens a directory the pi way (channels, schedules, the assembled agent).
|
|
14
|
+
export { createAgentService } from "./engines/pi/service.js";
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// No model is preset: `fastagent dev` shows the full model catalog (models you already have
|
|
6
6
|
// credentials for come first; picking one that needs auth logs you in inline) and writes your choice
|
|
7
7
|
// below. Or set it by hand to a "provider/modelId" (`fastagent models` lists them).
|
|
8
|
+
// Self-hosted model (vLLM/Ollama/…) or your own gateway? Declare it in a models.json next to this
|
|
9
|
+
// file and select it like any other spec — see docs/configuration.md "Custom model endpoints".
|
|
8
10
|
export default {
|
|
9
11
|
// model: "openai-codex/gpt-5.5",
|
|
10
12
|
// thinkingLevel: "high", // reasoning effort (off|minimal|low|medium|high|xhigh|max); default "medium" (pi TUI parity)
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
You are this workspace's agent. This file is your identity — it overrides the engine's default identity line, and it is re-read every turn along with the rest of your definition (`skills/` — capabilities you load when a task calls for them; `tools/` — code tools your author added, in the same directory as this file). An edit to any of them takes effect on your next message, no restart.
|
|
4
4
|
|
|
5
|
-
Your definition is this directory: `persona.md`, `skills/`, `tools/`, and the config beside them. Your WORKSPACE is the directory you were started in — the project you work on
|
|
5
|
+
Your definition is this directory: `persona.md`, `skills/`, `tools/`, and the config beside them. Your WORKSPACE is the directory you were started in — the project you work on. It may be this same directory, or the one containing it; `fastagent info` prints both. Use only the tools actually listed in your system prompt. If the workspace has an `AGENTS.md`, it is project context — follow it without assuming a file tool is available.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
When your mounted tools allow it, you can improve yourself. When a task reveals something durable — a repeatable process, a standing preference, a hard-won fact — write it into your definition instead of losing it:
|
|
8
8
|
|
|
9
9
|
- A repeatable process or capability → a new skill beside this file: `skills/<name>/SKILL.md`. Only the `skills/` next to this file is scanned. Read `skills/writing-great-skills/SKILL.md` first; it is the guide to authoring skills well.
|
|
10
10
|
- A standing instruction or fact → edit this file.
|
package/dist/schedule/state.js
CHANGED
|
@@ -4,12 +4,10 @@
|
|
|
4
4
|
* - `fires.json` — schedule name → last-fired ISO (durability for the cron catch-up-once);
|
|
5
5
|
* - `wakeups.json` — the agent's pending self-scheduled one-shot wake-ups (wakeups.ts).
|
|
6
6
|
* No .gitignore is written here: the agent's own (scaffolded by `init`) excludes `.state/`.
|
|
7
|
-
*
|
|
8
|
-
* ponytail: this atomic read/write duplicates channels/telegram/state.ts's primitive (both KB-JSON
|
|
9
|
-
* tmp+rename). Extract a neutral src/state.ts and have both import it when a third consumer appears.
|
|
10
7
|
*/
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { writeFileAtomic } from "../atomic-write.js";
|
|
13
11
|
import { log } from "../log.js";
|
|
14
12
|
/** Path of a JSON file under `<stateRoot>/schedule/`. */
|
|
15
13
|
export function scheduleFile(stateRoot, name) {
|
|
@@ -42,10 +40,7 @@ export function readScheduleFile(path) {
|
|
|
42
40
|
}
|
|
43
41
|
}
|
|
44
42
|
export function writeScheduleFile(path, value) {
|
|
45
|
-
|
|
46
|
-
const tmp = `${path}.tmp`;
|
|
47
|
-
writeFileSync(tmp, JSON.stringify(value));
|
|
48
|
-
renameSync(tmp, path);
|
|
43
|
+
writeFileAtomic(path, JSON.stringify(value));
|
|
49
44
|
}
|
|
50
45
|
export function loadFires(stateRoot) {
|
|
51
46
|
const v = readScheduleFile(scheduleFile(stateRoot, "fires"));
|
|
@@ -73,7 +73,7 @@ export declare function takeFirstDueWakeup(stateRoot: string, now?: Date): Wakeu
|
|
|
73
73
|
* already advanced the entry, and its next occurrence comes by definition — a busy one is skipped + audited.
|
|
74
74
|
*
|
|
75
75
|
* Known residual: between the claim (removed from the store) and this re-add there is a microtask-scale
|
|
76
|
-
* window (the busy reject yields before any
|
|
76
|
+
* window (the busy reject yields before any engine IO) where an `unwake` for this id reports "not found"
|
|
77
77
|
* and the defer then resurrects it — the one-shot cousin of the recurring resurrection the advance-in-place
|
|
78
78
|
* claim eliminated. Accepted: closing it needs a claim-lease with expiry, disproportionate to the window.
|
|
79
79
|
*/
|
package/dist/schedule/wakeups.js
CHANGED
|
@@ -201,7 +201,7 @@ export function takeFirstDueWakeup(stateRoot, now = new Date()) {
|
|
|
201
201
|
* already advanced the entry, and its next occurrence comes by definition — a busy one is skipped + audited.
|
|
202
202
|
*
|
|
203
203
|
* Known residual: between the claim (removed from the store) and this re-add there is a microtask-scale
|
|
204
|
-
* window (the busy reject yields before any
|
|
204
|
+
* window (the busy reject yields before any engine IO) where an `unwake` for this id reports "not found"
|
|
205
205
|
* and the defer then resurrects it — the one-shot cousin of the recurring resurrection the advance-in-place
|
|
206
206
|
* claim eliminated. Accepted: closing it needs a claim-lease with expiry, disproportionate to the window.
|
|
207
207
|
*/
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { Agent } from "./agent.ts";
|
|
2
|
+
import { type LoadedLongConnectionChannel } from "./channels/discover.ts";
|
|
3
|
+
import type { SessionControl } from "./session.ts";
|
|
4
|
+
import type { ChannelHandler, Routes } from "./channel.ts";
|
|
5
|
+
import { type PrefixMount } from "./channels/serve.ts";
|
|
6
|
+
import type { LoadedSchedule } from "./schedule/schedule.ts";
|
|
7
|
+
export interface ServingSurface {
|
|
8
|
+
routes: Routes;
|
|
9
|
+
/** Prefix-owning handlers mounted beside the routes (the session control plane). */
|
|
10
|
+
mounts?: readonly PrefixMount[];
|
|
11
|
+
longConnections: LoadedLongConnectionChannel[];
|
|
12
|
+
/** Route-channel basenames; the tunnel registers only this subset. */
|
|
13
|
+
routeChannels: string[];
|
|
14
|
+
builtinInvoke: boolean;
|
|
15
|
+
/** Marks the built-in health route ready after every long-connection channel first connects. */
|
|
16
|
+
/** Flip health between 200 and 503. Two-way on purpose: a long connection that dies after coming
|
|
17
|
+
* up leaves the surface serving something it no longer has, and a load balancer should hear it. */
|
|
18
|
+
setReady(value: boolean): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The surface this deployment serves: default `GET /health` plus discovered channels, or the default
|
|
22
|
+
* POST `/invoke` only when neither a route nor a long-connection channel was declared.
|
|
23
|
+
*/
|
|
24
|
+
export declare function routesFor(agentDir: string, agent: Agent, stateRoot: string, control: SessionControl | undefined, options?: {
|
|
25
|
+
builtinInvoke?: boolean;
|
|
26
|
+
}): Promise<ServingSurface>;
|
|
27
|
+
/**
|
|
28
|
+
* Refuse channel routes the control plane would swallow.
|
|
29
|
+
*
|
|
30
|
+
* `router` refuses this too, and would catch it a moment later; this exists for the sentence, not
|
|
31
|
+
* the check — an author who lands a route under `/control` needs to hear about `sessionControl`,
|
|
32
|
+
* which the host has no way to mention. Both ask {@link pathUnderPrefix}, so there is one rule with
|
|
33
|
+
* two wordings, not two rules.
|
|
34
|
+
*
|
|
35
|
+
* Called from BOTH mount points: here at boot, and again on agentcore's lazy path, whose channels
|
|
36
|
+
* load after this ran against an empty base.
|
|
37
|
+
*/
|
|
38
|
+
export declare function assertNoControlPlaneCollision(channelRoutes: Routes, plane: PrefixMount): void;
|
|
39
|
+
export declare function mountSessionControl(routes: Routes, control: SessionControl | undefined, stateRoot: string, options?: {
|
|
40
|
+
tunnel?: boolean;
|
|
41
|
+
agent?: Agent;
|
|
42
|
+
host?: string;
|
|
43
|
+
}): {
|
|
44
|
+
routes: Routes;
|
|
45
|
+
mounts: PrefixMount[];
|
|
46
|
+
/** The plane's bearer token and prefix — how an embedder distributes access without a discovery file. */
|
|
47
|
+
control?: {
|
|
48
|
+
token: string;
|
|
49
|
+
prefix: string;
|
|
50
|
+
};
|
|
51
|
+
/** Write the local discovery file; returns its removal. Installs no signal handlers. */
|
|
52
|
+
announce: (boundPort: number) => () => void;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Load and start the agent's `schedules/` — a time-trigger firing the agent on each cron. Starts iff
|
|
56
|
+
* there are static schedules OR `selfSchedule` is on. Best-effort stop on process signals. Returns the
|
|
57
|
+
* loaded schedules so a serving surface that needs them (the AgentCore adapter's fire binding) shares
|
|
58
|
+
* ONE load instead of re-discovering. `externalClock` (AgentCore) arms no resident cron timers.
|
|
59
|
+
*/
|
|
60
|
+
export declare function startSchedules(agentDir: string, agent: Agent, stateRoot: string, selfSchedule: boolean, options?: {
|
|
61
|
+
externalClock?: boolean;
|
|
62
|
+
}): Promise<{
|
|
63
|
+
schedules: LoadedSchedule[];
|
|
64
|
+
stop: () => void;
|
|
65
|
+
}>;
|
|
66
|
+
export interface AgentService {
|
|
67
|
+
/** The assembled Fetch handler: channel routes, the control plane, and health. Mount it wherever
|
|
68
|
+
* your host speaks `(Request) => Response`; `nodeListener` bridges it to Node's `(req, res)`. */
|
|
69
|
+
handler: ChannelHandler;
|
|
70
|
+
/** The agent behind it — invoke it directly when you also want a programmatic path. */
|
|
71
|
+
agent: Agent;
|
|
72
|
+
/** The literal routes `handler` was composed from — for a startup line naming what is served.
|
|
73
|
+
* Mounted prefixes are not here: nothing outside the assembly needed them, and a field kept for a
|
|
74
|
+
* hypothetical caller is a field nobody maintains. */
|
|
75
|
+
routes: Routes;
|
|
76
|
+
agentDir: string;
|
|
77
|
+
workspace: string;
|
|
78
|
+
/** What actually mounted, for a startup line: channel files serving routes, long connections, and
|
|
79
|
+
* whether the built-in `POST /invoke` fallback is one of the routes. That last one is a FACT of
|
|
80
|
+
* the assembly, not something to re-infer from a path — a channel may legally author
|
|
81
|
+
* `POST /invoke` with a protocol of its own. */
|
|
82
|
+
channels: {
|
|
83
|
+
routes: string[];
|
|
84
|
+
longConnections: string[];
|
|
85
|
+
builtinInvoke: boolean;
|
|
86
|
+
};
|
|
87
|
+
schedules: readonly LoadedSchedule[];
|
|
88
|
+
/** Settles when every long connection is up — immediately when there are none. REJECTS if one
|
|
89
|
+
* fails to come up, after closing the service: a host must not report itself serving while a
|
|
90
|
+
* declared channel is dead, and health answers 503 until this resolves. */
|
|
91
|
+
ready: Promise<void>;
|
|
92
|
+
/** The control plane's bearer token and prefix, when `sessionControl` is on — how an embedder
|
|
93
|
+
* hands access to a client without a discovery file. */
|
|
94
|
+
control?: {
|
|
95
|
+
token: string;
|
|
96
|
+
prefix: string;
|
|
97
|
+
};
|
|
98
|
+
/** Write `<stateRoot>/control.json` so a LOCAL client (`fastagent attach`) can find the plane,
|
|
99
|
+
* once the port is known. Optional: an embedder mounted inside a larger app has no port of its
|
|
100
|
+
* own to describe and uses {@link AgentService.control} instead.
|
|
101
|
+
*
|
|
102
|
+
* Removed by `close()`. Not by an `exit` handler: installing one is a decision about the whole
|
|
103
|
+
* process, which a mounted library does not get to make. A hard exit therefore leaves the file
|
|
104
|
+
* behind — advisory, overwritten by the next boot, and the client's own error stays honest. */
|
|
105
|
+
announce(boundPort: number): void;
|
|
106
|
+
/** Stop long connections and schedules. Idempotent; also runs when `options.signal` aborts. */
|
|
107
|
+
close(): Promise<void>;
|
|
108
|
+
}
|
|
109
|
+
/** What {@link mountAgentService} needs beyond an opened directory. */
|
|
110
|
+
export interface MountAgentServiceOptions {
|
|
111
|
+
/** Wrap the agent before anything consumes it — every consumer (routes, control plane, schedules)
|
|
112
|
+
* must get the SAME one, which is why this is a hook rather than the caller's own call. `dev`
|
|
113
|
+
* passes `logAgentLoop`. */
|
|
114
|
+
wrapAgent?: (agent: Agent) => Agent;
|
|
115
|
+
/** Passed through to the control plane mount: `--tunnel` widens its warning, `host` names the
|
|
116
|
+
* bind address in the discovery file. */
|
|
117
|
+
control?: {
|
|
118
|
+
tunnel?: boolean;
|
|
119
|
+
host?: string;
|
|
120
|
+
};
|
|
121
|
+
/** Aborting this closes the service, exactly like calling {@link AgentService.close}. */
|
|
122
|
+
signal?: AbortSignal;
|
|
123
|
+
/** Called when a long connection ends on its own — a dropped socket-mode channel, say. The CLI
|
|
124
|
+
* exits; an embedded host may prefer to log. Default: log an error. */
|
|
125
|
+
onChannelClosed?: (name: string, error?: unknown) => void;
|
|
126
|
+
/** How long `close()` waits for a channel to stop before reporting it stuck (default 5s). The CLI
|
|
127
|
+
* shortens it so its own forced exit lands after this answer, not before it. */
|
|
128
|
+
closeTimeoutMs?: number;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* What the assembly needs from an opened agent directory — the whole of it. Spelled as its own type
|
|
132
|
+
* rather than an engine's return shape: every field here is either the SPEC contract or a path, so
|
|
133
|
+
* an engine that is not pi can satisfy it without either side knowing about the other.
|
|
134
|
+
*/
|
|
135
|
+
export interface MountableAgent {
|
|
136
|
+
agent: Agent;
|
|
137
|
+
/** The definition dir: where channels/, tools/ and schedules/ are read from. */
|
|
138
|
+
agentDir: string;
|
|
139
|
+
/** The agent's cwd. */
|
|
140
|
+
workspace: string;
|
|
141
|
+
/** Where durable state lives (channel state, sessions, schedule fires). */
|
|
142
|
+
stateRoot: string;
|
|
143
|
+
/** Present iff this agent published a control plane. */
|
|
144
|
+
sessionControl?: SessionControl;
|
|
145
|
+
/** Whether the agent schedules its own follow-up turns. REQUIRED, not optional-with-a-default:
|
|
146
|
+
* an engine that forgot it would turn self-scheduling off silently, which is exactly the bug
|
|
147
|
+
* this type was introduced with. */
|
|
148
|
+
selfSchedule: boolean;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The assembly itself, over an already-opened directory: channels, the control plane, schedules and
|
|
152
|
+
* long connections, composed into one handler.
|
|
153
|
+
*
|
|
154
|
+
* {@link createAgentService} is this plus opening the directory. `dev`/`start` open separately — their
|
|
155
|
+
* startup report needs the opened values before anything mounts — and then arrive here, so there is
|
|
156
|
+
* one assembly rather than one per caller.
|
|
157
|
+
*/
|
|
158
|
+
export declare function mountAgentService(opened: MountableAgent, options?: MountAgentServiceOptions): Promise<AgentService>;
|