@fastagent-sh/fastagent 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -6
- package/dist/agent.d.ts +20 -0
- package/dist/agent.js +9 -0
- package/dist/channels/agentcore-limits.d.ts +7 -0
- package/dist/channels/agentcore-limits.js +9 -0
- package/dist/channels/agentcore-state.d.ts +83 -0
- package/dist/channels/agentcore-state.js +258 -0
- package/dist/channels/agentcore.d.ts +98 -0
- package/dist/channels/agentcore.js +260 -0
- package/dist/channels/busy.d.ts +23 -0
- package/dist/channels/busy.js +53 -0
- package/dist/channels/context-buffer.d.ts +35 -0
- package/dist/channels/context-buffer.js +103 -0
- package/dist/channels/control.d.ts +28 -0
- package/dist/channels/control.js +214 -0
- package/dist/channels/feishu/card.js +1 -1
- package/dist/channels/feishu/context-buffer.d.ts +15 -13
- package/dist/channels/feishu/context-buffer.js +62 -69
- package/dist/channels/feishu/feishu-api.d.ts +11 -3
- package/dist/channels/feishu/feishu-api.js +24 -3
- package/dist/channels/feishu/feishu.d.ts +27 -27
- package/dist/channels/feishu/feishu.js +318 -207
- package/dist/channels/feishu/invoke-turn.d.ts +7 -20
- package/dist/channels/feishu/invoke-turn.js +44 -79
- package/dist/channels/feishu/model.d.ts +2 -2
- package/dist/channels/feishu/normalize.d.ts +2 -1
- package/dist/channels/feishu/normalize.js +0 -1
- package/dist/channels/feishu/parse.d.ts +22 -8
- package/dist/channels/feishu/parse.js +24 -7
- package/dist/channels/feishu/preview.d.ts +4 -7
- package/dist/channels/feishu/preview.js +27 -142
- package/dist/channels/feishu/register-app.d.ts +2 -1
- package/dist/channels/feishu/register-webhook.d.ts +2 -1
- package/dist/channels/feishu/scaffold/channel.ts +9 -8
- package/dist/channels/feishu/scaffold/feishu-send.ts +6 -4
- package/dist/channels/feishu/setup-mode.d.ts +37 -0
- package/dist/channels/feishu/setup-mode.js +28 -0
- package/dist/channels/feishu/ws-ingress.d.ts +28 -0
- package/dist/channels/feishu/ws-ingress.js +136 -0
- package/dist/channels/github/github.js +16 -7
- package/dist/channels/http.d.ts +14 -0
- package/dist/channels/http.js +36 -3
- package/dist/channels/invoke-turn-kit.d.ts +65 -0
- package/dist/channels/invoke-turn-kit.js +87 -0
- package/dist/channels/lark/lark.d.ts +4 -2
- package/dist/channels/lark/lark.js +4 -1
- package/dist/channels/lark/onboard.d.ts +8 -4
- package/dist/channels/lark/onboard.js +8 -0
- package/dist/channels/lark/scaffold/channel.ts +9 -8
- package/dist/channels/lark/scaffold/lark-send.ts +6 -4
- package/dist/channels/preview-kit.d.ts +115 -0
- package/dist/channels/preview-kit.js +184 -0
- package/dist/channels/seen.d.ts +5 -0
- package/dist/channels/seen.js +35 -0
- package/dist/channels/slack/bot-auth.d.ts +15 -0
- package/dist/channels/slack/bot-auth.js +146 -0
- package/dist/channels/slack/config-api.d.ts +60 -0
- package/dist/channels/slack/config-api.js +149 -0
- package/dist/channels/slack/context-buffer.d.ts +24 -0
- package/dist/channels/slack/context-buffer.js +37 -0
- package/dist/channels/slack/invoke-turn.d.ts +19 -0
- package/dist/channels/slack/invoke-turn.js +63 -0
- package/dist/channels/slack/manifest.d.ts +49 -0
- package/dist/channels/slack/manifest.js +69 -0
- package/dist/channels/slack/model.d.ts +67 -0
- package/dist/channels/slack/model.js +2 -0
- package/dist/channels/slack/onboard.d.ts +41 -0
- package/dist/channels/slack/onboard.js +120 -0
- package/dist/channels/slack/onboarding-state.d.ts +31 -0
- package/dist/channels/slack/onboarding-state.js +69 -0
- package/dist/channels/slack/parse.d.ts +38 -0
- package/dist/channels/slack/parse.js +124 -0
- package/dist/channels/slack/preview.d.ts +23 -0
- package/dist/channels/slack/preview.js +403 -0
- package/dist/channels/slack/reaction.d.ts +24 -0
- package/dist/channels/slack/reaction.js +62 -0
- package/dist/channels/slack/register-webhook.d.ts +10 -0
- package/dist/channels/slack/register-webhook.js +49 -0
- package/dist/channels/slack/scaffold/channel.ts +33 -0
- package/dist/channels/slack/scaffold/slack-send.ts +171 -0
- package/dist/channels/slack/setup-server.d.ts +17 -0
- package/dist/channels/slack/setup-server.js +103 -0
- package/dist/channels/slack/slack-api.d.ts +57 -0
- package/dist/channels/slack/slack-api.js +399 -0
- package/dist/channels/slack/slack.d.ts +51 -0
- package/dist/channels/slack/slack.js +496 -0
- package/dist/channels/slack/welcomed.d.ts +5 -0
- package/dist/channels/slack/welcomed.js +32 -0
- package/dist/channels/state.d.ts +11 -4
- package/dist/channels/state.js +22 -15
- package/dist/channels/stop-command.d.ts +6 -0
- package/dist/channels/stop-command.js +36 -0
- package/dist/channels/tasks.d.ts +7 -0
- package/dist/channels/tasks.js +25 -0
- package/dist/channels/telegram/context-buffer.d.ts +8 -17
- package/dist/channels/telegram/context-buffer.js +6 -85
- package/dist/channels/telegram/invoke-turn.d.ts +5 -22
- package/dist/channels/telegram/invoke-turn.js +11 -58
- package/dist/channels/telegram/parse.d.ts +0 -7
- package/dist/channels/telegram/parse.js +4 -2
- package/dist/channels/telegram/preview.d.ts +4 -7
- package/dist/channels/telegram/preview.js +24 -142
- package/dist/channels/telegram/scaffold/telegram-send.ts +6 -3
- package/dist/channels/telegram/telegram.js +24 -10
- package/dist/channels/{feishu/text.d.ts → text.d.ts} +15 -1
- package/dist/channels/{feishu/text.js → text.js} +15 -1
- package/dist/channels/thread-participants.d.ts +21 -0
- package/dist/channels/thread-participants.js +132 -0
- package/dist/channels/turn-queue.js +8 -1
- package/dist/channels/turn-store.d.ts +1 -1
- package/dist/channels/turn-store.js +2 -3
- package/dist/cli/add-feishu.d.ts +30 -0
- package/dist/cli/add-feishu.js +366 -0
- package/dist/cli/add-slack.d.ts +11 -0
- package/dist/cli/add-slack.js +199 -0
- package/dist/{cli-auth.d.ts → cli/auth-view.d.ts} +1 -1
- package/dist/{cli-auth.js → cli/auth-view.js} +1 -1
- package/dist/cli/commands/add.d.ts +4 -0
- package/dist/cli/commands/add.js +191 -60
- package/dist/cli/commands/attach.d.ts +82 -0
- package/dist/cli/commands/attach.js +563 -0
- package/dist/cli/commands/chat.d.ts +1 -0
- package/dist/cli/commands/chat.js +12 -7
- package/dist/cli/commands/deploy.d.ts +3 -1
- package/dist/cli/commands/deploy.js +384 -82
- package/dist/cli/commands/dev.js +34 -26
- package/dist/cli/commands/fire.js +17 -18
- package/dist/cli/commands/info.js +36 -29
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.js +65 -53
- package/dist/cli/commands/invoke.js +11 -9
- package/dist/cli/commands/login.js +48 -45
- package/dist/cli/commands/models.js +1 -1
- package/dist/cli/commands/schedule.js +6 -8
- package/dist/cli/commands/start.js +102 -39
- package/dist/cli/commands/tool.js +23 -18
- package/dist/cli/fail.d.ts +17 -2
- package/dist/cli/fail.js +25 -1
- package/dist/{invoke-stream.d.ts → cli/invoke-stream.d.ts} +1 -1
- package/dist/{invoke-stream.js → cli/invoke-stream.js} +4 -0
- package/dist/cli/kernel.d.ts +3 -2
- package/dist/cli/models-view.d.ts +21 -0
- package/dist/cli/models-view.js +66 -0
- package/dist/cli/program.js +135 -56
- package/dist/cli/serve.d.ts +60 -20
- package/dist/cli/serve.js +252 -47
- package/dist/cli/shared.d.ts +40 -7
- package/dist/cli/shared.js +180 -38
- package/dist/collect.d.ts +14 -3
- package/dist/collect.js +24 -0
- package/dist/core.d.ts +3 -1
- package/dist/core.js +2 -0
- package/dist/deploy/agentcore/plan.d.ts +117 -0
- package/dist/deploy/agentcore/plan.js +721 -0
- package/dist/deploy/agentcore/run.d.ts +73 -0
- package/dist/deploy/agentcore/run.js +412 -0
- package/dist/deploy/agentcore/zip.d.ts +17 -0
- package/dist/deploy/agentcore/zip.js +68 -0
- package/dist/deploy/container.d.ts +26 -25
- package/dist/deploy/container.js +93 -89
- package/dist/deploy/docker/plan.d.ts +3 -3
- package/dist/deploy/docker/plan.js +20 -23
- package/dist/deploy/fly/plan.d.ts +5 -1
- package/dist/deploy/fly/plan.js +39 -26
- package/dist/deploy/fly/run.d.ts +17 -5
- package/dist/deploy/fly/run.js +52 -7
- package/dist/deploy/preflight.d.ts +20 -8
- package/dist/deploy/preflight.js +267 -71
- package/dist/deploy/railway/plan.d.ts +10 -1
- package/dist/deploy/railway/plan.js +53 -20
- package/dist/deploy/railway/run.d.ts +13 -5
- package/dist/deploy/railway/run.js +22 -7
- package/dist/deploy/runner.d.ts +7 -3
- package/dist/deploy/runner.js +9 -3
- package/dist/deploy/secrets.d.ts +2 -1
- package/dist/deploy/secrets.js +23 -3
- package/dist/dev-supervisor.d.ts +11 -10
- package/dist/dev-supervisor.js +56 -50
- package/dist/engines/pi/auth.d.ts +8 -7
- package/dist/engines/pi/auth.js +12 -10
- package/dist/engines/pi/channel.d.ts +22 -16
- package/dist/engines/pi/channel.js +92 -62
- package/dist/engines/pi/chat.d.ts +4 -16
- package/dist/engines/pi/chat.js +8 -261
- package/dist/engines/pi/config.d.ts +20 -55
- package/dist/engines/pi/config.js +49 -121
- package/dist/engines/pi/create.d.ts +30 -10
- package/dist/engines/pi/create.js +33 -26
- package/dist/engines/pi/definition.d.ts +7 -26
- package/dist/engines/pi/definition.js +8 -54
- package/dist/engines/pi/harness.d.ts +49 -0
- package/dist/engines/pi/harness.js +90 -2
- package/dist/engines/pi/invoke.d.ts +55 -2
- package/dist/engines/pi/invoke.js +304 -19
- package/dist/engines/pi/login.d.ts +12 -1
- package/dist/engines/pi/login.js +16 -4
- package/dist/engines/pi/models.d.ts +59 -13
- package/dist/engines/pi/models.js +62 -24
- package/dist/engines/pi/open.d.ts +114 -0
- package/dist/engines/pi/open.js +127 -0
- package/dist/engines/pi/session-builder.d.ts +16 -0
- package/dist/engines/pi/session-builder.js +308 -0
- package/dist/engines/pi/session-control.d.ts +50 -0
- package/dist/engines/pi/session-control.js +604 -0
- package/dist/engines/pi/sessions.d.ts +17 -2
- package/dist/engines/pi/sessions.js +9 -0
- package/dist/engines/pi/tool-context.d.ts +18 -11
- package/dist/engines/pi/tool-context.js +3 -9
- package/dist/engines/pi/tool.d.ts +6 -8
- package/dist/engines/pi/tool.js +10 -1
- package/dist/engines/pi/wake-tool.d.ts +0 -3
- package/dist/engines/pi/wake-tool.js +9 -7
- package/dist/env.d.ts +16 -4
- package/dist/env.js +43 -5
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/host/node.d.ts +25 -8
- package/dist/host/node.js +5 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lark.d.ts +1 -1
- package/dist/lark.js +1 -1
- package/dist/loader.d.ts +2 -2
- package/dist/loader.js +3 -3
- package/dist/log.d.ts +1 -1
- package/dist/log.js +1 -1
- package/dist/observe.js +3 -0
- package/dist/paths.d.ts +138 -0
- package/dist/paths.js +326 -0
- package/dist/pi.d.ts +6 -5
- package/dist/pi.js +4 -3
- package/dist/runtime.d.ts +7 -5
- package/dist/runtime.js +2 -2
- package/dist/scaffold/add-channel.d.ts +26 -9
- package/dist/scaffold/add-channel.js +175 -40
- package/dist/scaffold/init.d.ts +32 -41
- package/dist/scaffold/init.js +161 -185
- package/dist/scaffold/templates/env.example +15 -6
- package/dist/scaffold/templates/fastagent.config.mjs +5 -4
- package/dist/scaffold/templates/gitignore +14 -6
- package/dist/scaffold/templates/persona.md +4 -2
- package/dist/scaffold/templates/secrets.gitignore +5 -0
- package/dist/scaffold/templates.d.ts +1 -7
- package/dist/scaffold/templates.js +3 -25
- package/dist/scaffold/vendor-skill.d.ts +2 -2
- package/dist/scaffold/vendor-skill.js +13 -13
- package/dist/schedule/discover.js +4 -4
- package/dist/schedule/scheduler.d.ts +40 -1
- package/dist/schedule/scheduler.js +89 -56
- package/dist/schedule/state.js +1 -1
- package/dist/schedule/wake-alarm.d.ts +47 -0
- package/dist/schedule/wake-alarm.js +136 -0
- package/dist/schedule/wakeups.d.ts +1 -3
- package/dist/schedule/wakeups.js +19 -1
- package/dist/session-remote.d.ts +53 -0
- package/dist/session-remote.js +336 -0
- package/dist/session.d.ts +265 -0
- package/dist/session.js +37 -0
- package/dist/slack.d.ts +2 -0
- package/dist/slack.js +2 -0
- package/dist/tunnel.d.ts +7 -4
- package/dist/tunnel.js +23 -12
- package/package.json +22 -7
- package/dist/channels/feishu/owned-threads.d.ts +0 -11
- package/dist/channels/feishu/owned-threads.js +0 -47
- package/dist/channels/feishu/seen.d.ts +0 -5
- package/dist/channels/feishu/seen.js +0 -47
- package/dist/cli-add-feishu.d.ts +0 -8
- package/dist/cli-add-feishu.js +0 -223
- package/dist/cli-models.d.ts +0 -11
- package/dist/cli-models.js +0 -20
- package/dist/engines/pi/workspace.d.ts +0 -56
- package/dist/engines/pi/workspace.js +0 -72
- package/dist/scaffold/templates/gitignore.agentdir-root +0 -5
- package/dist/scaffold/templates/gitignore.kit +0 -2
- package/dist/workspace.d.ts +0 -9
- package/dist/workspace.js +0 -45
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `execute` (tool.ts). Undefined outside a turn (e.g. `fastagent tool`, which runs a tool with no
|
|
6
|
-
* session). This is what lets a tool know which conversation it is in — the mechanism the agent's
|
|
7
|
-
* self-scheduling `wake` tool needs to fire a later turn back into the SAME session.
|
|
8
|
-
*
|
|
9
|
-
* Only `session` lives here (a per-turn runtime value). Deploy-time ambients a tool closes over at
|
|
10
|
-
* build time (e.g. a stateRoot) do NOT belong here — pass them via the tool's own closure.
|
|
2
|
+
* Per-turn capabilities shared by every FastAgent-defined tool. A tool is built once and reused across
|
|
3
|
+
* turns, so current cwd/session/activation bindings ride AsyncLocalStorage rather than definition
|
|
4
|
+
* closures. Deploy-time ambients a tool closes over at build time do NOT belong here.
|
|
11
5
|
*/
|
|
12
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
|
+
import type { SessionTreeEntry } from "@earendil-works/pi-agent-core";
|
|
8
|
+
/** FastAgent's read-only port over the current conversation manager. Serving and chat adapt their
|
|
9
|
+
* different concrete session implementations to this one tool-runtime contract. */
|
|
10
|
+
export interface ReadonlySessionManager {
|
|
11
|
+
getSessionId(): string;
|
|
12
|
+
getHeader(): Promise<{
|
|
13
|
+
id: string;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
}>;
|
|
16
|
+
getBranch(): Promise<SessionTreeEntry[]>;
|
|
17
|
+
}
|
|
13
18
|
/**
|
|
14
19
|
* The turn's tool-activation bridge — narrow closures over the CURRENT harness (invoke.ts builds it
|
|
15
20
|
* per turn), so a loader tool can activate deferred tools mid-turn without tool.ts importing the
|
|
@@ -31,8 +36,10 @@ export interface ToolActivation {
|
|
|
31
36
|
activate(names: string[]): Promise<string[]>;
|
|
32
37
|
}
|
|
33
38
|
export interface TurnContext {
|
|
34
|
-
/**
|
|
35
|
-
|
|
39
|
+
/** Working directory for this execution. Falls back to process.cwd() only for an unbound direct call. */
|
|
40
|
+
cwd?: string;
|
|
41
|
+
/** Current conversation manager. Absent outside a FastAgent-managed agent turn. */
|
|
42
|
+
sessionManager?: ReadonlySessionManager;
|
|
36
43
|
/** Tool activation for the current turn. Two producers, one consumer surface: invoke.ts bridges the
|
|
37
44
|
* serving harness; chat.ts bridges pi's AgentSession (chat emulates deferral — same loader, same
|
|
38
45
|
* semantics). Absent only outside any turn (a bare `fastagent tool` run). */
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `execute` (tool.ts). Undefined outside a turn (e.g. `fastagent tool`, which runs a tool with no
|
|
6
|
-
* session). This is what lets a tool know which conversation it is in — the mechanism the agent's
|
|
7
|
-
* self-scheduling `wake` tool needs to fire a later turn back into the SAME session.
|
|
8
|
-
*
|
|
9
|
-
* Only `session` lives here (a per-turn runtime value). Deploy-time ambients a tool closes over at
|
|
10
|
-
* build time (e.g. a stateRoot) do NOT belong here — pass them via the tool's own closure.
|
|
2
|
+
* Per-turn capabilities shared by every FastAgent-defined tool. A tool is built once and reused across
|
|
3
|
+
* turns, so current cwd/session/activation bindings ride AsyncLocalStorage rather than definition
|
|
4
|
+
* closures. Deploy-time ambients a tool closes over at build time do NOT belong here.
|
|
11
5
|
*/
|
|
12
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
7
|
export const turnContext = new AsyncLocalStorage();
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { type ModuleLoadFailure } from "../../loader.ts";
|
|
4
|
-
import { type ToolActivation } from "./tool-context.ts";
|
|
4
|
+
import { type ReadonlySessionManager, type ToolActivation } from "./tool-context.ts";
|
|
5
5
|
export interface ToolContext {
|
|
6
|
+
/** Working directory for this execution. */
|
|
7
|
+
cwd: string;
|
|
6
8
|
/** Abort signal for the current turn — honor it to cancel in-flight work on cancellation. */
|
|
7
9
|
signal?: AbortSignal;
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
* `fastagent tool` run, or any call with no session). (The built-in `wake` tool is one consumer — it
|
|
11
|
-
* fires a later turn back into this same session.) In a `fastagent chat` turn this is pi's LOCAL
|
|
12
|
-
* chat session id, not a served session — serving-coupled consumers like wake are not mounted there. */
|
|
13
|
-
session?: string;
|
|
10
|
+
/** Current conversation manager. Present during serving/chat; absent for sessionless direct calls. */
|
|
11
|
+
sessionManager?: ReadonlySessionManager;
|
|
14
12
|
/** Tool activation for the current turn (a loader tool activates {@link DefineToolOptions.deferred}
|
|
15
13
|
* tools with it — the built-in `search_tools` is one consumer). Provided by both the serving path
|
|
16
14
|
* (invoke.ts, over the harness) and chat (over pi's AgentSession); undefined only outside any turn
|
|
@@ -48,7 +46,7 @@ export declare function isDeferredTool(tool: AgentTool): boolean;
|
|
|
48
46
|
/** The same tool without the deferred marker — for a loader that must stay active (a deferred loader
|
|
49
47
|
* could never be activated and would strand every deferred tool). */
|
|
50
48
|
export declare function stripDeferredMarker(tool: AgentTool): AgentTool;
|
|
51
|
-
export declare function defineTool<I extends z.ZodType>(options: DefineToolOptions<I>):
|
|
49
|
+
export declare function defineTool<I extends z.ZodType>(options: DefineToolOptions<I>): FastagentTool;
|
|
52
50
|
/** A discarded same-name tool (within `tools/`, or against an existing tool). Surfaced, never silent. */
|
|
53
51
|
export interface ToolCollision {
|
|
54
52
|
name: string;
|
package/dist/engines/pi/tool.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* });
|
|
12
12
|
*/
|
|
13
13
|
import { join } from "node:path";
|
|
14
|
+
import { assertInsideAgentDir } from "../../paths.js";
|
|
14
15
|
import { z } from "zod";
|
|
15
16
|
import { loadModuleDir } from "../../loader.js";
|
|
16
17
|
import { turnContext } from "./tool-context.js";
|
|
@@ -69,7 +70,12 @@ export function defineTool(options) {
|
|
|
69
70
|
},
|
|
70
71
|
}
|
|
71
72
|
: undefined;
|
|
72
|
-
const result = wrapResult(await options.execute(parsed.data, {
|
|
73
|
+
const result = wrapResult(await options.execute(parsed.data, {
|
|
74
|
+
cwd: store?.cwd ?? process.cwd(),
|
|
75
|
+
signal,
|
|
76
|
+
sessionManager: store?.sessionManager,
|
|
77
|
+
tools,
|
|
78
|
+
}));
|
|
73
79
|
if (added.length > 0) {
|
|
74
80
|
// A copy, not a mutation: wrapResult passes a full AgentToolResult through by REFERENCE, and an
|
|
75
81
|
// author may legally return a shared/frozen result object — stamping in place would corrupt it
|
|
@@ -89,6 +95,9 @@ export function defineTool(options) {
|
|
|
89
95
|
* `tools/` dir of its OWN scripts, which is exactly this case.
|
|
90
96
|
*/
|
|
91
97
|
export async function loadTools(dir) {
|
|
98
|
+
// The same containment guard channels/schedules/skills get — and `tools/` is the one that gets
|
|
99
|
+
// IMPORTED AND EXECUTED, so a symlink escaping the agent dir is exactly what it must refuse.
|
|
100
|
+
await assertInsideAgentDir(dir, "tools");
|
|
92
101
|
const { modules, failures } = await loadModuleDir(join(dir, "tools"));
|
|
93
102
|
const byName = new Map();
|
|
94
103
|
const collisions = [];
|
|
@@ -15,6 +15,3 @@ export declare function parseDelayMs(input: string | number): number | undefined
|
|
|
15
15
|
export declare function withWakeTool(tools: AgentTool[], stateRoot: string, enabled: boolean): AgentTool[];
|
|
16
16
|
/** Build the `wake` tool bound to `stateRoot` (where wake-ups persist). */
|
|
17
17
|
export declare function makeWakeTool(stateRoot: string, now?: () => Date): AgentTool;
|
|
18
|
-
/** Build the `unwake` tool: cancel one of THIS conversation's pending wake-ups by id (a wake/recurring
|
|
19
|
-
* that is no longer needed). Session-scoped — a conversation can never cancel another's. */
|
|
20
|
-
export declare function makeUnwakeTool(stateRoot: string): AgentTool;
|
|
@@ -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.
|
|
5
|
+
* context (ToolContext.sessionManager, set around the harness 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
|
|
@@ -67,14 +67,15 @@ export function makeWakeTool(stateRoot, now = () => new Date()) {
|
|
|
67
67
|
prompt: z.string().min(1).describe("the instruction for the woken turn (runs in this same conversation)"),
|
|
68
68
|
}),
|
|
69
69
|
execute(input, ctx) {
|
|
70
|
-
|
|
70
|
+
const session = ctx.sessionManager?.getSessionId();
|
|
71
|
+
if (!session)
|
|
71
72
|
return "wake is only available inside a conversation (there is no session to resume).";
|
|
72
73
|
if ((input.in === undefined) === (input.cron === undefined)) {
|
|
73
74
|
return "pass exactly one of `in` (one-shot) or `cron` (recurring).";
|
|
74
75
|
}
|
|
75
76
|
if (input.cron !== undefined) {
|
|
76
77
|
// addWakeup validates the cron and DERIVES the first instant itself — one computation, one truth.
|
|
77
|
-
const r = addWakeup(stateRoot, { session
|
|
78
|
+
const r = addWakeup(stateRoot, { session, prompt: input.prompt, cron: input.cron, tz: input.tz }, now());
|
|
78
79
|
if (!r.ok)
|
|
79
80
|
return r.error; // guardrail message the model can act on
|
|
80
81
|
return `OK — recurring wake ${r.id} (cron "${input.cron}"${input.tz ? ` ${input.tz}` : ""}), first at ${r.fireAt}: ${input.prompt}. Use unwake({ id: "${r.id}" }) to stop it.`;
|
|
@@ -84,7 +85,7 @@ export function makeWakeTool(stateRoot, now = () => new Date()) {
|
|
|
84
85
|
return `couldn't parse "in" (${JSON.stringify(input.in)}) — use a unit like "30m" / "2h" / "1d", or a number of seconds (a bare number as text like "120" is rejected).`;
|
|
85
86
|
}
|
|
86
87
|
const at = new Date(now().getTime() + ms);
|
|
87
|
-
const r = addWakeup(stateRoot, { session
|
|
88
|
+
const r = addWakeup(stateRoot, { session, prompt: input.prompt, fireAt: at }, now());
|
|
88
89
|
if (!r.ok)
|
|
89
90
|
return r.error; // guardrail message the model can act on
|
|
90
91
|
return `OK — I'll wake up at ${r.fireAt} (id ${r.id}) to: ${input.prompt}. Use unwake({ id: "${r.id}" }) if it becomes unnecessary.`;
|
|
@@ -93,16 +94,17 @@ export function makeWakeTool(stateRoot, now = () => new Date()) {
|
|
|
93
94
|
}
|
|
94
95
|
/** Build the `unwake` tool: cancel one of THIS conversation's pending wake-ups by id (a wake/recurring
|
|
95
96
|
* that is no longer needed). Session-scoped — a conversation can never cancel another's. */
|
|
96
|
-
|
|
97
|
+
function makeUnwakeTool(stateRoot) {
|
|
97
98
|
return defineTool({
|
|
98
99
|
name: "unwake",
|
|
99
100
|
description: "Cancel one of YOUR pending wake-ups (one-shot or recurring) by the id `wake` returned. Use it when " +
|
|
100
101
|
"a scheduled follow-up is no longer needed — especially to stop a recurring wake once its job is done.",
|
|
101
102
|
input: z.object({ id: z.string().min(1).describe("the wake-up id (returned by `wake`)") }),
|
|
102
103
|
execute(input, ctx) {
|
|
103
|
-
|
|
104
|
+
const session = ctx.sessionManager?.getSessionId();
|
|
105
|
+
if (!session)
|
|
104
106
|
return "unwake is only available inside a conversation.";
|
|
105
|
-
return removeWakeup(stateRoot, input.id,
|
|
107
|
+
return removeWakeup(stateRoot, input.id, session)
|
|
106
108
|
? `OK — wake-up ${input.id} cancelled.`
|
|
107
109
|
: `no pending wake-up ${input.id} in this conversation (already fired, or not yours).`;
|
|
108
110
|
},
|
package/dist/env.d.ts
CHANGED
|
@@ -16,9 +16,21 @@ export declare function loadEnvFile(file: string): void;
|
|
|
16
16
|
* anything else reading/deciding on .env content (e.g. `add`'s secret pre-fill) must use this, never a
|
|
17
17
|
* private re-implementation: two parsers of one dialect diverge silently. */
|
|
18
18
|
export declare function parseEnvContent(content: string): Map<string, string>;
|
|
19
|
+
/** The agent's `.env` file: `<resolved secrets dir>/.env` — default `<agentDir>/.secrets/.env`,
|
|
20
|
+
* moved together with auth.json by `FASTAGENT_SECRETS_DIR` ({@link resolveSecretsDir} in the neutral
|
|
21
|
+
* paths.ts). THE path every reader/writer of the agent's .env must use, so "where do secrets
|
|
22
|
+
* live" cannot diverge across commands. The file's OWN location resolves from the REAL environment:
|
|
23
|
+
* commands locate + load `.env` first, so a `FASTAGENT_SECRETS_DIR` set INSIDE it still relocates
|
|
24
|
+
* auth.json but cannot move the file it is read from. */
|
|
25
|
+
export declare function dotEnvPath(agentDir: string, env?: NodeJS.ProcessEnv): string;
|
|
26
|
+
/** The committable template: `<agentDir>/.secrets/.env.example` — deliberately NOT moved by
|
|
27
|
+
* `FASTAGENT_SECRETS_DIR`: it is authored agent surface that travels with the directory (the
|
|
28
|
+
* scaffolded `.secrets/.gitignore` un-ignores exactly it), while the real values follow the override. */
|
|
29
|
+
export declare function envExamplePath(agentDir: string): string;
|
|
19
30
|
/**
|
|
20
|
-
* Load
|
|
21
|
-
* — the
|
|
22
|
-
*
|
|
31
|
+
* Load the agent's `.env` ({@link dotEnvPath}) into `process.env` ({@link loadEnvFile}), treating a
|
|
32
|
+
* MISSING file as normal (no .env) — the agent-facing entry every command + the tunnel use. `agentDir`
|
|
33
|
+
* is the AGENT DIR (resolvePlacement().agentDir). Only ENOENT is swallowed; any other read error (a
|
|
34
|
+
* corrupt/unreadable file) propagates, so a real problem surfaces instead of silently skipping.
|
|
23
35
|
*/
|
|
24
|
-
export declare function loadDotEnv(
|
|
36
|
+
export declare function loadDotEnv(agentDir: string): void;
|
package/dist/env.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { log } from "./log.js";
|
|
5
|
+
import { SECRETS_DIRNAME, resolveSecretsDir } from "./paths.js";
|
|
3
6
|
/**
|
|
4
7
|
* Load a `.env` file into `process.env`, matching Node's `--env-file` / `process.loadEnvFile` precedence
|
|
5
8
|
* on BOTH axes (verified against Node): a real env var wins over the file (an already-set key is kept),
|
|
@@ -41,17 +44,52 @@ export function parseEnvContent(content) {
|
|
|
41
44
|
}
|
|
42
45
|
return parsed;
|
|
43
46
|
}
|
|
47
|
+
/** The agent's `.env` file: `<resolved secrets dir>/.env` — default `<agentDir>/.secrets/.env`,
|
|
48
|
+
* moved together with auth.json by `FASTAGENT_SECRETS_DIR` ({@link resolveSecretsDir} in the neutral
|
|
49
|
+
* paths.ts). THE path every reader/writer of the agent's .env must use, so "where do secrets
|
|
50
|
+
* live" cannot diverge across commands. The file's OWN location resolves from the REAL environment:
|
|
51
|
+
* commands locate + load `.env` first, so a `FASTAGENT_SECRETS_DIR` set INSIDE it still relocates
|
|
52
|
+
* auth.json but cannot move the file it is read from. */
|
|
53
|
+
export function dotEnvPath(agentDir, env = process.env) {
|
|
54
|
+
return join(resolveSecretsDir(agentDir, env), ".env");
|
|
55
|
+
}
|
|
56
|
+
/** The committable template: `<agentDir>/.secrets/.env.example` — deliberately NOT moved by
|
|
57
|
+
* `FASTAGENT_SECRETS_DIR`: it is authored agent surface that travels with the directory (the
|
|
58
|
+
* scaffolded `.secrets/.gitignore` un-ignores exactly it), while the real values follow the override. */
|
|
59
|
+
export function envExamplePath(agentDir) {
|
|
60
|
+
return join(agentDir, SECRETS_DIRNAME, ".env.example");
|
|
61
|
+
}
|
|
44
62
|
/**
|
|
45
|
-
* Load
|
|
46
|
-
* — the
|
|
47
|
-
*
|
|
63
|
+
* Load the agent's `.env` ({@link dotEnvPath}) into `process.env` ({@link loadEnvFile}), treating a
|
|
64
|
+
* MISSING file as normal (no .env) — the agent-facing entry every command + the tunnel use. `agentDir`
|
|
65
|
+
* is the AGENT DIR (resolvePlacement().agentDir). Only ENOENT is swallowed; any other read error (a
|
|
66
|
+
* corrupt/unreadable file) propagates, so a real problem surfaces instead of silently skipping.
|
|
48
67
|
*/
|
|
49
|
-
export function loadDotEnv(
|
|
68
|
+
export function loadDotEnv(agentDir) {
|
|
69
|
+
const path = dotEnvPath(agentDir);
|
|
50
70
|
try {
|
|
51
|
-
loadEnvFile(
|
|
71
|
+
loadEnvFile(path);
|
|
52
72
|
}
|
|
53
73
|
catch (error) {
|
|
54
74
|
if (error.code !== "ENOENT")
|
|
55
75
|
throw error;
|
|
56
76
|
}
|
|
77
|
+
// A `.env` at the agent's root is the file habit puts there, and nothing reads it. Left silent, the
|
|
78
|
+
// symptom is a setting that appears configured and is not.
|
|
79
|
+
//
|
|
80
|
+
// Scoped to keys FASTAGENT itself reads, because that is the only case we can be sure about: an agent
|
|
81
|
+
// directory can be the author's repository too (the shape `--agent-dir .` exists for), where a root
|
|
82
|
+
// `.env` is their APPLICATION's — and "move the values" would break it. Nothing here can tell those
|
|
83
|
+
// apart, so the broader guess (warn whenever the agent has no env of its own) was a warning on every
|
|
84
|
+
// boot, with destructive advice, aimed at the population that never had the problem. Channel
|
|
85
|
+
// credentials are deliberately out of scope: a channel that cannot find its token reports that itself,
|
|
86
|
+
// and it is the one that knows the name.
|
|
87
|
+
const stray = join(agentDir, ".env");
|
|
88
|
+
if (stray === path || !existsSync(stray))
|
|
89
|
+
return;
|
|
90
|
+
const misplaced = [...parseEnvContent(readFileSync(stray, "utf8")).keys()].filter((k) => k.startsWith("FASTAGENT_"));
|
|
91
|
+
if (misplaced.length > 0) {
|
|
92
|
+
log.warn(`[fastagent] ${stray} is NOT read — it sets ${misplaced.join(", ")}, and this agent's env lives at ` +
|
|
93
|
+
`${path}; move those values there`);
|
|
94
|
+
}
|
|
57
95
|
}
|
package/dist/feishu.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** `@fastagent-sh/fastagent/feishu` — the canonical Feishu (open.feishu.cn) bot-channel surface. */
|
|
2
|
-
export { feishuChannel, defaultFeishuRoute, feishuEnvelope, type FeishuChannelOptions, type FeishuMessageEvent, type FeishuMessage, type FeishuRoute, type FeishuFailure, } from "./channels/feishu/feishu.ts";
|
|
2
|
+
export { feishuChannel, feishuWebSocketChannel, defaultFeishuRoute, feishuEnvelope, type FeishuChannelOptions, type FeishuWebSocketChannelOptions, type FeishuMessageEvent, type FeishuMessage, type FeishuRoute, type FeishuFailure, } from "./channels/feishu/feishu.ts";
|
package/dist/feishu.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** `@fastagent-sh/fastagent/feishu` — the canonical Feishu (open.feishu.cn) bot-channel surface. */
|
|
2
|
-
export { feishuChannel, defaultFeishuRoute, feishuEnvelope, } from "./channels/feishu/feishu.js";
|
|
2
|
+
export { feishuChannel, feishuWebSocketChannel, defaultFeishuRoute, feishuEnvelope, } from "./channels/feishu/feishu.js";
|
package/dist/host/node.d.ts
CHANGED
|
@@ -1,23 +1,39 @@
|
|
|
1
1
|
import type { Agent } from "../agent.ts";
|
|
2
|
+
import type { SessionControl } from "../session.ts";
|
|
2
3
|
/** A mounted request handler (a channel's fetch, or a plain route like health). */
|
|
3
4
|
export type ChannelHandler = (req: Request) => Response | Promise<Response>;
|
|
4
5
|
/** This deployment's HTTP surface: route key → handler. Key is `"/path"` or `"METHOD /path"`. */
|
|
5
6
|
export type Routes = Record<string, ChannelHandler>;
|
|
6
7
|
/**
|
|
7
8
|
* What the framework hands a channel at mount time: the assembled agent plus the resolved state ROOT
|
|
8
|
-
* (absolute; `FASTAGENT_STATE_DIR` > `<
|
|
9
|
+
* (absolute; `FASTAGENT_STATE_DIR` > `<root>/.state`). Channels derive their OWN durable home from
|
|
9
10
|
* it (`<stateRoot>/channels/<kind>/`) — they never anchor on `process.cwd()`. env is the OPERATOR
|
|
10
|
-
* input plane; this context is how the resolved result reaches code (embedders without the
|
|
11
|
+
* input plane; this context is how the resolved result reaches code (embedders without the agent
|
|
11
12
|
* opener construct it explicitly).
|
|
12
13
|
*/
|
|
13
14
|
export interface ChannelContext {
|
|
14
15
|
agent: Agent;
|
|
15
16
|
stateRoot: string;
|
|
17
|
+
/** The serving session-control hub, when the serve wires one (`config.sessionControl`). Channels
|
|
18
|
+
* use it for DISPATCH only (the user-facing stop command); observation stays on the data plane. */
|
|
19
|
+
control?: SessionControl;
|
|
16
20
|
}
|
|
17
|
-
/** A `channels/<name>.ts`
|
|
18
|
-
* Adapters (`telegramChannel(opts)`, `githubChannel(opts)`) RETURN one of these, so user glue holds
|
|
19
|
-
* only policy — the framework pipes `agent`/`stateRoot` to the adapter without transiting user code. */
|
|
21
|
+
/** A `channels/<name>.ts` route channel: receives mount context and returns its HTTP routes. */
|
|
20
22
|
export type ChannelModule = (ctx: ChannelContext) => Routes;
|
|
23
|
+
/** One logical long connection's lifecycle. `ready` settles after its first usable connection — and
|
|
24
|
+
* when `signal` aborts before one exists it must still settle: resolution then means cancellation, not
|
|
25
|
+
* readiness (the server skips ready-side effects once the signal is aborted; it must never hang).
|
|
26
|
+
* `closed` resolves after abort-driven shutdown and rejects on a terminal connection failure. */
|
|
27
|
+
export interface LongConnection {
|
|
28
|
+
ready: Promise<void>;
|
|
29
|
+
closed: Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/** A long-connection channel is an explicit module object rather than an HTTP-route factory.
|
|
32
|
+
* The adapter owns reconnects and treats `signal` as its sole shutdown command. */
|
|
33
|
+
export interface LongConnectionChannelModule {
|
|
34
|
+
name: string;
|
|
35
|
+
connect(ctx: ChannelContext, signal: AbortSignal): LongConnection;
|
|
36
|
+
}
|
|
21
37
|
/** Parse a route key: `"METHOD /path"` → `{ method, path }`, or `"/path"` → `{ path }` (any method). */
|
|
22
38
|
export declare function parseRouteKey(key: string): {
|
|
23
39
|
method?: string;
|
|
@@ -29,13 +45,14 @@ export declare function parseRouteKey(key: string): {
|
|
|
29
45
|
*/
|
|
30
46
|
export declare function router(routes: Routes): ChannelHandler;
|
|
31
47
|
/**
|
|
32
|
-
* Serve `handler` on a Node HTTP server. Thin mechanism: bind, report the port, let the caller
|
|
33
|
-
*
|
|
34
|
-
* for port 0) or rejects on a bind error.
|
|
48
|
+
* Serve `handler` on a Node HTTP server. Thin mechanism: bind, report the port, let the caller stop
|
|
49
|
+
* accepting or force-close active connections — no logging/signals/exit (the CLI owns those).
|
|
50
|
+
* `listening` resolves with the bound port (useful for port 0) or rejects on a bind error.
|
|
35
51
|
*/
|
|
36
52
|
export declare function serveNode(handler: ChannelHandler, options: {
|
|
37
53
|
port: number;
|
|
38
54
|
}): {
|
|
39
55
|
listening: Promise<number>;
|
|
40
56
|
close: () => Promise<void>;
|
|
57
|
+
closeAllConnections: () => void;
|
|
41
58
|
};
|
package/dist/host/node.js
CHANGED
|
@@ -32,9 +32,9 @@ export function router(routes) {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* Serve `handler` on a Node HTTP server. Thin mechanism: bind, report the port, let the caller
|
|
36
|
-
*
|
|
37
|
-
* for port 0) or rejects on a bind error.
|
|
35
|
+
* Serve `handler` on a Node HTTP server. Thin mechanism: bind, report the port, let the caller stop
|
|
36
|
+
* accepting or force-close active connections — no logging/signals/exit (the CLI owns those).
|
|
37
|
+
* `listening` resolves with the bound port (useful for port 0) or rejects on a bind error.
|
|
38
38
|
*/
|
|
39
39
|
export function serveNode(handler, options) {
|
|
40
40
|
const server = createServer(nodeListener(async (req) => handler(req)));
|
|
@@ -46,5 +46,6 @@ export function serveNode(handler, options) {
|
|
|
46
46
|
});
|
|
47
47
|
});
|
|
48
48
|
const close = () => new Promise((resolve, reject) => server.close((e) => (e ? reject(e) : resolve())));
|
|
49
|
-
|
|
49
|
+
const closeAllConnections = () => server.closeAllConnections();
|
|
50
|
+
return { listening, close, closeAllConnections };
|
|
50
51
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,4 +2,5 @@
|
|
|
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 "./session.js";
|
|
5
6
|
export * from "./pi.js";
|
package/dist/lark.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** `@fastagent-sh/fastagent/lark` — the Lark-international compatibility surface over the canonical
|
|
2
2
|
* Feishu engine. Feishu tenants use `@fastagent-sh/fastagent/feishu`. */
|
|
3
|
-
export { larkChannel, defaultLarkRoute, larkEnvelope, type LarkChannelOptions, type LarkMessageEvent, type LarkMessage, type LarkRoute, type LarkFailure, } from "./channels/lark/lark.ts";
|
|
3
|
+
export { larkChannel, larkWebSocketChannel, defaultLarkRoute, larkEnvelope, type LarkChannelOptions, type LarkWebSocketChannelOptions, type LarkMessageEvent, type LarkMessage, type LarkRoute, type LarkFailure, } from "./channels/lark/lark.ts";
|
package/dist/lark.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** `@fastagent-sh/fastagent/lark` — the Lark-international compatibility surface over the canonical
|
|
2
2
|
* Feishu engine. Feishu tenants use `@fastagent-sh/fastagent/feishu`. */
|
|
3
|
-
export { larkChannel, defaultLarkRoute, larkEnvelope, } from "./channels/lark/lark.js";
|
|
3
|
+
export { larkChannel, larkWebSocketChannel, defaultLarkRoute, larkEnvelope, } from "./channels/lark/lark.js";
|
package/dist/loader.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Whether `name` is an importable
|
|
1
|
+
/** Whether `name` is an importable agent module (a discovery candidate, not a type declaration). */
|
|
2
2
|
export declare function isModuleFile(name: string): boolean;
|
|
3
3
|
export interface DiscoveredModule {
|
|
4
4
|
/** Basename without extension — the authoritative name for tools/channels. */
|
|
@@ -10,7 +10,7 @@ export interface DiscoveredModule {
|
|
|
10
10
|
default?: unknown;
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
13
|
+
/** An agent module that failed to load, surfaced as data so its caller can report the exact file.
|
|
14
14
|
* `loadModuleDir` fills it for import failures; domain loaders add validation failures. The caller owns
|
|
15
15
|
* policy: tools/schedules may skip one bad file, while serving treats a broken declared channel as fatal. */
|
|
16
16
|
export interface ModuleLoadFailure {
|
package/dist/loader.js
CHANGED
|
@@ -10,7 +10,7 @@ import { readdir } from "node:fs/promises";
|
|
|
10
10
|
import { basename, extname, join } from "node:path";
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
12
|
const MODULE_EXTS = new Set([".ts", ".js", ".mjs"]);
|
|
13
|
-
/** Whether `name` is an importable
|
|
13
|
+
/** Whether `name` is an importable agent module (a discovery candidate, not a type declaration). */
|
|
14
14
|
export function isModuleFile(name) {
|
|
15
15
|
return MODULE_EXTS.has(extname(name)) && !name.endsWith(".d.ts");
|
|
16
16
|
}
|
|
@@ -60,10 +60,10 @@ export async function loadModuleDir(subDir) {
|
|
|
60
60
|
*/
|
|
61
61
|
export function moduleLoadHint(error) {
|
|
62
62
|
if (error.code === "ERR_MODULE_NOT_FOUND" || /Cannot find (package|module)/.test(error.message)) {
|
|
63
|
-
return "\n (a dependency is not installed — run `npm install` in the
|
|
63
|
+
return "\n (a dependency is not installed — run `npm install` in the agent dir)";
|
|
64
64
|
}
|
|
65
65
|
if (/import statement outside a module|Unexpected token 'export'|ERR_REQUIRE_ESM/.test(error.message)) {
|
|
66
|
-
return '\n (
|
|
66
|
+
return '\n (the agent dir must be ESM — set "type": "module" in package.json)';
|
|
67
67
|
}
|
|
68
68
|
return "";
|
|
69
69
|
}
|
package/dist/log.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface Logger {
|
|
|
15
15
|
warn(msg: string): void;
|
|
16
16
|
error(msg: string): void;
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
18
|
+
/** An embedded logger over an explicit sink — used in tests to assert level gating without the singleton. */
|
|
19
19
|
export declare function createLogger(opts: {
|
|
20
20
|
level: LogLevel;
|
|
21
21
|
sink?: (line: string) => void;
|
package/dist/log.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
12
12
|
const isLevel = (s) => s in ORDER;
|
|
13
13
|
const format = (level, msg) => `${level.toUpperCase().padEnd(5)} ${msg}`;
|
|
14
|
-
/**
|
|
14
|
+
/** An embedded logger over an explicit sink — used in tests to assert level gating without the singleton. */
|
|
15
15
|
export function createLogger(opts) {
|
|
16
16
|
const sink = opts.sink ?? ((line) => console.error(line));
|
|
17
17
|
const make = (level) => (msg) => {
|
package/dist/observe.js
CHANGED
|
@@ -28,6 +28,9 @@ export function logAgentLoop(agent, sink = (line) => log.debug(line)) {
|
|
|
28
28
|
else if (e.type === "tool_ended") {
|
|
29
29
|
sink(`[agent] tool ${e.isError ? "✗" : "✓"} ${toolName.get(e.id) ?? e.id} → ${preview(e.content)}`);
|
|
30
30
|
}
|
|
31
|
+
else if (e.type === "retrying") {
|
|
32
|
+
sink(`[agent] retrying (${e.attempt}/${e.maxAttempts} in ${e.delayMs}ms): ${oneLine(e.reason)}`);
|
|
33
|
+
}
|
|
31
34
|
else if (e.type === "completed") {
|
|
32
35
|
if (thinking.trim() !== "")
|
|
33
36
|
sink(`[agent] thinking: ${oneLine(thinking)}`);
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The directory name `init` gives a nested agent (`<workspace>/fastagent/`) unless `--agent-dir` names
|
|
3
|
+
* another. A DEFAULT, not a rule: resolution reads the config marker and never a name, so renaming an
|
|
4
|
+
* agent directory changes nothing about how it resolves. Visible on purpose — the agent directory holds
|
|
5
|
+
* the AUTHOR's content (persona, skills, tool code: code, not tool configuration), so it follows the
|
|
6
|
+
* repo convention for code (a plain directory), while fastagent's own machinery inside it (`.secrets/`,
|
|
7
|
+
* `.state/`) keeps the dot prefix.
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_AGENT_DIRNAME = "fastagent";
|
|
10
|
+
/** The user-global machinery home under `$HOME` — hidden, per the dotfile convention for per-user
|
|
11
|
+
* tool homes (`~/.cargo`, `~/.docker`); unrelated to {@link DEFAULT_AGENT_DIRNAME}, which only names
|
|
12
|
+
* what `init` creates.
|
|
13
|
+
* It carries the same shape inside it as an agent dir does (`~/.fastagent/.secrets/auth.json`), so the
|
|
14
|
+
* resolvers below need no special case: `login` outside any agent simply hands them this directory. */
|
|
15
|
+
export declare const GLOBAL_HOME_DIR = ".fastagent";
|
|
16
|
+
/** The secrets segment inside an agent dir (or the global home): every PATH fastagent resolves —
|
|
17
|
+
* `.env`, `.env.example`, auth.json, the scaffold's write — derives from it, so they cannot drift
|
|
18
|
+
* apart. `FASTAGENT_SECRETS_DIR` relocates the RESOLVED dir ({@link resolveSecretsDir}), never this
|
|
19
|
+
* name. The scaffold's ignore templates are real files the author owns from `init` on, so they spell
|
|
20
|
+
* their rules out as literal text — renaming this constant means editing them too. */
|
|
21
|
+
export declare const SECRETS_DIRNAME = ".secrets";
|
|
22
|
+
/** The state segment inside an agent dir — same rule and same template caveat as {@link SECRETS_DIRNAME}. */
|
|
23
|
+
export declare const STATE_DIRNAME = ".state";
|
|
24
|
+
/** The config filenames, in load precedence. ONE source: the loader (below) and `scaffoldAgent`'s
|
|
25
|
+
* already-an-agent refusal both read this, so "is there a config?" can't diverge between them. */
|
|
26
|
+
export declare const AGENT_CONFIG_NAMES: readonly ["fastagent.config.ts", "fastagent.config.js", "fastagent.config.mjs"];
|
|
27
|
+
export interface ResolvedPlacement {
|
|
28
|
+
/** The AGENT directory — where the definition (persona.md/skills/tools/channels/schedules), the
|
|
29
|
+
* config, and the machinery dirs (`.secrets/`, `.state/`) live. Absolute. */
|
|
30
|
+
agentDir: string;
|
|
31
|
+
/** The WORKSPACE — what the agent works ON: its cwd, and the start of the ② context walk. ALWAYS the
|
|
32
|
+
* directory fastagent was pointed at, which makes it the agent dir's PARENT when the agent was found
|
|
33
|
+
* one level down, and the agent dir ITSELF when you aimed straight at it. Absolute.
|
|
34
|
+
* `agentDir === workspace` is the only discriminant — there is no mode field, because there are no
|
|
35
|
+
* two placements to distinguish: there is one lookup and the directory you gave it. The naming
|
|
36
|
+
* follows git: the repository sits at the root of its working tree, and WORK belongs to the tree. */
|
|
37
|
+
workspace: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The agents `dir` resolves over: ITSELF when it holds a config, else the ones directly inside it —
|
|
41
|
+
* never both, because aiming at an agent can only mean that agent. Exported for `init`, which asks the
|
|
42
|
+
* same question this lookup asks: which agents would `dir` resolve over, before and after scaffolding.
|
|
43
|
+
*/
|
|
44
|
+
export declare function agentsAt(dir: string): string[];
|
|
45
|
+
/**
|
|
46
|
+
* The one-line hint for "you pointed at the agent, but the project around it is what you meant" — or
|
|
47
|
+
* undefined. The workspace being whatever you aimed at is deliberate (a deployed box may hold nothing
|
|
48
|
+
* but the agent), and the cost is that `cd my-agent && fastagent dev` legitimately narrows the agent's
|
|
49
|
+
* WORKSPACE to its own directory: its cwd, its coding tools' root, and deploy's build context. (②
|
|
50
|
+
* context is not affected — that walk climbs ancestors either way.) Resolution must not guess which you
|
|
51
|
+
* wanted, so this is a HINT — and a hint may use the heuristic ("the parent carries an AGENTS.md or a
|
|
52
|
+
* .git") that a rule may not.
|
|
53
|
+
*
|
|
54
|
+
* It suggests a command only after RUNNING the lookup that command would run, because a hint that dead-ends
|
|
55
|
+
* is worse than none: with several agents beside this one, `..` refuses and names them — sending the
|
|
56
|
+
* reader to a refusal whose own advice points back here.
|
|
57
|
+
*/
|
|
58
|
+
export declare function workspaceHint({ agentDir, workspace }: ResolvedPlacement, env?: NodeJS.ProcessEnv): string | undefined;
|
|
59
|
+
/** The agent dir for `dir`, or undefined when there is none — {@link findPlacement} without the pair,
|
|
60
|
+
* and without the throw. `login` is the caller: it is the one command allowed to run outside an agent,
|
|
61
|
+
* so it needs the answer as a value rather than as a refusal. */
|
|
62
|
+
export declare function findAgentDir(dir: string): string | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* The agent whose DEFINITION contains `dir` — scaffolding there would make the new agent part of the
|
|
65
|
+
* outer one's loaded surface rather than an agent of its own. Narrower than {@link enclosingAgentDir} on
|
|
66
|
+
* purpose: an agent owns only what it LOADS ({@link LOADED_SURFACE}); the rest of its directory is the
|
|
67
|
+
* author's tree, where a second agent (a monorepo package, say) is a legitimate thing to create.
|
|
68
|
+
* `enclosingAgentDir` answers a different question ("where do I `cd` to?"), and for that an agent's
|
|
69
|
+
* `src/` genuinely IS inside it.
|
|
70
|
+
*/
|
|
71
|
+
export declare function agentDefinitionOwner(dir: string): string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Why `dir` is not an agent, when it has its OWN way out — or undefined when it is simply not near one.
|
|
74
|
+
* Two positions qualify: standing INSIDE an agent (its `tools/`, its `src/`), and standing on a
|
|
75
|
+
* directory whose several agents nothing selects between. Both matter because the generic advice ("run
|
|
76
|
+
* `fastagent init`") would not help — the agent already exists, one step away.
|
|
77
|
+
*
|
|
78
|
+
* Exported because `login` is the one command allowed to run outside an agent, and it must tell "truly
|
|
79
|
+
* outside" (→ the global credential) from "a dead end" (→ refuse, like every other command).
|
|
80
|
+
*/
|
|
81
|
+
export declare function placementDeadEnd(dir: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Resolve a directory into its placement — the ONE owner of the rule ({@link findPlacement}): `dir` is
|
|
84
|
+
* the workspace, and the agent is the single `fastagent.config.*` holder at it or one level inside.
|
|
85
|
+
* Placement is never configured and never detected from surroundings; it is that lookup and the
|
|
86
|
+
* directory you pointed at.
|
|
87
|
+
*
|
|
88
|
+
* Anything else throws (fail visibly). Resolution never walks UP — an agent must not be claimed from
|
|
89
|
+
* arbitrarily deep inside it — but the MESSAGE reads the path, so each dead end gets the exit that fits
|
|
90
|
+
* it ({@link placementDeadEnd}).
|
|
91
|
+
*/
|
|
92
|
+
export declare function resolvePlacement(dir: string, env?: NodeJS.ProcessEnv): ResolvedPlacement;
|
|
93
|
+
/** How to WRITE a path for someone standing in `cwd`: relative when it is inside `cwd`, absolute when
|
|
94
|
+
* it climbs out (a `../../..` is noise), and undefined when it IS `cwd` (nothing to say). ONE policy,
|
|
95
|
+
* shared by `init`'s `cd` step, `add`'s next-steps paths and `fire`'s "looked in" hint — they all answer
|
|
96
|
+
* the same question, which is a placement-PRESENTATION question, not a scaffolding one. */
|
|
97
|
+
export declare function displayPath(cwd: string, dir: string): string | undefined;
|
|
98
|
+
/** Does a path exist? Plain fs, no placement in it — it lives here because `paths.ts` is where the
|
|
99
|
+
* neutral path helpers are, and the scaffolder is not a utility home for the CLI and deploy. */
|
|
100
|
+
export declare function exists(p: string): Promise<boolean>;
|
|
101
|
+
/**
|
|
102
|
+
* Resolve a user-supplied path override (a CLI flag or an env var) to an absolute path, expanding a
|
|
103
|
+
* leading `~`/`~/` to the home dir FIRST. Path-valued config from `.env` (or any non-shell source)
|
|
104
|
+
* never gets the shell's `~` expansion, so a bare `resolve("~/x")` would silently create a literal `~`
|
|
105
|
+
* directory — a fail-silently footgun for a secret/state path. Expanding here makes `~` mean home
|
|
106
|
+
* everywhere these knobs are read.
|
|
107
|
+
*/
|
|
108
|
+
export declare function resolveOverridePath(raw: string | undefined): string | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* The resolved state root — the durable machine-state home (sessions/, channels/<kind>/, schedule/,
|
|
111
|
+
* control.json): `FASTAGENT_STATE_DIR` env > `<agentDir>/.state`. Absolute, so channels and the
|
|
112
|
+
* startup report agree regardless of cwd. Definition: mutable runtime state — single lifecycle
|
|
113
|
+
* (precious, survives redeploy), single process; a container points this at its mounted volume.
|
|
114
|
+
* Secrets are NOT here — they live under {@link resolveSecretsDir} (a different deploy lifecycle:
|
|
115
|
+
* secret store vs volume). The finer knob (`FASTAGENT_SESSIONS_DIR`) still overrides its path on top.
|
|
116
|
+
*
|
|
117
|
+
* `FASTAGENT_STATE_DIR` is an OPERATOR override, so a relative value resolves against `process.cwd()`
|
|
118
|
+
* — the CLI convention its sibling knobs share (`resolveOverridePath`), NOT against `dir`. Only the
|
|
119
|
+
* DEFAULT (`<root>/.state`) is dir-anchored.
|
|
120
|
+
*/
|
|
121
|
+
export declare function resolveStateRoot(dir: string, env?: NodeJS.ProcessEnv): string;
|
|
122
|
+
/**
|
|
123
|
+
* The resolved secrets dir — everything fastagent manages that must NEVER leave the machine (the
|
|
124
|
+
* agent's `.env` + auth.json): `FASTAGENT_SECRETS_DIR` env > `<agentDir>/.secrets`. Split from
|
|
125
|
+
* the state root on deploy lifecycle: secrets travel through the host's secret store (env vars / the
|
|
126
|
+
* auth seed), state through a volume. A deployed box sets both env knobs at its volume (e.g.
|
|
127
|
+
* `/data/.secrets`, `/data/.state`) so a seeded-then-ROTATED OAuth credential persists across
|
|
128
|
+
* restarts. The `.env`'s OWN location resolves from the REAL environment — commands locate and load
|
|
129
|
+
* `.env` before anything else, so a `FASTAGENT_SECRETS_DIR` set INSIDE `.env` still relocates
|
|
130
|
+
* auth.json but cannot move the file it is read from (env.ts dotEnvPath).
|
|
131
|
+
*/
|
|
132
|
+
export declare function resolveSecretsDir(dir: string, env?: NodeJS.ProcessEnv): string;
|
|
133
|
+
/**
|
|
134
|
+
* Guard that `<agentDir>/<name>` resolves INSIDE the agent dir — a symlink that escapes (or an
|
|
135
|
+
* absolute target) is rejected, so discovery/scaffolding never reaches out of the definition directory.
|
|
136
|
+
* A missing target is fine (nothing to guard yet).
|
|
137
|
+
*/
|
|
138
|
+
export declare function assertInsideAgentDir(agentDir: string, name: string): Promise<void>;
|