@fastagent-sh/fastagent 0.18.0 → 0.20.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 +1 -1
- package/dist/agent.js +1 -1
- package/dist/atomic-write.d.ts +23 -0
- package/dist/atomic-write.js +53 -0
- package/dist/{host/node.d.ts → channel.d.ts} +24 -28
- package/dist/channel.js +1 -0
- package/dist/channels/agentcore-limits.d.ts +9 -0
- package/dist/channels/agentcore-limits.js +9 -0
- package/dist/channels/agentcore-service.d.ts +55 -0
- package/dist/channels/agentcore-service.js +107 -0
- package/dist/channels/agentcore.d.ts +22 -20
- package/dist/channels/agentcore.js +20 -11
- package/dist/channels/busy.d.ts +1 -1
- package/dist/channels/busy.js +1 -1
- package/dist/channels/control.d.ts +55 -7
- package/dist/channels/control.js +312 -67
- package/dist/channels/discover.d.ts +59 -0
- package/dist/{engines/pi/channel.js → channels/discover.js} +19 -34
- 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.js +6 -9
- package/dist/channels/feishu/feishu.d.ts +1 -1
- package/dist/channels/feishu/feishu.js +41 -24
- package/dist/channels/feishu/invoke-turn.d.ts +1 -1
- package/dist/channels/feishu/invoke-turn.js +3 -4
- 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 +1 -109
- package/dist/channels/kit/attachment-path.d.ts +12 -0
- package/dist/channels/kit/attachment-path.js +43 -0
- package/dist/channels/{context-buffer.d.ts → kit/context-buffer.d.ts} +9 -0
- package/dist/channels/{context-buffer.js → kit/context-buffer.js} +13 -2
- package/dist/channels/{invoke-turn-kit.d.ts → kit/invoke-turn-kit.d.ts} +14 -3
- package/dist/channels/{invoke-turn-kit.js → kit/invoke-turn-kit.js} +17 -6
- 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/signature.d.ts +12 -0
- package/dist/channels/kit/signature.js +17 -0
- package/dist/channels/{state.js → kit/state.js} +4 -7
- package/dist/channels/{stop-command.d.ts → kit/stop-command.d.ts} +1 -1
- package/dist/channels/{stop-command.js → kit/stop-command.js} +5 -5
- package/dist/channels/{tasks.d.ts → kit/tasks.d.ts} +1 -1
- package/dist/channels/{tasks.js → kit/tasks.js} +11 -5
- package/dist/channels/{thread-participants.js → kit/thread-participants.js} +1 -1
- package/dist/channels/{turn-queue.js → kit/turn-queue.js} +2 -2
- package/dist/channels/{turn-store.d.ts → kit/turn-store.d.ts} +19 -3
- package/dist/channels/{turn-store.js → kit/turn-store.js} +30 -3
- 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 +2 -2
- package/dist/channels/slack/invoke-turn.js +13 -6
- package/dist/channels/slack/onboard.js +5 -5
- package/dist/channels/slack/onboarding-state.d.ts +7 -3
- package/dist/channels/slack/onboarding-state.js +13 -20
- 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/register-webhook.js +1 -1
- package/dist/channels/slack/scaffold/slack-send.ts +2 -17
- package/dist/channels/slack/slack-api.js +23 -55
- package/dist/channels/slack/slack.d.ts +1 -1
- package/dist/channels/slack/slack.js +20 -20
- 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 +1 -1
- package/dist/channels/telegram/parse.d.ts +21 -1
- package/dist/channels/telegram/parse.js +67 -13
- package/dist/channels/telegram/preview.d.ts +1 -1
- package/dist/channels/telegram/preview.js +6 -2
- package/dist/channels/telegram/scaffold/channel.ts +7 -3
- package/dist/channels/telegram/telegram-api.js +5 -6
- package/dist/channels/telegram/telegram.d.ts +3 -3
- package/dist/channels/telegram/telegram.js +60 -72
- package/dist/channels/telegram/turn-store.d.ts +2 -2
- package/dist/channels/telegram/turn-store.js +2 -2
- package/dist/cli/add-slack.js +6 -5
- package/dist/cli/commands/add.js +1 -5
- package/dist/cli/commands/attach.d.ts +2 -4
- package/dist/cli/commands/attach.js +17 -15
- package/dist/cli/commands/deploy.d.ts +50 -5
- package/dist/cli/commands/deploy.js +183 -134
- package/dist/cli/commands/dev.js +25 -40
- package/dist/cli/commands/fire.js +1 -1
- package/dist/cli/commands/info.js +7 -5
- package/dist/cli/commands/init.js +1 -2
- package/dist/cli/commands/schedule.js +1 -1
- package/dist/cli/commands/start.js +80 -121
- package/dist/cli/commands/tool.js +5 -7
- package/dist/cli/program.js +1 -1
- package/dist/cli/serve.d.ts +22 -70
- package/dist/cli/serve.js +66 -257
- package/dist/cli/shared.d.ts +40 -10
- package/dist/cli/shared.js +53 -6
- package/dist/core.d.ts +3 -5
- package/dist/core.js +7 -5
- package/dist/deploy/agentcore/logs.d.ts +2 -2
- package/dist/deploy/agentcore/logs.js +2 -2
- package/dist/deploy/agentcore/plan.d.ts +12 -10
- package/dist/deploy/agentcore/plan.js +39 -31
- package/dist/deploy/agentcore/run.d.ts +5 -4
- package/dist/deploy/agentcore/run.js +13 -36
- package/dist/deploy/channel-ingress.d.ts +73 -0
- package/dist/deploy/channel-ingress.js +101 -0
- package/dist/deploy/docker/plan.d.ts +3 -7
- package/dist/deploy/docker/plan.js +4 -15
- package/dist/deploy/docker/run.d.ts +18 -1
- package/dist/deploy/docker/run.js +10 -0
- package/dist/deploy/fly/plan.d.ts +4 -5
- package/dist/deploy/fly/plan.js +7 -22
- package/dist/deploy/fly/run.d.ts +9 -23
- package/dist/deploy/fly/run.js +32 -68
- package/dist/deploy/preflight.d.ts +6 -7
- package/dist/deploy/preflight.js +27 -23
- package/dist/deploy/railway/plan.d.ts +4 -5
- package/dist/deploy/railway/plan.js +11 -26
- package/dist/deploy/railway/run.d.ts +9 -9
- package/dist/deploy/railway/run.js +40 -40
- package/dist/deploy/runner.js +22 -2
- package/dist/deploy/secrets.d.ts +18 -9
- package/dist/deploy/secrets.js +64 -12
- package/dist/dev-supervisor.js +11 -6
- package/dist/engines/pi/agent-session-factory.d.ts +104 -0
- package/dist/engines/pi/agent-session-factory.js +295 -0
- package/dist/engines/pi/auth.js +43 -43
- package/dist/engines/pi/config.d.ts +5 -5
- package/dist/engines/pi/config.js +1 -1
- package/dist/engines/pi/create.d.ts +67 -42
- package/dist/engines/pi/create.js +142 -72
- package/dist/engines/pi/definition.d.ts +42 -8
- package/dist/engines/pi/definition.js +114 -11
- package/dist/engines/pi/invoke-session.d.ts +22 -19
- package/dist/engines/pi/invoke-session.js +286 -100
- package/dist/engines/pi/login.js +39 -20
- package/dist/engines/pi/models.d.ts +14 -0
- package/dist/engines/pi/models.js +9 -1
- package/dist/engines/pi/open.d.ts +10 -8
- package/dist/engines/pi/open.js +21 -11
- package/dist/engines/pi/report.d.ts +0 -4
- package/dist/engines/pi/report.js +0 -7
- package/dist/engines/pi/search-tools.js +1 -1
- package/dist/engines/pi/service.d.ts +23 -0
- package/dist/engines/pi/service.js +27 -0
- package/dist/engines/pi/session-builder.js +87 -118
- package/dist/engines/pi/session-control.d.ts +28 -14
- package/dist/engines/pi/session-control.js +570 -438
- package/dist/engines/pi/session-inheritance.d.ts +30 -0
- package/dist/engines/pi/session-inheritance.js +261 -0
- package/dist/engines/pi/session-markers.d.ts +48 -0
- package/dist/engines/pi/session-markers.js +59 -0
- package/dist/engines/pi/session-settings.d.ts +15 -3
- package/dist/engines/pi/session-settings.js +22 -1
- package/dist/engines/pi/session-store.d.ts +140 -0
- package/dist/engines/pi/session-store.js +666 -0
- package/dist/engines/pi/tool-context.d.ts +36 -13
- package/dist/engines/pi/tool-context.js +51 -4
- package/dist/engines/pi/tool.d.ts +5 -5
- package/dist/engines/pi/tool.js +3 -3
- package/dist/engines/pi/turn-kit.d.ts +32 -6
- package/dist/engines/pi/turn-kit.js +28 -1
- package/dist/engines/pi/wake-tool.js +1 -1
- package/dist/env.js +1 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/loader.d.ts +49 -7
- package/dist/loader.js +79 -18
- 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 +26 -0
- package/dist/paths.js +37 -2
- package/dist/pi.d.ts +14 -8
- package/dist/pi.js +6 -5
- package/dist/scaffold/add-channel.js +2 -2
- package/dist/scaffold/init.js +7 -2
- package/dist/scaffold/templates/persona.md +2 -2
- package/dist/scaffold/templates/tools/fetch-url.ts +0 -2
- package/dist/schedule/discover.js +3 -15
- package/dist/schedule/state.js +4 -9
- package/dist/schedule/wake-alarm.d.ts +13 -7
- package/dist/schedule/wake-alarm.js +88 -46
- package/dist/schedule/wakeups.d.ts +2 -2
- package/dist/schedule/wakeups.js +11 -8
- package/dist/service.d.ts +157 -0
- package/dist/service.js +376 -0
- package/dist/session-remote.d.ts +8 -3
- package/dist/session-remote.js +162 -92
- package/dist/session.d.ts +229 -93
- package/dist/session.js +61 -23
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +1 -1
- package/dist/tunnel.d.ts +18 -3
- package/dist/tunnel.js +32 -40
- package/package.json +14 -5
- package/dist/engines/pi/channel.d.ts +0 -37
- package/dist/engines/pi/harness.d.ts +0 -115
- package/dist/engines/pi/harness.js +0 -154
- package/dist/engines/pi/invoke.d.ts +0 -60
- package/dist/engines/pi/invoke.js +0 -459
- 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 -60
- package/dist/engines/pi/sessions.js +0 -464
- package/dist/host/node.js +0 -52
- /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/{state.d.ts → kit/state.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/{thread-participants.d.ts → kit/thread-participants.d.ts} +0 -0
- /package/dist/channels/{turn-queue.d.ts → kit/turn-queue.d.ts} +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* closures. Deploy-time ambients a tool closes over at build time do NOT belong here.
|
|
5
5
|
*/
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
|
-
import type {
|
|
7
|
+
import type { SessionEntry as PiSessionEntry, AgentSession } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
/** FastAgent's read-only port over the current conversation manager. Serving and chat adapt their
|
|
9
9
|
* different concrete session implementations to this one tool-runtime contract. */
|
|
10
10
|
export interface ReadonlySessionManager {
|
|
@@ -13,13 +13,22 @@ export interface ReadonlySessionManager {
|
|
|
13
13
|
id: string;
|
|
14
14
|
timestamp: string;
|
|
15
15
|
}>;
|
|
16
|
-
getBranch(): Promise<
|
|
16
|
+
getBranch(): Promise<PiSessionEntry[]>;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* pi's AgentSession as the port above — the SAME adapter for both of its consumers: chat's resident
|
|
20
|
+
* session (session-builder.ts) and serving's per-invoke one (agent-session-factory.ts).
|
|
21
|
+
*
|
|
22
|
+
* `sessionId` is the CALLER's, not pi's. A tool correlates its own state by the id the channel
|
|
23
|
+
* minted; pi's is that id encoded into a filename-safe record name, and leaking the encoding here
|
|
24
|
+
* would hand a telegram tool `s-1001234567890` for a room it knows as `-1001234567890`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function agentSessionManager(session: AgentSession, sessionId: string): ReadonlySessionManager;
|
|
27
|
+
/**
|
|
28
|
+
* The turn's tool-activation bridge — narrow closures over the CURRENT session (bound per turn), so
|
|
29
|
+
* a loader tool can activate deferred tools mid-turn without tool.ts importing the engine. pi records
|
|
30
|
+
* the change in the session (`active_tools_change`) and the per-invoke restore
|
|
31
|
+
* (agent-session-factory.ts) carries it into later turns; defineTool's wrapper stamps the newly-activated names on
|
|
23
32
|
* the tool result (`addedToolNames`) — the load point native deferred-loading providers preserve the
|
|
24
33
|
* prompt-cache prefix with.
|
|
25
34
|
*/
|
|
@@ -32,21 +41,35 @@ export interface ToolActivation {
|
|
|
32
41
|
description: string;
|
|
33
42
|
}>;
|
|
34
43
|
/** ADDITIVE activation. Unknown names are filtered out before reaching pi (whose `setActiveTools`
|
|
35
|
-
* THROWS on them);
|
|
36
|
-
|
|
44
|
+
* THROWS on them); answers the names actually newly activated (already-active names don't repeat).
|
|
45
|
+
*
|
|
46
|
+
* SYNCHRONOUS, and that is the contract, not an implementation detail: read-modify-write against
|
|
47
|
+
* pi's active set cannot be interleaved as long as no caller can await inside it. An async
|
|
48
|
+
* signature would need a lock to say the same thing, and the lock is what a previous version had
|
|
49
|
+
* — one rebuilt per tool call, so the parallel batch it existed for never met on it. If pi's
|
|
50
|
+
* setters ever become async, this signature is where that breaks, loudly. */
|
|
51
|
+
activate(names: string[]): string[];
|
|
37
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The activation bridge over a live pi session — the ONE implementation, for both consumers.
|
|
55
|
+
*
|
|
56
|
+
* Serving (`agent-session-factory.ts`) and chat (`session-builder.ts`) had a copy each, identical
|
|
57
|
+
* but for the persistence line; the neighbouring `definitionResourceLoaderOptions` exists because
|
|
58
|
+
* that exact duplication drifted once before. The difference is a PARAMETER now: `onActivated` is
|
|
59
|
+
* what a served session uses to record the delta that carries the discovery into its next turn,
|
|
60
|
+
* and chat has nowhere to put one (pi's SessionContext has no active-tool set).
|
|
61
|
+
*
|
|
62
|
+
* Bind it to the SESSION, never to a tool call: the next call has to see what this one activated.
|
|
63
|
+
*/
|
|
64
|
+
export declare function sessionToolActivation(session: AgentSession, onActivated?: (added: string[]) => void): ToolActivation;
|
|
38
65
|
export interface TurnContext {
|
|
39
66
|
/** Working directory for this execution. Falls back to process.cwd() only for an unbound direct call. */
|
|
40
67
|
cwd?: string;
|
|
41
68
|
/** Current conversation manager. Absent outside a FastAgent-managed agent turn. */
|
|
42
69
|
sessionManager?: ReadonlySessionManager;
|
|
43
70
|
/** Tool activation for the current turn. Two producers, one consumer surface: invoke.ts bridges the
|
|
44
|
-
*
|
|
71
|
+
* served session; chat.ts bridges the resident one (chat emulates deferral — same loader, same
|
|
45
72
|
* semantics). Absent only outside any turn (a bare `fastagent tool` run). */
|
|
46
73
|
tools?: ToolActivation;
|
|
47
74
|
}
|
|
48
75
|
export declare const turnContext: AsyncLocalStorage<TurnContext>;
|
|
49
|
-
/** The additive-activation contract, in ONE place for both bridges (invoke.ts over the harness,
|
|
50
|
-
* chat.ts over pi's AgentSession): dedupe → keep registered names only (pi's setters THROW on
|
|
51
|
-
* unknown) → exclude already-active → the names to actually add (empty = nothing to set). */
|
|
52
|
-
export declare function additiveActivation(registered: string[], current: string[], names: string[]): string[];
|
|
@@ -4,11 +4,58 @@
|
|
|
4
4
|
* closures. Deploy-time ambients a tool closes over at build time do NOT belong here.
|
|
5
5
|
*/
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
|
+
/**
|
|
8
|
+
* pi's AgentSession as the port above — the SAME adapter for both of its consumers: chat's resident
|
|
9
|
+
* session (session-builder.ts) and serving's per-invoke one (agent-session-factory.ts).
|
|
10
|
+
*
|
|
11
|
+
* `sessionId` is the CALLER's, not pi's. A tool correlates its own state by the id the channel
|
|
12
|
+
* minted; pi's is that id encoded into a filename-safe record name, and leaking the encoding here
|
|
13
|
+
* would hand a telegram tool `s-1001234567890` for a room it knows as `-1001234567890`.
|
|
14
|
+
*/
|
|
15
|
+
export function agentSessionManager(session, sessionId) {
|
|
16
|
+
return {
|
|
17
|
+
getSessionId: () => sessionId,
|
|
18
|
+
async getHeader() {
|
|
19
|
+
const header = session.sessionManager.getHeader();
|
|
20
|
+
if (!header)
|
|
21
|
+
throw new Error("session has no metadata header");
|
|
22
|
+
return { id: sessionId, timestamp: header.timestamp };
|
|
23
|
+
},
|
|
24
|
+
async getBranch() {
|
|
25
|
+
return session.sessionManager.getBranch();
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The activation bridge over a live pi session — the ONE implementation, for both consumers.
|
|
31
|
+
*
|
|
32
|
+
* Serving (`agent-session-factory.ts`) and chat (`session-builder.ts`) had a copy each, identical
|
|
33
|
+
* but for the persistence line; the neighbouring `definitionResourceLoaderOptions` exists because
|
|
34
|
+
* that exact duplication drifted once before. The difference is a PARAMETER now: `onActivated` is
|
|
35
|
+
* what a served session uses to record the delta that carries the discovery into its next turn,
|
|
36
|
+
* and chat has nowhere to put one (pi's SessionContext has no active-tool set).
|
|
37
|
+
*
|
|
38
|
+
* Bind it to the SESSION, never to a tool call: the next call has to see what this one activated.
|
|
39
|
+
*/
|
|
40
|
+
export function sessionToolActivation(session, onActivated) {
|
|
41
|
+
return {
|
|
42
|
+
active: () => session.getActiveToolNames(),
|
|
43
|
+
registered: () => session.getAllTools().map((t) => ({ name: t.name, description: t.description ?? "" })),
|
|
44
|
+
activate(names) {
|
|
45
|
+
const current = session.getActiveToolNames();
|
|
46
|
+
const added = additiveActivation(session.getAllTools().map((t) => t.name), current, names);
|
|
47
|
+
if (added.length === 0)
|
|
48
|
+
return added;
|
|
49
|
+
session.setActiveToolsByName([...current, ...added]);
|
|
50
|
+
onActivated?.(added);
|
|
51
|
+
return added;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
7
55
|
export const turnContext = new AsyncLocalStorage();
|
|
8
|
-
/**
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
export function additiveActivation(registered, current, names) {
|
|
56
|
+
/** dedupe → keep registered names only (pi's setters THROW on unknown) → exclude already-active →
|
|
57
|
+
* the names to actually add (empty = nothing to set). */
|
|
58
|
+
function additiveActivation(registered, current, names) {
|
|
12
59
|
const known = new Set(registered);
|
|
13
60
|
const active = new Set(current);
|
|
14
61
|
return [...new Set(names)].filter((name) => known.has(name) && !active.has(name));
|
|
@@ -11,7 +11,7 @@ export interface ToolContext {
|
|
|
11
11
|
sessionManager?: ReadonlySessionManager;
|
|
12
12
|
/** Tool activation for the current turn (a loader tool activates {@link DefineToolOptions.deferred}
|
|
13
13
|
* tools with it — the built-in `search_tools` is one consumer). Provided by both the serving path
|
|
14
|
-
* (
|
|
14
|
+
* (agent-session-factory.ts) and chat (session-builder.ts); undefined only outside any turn
|
|
15
15
|
* (a bare `fastagent tool` run). */
|
|
16
16
|
tools?: ToolActivation;
|
|
17
17
|
}
|
|
@@ -35,9 +35,9 @@ export interface DefineToolOptions<I extends z.ZodType> {
|
|
|
35
35
|
execute: (input: z.infer<I>, ctx: ToolContext) => unknown | Promise<unknown>;
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* A tool as MOUNTED: what the
|
|
39
|
-
* purpose —
|
|
40
|
-
*
|
|
38
|
+
* A tool as MOUNTED: what the engine actually runs. Wider than the authored {@link AgentTool} on
|
|
39
|
+
* purpose — a lower-level mounted tool may read an `ExecutionEnv` from the fifth `execute` parameter,
|
|
40
|
+
* while coding-agent's cwd-bound tools ignore it and fastagent's authored tools take four arguments.
|
|
41
41
|
* Naming the wider type is what lets `defineTool` stay context-free for authors while both families
|
|
42
42
|
* live in one array; every helper that only inspects or reorders tools is typed on THIS.
|
|
43
43
|
*/
|
|
@@ -73,7 +73,7 @@ export declare function loadTools(dir: string): Promise<{
|
|
|
73
73
|
failures: ModuleLoadFailure[];
|
|
74
74
|
}>;
|
|
75
75
|
/**
|
|
76
|
-
* Merge resolved tools (pi
|
|
76
|
+
* Merge resolved tools (pi coding tools + `config.tools`) with discovered `tools/`, deduped by name.
|
|
77
77
|
* Existing tools win; dropped discovered tools surface as collisions.
|
|
78
78
|
*/
|
|
79
79
|
export declare function mergeDiscoveredTools(existing: MountedTool[], discovered: AgentTool[]): {
|
package/dist/engines/pi/tool.js
CHANGED
|
@@ -62,9 +62,9 @@ export function defineTool(options) {
|
|
|
62
62
|
const tools = store?.tools
|
|
63
63
|
? {
|
|
64
64
|
...store.tools,
|
|
65
|
-
activate:
|
|
65
|
+
activate: (names) => {
|
|
66
66
|
// biome-ignore lint/style/noNonNullAssertion: guarded by the ternary above
|
|
67
|
-
const activated =
|
|
67
|
+
const activated = store.tools.activate(names);
|
|
68
68
|
added.push(...activated);
|
|
69
69
|
return activated;
|
|
70
70
|
},
|
|
@@ -116,7 +116,7 @@ export async function loadTools(dir) {
|
|
|
116
116
|
return { tools: [...byName.values()], collisions, failures };
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
119
|
-
* Merge resolved tools (pi
|
|
119
|
+
* Merge resolved tools (pi coding tools + `config.tools`) with discovered `tools/`, deduped by name.
|
|
120
120
|
* Existing tools win; dropped discovered tools surface as collisions.
|
|
121
121
|
*/
|
|
122
122
|
export function mergeDiscoveredTools(existing, discovered) {
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The turn mechanism's
|
|
3
|
-
* repo's sense — that term is reserved for code with no engine import at
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* The turn mechanism's ENGINE-agnostic half: the parts that describe a turn rather than pi. Not
|
|
3
|
+
* "engine-neutral" in this repo's sense — that term is reserved for code with no engine import at
|
|
4
|
+
* all (src/agent.ts), and the terminals here read pi's message shape. What they do not touch is how
|
|
5
|
+
* a turn is driven, which is why they survived the engine change unaltered.
|
|
6
6
|
*
|
|
7
7
|
* Lease — single-writer concurrency floor (injectable port + in-process default)
|
|
8
8
|
* Terminals — a settled pi message or a thrown error → the SPEC terminal, `retryable` included
|
|
9
9
|
* EventQueue — push→pull plumbing for engines that emit events beside their result
|
|
10
10
|
* Prompt prep — SPEC images → pi's ImageContent
|
|
11
|
+
* Projection — the rich SessionEvent stream → the narrow SPEC one
|
|
12
|
+
* Observation — the seam a control-plane hub attaches to (RunControls + SessionObserver)
|
|
11
13
|
*
|
|
12
|
-
* What is NOT neutral —
|
|
13
|
-
*
|
|
14
|
+
* What is NOT neutral — pi's event vocabulary and how a turn is driven — stays in invoke-session.ts,
|
|
15
|
+
* the L0 that owns it.
|
|
14
16
|
*/
|
|
15
17
|
import type { AssistantMessage, ImageContent } from "@earendil-works/pi-ai";
|
|
16
18
|
import { type AgentEvent, type Prompt } from "../../agent.ts";
|
|
19
|
+
import type { SessionEvent } from "../../session.ts";
|
|
17
20
|
export type Release = () => void;
|
|
18
21
|
export interface Lease {
|
|
19
22
|
/** Try to acquire exclusive write access for the session (fail-fast). Returns null if held. */
|
|
@@ -43,6 +46,29 @@ export declare function errorToTerminal(error: unknown): Extract<AgentEvent, {
|
|
|
43
46
|
export declare function toPiPromptOptions(prompt: Prompt): Promise<{
|
|
44
47
|
images?: ImageContent[];
|
|
45
48
|
} | undefined>;
|
|
49
|
+
/** Live modulation handles for one active run — what the control plane's `dispatch` routes to.
|
|
50
|
+
* Built inside the turn (it owns the engine instance); registered with the observer at
|
|
51
|
+
* run_started, gone after run_settled. RACE WINDOW (all three commands, symmetric): the run may
|
|
52
|
+
* resolve between the settled-check and the engine call landing — an accepted `abort` can still
|
|
53
|
+
* settle `completed`, and an accepted `steer`/`followUp` can settle without the prompt ever being
|
|
54
|
+
* consumed. Acceptance is not outcome; the settlement is the truth. */
|
|
55
|
+
export interface RunControls {
|
|
56
|
+
steer(prompt: Prompt): Promise<void>;
|
|
57
|
+
followUp(prompt: Prompt): Promise<void>;
|
|
58
|
+
abort(): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
/** The DATA-plane observation seam: every rich event of every run, pushed as it happens. `run`
|
|
61
|
+
* carries the live {@link RunControls}, attached to the `run_started` event only. A hub
|
|
62
|
+
* (session-control.ts) implements this to serve `events()`/`state()`/`dispatch`; absent = zero
|
|
63
|
+
* overhead. Scope: RUN events only — the hub's own boundary-mutation events (`state_changed`,
|
|
64
|
+
* `compaction_*`) originate in the hub and reach full-vocabulary taps via the hub's `tap` option,
|
|
65
|
+
* not this seam. TRUST BOUNDARY: this seam hands every wired observer the run's modulation handles — it is the trusted hub seam, not a public fan-out point. Do not wire
|
|
66
|
+
* untrusted taps here; give third parties the read-only `events()` stream instead. */
|
|
67
|
+
export type SessionObserver = (session: string, event: SessionEvent, run?: RunControls) => void;
|
|
68
|
+
/** The SPEC projection of the rich stream. Events with no `AgentEvent` counterpart (progress,
|
|
69
|
+
* message boundaries, run boundaries) project to null — the invoke terminal is produced from the
|
|
70
|
+
* resolved message ({@link toTerminal}), not from `run_settled`. */
|
|
71
|
+
export declare function projectAgentEvent(se: SessionEvent): AgentEvent | null;
|
|
46
72
|
export declare class EventQueue<T> {
|
|
47
73
|
private buffer;
|
|
48
74
|
private wake?;
|
|
@@ -82,7 +82,7 @@ function messageSignal(message) {
|
|
|
82
82
|
*/
|
|
83
83
|
export function toTerminal(message) {
|
|
84
84
|
if (message.stopReason === "aborted") {
|
|
85
|
-
// A deliberate stop (control-plane
|
|
85
|
+
// A deliberate stop (a control-plane or consumer abort), not an error — see {@link ABORTED_CODE}
|
|
86
86
|
// for the consumer contract (design §6).
|
|
87
87
|
const details = message.errorMessage ?? "run aborted";
|
|
88
88
|
return { type: "failed", details, retryable: false, code: ABORTED_CODE };
|
|
@@ -119,6 +119,33 @@ export async function toPiPromptOptions(prompt) {
|
|
|
119
119
|
}));
|
|
120
120
|
return { images };
|
|
121
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
|
+
}
|
|
122
149
|
// ── EventQueue: push→pull plumbing for a two-port engine ────────────────────
|
|
123
150
|
//
|
|
124
151
|
// Single-consumer async queue; single-threaded JS means no await interleaves between push and
|
|
@@ -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/env.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
4
3
|
import { log } from "./log.js";
|
|
5
4
|
import { SECRETS_DIRNAME, resolveSecretsDir } from "./paths.js";
|
|
6
5
|
/**
|
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/loader.d.ts
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/** One module file a directory declares. `name` is what the domain knows it by; nothing is imported
|
|
2
|
+
* to produce this. */
|
|
3
|
+
export interface InventoryEntry {
|
|
4
|
+
/** Basename without extension — the authoritative name for tools/channels/schedules. */
|
|
5
|
+
name: string;
|
|
6
|
+
/** "tools/foo.ts"-style label for errors and collisions. */
|
|
7
|
+
label: string;
|
|
8
|
+
file: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* WHAT A CODE-INPUT DIRECTORY DECLARES — the single answer to "which files here are modules",
|
|
12
|
+
* without importing any of them.
|
|
13
|
+
*
|
|
14
|
+
* It exists because four consumers need that answer and only one of them may import: the loader
|
|
15
|
+
* below, `fastagent info`'s channel listing, `--tunnel`'s webhook registration, and the deploy
|
|
16
|
+
* pre-flight's schedule probe. When they each read the directory themselves they disagreed — on
|
|
17
|
+
* what counts as a module file, on how to strip the extension, on which errno means "no such
|
|
18
|
+
* directory" — and a fix had to be applied in four places, which is why the fourth kept being
|
|
19
|
+
* missed. There is one reading now; what to DO with a failure stays with the caller, because that
|
|
20
|
+
* genuinely differs (the loader throws, the tunnel cannot).
|
|
21
|
+
*
|
|
22
|
+
* A missing directory is an empty inventory. Everything else throws, ENOTDIR included: `channels`
|
|
23
|
+
* and `schedules` are named directories, so the path existing as a FILE is a mistake in the agent
|
|
24
|
+
* rather than an agent without one (`service.test.ts` pins that for `schedules`). `paths.ts` folds
|
|
25
|
+
* ENOTDIR into its empty scan for the opposite reason — it asks which children HAPPEN to be agent
|
|
26
|
+
* dirs, where a file simply is not one. `not_found` is a non-Node runtime's ENOENT.
|
|
27
|
+
*
|
|
28
|
+
* SYMLINKS ARE SKIPPED, and that is a boundary rather than an oversight: `assertInsideAgentDir`
|
|
29
|
+
* guards the code-input DIRECTORY against escaping the definition, and nothing guards the entries
|
|
30
|
+
* inside it, so following a link would import from anywhere on the box past the very check meant to
|
|
31
|
+
* prevent it. Do not "fix" this by following them — report them, which this does.
|
|
32
|
+
*
|
|
33
|
+
* The skip is WARNED HERE, not handed back, because "this file is not loadable" holds for all four
|
|
34
|
+
* consumers and only one of them imports — a listing that reports the name it cannot load reads as
|
|
35
|
+
* "I never created it". What to do about a load FAILURE does differ per caller, so that travels as
|
|
36
|
+
* data. One warning per READ, so a command that both lists and loads the same directory (`deploy
|
|
37
|
+
* agentcore` does) says it twice — both readings are true, and remembering what was already said
|
|
38
|
+
* would mean a restart stops mentioning a skip that is still there.
|
|
39
|
+
*
|
|
40
|
+
* A skip is therefore a warning ONLY, unlike a {@link ModuleLoadFailure}: it is absent from `info
|
|
41
|
+
* --json` and `deploy` does not gate on it. That is the deliberate cost of one report for four
|
|
42
|
+
* consumers, three of which list names and have nowhere to put data. To give a machine consumer the
|
|
43
|
+
* skips, return them beside the entries — do not reconstruct them from stderr.
|
|
44
|
+
*/
|
|
45
|
+
export declare function moduleInventory(subDir: string): Promise<InventoryEntry[]>;
|
|
3
46
|
export interface DiscoveredModule {
|
|
4
47
|
/** Basename without extension — the authoritative name for tools/channels. */
|
|
5
48
|
name: string;
|
|
@@ -21,11 +64,10 @@ export interface ModuleLoadFailure {
|
|
|
21
64
|
message: string;
|
|
22
65
|
}
|
|
23
66
|
/**
|
|
24
|
-
* Import every module
|
|
25
|
-
*
|
|
26
|
-
* report every bad file and apply domain policy; `loadTools`/`loadChannels` add validation failures
|
|
27
|
-
* same way.
|
|
28
|
-
* — that's not a per-file problem.)
|
|
67
|
+
* Import every module the directory declares ({@link moduleInventory}). A file that fails to IMPORT
|
|
68
|
+
* is collected into `failures` (with {@link moduleLoadHint}) rather than thrown, so the caller can
|
|
69
|
+
* report every bad file and apply domain policy; `loadTools`/`loadChannels` add validation failures
|
|
70
|
+
* the same way. Entries the inventory SKIPPED are already reported by it, for every consumer.
|
|
29
71
|
*/
|
|
30
72
|
export declare function loadModuleDir(subDir: string): Promise<{
|
|
31
73
|
modules: DiscoveredModule[];
|
package/dist/loader.js
CHANGED
|
@@ -9,40 +9,101 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
import { readdir } from "node:fs/promises";
|
|
10
10
|
import { basename, extname, join } from "node:path";
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
|
+
import { log } from "./log.js";
|
|
12
13
|
const MODULE_EXTS = new Set([".ts", ".js", ".mjs"]);
|
|
13
14
|
/** Whether `name` is an importable agent module (a discovery candidate, not a type declaration). */
|
|
14
|
-
|
|
15
|
+
function isModuleFile(name) {
|
|
15
16
|
return MODULE_EXTS.has(extname(name)) && !name.endsWith(".d.ts");
|
|
16
17
|
}
|
|
18
|
+
/** The name a module is known by: its basename without the extension. */
|
|
19
|
+
function moduleName(fileName) {
|
|
20
|
+
return basename(fileName, extname(fileName));
|
|
21
|
+
}
|
|
17
22
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
* WHAT A CODE-INPUT DIRECTORY DECLARES — the single answer to "which files here are modules",
|
|
24
|
+
* without importing any of them.
|
|
25
|
+
*
|
|
26
|
+
* It exists because four consumers need that answer and only one of them may import: the loader
|
|
27
|
+
* below, `fastagent info`'s channel listing, `--tunnel`'s webhook registration, and the deploy
|
|
28
|
+
* pre-flight's schedule probe. When they each read the directory themselves they disagreed — on
|
|
29
|
+
* what counts as a module file, on how to strip the extension, on which errno means "no such
|
|
30
|
+
* directory" — and a fix had to be applied in four places, which is why the fourth kept being
|
|
31
|
+
* missed. There is one reading now; what to DO with a failure stays with the caller, because that
|
|
32
|
+
* genuinely differs (the loader throws, the tunnel cannot).
|
|
33
|
+
*
|
|
34
|
+
* A missing directory is an empty inventory. Everything else throws, ENOTDIR included: `channels`
|
|
35
|
+
* and `schedules` are named directories, so the path existing as a FILE is a mistake in the agent
|
|
36
|
+
* rather than an agent without one (`service.test.ts` pins that for `schedules`). `paths.ts` folds
|
|
37
|
+
* ENOTDIR into its empty scan for the opposite reason — it asks which children HAPPEN to be agent
|
|
38
|
+
* dirs, where a file simply is not one. `not_found` is a non-Node runtime's ENOENT.
|
|
39
|
+
*
|
|
40
|
+
* SYMLINKS ARE SKIPPED, and that is a boundary rather than an oversight: `assertInsideAgentDir`
|
|
41
|
+
* guards the code-input DIRECTORY against escaping the definition, and nothing guards the entries
|
|
42
|
+
* inside it, so following a link would import from anywhere on the box past the very check meant to
|
|
43
|
+
* prevent it. Do not "fix" this by following them — report them, which this does.
|
|
44
|
+
*
|
|
45
|
+
* The skip is WARNED HERE, not handed back, because "this file is not loadable" holds for all four
|
|
46
|
+
* consumers and only one of them imports — a listing that reports the name it cannot load reads as
|
|
47
|
+
* "I never created it". What to do about a load FAILURE does differ per caller, so that travels as
|
|
48
|
+
* data. One warning per READ, so a command that both lists and loads the same directory (`deploy
|
|
49
|
+
* agentcore` does) says it twice — both readings are true, and remembering what was already said
|
|
50
|
+
* would mean a restart stops mentioning a skip that is still there.
|
|
51
|
+
*
|
|
52
|
+
* A skip is therefore a warning ONLY, unlike a {@link ModuleLoadFailure}: it is absent from `info
|
|
53
|
+
* --json` and `deploy` does not gate on it. That is the deliberate cost of one report for four
|
|
54
|
+
* consumers, three of which list names and have nowhere to put data. To give a machine consumer the
|
|
55
|
+
* skips, return them beside the entries — do not reconstruct them from stderr.
|
|
23
56
|
*/
|
|
24
|
-
export async function
|
|
25
|
-
let
|
|
57
|
+
export async function moduleInventory(subDir) {
|
|
58
|
+
let dirents;
|
|
26
59
|
try {
|
|
27
|
-
|
|
60
|
+
dirents = await readdir(subDir, { withFileTypes: true });
|
|
28
61
|
}
|
|
29
62
|
catch (error) {
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
return
|
|
63
|
+
const code = error.code;
|
|
64
|
+
if (code === "ENOENT" || code === "not_found")
|
|
65
|
+
return [];
|
|
33
66
|
throw new Error(`cannot read ${subDir}: ${error.message}`);
|
|
34
67
|
}
|
|
35
68
|
const sub = basename(subDir);
|
|
69
|
+
const entries = [];
|
|
70
|
+
// Sorted by the NAME a consumer sees, so none of them re-sorts and none can disagree about order.
|
|
71
|
+
// Filename breaks a tie: `foo.js` and `foo.ts` both read as `foo`, and the domain loaders document
|
|
72
|
+
// that the FIRST wins — deciding that here keeps it from depending on readdir's order.
|
|
73
|
+
const byName = (a, b) => moduleName(a.name).localeCompare(moduleName(b.name)) || a.name.localeCompare(b.name);
|
|
74
|
+
for (const dirent of dirents.sort(byName)) {
|
|
75
|
+
if (!isModuleFile(dirent.name))
|
|
76
|
+
continue;
|
|
77
|
+
const label = `${sub}/${dirent.name}`;
|
|
78
|
+
if (dirent.isFile()) {
|
|
79
|
+
entries.push({ name: moduleName(dirent.name), label, file: join(subDir, dirent.name) });
|
|
80
|
+
}
|
|
81
|
+
else if (dirent.isSymbolicLink()) {
|
|
82
|
+
log.warn(`[fastagent] ${label} is a symlink — code inputs must be real files inside the agent dir — not loaded`);
|
|
83
|
+
}
|
|
84
|
+
else if (dirent.isDirectory()) {
|
|
85
|
+
log.warn(`[fastagent] ${label} is a directory, not a file — not loaded`);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
log.warn(`[fastagent] ${label} is not a regular file — not loaded`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return entries;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Import every module the directory declares ({@link moduleInventory}). A file that fails to IMPORT
|
|
95
|
+
* is collected into `failures` (with {@link moduleLoadHint}) rather than thrown, so the caller can
|
|
96
|
+
* report every bad file and apply domain policy; `loadTools`/`loadChannels` add validation failures
|
|
97
|
+
* the same way. Entries the inventory SKIPPED are already reported by it, for every consumer.
|
|
98
|
+
*/
|
|
99
|
+
export async function loadModuleDir(subDir) {
|
|
100
|
+
const entries = await moduleInventory(subDir);
|
|
36
101
|
const modules = [];
|
|
37
102
|
const failures = [];
|
|
38
|
-
for (const
|
|
39
|
-
if (!entry.isFile() || !isModuleFile(entry.name))
|
|
40
|
-
continue;
|
|
41
|
-
const file = join(subDir, entry.name);
|
|
42
|
-
const label = `${sub}/${entry.name}`;
|
|
103
|
+
for (const { name, label, file } of entries) {
|
|
43
104
|
try {
|
|
44
105
|
const mod = (await import(__rewriteRelativeImportExtension(pathToFileURL(file).href)));
|
|
45
|
-
modules.push({ name
|
|
106
|
+
modules.push({ name, label, file, mod });
|
|
46
107
|
}
|
|
47
108
|
catch (error) {
|
|
48
109
|
failures.push({
|
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
|
@@ -136,9 +136,35 @@ export declare function resolveStateRoot(dir: string, env?: NodeJS.ProcessEnv):
|
|
|
136
136
|
* auth.json but cannot move the file it is read from (env.ts dotEnvPath).
|
|
137
137
|
*/
|
|
138
138
|
export declare function resolveSecretsDir(dir: string, env?: NodeJS.ProcessEnv): string;
|
|
139
|
+
/** What a file under {@link resolveSecretsDir} is written with (auth.json, .env). */
|
|
140
|
+
export declare const SECRET_FILE_MODE = 384;
|
|
141
|
+
/**
|
|
142
|
+
* Create the secrets directory with the mode its contents require — and REPAIR it when it already
|
|
143
|
+
* exists, which is the case that matters.
|
|
144
|
+
*
|
|
145
|
+
* The DIRECTORY is the boundary that actually protects a credential: without its `x` bit nothing
|
|
146
|
+
* below it is reachable, whatever a file's own mode says. And it is decided ONCE, by whichever
|
|
147
|
+
* writer gets there first — `mkdir`'s `mode` is ignored for a directory that already exists, so a
|
|
148
|
+
* later, more careful caller silently inherits the first one's answer. Four callers create this
|
|
149
|
+
* directory (`init`, `add <channel>`, the credential store, the deploy seed) and the ordinary order
|
|
150
|
+
* is init → add → login, so the careful one is LAST: the rule has to live where all of them can
|
|
151
|
+
* reach it, and it has to chmod rather than trust the create.
|
|
152
|
+
*
|
|
153
|
+
* A chmod the caller never asked for owes them its reason: the raw `EPERM ... chmod '/shared/creds'`
|
|
154
|
+
* reads as a bug in whatever they WERE doing (storing a credential), not as fastagent tightening a
|
|
155
|
+
* directory they pointed it at, and says nothing about the way out.
|
|
156
|
+
*/
|
|
157
|
+
export declare function ensureSecretsDir(dir: string): Promise<void>;
|
|
139
158
|
/**
|
|
140
159
|
* Guard that `<agentDir>/<name>` resolves INSIDE the agent dir — a symlink that escapes (or an
|
|
141
160
|
* absolute target) is rejected, so discovery/scaffolding never reaches out of the definition directory.
|
|
142
161
|
* A missing target is fine (nothing to guard yet).
|
|
143
162
|
*/
|
|
144
163
|
export declare function assertInsideAgentDir(agentDir: string, name: string): Promise<void>;
|
|
164
|
+
/**
|
|
165
|
+
* Whether `targetPath` lives inside `baseDir` (same path counts). Used to ask "did an override move
|
|
166
|
+
* this OUT of the agent?" — the startup report's redeploy notes, `add`'s printed `.env` label, and the
|
|
167
|
+
* dev watcher's "your .env is not watched" warning all turn on that fact. Reporting only: fastagent
|
|
168
|
+
* does not act on where a user's paths point.
|
|
169
|
+
*/
|
|
170
|
+
export declare function isUnderDir(targetPath: string, baseDir: string): boolean;
|