@fastagent-sh/fastagent 0.13.0 → 0.15.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 +5 -5
- package/dist/agent.d.ts +20 -0
- package/dist/agent.js +9 -0
- package/dist/channels/context-buffer.d.ts +30 -0
- package/dist/channels/context-buffer.js +98 -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 +43 -0
- package/dist/channels/feishu/context-buffer.js +72 -0
- package/dist/channels/feishu/crypto.d.ts +4 -2
- package/dist/channels/feishu/crypto.js +4 -2
- package/dist/channels/feishu/feishu-api.d.ts +15 -7
- package/dist/channels/feishu/feishu-api.js +22 -4
- package/dist/channels/feishu/feishu.d.ts +38 -16
- package/dist/channels/feishu/feishu.js +286 -151
- package/dist/channels/feishu/invoke-turn.d.ts +24 -31
- package/dist/channels/feishu/invoke-turn.js +61 -62
- package/dist/channels/feishu/model.d.ts +98 -0
- package/dist/channels/feishu/model.js +9 -0
- package/dist/channels/feishu/normalize.d.ts +23 -0
- package/dist/channels/feishu/normalize.js +132 -0
- package/dist/channels/feishu/owned-threads.d.ts +7 -0
- package/dist/channels/feishu/owned-threads.js +47 -0
- package/dist/channels/feishu/parse.d.ts +21 -103
- package/dist/channels/feishu/parse.js +35 -145
- package/dist/channels/feishu/preview.d.ts +4 -7
- package/dist/channels/feishu/preview.js +26 -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 +10 -3
- package/dist/channels/feishu/setup-mode.d.ts +8 -0
- package/dist/channels/feishu/setup-mode.js +2 -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 +8 -6
- package/dist/channels/http.d.ts +14 -0
- package/dist/channels/http.js +35 -2
- 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 +10 -3
- package/dist/channels/preview-kit.d.ts +109 -0
- package/dist/channels/preview-kit.js +183 -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/owned-threads.d.ts +6 -0
- package/dist/channels/slack/owned-threads.js +43 -0
- package/dist/channels/slack/parse.d.ts +23 -0
- package/dist/channels/slack/parse.js +81 -0
- package/dist/channels/slack/preview.d.ts +24 -0
- package/dist/channels/slack/preview.js +359 -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 +77 -0
- package/dist/channels/slack/slack-api.js +415 -0
- package/dist/channels/slack/slack.d.ts +58 -0
- package/dist/channels/slack/slack.js +451 -0
- package/dist/channels/slack/welcomed.d.ts +5 -0
- package/dist/channels/slack/welcomed.js +32 -0
- package/dist/channels/state.js +3 -3
- package/dist/channels/stop-command.d.ts +6 -0
- package/dist/channels/stop-command.js +36 -0
- package/dist/channels/tasks.d.ts +13 -0
- package/dist/channels/tasks.js +10 -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/preview.d.ts +4 -7
- package/dist/channels/telegram/preview.js +24 -142
- package/dist/channels/telegram/telegram.js +23 -9
- package/dist/channels/{feishu/text.d.ts → text.d.ts} +1 -1
- package/dist/channels/{feishu/text.js → text.js} +1 -1
- package/dist/channels/turn-queue.js +1 -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 +27 -0
- package/dist/{cli-add-feishu.js → cli/add-feishu.js} +167 -44
- package/dist/cli/add-slack.d.ts +10 -0
- package/dist/cli/add-slack.js +204 -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 +13 -0
- package/dist/cli/commands/add.js +274 -0
- package/dist/cli/commands/attach.d.ts +82 -0
- package/dist/cli/commands/attach.js +559 -0
- package/dist/cli/commands/chat.d.ts +4 -0
- package/dist/cli/commands/chat.js +21 -0
- package/dist/cli/commands/deploy.d.ts +15 -0
- package/dist/cli/commands/deploy.js +394 -0
- package/dist/cli/commands/dev.d.ts +11 -0
- package/dist/cli/commands/dev.js +82 -0
- package/dist/cli/commands/fire.d.ts +7 -0
- package/dist/cli/commands/fire.js +45 -0
- package/dist/cli/commands/info.d.ts +7 -0
- package/dist/cli/commands/info.js +108 -0
- package/dist/cli/commands/init.d.ts +8 -0
- package/dist/cli/commands/init.js +81 -0
- package/dist/cli/commands/invoke.d.ts +7 -0
- package/dist/cli/commands/invoke.js +28 -0
- package/dist/cli/commands/login.d.ts +6 -0
- package/dist/cli/commands/login.js +52 -0
- package/dist/cli/commands/models.d.ts +1 -0
- package/dist/cli/commands/models.js +15 -0
- package/dist/cli/commands/schedule.d.ts +12 -0
- package/dist/cli/commands/schedule.js +89 -0
- package/dist/cli/commands/start.d.ts +10 -0
- package/dist/cli/commands/start.js +97 -0
- package/dist/cli/commands/tool.d.ts +1 -0
- package/dist/cli/commands/tool.js +38 -0
- package/dist/cli/fail.d.ts +17 -0
- package/dist/cli/fail.js +32 -0
- 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 +90 -0
- package/dist/cli/kernel.js +190 -0
- package/dist/cli/models-view.d.ts +21 -0
- package/dist/cli/models-view.js +66 -0
- package/dist/cli/program.d.ts +11 -0
- package/dist/cli/program.js +479 -0
- package/dist/cli/serve.d.ts +48 -0
- package/dist/cli/serve.js +248 -0
- package/dist/cli/shared.d.ts +44 -0
- package/dist/cli/shared.js +246 -0
- package/dist/cli.js +8 -1329
- 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/docker/plan.d.ts +45 -0
- package/dist/deploy/docker/plan.js +141 -0
- package/dist/deploy/docker/run.d.ts +40 -0
- package/dist/deploy/docker/run.js +126 -0
- package/dist/deploy/fly/plan.d.ts +3 -1
- package/dist/deploy/fly/plan.js +12 -7
- package/dist/deploy/fly/run.d.ts +5 -4
- package/dist/deploy/fly/run.js +16 -5
- package/dist/deploy/preflight.d.ts +10 -4
- package/dist/deploy/preflight.js +57 -12
- package/dist/deploy/railway/plan.d.ts +3 -1
- package/dist/deploy/railway/plan.js +12 -4
- package/dist/deploy/railway/run.d.ts +5 -4
- package/dist/deploy/railway/run.js +15 -5
- package/dist/deploy/runner.d.ts +6 -2
- package/dist/deploy/runner.js +1 -0
- package/dist/deploy/secrets.d.ts +2 -1
- package/dist/deploy/secrets.js +23 -3
- package/dist/dev-supervisor.d.ts +0 -2
- package/dist/dev-supervisor.js +7 -3
- package/dist/engines/pi/auth.js +160 -46
- package/dist/engines/pi/channel.d.ts +22 -16
- package/dist/engines/pi/channel.js +90 -60
- package/dist/engines/pi/chat.d.ts +4 -16
- package/dist/engines/pi/chat.js +8 -188
- package/dist/engines/pi/config.d.ts +23 -9
- package/dist/engines/pi/config.js +35 -5
- package/dist/engines/pi/create.d.ts +36 -7
- package/dist/engines/pi/create.js +63 -22
- package/dist/engines/pi/harness.d.ts +65 -1
- package/dist/engines/pi/harness.js +166 -2
- package/dist/engines/pi/invoke.d.ts +56 -3
- package/dist/engines/pi/invoke.js +340 -20
- package/dist/engines/pi/login.d.ts +11 -0
- package/dist/engines/pi/login.js +17 -5
- package/dist/engines/pi/models.d.ts +56 -10
- package/dist/engines/pi/models.js +61 -23
- package/dist/engines/pi/search-tools.d.ts +10 -0
- package/dist/engines/pi/search-tools.js +138 -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 +46 -11
- package/dist/engines/pi/tool-context.js +11 -9
- package/dist/engines/pi/tool.d.ts +35 -6
- package/dist/engines/pi/tool.js +47 -1
- package/dist/engines/pi/wake-tool.d.ts +0 -3
- package/dist/engines/pi/wake-tool.js +9 -7
- package/dist/engines/pi/workspace.d.ts +56 -1
- package/dist/engines/pi/workspace.js +75 -16
- package/dist/feishu.d.ts +1 -1
- package/dist/feishu.js +1 -1
- package/dist/host/node.d.ts +23 -6
- 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/observe.js +3 -0
- package/dist/pi.d.ts +6 -4
- package/dist/pi.js +2 -1
- package/dist/scaffold/add-channel.d.ts +19 -6
- package/dist/scaffold/add-channel.js +127 -16
- package/dist/scaffold/templates/fastagent.config.mjs +5 -3
- package/dist/schedule/wakeups.d.ts +0 -3
- package/dist/schedule/wakeups.js +1 -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 +21 -10
- package/package.json +22 -7
- 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-models.d.ts +0 -11
- package/dist/cli-models.js +0 -20
|
@@ -1,17 +1,52 @@
|
|
|
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
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The turn's tool-activation bridge — narrow closures over the CURRENT harness (invoke.ts builds it
|
|
20
|
+
* per turn), so a loader tool can activate deferred tools mid-turn without tool.ts importing the
|
|
21
|
+
* harness. pi records the change in the session (`active_tools_change`) and the per-invoke restore
|
|
22
|
+
* (harness.ts) carries it into later turns; defineTool's wrapper stamps the newly-activated names on
|
|
23
|
+
* the tool result (`addedToolNames`) — the load point native deferred-loading providers preserve the
|
|
24
|
+
* prompt-cache prefix with.
|
|
25
|
+
*/
|
|
26
|
+
export interface ToolActivation {
|
|
27
|
+
/** Names of the currently ACTIVE tools. */
|
|
28
|
+
active(): string[];
|
|
29
|
+
/** Every registered tool (active or not) — the discovery corpus for a loader like `search_tools`. */
|
|
30
|
+
registered(): Array<{
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
}>;
|
|
34
|
+
/** ADDITIVE activation. Unknown names are filtered out before reaching pi (whose `setActiveTools`
|
|
35
|
+
* THROWS on them); resolves the names actually newly activated (already-active names don't repeat). */
|
|
36
|
+
activate(names: string[]): Promise<string[]>;
|
|
37
|
+
}
|
|
13
38
|
export interface TurnContext {
|
|
14
|
-
/**
|
|
15
|
-
|
|
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;
|
|
43
|
+
/** Tool activation for the current turn. Two producers, one consumer surface: invoke.ts bridges the
|
|
44
|
+
* serving harness; chat.ts bridges pi's AgentSession (chat emulates deferral — same loader, same
|
|
45
|
+
* semantics). Absent only outside any turn (a bare `fastagent tool` run). */
|
|
46
|
+
tools?: ToolActivation;
|
|
16
47
|
}
|
|
17
48
|
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[];
|
|
@@ -1,13 +1,15 @@
|
|
|
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();
|
|
8
|
+
/** The additive-activation contract, in ONE place for both bridges (invoke.ts over the harness,
|
|
9
|
+
* chat.ts over pi's AgentSession): dedupe → keep registered names only (pi's setters THROW on
|
|
10
|
+
* unknown) → exclude already-active → the names to actually add (empty = nothing to set). */
|
|
11
|
+
export function additiveActivation(registered, current, names) {
|
|
12
|
+
const known = new Set(registered);
|
|
13
|
+
const active = new Set(current);
|
|
14
|
+
return [...new Set(names)].filter((name) => known.has(name) && !active.has(name));
|
|
15
|
+
}
|
|
@@ -1,23 +1,52 @@
|
|
|
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 ReadonlySessionManager, type ToolActivation } from "./tool-context.ts";
|
|
4
5
|
export interface ToolContext {
|
|
6
|
+
/** Working directory for this execution. */
|
|
7
|
+
cwd: string;
|
|
5
8
|
/** Abort signal for the current turn — honor it to cancel in-flight work on cancellation. */
|
|
6
9
|
signal?: AbortSignal;
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
|
|
10
|
+
/** Current conversation manager. Present during serving/chat; absent for sessionless direct calls. */
|
|
11
|
+
sessionManager?: ReadonlySessionManager;
|
|
12
|
+
/** Tool activation for the current turn (a loader tool activates {@link DefineToolOptions.deferred}
|
|
13
|
+
* tools with it — the built-in `search_tools` is one consumer). Provided by both the serving path
|
|
14
|
+
* (invoke.ts, over the harness) and chat (over pi's AgentSession); undefined only outside any turn
|
|
15
|
+
* (a bare `fastagent tool` run). */
|
|
16
|
+
tools?: ToolActivation;
|
|
12
17
|
}
|
|
13
18
|
export interface DefineToolOptions<I extends z.ZodType> {
|
|
14
19
|
/** Explicit name. Usually omitted — a `tools/<name>.ts` tool is named from its filename. */
|
|
15
20
|
name?: string;
|
|
16
21
|
description: string;
|
|
17
22
|
input: I;
|
|
23
|
+
/**
|
|
24
|
+
* Registered but NOT initially active: the tool's schema stays out of every request (and the model's
|
|
25
|
+
* sight) until a loader — the built-in `search_tools`, mounted automatically when any deferred tool
|
|
26
|
+
* exists — activates it mid-turn. For tool-heavy agents: fewer schemas per turn, and on providers
|
|
27
|
+
* with native deferred loading the activation preserves the prompt-cache prefix. The trade-off:
|
|
28
|
+
* discovery rides entirely on this description — write it for the search. Default: false.
|
|
29
|
+
*/
|
|
30
|
+
deferred?: boolean;
|
|
31
|
+
/** pi's per-tool execution mode: "sequential" makes pi run any batch containing this tool serially.
|
|
32
|
+
* REQUIRED for a tool that activates others (a loader): pi's own diff around SDK tools (chat)
|
|
33
|
+
* would attribute one activation to two parallel calls otherwise. */
|
|
34
|
+
executionMode?: "sequential" | "parallel";
|
|
18
35
|
execute: (input: z.infer<I>, ctx: ToolContext) => unknown | Promise<unknown>;
|
|
19
36
|
}
|
|
20
|
-
|
|
37
|
+
/** An AgentTool with fastagent's deferral marker — the type for raw tools handed to fastagent
|
|
38
|
+
* (`config.tools`, L1/L2 `tools`): plain `AgentTool` has no `deferred`, so an object literal with the
|
|
39
|
+
* marker would fail excess-property checking against upstream's type. `defineTool` produces it. */
|
|
40
|
+
export type FastagentTool = AgentTool & {
|
|
41
|
+
deferred?: boolean;
|
|
42
|
+
};
|
|
43
|
+
/** Read the {@link DefineToolOptions.deferred} marker off a mounted tool (extra property on the
|
|
44
|
+
* AgentTool object — pi ignores it). */
|
|
45
|
+
export declare function isDeferredTool(tool: AgentTool): boolean;
|
|
46
|
+
/** The same tool without the deferred marker — for a loader that must stay active (a deferred loader
|
|
47
|
+
* could never be activated and would strand every deferred tool). */
|
|
48
|
+
export declare function stripDeferredMarker(tool: AgentTool): AgentTool;
|
|
49
|
+
export declare function defineTool<I extends z.ZodType>(options: DefineToolOptions<I>): FastagentTool;
|
|
21
50
|
/** A discarded same-name tool (within `tools/`, or against an existing tool). Surfaced, never silent. */
|
|
22
51
|
export interface ToolCollision {
|
|
23
52
|
name: string;
|
package/dist/engines/pi/tool.js
CHANGED
|
@@ -14,6 +14,19 @@ import { join } from "node:path";
|
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
import { loadModuleDir } from "../../loader.js";
|
|
16
16
|
import { turnContext } from "./tool-context.js";
|
|
17
|
+
/** Read the {@link DefineToolOptions.deferred} marker off a mounted tool (extra property on the
|
|
18
|
+
* AgentTool object — pi ignores it). */
|
|
19
|
+
export function isDeferredTool(tool) {
|
|
20
|
+
return tool.deferred === true;
|
|
21
|
+
}
|
|
22
|
+
/** The same tool without the deferred marker — for a loader that must stay active (a deferred loader
|
|
23
|
+
* could never be activated and would strand every deferred tool). */
|
|
24
|
+
export function stripDeferredMarker(tool) {
|
|
25
|
+
if (!isDeferredTool(tool))
|
|
26
|
+
return tool;
|
|
27
|
+
const { deferred: _drop, ...active } = tool;
|
|
28
|
+
return active;
|
|
29
|
+
}
|
|
17
30
|
/** Wrap a plain return value into pi's tool-result shape; pass a full result through unchanged. */
|
|
18
31
|
function wrapResult(value) {
|
|
19
32
|
if (value && typeof value === "object" && Array.isArray(value.content)) {
|
|
@@ -28,6 +41,8 @@ export function defineTool(options) {
|
|
|
28
41
|
name: options.name ?? "",
|
|
29
42
|
description: options.description,
|
|
30
43
|
parameters,
|
|
44
|
+
...(options.deferred ? { deferred: true } : {}),
|
|
45
|
+
...(options.executionMode ? { executionMode: options.executionMode } : {}),
|
|
31
46
|
async execute(_toolCallId, rawParams, signal) {
|
|
32
47
|
const parsed = options.input.safeParse(rawParams);
|
|
33
48
|
if (!parsed.success) {
|
|
@@ -35,7 +50,38 @@ export function defineTool(options) {
|
|
|
35
50
|
const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
36
51
|
return { content: [{ type: "text", text: `Invalid arguments: ${detail}` }], details: { error: detail } };
|
|
37
52
|
}
|
|
38
|
-
|
|
53
|
+
const store = turnContext.getStore();
|
|
54
|
+
// Stamp tools THIS execute activates on its result — the load point that lets native
|
|
55
|
+
// deferred-loading providers add the definitions at this transcript position without
|
|
56
|
+
// invalidating the cached prompt prefix. The names come from this execute's OWN activate()
|
|
57
|
+
// calls (accumulated below), NOT from an active-set before/after diff: pi runs tool calls of a
|
|
58
|
+
// batch in parallel, and a snapshot diff would stamp a sibling's activation onto the wrong tool
|
|
59
|
+
// result, drifting the load point.
|
|
60
|
+
const added = [];
|
|
61
|
+
const tools = store?.tools
|
|
62
|
+
? {
|
|
63
|
+
...store.tools,
|
|
64
|
+
activate: async (names) => {
|
|
65
|
+
// biome-ignore lint/style/noNonNullAssertion: guarded by the ternary above
|
|
66
|
+
const activated = await store.tools.activate(names);
|
|
67
|
+
added.push(...activated);
|
|
68
|
+
return activated;
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
: undefined;
|
|
72
|
+
const result = wrapResult(await options.execute(parsed.data, {
|
|
73
|
+
cwd: store?.cwd ?? process.cwd(),
|
|
74
|
+
signal,
|
|
75
|
+
sessionManager: store?.sessionManager,
|
|
76
|
+
tools,
|
|
77
|
+
}));
|
|
78
|
+
if (added.length > 0) {
|
|
79
|
+
// A copy, not a mutation: wrapResult passes a full AgentToolResult through by REFERENCE, and an
|
|
80
|
+
// author may legally return a shared/frozen result object — stamping in place would corrupt it
|
|
81
|
+
// across calls (or throw on frozen), only on the rare activating path.
|
|
82
|
+
return { ...result, addedToolNames: [...new Set([...(result.addedToolNames ?? []), ...added])] };
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
39
85
|
},
|
|
40
86
|
};
|
|
41
87
|
return tool;
|
|
@@ -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
|
},
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
1
2
|
import type { Agent } from "../../agent.ts";
|
|
2
3
|
import { type FastagentConfig } from "./config.ts";
|
|
4
|
+
import type { SessionControl } from "../../session.ts";
|
|
5
|
+
import type { SessionObserver } from "./invoke.ts";
|
|
6
|
+
import type { PiSessionReader, PiSessionStore } from "./sessions.ts";
|
|
3
7
|
import type { ModuleLoadFailure } from "../../loader.ts";
|
|
4
8
|
import { type LoadedDefinition } from "./definition.ts";
|
|
5
9
|
import type { ToolCollision } from "./tool.ts";
|
|
@@ -24,7 +28,51 @@ export interface CreatePiAgentFromWorkspaceOptions {
|
|
|
24
28
|
* and never poll). The built-in `wake` tool mounts only when this is set AND `config.selfSchedule` is on.
|
|
25
29
|
*/
|
|
26
30
|
serving?: boolean;
|
|
31
|
+
/** Assemble the session control plane over this workspace's session store and return it as
|
|
32
|
+
* {@link sessionControl} — the store is created inside this opener, so the hub must be wired
|
|
33
|
+
* here too (an external `createPiSessionControl` cannot exist before the store does).
|
|
34
|
+
* Default: `config.sessionControl` AND {@link serving} — the config key means "serve the control
|
|
35
|
+
* plane", so one-shot commands (invoke/fire) do not assemble an unused hub. Pass explicitly to
|
|
36
|
+
* override either way. */
|
|
37
|
+
sessionControl?: boolean;
|
|
38
|
+
/** Additional raw tap with the FULL vocabulary: run events composed after the
|
|
39
|
+
* {@link sessionControl} hub's observer, plus the hub's own boundary-mutation events
|
|
40
|
+
* (`state_changed`/`compaction_*`) via the hub's tap. TRUSTED seam: since Phase 2a an observer
|
|
41
|
+
* receives each run's live modulation handles (see `SessionObserver`) — for read-only consumers
|
|
42
|
+
* use the hub's `events()` stream instead. */
|
|
43
|
+
observer?: SessionObserver;
|
|
27
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* The workspace assembly FRONT HALF — everything that is independent of how pi consumes the
|
|
47
|
+
* definition (transient harness for serving vs resident AgentSession for chat / session control):
|
|
48
|
+
* config → model spec → agentDir → the full tool surface ({@link resolveWorkspaceTools} — the ONE
|
|
49
|
+
* place it is computed) → state root → auth path. Both {@link createPiAgentFromWorkspace} and the
|
|
50
|
+
* session builder (session-builder.ts) consume this, so THESE inputs cannot drift between the two
|
|
51
|
+
* consumption shapes. (Definition loading and prompt assembly stay per-consumer: serving re-reads
|
|
52
|
+
* them live per invoke, the session builder snapshots at startup and lets pi append skills/env.)
|
|
53
|
+
*/
|
|
54
|
+
export interface WorkspaceAssembly {
|
|
55
|
+
config: FastagentConfig;
|
|
56
|
+
configPath?: string;
|
|
57
|
+
/** The resolved "provider/modelId" spec in use. */
|
|
58
|
+
modelSpec: string;
|
|
59
|
+
/** Absolute agent-definition dir (config.agentDir resolved against dir; = dir when unset). */
|
|
60
|
+
agentDir: string;
|
|
61
|
+
/** Absolute state root (FASTAGENT_STATE_DIR > <dir>/.fastagent). */
|
|
62
|
+
stateRoot: string;
|
|
63
|
+
/** Absolute credentials file (--auth-path/authPath option > FASTAGENT_AUTH_PATH > <stateRoot>/auth.json). */
|
|
64
|
+
authPath: string;
|
|
65
|
+
/** The full mounted tool surface (config.tools + discovered tools/, search_tools applied). */
|
|
66
|
+
tools: AgentTool[];
|
|
67
|
+
toolNames: string[];
|
|
68
|
+
deferredToolNames: string[];
|
|
69
|
+
toolCollisions: ToolCollision[];
|
|
70
|
+
toolFailures: ModuleLoadFailure[];
|
|
71
|
+
}
|
|
72
|
+
export declare function resolveWorkspaceAssembly(dir: string, options?: {
|
|
73
|
+
model?: string;
|
|
74
|
+
authPath?: string;
|
|
75
|
+
}): Promise<WorkspaceAssembly>;
|
|
28
76
|
/**
|
|
29
77
|
* "Point at a workspace → agent": load the config, resolve model and tools, then L2. Throws a clear
|
|
30
78
|
* error when no model source is set (fail visibly at startup). Returns everything an entry point needs
|
|
@@ -45,8 +93,15 @@ export declare function createPiAgentFromWorkspace(dir: string, options?: Create
|
|
|
45
93
|
sessionsDir: string;
|
|
46
94
|
/** Absolute credentials file in use (for the startup report). */
|
|
47
95
|
authPath: string;
|
|
48
|
-
/**
|
|
96
|
+
/** The session store in use — also a {@link PiSessionReader}. */
|
|
97
|
+
sessions: PiSessionStore & PiSessionReader;
|
|
98
|
+
/** The observation plane over this workspace's sessions; present iff `options.sessionControl`. */
|
|
99
|
+
sessionControl?: SessionControl;
|
|
100
|
+
/** Non-default, active-by-default tool names in effect: config.tools + discovered tools/. Each name
|
|
101
|
+
* lives in exactly one report slot — deferred names are in {@link deferredToolNames} instead. */
|
|
49
102
|
toolNames: string[];
|
|
103
|
+
/** Tools registered but not initially active (deferred) — activated via search_tools. */
|
|
104
|
+
deferredToolNames: string[];
|
|
50
105
|
toolCollisions: ToolCollision[];
|
|
51
106
|
/** `tools/` files that failed to import — skipped, reported by the caller, never fatal. */
|
|
52
107
|
toolFailures: ModuleLoadFailure[];
|
|
@@ -9,17 +9,13 @@
|
|
|
9
9
|
* watch and can point sessions at a mounted volume.
|
|
10
10
|
*/
|
|
11
11
|
import { mkdir } from "node:fs/promises";
|
|
12
|
-
import {
|
|
12
|
+
import { defaultSessionsDir, loadConfig, resolveAgentDir, resolveAuthPath, resolveModelSpec, resolveStateRoot, } from "./config.js";
|
|
13
13
|
import { createPiAgentFromDefinition, resolveWorkspaceTools } from "./create.js";
|
|
14
|
+
import { createPiSessionControl } from "./session-control.js";
|
|
14
15
|
import { withWakeTool } from "./wake-tool.js";
|
|
15
16
|
import { ensureStateRootSelfIgnored } from "./definition.js";
|
|
16
17
|
import { jsonlSessionStore } from "./sessions.js";
|
|
17
|
-
|
|
18
|
-
* "Point at a workspace → agent": load the config, resolve model and tools, then L2. Throws a clear
|
|
19
|
-
* error when no model source is set (fail visibly at startup). Returns everything an entry point needs
|
|
20
|
-
* to report what it assembled.
|
|
21
|
-
*/
|
|
22
|
-
export async function createPiAgentFromWorkspace(dir, options = {}) {
|
|
18
|
+
export async function resolveWorkspaceAssembly(dir, options = {}) {
|
|
23
19
|
const { config, path: configPath } = await loadConfig(dir);
|
|
24
20
|
const modelSpec = resolveModelSpec(options.model, config);
|
|
25
21
|
if (!modelSpec) {
|
|
@@ -28,34 +24,96 @@ export async function createPiAgentFromWorkspace(dir, options = {}) {
|
|
|
28
24
|
// The run root is `dir` (cwd — where config lives, whose AGENTS.md is ② context); the agent's own
|
|
29
25
|
// surface (persona/skills/tools/channels) lives in `agentDir` (config.agentDir, or `dir` when flat).
|
|
30
26
|
const agentDir = resolveAgentDir(dir, config);
|
|
31
|
-
const { tools, toolNames, toolCollisions, toolFailures } = await resolveWorkspaceTools(config, agentDir, dir);
|
|
27
|
+
const { tools, toolNames, deferredToolNames, toolCollisions, toolFailures } = await resolveWorkspaceTools(config, agentDir, dir);
|
|
32
28
|
// The state root: auth/sessions/channel state all derive from it, so FASTAGENT_STATE_DIR moves the
|
|
33
29
|
// whole machine-state home in one knob (a container mounts one volume); the finer overrides below
|
|
34
30
|
// still win for their specific path.
|
|
35
31
|
const stateRoot = resolveStateRoot(dir);
|
|
32
|
+
// The credentials file: project-level by default (under the state root); only resolved here, never
|
|
33
|
+
// created (a missing file reads as not-configured — `fastagent login` creates it).
|
|
34
|
+
const authPath = resolveAuthPath(dir, options.authPath);
|
|
35
|
+
// Self-ignore the state root iff it lands in-tree — which covers everything under it (sessions, auth,
|
|
36
|
+
// every channel's `channels/<kind>` home). HERE, not in the serving opener: every consumer of this
|
|
37
|
+
// assembly can WRITE under the state root (serving: sessions/channels; the session builder: pi's
|
|
38
|
+
// `/login` writing auth.json), so resolving a workspace's state root must make it leak-safe.
|
|
39
|
+
await ensureStateRootSelfIgnored(dir, stateRoot);
|
|
40
|
+
return {
|
|
41
|
+
config,
|
|
42
|
+
configPath,
|
|
43
|
+
modelSpec,
|
|
44
|
+
agentDir,
|
|
45
|
+
stateRoot,
|
|
46
|
+
authPath,
|
|
47
|
+
tools,
|
|
48
|
+
toolNames,
|
|
49
|
+
deferredToolNames,
|
|
50
|
+
toolCollisions,
|
|
51
|
+
toolFailures,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* "Point at a workspace → agent": load the config, resolve model and tools, then L2. Throws a clear
|
|
56
|
+
* error when no model source is set (fail visibly at startup). Returns everything an entry point needs
|
|
57
|
+
* to report what it assembled.
|
|
58
|
+
*/
|
|
59
|
+
export async function createPiAgentFromWorkspace(dir, options = {}) {
|
|
60
|
+
const { config, configPath, modelSpec, agentDir, stateRoot, authPath, tools, toolNames, deferredToolNames, toolCollisions, toolFailures, } = await resolveWorkspaceAssembly(dir, options);
|
|
36
61
|
// Mount the built-in `wake` tool only when BOTH: this is a long-running serve (the poller honors it) AND
|
|
37
62
|
// the author opted into self-scheduling (config.selfSchedule). The workspace's own `wake` wins if defined.
|
|
38
63
|
const mountedTools = withWakeTool(tools, stateRoot, !!options.serving && !!config.selfSchedule);
|
|
39
64
|
const sessionsDir = options.sessionsDir ?? defaultSessionsDir(stateRoot);
|
|
40
65
|
await mkdir(sessionsDir, { recursive: true });
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
66
|
+
const sessions = jsonlSessionStore({ dir: sessionsDir, cwd: dir });
|
|
67
|
+
// The hub is wired HERE because the store is created here: chicken-and-egg otherwise (the hub
|
|
68
|
+
// needs the store; the agent needs the hub's observer). Boundary parts (models/factory/lease)
|
|
69
|
+
// only exist after the assembly below — the hub takes them as a lazy thunk, filled by the
|
|
70
|
+
// assembly's onAssembly callback (assembly completes before this function returns, so every
|
|
71
|
+
// dispatch sees them). An extra caller observer composes after the hub's (TRUSTED seam).
|
|
72
|
+
let boundaryParts;
|
|
73
|
+
const caller = options.observer;
|
|
74
|
+
const wantControl = options.sessionControl ?? (config.sessionControl === true && options.serving === true);
|
|
75
|
+
const hub = wantControl
|
|
76
|
+
? createPiSessionControl({
|
|
77
|
+
sessions,
|
|
78
|
+
boundary: () => boundaryParts,
|
|
79
|
+
// The caller tap's boundary-event half: state_changed/compaction_* originate in the hub
|
|
80
|
+
// and never cross the data plane's observer seam — without this, an audit tap wired here
|
|
81
|
+
// would miss exactly the mutations it most needs to see (set_model).
|
|
82
|
+
tap: caller ? (session, event) => caller(session, event) : undefined,
|
|
83
|
+
})
|
|
84
|
+
: undefined;
|
|
85
|
+
const observer = hub
|
|
86
|
+
? caller
|
|
87
|
+
? (session, event, run) => {
|
|
88
|
+
hub.observer(session, event, run);
|
|
89
|
+
caller(session, event, run);
|
|
90
|
+
}
|
|
91
|
+
: hub.observer
|
|
92
|
+
: caller;
|
|
48
93
|
const { agent, definition } = await createPiAgentFromDefinition(agentDir, {
|
|
49
94
|
model: modelSpec,
|
|
95
|
+
thinkingLevel: config.thinkingLevel,
|
|
50
96
|
cwd: dir,
|
|
51
97
|
tools: mountedTools,
|
|
52
98
|
authPath,
|
|
53
99
|
// Skills are definition-only (the agent is its directory), so dev mirrors deployment exactly.
|
|
54
|
-
sessions
|
|
100
|
+
sessions,
|
|
101
|
+
observer,
|
|
102
|
+
onAssembly: hub
|
|
103
|
+
? (parts) => {
|
|
104
|
+
boundaryParts = {
|
|
105
|
+
lease: parts.lease,
|
|
106
|
+
models: parts.models,
|
|
107
|
+
harnessFactory: parts.harnessFactory,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
: undefined,
|
|
55
111
|
});
|
|
56
112
|
return {
|
|
57
113
|
agent,
|
|
58
114
|
definition,
|
|
115
|
+
sessions,
|
|
116
|
+
sessionControl: hub?.control,
|
|
59
117
|
agentDir,
|
|
60
118
|
config,
|
|
61
119
|
configPath,
|
|
@@ -64,6 +122,7 @@ export async function createPiAgentFromWorkspace(dir, options = {}) {
|
|
|
64
122
|
sessionsDir,
|
|
65
123
|
authPath,
|
|
66
124
|
toolNames,
|
|
125
|
+
deferredToolNames,
|
|
67
126
|
toolCollisions,
|
|
68
127
|
toolFailures,
|
|
69
128
|
};
|
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,4 +1,5 @@
|
|
|
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"`. */
|
|
@@ -13,11 +14,26 @@ export type Routes = Record<string, ChannelHandler>;
|
|
|
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/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)}`);
|